<h1>Kendo MultiSelect</h1>
<select kendo-multi-select
k-data-text-field="'Name'"
k-data-value-field="'Id'"
k-data-bind="{
value: 'Modelbind="Model.Multiselect',
name: 'kendoMultiSelect'
Multiselect" }"
k-options="options.multiselect"></select>
<hr />
(function () {
(function ($) {
$.compare = function (d, t) {
return $(d)[0] === $(t)[0];
};
})(jQuery);
/**
* a plugin for kendo that will bypass the k-model and k-ng-model to try and bind
* more appropriately with real data, instead of the primitives that kendo usually
* works with using angular.
**/
angular.module('kendo.directives')
.directive("kDataBind", ["$parse", "$filter", "$sce", function ($parse, $filter, $sce) {
return {
restrict: "A",
scope: false,
link: function (scope, element, attributes, controller) {
var evaluatedgetter = scope.$eval$parse(attributes.kDataBind);,
var getter setter = $parse(evaluatedgetter.value);assign,
var setter = getter // the property for the kendo data value field, if it is given.assign; this
var name = evaluated // property is needed by some of the widgets so that it can compare selected
// information to that stored in the widget's bound data source, and pull full
// objects out without trouble.name;
var _widget; property = $parse(attributes.kDataValueField)() || null,
// a function to fire when the widget changes, encapsulated here
// simply to provide more consistency.
onChange = function (t, f, event) {
t.bind(event || 'change', function (e) {
scope.$on$apply('kendoWidgetCreated'function () {
f(e);
});
});
},
// a function to fire when the model value changes, encapsulated here
// simply to provide more consistency.
onWatch = function (eventf) {
scope.$watch(getter, function (n, o) {
f(n, o);
});
},
// a function to fire when the widget is finished rendering, encapsulated here
// simply to provide more consistency. This is the best place to cause the
// widget to 'default' to a state that mirrors the data it is bound to on the model
onRendered = function (f) {
scope.$on('kendoRendered', function () {
f();
});
};
scope.$on('kendoWidgetCreated', function (event, target) {
// first, make sure we're interacting with the actual widget we
// want to work on, and not a different one - since this event is
// raised for every kendo widget on the page
if ($.compare(widgettarget.element,)[0] === $(element)[0]) {
// set the widget
_widget = widget;
// assign the widget name for the rendered function
//name = widget.ns;
// determine behavior based on the specific widget. // this is really bad design, but itThis is the best we can do rightnecessary nowbecause
// until the kendo team updates the widgets to properly bind to real data
switch (name) {
case "kendoDropDownList":
each widget may be different. For example, the kendoDropDownList caseaccepts "kendoComboBox":information
// using (functionthe .select(n) {
// where applicablefunction, getbut the data valuekendoMultiSelect fieldonly fortakes bindinginformation behaviorusing
var property// =the $parse(attributes.kDataValueField)value(arr) ||function. null;
// we are using onWatch and onChange purely for matters of consistency. scopeThe goal is to
// keep as much of the code re-usable as possible for the sake of trying to tighten the
// behaviors between each other.$watch
switch ($parsetarget.options.name) {
case "DropDownList":
case "ComboBox":
(getterfunction (), {
onWatch(function (n, o) {
widgettarget.select(function (dataItem) {
return dataItem[property] === n[property];
});
});
widget.bindonChange('change'target, function (e) {
scope.$apply(function () {
setter(scope, e.sender.dataItem().toJSON());
});
});
})();
break;
case "kendoMultiSelect""MultiSelect":
(function () {
widget.bindonWatch('change', function (en, o) {
scopevar values = $.$applymap(function getter(scope), function(val, i){
setter(scope,return widget.dataItems().slice(0));val[property];
}); target.value(values);
});
})();
break;
case "kendoAutoComplete":
(function () {
widget.bindonChange('select'target, function (e) {
scope.$apply(function () {
setter(scope, e.sender.dataItem(e.itemtarget.indexdataItems()).toJSONslice()0);
});
});
onRendered(function () {
var values = $.map(getter(scope), function (val, i) {
return val[property];
}); target.value(values);
});
})();
break;
// if the widget exposes no name, then we might need to do more
// advanced means of determining the widget
case "kendoDatePicker":
case "kendoDateTimePicker":
case "kendoTimePicker""AutoComplete":
(function () {
widget.bindonChange('change'target, function (e) {
scope.$apply(function () {
setter(scope, e.sender.valuedataItem(e.item.index()).toJSON());
});
, }'select');
})();
break;
case "kendoGrid""DatePicker":
case "DateTimePicker":
case "TimePicker":
(function () {
widget.bindonWatch('change', function (en, o) {
scopetarget.$applyvalue(function kendo.parseDate(n));
{ });
onChange(target, function (e) {
setter(scope, e.sender.dataItem(e.sender.selectvalue()).toJSON());
});
} onRendered(function () {
target.value(kendo.parseDate(getter(scope)));
});
})();
break;
case "kendoColorPalette":
case "kendoMaskedTextBox":
case "kendoNumericTextBox":
case "kendoSlider""Grid":
(function () {
widget.bindonChange('change'target, function (e) {
scope.$apply(function () {
setter(scope, e.sender.valuedataItem(e.sender.select());
}.toJSON());
});
})();
break;
case "kendoEditor""ColorPalette":
case "MaskedTextBox":
case "NumericTextBox":
case "Slider":
case "Editor":
(function () {
widget.bindonWatch('change', function (en, o) {
element.html(e.sendertarget.value()n);
scope.$apply(function (}) {;
setteronChange(scopetarget, e.sender.valuefunction ()e); {
}setter(scope, e.sender.value());
});
onRendered(function () {
target.value(getter(scope));
});
})();
break;
case "kendoListView""ListView":
(function () {
widget.bindonChange('change'target, function (e) {
// get the actual objects that were selected
var _datadata = widgettarget.dataSource.view(),
selected = $.map(widgettarget.select(), function (item) {
return _data[$data[$(item).index()];
});
scope.$apply(function (}) {;
setter(scope, selected);
});
});
})();
break;
}
}
});
scope.$on('kendoRendered', function () {
switch (name) {
case "kendoMultiSelect":
(function () {
var property = $parse(attributes.kDataValueField)();
// parse all of the data that has been bound to the proxy
var values = $.map(getter(scope), function (val, i) {
return val[property];
});
_widget.value(values);
})();
break;
case "kendoDatePicker":
case "kendoDateTimePicker":
case "kendoTimePicker":
(function () {
_widget.value(kendo.parseDate(getter(scope)));
})();
break;
case "kendoEditor":
case "kendoMaskedTextBox":
case "kendoNumericTextBox":
case "kendoSlider":
(function () {
_widget.value(getter(scope));
})();
break;
}
});
}
}
}]);
})();
I tried to do this firstcompletely updated the entire directive with much cleaner code and a smoother way of operating, without havingthe need for a
name
name field on the directive! Also removed the need for a second "switch", but that proved difficult since there are some widgets that do not expose their name withand homogenized the.ns
propertybehavior functions for cleanliness and clearer reading.I actually didn't want to use a
switch
, but given the simple logic of what I was doing, it seemed appropriate. The self-invoking functions are mostly for cleanliness, as I'm just accustomed to wrapping anonymous functions like that.the
$.compare
function isn't really necessary, it just looked cleaner to me to do it that way.I would really like to find a way to eliminate that
name
property, and maintain the same consistency. But some widgets (such asKendoGrid
) do not expose their name, and so determining them has proven very difficult for me.
<h1>Kendo MultiSelect</h1>
<select kendo-multi-select
k-data-text-field="'Name'"
k-data-value-field="'Id'"
k-data-bind="{
value: 'Model.Multiselect',
name: 'kendoMultiSelect'
}"
k-options="options.multiselect"></select>
<hr />
(function () {
(function ($) {
$.compare = function (d, t) {
return $(d)[0] === $(t)[0];
};
})(jQuery);
/**
* a plugin for kendo that will bypass the k-model and k-ng-model to try and bind
* more appropriately with real data, instead of the primitives that kendo usually
* works with using angular.
**/
angular.module('kendo.directives')
.directive("kDataBind", ["$parse", "$filter", "$sce", function ($parse, $filter, $sce) {
return {
restrict: "A",
scope: false,
link: function (scope, element, attributes, controller) {
var evaluated = scope.$eval(attributes.kDataBind);
var getter = $parse(evaluated.value);
var setter = getter.assign;
var name = evaluated.name;
var _widget;
scope.$on('kendoWidgetCreated', function (event, widget) {
// first, make sure we're interacting with the actual widget we
// want to work on, and not a different one - since this event is
// raised for every kendo widget on the page
if ($.compare(widget.element, element)) {
// set the widget
_widget = widget;
// assign the widget name for the rendered function
//name = widget.ns;
// determine behavior based on the specific widget. // this is really bad design, but it is the best we can do right now
// until the kendo team updates the widgets to properly bind to real data
switch (name) {
case "kendoDropDownList":
case "kendoComboBox":
(function () {
// where applicable, get the data value field for binding behavior
var property = $parse(attributes.kDataValueField)() || null;
scope.$watch($parse(getter), function (n, o) {
widget.select(function (dataItem) {
return dataItem[property] === n[property];
});
});
widget.bind('change', function (e) {
scope.$apply(function () {
setter(scope, e.sender.dataItem().toJSON());
});
});
})();
break;
case "kendoMultiSelect":
(function () {
widget.bind('change', function (e) {
scope.$apply(function () {
setter(scope, widget.dataItems().slice(0));
});
});
})();
break;
case "kendoAutoComplete":
(function () {
widget.bind('select', function (e) {
scope.$apply(function () {
setter(scope, e.sender.dataItem(e.item.index()).toJSON());
});
});
})();
break;
// if the widget exposes no name, then we might need to do more
// advanced means of determining the widget
case "kendoDatePicker":
case "kendoDateTimePicker":
case "kendoTimePicker":
(function () {
widget.bind('change', function (e) {
scope.$apply(function () {
setter(scope, e.sender.value().toJSON());
});
});
})();
break;
case "kendoGrid":
(function () {
widget.bind('change', function (e) {
scope.$apply(function () {
setter(scope, e.sender.dataItem(e.sender.select()).toJSON());
});
});
})();
break;
case "kendoColorPalette":
case "kendoMaskedTextBox":
case "kendoNumericTextBox":
case "kendoSlider":
(function () {
widget.bind('change', function (e) {
scope.$apply(function () {
setter(scope, e.sender.value());
});
});
})();
break;
case "kendoEditor":
(function () {
widget.bind('change', function (e) {
element.html(e.sender.value());
scope.$apply(function () {
setter(scope, e.sender.value());
});
});
})();
break;
case "kendoListView":
(function () {
widget.bind('change', function (e) {
// get the actual objects that were selected
var _data = widget.dataSource.view(),
selected = $.map(widget.select(), function (item) {
return _data[$(item).index()];
});
scope.$apply(function () {
setter(scope, selected);
});
});
})();
break;
}
}
});
scope.$on('kendoRendered', function () {
switch (name) {
case "kendoMultiSelect":
(function () {
var property = $parse(attributes.kDataValueField)();
// parse all of the data that has been bound to the proxy
var values = $.map(getter(scope), function (val, i) {
return val[property];
});
_widget.value(values);
})();
break;
case "kendoDatePicker":
case "kendoDateTimePicker":
case "kendoTimePicker":
(function () {
_widget.value(kendo.parseDate(getter(scope)));
})();
break;
case "kendoEditor":
case "kendoMaskedTextBox":
case "kendoNumericTextBox":
case "kendoSlider":
(function () {
_widget.value(getter(scope));
})();
break;
}
});
}
}
}]);
})();
I tried to do this first without having a
name
field, but that proved difficult since there are some widgets that do not expose their name with the.ns
property.I actually didn't want to use a
switch
, but given the simple logic of what I was doing, it seemed appropriate. The self-invoking functions are mostly for cleanliness, as I'm just accustomed to wrapping anonymous functions like that.the
$.compare
function isn't really necessary, it just looked cleaner to me to do it that way.I would really like to find a way to eliminate that
name
property, and maintain the same consistency. But some widgets (such asKendoGrid
) do not expose their name, and so determining them has proven very difficult for me.
<h1>Kendo MultiSelect</h1>
<select kendo-multi-select
k-data-text-field="'Name'"
k-data-value-field="'Id'"
k-data-bind="Model.Multiselect"
k-options="options.multiselect"></select>
<hr />
(function () {
/**
* a plugin for kendo that will bypass the k-model and k-ng-model to try and bind
* more appropriately with real data, instead of the primitives that kendo usually
* works with using angular.
**/
angular.module('kendo.directives')
.directive("kDataBind", ["$parse",function ($parse) {
return {
restrict: "A",
scope: false,
link: function (scope, element, attributes, controller) {
var getter = $parse(attributes.kDataBind),
setter = getter.assign,
// the property for the kendo data value field, if it is given. this
// property is needed by some of the widgets so that it can compare selected
// information to that stored in the widget's bound data source, and pull full
// objects out without trouble.
property = $parse(attributes.kDataValueField)() || null,
// a function to fire when the widget changes, encapsulated here
// simply to provide more consistency.
onChange = function (t, f, event) {
t.bind(event || 'change', function (e) {
scope.$apply(function () {
f(e);
});
});
},
// a function to fire when the model value changes, encapsulated here
// simply to provide more consistency.
onWatch = function (f) {
scope.$watch(getter, function (n, o) {
f(n, o);
});
},
// a function to fire when the widget is finished rendering, encapsulated here
// simply to provide more consistency. This is the best place to cause the
// widget to 'default' to a state that mirrors the data it is bound to on the model
onRendered = function (f) {
scope.$on('kendoRendered', function () {
f();
});
};
scope.$on('kendoWidgetCreated', function (event, target) {
// first, make sure we're interacting with the actual widget we
// want to work on, and not a different one - since this event is
// raised for every kendo widget on the page
if ($(target.element)[0] === $(element)[0]) {
// determine behavior based on the specific widget. This is necessary because
// each widget may be different. For example, the kendoDropDownList accepts information
// using the .select(n) function, but the kendoMultiSelect only takes information using
// the .value(arr) function.
// we are using onWatch and onChange purely for matters of consistency. The goal is to
// keep as much of the code re-usable as possible for the sake of trying to tighten the
// behaviors between each other.
switch (target.options.name) {
case "DropDownList":
case "ComboBox":
(function () {
onWatch(function (n, o) {
target.select(function (dataItem) {
return dataItem[property] === n[property];
});
});
onChange(target, function (e) {
setter(scope, e.sender.dataItem().toJSON());
});
})();
break;
case "MultiSelect":
(function () {
onWatch(function (n, o) {
var values = $.map(getter(scope), function(val, i){
return val[property];
}); target.value(values);
});
onChange(target, function (e) {
setter(scope, target.dataItems().slice(0));
});
onRendered(function () {
var values = $.map(getter(scope), function (val, i) {
return val[property];
}); target.value(values);
});
})();
break;
case "AutoComplete":
(function () {
onChange(target, function (e) {
setter(scope, e.sender.dataItem(e.item.index()).toJSON());
}, 'select');
})();
break;
case "DatePicker":
case "DateTimePicker":
case "TimePicker":
(function () {
onWatch(function (n, o) {
target.value(kendo.parseDate(n));
});
onChange(target, function (e) {
setter(scope, e.sender.value().toJSON());
});
onRendered(function () {
target.value(kendo.parseDate(getter(scope)));
});
})();
break;
case "Grid":
(function () {
onChange(target, function (e) {
setter(scope, e.sender.dataItem(e.sender.select()).toJSON());
});
})();
break;
case "ColorPalette":
case "MaskedTextBox":
case "NumericTextBox":
case "Slider":
case "Editor":
(function () {
onWatch(function (n, o) {
target.value(n);
});
onChange(target, function (e) {
setter(scope, e.sender.value());
});
onRendered(function () {
target.value(getter(scope));
});
})();
break;
case "ListView":
(function () {
onChange(target, function (e) {
var data = target.dataSource.view(),
selected = $.map(target.select(), function (item) {
return data[$(item).index()];
});
setter(scope, selected);
});
})();
break;
}
}
});
}
}
}]);
})();
I completely updated the entire directive with much cleaner code and a smoother way of operating, without the need for a name field on the directive! Also removed the need for a second "switch", and homogenized the behavior functions for cleanliness and clearer reading.
I actually didn't want to use a
switch
, but given the simple logic of what I was doing, it seemed appropriate. The self-invoking functions are mostly for cleanliness, as I'm just accustomed to wrapping anonymous functions like that.