On Github redaemn / javascript-kendo
an introduction
JavaScript’s inheritance model is fundamentally different from classical inheritance, in which classes inherit from other classes, and objects constitute instances of classes.
JavaScript does not have classes. Instead, JavaScript offers prototypes and prototype-based inheritance in which objects inherit from other objects.
It is possible to implement differt types of inheritance, based on your particular needs (Pseudo-classical Inheritance, Private Members and Privileged Methods, …)
But this is another story, maybe in the next presentation :)
Many frameworks (Kendo among them) offer their own implementation
function createWindow (height, width, animationEnter, animationExit, modal) { /* ... */ } var window = createWindow(50, 50, true, true, false);
Use a configuration object instead!
function createWindow (config) { var defaultConfig = { height: 70, width: 100, animation: { enter: true, exit: false }, modal: false } config = $.extend({}, defaultConfig, config); /* ... */ } var window = createWindow({ height: 50, width: 50, animation: { enter: true, exit: true } });
kendo global namespace contains many utitlities methods useful to accomplish common tasks:
$("#dialog").kendoWindow({ height: 400, width: 400, title: "Window title", actions: [ "Close", "Maximize" ], modal: true, visible: false }); var dialog = $("#dialog").data('kendoWindow'); dialog.open();
<form id="myForm"> <input type="text" name="name" required=""> </form>
var validator = $("#myForm").kendoValidator().data('kendoValidator'); validator.validate();
$("#myform").kendoValidator({ rules: { validName: function (input) { if (input.is('[valid-name]')) { var value = input.val(); return value === "" || value === "John"; } return true; } }, messages: { validName: "John is the only valid name" } });
Make it reusable:
kendo.ui.validator.rules.validName = function (input) { /* ... */ }; kendo.ui.validator.messages.validName = "...";
kendo.data.binders.slide = kendo.data.Binder.extend({ refresh: function() { var value = this.bindings["slide"].get(), content = $(this.element).find('.content'); if (value) { content.slideDown(); } else { content.slideUp(); } } });
kendo.data.binders.slide = kendo.data.Binder.extend({ init: function(element, bindings, options) { /* call the base constructor */ kendo.data.Binder.fn.init.call(this, element, bindings, options); var that = this; /* listen for the change event of the element */ $(that.element).on("click", function() { that.change(); /* call the change function */ }); }, refresh: function() { var value = this.bindings["slide"].get(), content = $(this.element).find('.content'); if (value) { content.slideDown(); } else { content.slideUp(); } }, change: function() { var value = this.bindings["slide"].get(); /* update the View-Model */ this.bindings["slide"].set(!value); } });
Widget boilerplate
(function($, kendo, undefined) { var PREFIX = "Reply", WIDGET = kendo.ui.Widget; var Rating = WIDGET.extend({ /* widget code goes here */ }); kendo.ui.plugin(Rating, kendo.ui, PREFIX); }(window.jQuery, window.kendo));- Highlight examples of standard JavaScript patterns (closure, namespace). - Show inheritance implemented by kendo
Extending an existing widget
var Rating = WIDGET.extend({ init: function (element, options) { var that = this; WIDGET.fn.init.call(that, element, options); /* widget initialization */ }, options: { /* widget options */ }, destroy: function() { var that = this; /* widget destruction */ WIDGET.fn.destroy.call(that); } });
HTML code
<ul id="rating-widget"> <li data-value="1" title="One"></li> <li data-value="2" title="Two"></li> <li data-value="3" title="Three"></li> <li data-value="4" title="Four"></li> <li data-value="5" title="Five"></li> </ul>
Widget style
.reply-rating { margin: 0; padding: 0; list-style-type: none; cursor: default; display: inline-block; } .reply-rating li { margin: 0; padding: 0; display: inline-block; cursor: pointer; } .reply-rating li.reply-rating-star-full:after { content: "\2605"; font-family: Arial; font-size: 16px; color: orange; } .reply-rating li.reply-rating-star-empty:after { content: "\2606"; font-family: Arial; font-size: 16px; color: gray; }
First version of the widget
(function($, kendo, undefined) { var NS = ".replyRating", PREFIX = "Reply", WIDGET = kendo.ui.Widget, PROXY = $.proxy, STAR = "li", MOUSEOVER = "mouseover", MOUSELEAVE = "mouseleave", CLICK = "click", REPLY_RATING = "reply-rating"; var Rating = WIDGET.extend({ init: function (element, options) { var that = this; WIDGET.fn.init.call(that, element, options); element = that.element; options = that.options; element.addClass(REPLY_RATING) .on(MOUSEOVER + NS, STAR, function(e) { that._mouseover(e); }) .on(MOUSELEAVE + NS, PROXY(that._mouseleave, that)) .on(CLICK + NS, STAR, PROXY(that._select, that)); element.find(STAR).addClass(options.starEmptyClass); that.value(options.value); }, options: { prefix: PREFIX, name: "Rating", starEmptyClass: "reply-rating-star-empty", starFullClass: "reply-rating-star-full", value: null }, value: function(value) { var that = this; if (value === undefined) { return that._currentValue; } else { that._currentValue = value; that._render(value); } }, _mouseover: function(e) { var that = this, star = $(e.currentTarget), value = star.data('value'); that._render(value); }, _mouseleave: function() { var that = this; that._render(that.value()); }, _select: function(e) { var that = this, star = $(e.currentTarget), value = star.data('value'); that.value(value); }, _render: function(value) { var that = this, opt = that.options, star = that.element.find(STAR + '[data-value="' + value + '"]'); if (value === null || value === undefined || star.length === 0) { that.element.find(STAR).removeClass(opt.starFullClass).addClass(opt.starEmptyClass); } else { star.prevAll(STAR).removeClass(opt.starEmptyClass).addClass(opt.starFullClass); star.removeClass(opt.starEmptyClass).addClass(opt.starFullClass); star.nextAll(STAR).removeClass(opt.starFullClass).addClass(opt.starEmptyClass); } }, destroy: function() { var that = this; that.element.off(NS); WIDGET.fn.destroy.call(that); } }); kendo.ui.plugin(Rating, kendo.ui, PREFIX); })(window.jQuery, window.kendo);In the constructor, highlight the benefit of "var that = this" inside the "mouseover" handler
Adding events
(function($, kendo, undefined) { var SELECT = "select", MOUSEOVER = "mouseover", MOUSELEAVE = "mouseleave"; var Rating = WIDGET.extend({ /* ... */ events: [ MOUSEOVER, MOUSELEAVE, SELECT ], _mouseover: function(e) { var that = this, star = $(e.currentTarget), value = star.data('value'); that._render(value); that.trigger(MOUSEOVER, { value: value, item: star }); }, _mouseleave: function() { var that = this; that._render(that.value()); that.trigger(MOUSELEAVE); }, _select: function(e) { var that = this, star = $(e.currentTarget), value = star.data('value'); that.value(value); that.trigger(SELECT, { value: value, item: star }); } }); kendo.ui.plugin(Rating, kendo.ui, PREFIX); })(window.jQuery, window.kendo);talk about observer class implemented by kendo
/* attach handler at widget creation */ var ratingWidget = $('.star-rating').kendoReplyRating({ mouseover: function(e) { /* do something here */ } }).data('kendoReplyRating'); /* attach handler after widget creation */ ratingWidget.bind('select', function(e) { /* do something here */ });
MVVM aware widgets
(function($, kendo, undefined) { var CHANGE = "change"; var Rating = WIDGET.extend({ /* ... */ events: [ /* ... */ CHANGE ], value: function(value) { /* ... */ }, _select: function(e) { /* ... */ that.trigger(CHANGE); } }); kendo.ui.plugin(Rating, kendo.ui, PREFIX); })(window.jQuery, window.kendo);