Rashim's Blog

Archive for the ‘AngularJS’ Category


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.


Getting instant response on the error in an input gives the user a better familiarity on how to precise it. Validation service is very handy to do that, and almost all controls have their own validation service. But sometime we require developing our own validation service to notify user that what input it might require, and it is very easy to inject our own validation to any control using Angular JS. Today I am going to introduce you with such custom validation technique using angular JS and its feature sets.

When we work with the date field we might be in situation to validate a date range that is to say that one date should be less than other date or vice versa. To implement such functionality I have written a small directive which provides this competence to us.

The following directive could be used to check that the date is lower than other date,

 
app.directive('dateLowerThan', ["$filter", function ($filter) {
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ctrl) {           
            var validateDateRange = function (inputValue) {
                var fromDate = $filter('date')(inputValue, 'short');
                var toDate = $filter('date')(attrs.dateLowerThan, 'short');
                var isValid = isValidDateRange(fromDate, toDate);
                ctrl.$setValidity('dateLowerThan', isValid);
                return inputValue;
            };

            ctrl.$parsers.unshift(validateDateRange);
            ctrl.$formatters.push(validateDateRange);
            attrs.$observe('dateLowerThan', function () {
                validateDateRange(ctrl.$viewValue);
            });
        }
    };
}]);
 

Here we see that there are two new things: $parser and $formatters. While we need to write a custom validation we have to write a directive which would be dependent on the ng-model directive, and $parsers and $formatters is used to hook up the custom validation logic. When the value in the control is modified the functions added to $parsers are called, and the functions added to $formatters are invoked when the model is modified in the code.The $formatters are useful when there is a possibility of the value getting changed from the code. You will get an better idea if you go through this link

Same way, the following directive could be used to check whether a date is greater than other,

 
app.directive('dateGreaterThan', ["$filter", function ($filter) {
    return {
        require: 'ngModel',
        link: function (scope, elm, attrs, ctrl) {            
            var validateDateRange = function (inputValue) {
                var fromDate = $filter('date')(attrs.dateGreaterThan, 'short');
                var toDate = $filter('date')(inputValue, 'short');
                var isValid = isValidDateRange(fromDate, toDate);
                ctrl.$setValidity('dateGreaterThan', isValid);
                return inputValue;
            };

            ctrl.$parsers.unshift(validateDateRange);
            ctrl.$formatters.push(validateDateRange);
            attrs.$observe('dateGreaterThan', function () {
                validateDateRange(ctrl.$viewValue);

            });
        }
    };
}]);
 

The utility function that has been used here,

 
var isValidDate = function (dateStr) {
    if (dateStr == undefined)
        return false;
    var dateTime = Date.parse(dateStr);

    if (isNaN(dateTime)) {
        return false;
    }
    return true;
};

var getDateDifference = function (fromDate, toDate) {
    return Date.parse(toDate) - Date.parse(fromDate);
};

var isValidDateRange = function (fromDate, toDate) {
    if (fromDate == "" || toDate == "")
        return true;
    if (isValidDate(fromDate) == false) {
        return false;
    }
    if (isValidDate(toDate) == true) {
        var days = getDateDifference(fromDate, toDate);
        if (days < 0) {
            return false;
        }
    }
    return true;
};
 

And finally the module and controller,

 
var app = angular.module("Main_app", ['ui.bootstrap']);

app.controller("MainController", ["$scope", function ($scope) {
    var currentDate = new Date();
    $scope.FromDate = new Date();
    $scope.ToDate = currentDate.setDate(currentDate.getDate() + 1);
    $scope.dateOptions = {
        formatYear: 'yy',
        startingDay: 1
    };
    $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate'];
    $scope.format = $scope.formats[0];
}]);
 

Files that you need to include,

angular.min.js

jquery.min.js

jquery-ui.min.js

ui-bootstrap.min.js

ui-bootstrap-tpls.min.js

And in html body you could use this directive like the way,


