hellojs-xiaotian
Version:
A clientside Javascript library for standardizing requests to OAuth2 web services (and OAuth1 - with a shim)
107 lines (95 loc) • 6.21 kB
JavaScript
ko.bindingHandlers['value'] = {
'after': ['options', 'foreach'],
'init': function (element, valueAccessor, allBindings) {
// If the value binding is placed on a radio/checkbox, then just pass through to checkedValue and quit
if (element.tagName.toLowerCase() == "input" && (element.type == "checkbox" || element.type == "radio")) {
ko.applyBindingAccessorsToNode(element, { 'checkedValue': valueAccessor });
return;
}
// Always catch "change" event; possibly other events too if asked
var eventsToCatch = ["change"];
var requestedEventsToCatch = allBindings.get("valueUpdate");
var propertyChangedFired = false;
var elementValueBeforeEvent = null;
if (requestedEventsToCatch) {
if (typeof requestedEventsToCatch == "string") // Allow both individual event names, and arrays of event names
requestedEventsToCatch = [requestedEventsToCatch];
ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
}
var valueUpdateHandler = function() {
elementValueBeforeEvent = null;
propertyChangedFired = false;
var modelValue = valueAccessor();
var elementValue = ko.selectExtensions.readValue(element);
ko.expressionRewriting.writeValueToProperty(modelValue, allBindings, 'value', elementValue);
}
// Workaround for https://github.com/SteveSanderson/knockout/issues/122
// IE doesn't fire "change" events on textboxes if the user selects a value from its autocomplete list
var ieAutoCompleteHackNeeded = ko.utils.ieVersion && element.tagName.toLowerCase() == "input" && element.type == "text"
&& element.autocomplete != "off" && (!element.form || element.form.autocomplete != "off");
if (ieAutoCompleteHackNeeded && ko.utils.arrayIndexOf(eventsToCatch, "propertychange") == -1) {
ko.utils.registerEventHandler(element, "propertychange", function () { propertyChangedFired = true });
ko.utils.registerEventHandler(element, "focus", function () { propertyChangedFired = false });
ko.utils.registerEventHandler(element, "blur", function() {
if (propertyChangedFired) {
valueUpdateHandler();
}
});
}
ko.utils.arrayForEach(eventsToCatch, function(eventName) {
// The syntax "after<eventname>" means "run the handler asynchronously after the event"
// This is useful, for example, to catch "keydown" events after the browser has updated the control
// (otherwise, ko.selectExtensions.readValue(this) will receive the control's value *before* the key event)
var handler = valueUpdateHandler;
if (ko.utils.stringStartsWith(eventName, "after")) {
handler = function() {
// The elementValueBeforeEvent variable is non-null *only* during the brief gap between
// a keyX event firing and the valueUpdateHandler running, which is scheduled to happen
// at the earliest asynchronous opportunity. We store this temporary information so that
// if, between keyX and valueUpdateHandler, the underlying model value changes separately,
// we can overwrite that model value change with the value the user just typed. Otherwise,
// techniques like rateLimit can trigger model changes at critical moments that will
// override the user's inputs, causing keystrokes to be lost.
elementValueBeforeEvent = ko.selectExtensions.readValue(element);
ko.utils.setTimeout(valueUpdateHandler, 0);
};
eventName = eventName.substring("after".length);
}
ko.utils.registerEventHandler(element, eventName, handler);
});
var updateFromModel = function () {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = ko.selectExtensions.readValue(element);
if (elementValueBeforeEvent !== null && newValue === elementValueBeforeEvent) {
ko.utils.setTimeout(updateFromModel, 0);
return;
}
var valueHasChanged = (newValue !== elementValue);
if (valueHasChanged) {
if (ko.utils.tagNameLower(element) === "select") {
var allowUnset = allBindings.get('valueAllowUnset');
var applyValueAction = function () {
ko.selectExtensions.writeValue(element, newValue, allowUnset);
};
applyValueAction();
if (!allowUnset && newValue !== ko.selectExtensions.readValue(element)) {
// If you try to set a model value that can't be represented in an already-populated dropdown, reject that change,
// because you're not allowed to have a model value that disagrees with a visible UI selection.
ko.dependencyDetection.ignore(ko.utils.triggerEvent, null, [element, "change"]);
} else {
// Workaround for IE6 bug: It won't reliably apply values to SELECT nodes during the same execution thread
// right after you've changed the set of OPTION nodes on it. So for that node type, we'll schedule a second thread
// to apply the value as well.
ko.utils.setTimeout(applyValueAction, 0);
}
} else {
ko.selectExtensions.writeValue(element, newValue);
}
}
};
ko.computed(updateFromModel, null, { disposeWhenNodeIsRemoved: element });
},
'update': function() {} // Keep for backwards compatibility with code that may have wrapped value binding
};
ko.expressionRewriting.twoWayBindings['value'] = true;