/** @module delite/FormValueWidget */
define([
"dcl/dcl",
"./FormWidget",
"./focus"
], function (dcl, FormWidget) {
/**
* Returns a method to set a new value and fire an event (change or input) if the value changed since the last
* call. Widget should use `handleOnChange()` or `handleOnInput()`.
* @param {string} eventType - The event type. Can be "change" or "input".
* @param {string} prevValueProp - The name of the property to hold the previous value.
* @param {string} deferHandleProp - The name of the property to hold the defer method that fire the event.
* @returns {Function}
* @private
*/
function genHandler(eventType, prevValueProp, deferHandleProp) {
// Set value and fire an input event if the value changed since the last call.
// @param {*} newValue - The new value.
return function (newValue) {
if ((typeof newValue !== typeof this[prevValueProp]) ||
(this.compare(newValue, this[prevValueProp]) !== 0)) {
this[prevValueProp] = this.value = newValue;
if (this[deferHandleProp]) {
this[deferHandleProp].remove();
}
// defer allows hidden value processing to run and
// also the onChange handler can safely adjust focus, etc
this[deferHandleProp] = this.defer(function () {
this[deferHandleProp] = null;
// make sure rendering is in sync when event handlers are called
this.deliver();
this.emit(eventType);
});
}
};
}
/**
* Mixin for widgets corresponding to native HTML elements such as `<input>` or `<select>`
* that have user changeable values.
*
* Each FormValueWidget represents a single input value, and has a (possibly hidden) `<input>` element,
* to which it serializes its input value, so that form submission works as expected.
*
* The subclass should call `handleOnChange()` and `handleOnInput()` to make the widget fire `change` and
* `input`events as the value changes.
*
* @mixin module:delite/FormValueWidget
* @augments module:delite/FormWidget
*/
return dcl(FormWidget, /** @lends module:delite/FormValueWidget# */{
/**
* If true, this widget won't respond to user input.
* Similar to disabled except readOnly form values are submitted.
* @member {boolean}
* @default false
*/
readOnly: false,
refreshRendering: function (oldValues) {
if ("readOnly" in oldValues) {
var isReadOnly = this.readOnly;
if (this.valueNode && this.valueNode !== this) {
this.valueNode.readOnly = isReadOnly; // inform screen reader
}
if (!isReadOnly) {
this.removeAttribute("readonly");
}
}
},
/**
* Compare two values (of this widget).
* @param {*} val1
* @param {*} val2
* @returns {number}
* @protected
*/
compare: function (val1, val2) {
if (typeof val1 === "number" && typeof val2 === "number") {
return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2;
} else if (val1 > val2) {
return 1;
} else if (val1 < val2) {
return -1;
} else {
return 0;
}
},
_onFocus: dcl.superCall(function (sup) {
return function () {
// Called when user may be about to start input.
// Saves the widget's current value, which is the most recent of:
//
// 1. the original value set on widget construction
// 2. the value the user set when he previously used the widget
// 3. the value the application set programatically
//
// This is all to avoid firing unnecessary change/input events in the corner case where the
// user just selects and releases the Slider handle for example.
sup.call(this);
this._previousOnChangeValue = this.value;
this._previousOnInputValue = this.value;
};
}),
/**
* Set value and fire a change event if the value changed since the last call.
* @param {*} newValue - The new value.
* @protected
*/
handleOnChange: genHandler("change", "_previousOnChangeValue", "_onChangeHandle"),
/**
* Set value and fire an input event if the value changed since the last call.
* @param {*} newValue - The new value.
* @protected
*/
handleOnInput: genHandler("input", "_previousOnInputValue", "_onInputHandle")
});
});