nexus site path corrected
[portal.git] / ecomp-portal-FE / client / bower_components / angular-material / modules / js / slider / slider.js
1 /*!
2  * Angular Material Design
3  * https://github.com/angular/material
4  * @license MIT
5  * v0.9.8
6  */
7 (function( window, angular, undefined ){
8 "use strict";
9
10   /**
11    * @ngdoc module
12    * @name material.components.slider
13    */
14   angular.module('material.components.slider', [
15     'material.core'
16   ])
17   .directive('mdSlider', SliderDirective);
18
19 /**
20  * @ngdoc directive
21  * @name mdSlider
22  * @module material.components.slider
23  * @restrict E
24  * @description
25  * The `<md-slider>` component allows the user to choose from a range of
26  * values.
27  *
28  * As per the [material design spec](http://www.google.com/design/spec/style/color.html#color-ui-color-application)
29  * the slider is in the accent color by default. The primary color palette may be used with
30  * the `md-primary` class.
31  *
32  * It has two modes: 'normal' mode, where the user slides between a wide range
33  * of values, and 'discrete' mode, where the user slides between only a few
34  * select values.
35  *
36  * To enable discrete mode, add the `md-discrete` attribute to a slider,
37  * and use the `step` attribute to change the distance between
38  * values the user is allowed to pick.
39  *
40  * @usage
41  * <h4>Normal Mode</h4>
42  * <hljs lang="html">
43  * <md-slider ng-model="myValue" min="5" max="500">
44  * </md-slider>
45  * </hljs>
46  * <h4>Discrete Mode</h4>
47  * <hljs lang="html">
48  * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
49  * </md-slider>
50  * </hljs>
51  *
52  * @param {boolean=} md-discrete Whether to enable discrete mode.
53  * @param {number=} step The distance between values the user is allowed to pick. Default 1.
54  * @param {number=} min The minimum value the user is allowed to pick. Default 0.
55  * @param {number=} max The maximum value the user is allowed to pick. Default 100.
56  */
57 function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture, $parse) {
58   return {
59     scope: {},
60     require: '?ngModel',
61     template:
62       '<div class="md-slider-wrapper">\
63         <div class="md-track-container">\
64           <div class="md-track"></div>\
65           <div class="md-track md-track-fill"></div>\
66           <div class="md-track-ticks"></div>\
67         </div>\
68         <div class="md-thumb-container">\
69           <div class="md-thumb"></div>\
70           <div class="md-focus-thumb"></div>\
71           <div class="md-focus-ring"></div>\
72           <div class="md-sign">\
73             <span class="md-thumb-text"></span>\
74           </div>\
75           <div class="md-disabled-thumb"></div>\
76         </div>\
77       </div>',
78     compile: compile
79   };
80
81   // **********************************************************
82   // Private Methods
83   // **********************************************************
84
85   function compile (tElement, tAttrs) {
86     tElement.attr({
87       tabIndex: 0,
88       role: 'slider'
89     });
90
91     $mdAria.expect(tElement, 'aria-label');
92
93     return postLink;
94   }
95
96   function postLink(scope, element, attr, ngModelCtrl) {
97     $mdTheming(element);
98     ngModelCtrl = ngModelCtrl || {
99       // Mock ngModelController if it doesn't exist to give us
100       // the minimum functionality needed
101       $setViewValue: function(val) {
102         this.$viewValue = val;
103         this.$viewChangeListeners.forEach(function(cb) { cb(); });
104       },
105       $parsers: [],
106       $formatters: [],
107       $viewChangeListeners: []
108     };
109
110     var isDisabledParsed = attr.ngDisabled && $parse(attr.ngDisabled);
111     var isDisabledGetter = isDisabledParsed ?
112       function() { return isDisabledParsed(scope.$parent); } :
113       angular.noop;
114     var thumb = angular.element(element[0].querySelector('.md-thumb'));
115     var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
116     var thumbContainer = thumb.parent();
117     var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
118     var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
119     var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
120     var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
121
122     // Default values, overridable by attrs
123     angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
124     angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
125     angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
126
127     // We have to manually stop the $watch on ngDisabled because it exists
128     // on the parent scope, and won't be automatically destroyed when
129     // the component is destroyed.
130     var stopDisabledWatch = angular.noop;
131     if (attr.ngDisabled) {
132       stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
133     }
134
135     $mdGesture.register(element, 'drag');
136
137     element
138       .on('keydown', keydownListener)
139       .on('$md.pressdown', onPressDown)
140       .on('$md.pressup', onPressUp)
141       .on('$md.dragstart', onDragStart)
142       .on('$md.drag', onDrag)
143       .on('$md.dragend', onDragEnd);
144
145     // On resize, recalculate the slider's dimensions and re-render
146     function updateAll() {
147       refreshSliderDimensions();
148       ngModelRender();
149       redrawTicks();
150     }
151     setTimeout(updateAll);
152
153     var debouncedUpdateAll = $$rAF.throttle(updateAll);
154     angular.element($window).on('resize', debouncedUpdateAll);
155
156     scope.$on('$destroy', function() {
157       angular.element($window).off('resize', debouncedUpdateAll);
158       stopDisabledWatch();
159     });
160
161     ngModelCtrl.$render = ngModelRender;
162     ngModelCtrl.$viewChangeListeners.push(ngModelRender);
163     ngModelCtrl.$formatters.push(minMaxValidator);
164     ngModelCtrl.$formatters.push(stepValidator);
165
166     /**
167      * Attributes
168      */
169     var min;
170     var max;
171     var step;
172     function updateMin(value) {
173       min = parseFloat(value);
174       element.attr('aria-valuemin', value);
175       updateAll();
176     }
177     function updateMax(value) {
178       max = parseFloat(value);
179       element.attr('aria-valuemax', value);
180       updateAll();
181     }
182     function updateStep(value) {
183       step = parseFloat(value);
184       redrawTicks();
185     }
186     function updateAriaDisabled(isDisabled) {
187       element.attr('aria-disabled', !!isDisabled);
188     }
189
190     // Draw the ticks with canvas.
191     // The alternative to drawing ticks with canvas is to draw one element for each tick,
192     // which could quickly become a performance bottleneck.
193     var tickCanvas, tickCtx;
194     function redrawTicks() {
195       if (!angular.isDefined(attr.mdDiscrete)) return;
196
197       var numSteps = Math.floor( (max - min) / step );
198       if (!tickCanvas) {
199         var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
200         tickCanvas = angular.element('<canvas style="position:absolute;">');
201         tickCtx = tickCanvas[0].getContext('2d');
202         tickCtx.fillStyle = trackTicksStyle.backgroundColor || 'black';
203         tickContainer.append(tickCanvas);
204       }
205       var dimensions = getSliderDimensions();
206       tickCanvas[0].width = dimensions.width;
207       tickCanvas[0].height = dimensions.height;
208
209       var distance;
210       for (var i = 0; i <= numSteps; i++) {
211         distance = Math.floor(dimensions.width * (i / numSteps));
212         tickCtx.fillRect(distance - 1, 0, 2, dimensions.height);
213       }
214     }
215
216
217     /**
218      * Refreshing Dimensions
219      */
220     var sliderDimensions = {};
221     refreshSliderDimensions();
222     function refreshSliderDimensions() {
223       sliderDimensions = trackContainer[0].getBoundingClientRect();
224     }
225     function getSliderDimensions() {
226       throttledRefreshDimensions();
227       return sliderDimensions;
228     }
229
230     /**
231      * left/right arrow listener
232      */
233     function keydownListener(ev) {
234       if(element[0].hasAttribute('disabled')) {
235         return;
236       }
237
238       var changeAmount;
239       if (ev.keyCode === $mdConstant.KEY_CODE.LEFT_ARROW) {
240         changeAmount = -step;
241       } else if (ev.keyCode === $mdConstant.KEY_CODE.RIGHT_ARROW) {
242         changeAmount = step;
243       }
244       if (changeAmount) {
245         if (ev.metaKey || ev.ctrlKey || ev.altKey) {
246           changeAmount *= 4;
247         }
248         ev.preventDefault();
249         ev.stopPropagation();
250         scope.$evalAsync(function() {
251           setModelValue(ngModelCtrl.$viewValue + changeAmount);
252         });
253       }
254     }
255
256     /**
257      * ngModel setters and validators
258      */
259     function setModelValue(value) {
260       ngModelCtrl.$setViewValue( minMaxValidator(stepValidator(value)) );
261     }
262     function ngModelRender() {
263       if (isNaN(ngModelCtrl.$viewValue)) {
264         ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
265       }
266
267       var percent = (ngModelCtrl.$viewValue - min) / (max - min);
268       scope.modelValue = ngModelCtrl.$viewValue;
269       element.attr('aria-valuenow', ngModelCtrl.$viewValue);
270       setSliderPercent(percent);
271       thumbText.text( ngModelCtrl.$viewValue );
272     }
273
274     function minMaxValidator(value) {
275       if (angular.isNumber(value)) {
276         return Math.max(min, Math.min(max, value));
277       }
278     }
279     function stepValidator(value) {
280       if (angular.isNumber(value)) {
281         var formattedValue = (Math.round(value / step) * step);
282         // Format to 3 digits after the decimal point - fixes #2015.
283         return (Math.round(formattedValue * 1000) / 1000);
284       }
285     }
286
287     /**
288      * @param percent 0-1
289      */
290     function setSliderPercent(percent) {
291       activeTrack.css('width', (percent * 100) + '%');
292       thumbContainer.css(
293         'left',
294         (percent * 100) + '%'
295       );
296       element.toggleClass('md-min', percent === 0);
297     }
298
299
300     /**
301      * Slide listeners
302      */
303     var isDragging = false;
304     var isDiscrete = angular.isDefined(attr.mdDiscrete);
305
306     function onPressDown(ev) {
307       if (isDisabledGetter()) return;
308
309       element.addClass('active');
310       element[0].focus();
311       refreshSliderDimensions();
312
313       var exactVal = percentToValue( positionToPercent( ev.pointer.x ));
314       var closestVal = minMaxValidator( stepValidator(exactVal) );
315       scope.$apply(function() {
316         setModelValue( closestVal );
317         setSliderPercent( valueToPercent(closestVal));
318       });
319     }
320     function onPressUp(ev) {
321       if (isDisabledGetter()) return;
322
323       element.removeClass('dragging active');
324
325       var exactVal = percentToValue( positionToPercent( ev.pointer.x ));
326       var closestVal = minMaxValidator( stepValidator(exactVal) );
327       scope.$apply(function() {
328         setModelValue(closestVal);
329         ngModelRender();
330       });
331     }
332     function onDragStart(ev) {
333       if (isDisabledGetter()) return;
334       isDragging = true;
335       ev.stopPropagation();
336
337       element.addClass('dragging');
338       setSliderFromEvent(ev);
339     }
340     function onDrag(ev) {
341       if (!isDragging) return;
342       ev.stopPropagation();
343       setSliderFromEvent(ev);
344     }
345     function onDragEnd(ev) {
346       if (!isDragging) return;
347       ev.stopPropagation();
348       isDragging = false;
349     }
350
351     function setSliderFromEvent(ev) {
352       // While panning discrete, update only the
353       // visual positioning but not the model value.
354       if ( isDiscrete ) adjustThumbPosition( ev.pointer.x );
355       else              doSlide( ev.pointer.x );
356     }
357
358     /**
359      * Slide the UI by changing the model value
360      * @param x
361      */
362     function doSlide( x ) {
363       scope.$evalAsync( function() {
364         setModelValue( percentToValue( positionToPercent(x) ));
365       });
366     }
367
368     /**
369      * Slide the UI without changing the model (while dragging/panning)
370      * @param x
371      */
372     function adjustThumbPosition( x ) {
373       var exactVal = percentToValue( positionToPercent( x ));
374       var closestVal = minMaxValidator( stepValidator(exactVal) );
375       setSliderPercent( positionToPercent(x) );
376       thumbText.text( closestVal );
377     }
378
379     /**
380      * Convert horizontal position on slider to percentage value of offset from beginning...
381      * @param x
382      * @returns {number}
383      */
384     function positionToPercent( x ) {
385       return Math.max(0, Math.min(1, (x - sliderDimensions.left) / (sliderDimensions.width)));
386     }
387
388     /**
389      * Convert percentage offset on slide to equivalent model value
390      * @param percent
391      * @returns {*}
392      */
393     function percentToValue( percent ) {
394       return (min + percent * (max - min));
395     }
396
397     function valueToPercent( val ) {
398       return (val - min)/(max - min);
399     }
400   }
401 }
402 SliderDirective.$inject = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse"];
403
404 })(window, window.angular);