From 796eed03dc492fbe62c460305087250b719f09ab Mon Sep 17 00:00:00 2001 From: YUI Builder Date: Wed, 15 Jun 2011 12:14:33 -0700 Subject: [PATCH] gallery-2011.06.15-19-18 ghinch gallery-form --- src/gallery-form/js/button.js | 57 ++++++-- src/gallery-form/js/choice-field.js | 41 ++++-- src/gallery-form/js/file-field.js | 16 +-- src/gallery-form/js/form-field.js | 192 +++++++++++++++++++++---- src/gallery-form/js/form.js | 2 +- src/gallery-form/js/reset.js | 2 +- src/gallery-form/js/select-field.js | 80 +++++++---- src/gallery-form/js/textarea-field.js | 26 +--- src/gallery-form/tests/button.js | 24 ++++ src/gallery-form/tests/choice-field.js | 74 ++++++++++ src/gallery-form/tests/form-field.js | 86 ++++++++++- src/gallery-form/tests/select-field.js | 28 ++++ 12 files changed, 500 insertions(+), 128 deletions(-) diff --git a/src/gallery-form/js/button.js b/src/gallery-form/js/button.js index 4077bc3c4e..888f1ff01b 100644 --- a/src/gallery-form/js/button.js +++ b/src/gallery-form/js/button.js @@ -1,13 +1,7 @@ Y.FormButton = Y.Base.create('button-field', Y.FormField, [Y.WidgetChild], { - _renderButtonNode : function () { - var contentBox = this.get('contentBox'), bn; - - bn = Y.Node.create(Y.FormButton.NODE_TEMPLATE); - contentBox.appendChild(bn); - this._fieldNode = bn; - }, - _syncLabelNode: function () {}, + FIELD_TEMPLATE : '', + LABEL_TEMPLATE: '', _syncFieldNode : function () { this._fieldNode.setAttrs({ @@ -23,17 +17,26 @@ Y.FormButton = Y.Base.create('button-field', Y.FormField, [Y.WidgetChild], { return; } - var oc = this.get('onclick'); Y.Event.purgeElement(this._fieldNode, true, 'click'); - Y.on('click', Y.bind(oc.fn, oc.scope, true), this._fieldNode); + Y.on('click', Y.bind(this._promptConfirm, this), this._fieldNode); }, - renderUI : function () { - this._renderButtonNode(); + _promptConfirm: function(event) { + event.preventDefault(); + var message = this.get("message"), + onclick = this.get("onclick"); + + if (message) { + if (!this.get("confirm")(message)) { + return; + } + } + onclick.fn.apply(onclick.scope); }, bindUI : function () { this.after('onclickChange', Y.bind(this._setClickHandler, this, true)); + this.after('disabledChange', this._syncDisabled, this); this._setClickHandler(); } }, { @@ -59,8 +62,32 @@ Y.FormButton = Y.Base.create('button-field', Y.FormField, [Y.WidgetChild], { val.argument = val.argument || {}; return val; } - } - }, + }, + + /** + * @attribute message + * @type String + * @default null + * @description Optional confirmation message to be passed to the + * confirm function. + */ + message: { + validator : Y.Lang.isString, + value: null + }, - NODE_TEMPLATE : '' + /** + * @attribute confirm + * @type Function + * @default null + * @description Optional confirmation function called when the button + * is clicked. It will be be passed the string set in the 'message' + * attribute. If it returns 'true' the the onclick handler will be + * called, otherwise it will be skipped. + */ + confirm: { + validator : Y.Lang.isFunction, + value: null + } + } }); diff --git a/src/gallery-form/js/choice-field.js b/src/gallery-form/js/choice-field.js index 0238f04c36..404c3e6f4e 100644 --- a/src/gallery-form/js/choice-field.js +++ b/src/gallery-form/js/choice-field.js @@ -7,6 +7,11 @@ * selection of choices */ Y.ChoiceField = Y.Base.create('choice-field', Y.FormField, [Y.WidgetParent, Y.WidgetChild], { + + LABEL_TEMPLATE: '', + SINGLE_CHOICE: Y.RadioField, + MULTI_CHOICE: Y.CheckboxField, + /** * @method _validateChoices * @protected @@ -45,22 +50,16 @@ Y.ChoiceField = Y.Base.create('choice-field', Y.FormField, [Y.WidgetParent, Y.Wi return true; }, - _renderLabelNode: function() { - var contentBox = this.get('contentBox'), - titleNode = Y.Node.create(''); - - titleNode.set('innerHTML', this.get('label')); - contentBox.appendChild(titleNode); - - this._labelNode = titleNode; - }, - _renderFieldNode: function() { var contentBox = this.get('contentBox'), - choices = this.get('choices'), - multiple = this.get('multi'), - fieldType = (multiple === true ? Y.CheckboxField: Y.RadioField); + parent = contentBox.one("." + this.FIELD_CLASS), + choices = this.get('choices'), + multiple = this.get('multi'), + fieldType = (multiple === true ? this.MULTI_CHOICE: this.SINGLE_CHOICE); + if (!parent) { + parent = contentBox; + } Y.Array.each(choices, function(c, i, a) { var cfg = { @@ -71,9 +70,9 @@ Y.ChoiceField = Y.Base.create('choice-field', Y.FormField, [Y.WidgetParent, Y.Wi }, field = new fieldType(cfg); - field.render(contentBox); + field.render(parent); }, this); - this._fieldNode = contentBox.all('input'); + this._fieldNode = parent.all('input'); }, _syncFieldNode: function() { @@ -91,6 +90,17 @@ Y.ChoiceField = Y.Base.create('choice-field', Y.FormField, [Y.WidgetParent, Y.Wi } }, + /** + * @method _afterChoiceChange + * @description When the available choices for the choice field change, + * the old ones are removed and the new ones are rendered. + */ + _afterChoicesChange: function(event) { + var contentBox = this.get("contentBox"); + contentBox.all(".yui3-form-field").remove(); + this._renderFieldNode(); + }, + clear: function() { this._fieldNode.each(function(node, index, list) { node.set('checked', false); @@ -114,6 +124,7 @@ Y.ChoiceField = Y.Base.create('choice-field', Y.FormField, [Y.WidgetParent, Y.Wi this.set('value', value); }, this)); + this.after('choicesChange', this._afterChoicesChange); } }, diff --git a/src/gallery-form/js/file-field.js b/src/gallery-form/js/file-field.js index 87005b4e3f..4139300c5d 100644 --- a/src/gallery-form/js/file-field.js +++ b/src/gallery-form/js/file-field.js @@ -6,18 +6,4 @@ * @description A file field node */ -Y.FileField = Y.Base.create('file-field', Y.FormField, [Y.WidgetChild], { - _renderFieldNode : function () { - var contentBox = this.get('contentBox'), - field = contentBox.one('#' + this.get('id')); - - if (!field) { - field = Y.Node.create(Y.FileField.FILE_INPUT_TEMPLATE); - contentBox.appendChild(field); - } - - this._fieldNode = field; - } -}, { - FILE_INPUT_TEMPLATE : '' -}); +Y.FileField = Y.Base.create('file-field', Y.FormField, [Y.WidgetChild]); diff --git a/src/gallery-form/js/form-field.js b/src/gallery-form/js/form-field.js index 65bf01a6ae..7233290aa9 100644 --- a/src/gallery-form/js/form-field.js +++ b/src/gallery-form/js/form-field.js @@ -10,6 +10,68 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi toString: function() { return this.name; }, + + /** + * @property FormField.FIELD_TEMPLATE + * @type String + * @description Template used to render the field node + */ + FIELD_TEMPLATE : '', + + /** + * @property FormField.FIELD_CLASS + * @type String + * @description CSS class used to locate a placeholder for + * the field node and style it. + */ + FIELD_CLASS : 'field', + + /** + * @property FormField.LABEL_TEMPLATE + * @type String + * @description Template used to draw a label node + */ + LABEL_TEMPLATE : '', + + /** + * @property FormField.LABEL_CLASS + * @type String + * @description CSS class used to locate a placeholder for + * the label node and style it. + */ + LABEL_CLASS : 'label', + + /** + * @property FormField.HINT_TEMPLATE + * @type String + * @description Optionally a template used to draw a hint node. Derived + * classes can use it to provide additional information about the field + */ + HINT_TEMPLATE : '', + + /** + * @property FormField.HINT_CLASS + * @type String + * @description CSS class used to locate a placeholder for + * the hint node and style it. + */ + HINT_CLASS : 'hint', + + /** + * @property FormField.ERROR_TEMPLATE + * @type String + * @description Template used to draw an error node + */ + ERROR_TEMPLATE : '', + + /** + * @property FormField.ERROR_CLASS + * @type String + * @description CSS class used to locate a placeholder for + * the error node and style it. + */ + ERROR_CLASS : 'error', + /** * @property _labelNode * @protected @@ -18,6 +80,14 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi */ _labelNode: null, + /** + * @property _hintNode + * @protected + * @type Object + * @description The hint node with extra text describing the field + */ + _hintNode : null, + /** * @property _fieldNode * @protected @@ -104,6 +174,35 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi return (valMap[val] ? valMap[val] : val); }, + /** + * @method _renderNode + * @protected + * @description Helper method to render new nodes, possibly replacing + * markup placeholders. + */ + _renderNode : function (nodeTemplate, nodeClass, nodeBefore) { + if (!nodeTemplate) { + return null; + } + var contentBox = this.get('contentBox'), + node = Y.Node.create(nodeTemplate), + placeHolder = contentBox.one('.' + nodeClass); + + node.addClass(nodeClass); + + if (placeHolder) { + placeHolder.replace(node); + } else { + if (nodeBefore) { + contentBox.insertBefore(node, nodeBefore); + } else { + contentBox.appendChild(node); + } + } + + return node; + }, + /** * @method _renderLabelNode * @protected @@ -114,13 +213,24 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi labelNode = contentBox.one('label'); if (!labelNode || labelNode.get('for') != this.get('id')) { - labelNode = Y.Node.create(Y.FormField.LABEL_TEMPLATE); - contentBox.appendChild(labelNode); + labelNode = this._renderNode(this.LABEL_TEMPLATE, this.LABEL_CLASS); } this._labelNode = labelNode; }, + /** + * @method _renderHintNode + * @protected + * @description Draws the hint node into the contentBox. If a node is + * found in the contentBox with class HINT_CLASS, it will be + * considered a markup placeholder and replaced with the hint node. + */ + _renderHintNode : function () { + this._hintNode = this._renderNode(this.HINT_TEMPLATE, + this.HINT_CLASS); + }, + /** * @method _renderFieldNode * @protected @@ -131,8 +241,7 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi field = contentBox.one('#' + this.get('id')); if (!field) { - field = Y.Node.create(Y.FormField.INPUT_TEMPLATE); - contentBox.appendChild(field); + field = this._renderNode(this.FIELD_TEMPLATE, this.FIELD_CLASS); } this._fieldNode = field; @@ -144,16 +253,35 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi * @description Syncs the the label node and this instances attributes */ _syncLabelNode: function() { + var label = this.get('label'), + required = this.get('required'), + requiredLabel = this.get('requiredLabel'); if (this._labelNode) { - this._labelNode.setAttrs({ - innerHTML: this.get('label') - }); + this._labelNode.set("text", ""); + if (label) { + this._labelNode.append("" + label + ""); + } + if (required && requiredLabel) { + this._labelNode.append(" "); + this._labelNode.append("" + requiredLabel + ""); + } this._labelNode.setAttribute('for', this.get('id') + Y.FormField.FIELD_ID_SUFFIX); } }, /** - * @method _syncLabelNode + * @method _syncHintNode + * @protected + * @description Syncs the hintNode + */ + _syncHintNode : function () { + if (this._hintNode) { + this._hintNode.set("text", this.get("hint")); + } + }, + + /** + * @method _syncFieldNode * @protected * @description Syncs the fieldNode and this instances attributes */ @@ -215,12 +343,9 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi * @description Adds an error node with the supplied message */ _showError: function(errMsg) { - var contentBox = this.get('contentBox'), - errorNode = Y.Node.create('' + errMsg + ''); - - errorNode.addClass('error'); - contentBox.insertBefore(errorNode, this._labelNode); + var errorNode = this._renderNode(this.ERROR_TEMPLATE, this.ERROR_CLASS, this._labelNode); + errorNode.set("text", errMsg); this._errorNode = errorNode; }, @@ -231,8 +356,7 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi */ _clearError: function() { if (this._errorNode) { - var contentBox = this.get('contentBox'); - contentBox.removeChild(this._errorNode); + this._errorNode.remove(); this._errorNode = null; } }, @@ -304,6 +428,7 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi renderUI: function() { this._renderLabelNode(); this._renderFieldNode(); + this._renderHintNode(); }, bindUI: function() { @@ -360,6 +485,7 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi syncUI: function() { this.get('boundingBox').removeAttribute('tabindex'); this._syncLabelNode(); + this._syncHintNode(); this._syncFieldNode(); this._syncError(); this._syncDisabled(); @@ -424,6 +550,17 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi validator: Y.Lang.isString }, + /** + * @attribute hint + * @type String + * @default "" + * @description Extra text explaining what the field is about. + */ + hint : { + value : '', + validator : Y.Lang.isString + }, + /** * @attribute validator * @type Function @@ -475,6 +612,17 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi validateInline: { value: false, validator: Y.Lang.isBoolean + }, + + /** + * @attribute requiredLabel + * @type String + * @description Text to append to the labal caption for a required + * field, by default nothing will be appended. + */ + requiredLabel : { + value : '', + validator : Y.Lang.isString } }, @@ -659,20 +807,6 @@ Y.FormField = Y.Base.create('form-field', Y.Widget, [Y.WidgetParent, Y.WidgetChi */ INVALID_SPECIAL_CHARS: "Please use only letters and numbers", - /** - * @property FormField.INPUT_TEMPLATE - * @type String - * @description Template used to draw an input node - */ - INPUT_TEMPLATE: '', - - /** - * @property FormField.LABEL_TEMPLATE - * @type String - * @description Template used to draw a label node - */ - LABEL_TEMPLATE: '', - /** * @property FormField.REQUIRED_ERROR_TEXT * @type String diff --git a/src/gallery-form/js/form.js b/src/gallery-form/js/form.js index c28b2046b4..7c7d9016f8 100644 --- a/src/gallery-form/js/form.js +++ b/src/gallery-form/js/form.js @@ -246,6 +246,7 @@ Y.Form = Y.Base.create('form', Y.Widget, [Y.WidgetParent], { var formAction = this.get('action'), formMethod = this.get('method'), submitViaIO = this.get('submitViaIO'), + io = this.get("io"), transaction, cfg; @@ -258,7 +259,6 @@ Y.Form = Y.Base.create('form', Y.Widget, [Y.WidgetParent], { } }; - var io = this.get("io"); transaction = io(formAction, cfg); this._ioIds[transaction.id] = transaction; } else { diff --git a/src/gallery-form/js/reset.js b/src/gallery-form/js/reset.js index 170fe433c1..ddfdbecac9 100644 --- a/src/gallery-form/js/reset.js +++ b/src/gallery-form/js/reset.js @@ -6,5 +6,5 @@ * @description A reset button */ Y.ResetButton = Y.Base.create('reset-button', Y.FormField, [Y.WidgetChild], { - _renderLabelNode: function() {} + LABEL_TEMPLATE: '' }); \ No newline at end of file diff --git a/src/gallery-form/js/select-field.js b/src/gallery-form/js/select-field.js index 466366c1e5..1869f6c687 100644 --- a/src/gallery-form/js/select-field.js +++ b/src/gallery-form/js/select-field.js @@ -6,22 +6,23 @@ * @description A select field node */ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y.WidgetChild], { + + FIELD_TEMPLATE : '', + + /** + * @property SelectField.DEFAULT_OPTION_TEXT + * @type String + * @description The display title of the default choice in the select box + */ + DEFAULT_OPTION_TEXT : 'Choose one', + /** * @method _renderFieldNode * @protected * @description Draws the select node into the contentBox */ _renderFieldNode: function() { - var contentBox = this.get('contentBox'), - field = contentBox.one('#' + this.get('id')); - - if (!field) { - field = Y.Node.create(Y.SelectField.NODE_TEMPLATE); - contentBox.appendChild(field); - } - - this._fieldNode = field; - + Y.SelectField.superclass.constructor.superclass._renderFieldNode.apply(this, arguments); this._renderOptionNodes(); }, @@ -57,6 +58,7 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. Y.SelectField.superclass.constructor.superclass._syncFieldNode.apply(this, arguments); this._fieldNode.setAttrs({ + size : this.get('size'), multiple: (this.get('multi') === true ? 'multiple': '') }); }, @@ -75,7 +77,7 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. if (useDefaultOption === true) { choices.unshift({ - label: Y.SelectField.DEFAULT_OPTION_TEXT, + label : this.DEFAULT_OPTION_TEXT, value: '' }); } @@ -99,6 +101,18 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. this); }, + /** + * @method _afterChoiceChange + * @description When the available options for the select field change, + * the old ones are removed and the new ones are rendered. + */ + _afterChoicesChange: function(evt) { + var options = this._fieldNode.all("option"); + options.remove(); + this._renderOptionNodes(); + this._syncOptionNodes(); + }, + /** * @method clear * @description Restores the selected option to the default @@ -109,6 +123,7 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. bindUI: function() { Y.SelectField.superclass.constructor.superclass.bindUI.apply(this, arguments); + this.after('choicesChange', this._afterChoicesChange); }, syncUI: function() { @@ -117,13 +132,6 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. } }, { - /** - * @property SelectField.NODE_TEMPLATE - * @type String - * @description Template used to draw a select node - */ - NODE_TEMPLATE: '', - /** * @property SelectField.OPTION_TEMPLATE * @type String @@ -131,13 +139,6 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. */ OPTION_TEMPLATE: '', - /** - * @property SelectField.DEFAULT_OPTION_TEXT - * @type String - * @description The display title of the default choice in the select box - */ - DEFAULT_OPTION_TEXT: 'Choose one', - ATTRS: { /** * @attribute useDefaultOption @@ -149,6 +150,35 @@ Y.SelectField = Y.Base.create('select-field', Y.ChoiceField, [Y.WidgetParent, Y. useDefaultOption: { validator: Y.Lang.isBoolean, value: true + }, + + /** + * @attribute choices + * @type Array + * @description The choices to render into this field + */ + choices: { + validator: function(val) { + if (this.get("useDefaultOption") && + Y.Lang.isArray(val) && + val.length === 0) { + // Empty arrays are okay if useDefaultOption is 'true' + return true; + } else { + return this._validateChoices(val); + } + } + }, + + /** + * @attribute size + * @type String + * @default 0 + * @description Value of 'size' attribute of the select element. + */ + size : { + validator : Y.Lang.isString, + value : '0' } } }); diff --git a/src/gallery-form/js/textarea-field.js b/src/gallery-form/js/textarea-field.js index c97645d1c8..05d5103ec0 100644 --- a/src/gallery-form/js/textarea-field.js +++ b/src/gallery-form/js/textarea-field.js @@ -6,29 +6,7 @@ * @description A hidden field node */ Y.TextareaField = Y.Base.create('textarea-field', Y.FormField, [Y.WidgetChild], { - _renderFieldNode : function () { - var contentBox = this.get('contentBox'), - field = contentBox.one('#' + this.get('id')); - - if (!field) { - field = Y.Node.create(Y.TextareaField.NODE_TEMPLATE); - field.setAttrs({ - name : this.get('name'), - innerHTML : this.get('value') - }); - contentBox.appendChild(field); - } - field.setAttribute('tabindex', Y.FormField.tabIndex); - Y.FormField.tabIndex++; - - this._fieldNode = field; - } -}, { - /** - * @property TextareaField.NODE_TEMPLATE - * @type String - * @description Template used to draw a textarea node - */ - NODE_TEMPLATE : '' + FIELD_TEMPLATE : '' + }); diff --git a/src/gallery-form/tests/button.js b/src/gallery-form/tests/button.js index b249ad0045..3c99e1564d 100644 --- a/src/gallery-form/tests/button.js +++ b/src/gallery-form/tests/button.js @@ -19,6 +19,7 @@ suite.add(new Y.Test.Case({ testRenderUI: function() { var contentBox = this.button.get("contentBox"); Y.Assert.isNotNull(contentBox.one("button")); + Y.Assert.isNull(contentBox.one("label")); }, // The 'onclick' attribute can be set to an event handler for the @@ -50,6 +51,29 @@ suite.add(new Y.Test.Case({ var buttonNode = contentBox.one("button"); Y.Assert.areEqual("Press me", buttonNode.get("text")); Y.Assert.areEqual(this.button.get("id") + "-field", buttonNode.get("id")); + }, + + // If the message attribute is set, the button prompts for confirmation. + testOnClickWithConfirm: function() { + var messages = []; + this.button.set("message", "Really?"); + this.button.set("confirm", function(message) { + messages.push(message); + return true; + }); + var contentBox = this.button.get("contentBox"); + var buttonNode = contentBox.one("button"); + this.button.set("onclick", {fn: function() {}}); + buttonNode.simulate("click"); + Y.ArrayAssert.itemsAreEqual(["Really?"], messages); + }, + + // It's possible to toggle the disabled state of a FormButton. + testDisable: function() { + this.button.disable(); + var contentBox = this.button.get("contentBox"); + var buttonNode = contentBox.one("button"); + Y.Assert.areEqual("disabled", buttonNode.getAttribute("disabled")); } })); diff --git a/src/gallery-form/tests/choice-field.js b/src/gallery-form/tests/choice-field.js index 8ae2b7ee87..3a27a1fdb1 100644 --- a/src/gallery-form/tests/choice-field.js +++ b/src/gallery-form/tests/choice-field.js @@ -72,6 +72,80 @@ suite.add(new Y.Test.Case({ this.choice.clear(); Y.ArrayAssert.itemsAreEqual([false, false], inputs.get("checked")); }, + + // When the 'choices' attribute changes, the widget is refreshed. + testChangeChoices: function() { + this.choice.set("choices", [{label: "Foo", value: "foo"}, + {label: "Bar", value: "bar"}]); + this.choice.render(); + this.choice.set("choices", [{label: "Egg", value: "egg"}]); + var contentBox = this.choice.get("contentBox"); + var radios = contentBox.all("> div"); + Y.Assert.areEqual(1, radios.size()); + var label = radios.item(0).one("label"); + var input = radios.item(0).one("input"); + Y.Assert.areEqual("Egg", label.get("text")); + Y.Assert.areEqual("egg", input.get("value")); + } +})); + + +var CustomRadioField = function(config) { + CustomRadioField.superclass.constructor.apply(this, arguments); +}; + +CustomRadioField.NAME = "radio-field"; + +Y.extend(CustomRadioField, Y.RadioField, { + + CONTENT_TEMPLATE: ["
", + " ", + " ", + "
"].join("") +}); + + +var CustomChoiceField = function(config) { + CustomChoiceField.superclass.constructor.apply(this, arguments); +}; + +CustomChoiceField.NAME = "custom-choice-field"; + +Y.extend(CustomChoiceField, Y.ChoiceField, { + + SINGLE_CHOICE: CustomRadioField, + CONTENT_TEMPLATE: ["
", + "
", + "
", + "
", + "
"].join("") +}); + + +suite.add(new Y.Test.Case({ + + name: "CustomChoiceFieldTest", + + setUp: function() { + var boundingBox = Y.Node.create("
"); + var scaffolding = Y.one("#scaffolding"); + scaffolding.setContent(boundingBox); + this.field = new CustomChoiceField({boundingBox: boundingBox, + name: "some-field", + choices: [{label: "Foo", value: "foo"}, + {label: "Bar", value: "bar"}]}); + this.field.render(); + }, + + // If a placeholder node is found for the field node, the choice widgets + // are appended to it. + testRenderUI: function() { + var contentBox = this.field.get("contentBox"), + parent = contentBox.one(".field"), + choices = parent.get("children"); + Y.Assert.areEqual(2, choices.size()); + Y.Assert.isTrue(choices.item(0).one("div").hasClass("custom-choice")); + } })); Y.Test.Runner.add(suite); diff --git a/src/gallery-form/tests/form-field.js b/src/gallery-form/tests/form-field.js index 2277ea5bc8..58e7c6abd2 100644 --- a/src/gallery-form/tests/form-field.js +++ b/src/gallery-form/tests/form-field.js @@ -53,9 +53,10 @@ suite.add(new Y.Test.Case({ var contentBox = this.field.get("contentBox"); var error = contentBox.one("label").previous(); Y.Assert.areEqual("span", error.get("nodeName").toLowerCase()); + Y.Assert.isTrue(error.hasClass("error")); Y.Assert.areEqual("Bad value", error.get("text")); this.field.set("error", null); - Y.Assert.isNull(contentBox.one("span")); + Y.Assert.isNull(contentBox.one("span.error")); }, // If the 'validateInline' attribute is set, the field value is validated @@ -68,12 +69,12 @@ suite.add(new Y.Test.Case({ var input = contentBox.one("input"); input.set("value", "foo"); input.simulate("blur"); - Y.Assert.isNotNull(contentBox.one("span")); + Y.Assert.isNotNull(contentBox.one("span.error")); this.field.set("error", null); this.field.set("validateInline", false); input.set("value", "bar"); input.simulate("blur"); - Y.Assert.isNull(contentBox.one("span")); + Y.Assert.isNull(contentBox.one("span.error")); }, // If the 'validateInline' attribute is set, the field value is validated @@ -101,9 +102,88 @@ suite.add(new Y.Test.Case({ Y.Assert.areEqual("nice-field", input.get("name")); Y.Assert.areEqual("Nice value", input.get("value")); Y.Assert.areEqual(id + "-field", input.get("id")); + }, + + // With the 'requiredLabel' attribute it's possible to specify some + // custom text to be appended to the label caption for required fields. + testRequiredLabel: function() { + this.field.set("required", true); + this.field.set("requiredLabel", "(Required)"); + this.field.set("label", "Nice field"); + this.field.syncUI(); + var contentBox = this.field.get("contentBox"); + var label = contentBox.one("label"); + var required = label.one("span.required"); + Y.Assert.areEqual("(Required)", required.get("text")); + Y.Assert.isTrue(required.hasClass("required")); } })); + +var CustomFormField = function(config) { + CustomFormField.superclass.constructor.apply(this, arguments); +}; + +CustomFormField.NAME = "custom-form-field"; + +Y.extend(CustomFormField, Y.FormField, { + + CONTENT_TEMPLATE: ["
", + " ", + "
", + "
", + " ", + "
", + "
", + " ", + "
", + "
", + " ", + "
"].join(""), + + HINT_TEMPLATE: "
" +}); + + +suite.add(new Y.Test.Case({ + + name: "CustomFormFieldTest", + + setUp: function() { + var boundingBox = Y.Node.create("
"); + var scaffolding = Y.one("#scaffolding"); + scaffolding.setContent(boundingBox); + this.field = new CustomFormField({boundingBox: boundingBox, + name: "some-field", + label: "Some field", + hint: "Very nice field", + required: true, + requiredLabel: "(Required)", + value: "foo"}); + this.field.render(); + }, + + // If placeholders nodes are found in the content box markup, they + // are replaced with the relevant form field nodes. + testRenderUI: function() { + var contentBox = this.field.get("contentBox"), + contentBoxChildren = contentBox.get("children"), + label = contentBoxChildren.item(0), + field = contentBoxChildren.item(1), + hint = contentBoxChildren.item(2); + Y.Assert.areEqual("label", label.get("nodeName").toLowerCase()); + Y.Assert.areEqual("Some field", label.one("span.caption").get("text")); + Y.Assert.areEqual("(Required)", label.one("span.required").get("text")); + Y.Assert.areEqual("div", field.get("nodeName").toLowerCase()); + Y.Assert.areEqual("foo", field.one("input.field").get("value")); + Y.Assert.areEqual("div", hint.get("nodeName").toLowerCase()); + Y.Assert.areEqual("Very nice field", hint.get("text")); + this.field.set("error", "Invalid"); + Y.Assert.areEqual("Invalid", field.one("span.error").get("text")); + } +})); + + Y.Test.Runner.add(suite); Y.Test.Runner.run(); diff --git a/src/gallery-form/tests/select-field.js b/src/gallery-form/tests/select-field.js index 8e7c01aa5f..869729fe97 100644 --- a/src/gallery-form/tests/select-field.js +++ b/src/gallery-form/tests/select-field.js @@ -21,11 +21,13 @@ suite.add(new Y.Test.Case({ field, options; this.select.set("name", "some-field"); + this.select.set("size", "2"); this.select.set("choices", [{label: "Foo", value: "foo"}, {label: "Bar", value: "bar"}]); this.select.render(); field = contentBox.one("select"); Y.Assert.areEqual("some-field", field.get("name")); + Y.Assert.areEqual("2", field.get("size")); options = contentBox.all("option"); Y.Assert.areEqual("Choose one", options.item(0).get("text")); Y.Assert.areEqual("", options.item(0).get("value")); @@ -36,6 +38,17 @@ suite.add(new Y.Test.Case({ Y.Assert.areEqual("bar", options.item(2).get("value")); }, + // The SelectField widget can render the default option only. + testRenderWithDefaultOnly: function() { + var contentBox = this.select.get("contentBox"), + options; + this.select.set("choices", []); + this.select.render(); + options = contentBox.all("option"); + Y.Assert.areEqual("Choose one", options.item(0).get("text")); + Y.Assert.areEqual("", options.item(0).get("value")); + }, + // If the 'multi' attribute is set to true, the select element // will have the 'multiple' property set. testRenderWithMulti: function() { @@ -47,6 +60,21 @@ suite.add(new Y.Test.Case({ this.select.render(); field = contentBox.one("select"); Y.Assert.isTrue(field.get("multiple")); + }, + + // When the 'choices' attribute changes, the widget is refreshed. + testChangeChoices: function() { + this.select.set("choices", [{label: "Foo", value: "foo"}, + {label: "Bar", value: "bar"}]); + this.select.render(); + this.select.set("choices", [{label: "Egg", value: "egg"}]); + var contentBox = this.select.get("contentBox"); + var options = contentBox.all("option"); + Y.Assert.areEqual(2, options.size()); + Y.Assert.areEqual("Choose one", options.item(0).get("text")); + Y.Assert.areEqual("", options.item(0).get("value")); + Y.Assert.areEqual("Egg", options.item(1).get("text")); + Y.Assert.areEqual("egg", options.item(1).get("value")); } }));