First version
[src/app-framework-demo.git] / afm-client / app / Frontend / widgets / RangeSliders / RangeSliderMod.js
1 /* 
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  * 
18  * Bugs: Input with Callback SHOULD BE get 'required' class
19  * 
20  * ref: https://developer.mozilla.org/en-US/docs/Web/Events/mouseover
21  * 
22  * usage: 
23 Usage  <range-slider>
24 ---------------------
25    <range-slider
26       id="my-slider-name"                     // only use as an argument to callback
27       class="my-custom-class"                 // default class is ibz-range-slider
28       placeholder="Track Date Selection"      // place holder for date readonly input zone
29
30       <!-- Foundation classes -->
31       class="radius"                          // check Zurb foundation doc for further info.
32       class="ibz-handle-display"              // increase handle width to hold slider current value
33
34       <!-- Angular Scope Variables -->
35       callback="myCallBack"                    // $scope.myCallBack(sliderhandle) is called when ever slider handle blur
36       formatter="SliderFormatCB"               // $scope.myFormatter(value, sliderid) when exist is call when ever slider handle moves. Should return external form of slider value.
37       ng-model="xxxxxx"                        // xxx Must be defined, script will store a new RangerObject within provided ng-model variable.
38       start-at="ScopeVar"                      // Dynamic limitation when slider is constrains by an external componant [ex: check in/out]
39       stop-at="ScopeVar"                       // Idem but for end.
40
41       <!-- Angular Directive Attributes -->
42       not-less="integer"                       // Fixed starting value for slider [default 0]
43       not-more="integer"                       // Fixed end value for sliders [default 100]
44       by-step="+-integer"                      // If by-step is >0 then slider use it as step-value, when negative use it for decimal precision
45       display-target="handle"                  // display slider external formated value in the handle [requirer calss="ibz-handle-display"]
46       dual-handles='true'                      // add a second handle to slider for min/max range
47       initial='value|[start/stop]'             // slider initial value [dual-handles] may have initial values
48    /></range-slider>
49  */
50
51 (function () {
52     'use strict';
53
54 var RangeSlider = angular.module('RangeSlider',[]);
55
56 function RangeSliderHandle (scope) {
57     var internals = [];
58     var externals = [];
59
60     this.getId = function() {
61         return scope.sliderid;
62     };
63
64     this.getCbHandle = function() {
65         return scope.cbhandle;
66     };
67
68     this.getView= function (handle) {
69         if (!handle) handle = 0;
70
71         // if value did not change return current external representation
72         if (scope.value[handle] === internals[handle]) return externals[handle];
73
74         // build external representation and save it for further requests
75         internals[handle] = scope.value[handle];
76         if (scope.formatter) externals[handle] = scope.formatter(scope.value[handle], scope.ctrlhandle);
77         else  externals[handle] = scope.value[handle];
78
79         return externals[handle];
80     };
81
82     this.updateClass = function (classe, status) {
83        scope.updateClass (classe, status);
84     };
85
86     this.forceRefresh = function (timer) {
87        scope.forceRefresh(timer);
88     };
89
90     this.getValue= function (handle) {
91         if (!handle) handle = 0;
92         return scope.value[handle];
93     };
94
95     this.getRelative= function (handle) {
96         if (!handle) handle = 0;
97         return scope.relative[handle];
98     };
99
100     this.setValue= function (value, handle) {
101         if (!handle) handle = 0;
102         scope.setValue (value, handle);
103     };
104
105     this.setDisable= function (flag) {
106         scope.setDisable(flag);
107     };
108 }
109
110 RangeSlider.directive('rangeSlider', function ($log, $document, $timeout) {
111
112     var template= '<div class="ibz-range-slider range-slider" title="{{title}}"data-slider>'+
113                   '<span class="range-slider-handle handle-min" ng-mousedown="handleCB($event,0)" ng-focus="focusCB(true)" ng-blur="focusCB(false)" role="slider" tabindex="0"></span>'+
114                   '<span class="handle-max" ng-mousedown="handleCB($event,1)" ng-focus="focusCB(true)" ng-blur="focusCB(false)" role="slider" tabindex="0"></span>'+
115                   '<span class="range-slider-active-segment"></span>'+
116                   '<span class="ibz-range-slider-start" ></span> '+
117                   '<span class="ibz-range-slider-stop"></span> '+
118                   '<input id={{sliderid}} type="hidden">'+
119                   '</div>';
120
121
122     function link (scope, element, attrs, model) {
123         // full initialisation of slider from a single object
124         scope.initWidget = function (initvalues) {
125
126             if (initvalues.byStep)  scope.byStep  = parseInt(initvalues.byStep);
127             if (initvalues.notMore) scope.notMore = parseInt(initvalues.notMore);
128             if (initvalues.notLess) scope.notLess = parseInt(initvalues.notLess);
129             if (initvalues.id)      scope.sliderid= initvalues.id;
130
131             // hugely but in some case DOM is not finish when we try to set values !!!
132             if (initvalues.value !== undefined)   {
133                 scope.value = initvalues.value;
134                 scope.forceRefresh (50); // wait 50ms for DOM to be ready
135             }
136         };
137
138         // this function recompute slide positioning
139         scope.forceRefresh = function (timer) {
140            var value = scope.value;
141            scope.value = [undefined,undefined];
142            $timeout (function() {
143                scope.setValue(value[0],0);
144                if (scope.dual)  scope.setValue(value[1],1);
145            }, timer);
146         };
147
148         // handler to change class from slider handle
149         scope.updateClass = function (classe, status) {
150
151             if (status) element.addClass (classe);
152             else  element.removeClass (classe);
153         };
154
155         scope.setDisable = function (disabled) {
156
157             if (disabled) {
158                 element.addClass ("disable");
159                 scope.handles[0].css ('visibility','hidden');
160                 if (scope.dual) {
161                     scope.handles[1].css ('visibility','hidden');
162                 }
163             } else {
164                 element.removeClass ("disable");
165                 scope.handles[0].css ('visibility','visible');
166                 if (scope.dual) scope.handles[1].css ('visibility','visible');
167             }
168
169         };
170
171         scope.normalize = function (value) {
172             var result;
173             var range = scope.notMore - scope.notLess;
174             var point = value * range;
175
176             // if step is positive let's round step by step
177             if (scope.byStep >  0) {
178                 var mod = (point - (point % scope.byStep)) / scope.byStep;
179                 var rem = point % scope.byStep;
180
181                 var round = (rem >= scope.byStep * 0.5 ? scope.byStep : 0);
182                 result= (mod * scope.byStep + round) + scope.notLess;
183                 //console.log ("range=%d value=%d point=%d mod=%d rem=%d round=%d result=%d", range, value, point, mod, rem, round, result)
184                 return result;
185             }
186
187             // if step is negative return round to asked decimal
188             if (scope.byStep <  0) {
189                 var power  =  Math.pow (10,(scope.byStep * -1));
190                 result = scope.notLess + parseInt (point * power) / power;
191                 return (result);
192             }
193
194             // if step is null return full value
195             return point;
196        };
197
198         // return current value
199         scope.getValue = function (offset, handle) {
200             if (scope.vertical) {
201                 scope.relative[handle] = (offset - scope.bounds.handles[handle].getBoundingClientRect().height) / (scope.bounds.bar.getBoundingClientRect().height - scope.bounds.handles[handle].getBoundingClientRect().height);
202             } else {
203                 scope.relative[handle] = offset /  (scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width);
204             }
205
206             var newvalue = scope.normalize (scope.relative[handle]);
207
208
209             // if internal value change update or model
210             if (newvalue !== scope.value[handle]) {
211                 if (newvalue < scope.startValue) newvalue=scope.startValue;
212                 if (newvalue > scope.stopValue)  newvalue=scope.stopValue;
213
214
215                 if (scope.formatter) {
216                     scope.viewValue = scope.formatter (newvalue, scope.ctrlhandle);
217                 } else {
218                     scope.viewValue = newvalue;
219                 }
220                 if (scope.displays[handle]) {
221                     scope.displays[handle].html (scope.viewValue);
222                 }
223
224                 // update external representation of the model
225                 scope.value[handle] = newvalue;
226                 if (model) model.$setViewValue (scope.viewValue);
227                 scope.$apply();
228                 if (newvalue > scope.startValue && newvalue < scope.stopValue) scope.translate(offset, handle);
229             }
230         };
231
232
233         scope.setStart = function (value) {
234             var offset;
235             
236             if (value > scope.value[0]) {
237                 if (!scope.dual) scope.setValue (value,0);
238                 else scope.setValue (value,1);
239             }
240
241             if (scope.vertical) {
242                 offset = scope.bounds.bar.getBoundingClientRect().height * (value - scope.notLess) / (scope.notMore - scope.notLess);
243                 scope.start.css('height',offset + 'px');
244             } else {
245                 offset = scope.bounds.bar.getBoundingClientRect().width * (value - scope.notLess) / (scope.notMore - scope.notLess);
246                 scope.start.css('width',offset + 'px');
247             }
248
249             scope.startValue= value;
250         };
251
252         scope.setStop = function (value) {
253             var offset;
254             
255             if (value < scope.value[0]) {
256                 if (!scope.dual) scope.setValue (value,0);
257                 else scope.setValue (value,1);
258             }
259
260             if (scope.vertical) {
261                 offset = scope.bounds.bar.getBoundingClientRect().height * (value - scope.notLess) / (scope.notMore - scope.notLess);
262                 scope.start.css('height',offset + 'px');
263             } else {
264                 offset = scope.bounds.bar.getBoundingClientRect().width * (value - scope.notLess) / (scope.notMore - scope.notLess);
265                 scope.stop.css({'right': 0, 'width': (scope.bounds.bar.getBoundingClientRect().width  - offset) + 'px'});
266             }
267
268             scope.stopValue= value;
269         };
270
271         scope.translate = function (offset, handle) {
272             var start;
273             
274             if (scope.vertical) {
275                 // take handle size in account to compute middle
276                 var voffset = scope.bounds.bar.getBoundingClientRect().height - offset;
277
278                 scope.handles[handle].css({
279                     '-webkit-transform': 'translateY(' + voffset + 'px)',
280                     '-moz-transform': 'translateY(' + voffset + 'px)',
281                     '-ms-transform': 'translateY(' + voffset + 'px)',
282                     '-o-transform': 'translateY(' + voffset + 'px)',
283                     'transform': 'translateY(' + voffset + 'px)'
284                });
285                if (!scope.dual) scope.slider.css('height', offset + 'px');
286                else if (scope.relative[1] && scope.relative[0]) {
287                    var height = (scope.relative[1] - scope.relative[0]) *  scope.bounds.bar.getBoundingClientRect().height;
288                    start  = (scope.relative[0] *  scope.bounds.bar.getBoundingClientRect().height);
289                    scope.slider.css ({'bottom': start+'px','height': height + 'px'});
290                }
291             } else {
292
293                 scope.handles[handle].css({
294                     '-webkit-transform': 'translateX(' + offset + 'px)',
295                     '-moz-transform': 'translateX(' + offset + 'px)',
296                     '-ms-transform': 'translateX(' + offset + 'px)',
297                     '-o-transform': 'translateX(' + offset + 'px)',
298                     'transform': 'translateX(' + offset + 'px)'
299                 });
300                 if (!scope.dual) scope.slider.css('width',offset + 'px');
301                 else if (scope.relative[1] && scope.relative[0]) {
302                     var width = (scope.relative[1] - scope.relative[0]) *  scope.bounds.bar.getBoundingClientRect().width;
303                     start = (scope.relative[0] *  scope.bounds.bar.getBoundingClientRect().width);
304                     scope.slider.css ({'left': start+'px','width': width + 'px'});
305                 }
306             }
307         };
308
309         // position handle on the bar depending a given value
310         scope.setValue = function (value , handle) {
311             var offset;
312
313             // if value did not change ignore
314             if (value === scope.value[handle]) return;
315             if (value === undefined)   value=0;
316             if (value > scope.notMore) value=scope.notMore;
317             if (value < scope.notLess) value=scope.notLess;
318
319             if (scope.vertical) {
320                 scope.relative[handle] = (value - scope.notLess) / (scope.notMore - scope.notLess);
321                 if (handle === 0) offset = (scope.relative[handle] * scope.bounds.bar.getBoundingClientRect().height) + scope.bounds.handles[handle].getBoundingClientRect().height/2;
322                 if (handle === 1) offset = scope.relative[handle] * scope.bounds.bar.getBoundingClientRect().height;
323
324             } else {
325                 scope.relative[handle] = (value - scope.notLess) / (scope.notMore - scope.notLess);
326                 offset = scope.relative[handle] *  (scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width);
327             }
328
329             scope.translate (offset,handle);
330             scope.value[handle] = value;
331
332             if (scope.formatter) {
333                 // when call through setValue we do not pass cbHandle
334                 scope.viewValue = scope.formatter (value, undefined);
335             } else {
336                 scope.viewValue = value;
337             }
338
339             if (model) model.$setViewValue( scope.viewValue);
340
341             if (scope.displays[handle]) {
342                 scope.displays[handle].html (scope.viewValue);
343             }
344         };
345
346
347         // Minimal keystroke handling to close picker with ESC [scope.actif is current handle index]
348         scope.keydown=  function(e){
349
350             switch(e.keyCode){
351                 case 39: // Right
352                 case 38: // up
353                      if (scope.byStep > 0) scope.$apply(scope.setValue ((scope.value[scope.actif]+scope.byStep), scope.actif));
354                      if (scope.byStep < 0) scope.$apply(scope.setValue ((scope.value[scope.actif]+(1 / Math.pow(10, scope.byStep*-1))),scope.actif));
355                      if (scope.callback)  scope.callback (scope.value[scope.actif], scope.ctrlhandle);
356                      break;
357                 case 37: // left
358                 case 40: // down
359                     if (scope.byStep > 0) scope.$apply(scope.setValue ((scope.value[scope.actif] - scope.byStep), scope.actif));
360                     if (scope.byStep < 0) scope.$apply(scope.setValue ((scope.value[scope.actif] - (1 / Math.pow(10, scope.byStep*-1))),scope.actif));
361                     if (scope.callback)  scope.callback (scope.value[scope.actif], scope.ctrlhandle);
362                     break;
363                 case 27: // esc
364                     scope.handles[scope.actif][0].blur();
365             }
366         };
367
368         scope.moveHandle = function (handle, clientX, clientY) {
369             var offset;
370             if (scope.vertical) {
371                 offset = scope.bounds.bar.getBoundingClientRect().bottom - clientY;
372                 if (offset > scope.bounds.bar.getBoundingClientRect().height) offset = scope.bounds.bar.getBoundingClientRect().height;
373                 if (offset < scope.bounds.handles[handle].getBoundingClientRect().height) offset = scope.bounds.handles[handle].getBoundingClientRect().height;
374             } else {
375                 offset = clientX - scope.bounds.bar.getBoundingClientRect().left;
376
377                 if (offset < 0) offset = 0;
378                 if ((clientX + scope.bounds.handles[handle].getBoundingClientRect().width) > scope.bounds.bar.getBoundingClientRect().right) {
379                     offset = scope.bounds.bar.getBoundingClientRect().width - scope.bounds.handles[handle].getBoundingClientRect().width;
380                 }
381             }
382
383             scope.getValue  (offset, handle);
384
385             // prevent dual handle to cross
386             if (scope.dual && scope.value [0] > scope.value[1]) {
387                 if (handle === 0) scope.setValue (scope.value[0] , 1);
388                 else scope.setValue(scope.value[1],0);
389             }
390         };
391
392
393         scope.focusCB = function (inside) {
394             if (inside) {
395                 $document.on('keydown',scope.keydown);
396             } else {
397                 $document.unbind('keydown',scope.keydown);
398             }
399         };
400
401         // bar was touch let move handle to this point
402         scope.touchBarCB = function (event) {
403             var handle=0;
404             var relative;
405             var touches = event.changedTouches;
406             var oldvalue = scope.value[handle];
407
408             event.preventDefault();
409
410             // if we have two handles select closest one from touch point
411             if (scope.dual) {
412                 if (scope.vertical) relative = (touches[0].pageY - scope.bounds.bar.getBoundingClientRect().bottom) / scope.bounds.bar.getBoundingClientRect().height;
413                 else relative= (touches[0].pageX - scope.bounds.bar.getBoundingClientRect().left) / scope.bounds.bar.getBoundingClientRect().width;
414
415                 var distance0 = Math.abs(relative - scope.relative[0]);
416                 var distance1 = Math.abs(relative - scope.relative[1]);
417                 if (distance1 < distance0) handle=1;
418             }
419
420             // move handle to new place
421             scope.moveHandle (handle,touches[0].pageX, touches[0].pageY);
422             if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
423         };
424
425         // handle was touch and drag
426         scope.touchHandleCB = function (touchevt, handle) {
427             var oldvalue = scope.value[handle];
428
429             touchevt.preventDefault();
430             $document.on('touchmove',touchmove);
431             $document.on('touchend' ,touchend);
432             element.unbind('touchstart', scope.touchBarCB);
433
434             function touchmove(event) {
435                 event.preventDefault();
436                 var touches = event.changedTouches;
437                 for (var idx = 0; idx < touches.length; idx++) {
438                     scope.moveHandle (handle,touches[idx].pageX, touches[idx].pageY);
439                 }
440             }
441
442             function touchend(event) {
443                $document.unbind('touchmove',touchmove);
444                $document.unbind('touchend' ,touchend);
445                element.on('touchstart', scope.touchBarCB);
446
447                 // if value change notify application callback
448                 if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
449             }
450         };
451
452         scope.handleCB = function (clickevent, handle) {
453
454             if (attrs.automatic) return;
455             
456             var oldvalue = scope.value[handle];
457             // register mouse event to track handle
458             clickevent.preventDefault();
459
460             $document.on('mousemove',mousemove);
461             $document.on('mouseup', mouseup);
462             scope.handles[handle][0].focus();
463             scope.actif=handle;
464
465             // slider handle is moving
466             function mousemove(event) {
467                 scope.moveHandle (handle, event.clientX, event.clientY);
468             }
469
470             // mouse is up dans leave slider send resize events
471             function mouseup() {
472                 $document.unbind('mousemove', mousemove);
473                 $document.unbind('mouseup', mouseup);
474
475                 // if value change notify application callback
476                 if (scope.callback && oldvalue !== scope.value[handle]) scope.callback (scope.value[handle], scope.ctrlhandle);
477             }
478         };
479
480         // simulate jquery find by classes capabilities [warning only return 1st elements]
481         scope.find = function (select, elem) {
482             var domelem;
483
484             if (elem) domelem = elem[0].querySelector(select);
485             else domelem = element[0].querySelector(select);
486
487             var angelem = angular.element(domelem);
488             return (angelem);
489         };
490
491
492
493         scope.initialSettings = function (initial) {
494             var decimal_places_match_result;
495             scope.value=[];  // store low/height value when two handles
496             scope.relative=[];
497
498             if (scope.precision === null) {
499                 decimal_places_match_result = ('' + scope.byStep).match(/\.([\d]*)/);
500                 scope.precision = decimal_places_match_result && decimal_places_match_result[1] ? decimal_places_match_result[1].length : 0;
501             }
502
503             // position handle to initial value(s)
504             element.on('touchstart', scope.touchBarCB);
505             scope.handles[0].on('touchstart', function(evt){scope.touchHandleCB(evt,0);});
506
507             // this slider has two handles low/hight
508             if (scope.dual) {
509                 scope.handles[1].addClass('range-slider-handle');
510                 scope.handles[1].on('touchstart', function(evt){scope.touchHandleCB(evt,1);});
511                 if (!scope.initvalues) scope.setValue (initial[1],1);
512             }
513
514             // if we have an initstate object apply it
515             if (scope.initvalues) scope.initWidget (scope.initvalues);
516             else   scope.setValue (initial[0],0);
517         };
518
519         scope.init = function () {
520             scope.sliderid   = attrs.id || "slider-" + parseInt (Math.random() * 1000);
521             scope.startValue = -Infinity;
522             scope.stopValue  = Infinity;
523             scope.byStep   = parseInt(attrs.byStep) || 1;
524             scope.vertical = attrs.vertical   || false;
525             scope.dual     = attrs.dualHandles|| false;
526             scope.trigger_input_change= false;
527             scope.notMore  = parseInt(attrs.notMore)   || 100;
528             scope.notLess  = parseInt(attrs.notLess)   || 0;
529
530             if (scope.vertical) element.addClass("vertical-range");
531
532             scope.handles= [scope.find('.handle-min'), scope.find('.handle-max')];
533             scope.bar    = element;
534             scope.slider = scope.find('.range-slider-active-segment');
535             scope.start  = scope.find('.ibz-range-slider-start');
536             scope.stop   = scope.find('.ibz-range-slider-stop');
537             scope.disable= attrs.disable || false;
538
539             scope.ctrlhandle = new RangeSliderHandle (scope);
540
541             // prepare DOM object pointer to compute size dynamically
542             scope.bounds = {
543                 bar    : element[0],
544                 handles: [scope.handles[0][0], scope.handles[1][0]]
545             };
546
547             if (attrs.disable === 'true') scope.setDisable(true);
548
549             if (attrs.displayTarget) {
550                 switch (attrs.displayTarget) {
551                     case true :
552                     case 'handle' :
553                         scope.displays = scope.handles;
554                         scope.handles[0].addClass('ibz-range-slider-display');
555                         if (scope.dual) scope.handles[1].addClass('ibz-range-slider-display');
556                         break;
557                     default:
558                         scope.displays =  [$document.getElementById (attrs.displayTarget)];
559                 }
560             } else scope.displays=[];
561
562             // extract initial values from attrs and parse into int
563             if (!attrs.initial) {
564                 scope.initial  = [scope.ngModel, scope.ngModel]; // initialize to model values
565             } else {
566                 var initial  = attrs.initial.split(',');
567                 scope.initial = [
568                     initial[0] !== undefined ? parseInt (initial[0]) : scope.notLess,
569                     initial[1] !== undefined ? parseInt (initial[1]) : scope.notMore
570                 ];
571             }
572
573             // Monitor any changes on start/stop dates.
574             scope.$watch('startAt', function() {
575                 if (scope.value < scope.startAt ) {
576                     //scope.setValue (scope.startAt);
577                 }
578                 if (scope.startAt) scope.setStart (scope.startAt);
579             });
580
581             scope.$watch('stopAt' , function() {
582                 if (scope.value > scope.stopAt) {
583                     //scope.setValue (scope.stopAt);
584                 }
585                 if (scope.stopAt) scope.setStop (scope.stopAt);
586             });
587
588             // finish widget initialisation
589             scope.initialSettings (scope.initial);
590
591         };
592
593         scope.init();
594         
595          // slider is ready provide control handle to application controller
596         scope.$watch ('inithook', function () {         // init Values may arrive late
597             if (scope.inithook) scope.inithook (scope.ctrlhandle);
598         });
599
600         scope.$watch ('initvalues', function () {       // init Values may arrive late
601             if (scope.initvalues) scope.initWidget(scope.initvalues);
602         });
603
604         // two-way binding if model value changes
605         scope.$watch ('ngModel', function (newValue) {
606           scope.setValue(newValue, 0);
607         });
608     }
609
610 return {
611     restrict: "E",    // restrict to <range-slider> HTML element name
612     scope: {
613         startAt  :'=',  // First acceptable date
614         stopAt   :'=',  // Last acceptable date
615         callback :'=',  // Callback to actif when a date is selected
616         formatter:'=',  // Callback for drag event call each time internal value changes
617         inithook :'=',  // Hook point to control slider from API
618         cbhandle :'=',  // Argument added to every callback
619         initvalues:'=',   // Initial values as a single object
620         ngModel: '='    // the model value
621     },
622     require: '?ngModel',
623     template: template, // html template is build from JS
624     replace: true,      // replace current directive with template while inheriting of class
625     link: link          // pickadate object's methods
626 };
627 });
628
629 console.log ("RangeSlider Loaded");
630
631 })();