/*global define*/ define( [ 'jquery', 'knockout', './utils', 'jquery-ui/widget' ], function ($, ko, utils) { 'use strict'; var domDataKey, filterAndUnwrapProperties, subscribeToRefreshOn, BindingHandler; domDataKey = '__kojqui_options'; filterAndUnwrapProperties = function (source, properties) { /// Filters and unwraps the properties of an object. /// /// /// A new object with the specified properties copied /// and unwrapped from source. var result = {}; ko.utils.arrayForEach(properties, function (property) { if (source[property] !== undefined) { result[property] = ko.utils.unwrapObservable(source[property]); } }); return result; }; subscribeToRefreshOn = function (widgetName, element, bindingValue) { /// Creates a subscription to the refreshOn observable. /// The widget's name. /// /// if (ko.isObservable(bindingValue.refreshOn)) { ko.computed({ read: function () { bindingValue.refreshOn(); $(element)[widgetName]('refresh'); }, disposeWhenNodeIsRemoved: element }); } }; BindingHandler = function (widgetName) { /// Constructor. /// The jQuery UI widget's /// name. this.widgetName = widgetName; this.widgetEventPrefix = widgetName; this.options = []; this.events = []; this.after = []; this.hasRefresh = false; }; /*jslint unparam:true*/ BindingHandler.prototype.init = function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { var widgetName, value, unwrappedOptions, unwrappedEvents, shouldApplyBindingsToDescendants; widgetName = this.widgetName; value = valueAccessor(); unwrappedOptions = filterAndUnwrapProperties(value, this.options); unwrappedEvents = filterAndUnwrapProperties(value, this.events); // There can be control flow- or other bindings on some of the descendant // elements which affect the shape of the element-rooted DOM subtree. These // should be processed before instantiating the jQuery UI widget, because they // can add pages to the tabs widget, menu items to the menu widget, etc. shouldApplyBindingsToDescendants = !ko.utils.arrayFirst( utils.descendantControllingBindings, function (bindingName) { return this.hasOwnProperty(bindingName); }, allBindingsAccessor() ); if (shouldApplyBindingsToDescendants) { // process descendant bindings ko.applyBindingsToDescendants(bindingContext, element); } // store the options' values so they can be checked for changes in the // update() method ko.utils.domData.set(element, domDataKey, unwrappedOptions); // bind the widget events to the viewmodel $.each(unwrappedEvents, function (key, value) { unwrappedEvents[key] = value.bind(viewModel); }); // initialize the widget $(element)[widgetName](ko.utils.extend(unwrappedOptions, unwrappedEvents)); if (this.hasRefresh) { subscribeToRefreshOn(widgetName, element, value); } // store the element in the widget observable if (ko.isWriteableObservable(value.widget)) { value.widget($(element)); } // handle disposal ko.utils.domNodeDisposal.addDisposeCallback(element, function () { $(element)[widgetName]('destroy'); }); return { controlsDescendantBindings: shouldApplyBindingsToDescendants }; }; /*jslint unparam:false*/ BindingHandler.prototype.update = function (element, valueAccessor) { var widgetName, value, oldOptions, newOptions; widgetName = this.widgetName; value = valueAccessor(); oldOptions = ko.utils.domData.get(element, domDataKey); newOptions = filterAndUnwrapProperties(value, this.options); // set only the changed options $.each(newOptions, function (prop, val) { if (val !== oldOptions[prop]) { $(element)[widgetName]('option', prop, newOptions[prop]); } }); // store the options' values so they can be checked for changes in the next // update() method ko.utils.domData.set(element, domDataKey, newOptions); }; BindingHandler.prototype.on = function (element, type, callback) { /// Attaches callback to a widget event. /// /// /// var eventName; // the same algorithm as in widget._trigger() if (type === this.widgetEventPrefix) { eventName = type; } else { eventName = this.widgetEventPrefix + type; } eventName = [eventName.toLowerCase(), '.', this.widgetName].join(''); $(element).on(eventName, callback); // handle disposal ko.utils.domNodeDisposal.addDisposeCallback(element, function () { $(element).off(eventName); }); }; return BindingHandler; } );