Search Input Directive

I was asked to create reuse component for free text filter that will be use in several different pages.

The requirements:

  1. Add prefix to special characters
  2. Make the input not valid when the number of occurrences of apostrophes is odd

Two requirements were to prevent exceptions in the server.

I created two directives:

double apostrophes directive
This directive make the value of the input valid in case the number of occurrences of apostrophes is even or invalid when it odd. Check using the regular expression the number of occurrences and then set it valid or not.

I’d like to mention that the directive has only behavior and doesn’t have template .

See code below:


export function DoubleApostrophesDirective() {
    return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ctrl) {
            ctrl.$validators.doubleApostrophes = function(modelValue, viewValue) {
                if (ctrl.$isEmpty(modelValue)) {
                    // consider empty models to be valid
                    return true;
                }

                let numOfApostrophes = (viewValue.match(/\"/g) || []).length;

                if(numOfApostrophes % 2 === 0){
                    return true;
                }else{
                    return false;
                }
            };
        },
        restrict: 'A'
    }
}

you can see reference custom validation (search custom validation)

Later I’ll show example how to use the directive.

Search Input Directive

This directive envelope an ‘input’ tag into form in order to use the validation mechanism, the directive first check if the value is valid or not using the ‘double apostrophes directive’ that I mention before (you can use any validation )  and then in case the value is valid add prefix to value using another utility.


export function SearchInputDirective(){
    return{
        scope: {
            placeholder: '@',
            onValidClick: '&'
        },
        restrict: 'E',
        transclude: true,
        templateUrl: '/app/js/common-ui/search-input/search-input.template.html',
        controller: 'searchInputController',
        controllerAs: 'searchInputVM',
        bindToController: true
    }
}

export class SearchInputController{
    constructor(addBackSlashToSpecialCharactersUtil, $timeout){
        this.addBackSlashToSpecialCharactersUtil = addBackSlashToSpecialCharactersUtil;
        this.$timeout = $timeout;

        this._timeout;
    }

    action(){
        if(this.searchForm.$valid) {
            let validStr = this.addBackSlashToSpecialCharactersUtil(this.search);
            this.onValidClick({
                validStr: validStr,
                originalQueryText: this.search
            });
        }
    }

    onMagnifierClick(){
        this.action();
    }


    onSearchInputKeyUp($event){

        if(this._timeout){ //if there is already a timeout in process cancel it
            this.$timeout.cancel(this._timeout);
        }

        this._timeout = this.$timeout(() =>{

            if($event){
                if(!this.searchForm.$valid) {
                    $($event.target).blur();
                }
                this.action();
            }

            this._timeout = null;
        },500);
    }   
}

example how to use the double apostrophes directive

template 

Export leaflet map to pdf report

How to add leaflet map to pdf report?

We using the the libarary “Leaflet” in order to display map

I was asked to export an image of the leaflet map in order to add it to report

The original plan was to export an image in the client and then post it to the server that will generate the report.

After research I found the plugin “leaflet-image“, it seems that it is work well for standard markers but we have a special markers:

So, I succeeded to export the map to image but without the markers, ouch…

The above plugin “leaflet-image” has the following limitation:
“This library does not rasterize HTML because browsers cannot rasterize HTML. Therefore, L.divIcon and other HTML-based features of a map, like zoom controls or legends, are not included in the output, because they are HTML.”

I also tried html2canvas but it also did not help.

The solution we found:

After brainstorming , It was suggested that we will generate the map in the server, Our server is Node and we using  jsreport to generate the report.
The jsreport based on the library phantomjs when it generate pdf report, which give us the ability of web “Full web stack No browser required”, therefore theoretically we can generate a leaflet map like in the client.

We added “client” code to node server, include all the third party libaries (‘leaflet’, ‘leaflet.awesome-markers’, ‘leaflet.markercluster’ etc) and execute it in the report template.
Since we want to export the same map like in the client, we posted the current ‘zoom’ & the current ‘center’.

The result:

Exporting the map to pdf report!!!!

Although this solution requires duplicating code, it is the only solution that gave us the desired result.

Example of pdf with ‘leaflet’ map: leafletMapPdfExample

I’d like to mention Felix Piskunov that help to find the final solution

Using angular ui-layout

I was looking for a simple “angular layout component” for the system that we develop.The component should support resize (by drag),collapse and expand. I found the component ui-layout“.

It was quite simply add the component to the system, I added it to all the necessary views and then I came across the problem with one of the component of the system. The problem: When I tried to reduce the size of one of the component, the behavior of the layout was corrupted. It turns out that the layout cannot properly deal with the fact that some components cancel the event propagation of the mouse events. I would have to make workaround (With the generous help of Sebastian from the yFiles support). I need to capture the mousemove event while I drag the splitter above the component thus prevent the seepage of the event to the internal component, when I finish to drag I “release” the event capture.