<div ng-controller="MainController">
        <form name="frmDateRange" novalidate>
            <div style="height: 400px">
                <div class="col-md-12 marginBottom15">
                    <label class="col-md-3 control-label">
                        From date
                    </label>
                    <div class="col-md-5">
                        <input type="text"
                            name="fromDate"
                            class="form-control"
                            datepicker-popup="{{format}}"
                            ng-model="FromDate"
                            datepicker-options="dateOptions"
                               required 
                            date-lower-than="{{ToDate| date:'short'}}" />
                    </div>
                    <div class="col-md-4 offset0">
                        <span 
                            ng-show="frmDateRange.fromDate.$error.required && 
                            !frmDateRange.fromDate.$pristine">
                            Invalid or Empty from date
                        </span>
                        <span
                            ng-show="frmDateRange.fromDate.$error.dateLowerThan">
                            From date must be lower than To date
                        </span>
                    </div>
                </div>
                <div class="col-md-12 marginBottom15">
                    <label class="col-md-3 control-label">
                        To date
                    </label>
                    <div class="col-md-5">
                        <input type="text"
                            name="endDate"
                            class="form-control"
                            datepicker-popup="{{format}}"
                            ng-model="ToDate"
                            min="FromDate"
                            datepicker-options="dateOptions"
                               required 
                            date-greater-than="{{FromDate| date:'short'}}" />
                    </div>
                    <div class="col-md-4 offset0">
                        <span 
                            ng-show="frmDateRange.endDate.$error.required 
                            && !frmDateRange.endDate.$pristine">
                            Invalid or Empty to date
                        </span>
                        <span 
                            ng-show="frmDateRange.endDate.$error.dateGreaterThan">
                            To date must be greater than from date
                        </span>
                    </div>
                </div>
            </div>
        </form>
    </div>
 

That’s it. Now play with this. The source code is available here.


Almost all web based projects, where location/address is included, we might require mapping service to show the exact location or street view on the page. Thanks to Google to give us such amazing service application, yes I am talking about Google Maps which offers street maps and street view perspective and many more. To get the map rendered in the web page using Angular JS is pretty easy and straight forward. I am going to show you how we can do this,

For this is so, I have created a directive so that I could reuse it throughout the application as an html tag. To locate the area, we might put its Latitude and Longitude or we might put just a valid address. The following directive could be used while we have the latitude and longitude for a particular area,


app.directive('areaBasedGoogleMap', function () {
    return {
        restrict: "A",
        template: "<div id='areaMap'></div>",
        scope: {           
            area: "=",
            zoom: "="
        },
        controller: function ($scope) {
            var mapOptions;
            var map;           
            var marker;

            var initialize = function () {                                
                mapOptions = {
                    zoom: $scope.zoom,
                    center: new google.maps.LatLng(40.0000, -98.0000),
                    mapTypeId: google.maps.MapTypeId.TERRAIN
                };
                map = new google.maps.Map
(document.getElementById('areaMap'), mapOptions); }; var createMarker = function (area) { var position = new google.maps.LatLng
(area.Latitude, area.Longitude); map.setCenter(position); marker = new google.maps.Marker({ map: map, position: position, title: area.Name }); }; $scope.$watch("area", function (area) { if (area != undefined) { createMarker(area); } }); initialize(); }, }; });

And this directive might be used while we have a valid address,


app.directive('addressBasedGoogleMap', function () {
    return {
        restrict: "A",
        template: "<div id='addressMap'></div>",
        scope: {
            address: "=",
            zoom: "="
        },
        controller: function ($scope) {
            var geocoder;
            var latlng;
            var map;
            var marker;
            var initialize = function () {
                geocoder = new google.maps.Geocoder();
                latlng = new google.maps.LatLng(-34.397, 150.644);
                var mapOptions = {
                    zoom: $scope.zoom,
                    center: latlng,
                    mapTypeId: google.maps.MapTypeId.ROADMAP
                };
                map = new google.maps.Map
(document.getElementById('addressMap'), mapOptions); }; var markAdressToMap = function () { geocoder.geocode({ 'address': $scope.address },
function (results, status)
{ if (status == google.maps.GeocoderStatus.OK) { map.setCenter(results[0].geometry.location); marker = new google.maps.Marker({ map: map, position: results[0].geometry.location }); } }); }; $scope.$watch("address", function () { if ($scope.address != undefined) { markAdressToMap(); } }); initialize(); }, }; });

