aurelia-ui-toolkits / aurelia-syncfusion-bridge

27 stars 21 forks source link

Quick bridge developer's tutorial #4

Closed adriatic closed 8 years ago

adriatic commented 8 years ago

Introduction

This document explains the basic structure of the Syncfusion bridge (implemented as a typical Aurelia plugin) by defining the process of adding the simplest fictitious ej-widget called click-counter shown below:



Image 1

Note the Image 1 shows the initial temporary UI for the Aurelia Syncfusion bridge's Catalog application which will be used until all ej widgets needed to render the complete Catalog's User Interface become available as Aurelia Components, implemented in the Syncfusion bridge. At that time the Catalog application will be similar to this (KendoUI version)


Image 2

adriatic commented 8 years ago

Internal structure



Image 3

As Image 3 depicts, the Aurelia Syncfusion bridge is a composite application consisting of two main parts:

  1. The bridge - which contains all of the code needed to "convert" native ej widgets to Aurelia components. All of the bridge code is contained in this folder



Image 4

  1. The catalog - which renders all of the Aurelia ej components exported by the bridge. All of the catalog code is contained in this folder.



Image 5

adriatic commented 8 years ago

1. Adding clickcounter widget to the bridge

Step 1.1 Create widget wrapper

Each widget wrapper consists of at least one file - JavaScript class that implements the widget. There is also an optional html file.

In this document we will create the src/clickcounter folder with the following content

clickounter.js

import {customElement} from 'aurelia-templating';
import {constants} from '../common/constants';

@customElement(`${constants.elementPrefix}click-counter`)
export class ClickCounter {
  count = 0;

  increment() {
    this.count++;
  }
}

Note that this code defines the clickcounter Aurelia component as a custom element.


Step 1.2 Update config-builder code



Image 6

Hints:

useAll() method returns the list of all widgets that need to be wrapped. This list consists of a single clickounter.js JavaScript class.


Step 1.3 Update builder infrastructure

Define Syncfusion bridge global constants:

export const constants = {
  eventPrefix: 'ej-on',
  bindablePrefix: 'ej-',
  attributePrefix: 'ej-',
  elementPrefix: 'ej-'
};
adriatic commented 8 years ago

2. Adding clickcounter component to the catalog application.

Image 7 below is an expanded view of the Image 5 - presenting more details of the catalog application's structure:



Image 7

Step 2.1 Add sample/src/samples/click-counter folder

This folder should have the following files

2.1.1 basic-use.html

<template>
    <div>
        <div>
            <div>
                <h4>Basic Click control</h4>
                <ej-click-counter></ej-click-counter>
            </div>
        </div>
    </div>
</template>

2.1.2 basic-use.js

export class BasicUse {}

2.1.3 index.js

import {useView, inject} from 'aurelia-framework';
import {Registry} from 'shared/registry';

@useView('shared/showcase.html')
@inject(Registry)
export class Index {

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

  configureRouter(config, router) {
    this.router = router;

    return this.registry.load(config, 'click-counter');
  }
}

2.1.4 registry.json

{
  "title": "Click counter",
  "samples": {
    "basic-use": {
      "default" : true,
      "files": ["html", "js"]
    }
  }
}

Step 2.2 Add sample/src/samples folder with page rendering infrastructure

index.html

<template>
    <require from="./menu"></require>
    <menu router.bind="router"></menu>

    <router-view></router-view>
</template>

index.js

import {inject} from 'aurelia-framework';
import {Registry} from 'shared/registry';
import { ComponentService } from '../shared/component-service';

@inject(Registry, ComponentService)
export class Index {

  constructor(registry, componentService) {
    this.registry = registry;
    this.componentService = componentService;
    this.routerConfig = componentService.getRouterConfig(true);
  }
  configureRouter(config, router) {
    config.title = 'Samples';

    // config.map([
    //   { name: 'default', route: '', redirect: 'click-counter' },
    //   { name: 'click-counter', route: 'click-counter', moduleId: './click-counter/index', title: 'Click-counter' },
    //   { name: 'navbar', route: 'navbar', moduleId: './navbar/index', title: 'Navbar' },
    //   { name: 'navs', route: 'navs', moduleId: './navs/index', title: 'Navs' },
    //   { name: 'button', route: 'button', moduleId: './button/index', title: 'Button' },
    //   { name: 'collapse', route: 'collapse', moduleId: './collapse/index', title: 'Collapse' },
    //   { name: 'panel', route: 'panel', moduleId: './panel/index', title: 'Panel' }
    // ]);
    this.routerConfig.unshift({ name: 'default', route: '', redirect: 'click-counter' });
    config.map(this.routerConfig);
    this.router = router;
  }
}

