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