And in html we can use these directives like the way,

<div address-based-google-map address="Adress" zoom="16"></div>
<div area-based-google-map area="Area" zoom="8"></div>

Finally Module and Controller definition,


var app = angular.module("Main_app", []);

app.controller("MainController", ["$scope", 
function MainController($scope) 
{    
    $scope.Adress = "American University,4400 Massachusetts Ave NW,Washington, DC 20016";
    $scope.Area = { 
                    Name: "Melbourne", 
                    Latitude: -37.8140000, 
                    Longitude: 144.9633200 
                  };
}]);

One more thing, you need to include following JS file to your html page,


angular.js
https://maps.googleapis.com/maps/api/js?sensor=false 

And the style,


 #areaMap, #addressMap
  {
    height: 400px;
    width: 400px;
    margin: 10px;
    padding: 10px;
  }

Very easy stuff but effective enough!!! Download Source Code


After a huge gap, I have started working in web arena again. I had been working in desktop zone for recent few years on the technologies WPF/WCF, but, now here I am to dig down again with JavaScript and its rich set of open source libraries. While playing with some other JS Libraries, I have stunned to see the power of AngularJS and I am becoming a very good fan of it.  For this is so, I am about to write a small piece of things using AngularJS and would eager to continue with it more from now on. Since I had been working on MVVM pattern in WPF, AngularJS seems to me very much similar with the idea that I had been used to in WPF. However, I would love the way AngularJS gives us the features and its continuous growing.

AngularJS is a MV* framework for dynamic web apps which outlines abundant new concepts to properly manage our web application. It enhances HTML by incorporating new ideas which is called directives that could attach in our web pages with new attributes or tags. And by using those directives we can define very powerful templates directly in our HTML. Today I am going to introduce with such thing that is custom directives for Multi Select Drop Down.

Let’s move into code,


app.directive('dropdownMultiselect', function () {
    return {
        restrict: 'A',
        scope: {
            items: "=",
            selectedItems: "="
        },
        template:"<div class='dropdown col-md-1 offset0 margintLeft15 marginRigth5'                is_open='status.open'>" +
"<button type='button' class='btn btn-success dropdown-toggle' ng-click='openDropdown($event)'>" +
"Add <span class='caret'></span>" +
"</button>" +
"<div class='dropdown-menu expertDropList' >" +
"<div class='col-md-12 marginBottom15'>" +
"<div class='pull-right'>" +
"<button class='btn btn-sm btn-success' ng-click='closeDropDown()'>Close</button>" +
"<button class='btn btn-sm btn-success' ng-click='selectAll($event)'>Check All</button>" +
"<button class='btn btn-sm btn-success' ng-click='deselectAll($event);'>Uncheck All</button>" +
"</div>" +
"</div>" +
"<div data-ng-repeat='item in items' class='expertDropListBox' ng-click='handleClick($event)'>" +
"<input type='checkbox' ng-model='item.IsSelected' ng-click='clickItem($event)' ng-change='selectItem(item)' />" +
"{{item.Name}}" +
"</div>" +
"</div>" +
"</div>",
        controller: function ($scope) {
            $scope.handleClick = function ($event) {
                $event.stopPropagation();
            };
            $scope.status = {
                isopen: false
            };
            $scope.status = { isopen: false };
            $scope.openDropdown = function ($event) {
                if ($scope.items != undefined && $scope.items.length > 0) {
                    for (var index = 0; index < $scope.items.length; index++) {                         $scope.items[index].IsSelected = false;                     }                     if ($scope.selectedItems != undefined && $scope.selectedItems.length > 0) {
                        for (var selectedItemIndex = 0; selectedItemIndex < $scope.selectedItems.length; selectedItemIndex++) {
                            for (var itemIndex = 0; itemIndex < $scope.items.length; itemIndex++) {
                                if ($scope.selectedItems[selectedItemIndex].Id == $scope.items[itemIndex].Id) {
                                    $scope.items[itemIndex].IsSelected = true;
                                    break;
                                }
                            }
                        }
                    }
                }
                $event.stopPropagation();
                $scope.status.isopen = true;
            };
            $scope.selectAll = function ($event) {
                $scope.selectedItems = [];
                angular.forEach($scope.items, function (item) {
                    item.IsSelected = true;
                    $scope.selectedItems.push(item);
                });
                $event.stopPropagation();
            };
            $scope.deselectAll = function ($event) {
                angular.forEach($scope.items, function (item) {
                    item.IsSelected = false;
                });
                $scope.selectedItems = [];
                $event.stopPropagation();
            };
            $scope.selectItem = function (item) {
                if (item.IsSelected == false) {
                    for (var index = 0; index < $scope.selectedItems.length; index++) {
                        if (item.Id == $scope.selectedItems[index].Id) {
                            item.IsSelected = false;
                            $scope.selectedItems.splice(index, 1);
                            break;
                        }
                    }
                } else {
                    $scope.selectedItems.push(item);
                }
            };
            $scope.clickItem = function ($event) {
                $event.stopPropagation();
            };
            $scope.closeDropDown = function () {
                $scope.status.isopen = false;
                $event.stopPropagation();
            };
        }
    };
});

