Wednesday, March 1, 2017

KnockoutJS - Components

Components are a huge way of organizing the UI code for structuring a large application and promote code reusability.
  • It is inherited or nested from other component.
  • For loading and configuration, it defines its own conventions or logic.
  • It is packaged to reuse throughout the application or project.
  • Represents the complete sections of application or small controls/widgets.
  • Loaded or preloaded on demand.

Component registration

Components can register using the ko.components.register() API. It helps to load and represent the components in KO. Component name with configuration is expected for registration. The configuration specifies how to determine the viewModel and template.

Syntax

Components can be registered as follows:
ko.components.register('component-name', {
   viewModel: {...},    //function code
   template: {....) //function code
});
  • The component-name can be any nonempty string.
  • viewModel is optional, and can take any of the viewModel formats listed in next sections.
  • template is required, and can take any of the template formats listed in next sections.

Stating a ViewModel

Following table lists the viewModel formats that can be used to register the components:
S.N.viewModel Forms & Description
1constructor function
It creates a separate viewModel object for each component. The object or function are used to bind in components view.
function SomeComponentViewModel(params) {
    this.someProperty = params.something;
}
ko.components.register('component name', {
    viewModel: SomeComponentViewModel,
    template: ...
});
2shared object instance
The viewModel object instance is shared. The instance property is passed to use the object directly.
var sharedViewModelInstance = { ... };

ko.components.register('component name', {
    viewModel: { instance: sharedViewModelInstance },
    template: ...
});
3createViewModel
It calls a function which act as a factory and can be used as view model that can return an object.
ko.components.register('component name', {

    viewModel: {

        createViewModel: function(params, componentInfo) {

           ... //function code

           ...}

        },

        template: ....

        });
4AMD module
It is module format for defining modules where module and dependencies both are loaded asynchronously.
ko.components.register('component name', {
    viewModel: { require: 'some/module/name' },
    template: ...
});

define(['knockout'], function(ko) {
    function MyViewModel() {
        // ...
    }

    return MyViewModel;
});

Stating a Template

Following table lists the template formats that can be used to register the components:
S.N.Template Forms
1element ID
ko.components.register('component name', {
    template: { element: 'component-template' },
    viewModel: ...
});
2element instance
var elemInstance = document.getElementById('component-template');

ko.components.register('component name', {
    template: { element: elemInstance },
    viewModel: ...
});
3string of markup
ko.components.register('component name', {
    template: '<input data-bind="value: yourName" />\
               <button data-bind="click: addEmp">Add Emp </button>',
    viewModel: ...
});
4DOM nodes
var emp = [
    document.getElementById('node 1'),
    document.getElementById('node 2'),
];

ko.components.register('component name', {
    template: emp,
    viewModel: ...
});
5document fragement
ko.components.register('component name', {
    template: someDocumentFragmentInstance,
    viewModel: ...
});
6AMD module
ko.components.register('component name', {
    template: { require: 'some/template' },
    viewModel: ...
});

Components registered as a single AMD module

The AMD module can register a component by itself without using viewModel/template pair.
ko.components.register('component name',{ require: 'some/module'});

Component Binding

There are two ways of component binding:
  • Full syntax: It passes parameter and object to the component. It can pass using following properties:
    • name: It adds the component name.
    • params: It can pass multiple parameters in object on the component.
      <div data-bind='component: {
        name: "tutorials point",
        params: { mode: "detailed-list", items: productsList }
      }'>
      </div>
  • Shorthand syntax: It passes string as a component name and it does not include parameter in it.
    <div data-bind='component: "component name"'></div>

Observations

  • Template-only components: Components can only define template without specifing the viewModel.
    ko.components.register('component name', {
    template:'<input data-bind="value: someName" />,
    });
  • Using Component without a container element: Components can be used without using extra container element. This can be done by using containerless flow control which is similar as comment tag.
    <!--ko.component: ""-->
    <!--/ko-->

Custom Element

Custom element is a way for rendering a component. Here you can directly write a self descriptive markup element name instead of defining a placeholder where components are binded through it.
<products-list params="name: userName, type: userType"></products-list>

Passing Parameter

params attribute is used to pass the parameter to component viewModel. It is similar to data-bind attribute. The contents of the params attribute are interpreted like a JavaScript object literal (just like a data-bind attribute), so you can pass arbitrary values of any type. It can pass parameter in following ways:
  • Communication between parent and child components: The component is not instantiated by itself so the viewmodel properties are referred from outside of the component and thus would be received by child component viewmodel . For example you can see in below syntax that ModelValue is parent viewmodel and which is received by child viewModel constructor ModelProperty
  • Passing observable expressions: It has three values in params parameter:
    • simpleExpression: It is a numeric value. It does not involve any observables.
    • simpleObservable: It is an instance that is defined on parent viewModel. The parent viewModel will automatically get the changes on observable done by child viewModel.
    • observableExpression: Expression reads the observable when the expression is evaluated by itself. When observable value gets change then the result of expression could also get changes over time.
    We can pass the parameters as follows:
    <some-component
        params='simpleExpression: 1 + 1,
            simpleObservable: myObservable,
            observableExpression: myObservable() + 1'>
    </some-component>
