(function () {
var lang = YAHOO.lang, Event = YAHOO.util.Event, Dom = YAHOO.util.Dom;
/**
* Create a radio button. Here are the added options :
* <ul>
* <li>choices: list of choices (array of string)</li>
* <li>values: list of returned values (array )</li>
* <li>allowAny: add an option with a string field</li>
* </ul>
* @class inputEx.RadioField
* @extends inputEx.Field
* @constructor
* @param {Object} options inputEx.Field options object
*/
inputEx.RadioField = function (options) {
inputEx.RadioField.superclass.constructor.call(this,options);
// IE BUG: doesn't want to set the value if the node is not in the DOM
if (YAHOO.env.ua.ie && !lang.isUndefined(this.options.value)) {
// Set the initial value, use setTimeout to escape the stack (for nested usage in Group or Form)
var that = this;
setTimeout(function () {
that.setValue(that.options.value, false);
},0);
}
};
lang.extend(inputEx.RadioField, inputEx.Field, {
/**
* Adds the Radio button specific options
* @param {Object} options Options object as passed to the constructor
*/
setOptions: function (options) {
var i, length;
inputEx.RadioField.superclass.setOptions.call(this, options);
// Display mode
this.options.display = options.display === "vertically" ? "vertically" : "inline"; // default "inline"
// Classname
this.options.className = options.className ? options.className : 'inputEx-Field inputEx-RadioField';
if (this.options.display === "vertically") {
this.options.className += ' inputEx-RadioField-Vertically';
}
// Choices creation
// Retro-compatibility with old pattern (DEPRECATED since 2010-06-30)
if (lang.isArray(options.values)) {
this.options.choices = [];
for (i = 0, length = options.values.length; i < length; i += 1) {
this.options.choices.push({ value: options.values[i], label: options.choices[i] });
}
// New pattern to define choices
} else {
this.options.choices = options.choices; // ['val1','val2'] or [{ value: 'val1', label: '1st Choice' }, etc.]
}
if (lang.isUndefined(options.allowAny) || options.allowAny === false ) {
this.options.allowAny = false;
} else {
this.options.allowAny = {};
if (lang.isArray(options.allowAny.separators)) { this.options.allowAny.separators = options.allowAny.separators;}
this.options.allowAny.validator = lang.isFunction(options.allowAny.validator) ? options.allowAny.validator : function (val) {return true;};
this.options.allowAny.value = !lang.isUndefined(options.allowAny.value) ? options.allowAny.value : "";
this.options.allowAny.field = lang.isUndefined(options.allowAny.field) ? { type: "string", value: this.options.allowAny.value } : options.allowAny.field;
}
},
/**
* Render the checkbox and the hidden field
*/
renderComponent: function () {
var choices, length, i, sep;
this.choicesList = [];
choices = this.options.choices;
for (i = 0, length = choices.length ; i < length ; i += 1 ) {
this.addChoice(choices[i]);
}
// Build a "any" radio combined with a StringField
if (this.options.allowAny) {
this.allowAnyChoice = this.addChoice({ value: this.options.allowAny.value, label:'' });
this.radioAny = this.allowAnyChoice.node.firstChild;
this.anyField = new inputEx(this.options.allowAny.field);
this.anyField.disable();
Dom.setStyle(this.radioAny, "float","left");
Dom.setStyle(this.anyField.getEl(), "float","left");
// Hack for firefox 3.5+
if (YAHOO.env.ua.gecko >= 1.91) { Dom.setStyle(this.radioAny, "marginTop","0.2em"); }
if (this.options.allowAny.separators) {
sep = inputEx.cn("div",null,{marginRight:"3px"},this.options.allowAny.separators[0] || '');
Dom.setStyle(sep, "float","left");
this.allowAnyChoice.node.appendChild(sep);
}
this.allowAnyChoice.node.appendChild(this.anyField.getEl());
if (this.options.allowAny.separators) {
sep = inputEx.cn("div",null,{marginLeft:"3px"},this.options.allowAny.separators[1] || '');
Dom.setStyle(sep, "float","left");
this.allowAnyChoice.node.appendChild(sep);
}
}
},
/**
* Listen for change events on all radios
*/
initEvents: function () {
// Delegate event listening because list of choices is dynamic
// so we can't listen on each <input type="radio" class='inputEx-RadioField-radio' />
// Change event (IE does not fire "change" event, so listen to click instead)
Event.delegate(this.fieldContainer, YAHOO.env.ua.ie ? "click" : "change", function(e, matchedEl, container) {
this.onChange(e);
}, "input.inputEx-RadioField-radio", this, true);
// Focus / Blur events
Event.delegate(this.fieldContainer, "focusin", function(e, matchedEl, container) {
this.onFocus(e);
}, "input.inputEx-RadioField-radio", this, true);
Event.delegate(this.fieldContainer, "focusout", function(e, matchedEl, container) {
this.onBlur(e);
}, "input.inputEx-RadioField-radio", this, true);
// AnyField events
if (this.allowAnyChoice) {
this.anyField.updatedEvt.subscribe(function (e, params) {
var value = params[0];
this.radioAny.value = value;
this.setClassFromState();
inputEx.RadioField.superclass.onChange.call(this,e);
}, this, true);
// Update radio field style after editing anyField content !
Event.addBlurListener(this.anyField.el, this.onBlur, this, true);
}
},
/**
* Add an additional class to the currently selected inputEx-RadioField-choice
*/
setSelectedClass: function () {
var i, length;
for (i = 0, length = this.choicesList.length ; i < length ; i += 1) {
if (this.choicesList[i].node.firstChild.checked) {
Dom.addClass(this.choicesList[i].node,"inputEx-selected");
} else {
Dom.removeClass(this.choicesList[i].node,"inputEx-selected");
}
}
},
setClassFromState: function () {
// call superclass method (will fire updatedEvt)
inputEx.RadioField.superclass.setClassFromState.call(this);
this.setSelectedClass();
},
/**
* Function called when the checkbox is toggled
* @param {Event} e The original 'change' event
*/
onChange: function (e) {
var target = Event.getTarget(e);
// Enable/disable the "any" field
if (this.allowAnyChoice) {
// if clicked another choice than allowAnyChoice
if (inputEx.indexOf(target, this.choicesList, function(el,arrEl) { return el === arrEl.node.firstChild; }) !== -1 && this.radioAny !== target) {
this.anyField.disable();
} else {
this.anyField.enable();
lang.later( 50 , this.anyField , "focus");
}
}
this.setSelectedClass();
// call superclass method (will fire updatedEvt)
inputEx.RadioField.superclass.onChange.call(this,e);
},
/**
* Get the field value
* @return {Any}
*/
getValue: function () {
var i, length;
for (i = 0, length = this.choicesList.length ; i < length ; i += 1) {
if (this.choicesList[i].node.firstChild.checked) {
if (this.radioAny && this.radioAny == this.choicesList[i].node.firstChild) {
return this.anyField.getValue();
}
return this.choicesList[i].value;
}
}
return "";
},
/**
* Set the value of the Radio
* @param {Any} value The value schould be one of this.options.values (which defaults to this.options.choices if missing) if allowAny option not true.
* @param {boolean} [sendUpdatedEvt] (optional) Wether this setValue should fire the updatedEvt or not (default is true, pass false to NOT send the event)
*/
setValue: function (value, sendUpdatedEvt) {
var checkAny = true, valueFound = false, i, length;
for (i = 0, length = this.choicesList.length ; i < length ; i += 1) {
// valueFound is a useful when "real" choice has a value equal to allowAny choice default value
// so we check only the first value-matching radio button
if (value === this.choicesList[i].value && !valueFound) {
this.choicesList[i].node.firstChild.checked = true;
valueFound = true;
checkAny = false;
} else {
this.choicesList[i].node.firstChild.checked = false;
}
}
// Option allowAny
if (this.radioAny) {
if (checkAny) {
this.radioAny.checked = true;
this.radioAny.value = value;
this.anyField.enable();
this.anyField.setValue(value, false);
} else {
this.anyField.disable();
}
}
// call parent class method to set style and fire updatedEvt
inputEx.RadioField.superclass.setValue.call(this, value, sendUpdatedEvt);
},
/**
* Clear the field by setting the field value to this.options.value
* @param {boolean} [sendUpdatedEvt] (optional) Wether this clear should fire the updatedEvt or not (default is true, pass false to NOT send the event)
*/
clear: function (sendUpdatedEvt) {
if (this.radioAny){
this.anyField.setValue(this.options.allowAny.value, false);
}
inputEx.RadioField.superclass.clear.call(this, sendUpdatedEvt);
},
/**
* Should return true if empty
*/
isEmpty: function () {
var i, length, radioInput;
for (i = 0, length = this.choicesList.length ; i < length ; i += 1) {
radioInput = this.choicesList[i].node.firstChild;
if (radioInput.checked) {
// if "any" option checked
if (this.radioAny && this.radioAny == radioInput) {
return this.anyField.getValue() === '';
} else {
return false;
}
}
}
return true;
},
validate: function () {
var i, length, radioInput, anyVal;
if (this.options.allowAny) {
for (i = 0, length = this.choicesList.length ; i < length ; i += 1) {
radioInput = this.choicesList[i].node.firstChild;
if (radioInput.checked) {
// if "any" option checked
if (this.radioAny && this.radioAny == radioInput) {
anyVal = this.anyField.getValue();
return this.anyField.validate() && this.options.allowAny.validator(anyVal);
}
}
}
}
return true;
},
/**
* Disable the field
*/
disable: function () {
var i, length;
for (i = 0, length = this.choicesList.length; i < length; i += 1) {
this.disableChoice(this.choicesList[i], false);
}
},
/**
* Enable the field
*/
enable: function () {
var i, length;
for (i = 0, length = this.choicesList.length; i < length; i += 1) {
this.enableChoice(this.choicesList[i]);
}
},
createChoiceNode: function (choice) {
var div, radioId, radioNode, labelNode;
div = inputEx.cn('div', {className: 'inputEx-RadioField-choice'});
// radioId MUST be different for each option, to allow click on label (with for:id trick)
radioId = YAHOO.util.Dom.generateId();
radioNode = inputEx.cn('input', { id: radioId, type: 'radio', name: this.options.name, value: choice.value, className: 'inputEx-RadioField-radio' });
div.appendChild(radioNode);
if (choice.label.length > 0) {
labelNode = inputEx.cn('label', {"for": radioId, className: 'inputEx-RadioField-rightLabel'}, null, ""+choice.label);
div.appendChild(labelNode);
}
return div;
},
removeChoiceNode: function (node) {
// remove from selector
//
// -> style.display = 'none' would work only on FF (when node is an <option>)
// -> other browsers (IE, Chrome...) require to remove <option> node from DOM
//
this.fieldContainer.removeChild(node);
},
disableChoiceNode: function (node) {
//node.firstChild.disabled = "disabled";
node.firstChild.disabled = true;
},
enableChoiceNode: function (node) {
//node.firstChild.removeAttribute("disabled");
node.firstChild.disabled = false;
},
/**
* Attach an <option> node to the <select> at the specified position
* @param {HTMLElement} node The <option> node to attach to the <select>
* @param {Int} position The position of the choice in choicesList (may not be the "real" position in DOM)
*/
appendChoiceNode: function (node, position) {
var domPosition, i;
// Compute real DOM position (since previous choices in choicesList may be hidden)
domPosition = 0;
for (i = 0; i < position; i += 1) {
if (this.choicesList[i].visible) {
domPosition += 1;
}
}
// Insert in DOM
if (domPosition < this.fieldContainer.childNodes.length) {
YAHOO.util.Dom.insertBefore(node, this.fieldContainer.childNodes[domPosition]);
} else {
this.fieldContainer.appendChild(node);
}
}
});
// Augment prototype with choice mixin (functions : addChoice, removeChoice, etc.)
lang.augmentObject(inputEx.RadioField.prototype, inputEx.mixin.choice);
// Register this class as "radio" type
inputEx.registerType("radio", inputEx.RadioField, [
{
type: 'list',
name: 'choices',
label: 'Choices',
elementType: {
type: 'group',
fields: [
{ label: 'Value', name: 'value', value: '' }, // not required to allow '' value (which is default)
{ label: 'Label', name: 'label' } // optional : if left empty, label is not created
]
},
value: [],
required: true
},
{type: 'boolean', label: 'Allow custom value', name: 'allowAny', value: false }
]);
})();