Kendo UI Web – Configuration object instead of function parameters – Form validation



Kendo UI Web – Configuration object instead of function parameters – Form validation

1 0


javascript-kendo

An introduction to Kendo UI JavaScript framework

On Github redaemn / javascript-kendo

If you find this presentation useful, please let me know
made with ♡ by ReDaemn using reveal.js

Kendo UI Web

an introduction

Presentation goal

  • Highlight the main features that Kendo UI Web framework has to offer
  • Show how it is possible to extend the framework with custom features
  • Learn from Kendo UI code how to write better JavaScript code

Javascript objects (advanced)

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

Configuration object instead of function parameters

  • widely used pattern
  • function definition with many parameters:
    function createWindow (height, width, animationEnter, animationExit, modal) { /* ... */ }
    
    var window = createWindow(50, 50, true, true, false);
  • when calling the function, the API is cryptic
  • parameters must be passed in the correct order (easy to introduce bugs)
  • when the API changes, new parameters can only be added at the end to avoid braking existing code

Configuration object instead of function parameters

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

The framework

  • based on jQuery
  • it's more than just widgets:
    • utilities (kendo.toString, kendo.parseDate, kendo.parseInt, …)
    • MVVM
    • various features to improve and simplify your code's structure (templates, class inheritance, form validation, …)
  • two companion frameworks: Kendo UI Mobile and Kendo UI DataViz

Utilities

kendo global namespace contains many utitlities methods useful to accomplish common tasks:

  • kendo.toString(): formats a Number or Date using the specified format and the current culture
  • kendo.parseDate(): parses a formatted string as a Date
  • kendo.template(): compiles a template to a function that builds HTML
  • many more, take a look at the API documentation

Widgets

  • there are many existing widgets that you can use out-of-the-box
  • how to use a widget:
    $("#dialog").kendoWindow({
      height: 400,
      width: 400,
      title: "Window title",
      actions: [ "Close", "Maximize" ],
      modal: true,
      visible: false
    });
    
    var dialog = $("#dialog").data('kendoWindow');
    
    dialog.open();

    Live DEMO!

Form validation

  • a feature useful in many projects
  • there are many standard validators available out-of-the-box
  • easy to use
    <form id="myForm">
      <input type="text" name="name" required="">
    </form>
    var validator = $("#myForm").kendoValidator().data('kendoValidator');
    
    validator.validate();
  • easy to extend with custom validators

Creating a custom validator

$("#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"
  }

});

Live DEMO!

Make it reusable:

kendo.ui.validator.rules.validName = function (input) { /* ... */ };

kendo.ui.validator.messages.validName = "...";

MVVM

One-way custom binding

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();
    }
  }
});

Live DEMO!

Two-way custom binding

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

Live DEMO!

Creating a custom widget

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

Creating a custom widget

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

Creating a custom widget

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>

Creating a custom widget

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;
}

Creating a custom widget

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);

Live DEMO!

In the constructor, highlight the benefit of "var that = this" inside the "mouseover" handler

Creating a custom widget

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

Using widget events

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

Live DEMO!

Creating a custom widget

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);

Live DEMO!

Resources and Credits

(in random order)