How to override third party library in angular?

Another challenge was how to make the repair without changing the original file that is not found in version control system (we using the ‘ui-layout’ through the bower) I wonder how to override the third party library and after read this I remembered that the directive has the property ‘priority‘.
“The priority is used to sort the directives before their compile functions get called. ”

I created directive with the same name, in order my directive will compile after, I set its priority to ‘-1’

The code I’ve written below, solves the challenge of overriding a 3rd party directive by using the ‘priority’ property, the directive handle the event clash of ui-layout and the yFiles component (specific components that we are have)

 'use strict';  
 /**  
  * ui.layout-override  
  *  
  * This patch solve specific problem that occur when the content of the ui-layout handle also the mouse event in  
  * our case 'Yfiles'. *  
  * In case of 'mouseup' & 'touchend' We capture the event 'mousemove' and thus prevent the regular behavior of the event  
  * We 'release' the capture after 'mouseup' & 'touchend'  
  */  
 angular.module('ui.layout-override', [])  
   .directive('uiSplitbar', ['LayoutContainer', function(LayoutContainer) {  
     // Get all the page.  
     var htmlElement = angular.element(document.body.parentElement);  
     return {  
       priority: -1,  
       restrict: 'EAC',  
       require: '^uiLayout',  
       link: function(scope, element, attrs, ctrl) {  
         var yfiles = angular.element(element[0].parentElement.querySelector('#graphCanvas'));  
         if(yfiles.length > 0){  
           var moveListener;  
           element.on('mousedown touchstart', function(e) {  
             ctrl.movingSplitbar = scope.splitbar;  
             ctrl.processSplitbar(scope.splitbar);  
             e.preventDefault();  
             e.stopPropagation();  
             moveListener = function(event) {  
               scope.$apply(angular.bind(ctrl, ctrl.mouseMoveHandler, event));  
             };  
             //Capture the mousemove event  
             document.body.parentElement.addEventListener("mousemove", moveListener, true);  
             return false;  
           });  
           htmlElement.on('mouseup touchend', function(event) {  
             scope.$apply(angular.bind(ctrl, ctrl.mouseUpHandler, event));  
             //Release the capture the mousemove event  
             moveListener && document.body.parentElement.removeEventListener("mousemove", moveListener, true);  
             moveListener = null;  
           });  
           yfiles.on('mouseup touchend', function(event) {  
             scope.$apply(angular.bind(ctrl, ctrl.mouseUpHandler, event));  
             //Release the capture the mousemove event  
             moveListener && document.body.parentElement.removeEventListener("mousemove", moveListener, true);  
             moveListener = null;  
           });  
         }  
       }  
     };  
   }]);  

Store “big” data in browser

How to develop an offline web application for mobile?

Today, we’re developing an offline web application, targeted at the mobile market.

One of the main application requirements for the app is to work offline – it should work even when there is no network.

We had two main requirements:

1. The app using  a lot of data, more than 5 MB –  we couldn’t use local storage because its size limitation.

2. The app should work when there is no network.

In this post, I will list the solution that we found to the first requirement.

How to store data in the browser?

Html 5 has a new feature called indexedDB – It replaced the older (W3C deprecated) Web SQL database. The IndexedDB web database allows your HTML5 web application to store data associated with a host/protocol/port, locally on the client’s hard-drive. Unlike LocalStorage, which lets you store data using a simple key-value pair only, the IndexedDB is more powerful and useful for applications that requires you to store a large amount of data.

As a side note, at this time we only need a quota of 10MB for storage.

Also, currently we don’t have to use the capabilities of a relational databases such as complex queries, etc. (we may need later).

The first challenge we encountered – Safari browser doesn’t support indexedDB.

safari doesn’t support indexedDB.

So we had to use a polyfill to enable IndexedDB using WebSql, we chose indexedDBShim as the appropriate polyfill.

The use of the indexedDB is not intuitive and therefore we searched for a wrapper that will simplify the work with the indexedDB.

We found the IDBWrapper, a cross-browser wrapper for the HTML5 IndexedDB API. While this API is the future of offline storage, it is not very intuitive to use. IDBWrapper is there to provide easy access to IndexedDB’s features.
The second challenge we encountered – performance

While data loading time in the browser chrome on desktop was reasonable (about 20 sec), data loading time was very long on the iPad (few minutes).

We found (dev-tools->resources) that the chrome arrange the data according index of the array (the stored data was array)

devtools 1

While the safari stores the data in one long string.

devtools safari

The solution that solved the performance problem was to divide the data to bulks

code

For Summary

In order to develop an offline web application, we used the feature indexedDB

And we added two libraries indexedDBShim to support safari and

IDBWrapper to simplify the usage of indexedDB.