blob: b34b8b6ba12df2cd9c38cac9c21755832180017a [file] [log] [blame]
Copybara botbe50d492023-11-30 00:16:42 +01001/**
2 * @license
3 * Copyright 2015 Google Inc. All Rights Reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18(function() {
19 'use strict';
20
21 /**
22 * Class constructor for Slider MDL component.
23 * Implements MDL component design pattern defined at:
24 * https://github.com/jasonmayes/mdl-component-design-pattern
25 *
26 * @constructor
27 * @param {HTMLElement} element The element that will be upgraded.
28 */
29 var MaterialSlider = function MaterialSlider(element) {
30 this.element_ = element;
31 // Browser feature detection.
32 this.isIE_ = window.navigator.msPointerEnabled;
33 // Initialize instance.
34 this.init();
35 };
36 window['MaterialSlider'] = MaterialSlider;
37
38 /**
39 * Store constants in one place so they can be updated easily.
40 *
41 * @enum {string | number}
42 * @private
43 */
44 MaterialSlider.prototype.Constant_ = {
45 // None for now.
46 };
47
48 /**
49 * Store strings for class names defined by this component that are used in
50 * JavaScript. This allows us to simply change it in one place should we
51 * decide to modify at a later date.
52 *
53 * @enum {string}
54 * @private
55 */
56 MaterialSlider.prototype.CssClasses_ = {
57 IE_CONTAINER: 'mdl-slider__ie-container',
58 SLIDER_CONTAINER: 'mdl-slider__container',
59 BACKGROUND_FLEX: 'mdl-slider__background-flex',
60 BACKGROUND_LOWER: 'mdl-slider__background-lower',
61 BACKGROUND_UPPER: 'mdl-slider__background-upper',
62 IS_LOWEST_VALUE: 'is-lowest-value',
63 IS_UPGRADED: 'is-upgraded'
64 };
65
66 /**
67 * Handle input on element.
68 *
69 * @param {Event} event The event that fired.
70 * @private
71 */
72 MaterialSlider.prototype.onInput_ = function(event) {
73 this.updateValueStyles_();
74 };
75
76 /**
77 * Handle change on element.
78 *
79 * @param {Event} event The event that fired.
80 * @private
81 */
82 MaterialSlider.prototype.onChange_ = function(event) {
83 this.updateValueStyles_();
84 };
85
86 /**
87 * Handle mouseup on element.
88 *
89 * @param {Event} event The event that fired.
90 * @private
91 */
92 MaterialSlider.prototype.onMouseUp_ = function(event) {
93 event.target.blur();
94 };
95
96 /**
97 * Handle mousedown on container element.
98 * This handler is purpose is to not require the use to click
99 * exactly on the 2px slider element, as FireFox seems to be very
100 * strict about this.
101 *
102 * @param {Event} event The event that fired.
103 * @private
104 * @suppress {missingProperties}
105 */
106 MaterialSlider.prototype.onContainerMouseDown_ = function(event) {
107 // If this click is not on the parent element (but rather some child)
108 // ignore. It may still bubble up.
109 if (event.target !== this.element_.parentElement) {
110 return;
111 }
112
113 // Discard the original event and create a new event that
114 // is on the slider element.
115 event.preventDefault();
116 var newEvent = new MouseEvent('mousedown', {
117 target: event.target,
118 buttons: event.buttons,
119 clientX: event.clientX,
120 clientY: this.element_.getBoundingClientRect().y
121 });
122 this.element_.dispatchEvent(newEvent);
123 };
124
125 /**
126 * Handle updating of values.
127 *
128 * @private
129 */
130 MaterialSlider.prototype.updateValueStyles_ = function() {
131 // Calculate and apply percentages to div structure behind slider.
132 var fraction = (this.element_.value - this.element_.min) /
133 (this.element_.max - this.element_.min);
134
135 if (fraction === 0) {
136 this.element_.classList.add(this.CssClasses_.IS_LOWEST_VALUE);
137 } else {
138 this.element_.classList.remove(this.CssClasses_.IS_LOWEST_VALUE);
139 }
140
141 if (!this.isIE_) {
142 this.backgroundLower_.style.flex = fraction;
143 this.backgroundLower_.style.webkitFlex = fraction;
144 this.backgroundUpper_.style.flex = 1 - fraction;
145 this.backgroundUpper_.style.webkitFlex = 1 - fraction;
146 }
147 };
148
149 // Public methods.
150
151 /**
152 * Disable slider.
153 *
154 * @public
155 */
156 MaterialSlider.prototype.disable = function() {
157 this.element_.disabled = true;
158 };
159 MaterialSlider.prototype['disable'] = MaterialSlider.prototype.disable;
160
161 /**
162 * Enable slider.
163 *
164 * @public
165 */
166 MaterialSlider.prototype.enable = function() {
167
168 this.element_.disabled = false;
169 };
170 MaterialSlider.prototype['enable'] = MaterialSlider.prototype.enable;
171
172 /**
173 * Update slider value.
174 *
175 * @param {number} value The value to which to set the control (optional).
176 * @public
177 */
178 MaterialSlider.prototype.change = function(value) {
179
180 if (typeof value !== 'undefined') {
181 this.element_.value = value;
182 }
183 this.updateValueStyles_();
184 };
185 MaterialSlider.prototype['change'] = MaterialSlider.prototype.change;
186
187 /**
188 * Initialize element.
189 */
190 MaterialSlider.prototype.init = function() {
191
192 if (this.element_) {
193 if (this.isIE_) {
194 // Since we need to specify a very large height in IE due to
195 // implementation limitations, we add a parent here that trims it down to
196 // a reasonable size.
197 var containerIE = document.createElement('div');
198 containerIE.classList.add(this.CssClasses_.IE_CONTAINER);
199 this.element_.parentElement.insertBefore(containerIE, this.element_);
200 this.element_.parentElement.removeChild(this.element_);
201 containerIE.appendChild(this.element_);
202 } else {
203 // For non-IE browsers, we need a div structure that sits behind the
204 // slider and allows us to style the left and right sides of it with
205 // different colors.
206 var container = document.createElement('div');
207 container.classList.add(this.CssClasses_.SLIDER_CONTAINER);
208 this.element_.parentElement.insertBefore(container, this.element_);
209 this.element_.parentElement.removeChild(this.element_);
210 container.appendChild(this.element_);
211 var backgroundFlex = document.createElement('div');
212 backgroundFlex.classList.add(this.CssClasses_.BACKGROUND_FLEX);
213 container.appendChild(backgroundFlex);
214 this.backgroundLower_ = document.createElement('div');
215 this.backgroundLower_.classList.add(this.CssClasses_.BACKGROUND_LOWER);
216 backgroundFlex.appendChild(this.backgroundLower_);
217 this.backgroundUpper_ = document.createElement('div');
218 this.backgroundUpper_.classList.add(this.CssClasses_.BACKGROUND_UPPER);
219 backgroundFlex.appendChild(this.backgroundUpper_);
220 }
221
222 this.boundInputHandler = this.onInput_.bind(this);
223 this.boundChangeHandler = this.onChange_.bind(this);
224 this.boundMouseUpHandler = this.onMouseUp_.bind(this);
225 this.boundContainerMouseDownHandler = this.onContainerMouseDown_.bind(this);
226 this.element_.addEventListener('input', this.boundInputHandler);
227 this.element_.addEventListener('change', this.boundChangeHandler);
228 this.element_.addEventListener('mouseup', this.boundMouseUpHandler);
229 this.element_.parentElement.addEventListener('mousedown', this.boundContainerMouseDownHandler);
230
231 this.updateValueStyles_();
232 this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
233 }
234 };
235
236 // The component registers itself. It can assume componentHandler is available
237 // in the global scope.
238 componentHandler.register({
239 constructor: MaterialSlider,
240 classAsString: 'MaterialSlider',
241 cssClass: 'mdl-js-slider',
242 widget: true
243 });
244})();