/*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;
}
);