blob: 5df1543846dfd7c0f920cef0cbb911177d3a1a6d [file] [log] [blame]
Akron9c4d1ae2016-05-25 21:43:22 +02001/**
2 * Create slider for menus.
3 * The slider will only be used by mouse - touch support
4 * shouldn't be necessary, as the menu can be scrolled using touch.
5 *
6 * @author Nils Diewald
7 */
Akron9905e2a2016-05-10 16:06:44 +02008define({
9
10 /**
Akron9c4d1ae2016-05-25 21:43:22 +020011 * Create new slider for Menu.
12 * @this {Slider}
13 * @constructor
14 * @param {Menu} menu object
Akron9905e2a2016-05-10 16:06:44 +020015 */
Akron47c086c2016-05-18 21:22:06 +020016 create : function (menu) {
17 return Object.create(this)._init(menu);
Akron9905e2a2016-05-10 16:06:44 +020018 },
19
Akron9c4d1ae2016-05-25 21:43:22 +020020 /**
21 * Length attribute of the slider
22 * (as number of items).
23 *
24 * @param {number} Number of items (optional)
25 */
Akron6ed13992016-05-23 18:06:05 +020026 length : function (i) {
27 if (arguments.length === 0)
28 return this._length;
29 if (i == this._length)
Akron9c4d1ae2016-05-25 21:43:22 +020030 return this;
Akron6ed13992016-05-23 18:06:05 +020031 this._length = i;
Akron9c4d1ae2016-05-25 21:43:22 +020032 return this;
Akron6ed13992016-05-23 18:06:05 +020033 },
34
Akron9c4d1ae2016-05-25 21:43:22 +020035 /**
36 * Limit of items per screen.
37 *
38 * @param {number} Number of items per screen (optional)
39 */
Akron6ed13992016-05-23 18:06:05 +020040 limit : function (i) {
41 if (arguments.length === 0)
42 return this._limit;
43 if (i == this._limit)
Akron9c4d1ae2016-05-25 21:43:22 +020044 return this;
Akron6ed13992016-05-23 18:06:05 +020045 this._limit = i;
Akron9c4d1ae2016-05-25 21:43:22 +020046 return this;
Akron6ed13992016-05-23 18:06:05 +020047 },
48
Akron9c4d1ae2016-05-25 21:43:22 +020049 /**
50 * Is the slider active or not.
51 *
52 * @param {bool} true or false (optional)
53 */
Akrona92fd8d2016-05-24 21:13:41 +020054 active : function (bool) {
55 if (arguments.length === 1) {
56 if (bool) {
57 if (!this._active) {
58 this._element.classList.add('active');
59 this._active = true;
60 };
61 }
62 else if (this._active) {
63 this._element.classList.remove('active');
64 this._active = false;
65 }
66 };
67 return this._active;
68 },
69
Akron9c4d1ae2016-05-25 21:43:22 +020070 /**
71 * Move the slider to a relative position
72 *
73 * @param {number} relative position
74 */
Akron6ed13992016-05-23 18:06:05 +020075 movetoRel : function (relativePos) {
Akron71b91e42016-06-01 22:12:43 +020076
77 // This is important to find the correct percentage!
Akron24b1eaa2016-05-18 16:00:25 +020078 var diffHeight = (this._rulerHeight - this._sliderHeight);
Akron47c086c2016-05-18 21:22:06 +020079 var relativeOffset = (relativePos / diffHeight);
80
Akron71b91e42016-06-01 22:12:43 +020081 // Offset is a value 0 to this._screens
82 var off = this.offset(
83 parseInt(relativeOffset * this._screens) + this._event.initOffset
84 );
85
Akron47c086c2016-05-18 21:22:06 +020086 if (off !== undefined) {
87 this._menu.screen(off);
88 };
Akron6b24b202016-05-17 23:04:36 +020089 },
90
Akron9c4d1ae2016-05-25 21:43:22 +020091 /**
92 * Move the slider to an absolute position
93 *
94 * @param {number} absolute position
95 */
Akron6ed13992016-05-23 18:06:05 +020096 movetoAbs : function (absPos) {
97 var absOffset = (absPos / this._rulerHeight);
98
99 var off = this.offset(parseInt(absOffset * (this._screens + 1)));
100 if (off !== undefined) {
101 this._menu.screen(off);
102 };
Akron6b24b202016-05-17 23:04:36 +0200103 },
104
Akron9c4d1ae2016-05-25 21:43:22 +0200105 /**
106 * Screen offset of the slider
107 *
108 * @param {number} Offset position of the slider (optional)
109 */
Akron6ed13992016-05-23 18:06:05 +0200110 offset : function (off) {
111 if (arguments.length === 0)
112 return this._offset;
Akron6b24b202016-05-17 23:04:36 +0200113
Akron9c4d1ae2016-05-25 21:43:22 +0200114 // Normalize offset
115 if (off > this._screens)
Akron6ed13992016-05-23 18:06:05 +0200116 off = this._screens;
Akron9c4d1ae2016-05-25 21:43:22 +0200117 else if (off < 0)
Akron6ed13992016-05-23 18:06:05 +0200118 off = 0;
Akron24b1eaa2016-05-18 16:00:25 +0200119
Akron9c4d1ae2016-05-25 21:43:22 +0200120 // Identical with old value
Akron6ed13992016-05-23 18:06:05 +0200121 if (off === this._offset)
122 return undefined;
Akron6b24b202016-05-17 23:04:36 +0200123
Akron9c4d1ae2016-05-25 21:43:22 +0200124 // Set offset and move
Akron6ed13992016-05-23 18:06:05 +0200125 this._offset = off;
126 this._slider.style.top = (this._step * off) + '%';
127 return off;
128 },
Akron6b24b202016-05-17 23:04:36 +0200129
Akron9c4d1ae2016-05-25 21:43:22 +0200130 /**
131 * Get the associated dom element.
132 */
Akron6ed13992016-05-23 18:06:05 +0200133 element : function () {
134 return this._element;
Akron6b24b202016-05-17 23:04:36 +0200135 },
136
Akron9c4d1ae2016-05-25 21:43:22 +0200137 /**
138 * Reinitialize the size of the slider.
139 * Necessary to call after each adjustment of the list.
140 */
141 reInit : function () {
142
143 var s = this._element.style;
144
145 // Do not show the slider, in case there is nothing to scroll
146 if (this._length <= this._limit) {
147 s.display = 'none';
148 return;
149 }
150 else {
151 s.display = 'block';
152 };
153
154 this._height = ((this._limit / this._length) * 100);
155 this._screens = this._length - this._limit;
156 this._step = (100 - this._height) / this._screens;
157 this._slider.style.height = this._height + '%';
158 },
159
Akron9905e2a2016-05-10 16:06:44 +0200160 // Initialize prefix object
Akron47c086c2016-05-18 21:22:06 +0200161 _init : function (menu) {
162
163 this._menu = menu;
Akron9905e2a2016-05-10 16:06:44 +0200164
Akronf86eaea2016-05-13 18:02:27 +0200165 this._offset = 0;
Akron6b24b202016-05-17 23:04:36 +0200166 this._event = {};
Akrona92fd8d2016-05-24 21:13:41 +0200167 this._active = false;
Akronf86eaea2016-05-13 18:02:27 +0200168
Akron9c4d1ae2016-05-25 21:43:22 +0200169 var el = this._element = document.createElement('div');
170 el.setAttribute('class', 'ruler');
Akron9905e2a2016-05-10 16:06:44 +0200171
Akron9c4d1ae2016-05-25 21:43:22 +0200172 this._slider = el.appendChild(
Akron9905e2a2016-05-10 16:06:44 +0200173 document.createElement('span')
174 );
175
Akron9c4d1ae2016-05-25 21:43:22 +0200176 this._ruler = el.appendChild(document.createElement('div'));
Akron47c086c2016-05-18 21:22:06 +0200177
Akron6ed13992016-05-23 18:06:05 +0200178 // Do not mark the menu on mousedown
179 this._ruler.addEventListener('mousedown', function (e) {
180 e.halt()
181 }, false);
182
183 // Move the slider to the click position
184 this._ruler.addEventListener('click', this._mouseclick.bind(this), false);
185
Akron6b24b202016-05-17 23:04:36 +0200186 this._slider.addEventListener('mousedown', this._mousedown.bind(this), false);
187
Akron9905e2a2016-05-10 16:06:44 +0200188 return this;
189 },
190
Akron9c4d1ae2016-05-25 21:43:22 +0200191 // Reinit height based on dom position
Akron6ed13992016-05-23 18:06:05 +0200192 _initClientHeight : function () {
Akron9c4d1ae2016-05-25 21:43:22 +0200193 this._rulerHeight = this._element.clientHeight;
194 this._sliderHeight = this._slider.clientHeight;
Akron9905e2a2016-05-10 16:06:44 +0200195 },
196
Akron9c4d1ae2016-05-25 21:43:22 +0200197 // Release mousemove event
Akron6ed13992016-05-23 18:06:05 +0200198 _mousemove : function (e) {
199 this.movetoRel(e.clientY - this._event.init);
200 e.halt();
Akronf86eaea2016-05-13 18:02:27 +0200201 },
202
Akron9c4d1ae2016-05-25 21:43:22 +0200203 // Release mouseup event
Akron6ed13992016-05-23 18:06:05 +0200204 _mouseup : function (e) {
Akrona92fd8d2016-05-24 21:13:41 +0200205 this.active(false);
Akron6ed13992016-05-23 18:06:05 +0200206 window.removeEventListener('mousemove', this._event.mov);
207 window.removeEventListener('mouseup', this._event.up);
208 this._menu.focus();
Akron9905e2a2016-05-10 16:06:44 +0200209 },
210
Akron9c4d1ae2016-05-25 21:43:22 +0200211 // Release mousedown event
Akron6ed13992016-05-23 18:06:05 +0200212 _mousedown : function (e) {
213 // Bind drag handler
214 var ev = this._event;
Akron71b91e42016-06-01 22:12:43 +0200215
216 // _step * _offset is the distance of the ruler to the top
217 ev.init = e.clientY;
218 ev.initOffset = this._offset;
219 // By substracting that, it is like initializing to the first point
220
Akron9c4d1ae2016-05-25 21:43:22 +0200221 ev.mov = this._mousemove.bind(this);
222 ev.up = this._mouseup.bind(this);
Akron6ed13992016-05-23 18:06:05 +0200223
224 // TODO: This may not be necessary all the time
225 this._initClientHeight();
226
Akrona92fd8d2016-05-24 21:13:41 +0200227 this.active(true);
Akron6ed13992016-05-23 18:06:05 +0200228
229 window.addEventListener('mousemove', ev.mov);
230 window.addEventListener('mouseup', ev.up);
231
232 e.halt();
233 },
234
Akron9c4d1ae2016-05-25 21:43:22 +0200235 // Release event to reposition slider on ruler
Akron6ed13992016-05-23 18:06:05 +0200236 _mouseclick : function (e) {
237 this._initClientHeight();
238
239 this.movetoAbs(
240 e.clientY - this._ruler.getClientRects()[0].top
241 );
242 e.halt();
Akron9905e2a2016-05-10 16:06:44 +0200243 }
244});