Rashim's Blog

Posts Tagged ‘Custom Control


Angular directive is a powerful feature to develop your own DOM marker that tells Angular HTML compiler to attach its functionality.  By using this feature, we could easily develop any html attributes (which we could consider as a control) that would give us to meet our business requirements. Couple of days ago I was assigned to develop a search enable input box which makes me to start developing a new directive for these purposes. Today I would like to share how I developed a new directive for search enable and autocomplete input. This is very easy and straight forward and anyone who is well-versed about angular js could easily develop this. Let’s have a bit explanation of the code,

First, the template that would use for custom input box which will work as a search input and autocomplete as well,

<div>
    <div>
        <input type="text"
               ng-keydown="handleKeyDown($event)"
               ng-model="searchText"
               ng-change="handleSearch()" />
    </div>
    <ul class="suggestions-list" ng-show="isOpen">
        <li ng-repeat="suggestion in items"
            ng-click="addToSelectedItem($index)"
            ng-mouseover="$parent.selectedIndex=$index"
            ng-class="{active : selectedIndex===$index}">{{suggestion.displayName}}</li>
    </ul>
</div>

Here, in the input box, the observable property is searchText and each time you change input value of the input text handleSearch will get executed and if there is any result it has got from its consumer to display, it just open the drop down and display the result from where we can choose.
Now the directive definition and functionality implementation,

app.directive('autoComplete', [function () {
    var autoComplete = {
        restrict: 'AE',
        scope: {
            selectedItem: "=",
            onSearch: '&'            
        },
        controller: function ($scope) {
            $scope.isOpen = false;
            $scope.selectedIndex = -1;
            $scope.searchText = '';
            $scope.items = [];          
            $scope.handleKeyDown = function (event) {
                if (event.keyCode === 40) {
                    event.preventDefault();
                    if ($scope.selectedIndex + 1 !== $scope.items.length) {
                        $scope.selectedIndex++;
                    }
                }
                else if (event.keyCode === 38) {
                    event.preventDefault();
                    if ($scope.selectedIndex - 1 !== -1) {
                        $scope.selectedIndex--;
                    }
                }
                else if (event.keyCode === 13) {
                    $scope.addToSelectedItem($scope.selectedIndex);
                    $scope.isOpen = false;
                }
            },

            $scope.handleSearch = function () {
                $scope.selectedIndex = -1;
                $scope.isOpen = false;
                $scope.items = [];
                if ($scope.searchText) {
                    var searchData = $scope.onSearch({ searchKey: $scope.searchText });
                    $scope.items = [];
                    if (searchData && searchData.length > 0) {
                        searchData.forEach(function(data) {
                            $scope.items.push(data);
                        });
                        $scope.isOpen = true;
                    } else {
                        $scope.selectedItem = $scope.searchText;
                    }
                }
            }

            $scope.addToSelectedItem = function (index) {
                $scope.selectedItem = $scope.items[index];
                $scope.searchText = $scope.selectedItem.displayName;
                $scope.isOpen = false;
            }            
        },
        templateUrl: 'autocomplete-template.html'
    };

    return autoComplete;
}]);

In this code we restrict the consumer to supply two things: one is selected Item, another one is search handler. selectedItem is needed because when you select any data from drop down ,it needs to send this data to its consumer and search handler is needed to get the search result from the consumer. That’s it; rest of the code is self-explanatory, and nothing tough as well as no extra work has been done to mention here. Source code is available here.

Advertisements

We usually need to develop some independent controls when we work in web or in other platform as well. This time I am going to introduce how to develop your own dropdown. We can easily use built in HTML select control but in some cases it might not fulfill our business requirements. Recently I have been working in windows 8 app where there is in need to develop a custom drop down for some of its business requirement. So I would like to share you today how to develop a custom dropdown or another controls that you would like to develop using WInJS, Html and Knockout and how to bridge those three technologies in a single hub.

If you are familiar with knockout you definitely know about how to develop custom components or if you are working with angular then you can guess it like a directive since knockout component’s is pretty much same with Angular directive. As my thought goes to use knockout, I took initiatives to develop a custom components in knockout. Here I have used using WinJS from what  I have got the flavor of OOP. But I could use raw Javascript but in this case I need to do some extra work should I want to have the taste of class based coding. Let’s move to code,

First the template that I have used for this custom drop down,

<div>
    <button class="dropdown_container" id="btnDropDown" data-bind="click:handleDropDown.bind($data)">
        <div data-bind="with: selectedItem">
            <span data-bind="text:title"></span>
        </div>
    </button>
    <div class="dropdown_bottom_frame" id="dropDownFlyout" data-win-control="WinJS.UI.Flyout" data-win-options="{ _sticky: false}">
        <div data-bind="foreach:items">
            <div data-bind="template: {name:$parent.itemTemplate}"></div>
        </div>
    </div>
</div>

There is nothing to explain about this template code since this is pretty straight forward. Here I have used WinJS Flyout for drop down and the data for this control has been bind from its controller.

The controller,

(function () {
    var dropdownController = WinJS.Class.define(function(params, element) {
        var self = this;
        var flyout = document.getElementById("dropDownFlyout");
        var anchor = document.getElementById("btnDropDown");
        this.items = params.items;
        this.selectedItem = params.selectedItem;
        this.isOpen = ko.observable(false);
        this.itemTemplate = params.itemTemplate;
        this.isOpen.subscribe(function(newValue) {
            if (newValue) {
                flyout.winControl.show(anchor, "bottom", "center");
            } else {
                flyout.winControl.hide();
            }
        });

        flyout.addEventListener("afterhide", function() {
            self.isOpen(false);
        });

        WinJS.UI.processAll().done();
    }, {
        handleDropDown: function() {
            this.isOpen(!this.isOpen());
        },
        selectItem: function(item) {
            this.selectedItem(item);
            this.handleDropDown();
        }
    }, null);
    WinJS.Namespace.define("Rashim.RND.Controls", {
        DropdownController: dropdownController        
    });
})();

In that code I have just taken the flyout control and manually drop and down it through code. Some functions have been given here to handle for its selection mode and passing data to its consumer. Through the params I have got the collections and selected item object so that I could propagate selected Item to the consumer.

And this is the component definition which define its ViewModel and template,

define(['text!./dropdownView.html'], function (htmlTemplate) {
    var viewModel = Rashim.RND.Controls.DropdownController;
    return {
        viewModel: {
            createViewModel: function (params, componentInfo) {
                return new viewModel(params, componentInfo.element);
            }
        },
        template: htmlTemplate
    };
});

I have used here require JS for lazy loading and text is an AMD loader for require to load text resource in my case which is template.

That’s it, Now the consumer consume it on it’s in any dom inside,

<dropdown params="{items:items,selectedItem:selectedItem,itemTemplate:'dropdown-template'}"></dropdown>

Here Items and selected item are the observable property of the ViewModel of the consumer and the control will get those through its params.

Enjoy!!! Source code is available here


%d bloggers like this: