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