1 | /*!
|
---|
2 | * AngularJS Material Design
|
---|
3 | * https://github.com/angular/material
|
---|
4 | * @license MIT
|
---|
5 | * v1.2.3
|
---|
6 | */
|
---|
7 | (function( window, angular, undefined ){
|
---|
8 | "use strict";
|
---|
9 |
|
---|
10 | /**
|
---|
11 | * @ngdoc module
|
---|
12 | * @name material.components.slider
|
---|
13 | */
|
---|
14 | SliderDirective['$inject'] = ["$$rAF", "$window", "$mdAria", "$mdUtil", "$mdConstant", "$mdTheming", "$mdGesture", "$parse", "$log", "$timeout"];
|
---|
15 | angular.module('material.components.slider', [
|
---|
16 | 'material.core'
|
---|
17 | ])
|
---|
18 | .directive('mdSlider', SliderDirective)
|
---|
19 | .directive('mdSliderContainer', SliderContainerDirective);
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * @type {number} the page size used for stepping when page up/down keys are pressed.
|
---|
23 | */
|
---|
24 | var stepPageSize = 10;
|
---|
25 | /**
|
---|
26 | * @type {number} the multiplier applied to a step when the arrow key is pressed along with
|
---|
27 | * alt, meta, or ctrl.
|
---|
28 | */
|
---|
29 | var modifierMultiplier = 4;
|
---|
30 |
|
---|
31 | /**
|
---|
32 | * @ngdoc directive
|
---|
33 | * @name mdSliderContainer
|
---|
34 | * @module material.components.slider
|
---|
35 | * @restrict E
|
---|
36 | * @description
|
---|
37 | * The `<md-slider-container>` can hold the slider with two other elements.
|
---|
38 | * In this case, the other elements are a `span` for the label and an `input` for displaying
|
---|
39 | * the model value.
|
---|
40 | *
|
---|
41 | * @usage
|
---|
42 | * <hljs lang="html">
|
---|
43 | * <md-slider-container>
|
---|
44 | * <span>Red</span>
|
---|
45 | * <md-slider min="0" max="255" ng-model="color.red" aria-label="red" id="red-slider">
|
---|
46 | * </md-slider>
|
---|
47 | * <md-input-container>
|
---|
48 | * <input type="number" ng-model="color.red" aria-label="Red" aria-controls="red-slider">
|
---|
49 | * </md-input-container>
|
---|
50 | * </md-slider-container>
|
---|
51 | * </hljs>
|
---|
52 | */
|
---|
53 | function SliderContainerDirective() {
|
---|
54 | return {
|
---|
55 | controller: function () {},
|
---|
56 | compile: function (elem) {
|
---|
57 | var slider = elem.find('md-slider');
|
---|
58 |
|
---|
59 | if (!slider) {
|
---|
60 | return;
|
---|
61 | }
|
---|
62 |
|
---|
63 | var vertical = slider.attr('md-vertical');
|
---|
64 |
|
---|
65 | if (vertical !== undefined) {
|
---|
66 | elem.attr('md-vertical', '');
|
---|
67 | }
|
---|
68 |
|
---|
69 | if (!slider.attr('flex')) {
|
---|
70 | slider.attr('flex', '');
|
---|
71 | }
|
---|
72 |
|
---|
73 | return function postLink(scope, element, attr, ctrl) {
|
---|
74 | element.addClass('_md'); // private md component indicator for styling
|
---|
75 |
|
---|
76 | // We have to manually stop the $watch on ngDisabled because it exists
|
---|
77 | // on the parent scope, and won't be automatically destroyed when
|
---|
78 | // the component is destroyed.
|
---|
79 | function setDisable(value) {
|
---|
80 | element.children().attr('disabled', value);
|
---|
81 | element.find('input').attr('disabled', value);
|
---|
82 | }
|
---|
83 |
|
---|
84 | var stopDisabledWatch = angular.noop;
|
---|
85 |
|
---|
86 | if (attr.disabled) {
|
---|
87 | setDisable(true);
|
---|
88 | }
|
---|
89 | else if (attr.ngDisabled) {
|
---|
90 | stopDisabledWatch = scope.$watch(attr.ngDisabled, function (value) {
|
---|
91 | setDisable(value);
|
---|
92 | });
|
---|
93 | }
|
---|
94 |
|
---|
95 | scope.$on('$destroy', function () {
|
---|
96 | stopDisabledWatch();
|
---|
97 | });
|
---|
98 |
|
---|
99 | var initialMaxWidth;
|
---|
100 |
|
---|
101 | /**
|
---|
102 | * @param {number} length of the input's string value
|
---|
103 | */
|
---|
104 | ctrl.fitInputWidthToTextLength = function (length) {
|
---|
105 | var input = element[0].querySelector('md-input-container');
|
---|
106 |
|
---|
107 | if (input) {
|
---|
108 | var computedStyle = getComputedStyle(input);
|
---|
109 | var minWidth = parseInt(computedStyle.minWidth);
|
---|
110 | var padding = parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight);
|
---|
111 |
|
---|
112 | initialMaxWidth = initialMaxWidth || parseInt(computedStyle.maxWidth);
|
---|
113 | var newMaxWidth = Math.max(initialMaxWidth, minWidth + padding + (minWidth / 2 * length));
|
---|
114 |
|
---|
115 | input.style.maxWidth = newMaxWidth + 'px';
|
---|
116 | }
|
---|
117 | };
|
---|
118 | };
|
---|
119 | }
|
---|
120 | };
|
---|
121 | }
|
---|
122 |
|
---|
123 | /**
|
---|
124 | * @ngdoc directive
|
---|
125 | * @name mdSlider
|
---|
126 | * @module material.components.slider
|
---|
127 | * @restrict E
|
---|
128 | * @description
|
---|
129 | * The `<md-slider>` component allows the user to choose from a range of values.
|
---|
130 | *
|
---|
131 | * As per the [Material Design spec](https://material.io/archive/guidelines/style/color.html#color-color-system)
|
---|
132 | * the slider is in the accent color by default. The primary color palette may be used with
|
---|
133 | * the `md-primary` class.
|
---|
134 | *
|
---|
135 | * The slider has two modes:
|
---|
136 | * - "normal" mode where the user slides between a wide range of values
|
---|
137 | * - "discrete" mode where the user slides between only a few select values
|
---|
138 | *
|
---|
139 | * To enable discrete mode, add the `md-discrete` attribute to a slider
|
---|
140 | * and use the `step` attribute to change the distance between
|
---|
141 | * values the user is allowed to pick.
|
---|
142 | *
|
---|
143 | * When using the keyboard:
|
---|
144 | * - pressing the arrow keys will increase or decrease the slider's value by one step
|
---|
145 | * - holding the Meta, Control, or Alt key while pressing the arrow keys will
|
---|
146 | * move the slider four steps at a time
|
---|
147 | * - pressing the Home key will move the slider to the first allowed value
|
---|
148 | * - pressing the End key will move the slider to the last allowed value
|
---|
149 | * - pressing the Page Up key will increase the slider value by ten
|
---|
150 | * - pressing the Page Down key will decrease the slider value by ten
|
---|
151 | *
|
---|
152 | * @usage
|
---|
153 | * <h4>Normal Mode</h4>
|
---|
154 | * <hljs lang="html">
|
---|
155 | * <md-slider ng-model="myValue" min="5" max="500">
|
---|
156 | * </md-slider>
|
---|
157 | * </hljs>
|
---|
158 | * <h4>Discrete Mode</h4>
|
---|
159 | * <hljs lang="html">
|
---|
160 | * <md-slider md-discrete ng-model="myDiscreteValue" step="10" min="10" max="130">
|
---|
161 | * </md-slider>
|
---|
162 | * </hljs>
|
---|
163 | * <h4>Invert Mode</h4>
|
---|
164 | * <hljs lang="html">
|
---|
165 | * <md-slider md-invert ng-model="myValue" step="10" min="10" max="130">
|
---|
166 | * </md-slider>
|
---|
167 | * </hljs>
|
---|
168 | *
|
---|
169 | * @param {expression} ng-model Assignable angular expression to be data-bound.
|
---|
170 | * The expression should evaluate to a `number`.
|
---|
171 | * @param {expression=} ng-disabled If this expression evaluates as truthy, the slider will be
|
---|
172 | * disabled.
|
---|
173 | * @param {expression=} ng-readonly If this expression evaluates as truthy, the slider will be in
|
---|
174 | * read only mode.
|
---|
175 | * @param {boolean=} md-discrete If this attribute exists during initialization, enable discrete
|
---|
176 | * mode. Defaults to `false`.
|
---|
177 | * @param {boolean=} md-vertical If this attribute exists during initialization, enable vertical
|
---|
178 | * orientation mode. Defaults to `false`.
|
---|
179 | * @param {boolean=} md-invert If this attribute exists during initialization, enable inverted mode.
|
---|
180 | * Defaults to `false`.
|
---|
181 | * @param {number=} step The distance between values the user is allowed to pick. Defaults to `1`.
|
---|
182 | * @param {number=} min The minimum value the user is allowed to pick. Defaults to `0`.
|
---|
183 | * @param {number=} max The maximum value the user is allowed to pick. Defaults to `100`.
|
---|
184 | * @param {number=} round The amount of numbers after the decimal point. The maximum is 6 to
|
---|
185 | * prevent scientific notation. Defaults to `3`.
|
---|
186 | */
|
---|
187 | function SliderDirective($$rAF, $window, $mdAria, $mdUtil, $mdConstant, $mdTheming, $mdGesture,
|
---|
188 | $parse, $log, $timeout) {
|
---|
189 | return {
|
---|
190 | scope: {},
|
---|
191 | require: ['?ngModel', '?^mdSliderContainer'],
|
---|
192 | template:
|
---|
193 | '<div class="md-slider-wrapper">' +
|
---|
194 | '<div class="md-slider-content">' +
|
---|
195 | '<div class="md-track-container">' +
|
---|
196 | '<div class="md-track"></div>' +
|
---|
197 | '<div class="md-track md-track-fill"></div>' +
|
---|
198 | '<div class="md-track-ticks"></div>' +
|
---|
199 | '</div>' +
|
---|
200 | '<div class="md-thumb-container">' +
|
---|
201 | '<div class="md-thumb"></div>' +
|
---|
202 | '<div class="md-focus-thumb"></div>' +
|
---|
203 | '<div class="md-focus-ring"></div>' +
|
---|
204 | '<div class="md-sign">' +
|
---|
205 | '<span class="md-thumb-text"></span>' +
|
---|
206 | '</div>' +
|
---|
207 | '<div class="md-disabled-thumb"></div>' +
|
---|
208 | '</div>' +
|
---|
209 | '</div>' +
|
---|
210 | '</div>',
|
---|
211 | compile: compile
|
---|
212 | };
|
---|
213 |
|
---|
214 | // **********************************************************
|
---|
215 | // Private Methods
|
---|
216 | // **********************************************************
|
---|
217 |
|
---|
218 | function compile (tElement, tAttrs) {
|
---|
219 | var wrapper = angular.element(tElement[0].getElementsByClassName('md-slider-wrapper'));
|
---|
220 |
|
---|
221 | var tabIndex = tAttrs.tabindex || 0;
|
---|
222 | wrapper.attr('tabindex', tabIndex);
|
---|
223 |
|
---|
224 | if (tAttrs.disabled || tAttrs.ngDisabled) wrapper.attr('tabindex', -1);
|
---|
225 |
|
---|
226 | wrapper.attr('role', 'slider');
|
---|
227 |
|
---|
228 | $mdAria.expect(tElement, 'aria-label');
|
---|
229 |
|
---|
230 | return postLink;
|
---|
231 | }
|
---|
232 |
|
---|
233 | function postLink(scope, element, attr, ctrls) {
|
---|
234 | $mdTheming(element);
|
---|
235 | var ngModelCtrl = ctrls[0] || {
|
---|
236 | // Mock ngModelController if it doesn't exist to give us
|
---|
237 | // the minimum functionality needed
|
---|
238 | $setViewValue: function(val) {
|
---|
239 | this.$viewValue = val;
|
---|
240 | this.$viewChangeListeners.forEach(function(cb) { cb(); });
|
---|
241 | },
|
---|
242 | $parsers: [],
|
---|
243 | $formatters: [],
|
---|
244 | $viewChangeListeners: []
|
---|
245 | };
|
---|
246 |
|
---|
247 | var containerCtrl = ctrls[1];
|
---|
248 | var container = angular.element($mdUtil.getClosest(element, '_md-slider-container', true));
|
---|
249 | var isDisabled = attr.ngDisabled ? angular.bind(null, $parse(attr.ngDisabled), scope.$parent) : function () {
|
---|
250 | return element[0].hasAttribute('disabled');
|
---|
251 | };
|
---|
252 |
|
---|
253 | var thumb = angular.element(element[0].querySelector('.md-thumb'));
|
---|
254 | var thumbText = angular.element(element[0].querySelector('.md-thumb-text'));
|
---|
255 | var thumbContainer = thumb.parent();
|
---|
256 | var trackContainer = angular.element(element[0].querySelector('.md-track-container'));
|
---|
257 | var activeTrack = angular.element(element[0].querySelector('.md-track-fill'));
|
---|
258 | var tickContainer = angular.element(element[0].querySelector('.md-track-ticks'));
|
---|
259 | var wrapper = angular.element(element[0].getElementsByClassName('md-slider-wrapper'));
|
---|
260 | var content = angular.element(element[0].getElementsByClassName('md-slider-content'));
|
---|
261 | var throttledRefreshDimensions = $mdUtil.throttle(refreshSliderDimensions, 5000);
|
---|
262 |
|
---|
263 | // Default values, overridable by attrs
|
---|
264 | var DEFAULT_ROUND = 3;
|
---|
265 | var vertical = angular.isDefined(attr.mdVertical);
|
---|
266 | var discrete = angular.isDefined(attr.mdDiscrete);
|
---|
267 | var invert = angular.isDefined(attr.mdInvert);
|
---|
268 | angular.isDefined(attr.min) ? attr.$observe('min', updateMin) : updateMin(0);
|
---|
269 | angular.isDefined(attr.max) ? attr.$observe('max', updateMax) : updateMax(100);
|
---|
270 | angular.isDefined(attr.step)? attr.$observe('step', updateStep) : updateStep(1);
|
---|
271 | angular.isDefined(attr.round)? attr.$observe('round', updateRound) : updateRound(DEFAULT_ROUND);
|
---|
272 |
|
---|
273 | // We have to manually stop the $watch on ngDisabled because it exists
|
---|
274 | // on the parent scope, and won't be automatically destroyed when
|
---|
275 | // the component is destroyed.
|
---|
276 | var stopDisabledWatch = angular.noop;
|
---|
277 | if (attr.ngDisabled) {
|
---|
278 | stopDisabledWatch = scope.$parent.$watch(attr.ngDisabled, updateAriaDisabled);
|
---|
279 | }
|
---|
280 |
|
---|
281 | $mdGesture.register(wrapper, 'drag', { horizontal: !vertical });
|
---|
282 |
|
---|
283 | scope.mouseActive = false;
|
---|
284 |
|
---|
285 | wrapper
|
---|
286 | .on('keydown', keydownListener)
|
---|
287 | .on('mousedown', mouseDownListener)
|
---|
288 | .on('focus', focusListener)
|
---|
289 | .on('blur', blurListener)
|
---|
290 | .on('$md.pressdown', onPressDown)
|
---|
291 | .on('$md.pressup', onPressUp)
|
---|
292 | .on('$md.dragstart', onDragStart)
|
---|
293 | .on('$md.drag', onDrag)
|
---|
294 | .on('$md.dragend', onDragEnd);
|
---|
295 |
|
---|
296 | // On resize, recalculate the slider's dimensions and re-render
|
---|
297 | function updateAll() {
|
---|
298 | refreshSliderDimensions();
|
---|
299 | ngModelRender();
|
---|
300 | }
|
---|
301 | setTimeout(updateAll, 0);
|
---|
302 |
|
---|
303 | var debouncedUpdateAll = $$rAF.throttle(updateAll);
|
---|
304 | angular.element($window).on('resize', debouncedUpdateAll);
|
---|
305 |
|
---|
306 | scope.$on('$destroy', function() {
|
---|
307 | angular.element($window).off('resize', debouncedUpdateAll);
|
---|
308 | });
|
---|
309 |
|
---|
310 | ngModelCtrl.$render = ngModelRender;
|
---|
311 | ngModelCtrl.$viewChangeListeners.push(ngModelRender);
|
---|
312 | ngModelCtrl.$formatters.push(minMaxValidator);
|
---|
313 | ngModelCtrl.$formatters.push(stepValidator);
|
---|
314 |
|
---|
315 | /**
|
---|
316 | * Attributes
|
---|
317 | */
|
---|
318 | var min;
|
---|
319 | var max;
|
---|
320 | var step;
|
---|
321 | var round;
|
---|
322 | function updateMin(value) {
|
---|
323 | min = parseFloat(value);
|
---|
324 | ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$modelValue, min, max);
|
---|
325 | wrapper.attr('aria-valuemin', value);
|
---|
326 | updateAll();
|
---|
327 | }
|
---|
328 | function updateMax(value) {
|
---|
329 | max = parseFloat(value);
|
---|
330 | ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$modelValue, min, max);
|
---|
331 | wrapper.attr('aria-valuemax', value);
|
---|
332 | updateAll();
|
---|
333 | }
|
---|
334 | function updateStep(value) {
|
---|
335 | step = parseFloat(value);
|
---|
336 | }
|
---|
337 | function updateRound(value) {
|
---|
338 | // Set max round digits to 6, after 6 the input uses scientific notation
|
---|
339 | round = minMaxValidator(parseInt(value), 0, 6);
|
---|
340 | }
|
---|
341 | function updateAriaDisabled() {
|
---|
342 | element.attr('aria-disabled', !!isDisabled());
|
---|
343 | }
|
---|
344 |
|
---|
345 | // Draw the ticks with canvas.
|
---|
346 | // The alternative to drawing ticks with canvas is to draw one element for each tick,
|
---|
347 | // which could quickly become a performance bottleneck.
|
---|
348 | var tickCanvas, tickCtx;
|
---|
349 | function redrawTicks() {
|
---|
350 | if (!discrete || isDisabled()) return;
|
---|
351 | if (angular.isUndefined(step)) return;
|
---|
352 |
|
---|
353 | if (step <= 0) {
|
---|
354 | var msg = 'Slider step value must be greater than zero when in discrete mode';
|
---|
355 | $log.error(msg);
|
---|
356 | throw new Error(msg);
|
---|
357 | }
|
---|
358 |
|
---|
359 | var numSteps = Math.floor((max - min) / step);
|
---|
360 | if (!tickCanvas) {
|
---|
361 | tickCanvas = angular.element('<canvas>').css('position', 'absolute');
|
---|
362 | tickContainer.append(tickCanvas);
|
---|
363 |
|
---|
364 | tickCtx = tickCanvas[0].getContext('2d');
|
---|
365 | }
|
---|
366 |
|
---|
367 | var dimensions = getSliderDimensions();
|
---|
368 |
|
---|
369 | // If `dimensions` doesn't have height and width it might be the first attempt so we will refresh dimensions
|
---|
370 | if (dimensions && !dimensions.height && !dimensions.width) {
|
---|
371 | refreshSliderDimensions();
|
---|
372 | dimensions = sliderDimensions;
|
---|
373 | }
|
---|
374 |
|
---|
375 | tickCanvas[0].width = dimensions.width;
|
---|
376 | tickCanvas[0].height = dimensions.height;
|
---|
377 |
|
---|
378 | var distance;
|
---|
379 | for (var i = 0; i <= numSteps; i++) {
|
---|
380 | var trackTicksStyle = $window.getComputedStyle(tickContainer[0]);
|
---|
381 | tickCtx.fillStyle = trackTicksStyle.color || 'black';
|
---|
382 |
|
---|
383 | distance = Math.floor((vertical ? dimensions.height : dimensions.width) * (i / numSteps));
|
---|
384 |
|
---|
385 | tickCtx.fillRect(vertical ? 0 : distance - 1,
|
---|
386 | vertical ? distance - 1 : 0,
|
---|
387 | vertical ? dimensions.width : 2,
|
---|
388 | vertical ? 2 : dimensions.height);
|
---|
389 | }
|
---|
390 | }
|
---|
391 |
|
---|
392 | function clearTicks() {
|
---|
393 | if (tickCanvas && tickCtx) {
|
---|
394 | var dimensions = getSliderDimensions();
|
---|
395 | tickCtx.clearRect(0, 0, dimensions.width, dimensions.height);
|
---|
396 | }
|
---|
397 | }
|
---|
398 |
|
---|
399 | /**
|
---|
400 | * Refreshing Dimensions
|
---|
401 | */
|
---|
402 | var sliderDimensions = {};
|
---|
403 | refreshSliderDimensions();
|
---|
404 | function refreshSliderDimensions() {
|
---|
405 | sliderDimensions = trackContainer[0].getBoundingClientRect();
|
---|
406 | }
|
---|
407 | function getSliderDimensions() {
|
---|
408 | throttledRefreshDimensions();
|
---|
409 | return sliderDimensions;
|
---|
410 | }
|
---|
411 |
|
---|
412 | /**
|
---|
413 | * left/right/up/down arrow listener
|
---|
414 | * @param {!KeyboardEvent} ev
|
---|
415 | */
|
---|
416 | function keydownListener(ev) {
|
---|
417 | if (isDisabled()) return;
|
---|
418 | var keyCodes = $mdConstant.KEY_CODE;
|
---|
419 |
|
---|
420 | var changeAmount;
|
---|
421 | switch (ev.keyCode) {
|
---|
422 | case keyCodes.DOWN_ARROW:
|
---|
423 | case keyCodes.LEFT_ARROW:
|
---|
424 | ev.preventDefault();
|
---|
425 | changeAmount = -step;
|
---|
426 | break;
|
---|
427 | case keyCodes.UP_ARROW:
|
---|
428 | case keyCodes.RIGHT_ARROW:
|
---|
429 | ev.preventDefault();
|
---|
430 | changeAmount = step;
|
---|
431 | break;
|
---|
432 | case keyCodes.PAGE_DOWN:
|
---|
433 | ev.preventDefault();
|
---|
434 | changeAmount = -step * stepPageSize;
|
---|
435 | break;
|
---|
436 | case keyCodes.PAGE_UP:
|
---|
437 | ev.preventDefault();
|
---|
438 | changeAmount = step * stepPageSize;
|
---|
439 | break;
|
---|
440 | case keyCodes.HOME:
|
---|
441 | ev.preventDefault();
|
---|
442 | ev.stopPropagation();
|
---|
443 | updateValue(min);
|
---|
444 | break;
|
---|
445 | case keyCodes.END:
|
---|
446 | ev.preventDefault();
|
---|
447 | ev.stopPropagation();
|
---|
448 | updateValue(max);
|
---|
449 | break;
|
---|
450 | }
|
---|
451 | if (changeAmount) {
|
---|
452 | changeAmount = invert ? -changeAmount : changeAmount;
|
---|
453 | if (ev.metaKey || ev.ctrlKey || ev.altKey) {
|
---|
454 | changeAmount *= modifierMultiplier;
|
---|
455 | }
|
---|
456 | ev.preventDefault();
|
---|
457 | ev.stopPropagation();
|
---|
458 | updateValue(ngModelCtrl.$viewValue + changeAmount);
|
---|
459 | }
|
---|
460 | }
|
---|
461 |
|
---|
462 | /**
|
---|
463 | * @param value new slider value used for setting the model value
|
---|
464 | */
|
---|
465 | function updateValue(value) {
|
---|
466 | scope.$evalAsync(function() {
|
---|
467 | setModelValue(value);
|
---|
468 | });
|
---|
469 | }
|
---|
470 |
|
---|
471 | function mouseDownListener() {
|
---|
472 | redrawTicks();
|
---|
473 |
|
---|
474 | scope.mouseActive = true;
|
---|
475 | wrapper.removeClass('md-focused');
|
---|
476 |
|
---|
477 | $timeout(function() {
|
---|
478 | scope.mouseActive = false;
|
---|
479 | }, 100);
|
---|
480 | }
|
---|
481 |
|
---|
482 | function focusListener() {
|
---|
483 | if (scope.mouseActive === false) {
|
---|
484 | wrapper.addClass('md-focused');
|
---|
485 | }
|
---|
486 | }
|
---|
487 |
|
---|
488 | function blurListener() {
|
---|
489 | wrapper.removeClass('md-focused');
|
---|
490 | element.removeClass('md-active');
|
---|
491 | clearTicks();
|
---|
492 | }
|
---|
493 |
|
---|
494 | /**
|
---|
495 | * ngModel setters and validators
|
---|
496 | */
|
---|
497 | function setModelValue(value) {
|
---|
498 | ngModelCtrl.$setViewValue(minMaxValidator(stepValidator(value)));
|
---|
499 | }
|
---|
500 | function ngModelRender() {
|
---|
501 | if (isNaN(ngModelCtrl.$viewValue)) {
|
---|
502 | ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
|
---|
503 | }
|
---|
504 |
|
---|
505 | ngModelCtrl.$viewValue = minMaxValidator(ngModelCtrl.$viewValue);
|
---|
506 |
|
---|
507 | var percent = valueToPercent(ngModelCtrl.$viewValue);
|
---|
508 | scope.modelValue = ngModelCtrl.$viewValue;
|
---|
509 | wrapper.attr('aria-valuenow', ngModelCtrl.$viewValue);
|
---|
510 | setSliderPercent(percent);
|
---|
511 | thumbText.text(ngModelCtrl.$viewValue);
|
---|
512 | }
|
---|
513 |
|
---|
514 | function minMaxValidator(value, minValue, maxValue) {
|
---|
515 | if (angular.isNumber(value)) {
|
---|
516 | minValue = angular.isNumber(minValue) ? minValue : min;
|
---|
517 | maxValue = angular.isNumber(maxValue) ? maxValue : max;
|
---|
518 |
|
---|
519 | return Math.max(minValue, Math.min(maxValue, value));
|
---|
520 | }
|
---|
521 | }
|
---|
522 |
|
---|
523 | function stepValidator(value) {
|
---|
524 | if (angular.isNumber(value)) {
|
---|
525 | var formattedValue = (Math.round((value - min) / step) * step + min);
|
---|
526 | formattedValue = (Math.round(formattedValue * Math.pow(10, round)) / Math.pow(10, round));
|
---|
527 |
|
---|
528 | if (containerCtrl && containerCtrl.fitInputWidthToTextLength) {
|
---|
529 | $mdUtil.debounce(function () {
|
---|
530 | containerCtrl.fitInputWidthToTextLength(formattedValue.toString().length);
|
---|
531 | }, 100)();
|
---|
532 | }
|
---|
533 |
|
---|
534 | return formattedValue;
|
---|
535 | }
|
---|
536 | }
|
---|
537 |
|
---|
538 | /**
|
---|
539 | * @param {number} percent 0-1
|
---|
540 | */
|
---|
541 | function setSliderPercent(percent) {
|
---|
542 |
|
---|
543 | percent = clamp(percent);
|
---|
544 |
|
---|
545 | var thumbPosition = (percent * 100) + '%';
|
---|
546 | var activeTrackPercent = invert ? (1 - percent) * 100 + '%' : thumbPosition;
|
---|
547 |
|
---|
548 | if (vertical) {
|
---|
549 | thumbContainer.css('bottom', thumbPosition);
|
---|
550 | }
|
---|
551 | else {
|
---|
552 | $mdUtil.bidiProperty(thumbContainer, 'left', 'right', thumbPosition);
|
---|
553 | }
|
---|
554 |
|
---|
555 |
|
---|
556 | activeTrack.css(vertical ? 'height' : 'width', activeTrackPercent);
|
---|
557 |
|
---|
558 | element.toggleClass((invert ? 'md-max' : 'md-min'), percent === 0);
|
---|
559 | element.toggleClass((invert ? 'md-min' : 'md-max'), percent === 1);
|
---|
560 | }
|
---|
561 |
|
---|
562 | /**
|
---|
563 | * Slide listeners
|
---|
564 | */
|
---|
565 | var isDragging = false;
|
---|
566 |
|
---|
567 | function onPressDown(ev) {
|
---|
568 | if (isDisabled()) return;
|
---|
569 |
|
---|
570 | element.addClass('md-active');
|
---|
571 | element[0].focus();
|
---|
572 | refreshSliderDimensions();
|
---|
573 |
|
---|
574 | var exactVal = percentToValue(positionToPercent(vertical ? ev.srcEvent.clientY : ev.srcEvent.clientX));
|
---|
575 | var closestVal = minMaxValidator(stepValidator(exactVal));
|
---|
576 | scope.$apply(function() {
|
---|
577 | setModelValue(closestVal);
|
---|
578 | setSliderPercent(valueToPercent(closestVal));
|
---|
579 | });
|
---|
580 | }
|
---|
581 | function onPressUp(ev) {
|
---|
582 | if (isDisabled()) return;
|
---|
583 |
|
---|
584 | element.removeClass('md-dragging');
|
---|
585 |
|
---|
586 | var exactVal = percentToValue(positionToPercent(vertical ? ev.srcEvent.clientY : ev.srcEvent.clientX));
|
---|
587 | var closestVal = minMaxValidator(stepValidator(exactVal));
|
---|
588 | scope.$apply(function() {
|
---|
589 | setModelValue(closestVal);
|
---|
590 | ngModelRender();
|
---|
591 | });
|
---|
592 | }
|
---|
593 | function onDragStart(ev) {
|
---|
594 | if (isDisabled()) return;
|
---|
595 | isDragging = true;
|
---|
596 |
|
---|
597 | ev.stopPropagation();
|
---|
598 |
|
---|
599 | element.addClass('md-dragging');
|
---|
600 | setSliderFromEvent(ev);
|
---|
601 | }
|
---|
602 | function onDrag(ev) {
|
---|
603 | if (!isDragging) return;
|
---|
604 | ev.stopPropagation();
|
---|
605 | setSliderFromEvent(ev);
|
---|
606 | }
|
---|
607 | function onDragEnd(ev) {
|
---|
608 | if (!isDragging) return;
|
---|
609 | ev.stopPropagation();
|
---|
610 | isDragging = false;
|
---|
611 | }
|
---|
612 |
|
---|
613 | function setSliderFromEvent(ev) {
|
---|
614 | // While panning discrete, update only the
|
---|
615 | // visual positioning but not the model value.
|
---|
616 | if (discrete) adjustThumbPosition(vertical ? ev.srcEvent.clientY : ev.srcEvent.clientX);
|
---|
617 | else doSlide(vertical ? ev.srcEvent.clientY : ev.srcEvent.clientX);
|
---|
618 | }
|
---|
619 |
|
---|
620 | /**
|
---|
621 | * Slide the UI by changing the model value
|
---|
622 | * @param x
|
---|
623 | */
|
---|
624 | function doSlide(x) {
|
---|
625 | scope.$evalAsync(function() {
|
---|
626 | setModelValue(percentToValue(positionToPercent(x)));
|
---|
627 | });
|
---|
628 | }
|
---|
629 |
|
---|
630 | /**
|
---|
631 | * Slide the UI without changing the model (while dragging/panning)
|
---|
632 | * @param x
|
---|
633 | */
|
---|
634 | function adjustThumbPosition(x) {
|
---|
635 | var exactVal = percentToValue(positionToPercent(x));
|
---|
636 | var closestVal = minMaxValidator(stepValidator(exactVal));
|
---|
637 | setSliderPercent(positionToPercent(x));
|
---|
638 | thumbText.text(closestVal);
|
---|
639 | }
|
---|
640 |
|
---|
641 | /**
|
---|
642 | * Clamps the value to be between 0 and 1.
|
---|
643 | * @param {number} value The value to clamp.
|
---|
644 | * @returns {number}
|
---|
645 | */
|
---|
646 | function clamp(value) {
|
---|
647 | return Math.max(0, Math.min(value || 0, 1));
|
---|
648 | }
|
---|
649 |
|
---|
650 | /**
|
---|
651 | * Convert position on slider to percentage value of offset from beginning...
|
---|
652 | * @param position
|
---|
653 | * @returns {number}
|
---|
654 | */
|
---|
655 | function positionToPercent(position) {
|
---|
656 | var offset = vertical ? sliderDimensions.top : sliderDimensions.left;
|
---|
657 | var size = vertical ? sliderDimensions.height : sliderDimensions.width;
|
---|
658 | var calc = (position - offset) / size;
|
---|
659 |
|
---|
660 | if (!vertical && $mdUtil.isRtl(attr)) {
|
---|
661 | calc = 1 - calc;
|
---|
662 | }
|
---|
663 |
|
---|
664 | return Math.max(0, Math.min(1, vertical ? 1 - calc : calc));
|
---|
665 | }
|
---|
666 |
|
---|
667 | /**
|
---|
668 | * Convert percentage offset on slide to equivalent model value
|
---|
669 | * @param percent
|
---|
670 | * @returns {*}
|
---|
671 | */
|
---|
672 | function percentToValue(percent) {
|
---|
673 | var adjustedPercent = invert ? (1 - percent) : percent;
|
---|
674 | return (min + adjustedPercent * (max - min));
|
---|
675 | }
|
---|
676 |
|
---|
677 | function valueToPercent(val) {
|
---|
678 | var percent = (val - min) / (max - min);
|
---|
679 | return invert ? (1 - percent) : percent;
|
---|
680 | }
|
---|
681 | }
|
---|
682 | }
|
---|
683 |
|
---|
684 | })(window, window.angular); |
---|