Skip to content

How to build and use aurelia kendoui plugin

Nikolaj Ivancic edited this page Apr 13, 2016 · 1 revision

The internal organization of this plugin is slightly different (we believe that it is more convenient) than Aurelia's standard plugin.

The following three sections describe the details of the plugin structure and building process


Section 1 - Plugin code

The plugin's src folder contains the common subfolder with utilities used by more than one KendoUI controls "wrapped" by this plugin. The class events.js for example handles the creation of "fake" input events that need to be created in order to allow Aurelia to handle the jQuery raised events. If this sounds confusing, please check the blog jquery-ui datepicker with Aurelia by Jeremy Danyow

function createEvent(name) {  
  var event = document.createEvent('Event');
  event.initEvent(name, true, true);
  return event;
}

export function fireEvent(element, name) {  
  var event = createEvent(name);
  element.dispatchEvent(event);
}

In addition to the just described utility of the common folder, the src folder contains a subfolder for each of the KendoUI control that is wrapped (componentized) by this plugin. The above screenshot indicates that the current version of the plugin wraps KendoUI autocomplete and button controls:

File autocomplete.js
import {customAttribute, bindable, bindingMode, inject} from 'aurelia-framework';
import {fireEvent} from '../common/events';
import {pruneOptions} from '../common/options';
import $ from 'jquery';
import 'kendo-ui/src/js/kendo.autocomplete';

@customAttribute('au-kendo-autocomplete')
//@bindable({ name: 'value', changeHandler: 'valueChanged', defaultValue: [], defaultBindingMode: bindingMode.twoWay })
//@bindable({ name: 'data', changeHandler: 'dataChanged', defaultValue: [], defaultBindingMode: bindingMode.oneWay })
//@bindable({ name: 'options', changeHandler: 'optionsChanged', defaultValue: {}, defaultBindingMode: bindingMode.oneWay })
//@bindable({ name: 'class', defaultValue: '', defaultBindingMode: bindingMode.oneWay })
@inject(Element)
export class AuKendoAutoComplete {

    element;

    // Autocomplete API
    // Full options object if you want to set options that way
    @bindable options = {};

    // Individual properties - default values need setting
    @bindable animation;
    @bindable dataSource;
    @bindable dataTextField = null;
    @bindable delay = 200;
    @bindable enable = true;
    @bindable filter = "startswith";
    @bindable fixedGroupTemplate;
    @bindable groupTemplate;
    @bindable height;
    @bindable highlightFirst;
    @bindable ignoreCase;
    @bindable minLength;
    @bindable placeholder;
    @bindable popup;
    @bindable separator = "";
    @bindable suggest = true;
    @bindable headerTemplate;
    @bindable template;
    @bindable valuePrimitive;
    @bindable virtual;

    // Aurelia value-added API
    @bindable value;

    constructor(element) {
        this.element = element;
    }

    bind() {
        this._component = $(this.element).kendoAutoComplete(this.getOptions()).data("kendoAutoComplete");

        this._component.bind('change', (event) => {
            this.value = event.sender.value();

            // Update the kendo binding
            fireEvent(this.element, 'input');
        });

        this._component.bind('select', (event) => {
            this.value = event.sender.value();

            // Update the kendo binding
            fireEvent(this.element, 'input');
        });
    }

    detached() {
        this._component.destroy();
    }
    
    getOptions() {
        var options = pruneOptions({
            animation: this.animation,
            dataSource: this.dataSource,
            dataTextField: this.dataTextField,
            delay: this.delay,
            enable: this.enable,
            filter: this.filter,
            fixedGroupTemplate: this.fixedGroupTemplate,
            groupTemplate: this.groupTemplate,
            height: this.height,
            highlightFirst: this.highlightFirst,
            ignoreCase: this.ignoreCase,
            minLength: this.minLength,
            placeholder: this.placeholder,
            popup: this.popup,
            separator: this.separator,
            suggest: this.suggest,
            headerTemplate: this.headerTemplate,
            template: this.template,
            valuePrimitive: this.valuePrimitive,
            virtual: this.virtual
        });

        return Object.assign({}, this.options, options);
    }

    enableChanged(newValue) {
        if (this._component)
            this._component.enable(newValue);
    }
}
File button.js
import {customAttribute, bindable, inject} from 'aurelia-framework';
import {pruneOptions} from '../common/options';
import $ from 'jquery';
import 'kendo-ui/src/js/kendo.button';

@customAttribute('au-kendo-button')
@inject(Element)
export class AuKendoButton {

    _component;

    @bindable enable = true;
    @bindable icon;
    @bindable imageUrl;
    @bindable spriteCssClass;

    @bindable options;

    constructor(element) {
        this.element = element;
        this.options = {};
    }

    bind() {
        this._component = $(this.element).kendoButton(this.getOptions()).data('kendoButton');
    }

    detached() {
    	if(this._component)
	        this._component.destroy();
    }

    getOptions() {
        
        var options = pruneOptions({
            icon: this.icon,
            enable: this.enable,
            imageUrl: this.imageUrl,
            spriteCssClass: this.spriteCssClass
        });

        return Object.assign({}, this.options, options);
    }

    enableChanged(newValue) {
    	if(this._component)
    		this._component.enable(newValue);
    }
}
File index.js

This file declares the resources (KendoUI wrapped controls) that this plugin exports - with the intent to provide them to the plugin consumer (sample application residing in the sample folder) as API.

// TODO: Do we import the common styles here or let the user do it in their app?
import 'kendo-ui/src/styles/web/kendo.common.core.css!';

export function configure(aurelia){

	var resources = [
		'button/button',
		'autocomplete/autocomplete'
	];
	
	aurelia.globalResources(resources);
}
Note: the intent to consume the exports from the above presented file index.js is declared in the sample/src/main.js class:
...
aurelia.use
        .standardConfiguration()
        .developmentLogging()
        .plugin('aurelia-kendoui-plugin');

...

Section 2 Sample Application that acts as the Plugin consumer

##### Color codes: - **yellow**: application runtime - created by running the **`jspm install`** command in the **`aurelia-kendoui-plugin/sample`** folder - **orange**: KendoUI controls hosted by this sample app (these controls are the consumers of the Aurelia-KendoUI-plugin) - **bluish**: Standard Aurelia application files collection - **olive**: Standard Aurelia application configuration/ css / html / js files

Sample application showing the KendoUI Autocomplete control:


Section 3 - How to build this plugin and sample application - both in the same project

At this point it makes a lot of sense to stop looking at all details and go back to a very high level view of this "combo" (plugin and consuming app in the same project).

This relationship between the sample app and the plugin is established as follows below.

In the standard situation - like the case with Aurelia-Skeleton-Navigation app, the application's bootstraping process starts in the index.html file by loading the two generated files:

In our specific case, the index.html is constructed differently - as shown on the following screen shot:

This "dynamically constructed" instance of the system.js class allows this sample to use the name of the plugin as specified in the package.json file rather than depending on the more static specification of the plugin name that would have to exist in jspm directory or in github - like the case with defining the access to kendo-ui-core module:

"telerik/kendo-ui-core": "github:telerik/kendo-ui-core@^2015.3.930"

which is defined here

Clone this wiki locally