The next generation
postCreate: function() {
aspect.before(this, '_setValues', this.__setValues.bind(this));
...
/**
* Runs before _setValues using aspect.before, set up in postCreate
*/
__setValues: function(values) {
this._removeAddedElements();
if (values !== undefined) {
values = this.parseDefinitionValues(values);
if ('seed_provider' in values) {
var seeds;
try {
seeds = values.seed_provider[0].parameters[0].seeds;
} catch (e) {}
values.seeds = seeds;
delete values.seed_provider;
}
}
return [values];
}
/**
* Populate form based on cluster conf
*/
_populate: function() {
var values = {},
config = this.cluster_config || {};
if (config.cassandra) {
['seed_hosts', 'ssl_validate', 'ssl_ca_certs'].forEach(function(prop) {
values[prop] = config.cassandra[prop] || '';
});
lang.mixin(values, {
'thrift_port': config.cassandra.api_port || undefined, // want field's default value
'thrift_username': config.cassandra.username || '',
'thrift_password': config.cassandra.password || ''});
}
if (config.jmx) {
lang.mixin(values, {
'jmx_port': config.jmx.port || undefined, // want field's default value
'jmx_username': config.jmx.username || '',
'jmx_password': config.jmx.password || ''});
}
if (config.agents) {
lang.mixin(values, {
'ssl_keystore': config.agents.ssl_keystore || '',
'ssl_keystore_password': config.agents.ssl_keystore_password || ''});
}
if (config.kerberos) {
values.kerberos_service_name = config.kerberos.default_service || '';
}
this._setValues(values);
this._toggleCreds(values.jmx_username || values.thrift_username);
this.kerberosCheckboxNode.set('checked', !!values.kerberos_service_name);
this.sslCheckboxNode.set('checked', !!values.ssl_ca_certs);
}
var prompt = new Prompt({
title: 'Flush?',
message: phrases.flushWarning,
buttons: ['Flush']
});
prompt.show().then(function() {
this.executeNodeOp(
this.cluster_ds.flushNodeKeyspace(
this.getNodeSelection(),
ksname,
column_families,
true),
phrases.flushSuccess,
phrases.flushError,
phrases.flushStarted
);
}.bind(this));
var prompt = new Simple({
title: 'Flush?',
message: phrases.flushWarning,
button_label: 'Flush'
});
prompt.on('confirm', function() {
this.executeNodeOp(
this.cluster_ds.flushNodeKeyspace(
this.getNodeSelection(),
ksname,
column_families,
true),
phrases.flushSuccess,
phrases.flushError,
phrases.flushStarted
);
}.bind(this));
prompt.show();
Trivial Example
var ChangePasswordDialog = declare([
ChangePassword,
_FormDialogMixin,
_SendValidationErrorsToPrompt
], {
title: phrases.changePwdTitle
});
...
new ChangePasswordDialog({
resolutionAction: function(value_object) {
return this.opsc_datasource.updateUser(this._meta.username, value_object.values)
.then(function() {
return new NotifyPrompt({
title: phrases.changePwdSuccessTitle,
message: phrases.changePwdSuccessMessage
}).show();
});
}.bind(this),
resolutionActionFailure: function(err) {
ui.error({prefix: phrases.changePwdUpdateError, error: err});
}
}).show();
new ChangePasswordDialog({
opsc_ds: this.opsc_datasource,
destroy_on_hide: true,
username: this._meta.username
}).show();
postCreate: function() {
this.setContent(this.domNode);
this.inherited(arguments);
this.own(
this.on('submit', this._updatePassword.bind(this)),
aspect.after(this.passwordNode, 'validator', function(is_valid) {
var values = this.getValues();
return is_valid && values.password === values.password_2;
}.bind(this))
);
},
_updatePassword: function() {
var values = this.getValues();
return this.opsc_ds.updateUser(this.username, values)
.then(function() {
new Notify({
title: phrases.changePwdSuccessTitle,
message: phrases.changePwdSuccessMessage
}).show();
this.hide();
}.bind(this)).otherwise(function(err) {
ui.error({prefix: phrases.changePwdUpdateError, error: err});
}.bind(this));
}
// vim:ts=4:sw=4:et:tw=100:
//>> pure-amd
define([
'dojo/_base/declare',
'dojo/text!./changepassword.html',
'ripcord/forms/_form',
'ripcord/phrases',
'ripcord/widgets/_phrasesmixin',
// template
'dijit/form/ValidationTextBox'
], function(declare, template, _Form, phrases, _PhrasesMixin) {
/**
* Widget that shows the change password dialog
*/
return declare([_Form, _PhrasesMixin], {
/** @inheritDoc */
templateString: template,
getValidation: function(values) {
if (values.password !== values.password_2) {
return phrases.changePwdPasswordsDoNotMatch;
}
}
});});
ChangePasswordDialog.js
77 lines of code
Main.js
5 lines of code
_Form.js
413 lines of code
_TemplatedDialogMixin.js
95 lines of code
_FormDialogMixin.js
58 lines of code
ChangePassword.js
28 lines of code
Main.js
14 lines of code
_Form.js
255 lines of code
_TemplatedDialogMixin.js
274 lines of code
_FormDialogMixin.js
34 lines of code
_templateddialogmixin.js
Array of labels for buttons!
new DialogedClass({
buttons: ['Foo', 'Bar']
})
Can also be an array of Objects with more granular control of attributes -- see tests
A method (returning Promise|Anything) defining what to do when a resolving button is pressed.
new DialogedClass({
buttons: ["Save"],
resolutionAction: function() {
return asynchronousTask();
}
});
Dialog will remain open until promise returned by fn() or when(fn()) succeeds
resolutionActions is an object which gives you action-level control over your API calls
new DialogedClass({
buttons: ['Save', 'Clone'],
resolutionActions: {
'Save' : function() {
return asynchronousSaveTask();
},
'Clone' : function() {
return asynchronousCloneTask();
}
}
});
You generally won't need to use this unless you're making a non-buttoned Dialog
buttons: [],
// public:
/** @inheritDoc */
startup: function() {
this.inherited(arguments);
this.own(
this.provNode.on('click', this.whenResolve.bind(this, 'createnew')),
this.addNode.on('click', this.whenResolve.bind(this, 'manageexisting'))
);
}
This allows you to hook your own template buttons into the resolve/rejection flow (and resolutionActions)
Unless you're writing a mixin for use with _TemplatedDialogMixin, it's not a good idea.
You should almost certainly call
somewhere in your override if you do.
this.inherited(arguments);
whenResolve: function(action) {
var isInvalid = this.isInvalid();
if (isInvalid) {
this.handleValidationError(isInvalid);
this._progress({
isValidationError: true,
values: isInvalid
});
return;
}
var values = this.getValues();
values.action = action;
this.inherited(arguments, [action, values]);
},
_Form
Handle transformation of data for your form here.
Forms like flat objects of name/value pairs
APIs like nested objects.
Meld the two together so each side gets what they want.
transformDataOut: function(form_values) {
var values = lang.mixin({}, form_values);
if (form_values.exclude_foo) {
delete values.foo;
}
return values;
},
transformDataIn: function(values) {
var form_values = lang.mixin({}, values);
if (!values.hasOwnProperty('foo')) {
form_values.exclude_foo = true;
}
return form_values;
}
Try to make these complementary functions, it will make your life a lot easier.
Also, don't put validation in here. Just convert values.
Odds are, you shouldn't really need these, but they exist. get and setValues both go through their respective transformation functions
form.getValues() === { "my" : "transformed values" }
form.setValues({"my": "incoming values"});
// You REALLY shouldn't need these
form._getValues() === { "my_textbox": "incoming values" }
form._setValues({"my_textbox": "transformed values"})
Not much changed from before -- works off of untransformed values
form._getValues() // { 'my_textbox' : 'some value' }
form.isDirty() // false
form.findChildByName('my_textbox').set('value', 'Something else');
form.isDirty() // true
form.findChildByName('my_textbox').set('value', 'some value');
form.isDirty() // false
form._setValues({'my_textbox':'Something else'});
form.isDirty() // false -- _setValues resets clean values
Lets you initialize values with defaults if you like
new DialogedClass({
initializeFields: function(form_values) {
form_values.my_textbox = 'Some other default for whatever reason';
return form_values;
}
});
Throw some custom validation logic in here
getValidation: function(values) {
if (values.password !== values.password_2) {
return phrases.changePwdPasswordsDoNotMatch;
}
}
Returns either a string or an array of strings which represent the error messages associated with the form
Returns false if the form is valid
Returns an array of error messages associated with the form, including custom and standard messages if the form is invalid
form = new DialogedClass({
getValidation: function(form_values) {
if (form_values.my_required_textbox !== 'foo') {
return "'my_required_textbox' must be 'foo'";
}
}
});
form.isInvalid(); // ["my_required_textbox is required", "'my_required_textbox' must be 'foo'"]
form.findChildByName('my_required_textbox').set('value', 'bar');
form.isInvalid(); // ["'my_required_textbox' must be 'foo'"]
form.findChildByName('my_required_textbox').set('value', 'foo');
form.isInvalid(); // false
Nested Forms!
(see tests)
_FormDialogMixin
What do you do in a form when the user tries to submit, but the _Form isInvalid()?
new DialogedClass({
handleValidationError: function(errors) {
new NotifyPrompt({
title: phrases.validationMessageTitle,
escapeHtml: false,
message: phrases.validationMessageBlurb + '<ul><li>'+errors.join("</li><li>")+'</li></ul>'
}).show();
}
});
Neatly abstracted away to _SendValidationErrorsToPrompt mixin
(OPSC-3510)
action-value object
Sometimes it's important to know some metadata about the form being submitted
{
action: 'Save',
values: {
"these": "are the",
"fully": ["transformed", "and", "validated", "values"]
}
}
This gets returned from the show() promise so the consumer knows what action was taken
buttons can be specified as objects, we already know:
new DialogedClass({
buttons: [{label: 'Save This Information', action: 'Save'}]
});
But with _FormDialogMixin, we can specify if the validation should be processed
new DialogedClass({
buttons: [{label: 'Save This Information', action: 'Save', validate: false}]
});
EDIT I could have sworn I did this, but it doesn't look like it now. It should happen.