menu.html

<template>
  <div class="btn-group" role="group">
    <div repeat.for="category of categories" class="btn-group">
      <button class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
        ${category.title}<span class="caret"></span>
      </button>
      <ul class="dropdown-menu">
        <li repeat.for="ctrl of category.controls">
          <a href.bind="ctrl.link">${ ctrl.title }</a>
        </li>
      </ul>
    </div>
  </div>
</template>

menu.js

import {inject, bindable} from 'aurelia-framework';
import {DOM} from 'aurelia-pal';
import { ComponentService } from '../shared/component-service';
import json from './menu.json!';

@inject(Element, ComponentService)
export class Menu {

  @bindable router;

  constructor(element, componentService) {
    this.categories = componentService.getIterableComponents(true);
    this.element = element;
    this.componentService = componentService;
  }

  attached() {
    // this.generateRow(json);
  }

  generateRow(data) {
    let div = DOM.createElement('div');
    div.className = 'btn-group';
    div.setAttribute('role', 'group');

    this.element.appendChild(div);

    for (let key of Object.keys(data)) {
      let buttonDiv = DOM.createElement('div');
      buttonDiv.className = 'btn-group';
      buttonDiv.setAttribute('role', 'group');

      let button = DOM.createElement('button');
      button.className = 'btn btn-default dropdown-toggle';
      button.setAttribute('data-toggle', 'dropdown');
      button.setAttribute('aria-haspopup', 'true');
      button.setAttribute('aria-expanded', 'false');
      button.innerHTML = key + ' <span class="caret"></span>';
      buttonDiv.appendChild(button);

      let ulItem = DOM.createElement('ul');
      ulItem.className = 'dropdown-menu';
      for (let subNav of Object.keys(data[key])) {
        let liItem = DOM.createElement('li');
        let aItem = DOM.createElement('a');
        aItem.setAttribute('href', `#/samples/${data[key][subNav]}`);
        aItem.innerHTML = subNav;

        liItem.appendChild(aItem);
        ulItem.appendChild(liItem);
      }

      buttonDiv.appendChild(ulItem);
      div.appendChild(buttonDiv);
    }
  }
}

menu.json

{
  "Most popular": {
    "Click counter": "click-counter"
  }
}

Note: the above file will have to be changed for each newly added wrapper and component - it is the initial component chooser used to select a few initially implement components.




Step 2.3 Add sample/src/samples/shared folder with page rendering infrastructure

Note: at this time we will consider the content of this folder finished and will return to its content description only if something needs to be changed later.


Step 2.4 Observe sample/src/samples/ folder with application rendering infrastructure

Note: at this time we will consider the content of this folder finished and will return to its content description only if something needs to be changed later.


adriatic commented 8 years ago

3. Run the catalog to test the bridge

Since this application follows the es2016 application skeleton structure specification it should be built and run as described in this README.md file. Specifically, having all tooling installed, you simply type

gulp watch

after setting your console's current directory to the root of the local clone of the aurelia-syncfusion-bridge repository as shown below:

C:\work\aurelia-ui-toolkits\Syncfusion\aurelia-syncfusion-bridge (master)                 
λ ls                                                                                      
LICENSE    build      devbuild     jspm_packages  node_modules  sample  test              
README.md  config.js  gulpfile.js  karma.conf.js  package.json  src     wallaby.js        

C:\work\aurelia-ui-toolkits\Syncfusion\aurelia-syncfusion-bridge (master)                 
λ gulp watch                                                                              
Mydeen-SN commented 8 years ago

@bharathm03 - I have completed the button control creation for Aurelia bridge and currently working on adding of the button control in the catalog application.

Mydeen-SN commented 8 years ago

@bharathm03 -I have completed the catalog application integration and testcases for util and events classes and currently working on the unit test cases for the widgetBase class

bharathm03 commented 8 years ago

@Mydeen-SN also, you need to add JsDoc for APIs in common folder. It is better to rename the file widgetBase.js to widget-base.js to avoid issues at Linux machines. Also, ensure this in future.

Mydeen-SN commented 8 years ago

@bharathm03 -Added the JsDoc for the widget-base and events files and also complete the testcases for WidgetBase classes.Please find the pull request link below

11