]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/layout/container/boxOverflow/Scroller.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / layout / container / boxOverflow / Scroller.js
CommitLineData
6527f429
DM
1/**\r
2 * @private\r
3 */\r
4Ext.define('Ext.layout.container.boxOverflow.Scroller', {\r
5\r
6 /* Begin Definitions */\r
7\r
8 extend: 'Ext.layout.container.boxOverflow.None',\r
9 requires: ['Ext.util.ClickRepeater', 'Ext.Element'],\r
10 alternateClassName: 'Ext.layout.boxOverflow.Scroller',\r
11 alias: [\r
12 'box.overflow.scroller',\r
13 'box.overflow.Scroller' // capitalized for 4.x compat\r
14 ],\r
15 mixins: {\r
16 observable: 'Ext.mixin.Observable'\r
17 },\r
18 \r
19 /* End Definitions */\r
20\r
21 /**\r
22 * @cfg {Boolean} animateScroll\r
23 * True to animate the scrolling of items within the layout (ignored if enableScroll is false)\r
24 */\r
25 animateScroll: false,\r
26\r
27 /**\r
28 * @cfg {Number} scrollIncrement\r
29 * The number of pixels to scroll by on scroller click\r
30 */\r
31 scrollIncrement: 20,\r
32\r
33 /**\r
34 * @cfg {Number} wheelIncrement\r
35 * The number of pixels to increment on mouse wheel scrolling.\r
36 */\r
37 wheelIncrement: 10,\r
38\r
39 /**\r
40 * @cfg {Number} scrollRepeatInterval\r
41 * Number of milliseconds between each scroll while a scroller button is held down\r
42 */\r
43 scrollRepeatInterval: 60,\r
44\r
45 /**\r
46 * @cfg {Number} scrollDuration\r
47 * Number of milliseconds that each scroll animation lasts\r
48 */\r
49 scrollDuration: 400,\r
50\r
51 /**\r
52 * @private\r
53 */\r
54 scrollerCls: Ext.baseCSSPrefix + 'box-scroller',\r
55 beforeSuffix: '-before-scroller',\r
56 afterSuffix: '-after-scroller',\r
57\r
58 /**\r
59 * @event scroll\r
60 * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller\r
61 * @param {Number} newPosition The new position of the scroller\r
62 * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false\r
63 */\r
64\r
65 constructor: function(config) {\r
66 var me = this;\r
67\r
68 me.mixins.observable.constructor.call(me, config);\r
69\r
70 me.scrollPosition = 0;\r
71 me.scrollSize = 0;\r
72 },\r
73\r
74 getPrefixConfig: function() {\r
75 return {\r
76 role: 'presentation',\r
77 id: this.layout.owner.id + this.beforeSuffix,\r
78 cls: this.createScrollerCls('beforeX'),\r
79 style: 'display:none'\r
80 };\r
81 },\r
82\r
83 getSuffixConfig: function() {\r
84 return {\r
85 role: 'presentation',\r
86 id : this.layout.owner.id + this.afterSuffix,\r
87 cls: this.createScrollerCls('afterX'),\r
88 style: 'display:none'\r
89 };\r
90 },\r
91\r
92 createScrollerCls: function(xName) {\r
93 var me = this,\r
94 layout = me.layout,\r
95 owner = layout.owner,\r
96 type = me.getOwnerType(owner),\r
97 scrollerCls = me.scrollerCls,\r
98 cls =\r
99 scrollerCls + ' ' +\r
100 scrollerCls + '-' + layout.names[xName] + ' ' +\r
101 scrollerCls + '-' + type + ' ' +\r
102 scrollerCls + '-' + type + '-' + owner.ui;\r
103\r
104 if (owner.plain) {\r
105 // Add plain class for components that need separate "plain" styling (e.g. tab bar)\r
106 cls += ' ' + scrollerCls + '-plain';\r
107 }\r
108\r
109 return cls;\r
110 },\r
111\r
112 getOverflowCls: function(direction) {\r
113 return this.scrollerCls + '-body-' + direction;\r
114 },\r
115\r
116 beginLayout: function (ownerContext) {\r
117 ownerContext.innerCtScrollPos = this.getScrollPosition();\r
118\r
119 this.callParent(arguments);\r
120 },\r
121\r
122 finishedLayout: function(ownerContext) {\r
123 var me = this,\r
124 plan = ownerContext.state.boxPlan,\r
125 layout = me.layout,\r
126 names = layout.names,\r
127 scrollPos = Math.min(me.getMaxScrollPosition(), ownerContext.innerCtScrollPos),\r
128 lastProps;\r
129\r
130 // If there is overflow...\r
131 if (plan && plan.tooNarrow) {\r
132 lastProps = ownerContext.childItems[ownerContext.childItems.length - 1].props;\r
133\r
134 // capture this before callParent since it calls handle/clearOverflow:\r
135 me.scrollSize = lastProps[names.x] + lastProps[names.width];\r
136 me.updateScrollButtons();\r
137\r
138 // Restore pre layout scroll position\r
139 layout.innerCt[names.setScrollLeft](scrollPos);\r
140 }\r
141\r
142 me.callParent([ownerContext]);\r
143 },\r
144\r
145 handleOverflow: function(ownerContext) {\r
146 var me = this,\r
147 names = me.layout.names,\r
148 getWidth = names.getWidth,\r
149 parallelMargins = names.parallelMargins,\r
150 scrollerWidth, targetPaddingWidth, beforeScroller, afterScroller;\r
151\r
152 me.showScrollers();\r
153\r
154 beforeScroller = me.getBeforeScroller();\r
155 afterScroller = me.getAfterScroller();\r
156 \r
157 scrollerWidth = beforeScroller[getWidth]() + afterScroller[getWidth]() +\r
158 beforeScroller.getMargin(parallelMargins) + afterScroller.getMargin(parallelMargins);\r
159 \r
160 targetPaddingWidth = ownerContext.targetContext.getPaddingInfo()[names.width];\r
161 \r
162 return {\r
163 reservedSpace: Math.max(scrollerWidth - targetPaddingWidth, 0)\r
164 };\r
165 },\r
166\r
167 /**\r
168 * @private\r
169 * Returns a reference to the "before" scroller element. Creates click handlers on\r
170 * the first call.\r
171 */\r
172 getBeforeScroller: function() {\r
173 var me = this;\r
174\r
175 return me._beforeScroller || (me._beforeScroller =\r
176 me.createScroller(me.beforeSuffix, 'beforeRepeater', 'scrollLeft'));\r
177 },\r
178\r
179 /**\r
180 * @private\r
181 * Returns a reference to the "after" scroller element. Creates click handlers on\r
182 * the first call.\r
183 */\r
184 getAfterScroller: function() {\r
185 var me = this;\r
186\r
187 return me._afterScroller || (me._afterScroller =\r
188 me.createScroller(me.afterSuffix, 'afterRepeater', 'scrollRight'));\r
189 },\r
190\r
191 createScroller: function(suffix, repeaterName, scrollHandler) {\r
192 var me = this,\r
193 owner = me.layout.owner,\r
194 scrollerCls = me.scrollerCls,\r
195 scrollerEl;\r
196\r
197 scrollerEl = owner.el.getById(owner.id + suffix);\r
198\r
199 scrollerEl.addClsOnOver(scrollerCls + '-hover');\r
200 scrollerEl.addClsOnClick(scrollerCls + '-pressed');\r
201\r
202 scrollerEl.setVisibilityMode(Ext.Element.DISPLAY);\r
203\r
204 me[repeaterName] = new Ext.util.ClickRepeater(scrollerEl, {\r
205 interval: me.scrollRepeatInterval,\r
206 handler: scrollHandler,\r
207 scope: me\r
208 });\r
209\r
210 return scrollerEl;\r
211 },\r
212\r
213 /**\r
214 * @private\r
215 * Sets up an listener to scroll on the layout's innerCt mousewheel event\r
216 */\r
217 createWheelListener: function() {\r
218 var me = this;\r
219 me.wheelListener = me.layout.innerCt.on('mousewheel', me.onMouseWheel, me, {destroyable: true});\r
220 },\r
221\r
222 onMouseWheel: function(e) {\r
223 e.stopEvent();\r
224 this.scrollBy(this.getWheelDelta(e) * this.wheelIncrement * -1, false);\r
225 },\r
226\r
227 getWheelDelta: function (e) {\r
228 return e.getWheelDelta();\r
229 },\r
230\r
231 /**\r
232 * @private\r
233 */\r
234 clearOverflow: function () {\r
235 this.hideScrollers();\r
236 },\r
237\r
238 /**\r
239 * @private\r
240 * Shows the scroller elements. Creates the scrollers first if they are not already present.\r
241 */\r
242 showScrollers: function() {\r
243 var me = this;\r
244\r
245 if (!me.wheelListener) {\r
246 me.createWheelListener();\r
247 }\r
248 me.getBeforeScroller().show();\r
249 me.getAfterScroller().show();\r
250 me.layout.owner.addClsWithUI(me.layout.direction === 'vertical' ? 'vertical-scroller' : 'scroller');\r
251 // TODO - this may invalidates data in the ContextItem's styleCache\r
252 },\r
253\r
254 /**\r
255 * @private\r
256 * Hides the scroller elements.\r
257 */\r
258 hideScrollers: function() {\r
259 var me = this,\r
260 beforeScroller = me.getBeforeScroller(),\r
261 afterScroller = me.getAfterScroller();\r
262\r
263 if (beforeScroller) {\r
264 beforeScroller.hide();\r
265 afterScroller.hide();\r
266 me.layout.owner.removeClsWithUI(me.layout.direction === 'vertical' ? 'vertical-scroller' : 'scroller');\r
267 // TODO - this may invalidates data in the ContextItem's styleCache\r
268 }\r
269 },\r
270\r
271 destroy: function() {\r
272 Ext.destroyMembers(this, 'beforeRepeater', 'afterRepeater', '_beforeScroller', '_afterScroller', 'wheelListener');\r
273 this.callParent();\r
274 },\r
275\r
276 /**\r
277 * @private\r
278 * Scrolls left or right by the number of pixels specified\r
279 * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left\r
280 */\r
281 scrollBy: function(delta, animate) {\r
282 this.scrollTo(this.getScrollPosition() + delta, animate);\r
283 },\r
284\r
285 /**\r
286 * @private\r
287 * @return {Object} Object passed to scrollTo when scrolling\r
288 */\r
289 getScrollAnim: function() {\r
290 return {\r
291 duration: this.scrollDuration, \r
292 callback: this.updateScrollButtons, \r
293 scope : this\r
294 };\r
295 },\r
296\r
297 /**\r
298 * @private\r
299 * Enables or disables each scroller button based on the current scroll position\r
300 */\r
301 updateScrollButtons: function() {\r
302 var me = this,\r
303 beforeScroller = me.getBeforeScroller(),\r
304 afterScroller = me.getAfterScroller(),\r
305 disabledCls;\r
306\r
307 if (!beforeScroller || !afterScroller) {\r
308 return;\r
309 }\r
310\r
311 disabledCls = me.scrollerCls + '-disabled';\r
312\r
313 beforeScroller[me.atExtremeBefore() ? 'addCls' : 'removeCls'](disabledCls);\r
314 afterScroller[me.atExtremeAfter() ? 'addCls' : 'removeCls'](disabledCls);\r
315 me.scrolling = false;\r
316 },\r
317\r
318 /**\r
319 * @private\r
320 * Scrolls to the left by the configured amount\r
321 */\r
322 scrollLeft: function() {\r
323 this.scrollBy(-this.scrollIncrement, false);\r
324 },\r
325\r
326 /**\r
327 * @private\r
328 * Scrolls to the right by the configured amount\r
329 */\r
330 scrollRight: function() {\r
331 this.scrollBy(this.scrollIncrement, false);\r
332 },\r
333\r
334 /**\r
335 * Returns the current scroll position of the innerCt element\r
336 * @return {Number} The current scroll position\r
337 */\r
338 getScrollPosition: function(){\r
339 var me = this,\r
340 layout = me.layout,\r
341 result;\r
342\r
343 // Until we actually scroll, the scroll[Top|Left] is stored as zero to avoid DOM\r
344 // hits, after that it's NaN.\r
345 if (isNaN(me.scrollPosition)) {\r
346 result = layout.innerCt[layout.names.getScrollLeft]();\r
347 } else {\r
348 result = me.scrollPosition;\r
349 }\r
350 return result;\r
351 },\r
352\r
353 /**\r
354 * @private\r
355 * Returns the maximum value we can scrollTo\r
356 * @return {Number} The max scroll value\r
357 */\r
358 getMaxScrollPosition: function() {\r
359 var me = this,\r
360 layout = me.layout,\r
361 maxScrollPos = me.scrollSize - layout.innerCt.lastBox[layout.names.width];\r
362\r
363 return (maxScrollPos < 0) ? 0 : maxScrollPos;\r
364 },\r
365\r
366 /**\r
367 * @private\r
368 * Returns true if the innerCt scroll is already at its left-most point\r
369 * @return {Boolean} True if already at furthest left point\r
370 */\r
371 atExtremeBefore: function() {\r
372 return !this.getScrollPosition();\r
373 },\r
374\r
375 /**\r
376 * @private\r
377 * Returns true if the innerCt scroll is already at its right-most point\r
378 * @return {Boolean} True if already at furthest right point\r
379 */\r
380 atExtremeAfter: function() {\r
381 return this.getScrollPosition() >= this.getMaxScrollPosition();\r
382 },\r
383\r
384 /**\r
385 * @private\r
386 */\r
387 setVertical: function() {\r
388 var me = this,\r
389 beforeScroller = me.getBeforeScroller(),\r
390 afterScroller = me.getAfterScroller(),\r
391 names = me.layout.names,\r
392 scrollerCls = me.scrollerCls;\r
393\r
394 beforeScroller.removeCls(scrollerCls + '-' + names.beforeY);\r
395 afterScroller.removeCls(scrollerCls + '-' + names.afterY);\r
396\r
397 beforeScroller.addCls(scrollerCls + '-' + names.beforeX);\r
398 afterScroller.addCls(scrollerCls + '-' + names.afterX);\r
399\r
400 this.callParent();\r
401 },\r
402\r
403 /**\r
404 * @private\r
405 * Scrolls to the given position. Performs bounds checking.\r
406 * @param {Number} position The position to scroll to. This is constrained.\r
407 * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll\r
408 */\r
409 scrollTo: function(position, animate) {\r
410 var me = this,\r
411 layout = me.layout,\r
412 names = layout.names,\r
413 oldPosition = me.getScrollPosition(),\r
414 newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());\r
415\r
416 if (newPosition !== oldPosition && !me.scrolling) {\r
417 me.scrollPosition = NaN;\r
418 if (animate === undefined) {\r
419 animate = me.animateScroll;\r
420 }\r
421\r
422 layout.innerCt[names.scrollTo](names.beforeScrollX, newPosition, animate ? me.getScrollAnim() : false);\r
423 if (animate) {\r
424 me.scrolling = true;\r
425 } else {\r
426 me.updateScrollButtons();\r
427 }\r
428 me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);\r
429 }\r
430 },\r
431\r
432 /**\r
433 * Scrolls to the given component.\r
434 * @param {String/Number/Ext.Component} item The item to scroll to. Can be a numerical index, component id \r
435 * or a reference to the component itself.\r
436 * @param {Boolean} animate True to animate the scrolling\r
437 */\r
438 scrollToItem: function(item, animate) {\r
439 var me = this,\r
440 layout = me.layout,\r
441 owner = layout.owner,\r
442 names = layout.names,\r
443 innerCt = layout.innerCt,\r
444 visibility,\r
445 box,\r
446 newPos;\r
447\r
448 item = me.getItem(item);\r
449 if (item !== undefined) {\r
450 if (item === owner.items.first()) {\r
451 newPos = 0;\r
452 } else if (item === owner.items.last()) {\r
453 newPos = me.getMaxScrollPosition();\r
454 } else {\r
455 visibility = me.getItemVisibility(item);\r
456 if (!visibility.fullyVisible) {\r
457 box = item.getBox(false, true);\r
458 newPos = box[names.x];\r
459 if (visibility.hiddenEnd) {\r
460 newPos -= (innerCt[names.getWidth]() - box[names.width]);\r
461 }\r
462 }\r
463 }\r
464 if (newPos !== undefined) {\r
465 me.scrollTo(newPos, animate);\r
466 }\r
467 }\r
468 },\r
469\r
470 /**\r
471 * @private\r
472 * For a given item in the container, return an object with information on whether the item is visible\r
473 * with the current innerCt scroll value.\r
474 * @param {Ext.Component} item The item\r
475 * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd\r
476 */\r
477 getItemVisibility: function(item) {\r
478 var me = this,\r
479 box = me.getItem(item).getBox(true, true),\r
480 layout = me.layout,\r
481 names = layout.names,\r
482 itemStart = box[names.x],\r
483 itemEnd = itemStart + box[names.width],\r
484 scrollStart = me.getScrollPosition(),\r
485 scrollEnd = scrollStart + layout.innerCt[names.getWidth]();\r
486\r
487 return {\r
488 hiddenStart : itemStart < scrollStart,\r
489 hiddenEnd : itemEnd > scrollEnd,\r
490 fullyVisible: itemStart >= scrollStart && itemEnd <= scrollEnd\r
491 };\r
492 }\r
493});\r