We can pass the parameters in viewModel as below:
<some-component
    params='objectValue:{a: 3, b: 2},
        dateValue: new date(),
        stringValue: "Hi",
        numericValue:123,
        boolValue: true/false,
        ModelProperty: ModelValue'>
</some-component>

Passing markup into components

The received markup is used to create component and is been selected as a part of the output. Below nodes are passed as part of the output in the component template.
template: { nodes: $componentTemplateNodes }

Controlling custom element tag names

The names which you register in the components by using ko.components.register, the same name corresponds to the custom element tag names. We can change the custom element tag names by overriding it to control using getComponentNameForNode.
ko.components.getComponentNameForNode = function(node) {
...
... //function code
...
}

Registering custom elements

The custom elements can be made available immediately if the default component loader is used and hence the component is registered using ko.components.register. If we are not using the ko.components.register and implementing custom component loader, then you can use the custom element by defining any element name of your wish. There is no need to specify configuration when you are using ko.components.register as custom component loader does not use it anymore.
ko.components.register('custom-element', { ......... });

Example

<!DOCTYPE html>
<head>
<title>KnockoutJS Components</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
</head>
<body>
<!--params attribute is used to pass the parameter to component viewModel.-->
<click params="a: a, b: b"></click>

<!--template is used for a component by specifying its ID -->
<template id="click-l">
   <div data-bind="text: a"></div>

   <!--Use data-bind attribute to bind click:function() to ViewModel. -->
   <button data-bind="click:function(){callback(1)}">Increase</button>
   <button data-bind="click:function(){callback(-1)}">Decrease</button>
</template>

<script>
//Here components are registered
  ko.components.register('click', {
      viewModel: function(params) {
         self = this;
         this.a = params.a;
         this.b = params.b;

         this.callback = function(num){
            self.b(parseInt(num));
            self.a( self.a() + parseInt(num) );
        };
     },
     template: { element: 'click-l' }
  });

 //keeps an eye on variable for any modification in data
  function viewModel(){
      this.a = ko.observable(2);
      this.b = ko.observable(0);
  }

  ko.applyBindings(new viewModel() );
</script>
</body>
</html>

Output

Let's carry out the following steps to see how the above code works:
  • Save the above code in component_register.htm file.
  • Open this HTML file in a browser.

Component Loaders

Component loaders are used to pass the template/viewModel pair asynchronously for given component name.

The default component loader

The default component loader depends on the explicitly registering configuration. Each component is registered before using the component.
ko.components.defaultLoader

Component loader utility functions

The default component loader can read and write by using the following functions :
S.N.Utility functions & Description
1ko.components.register(name, configuration)
Component is registered.
2ko.components.isRegistered(name)
If the particular component name is already been registered, then it returns as true else false.
3ko.components.unregister(name)
The component name is removed from the registry.
4ko.components.get(name, callback)
This function goes turn by turn to each registered loader to find that who has passed the viewModel/template definition for component name as first and then it returns viewModel/template declaration by invoking callback. If registered loader could not find anything about the component, then it invokes callback(null).
5ko.components.clearCachedDefinition(name)
This function can be called when we want to clear the given component cache entry. If the component is needed next time again then loaders will be consulted.

Implementing a custom component loader

The custom component loader can be implemented in following ways:
  • getConfig(name, callback): Depending on names we can pass configurations programatically. We can call callback(componentConfig) to pass the configurations, where the object componentConfig can be used by the loadComponent or any other loader.
  • loadComponent(name, componentConfig, callback): This function resolves the viewModel and template portion of config depending upon the way it is configured. We can call callback(result) to pass the viewmodel/template pair, where the object result is defined by following properties:
    • template - Required. Return array of DOM nodes.
    • createViewModel(params, componentInfo)- Optional. Returns the viewModel Object depending on how the viewModel property was configured.
  • loadTemplate(name, templateConfig, callback): DOM nodes is passed in a template by using custom logic. The object templateConfig is a property of template from an object componentConfig. callback(domNodeArray) is call to pass an array of DOM nodes.
  • loadViewModel(name, templateConfig, callback): viewModel factory is passed in a viewModel configuration by using custom logic.

No comments:

Post a Comment