/*
 * knockout-kendo 0.10.0
 * Copyright © 2017 Ryan Niemeyer & Telerik
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 * http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

;(function(factory) {
    // CommonJS
    if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') {
        factory(require('knockout'), require('jquery'), require('kendo'));
        // AMD
    } else if (typeof define === 'function' && define.amd) {
        define(['knockout', 'jquery', 'kendo'], factory);
        // Normal script tag
    } else {
        factory(window.ko, window.jQuery, window.kendo);
    }
}(function(ko, $, kendo, undefined) {

//handle require.js scenarios where kendo is not actually returned
kendo = kendo || window.kendo;

ko.kendo = ko.kendo || {};

var unwrap = ko.utils.unwrapObservable; //support older 2.x KO where ko.unwrap was not defined

ko.kendo.BindingFactory = function() {
    var self = this;

    this.createBinding = function(widgetConfig) {
        //only support widgets that are available when this script runs
        if (!$()[widgetConfig.parent || widgetConfig.name]) {
            return;
        }

        var binding = {};

        //the binding handler's init function
        binding.init = function(element, valueAccessor, all, vm, context) {
            //step 1: build appropriate options for the widget from values passed in and global options
            var options = self.buildOptions(widgetConfig, valueAccessor);

            //apply async, so inner templates can finish content needed during widget initialization
            if (options.async === true || (widgetConfig.async === true && options.async !== false)) {
                setTimeout(function() {
                    binding.setup(element, options, context);
                }, 0);
                return;
            }

            binding.setup(element, options, context);

            if (options && options.useKOTemplates) {
                return { controlsDescendantBindings: true };
            }
        };

        //build the core logic for the init function
        binding.setup = function(element, options, context) {
            var widget, $element = $(element);

            //step 2: setup templates
            self.setupTemplates(widgetConfig.templates, options, element, context);

            //step 3: initialize widget
            widget = self.getWidget(widgetConfig, options, $element);

            //step 4: add handlers for events that we need to react to for updating the model
            self.handleEvents(options, widgetConfig, element, widget, context);

            //step 5: set up computed observables to update the widget when observable model values change
            self.watchValues(widget, options, widgetConfig, element);

            //step 6: handle disposal, if there is a destroy method on the widget
            if (widget.destroy) {
                ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
                    if (widget.element) {
                        if (typeof kendo.destroy === "function") {
                            kendo.destroy(widget.element);
                        } else {
                            widget.destroy();
                        }
                    }
                });
            }
        };

        binding.options = {}; //global options
        binding.widgetConfig = widgetConfig; //expose the options to use in generating tests

        ko.bindingHandlers[widgetConfig.bindingName || widgetConfig.name] = binding;
    };

    //combine options passed in binding with global options
    this.buildOptions = function(widgetConfig, valueAccessor) {
        var defaultOption = widgetConfig.defaultOption,
            options = ko.utils.extend({}, ko.bindingHandlers[widgetConfig.name].options),
            valueOrOptions = unwrap(valueAccessor());

        if (valueOrOptions instanceof kendo.data.DataSource || typeof valueOrOptions !== "object" || valueOrOptions === null || (defaultOption && !(defaultOption in valueOrOptions))) {
            options[defaultOption] = valueAccessor();
        }  else {
            ko.utils.extend(options, valueOrOptions);
        }

        return options;
    };

    var templateRenderer = function(id, context) {
        return function(data) {
            return ko.renderTemplate(id, context.createChildContext((data._raw && data._raw()) || data));
        };
    };

    //prepare templates, if the widget uses them
    this.setupTemplates = function(templateConfig, options, element, context) {
        var i, j, option, existingHandler;

        if (templateConfig && options && options.useKOTemplates) {
            //create a function to render each configured template
            for (i = 0, j = templateConfig.length; i < j; i++) {
                option = templateConfig[i];
                if (options[option]) {
                    options[option] = templateRenderer(options[option], context);
                }
            }

            //initialize bindings in dataBound event
            existingHandler = options.dataBound;
            options.dataBound = function() {
                ko.memoization.unmemoizeDomNodeAndDescendants(element);
                if (existingHandler) {
                    existingHandler.apply(this, arguments);
                }
            };
        }
    };

    //unless the object is a kendo datasource, get a clean object with one level unwrapped
    this.unwrapOneLevel = function(object) {
        var prop,
            result = {};

        if (object) {
            if (object instanceof kendo.data.DataSource) {
                result = object;
            }
            else if (typeof object === "object") {
                for (prop in object) {
                    //include things on prototype
                    result[prop] = unwrap(object[prop]);
                }
            }
        }

        return result;
    };

    //return the actual widget
    this.getWidget = function(widgetConfig, options, $element) {
        var widget;
        if (widgetConfig.parent) {
            //locate the actual widget
            var parent = $element.closest("[data-bind*='" + widgetConfig.parent + ":']");
            widget = parent.length ? parent.data(widgetConfig.parent) : null;
        } else {
            widget = $element[widgetConfig.name](this.unwrapOneLevel(options)).data(widgetConfig.name);
        }

        //if the widget option was specified, then fill it with our widget
        if (ko.isObservable(options.widget)) {
            options.widget(widget);
        }

        return widget;
    };

    //respond to changes in the view model
    this.watchValues = function(widget, options, widgetConfig, element) {
        var watchProp, watchValues = widgetConfig.watch;
        if (watchValues) {
            for (watchProp in watchValues) {
                if (watchValues.hasOwnProperty(watchProp)) {
                    self.watchOneValue(watchProp, widget, options, widgetConfig, element);
                }
            }
        }
    };

    this.watchOneValue = function(prop, widget, options, widgetConfig, element) {
        var computed = ko.computed({
            read: function() {
                var existing, custom,
                    action = widgetConfig.watch[prop],
                    value = unwrap(options[prop]),
                    params = widgetConfig.parent ? [element] : []; //child bindings pass element first to APIs

                //support passing multiple events like ["open", "close"]
                if ($.isArray(action)) {
                    action = widget[value ? action[0] : action[1]];
                } else if (typeof action === "string") {
                    action = widget[action];
                } else {
                    custom = true; //running a custom function
                }

                if (action && options[prop] !== undefined) {
                    if (!custom) {
                        existing = action.apply(widget, params);
                        params.push(value);
                    } else {
                        params.push(value, options);
                    }

                    //try to avoid unnecessary updates when the new value matches the current value
                    if (custom || existing !== value) {
                        action.apply(widget, params);
                    }
                }
            },
            disposeWhenNodeIsRemoved: element
        }).extend({ throttle: (options.throttle || options.throttle === 0) ? options.throttle : 1 });

        //if option is not observable, then dispose up front after executing the logic once
        if (!ko.isObservable(options[prop])) {
            computed.dispose();
        }
    };

    //write changes to the widgets back to the model
    this.handleEvents = function(options, widgetConfig, element, widget, context) {
        var prop, eventConfig, events = widgetConfig.events;

        if (events) {
            for (prop in events) {
                if (events.hasOwnProperty(prop)) {
                    eventConfig = events[prop];
                    if (typeof eventConfig === "string") {
                        eventConfig = { value: eventConfig, writeTo: eventConfig };
                    }

                    self.handleOneEvent(prop, eventConfig, options, element, widget, widgetConfig.childProp, context);
                }
            }
        }
    };

    //bind to a single event
    this.handleOneEvent = function(eventName, eventConfig, options, element, widget, childProp, context) {
        var handler = typeof eventConfig === "function" ? eventConfig : options[eventConfig.call];

        //call a function defined directly in the binding definition, supply options that were passed to the binding
        if (typeof eventConfig === "function") {
            handler = handler.bind(context.$data, options);
        }
        //use function passed in binding options as handler with normal KO args
        else if (eventConfig.call && typeof options[eventConfig.call] === "function") {
            handler = options[eventConfig.call].bind(context.$data, context.$data);
        }
        //option is observable, determine what to write to it
        else if (eventConfig.writeTo && ko.isWriteableObservable(options[eventConfig.writeTo])) {
            handler = function(e) {
                var propOrValue, value;

                if (!childProp || !e[childProp] || e[childProp] === element) {
                    propOrValue = eventConfig.value;
                    value = (typeof propOrValue === "string" && this[propOrValue]) ? this[propOrValue](childProp && element) : propOrValue;
                    options[eventConfig.writeTo](value);
                }
            };
        }

        if (handler) {
            widget.bind(eventName, handler);
        }
    };
};

ko.kendo.bindingFactory = new ko.kendo.BindingFactory();

//utility to set the dataSource with a clean copy of data. Could be overridden at run-time.
ko.kendo.setDataSource = function(widget, data, options) {
    var isMapped, cleanData;

    if (data instanceof kendo.data.DataSource) {
        widget.setDataSource(data);
        return;
    }

    if (!options || !options.useKOTemplates) {
        isMapped = ko.mapping && data && data.__ko_mapping__;
        cleanData = data && isMapped ? ko.mapping.toJS(data) : ko.toJS(data);
    }

    widget.dataSource.data(cleanData || data);
};

//attach the raw data after Kendo wraps our items
(function() {
    var existing = kendo.data.ObservableArray.fn.wrap;
    kendo.data.ObservableArray.fn.wrap = function(object) {
        if (object === null) {
            return;
        }
        var result = existing.apply(this, arguments);
        result._raw = function() {
            return object;
        };

        return result;
    };
})();

//private utility function generator for gauges
var extendAndRedraw = function(prop) {
    return function(value) {
        if (value) {
            ko.utils.extend(this.options[prop], value);
            this.redraw();
            this.value(0.001 + this.value());
        }
    };
};

var openIfVisible = function(value, options) {
    if (!value) {
        //causes issues with event triggering, if closing programmatically, when unnecessary
        if (this.element.parent().is(":visible")) {
            this.close();
        }
    } else {
        this.open(typeof options.target === "string" ? $(unwrap(options.target)) : options.target);
    }
};


//library is in a closure, use this private variable to reduce size of minified file
var createBinding = ko.kendo.bindingFactory.createBinding.bind(ko.kendo.bindingFactory);

//use constants to ensure consistency and to help reduce minified file size
var CLICK = "click",
    CENTER = "center",
    CHECK = "check",
    CHECKED = "checked",
    CLICKED = "clicked",
    CLOSE = "close",
    COLLAPSE = "collapse",
    CONTENT = "content",
    DATA = "data",
    DATE = "date",
    DISABLE = "disable",
    ENABLE = "enable",
    EXPAND = "expand",
    ENABLED = "enabled",
    EXPANDED = "expanded",
    ERROR = "error",
    FILTER = "filter",
    HIDE = "hide",
    INFO = "info",
    ISOPEN = "isOpen",
    ITEMS = "items",
    MAX = "max",
    MIN = "min",
    OPEN = "open",
    PALETTE = "palette",
    READONLY = "readonly",
    RESIZE = "resize",
    SCROLLTO = "scrollTo",
    SEARCH = "search",
    SELECT = "select",
    SELECTED = "selected",
    SELECTEDINDEX = "selectedIndex",
    SHOW = "show",
    SIZE = "size",
    SUCCESS = "success",
    TARGET = "target",
    TITLE = "title",
    VALUE = "value",
    VALUES = "values",
    WARNING = "warning",
    ZOOM = "zoom";

createBinding({
    name: "kendoAutoComplete",
    events: {
        change: VALUE,
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        search: [SEARCH, CLOSE],
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        },
        value: VALUE
    }
});

createBinding({
    name: "kendoButton",
    defaultOption: CLICKED,
    events: {
        click: {
            call: CLICKED
        }
    },
    watch: {
        enabled: ENABLE
    }
});

createBinding({
    name: "kendoCalendar",
    defaultOption: VALUE,
    events: {
        change: VALUE
    },
    watch: {
        max: MAX,
        min: MIN,
        value: VALUE
    }
});

createBinding({
    name: "kendoColorPicker",
    events: {
        change: VALUE,
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        value: VALUE,
        color: VALUE,
        palette: PALETTE
    }
});

createBinding({
    name: "kendoComboBox",
    events: {
        change: VALUE,
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        isOpen: [OPEN, CLOSE],
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        },
        value: VALUE
    }
});

createBinding({
    name: "kendoDatePicker",
    defaultOption: VALUE,
    events: {
        change: VALUE,
        open:
        {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        max: MAX,
        min: MIN,
        value: VALUE,
        isOpen: [OPEN, CLOSE]
    }
});

createBinding({
    name: "kendoDateTimePicker",
    defaultOption: VALUE,
    events: {
        change: VALUE,
        open:
        {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        max: MAX,
        min: MIN,
        value: VALUE,
        isOpen: [OPEN, CLOSE]
    }
});

createBinding({
    name: "kendoDropDownList",
    events: {
        change: VALUE,
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        isOpen: [OPEN, CLOSE],
        data: function(value) {
            ko.kendo.setDataSource(this, value);

            //if nothing is selected and there is an optionLabel, select it
            if (value.length && this.options.optionLabel && this.select() < 0) {
                this.select(0);
            }
        },
        value: VALUE
    }
});

createBinding({
    name: "kendoEditor",
    defaultOption: VALUE,
    events: {
        change: VALUE
    },
    watch: {
        enabled: ENABLE,
        value: VALUE
    }
});

createBinding({
    name: "kendoGantt",
    defaultOption: DATA,
    watch: {
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});

createBinding({
    name: "kendoGrid",
    defaultOption: DATA,
    watch: {
        data: function(value, options) {
            ko.kendo.setDataSource(this, value, options);
        }
    },
    templates: ["rowTemplate", "altRowTemplate"]
});

createBinding({
    name: "kendoListView",
    defaultOption: DATA,
    watch: {
        data: function(value, options) {
            ko.kendo.setDataSource(this, value, options);
        }
    },
    templates: ["template"]
});

createBinding({
    name: "kendoPager",
    defaultOption: DATA,
    watch: {
        data: function (value, options) {
            ko.kendo.setDataSource(this, value, options);
        },
        page: "page"
    },
    templates: ["selectTemplate", "linkTemplate"]
});

createBinding({
    name: "kendoMaskedTextBox",
    defaultOption: VALUE,
    events: {
        change: VALUE
    },
    watch: {
        enabled: ENABLE,
        isReadOnly: READONLY,
        value: VALUE
    }
});

createBinding({
    name: "kendoMap",
    events: {
        zoomEnd: function (options, event) {
            if (ko.isWriteableObservable(options.zoom)) {
                options.zoom(event.sender.zoom());
            }
        },
        panEnd: function (options, event) {
            var coordinates;

            if (ko.isWriteableObservable(options.center)) {
                coordinates = event.sender.center();

                options.center([coordinates.lat, coordinates.lng]);
            }
        }
    },
    watch: {
        center: CENTER,
        zoom: ZOOM
    }
});

createBinding({
    name: "kendoMenu",
    async: true
});

createBinding({
    name: "kendoMenuItem",
    parent: "kendoMenu",
    watch: {
        enabled: ENABLE,
        isOpen: [OPEN, CLOSE]
    },
    async: true
});

createBinding({
    name: "kendoMobileActionSheet",
    events: {
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        isOpen: openIfVisible
    },
    async: true
});

createBinding({
    name: "kendoMobileButton",
    defaultOption: CLICKED,
    events: {
        click: {
            call: CLICKED
        }
    },
    watch: {
        enabled: ENABLE
    }
});

createBinding({
    name: "kendoMobileButtonGroup",
    events: {
        select: function(options, event) {
            if (ko.isWriteableObservable(options.selectedIndex)) {
                options.selectedIndex(event.sender.current().index());
            }
        }
    },
    watch: {
        enabled: ENABLE,
        selectedIndex: SELECT
    }
});

createBinding({
    name: "kendoMobileDrawer",
    events: {
        show: {
            writeTo: ISOPEN,
            value: true
        },
        hide: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        isOpen: function(value) {
            this[value ? "show" : "hide"]();
        }
    },
    async: true
});

createBinding({
    name: "kendoMobileListView",
    defaultOption: DATA,
    events: {
        click: {
            call: CLICKED
        }
    },
    watch: {
        data: function(value, options) {
            ko.kendo.setDataSource(this, value, options);
        }
    },
    templates: ["template"]
});

createBinding({
    name: "kendoMobileModalView",
    events: {
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        isOpen: openIfVisible
    },
    async: true
});

createBinding({
    name: "kendoMobileNavBar",
    watch: {
        title: TITLE
    }
});

createBinding({
    name: "kendoMobilePopOver",
    events: {
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        isOpen: openIfVisible
    },
    async: true
});

createBinding({
    name: "kendoMobileScroller",
    events: {
        pull: function(options, event) {
            var doneCallback = event.sender.pullHandled.bind(event.sender);

            if (typeof options.pulled === "function") {
                options.pulled.call(this, this, event, doneCallback);
            }
        }
    },
    watch: {
        enabled: [ENABLE, DISABLE]
    }
});

createBinding({
    name: "kendoMobileScrollView",
    events: {
        change: function(options, event) {
            if ((event.page || event.page === 0) && ko.isWriteableObservable(options.currentIndex)) {
                options.currentIndex(event.page);
            }
        }
    },
    watch: {
        currentIndex: SCROLLTO,
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});

createBinding({
    name: "kendoMobileSwitch",
    events: {
        change: function(options, event) {
            if (ko.isWriteableObservable(options.checked)) {
                options.checked(event.checked);
            }
        }
    },
    watch: {
        enabled: ENABLE,
        checked: CHECK
    }
});

createBinding({
    name: "kendoMobileTabStrip",
    events: {
        select: function(options, event) {
            if (ko.isWriteableObservable(options.selectedIndex)) {
                options.selectedIndex(event.item.index());
            }
        }
    },
    watch: {
        selectedIndex: function(value) {
            if (value || value === 0) {
                this.switchTo(value);
            }
        }
    }
});

createBinding({
    name: "kendoMultiSelect",
    events: {
        change: VALUE,
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        enabled: ENABLE,
        search: [SEARCH, CLOSE],
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        },
        value: function(value) {
            this.dataSource.filter({});
            this.value(value);
        }
    }
});

var notificationHandler = function(type, value) {
    if (value || value === 0) {
        this.show(value, type);
    }
    else {
        this.hide();
    }
};

createBinding({
    name: "kendoNotification",
    watch: {
        error: function(value) {
            notificationHandler.call(this, ERROR, value);
        },
        info: function(value) {
            notificationHandler.call(this, INFO, value);
        },
        success: function(value) {
            notificationHandler.call(this, SUCCESS, value);
        },
        warning: function(value) {
            notificationHandler.call(this, WARNING, value);
        }
    }
});

createBinding({
    name: "kendoNumericTextBox",
    defaultOption: VALUE,
    events: {
        change: VALUE,
        spin: VALUE
    },
    watch: {
        enabled: ENABLE,
        value: VALUE,
        max: function(newMax) {
            this.options.max = newMax;
            //make sure current value is still valid
            var value = this.value();
            if ((value || value === 0) && value > newMax) {
                this.value(newMax);
            }
        },
        min: function(newMin) {
            this.options.min = newMin;
            //make sure that current value is still valid
            var value = this.value();
            if ((value || value === 0) && value < newMin ) {
                this.value(newMin);
            }
        }
    }
});


createBinding({
    name: "kendoPanelBar",
    async: true
});

createBinding({
    name: "kendoPanelItem",
    parent: "kendoPanelBar",
    watch: {
        enabled: ENABLE,
        expanded: [EXPAND, COLLAPSE],
        selected: [SELECT]
    },
    childProp: "item",
    events: {
        expand: {
            writeTo: EXPANDED,
            value: true
        },
        collapse: {
            writeTo: EXPANDED,
            value: false
        },
        select: {
            writeTo: SELECTED,
            value: VALUE
        }
    },
    async: true
});

createBinding({
    name: "kendoPivotGrid",
    watch: {
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});

createBinding({
    name: "kendoProgressBar",
    defaultOption: VALUE,
    events: {
        change: VALUE
    },
    watch: {
        enabled: ENABLE,
        value: VALUE
    }
});

createBinding({
    name: "kendoRangeSlider",
    defaultOption: VALUES,
    events: {
        change: VALUES
    },
    watch: {
        values: VALUES,
        enabled: ENABLE
    }
});

var schedulerUpdateModel = function(func) {
    return function(options, e) {
        var allModels = unwrap(options.data || options.dataSource),
            idField = unwrap(options.idField) || "id",
            model = ko.utils.arrayFirst(allModels, function(item) {
                return unwrap(item[idField]) === e.event[idField];
            }),
            write = function(data) {
                for (var prop in model) {
                    if (data.hasOwnProperty(prop) && model.hasOwnProperty(prop)) {
                        var value = data[prop],
                            writeTo = model[prop];

                        if (ko.isWriteableObservable(writeTo)) {
                            writeTo(value);
                        }
                    }
                }
            };

        if (model) {
            func(options, e, model, write);
        }
    };
};

createBinding({
    name: "kendoScheduler",
    events: {
        moveEnd: schedulerUpdateModel(function(options, e, model, write) {
            write(e);
            write(e.resources);
        }),
        save: schedulerUpdateModel(function(options, e, model, write) {
            write(e.event);
        }),
        remove: function(options, e) {
            var match;
            var data = options.data || options.dataSource;
            var unwrapped = ko.unwrap(data);

            if (unwrapped && unwrapped.length) {
                match = ko.utils.arrayFirst(ko.unwrap(data), function(item) {
                    return item.uuid === e.event.uuid;
                });

                if (match) {
                    ko.utils.arrayRemoveItem(unwrapped, match);

                    if (ko.isWriteableObservable(data)) {
                        data.valueHasMutated();
                    }
                }
            }
        }
    },
    watch: {
        data: function(value, options) {
            ko.kendo.setDataSource(this, value, options);
        },
        date: DATE
    },
    async: true
});

createBinding({
    name: "kendoSlider",
    defaultOption: VALUE,
    events: {
        change: VALUE
    },
    watch: {
        value: VALUE,
        enabled: ENABLE,
        min: MIN,
        max: MAX
    }
});

createBinding({
    name: "kendoSortable",
    defaultOption: DATA,
    events: {
        end: function(options, e) {
            var dataKey = "__ko_kendo_sortable_data__",
                data = e.action !== "receive" ? ko.dataFor(e.item[0]) : e.draggableEvent[dataKey],
                items = options.data,
                underlyingArray = options.data;

            //remove item from its original position
            if (e.action === "sort" || e.action === "remove") {
                underlyingArray.splice(e.oldIndex, 1);

                //keep track of the item between remove and receive
                if (e.action === "remove") {
                    e.draggableEvent[dataKey] = data;
                }
            }

            //add the item to its new position
            if (e.action === "sort" || e.action === "receive") {
                underlyingArray.splice(e.newIndex, 0, data);

                //clear the data we passed
                delete e.draggableEvent[dataKey];

                //we are moving the item ourselves via the observableArray, cancel the draggable and hide the animation
                e.sender.placeholder.remove();
            }

            //signal that the observableArray has changed now that we are done changing the array
            items.valueHasMutated();
        }
    }
});


createBinding({
    name: "kendoSplitter",
    async: true
});

createBinding({
    name: "kendoSplitterPane",
    parent: "kendoSplitter",
    watch: {
        max: MAX,
        min: MIN,
        size: SIZE,
        expanded: [EXPAND, COLLAPSE]
    },
    childProp: "pane",
    events: {
        collapse: {
            writeTo: EXPANDED,
            value: false
        },
        expand: {
            writeTo: EXPANDED,
            value: true
        },
        resize: SIZE
    },
    async: true
});

createBinding({
    name: "kendoTabStrip",
    async: true
});

createBinding({
    name: "kendoTab",
    parent: "kendoTabStrip",
    watch: {
        enabled: ENABLE
    },
    childProp: "item",
    async: true
});

createBinding({
    name: "kendoToolBar"
});

createBinding({
    name: "kendoTooltip",
    events: {},
    watch: {
        content: function(content) {
            this.options.content = content;
            this.refresh();
        },
        filter: FILTER
    }
});


createBinding({
    name: "kendoTimePicker",
    defaultOption: VALUE,
    events: {
        change: VALUE
    },
    watch: {
        max: MAX,
        min: MIN,
        value: VALUE,
        enabled: ENABLE,
        isOpen: [OPEN, CLOSE]
    }
});

createBinding({
    name: "kendoTreeMap",
    watch: {
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});

createBinding({
    name: "kendoTreeView",
    watch: {
        data: function(value, options) {
            ko.kendo.setDataSource(this, value, options);
        }
    },
    events: {
        change: function(options, e) {
            if (ko.isWriteableObservable(options.value)) {
                var tree = e.sender;
                options.value(tree.dataItem(tree.select()));
            }
        }
    },
    async: true
});

createBinding({
    name: "kendoTreeItem",
    parent: "kendoTreeView",
    watch: {
        enabled: ENABLE,
        expanded: [EXPAND, COLLAPSE],
        selected: function(element, value) {
            if (value) {
                this.select(element);
            } else if (this.select()[0] == element) {
                this.select(null);
            }
        }
    },
    childProp: "node",
    events: {
        collapse: {
            writeTo: EXPANDED,
            value: false
        },
        expand: {
            writeTo: EXPANDED,
            value: true
        },
        select: {
            writeTo: SELECTED,
            value: true
        }
    },
    async: true
});


createBinding({
    name: "kendoUpload",
    watch: {
        enabled: ENABLE
    }
});

createBinding({
    name: "kendoWindow",
    events: {
        open: {
            writeTo: ISOPEN,
            value: true
        },
        close: {
            writeTo: ISOPEN,
            value: false
        }
    },
    watch: {
        content: CONTENT,
        title: TITLE,
        isOpen: [OPEN, CLOSE]
    },
    async: true
});

createBinding({
    name: "kendoBarcode",
    watch: {
        value: VALUE
    }
});

createBinding({
    name: "kendoChart",
    watch: {
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});

createBinding({
    name: "kendoLinearGauge",
    defaultOption: VALUE,
    watch: {
        value: VALUE,
        gaugeArea: extendAndRedraw("gaugeArea"),
        pointer: extendAndRedraw("pointer"),
        scale: extendAndRedraw("scale")
    }
});

createBinding({
    name: "kendoQRCode",
    watch: {
        value: VALUE
    }
});

createBinding({
    name: "kendoRadialGauge",
    defaultOption: VALUE,
    watch: {
        value: VALUE,
        gaugeArea: extendAndRedraw("gaugeArea"),
        pointer: extendAndRedraw("pointer"),
        scale: extendAndRedraw("scale")
    }
});

createBinding({
    name: "kendoSparkline",
    watch: {
        data: function (value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});


createBinding({
    name: "kendoStockChart",
    watch: {
        data: function(value) {
            ko.kendo.setDataSource(this, value);
        }
    }
});

}));