]> git.proxmox.com Git - extjs.git/blame - extjs/classic/classic/src/LoadMask.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / LoadMask.js
CommitLineData
6527f429
DM
1/**\r
2 * A modal, floating Component which may be shown above a specified {@link Ext.Component Component} while loading data.\r
3 * When shown, the configured owning Component will be covered with a modality mask, and the LoadMask's {@link #msg} will be\r
4 * displayed centered, accompanied by a spinner image.\r
5 *\r
6 * If the {@link #store} config option is specified, the masking will be automatically shown and then hidden synchronized with\r
7 * the Store's loading process.\r
8 *\r
9 * Because this is a floating Component, its z-index will be managed by the global {@link Ext.WindowManager ZIndexManager}\r
10 * object, and upon show, it will place itsef at the top of the hierarchy.\r
11 *\r
12 * Example usage:\r
13 *\r
14 * @example\r
15 * var myPanel = new Ext.panel.Panel({\r
16 * renderTo : document.body,\r
17 * height : 100,\r
18 * width : 200,\r
19 * title : 'Foo'\r
20 * });\r
21 *\r
22 * var myMask = new Ext.LoadMask({\r
23 * msg : 'Please wait...',\r
24 * target : myPanel\r
25 * });\r
26 *\r
27 * myMask.show();\r
28 */\r
29Ext.define('Ext.LoadMask', {\r
30\r
31 extend: 'Ext.Component',\r
32\r
33 alias: 'widget.loadmask',\r
34\r
35 /* Begin Definitions */\r
36\r
37 mixins: [\r
38 'Ext.util.StoreHolder'\r
39 ],\r
40\r
41 uses: ['Ext.data.StoreManager'],\r
42\r
43 /* End Definitions */\r
44 \r
45 /**\r
46 * @property {Boolean} isLoadMask\r
47 * `true` in this class to identify an object as an instantiated LoadMask, or subclass thereof.\r
48 */\r
49 isLoadMask: true,\r
50\r
51 /**\r
52 * @cfg {Ext.Component} target The Component you wish to mask. The the mask will be automatically sized\r
53 * upon Component resize, and the message box will be kept centered.\r
54 */\r
55\r
56 /**\r
57 * @cfg {Ext.data.Store} store\r
58 * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and\r
59 * hidden on either load success, or load fail.\r
60 */\r
61\r
62 //<locale>\r
63 /**\r
64 * @cfg {String} [msg="Loading..."]\r
65 * The text to display in a centered loading message box.\r
66 */\r
67 msg: 'Loading...',\r
68 //</locale>\r
69\r
70 msgCls: Ext.baseCSSPrefix + 'mask-loading',\r
71\r
72 msgWrapCls: Ext.baseCSSPrefix + 'mask-msg',\r
73\r
74 /**\r
75 * @cfg {Boolean} [useMsg=true]\r
76 * Whether or not to use a loading message class or simply mask the bound element.\r
77 */\r
78 useMsg: true,\r
79\r
80 /**\r
81 * @cfg {Boolean} [useTargetEl=false]\r
82 * True to mask the {@link Ext.Component#getTargetEl targetEl} of the bound Component. By default,\r
83 * the {@link Ext.Component#getEl el} will be masked.\r
84 */\r
85 useTargetEl: false,\r
86\r
87 /**\r
88 * @cfg {Boolean} shim `true` to enable an iframe shim for this LoadMask to keep\r
89 * windowed objects from showing through.\r
90 */\r
91\r
92 /**\r
93 * @private\r
94 */\r
95 cls: Ext.baseCSSPrefix + 'mask',\r
96 componentCls: Ext.baseCSSPrefix + 'border-box',\r
97 \r
98 ariaRole: 'status',\r
99 focusable: true,\r
100 tabIndex: 0,\r
101 \r
102 autoEl: {\r
103 tag: 'div',\r
104 role: 'status'\r
105 },\r
106\r
107 childEls: [\r
108 'msgWrapEl',\r
109 'msgEl',\r
110 'msgTextEl'\r
111 ],\r
112\r
113 renderTpl: [\r
114 '<div id="{id}-msgWrapEl" data-ref="msgWrapEl" class="{[values.$comp.msgWrapCls]}">',\r
115 '<div id="{id}-msgEl" data-ref="msgEl" class="{[values.$comp.msgCls]} ',\r
116 Ext.baseCSSPrefix, 'mask-msg-inner {childElCls}">',\r
117 '<div id="{id}-msgTextEl" data-ref="msgTextEl" class="',\r
118 Ext.baseCSSPrefix, 'mask-msg-text',\r
119 '{childElCls}">{msg}</div>',\r
120 '</div>',\r
121 '</div>'\r
122 ],\r
123\r
124 maskOnDisable : false,\r
125 \r
126 /**\r
127 * @private\r
128 */\r
129 skipLayout: true,\r
130\r
131 /**\r
132 * Creates new LoadMask.\r
133 * @param {Object} [config] The config object.\r
134 */\r
135 constructor : function(config) {\r
136 var me = this,\r
137 comp;\r
138\r
139 if (arguments.length === 2) {\r
140 //<debug>\r
141 if (Ext.isDefined(Ext.global.console)) {\r
142 Ext.global.console.warn('Ext.LoadMask: LoadMask now uses a standard 1 arg constructor: use the target config');\r
143 }\r
144 //</debug>\r
145 comp = me.target = config;\r
146 config = arguments[1];\r
147 } else {\r
148 comp = config.target;\r
149 }\r
150 \r
151 //<debug>\r
152 if (config.maskCls) {\r
153 Ext.log.warn('Ext.LoadMask property maskCls is deprecated, use msgWrapCls instead');\r
154 config.msgWrapCls = config.msgWrapCls || config.maskCls;\r
155 }\r
156 //</debug>\r
157 \r
158 // Must apply configs early so that renderTo can be calculated correctly.\r
159 me.callParent([config]);\r
160\r
161 // Target is a Component\r
162 if (comp.isComponent) {\r
163 me.ownerCt = comp;\r
164 me.hidden = true;\r
165\r
166 // Ask the component which element should be masked.\r
167 // Most will not have an answer, in which case this returns the document body\r
168 // Ext.view.Table for example returns the el of its owning Panel.\r
169 me.renderTo = me.getMaskTarget();\r
170 me.external = me.renderTo === Ext.getBody();\r
171 me.bindComponent(comp);\r
172 }\r
173 // Element support to be deprecated\r
174 else {\r
175 //<debug>\r
176 if (Ext.isDefined(Ext.global.console)) {\r
177 Ext.global.console.warn('Ext.LoadMask: LoadMask for elements has been deprecated, use Ext.dom.Element.mask & Ext.dom.Element.unmask');\r
178 }\r
179 //</debug>\r
180 comp = Ext.get(comp);\r
181 me.isElement = true;\r
182 me.renderTo = me.target;\r
183 }\r
184 me.render(me.renderTo);\r
185 if (me.store) {\r
186 me.bindStore(me.store, true);\r
187 }\r
188 },\r
189\r
190 initRenderData: function() {\r
191 var result = this.callParent(arguments);\r
192 result.msg = this.msg || '';\r
193 return result;\r
194 },\r
195 \r
196 onRender: function() {\r
197 this.callParent(arguments);\r
198 \r
199 // In versions prior to 5.1, maskEl was rendered outside of the\r
200 // LoadMask's main el and had a reference to it; we keep this\r
201 // reference for backwards compatibility.\r
202 this.maskEl = this.el;\r
203 },\r
204\r
205 bindComponent: function(comp) {\r
206 var me = this,\r
207 listeners = {\r
208 scope: this,\r
209 resize: me.sizeMask\r
210 };\r
211\r
212 if (me.external) {\r
213 listeners.added = me.onComponentAdded;\r
214 listeners.removed = me.onComponentRemoved;\r
215 if (comp.floating) {\r
216 listeners.move = me.sizeMask;\r
217 me.activeOwner = comp;\r
218 } else if (comp.ownerCt) {\r
219 me.onComponentAdded(comp.ownerCt);\r
220 }\r
221 }\r
222\r
223 me.mon(comp, listeners);\r
224 \r
225 // Subscribe to the observer that manages the hierarchy\r
226 // Only needed if we had to be rendered outside of the target\r
227 if (me.external) {\r
228 me.mon(Ext.GlobalEvents, {\r
229 show: me.onContainerShow,\r
230 hide: me.onContainerHide,\r
231 expand: me.onContainerExpand,\r
232 collapse: me.onContainerCollapse,\r
233 scope: me\r
234 });\r
235 }\r
236 },\r
237\r
238 onComponentAdded: function(owner) {\r
239 var me = this;\r
240 delete me.activeOwner;\r
241 me.floatParent = owner;\r
242 if (!owner.floating) {\r
243 owner = owner.up('[floating]');\r
244 }\r
245 if (owner) {\r
246 me.activeOwner = owner;\r
247 me.mon(owner, 'move', me.sizeMask, me);\r
248 me.mon(owner, 'tofront', me.onOwnerToFront, me);\r
249 } else {\r
250 me.preventBringToFront = true;\r
251 }\r
252 owner = me.floatParent.ownerCt;\r
253 if (me.rendered && me.isVisible() && owner) {\r
254 me.floatOwner = owner;\r
255 me.mon(owner, 'afterlayout', me.sizeMask, me, {single: true});\r
256 }\r
257 },\r
258\r
259 onComponentRemoved: function(owner) {\r
260 var me = this,\r
261 activeOwner = me.activeOwner,\r
262 floatOwner = me.floatOwner;\r
263\r
264 if (activeOwner) {\r
265 me.mun(activeOwner, 'move', me.sizeMask, me);\r
266 me.mun(activeOwner, 'tofront', me.onOwnerToFront, me);\r
267 }\r
268 if (floatOwner) {\r
269 me.mun(floatOwner, 'afterlayout', me.sizeMask, me);\r
270 }\r
271 delete me.activeOwner;\r
272 delete me.floatOwner;\r
273 },\r
274\r
275 afterRender: function() {\r
276 var me = this;\r
277 \r
278 me.callParent(arguments);\r
279 \r
280 // In IE8-11, clicking on an inner msgEl will focus it, despite\r
281 // it having no tabindex attribute and thus being canonically\r
282 // non-focusable. Placing unselectable="on" attribute will make\r
283 // it unfocusable but will also prevent clicks from focusing\r
284 // the parent element. We want clicks within the mask's main el\r
285 // to focus it, hence the workaround.\r
286 if (Ext.isIE) {\r
287 me.el.on('mousedown', me.onMouseDown, me);\r
288 }\r
289\r
290 // This LoadMask shares the DOM and may be tipped out by the use of innerHTML\r
291 // Ensure the element does not get garbage collected from under us.\r
292 this.el.skipGarbageCollection = true;\r
293 },\r
294 \r
295 onMouseDown: function(e) {\r
296 var el = this.el;\r
297 \r
298 if (e.within(el)) {\r
299 e.preventDefault();\r
300 el.focus();\r
301 }\r
302 },\r
303\r
304 onOwnerToFront: function(owner, zIndex) {\r
305 this.el.setStyle('zIndex', zIndex + 1);\r
306 },\r
307\r
308 // Only called if we are rendered external to the target.\r
309 // Best we can do is show.\r
310 onContainerShow: function(container) {\r
311 if (!this.isHierarchicallyHidden()) {\r
312 this.onComponentShow();\r
313 }\r
314 },\r
315\r
316 // Only called if we are rendered external to the target.\r
317 // Best we can do is hide.\r
318 onContainerHide: function(container) {\r
319 if (this.isHierarchicallyHidden()) {\r
320 this.onComponentHide();\r
321 }\r
322 },\r
323\r
324 // Only called if we are rendered external to the target.\r
325 // Best we can do is show.\r
326 onContainerExpand: function(container) {\r
327 if (!this.isHierarchicallyHidden()) {\r
328 this.onComponentShow();\r
329 }\r
330 },\r
331\r
332 // Only called if we are rendered external to the target.\r
333 // Best we can do is hide.\r
334 onContainerCollapse: function(container) {\r
335 if (this.isHierarchicallyHidden()) {\r
336 this.onComponentHide();\r
337 }\r
338 },\r
339\r
340 onComponentHide: function() {\r
341 var me = this;\r
342\r
343 if (me.rendered && me.isVisible()) {\r
344 me.hide();\r
345 me.showNext = true;\r
346 }\r
347 },\r
348\r
349 onComponentShow: function() {\r
350 if (this.showNext) {\r
351 this.show();\r
352 }\r
353 delete this.showNext;\r
354 },\r
355\r
356 /**\r
357 * @private\r
358 * Called when this LoadMask's Component is resized. The toFront method rebases and resizes the modal mask.\r
359 */\r
360 sizeMask: function() {\r
361 var me = this,\r
362 // Need to use the closest floating component (if it exists) as the basis\r
363 // for our z-index positioning\r
364 target = me.activeOwner || me.target,\r
365 boxTarget = me.external ? me.getOwner().el : me.getMaskTarget();\r
366\r
367 if (me.rendered && me.isVisible()) {\r
368 // Only need to move and size the message wrap if we are outside of\r
369 // the masked element.\r
370 // If we are inside, it will be left:0;top:0;width:100%;height:100% by default\r
371 if (me.external) {\r
372 if (!me.isElement && target.floating) {\r
373 me.onOwnerToFront(target, target.el.getZIndex());\r
374 }\r
375 me.el.setSize(boxTarget.getSize()).alignTo(boxTarget, 'tl-tl');\r
376 }\r
377 \r
378 // Always need to center the message wrap\r
379 me.msgWrapEl.center(me.el);\r
380 }\r
381 },\r
382\r
383 /**\r
384 * Changes the data store bound to this LoadMask.\r
385 * @param {Ext.data.Store} store The store to bind to this LoadMask\r
386 */\r
387 bindStore : function(store, initial) {\r
388 var me = this;\r
389\r
390 // If the server returns a failure, and the proxy fires an exception instead of\r
391 // loading the store, the mask must clear.\r
392 Ext.destroy(me.proxyListeners);\r
393\r
394 me.mixins.storeholder.bindStore.apply(me, arguments);\r
395 store = me.store;\r
396\r
397 if (store) {\r
398 // Skip ChainedStores to the store that does the loading\r
399 while (store.getSource) {\r
400 store = store.getSource();\r
401 }\r
402 if (!store.loadsSynchronously()) {\r
403 me.proxyListeners = store.getProxy().on({\r
404 exception: me.onLoad,\r
405 scope: me,\r
406 destroyable: true\r
407 });\r
408 }\r
409\r
410 if (store.isLoading()) {\r
411 me.onBeforeLoad();\r
412 }\r
413 }\r
414 },\r
415\r
416 getStoreListeners: function(store) {\r
417 var onLoad = this.onLoad,\r
418 beforeLoad = this.onBeforeLoad,\r
419 result = {\r
420 // Fired when a range is requested for rendering that is not in the cache\r
421 cachemiss: beforeLoad,\r
422\r
423 // Fired when a range for rendering which was previously missing from the cache is loaded.\r
424 // buffer so that scrolling and store filling has settled, and the results have been rendered.\r
425 cachefilled: {\r
426 fn: onLoad,\r
427 buffer: 100\r
428 }\r
429 };\r
430\r
431 // Only need to mask on load if the proxy is asynchronous - ie: Ajax/JsonP\r
432 if (!store.loadsSynchronously()) {\r
433 result.beforeload = beforeLoad;\r
434 result.load = onLoad;\r
435 }\r
436 return result;\r
437 },\r
438\r
439 onDisable : function() {\r
440 this.callParent(arguments);\r
441 if (this.loading) {\r
442 this.onLoad();\r
443 }\r
444 },\r
445\r
446 getOwner: function() {\r
447 return this.ownerCt || this.ownerCmp || this.floatParent;\r
448 },\r
449\r
450 getMaskTarget: function() {\r
451 var owner = this.getOwner();\r
452 if (this.isElement) {\r
453 return this.target;\r
454 }\r
455 return this.useTargetEl ? owner.getTargetEl() : (owner.getMaskTarget() || Ext.getBody());\r
456 },\r
457\r
458 /**\r
459 * @private\r
460 */\r
461 onBeforeLoad : function() {\r
462 var me = this,\r
463 owner = me.getOwner(),\r
464 origin;\r
465\r
466 if (!me.disabled) {\r
467 me.loading = true;\r
468 // If the owning Component has not been layed out, defer so that the ZIndexManager\r
469 // gets to read its layed out size when sizing the modal mask\r
470 if (owner.componentLayoutCounter) {\r
471 me.maybeShow();\r
472 } else {\r
473 // The code below is a 'run-once' interceptor.\r
474 origin = owner.afterComponentLayout;\r
475 owner.afterComponentLayout = function() {\r
476 owner.afterComponentLayout = origin;\r
477 origin.apply(owner, arguments);\r
478 me.maybeShow();\r
479 };\r
480 }\r
481 }\r
482 },\r
483\r
484 maybeShow: function() {\r
485 var me = this,\r
486 owner = me.getOwner();\r
487\r
488 if (!owner.isVisible(true)) {\r
489 me.showNext = true;\r
490 }\r
491 else if (me.loading && owner.rendered) {\r
492 me.show();\r
493 }\r
494 },\r
495\r
496 hide: function() {\r
497 var me = this,\r
498 ownerCt = me.ownerCt;\r
499 \r
500 // Element support to be deprecated\r
501 if (me.isElement) {\r
502 ownerCt.unmask();\r
503 me.fireEvent('hide', this);\r
504 \r
505 return;\r
506 }\r
507 \r
508 // Could be already nulled while destroying\r
509 if (ownerCt) {\r
510 ownerCt.enableTabbing();\r
511 ownerCt.setMasked(false);\r
512 }\r
513 \r
514 delete me.showNext;\r
515 \r
516 return me.callParent(arguments);\r
517 },\r
518\r
519 show: function() {\r
520 var me = this;\r
521\r
522 // Element support to be deprecated\r
523 if (me.isElement) {\r
524 me.ownerCt.mask(this.useMsg ? this.msg : '', this.msgCls);\r
525 me.fireEvent('show', this);\r
526 \r
527 return;\r
528 }\r
529\r
530 return me.callParent(arguments);\r
531 },\r
532\r
533 afterShow: function() { \r
534 var me = this,\r
535 ownerCt = me.ownerCt;\r
536\r
537 me.loading = true;\r
538 me.callParent(arguments);\r
539\r
540 ownerCt.disableTabbing();\r
541 ownerCt.setMasked(true);\r
542 \r
543 // Owner's disabled tabbing will also make the mask\r
544 // untabbable since it is rendered within the target\r
545 me.el.restoreTabbableState();\r
546\r
547 me.syncMaskState();\r
548 },\r
549\r
550 /**\r
551 * Synchronizes the visible state of the mask with the configuration settings such\r
552 * as {@link #msgWrapCls}, {@link #msg}, sizes the mask to occlude the target element or Component\r
553 * and focuses the mask.\r
554 * @private\r
555 */\r
556 syncMaskState: function() {\r
557 var me = this,\r
558 ownerCt = me.ownerCt,\r
559 el = me.el;\r
560\r
561 if (me.isVisible()) {\r
562\r
563 // Allow dynamic setting of msgWrapCls\r
564 if (me.hasOwnProperty('msgWrapCls')) {\r
565 el.dom.className = me.msgWrapCls;\r
566 }\r
567\r
568 if (me.useMsg) {\r
569 me.msgTextEl.setHtml(me.msg);\r
570 } else {\r
571 // Only the mask is visible if useMsg is false\r
572 me.msgEl.hide();\r
573 }\r
574\r
575 if (me.shim || Ext.useShims) {\r
576 el.enableShim(null, true);\r
577 } else {\r
578 // Just in case me.shim was changed since last time we were shown (by\r
579 // Component#setLoading())\r
580 el.disableShim();\r
581 }\r
582\r
583 // If owner contains focus, focus this.\r
584 // Component level onHide processing takes care of focus reversion on hide.\r
585 if (ownerCt.el.contains(Ext.Element.getActiveElement())) {\r
586 me.focus();\r
587 }\r
588 me.sizeMask();\r
589 }\r
590 },\r
591\r
592 /**\r
593 * @private\r
594 */\r
595 onLoad : function() {\r
596 this.loading = false;\r
597 this.hide();\r
598 },\r
599\r
600 beforeDestroy: function() {\r
601 // We don't have a real ownerCt, so clear it out here to prevent\r
602 // spurious warnings when we are destroyed\r
603 this.ownerCt = null;\r
604 this.bindStore(null);\r
605 this.callParent();\r
606 },\r
607\r
608 onDestroy: function() {\r
609 var me = this;\r
610\r
611 if (me.isElement) {\r
612 me.ownerCt.unmask();\r
613 }\r
614\r
615 me.callParent();\r
616 }\r
617});\r