Though the code is self-explanatory I would like to explain a bit so that anyone can easily understand the technique behind this is. In order to make a directive, first, we need to set up ‘restirct’ which means how we would use this directive; would that be used as an element or attribute or comments; in my case this is ‘A’ that is it could be used as an attributes. Next, I define the isolated scope and define two properties ‘items’ and ‘selectedItems’ which will be filled up from the controller where  it has been used. And the responsibility of this directive is to give its parent controller to the selected Items. The template defines its look and the controller contains all the logic related to the templates binding property and its event stuffs that is we can think this controller as the presenter of the template. So, we can use this directive like in our HTML page like,


<div dropdown-multiselect="" items="FirstItems" selected-items="FirstSelectedItems"></div>
<div dropdown-multiselect="" items="SecondItems" selected-items="SecondSelectedItems"></div>

Now Module and Controller definition,


var app = angular.module('myApp', ['ui.bootstrap']);

app.controller('AppCtrl', ["$scope", function ($scope) {
    $scope.status = {
        isopen: false
    };

    $scope.FirstItems = [{ Id: 1, Name: "A", IsSelected: false },
            { Id: 2, Name: "B", IsSelected: false },
              { Id: 3, Name: "C", IsSelected: false },
                { Id: 4, Name: "D", IsSelected: false },
                  { Id: 5, Name: "E", IsSelected: false },
                    { Id: 6, Name: "F", IsSelected: false },
                      { Id: 7, Name: "G", IsSelected: false },
                        { Id: 8, Name: "H", IsSelected: false}];
    $scope.FirstSelectedItems = [];

    $scope.SecondItems = [{ Id: 1, Name: "A", IsSelected: false },
            { Id: 2, Name: "B", IsSelected: false },
              { Id: 3, Name: "C", IsSelected: false },
                { Id: 4, Name: "D", IsSelected: false },
                  { Id: 5, Name: "E", IsSelected: false },
                    { Id: 6, Name: "F", IsSelected: false },
                      { Id: 7, Name: "G", IsSelected: false },
                        { Id: 8, Name: "H", IsSelected: false}];
    $scope.SecondSelectedItems = [];

    var removeItem = function (items, item) {
        for (var index = 0; index < items.length; index++) {
            if (item.Id == items[index].Id) {
                item.IsSelected = false;
                items.splice(index, 1);
                break;
            }
        }
    };
    $scope.removeFirstItem = function (item) {
        removeItem($scope.FirstSelectedItems, item);
    };
    $scope.removeSecondItem = function (item) {
        removeItem($scope.SecondSelectedItems, item);
    };
} ]);

One more thing, you need to include the following files in your html page,

      angular.js
      ui-bootstrap-tpls-0.11.0.js
      bootstrap.min.css

That’s all. Nothing is tough or impressive here. Everything is straight forward and the code is also very easy to understand. The total code and live demo can be found in the Plunker.


%d bloggers like this: