Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

AppJs Framework

4.95/5 (6 votes)
30 Jun 2017CPOL4 min read 19.3K   43  
Fun with Interchangeable UI Controls

Introduction

AppJs Framework is a collection of JavaScript tools (RequireJs, KnockoutJs, jQuery, jQuery UI, Kendo UI, etc.) "soup in together" to create a simple application framework. This framework facilitates render delegation of user interface elements to AppJs Framework, then performs the enhancement progressively or regressively. This allows the user interfaces to deal separately when new features and behaviors are added.

The AppJs Framework user interface renderer is configurable to which component will render the user interface elements. The components are modularized and isolated, and can depend on specific version of jQuery version while other components run with another version side by side. In theory, we can keep some of our legacy codes while other components are progressing with newer versions of dependencies. Now that we have isolated the user interface components, we can focus on the ViewModel (KnockoutJs Model-View-ViewModel) and implementing its core functionality with breeze.

Background

What transpired me to create this framework is when working on a task with user interface elements to render in a certain look and the implementation is using a third party JavaScript UI controls, and months ahead, the user interface elements is going to change. And the change is to use a different third party JavaScript UI controls, now the task on hand has become overwhelming. To implement the change, we have to decouple the previous implementation, removing its dependencies, implementing the user interface using the new third party JavaScript UI controls, and repeat the process for every user interface elements.

Aside from that, there are also problems when upgrading jQuery modules into a newer version. Some of the legacy codes do not behave as expected, for it uses some of the deprecated methods which are no longer being supported by the new jQuery and/or other code problems.

Using the Code

The sample codes provided for this tip is here. Open the project with Visual Studio 2013 or Visual Studio 2013 Express.

Let us start by looking at the Index.cshtml file of this project.

HTML
<div id="peopleContainer">
    <div>
        <label>First name:</label><input type="text" 
        data-bind="value: firstName"/>
        <label>Last name:</label><input type="text" 
        data-bind="value: lastName"/>
        <label>Birth date:</label><input type="text" 
        data-bind="value: birthDate" datepicker/>
        <button data-bind="click: addPerson">Add</button>
    </div>

    <table>
        <thead>
            <tr><th>First name</th><th>Last name</th>
            <th>Birth date</th><th>Action</th></tr>
        </thead>
        <tbody data-bind="foreach: people">
            <tr>
                <td data-bind="text: firstName"></td>
                <td data-bind="text: lastName"></td>
                <td data-bind="text: birthDate"></td>
                <td><button data-bind="click: 
                $root.removePerson">Remove</button></td>
            </tr>
        </tbody>
    </table>

    <button data-bind="click: submitChanges, enable: isDirty">Submit Changes</button>
</div>

<script type="text/javascript">
    require(['App', 'Modules/peopleProvider', 'Modules/peopleViewModel', 'knockout'], 
        function (app, provider, viewModel, ko) {
            app.ready(function () {
                var model = viewModel.peopleViewModel(
                    provider.peopleProvider('@Url.Action("GetPeople")', 
                        '@Url.Action("SavePeople")'));
                ko.applyBindings(model);
            });
    });
</script>

If you are fond of working with RequireJs and KnockoutJs, you will be seeing a familiar code. We have KnockoutJs "data-bind" attributes and RequireJs requesting dependencies to make this view page.

In the code above, you will notice one of the input elements has a "datepicker" attribute. Here the AppJs Framework will take that element and delegate to the configured component for rendering. This works by observing the Document Object Model for any node changes; intercept change and route the node to the user interface renderer.

Here is the code that will render the input element to a datepicker.

JavaScript
define(['ui/attr', 'ui/common', 'moment', 'kendo-ui', 'jquery'], 
    function (attr, common, moment, ui, $) {
        return {
            render: function (node) {
                return $("input[type='text'][datepicker]", node).each(function (i, element) {
                    var datepicker = $(element);
                    if (typeof datepicker.data('uiDatePickerRendered') == 'undefined') {
                        datepicker.data("uiDatePickerRendered", true);

                        (function (change) {
                            var value = datepicker.val();
                            if (value) {
                                var dateValue = new Date(moment(value));
                                ui(datepicker).kendoDatePicker({
                                    value: dateValue,
                                    change: change
                                });
                            }
                            else
                                ui(datepicker).kendoDatePicker({ change: change });

                            change();
                        })(function (e) {
                            datepicker.triggerHandler("change");
                        });

                        attr.subscribe("disabled", datepicker, function () {
                            var disabled = common.attributed(datepicker.attr("disabled"));
                            ui(datepicker).data('kendoDatePicker').enable(!disabled);
                        });
                    }
                });
            }
        };
});

The user interface component is a RequireJs module that uses Kendo UI to render a datepicker control. Note that we used multiple version of jQuery in the code above, the jQuery reference as "$" and the Kendo UI implemented jQuery reference as "ui". Running two references of jQuery here have some gotchas, the change event from the Kendo UI is needed to delegate to the other jQuery so that it will properly propagate the event to any subscribers of that instance of jQuery.

We also subscribe to any changes to the "disabled" attribute of the input element, so that we can update the state of the datepicker control when it changes. This works by observing the Document Object Model for any node changes; intercept change and route the node to the user interface renderer.

In the /App/requrie.config.js, you will find a registration code snippet of the user interface controls as per below:

JavaScript
require.config({
    config: {
        'ui/widgets': [
            'ui/jquery-ui/button',
            'ui/kendo-ui/datepicker'
        ]
    }
});

When writing a user interface component, below is the sample code signature:

JavaScript
define(['<dependencies>', 'jquery'], 
    function ('<dependency references>', $) {
    return {
        render: function (node) {
            return $("<selector>", node).each(function (i, element) {
                <your code goes here!>
            });
        }
    };
});

Keep in mind that when an element is rendered, you have to mark it so that when the user interface renderer calls the user interface component, you will know when to skip it.

If there are no user interface controls registered in the AppJs Framework, basic elements are shown.

Image 1

When adding user interface controls for button elements, below is shown:

Image 2

Here, our button elements are shown as Kendo UI Buttons. AppJS framework allows us to change certain user interface by adding the user controls configuration.

On the next illustrated screen capture, the buttons were changed to jQuery UI Button and the input elements with "datepicker" attribute will be rendered/shown using Kendo UI Datepicker.

Image 3

Points of Interest

We can build our collections of user interface controls by using any third party JavaScript UI controls or build it ourselves from ground up.

Some of the cumbersome things of building user interface controls are controls that are needed in and out of data coming from the server. We can also utilize the data list elements to hold the data information required by user interface controls.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)