Ext.ux.Accordion extension class code:
1 // vim: ts=2:sw=2:nu:fdc=2:nospell
2
3 // Create user extensions namespace (Ext.ux)
4 Ext.namespace('Ext.ux');
5
6 /**
7 * Ext.ux.Accordion Extension Class
8 *
9 * @author Ing. Jozef Sakalos
10 * @version $Id: Ext.ux.Accordion.js 18 2007-06-13 17:55:16Z jozo $
11 *
12 * @class Ext.ux.Accordion
13 * @extends Ext.ContentPanel
14 * @constructor
15 * @param {String/HTMLElement/Element} el The container element for this panel
16 * @param {String/Object} config A string to set only the title or a config object
17 * @cfg {Boolean} animate global animation flag for all panels. (defaults to true)
18 * @cfg {Boolean} boxWrap set to true to wrap wrapEl the body is child of (defaults to false)
19 * @cfg {Boolean} draggable set to false to disallow panels dragging (defaults to true)
20 * @cfg {Boolean} fitHeight set to true if you use fixed height dock
21 * @cfg {Boolean} independent true to make panels independent (defaults to false)
22 * @cfg {Integer} initialHeight Initial height to set box to (defaults to 0)
23 * @cfg {Boolean} monitorWindowResize if true panels are moved to
24 * viewport if window is small (defaults to true)
25 * @cfg {Boolean} resizable global resizable flag for all panels (defaults to true)
26 * @cfg {Boolean} undockable true to allow undocking of panels (defaults to true)
27 * @cfg {Boolean} useShadow global useShadow flag for all panels. (defaults to true)
28 * @cfg {Element/HTMLElement/String} wrapEl Element to wrap with nice surrounding
29 */
30 Ext.ux.Accordion = function(el, config) {
31
32 // call parent constructor
33 Ext.ux.Accordion.superclass.constructor.call(this, el, config);
34
35 // create collection for panels
36 this.items = new Ext.util.MixedCollection();
37
38 // assume no panel is expanded
39 this.expanded = null;
40
41 // {{{
42 // install event handlers
43 this.on({
44
45 // {{{
46 // runs before expansion. Triggered by panel's beforeexpand event
47 beforeexpand: {
48 scope: this
49 , fn: function(panel) {
50 // raise panel above others
51 if(!panel.docked) {
52 this.raise(panel);
53 }
54
55 // set fixed height
56 var panelBodyHeight;
57 if(this.fitHeight && panel.docked) {
58 panelBodyHeight = this.getPanelBodyHeight();
59 if(panelBodyHeight) {
60 panel.body.setHeight(panelBodyHeight);
61 }
62 }
63
64 if(panel.docked) {
65 this.expandCount++;
66 this.expanding = true;
67 // this.setDockScroll(false);
68 }
69
70 // don't collapse others if independent or not docked
71 if(this.independent || !panel.docked) {
72 return this;
73 }
74
75 // collapse expanded panel
76 if(this.expanded && this.expanded.docked) {
77 this.expanded.collapse();
78 }
79
80 // remember this panel as expanded
81 this.expanded = panel;
82 }}
83 // }}}
84 // {{{
85 // runs before panel collapses. Triggered by panel's beforecollapse event
86 , beforecollapse: {
87 scope: this
88 , fn: function(panel) {
89
90 // raise panel if not docked
91 if(!panel.docked) {
92 this.raise(panel);
93 }
94 return this;
95 }}
96 // }}}
97 // {{{
98 // runs on when panel expands (before animation). Triggered by panel's expand event
99 , expand: {
100 scope: this
101 , fn: function(panel) {
102 this.fireEvent('panelexpand', panel);
103 }}
104 // }}}
105 // {{{
106 // runs on when panel collapses (before animation). Triggered by panel's collapse event
107 , collapse: {
108 scope: this
109 , fn: function(panel) {
110 this.fireEvent('panelcollapse', panel);
111 }}
112 // }}}
113 // {{{
114 // runs on when animation is completed. Triggered by panel's animationcompleted event
115 , animationcompleted: {
116 scope: this
117 , fn: function(panel) {
118 var box = panel.el.getBox();
119 this.expandCount = (this.expandCount && this.expanding) ? --this.expandCount : 0;
120 if((0 === this.expandCount) && this.expanding) {
121 // this.setDockScroll(true);
122 this.expanding = false;
123 }
124 this.fireEvent('panelbox', panel, box);
125 }}
126 // }}}
127 // {{{
128 // runs when panel is pinned. Triggered by panel's pinned event
129 , pinned: {
130 scope: this
131 , fn: function(panel, pinned) {
132 if(!pinned) {
133 if(panel.collapseOnUnpin) {
134 panel.collapse();
135 }
136 else if(!this.independent) {
137 this.items.each(function(p) {
138 if(p !== panel && p.docked && !p.pinned) {
139 p.collapse();
140 }
141 });
142 this.expanded = panel;
143 }
144 }
145 this.fireEvent('panelpinned', panel, pinned);
146 }}
147 // }}}
148
149 , destroy: {
150 scope:this
151 , fn: function(panel) {
152 this.items.removeKey(panel.id);
153 this.updateOrder();
154 }}
155 });
156 // }}}
157 // {{{
158 // add events
159 this.addEvents({
160 /**
161 * Fires when a panel of the dock is collapsed
162 * @event panelcollapse
163 * @param {Ext.ux.InfoPanel} panel
164 */
165 panelcollapse: true
166
167 /**
168 * Fires when a panel of the dock is expanded
169 * @event panelexpand
170 * @param {Ext.ux.InfoPanel} panel
171 */
172 , panelexpand: true
173
174 /**
175 * Fires when a panel of the dock is pinned
176 * @event panelpinned
177 * @param {Ext.ux.InfoPanel} panel
178 * @param {Boolean} pinned true if panel was pinned false if unpinned
179 */
180 , panelpinned: true
181
182 /**
183 * Fires when the independent state of dock changes
184 * @event independent
185 * @param {Ext.ux.Accordion} this
186 * @param {Boolean} independent New independent state
187 */
188 , independent: true
189
190 /**
191 * Fires when the order of panel is changed
192 * @event orderchange
193 * @param {Ext.ux.Accordion} this
194 * @param {Array} order New order array
195 */
196 , orderchange: true
197
198 /**
199 * Fires when the undockable state of dock changes
200 * @event undockable
201 * @param {Ext.ux.Accordion} this
202 * @param {Array} undockable New undockable state
203 */
204 , undockable: true
205
206 /**
207 * Fires when a panel is undocked
208 * @event panelundock
209 * @param {Ext.ux.InfoPanel} panel
210 * @param {Object} box Position and size object
211 */
212 , panelundock: true
213
214 /**
215 * Fires when a panel is undocked
216 * @event paneldock
217 * @param {Ext.ux.InfoPanel} panel
218 */
219 , paneldock: true
220
221 /**
222 * Fires when a panel box is changed, e.g. after dragging
223 * @event panelbox
224 * @param {Ext.ux.InfoPanel} panel
225 * @param {Object} box Position and size object
226 */
227 , panelbox: true
228
229 /**
230 * Fires when useShadow status changes
231 * @event useshadow
232 * @param {Ext.ux.Accordion} this
233 * @param {Boolean} shadow Use shadow (for undocked panels) flag
234 */
235 , useshadow: true
236 });
237 // }}}
238
239 // setup body
240 this.body = Ext.get(this.body) || this.el;
241 this.resizeEl = this.body;
242 this.id = this.el.id;
243 this.body.addClass('x-dock-body');
244
245 // setup desktop
246 this.desktop = Ext.get(this.desktop || document.body);
247 //this.desktop = this.desktop.dom || this.desktop;
248
249 // setup fixed hight
250 this.wrapEl = Ext.get(this.wrapEl);
251 if(this.fitHeight) {
252 this.body.setStyle('overflow', 'hidden');
253 // this.bodyHeight = this.initialHeight || this.body.getHeight();
254 this.body.setHeight(this.initialHeight || this.body.getHeight());
255 if(this.boxWrap && this.wrapEl) {
256 this.wrapEl.boxWrap();
257 }
258 }
259
260 // watch window resize
261 if(this.monitorWindowResize) {
262 Ext.EventManager.onWindowResize(this.adjustViewport, this);
263 }
264
265 // create drop zone for panels
266 this.dd = new Ext.dd.DropZone(this.body.dom, {ddGroup:'dock-' + this.id });
267
268 }; // end of constructor
269
270 // extend
271 Ext.extend(Ext.ux.Accordion, Ext.ContentPanel, {
272
273 // {{{
274 // defaults
275 independent: false
276 , undockable: true
277 , useShadow: true
278 , boxWrap: false
279 , fitHeight: false
280 , initialHeight: 0
281 , animate: true // global animation flag
282 , zindex: 9999 // (private)
283 , zindexInc: 2 // (private) one for shadow
284 , expandCount: 0
285 , expanding: false
286 , monitorWindowResize: true
287 , resizable: true // global resizable flag
288 , draggable: true // global draggable flag
289 // }}}
290 // {{{
291 /**
292 * Adds the panel to Accordion
293 * @param {Ext.ux.InfoPanel} panel Panel to add
294 * @return {Ext.ux.InfoPanel} added panel
295 */
296 , add: function(panel) {
297
298
299 // append panel to body
300 this.body.appendChild(panel.el);
301
302 panel.docked = true;
303
304 // add docked class to panel body
305 // panel.body.addClass('x-dock-panel-body-docked');
306 // panel.body.removeClass('x-dock-panel-body-undocked');
307 panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
308
309 // add panel to items collection
310 this.items.add(panel.el.id, panel);
311
312 // relay these events from panel to dock
313 this.relayEvents(panel, [
314 'beforecollapse'
315 , 'collapse'
316 , 'beforeexpand'
317 , 'expand'
318 , 'animationcompleted'
319 , 'pinned'
320 , 'boxchange'
321 , 'destroy'
322 ]);
323
324 // panel dragging
325 if(this.draggable) {
326 panel.dd = new Ext.ux.Accordion.DDDock(panel, 'dock-' + this.id, this);
327 }
328
329 // panel resizing
330 panel.resizable = this.resizable;
331 // panel.setResizable(this.resizable);
332
333 // shadow and animate flags
334 panel.useShadow = this.useShadow;
335 panel.setShadow(this.useShadow);
336 if(panel.shadow) {
337 panel.shadow.hide();
338 }
339 panel.animate = undefined === panel.animate ? this.animate : panel.animate;
340
341 // z-index for panel
342 this.zindex += this.zindexInc;
343 panel.zindex = this.zindex;
344
345 // onclick handler for panel body (allows raising when panel body is clicked)
346 panel.body.on('click', this.onClickPanelBody.createDelegate(this, [panel]));
347
348 if(this.fitHeight) {
349 this.setPanelHeight(panel);
350 }
351
352 panel.dock = this;
353 panel.desktop = this.desktop;
354
355 return panel;
356
357 }
358 // }}}
359 // {{{
360 /**
361 * Called internally to raise panel above others
362 * Maintains z-index stack
363 * @param {Ext.ux.InfoPanel} panel Panel to raise
364 */
365 , raise: function(panel) {
366 this.items.each(function(p) {
367 if(p.zindex > panel.zindex) {
368 p.zindex -= this.zindexInc;
369 p.el.applyStyles({'z-index':p.zindex});
370 if(!p.docked) {
371 p.setShadow(true);
372 }
373 }
374 }, this);
375 panel.zindex = this.zindex;
376 panel.el.applyStyles({'z-index':panel.zindex});
377 if(this.desktop.lastChild !== panel.el.dom) {
378 this.desktop.appendChild(panel.el.dom);
379 }
380 if(!panel.docked) {
381 panel.setShadow(true);
382 }
383 }
384 // }}}
385 // {{{
386 /**
387 * Resets the order of panels within the dock
388 *
389 * @return {Ext.ux.Accordion} this
390 */
391 , resetOrder: function() {
392 this.items.each(function(panel) {
393 if(!panel.docked) {
394 return;
395 }
396 this.body.appendChild(panel.el);
397 }, this);
398 this.updateOrder();
399 return this;
400 }
401 // }}}
402 // {{{
403 /**
404 * Called internally to update the order variable after dragging
405 */
406 , updateOrder: function() {
407 var order = [];
408 var titles = this.body.select('.x-layout-panel-hd');
409 titles.each(function(titleEl){
410 order.push(titleEl.dom.parentNode.id);
411 });
412 this.order = order;
413 this.fireEvent('orderchange', this, order);
414 }
415 // }}}
416 // {{{
417 /**
418 * Returns array of panel ids in the current order
419 * @return {Array} order of panels
420 */
421 , getOrder: function() {
422 return this.order;
423 }
424 // }}}
425 // {{{
426 /**
427 * Set the order of panels
428 * @param {Array} order Array of ids of panels in required order.
429 * @return {Ext.ux.Accordion} this
430 */
431 , setOrder: function(order) {
432 if('object' !== typeof order || undefined === order.length) {
433 throw "setOrder: Argument is not array.";
434 }
435 var panelEl;
436 for(var i = 0; i < order.length; i++) {
437 panelEl = Ext.get(order[i]);
438 if(panelEl) {
439 this.body.appendChild(panelEl);
440 }
441 }
442 this.updateOrder();
443 return this;
444 }
445 // }}}
446 // {{{
447 /**
448 * Collapse all docked panels
449 * @param {Boolean} alsoPinned true to first unpin then collapse
450 * @param {Ext.ux.InfoPanel} except This panel will not be collapsed.
451 * @return {Ext.ux.Accordion} this
452 */
453 , collapseAll: function(alsoPinned, except) {
454 this.items.each(function(panel) {
455 if(panel.docked) {
456 panel.pinned = alsoPinned ? false : panel.pinned;
457 if(!except || panel !== except) {
458 panel.collapse();
459 }
460 }
461 }, this);
462 return this;
463 }
464 // }}}
465 // {{{
466 /**
467 * Expand all docked panels in independent mode
468 * @return {Ext.ux.Accordion} this
469 */
470 , expandAll: function() {
471 if(this.independent) {
472 this.items.each(function(panel) {
473 if(panel.docked && panel.collapsed) {
474 panel.expand();
475 }
476 }, this);
477 }
478 }
479 // }}}
480 // {{{
481 /**
482 * Called internally while dragging and by state manager
483 * @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
484 * @box {Object} box coordinates with target position and size
485 * @return {Ext.ux.Accordion} this
486 */
487 , undock: function(panel, box) {
488
489 // get panel if necessary
490 panel = 'string' === typeof panel ? this.items.get(panel) : panel;
491
492 // proceed only if we have docked panel and in undockable mode
493 if(panel && panel.docked && this.undockable) {
494
495 // sanity check
496 if(box.x < 0 || box.y < 0) {
497 return this;
498 }
499
500 // move the panel in the dom (append to desktop)
501 this.desktop.appendChild(panel.el.dom);
502
503 // adjust panel visuals
504 panel.el.applyStyles({
505 position:'absolute'
506 , 'z-index': panel.zindex
507 });
508 panel.body.replaceClass('x-dock-panel-body-docked', 'x-dock-panel-body-undocked');
509
510 // position the panel
511 panel.setBox(box);
512
513 // reset docked flag
514 panel.docked = false;
515
516 // hide panel shadow (will be shown by raise)
517 if(panel.shadow) {
518 panel.shadow.hide();
519 }
520
521 // raise panel above others
522 this.raise(panel);
523
524 // set the height of a docked expanded panel
525 this.setPanelHeight(this.expanded);
526
527 // enable resizing and scrolling
528 panel.setResizable(!panel.collapsed);
529 if(panel.bodyScroll) {
530 panel.body.setStyle('overflow','auto');
531 }
532
533 // size the undocked panel
534 // todo: revise
535 panel.lastWidth = box.width;
536 panel.lastHeight = box.height;
537
538 // fire panelundock event
539 this.fireEvent('panelundock', panel, {x:box.x, y:box.y, width:box.width, height:box.height});
540 }
541
542 return this;
543 }
544 // }}}
545 // {{{
546 /**
547 * Called internally while dragging
548 * @param {Ext.ux.InfoPanel/String} panel Panel object or id of the panel
549 * @param {String} targetId id of panel after which this panel will be docked
550 * @return {Ext.ux.Accordion} this
551 */
552 , dock: function(panel, targetId) {
553
554 // get panel if necessary
555 panel = 'string' === typeof panel ? this.items.get(panel) : panel;
556
557 // proceed only if we have a docked panel
558 if(panel && !panel.docked) {
559
560 // remember width and height
561 if(!panel.collapsed) {
562 panel.lastWidth = panel.el.getWidth();
563 panel.lastHeight = panel.el.getHeight();
564 }
565
566 // move the panel element in the dom
567 if(targetId && (this.body.id !== targetId)) {
568 panel.el.insertBefore(Ext.fly(targetId));
569 }
570 else {
571 panel.el.appendTo(this.body);
572 }
573
574 // set docked flag
575 panel.docked = true;
576
577 // adjust panel visuals
578 panel.body.replaceClass('x-dock-panel-body-undocked', 'x-dock-panel-body-docked');
579 panel.el.applyStyles({
580 top:''
581 , left:''
582 , width:''
583 , height:''
584 , 'z-index':''
585 , position:'relative'
586 , visibility:''
587 });
588 panel.body.applyStyles({width:'',height:''});
589
590 // disable resizing and shadow
591 panel.setResizable(false);
592 if(panel.shadow) {
593 panel.shadow.hide();
594 }
595
596 // set panel height (only if this.fitHeight = true)
597 this.setPanelHeight(panel.collapsed ? this.expanded : panel);
598
599 // fire paneldock event
600 this.fireEvent('paneldock', panel);
601 }
602
603 return this;
604 }
605 // }}}
606 // {{{
607 /**
608 * Sets the independent mode
609 * @param {Boolean} independent set to false for normal mode
610 * @return {Ext.ux.Accordion} this
611 */
612 , setIndependent: function(independent) {
613 this.independent = independent ? true : false;
614 this.fireEvent('independent', this, independent);
615 return this;
616 }
617 // }}}
618 // {{{
619 /**
620 * Sets the undockable mode
621 * If undockable === true all undocked panels are docked and collapsed (except pinned)
622 * @param {Boolean} undockable set to true to not allow undocking
623 * @return {Ext.ux.Accordion} this
624 */
625 , setUndockable: function(undockable) {
626 this.items.each(function(panel) {
627
628 // dock and collapse (except pinned) all undocked panels if not undockable
629 if(!undockable && !panel.docked) {
630 this.dock(panel);
631 if(!this.independent && !panel.collapsed && !panel.pinned) {
632 panel.collapse();
633 }
634 }
635
636 // refresh dragging constraints
637 if(panel.docked && panel.draggable) {
638 panel.dd.constrainTo(this.body, 0, false);
639 panel.dd.clearConstraints();
640 if(undockable) {
641 panel.constrainToDesktop();
642 }
643 else {
644 panel.dd.setXConstraint(0,0);
645 }
646 }
647 }, this);
648
649 // set the flag and fire event
650 this.undockable = undockable;
651 this.fireEvent('undockable', this, undockable);
652 return this;
653 }
654 // }}}
655 // {{{
656 /**
657 * Restores state of dock and panels
658 * @param {Ext.state.Provider} provider (optional) An alternate state provider
659 */
660 , restoreState: function(provider) {
661 if(!provider) {
662 provider = Ext.state.Manager;
663 }
664 var sm = new Ext.ux.AccordionStateManager();
665 sm.init(this, provider);
666
667 }
668 // }}}
669 // {{{
670 /**
671 * Sets the shadows for all panels
672 * @param {Boolean} shadow set to false to disable shadows
673 * @return {Ext.ux.Accordion} this
674 */
675 , setShadow: function(shadow) {
676 this.items.each(function(panel) {
677 panel.useShadow = shadow;
678 panel.setShadow(false);
679 if(!panel.docked) {
680 panel.setShadow(shadow);
681 }
682 });
683 this.useShadow = shadow;
684 this.fireEvent('useshadow', this, shadow);
685 return this;
686 }
687 // }}}
688 // {{{
689 /**
690 * Called when user clicks the panel body
691 * @param {Ext.ux.InfoPanel} panel
692 */
693 , onClickPanelBody: function(panel) {
694 if(!panel.docked) {
695 this.raise(panel);
696 }
697 }
698 // }}}
699 // {{{
700 /**
701 * Called internally for fixed height docks to get current height of panel(s)
702 */
703 , getPanelBodyHeight: function() {
704 var titleHeight = 0;
705 this.items.each(function(panel) {
706 titleHeight += panel.docked ? panel.titleEl.getHeight() : 0;
707 });
708 this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb') + 1;
709 // this.panelBodyHeight = this.body.getHeight() - titleHeight - this.body.getFrameWidth('tb');
710 return this.panelBodyHeight;
711 }
712 // }}}
713 // {{{
714 /**
715 * Sets the height of panel body
716 * Used with fixed height (fitHeight:true) docs
717 * @param {Ext.ux.InfoPanel} panel (defaults to this.expanded)
718 * @return {Ext.ux.Accordion} this
719 */
720 , setPanelHeight: function(panel) {
721 panel = panel || this.expanded;
722 if(this.fitHeight && panel && panel.docked) {
723 panel.body.setHeight(this.getPanelBodyHeight());
724 }
725 return this;
726 }
727 // }}}
728 // {{{
729 /**
730 * Constrains the dragging of panels do the desktop
731 * @return {Ext.ux.Accordion} this
732 */
733 , constrainToDesktop: function() {
734 this.items.each(function(panel) {
735 panel.constrainToDesktop();
736 }, this);
737 return this;
738 }
739 // }}}
740 // {{{
741 /**
742 * Clears dragging constraints of panels
743 * @return {Ext.ux.Accordion} this
744 */
745 , clearConstraints: function() {
746 this.items.each(function(panel) {
747 panel.dd.clearConstraints();
748 });
749 }
750 // }}}
751 // {{{
752 /**
753 * Shows all panels
754 * @param {Boolean} show (optional) if false hides the panels instead of showing
755 * @param {Boolean} alsoUndocked show also undocked panels (defaults to false)
756 * @return {Ext.ux.Accordion} this
757 */
758 , showAll: function(show, alsoUndocked) {
759 show = (false === show ? false : true);
760 this.items.each(function(panel) {
761 panel.show(show, alsoUndocked);
762 });
763 return this;
764 }
765 // }}}
766 // {{{
767 /**
768 * Hides all panels
769 * @param {Boolean} alsoUndocked hide also undocked panels (defaults to false)
770 * @return {Ext.ux.Accordion} this
771 */
772 , hideAll: function(alsoUndocked) {
773 return this.showAll(false, alsoUndocked);
774 }
775 // }}}
776 // {{{
777 /**
778 * Called internally to disable/enable scrolling of the dock while animating
779 * @param {Boolean} enable true to enable, false to disable
780 * @return {void}
781 * @todo not used at present - revise
782 */
783 , setDockScroll: function(enable) {
784 if(enable && !this.fitHeight) {
785 this.body.setStyle('overflow','auto');
786 }
787 else {
788 this.body.setStyle('overflow','hidden');
789 }
790 }
791 // }}}
792 // {{{
793 /**
794 * Set Accordion size
795 * Overrides ContentPanel.setSize
796 *
797 * @param {Integer} w width
798 * @param {Integer} h height
799 * @return {Ext.ux.Accordion} this
800 */
801 , setSize: function(w, h) {
802 // call parent's setSize
803 Ext.ux.Accordion.superclass.setSize.call(this, w, h);
804 // this.body.setHeight(h);
805 this.setPanelHeight();
806
807 return this;
808 }
809 // }}}
810 // {{{
811 /**
812 * Called as windowResize event handler
813 *
814 * @todo: review
815 */
816 , adjustViewport: function() {
817 var viewport = this.desktop.dom === document.body ? {} : Ext.get(this.desktop).getBox();
818
819 viewport.height =
820 this.desktop === document.body
821 ? window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
822 : viewport.height
823 ;
824
825 viewport.width =
826 this.desktop === document.body
827 ? window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
828 : viewport.width
829 ;
830
831 viewport.x = this.desktop === document.body ? 0 : viewport.x;
832 viewport.y = this.desktop === document.body ? 0 : viewport.y;
833
834 this.items.each(function(panel) {
835 if(!panel.docked) {
836 panel.moveToViewport(viewport);
837 }
838 });
839
840 }
841 // }}}
842
843 }); // end of extend
844
845 // {{{
846 // {{{
847 /**
848 * @class Ext.ux.Accordion.DDDock
849 * @constructor
850 * @extends Ext.dd.DDProxy
851 * @param {Ext.ux.InfoPanel} panel Panel the dragging object is created for
852 * @param {String} group Only elements of same group interact
853 * @param {Ext.ux.Accordion} dock Place where panels are docked/undocked
854 */
855 Ext.ux.Accordion.DDDock = function(panel, group, dock) {
856
857 // call parent constructor
858 Ext.ux.Accordion.DDDock.superclass.constructor.call(this, panel.el.dom, group);
859
860 // save panel and dock references for use in methods
861 this.panel = panel;
862 this.dock = dock;
863
864 // drag by grabbing the title only
865 this.setHandleElId(panel.titleEl.id);
866
867 // move only in the dock if undockable
868 if(false === dock.undockable) {
869 this.setXConstraint(0, 0);
870 }
871
872 // init internal variables
873 this.lastY = 0;
874
875 //this.DDM.mode = Ext.dd.DDM.INTERSECT;
876 this.DDM.mode = Ext.dd.DDM.POINT;
877
878 }; // end of constructor
879 // }}}
880
881 // extend
882 Ext.extend(Ext.ux.Accordion.DDDock, Ext.dd.DDProxy, {
883
884 // {{{
885 /**
886 * Default DDProxy startDrag override
887 * Saves some variable for use by other methods
888 * and creates nice dragging proxy (ghost)
889 *
890 * Passed x, y arguments are not used
891 */
892 startDrag: function(x, y) {
893
894 this.lastMoveTarget = null;
895
896 // create nice dragging ghost
897 this.createGhost();
898
899 // get srcEl (the original) and dragEl (the ghost)
900 var srcEl = Ext.get(this.getEl());
901 var dragEl = Ext.get(this.getDragEl());
902
903 // refresh constraints
904 this.panel.constrainToDesktop();
905 var dragHeight, rightC, bottomC;
906 if(this.dock.undockable) {
907 if(this.panel.collapsed) {
908 dragHeight = this.panel.titleEl.getHeight();
909 }
910 else {
911 dragHeight = dragEl.getHeight();
912 dragHeight = dragHeight <= this.panel.titleEl.getHeight() ? srcEl.getHeight() : dragHeight;
913 }
914
915 rightC = this.rightConstraint + srcEl.getWidth() - dragEl.getWidth();
916 bottomC = this.bottomConstraint + srcEl.getHeight() - dragHeight;
917 this.setXConstraint(this.leftConstraint, rightC);
918 this.setYConstraint(this.topConstraint, bottomC);
919 }
920 else {
921 if(this.panel.docked) {
922 this.setXConstraint(0, 0);
923 }
924 }
925
926 // hide dragEl (will be shown by onDrag)
927 dragEl.hide();
928
929 // raise panel's "window" above others
930 if(!this.panel.docked) {
931 this.dock.raise(this.panel);
932 }
933
934 // hide panel's shadow if any
935 this.panel.setShadow(false);
936
937 // clear visibility of panel's body (was setup by animations)
938 this.panel.body.dom.style.visibility = '';
939
940 // hide source panel if undocked
941 if(!this.panel.docked) {
942 srcEl.hide();
943 dragEl.show();
944 }
945
946 } // end of function startDrag
947 // }}}
948 // {{{
949 /**
950 * Called internally to create nice dragging proxy (ghost)
951 */
952 , createGhost: function() {
953
954 // get variables
955 var srcEl = Ext.get(this.getEl());
956 var dragEl = Ext.get(this.getDragEl());
957 var dock = this.dock;
958 var panel = this.panel;
959
960 // adjust look of ghost
961 dragEl.addClass('x-dock-panel-ghost');
962 dragEl.applyStyles({border:'1px solid #84a0c4','z-index': dock.zindex + dock.zindexInc});
963
964 // set size of ghost same as original
965 dragEl.setBox(srcEl.getBox());
966 if(panel.docked) {
967 if(panel.lastWidth && dock.undockable) {
968 dragEl.setWidth(panel.lastWidth);
969 }
970 if(!panel.collapsed && dock.undockable && (panel.lastHeight > panel.titleEl.getHeight())) {
971 dragEl.setHeight(panel.lastHeight);
972 }
973 }
974
975 // remove unnecessary text nodes from srcEl
976 srcEl.clean();
977
978 // setup title
979 var dragTitleEl = Ext.DomHelper.append(dragEl, {tag:'div'}, true);
980 dragTitleEl.update(srcEl.dom.firstChild.innerHTML);
981 dragTitleEl.dom.className = srcEl.dom.firstChild.className;
982 if(panel.collapsed && Ext.isIE) {
983 dragTitleEl.dom.style.borderBottom = "0";
984 }
985
986 } // end of function createGhost
987 // }}}
988 // {{{
989 /**
990 * Default DDProxy onDragOver override
991 * It is called when dragging over a panel
992 * or over the dock.body DropZone
993 *
994 * @param {Event} e not used
995 * @param {String} targetId id of the target we're over
996 *
997 * Beware: While dragging over docked panels it's called
998 * twice. Once for panel and once for DropZone
999 */
1000 , onDragOver: function(e, targetId) {
1001
1002 this.currentTarget = targetId;
1003
1004 // save targetId for use by endDrag
1005 this.lastTarget = targetId;
1006
1007 // get panel element
1008 var srcEl = Ext.get(this.getEl());
1009
1010 // get target panel
1011 var targetPanel = this.dock.items.get(targetId);
1012
1013 // landing indicators
1014 if(targetPanel) {
1015 if(targetPanel.docked && (targetPanel.collapsed || !this.panel.docked)) {
1016 targetPanel.titleEl.addClass('x-dock-panel-title-dragover');
1017 }
1018 }
1019 else {
1020 if(!this.panel.docked) {
1021 this.dock.body.addClass('x-dock-body-dragover');
1022 }
1023 else {
1024 this.panel.titleEl.addClass('x-dock-panel-title-dragover');
1025 }
1026 }
1027
1028 // do nothing else if we're not over another docked panel
1029 if(!targetPanel || !targetPanel.docked) {
1030 return;
1031 }
1032
1033 // reorder panels in dock if we're docked too
1034 var targetEl;
1035 if(this.panel.docked) {
1036 targetEl = targetPanel.el;
1037
1038 if(targetPanel.collapsed || this.lastMoveTarget !== targetPanel) {
1039 if(this.movingUp) {
1040 srcEl.insertBefore(targetEl);
1041 this.lastMoveTarget = targetPanel;
1042 }
1043 else {
1044 srcEl.insertAfter(targetEl);
1045 this.lastMoveTarget = targetPanel;
1046 }
1047 }
1048 this.DDM.refreshCache(this.groups);
1049 }
1050
1051 } // end of function onDragOver
1052 // }}}
1053 // {{{
1054 /**
1055 * Called internally when cursor leaves a drop target
1056 * @param {Ext.Event} e
1057 * @param {String} targetId id of target we're leaving
1058 */
1059 , onDragOut: function(e, targetId) {
1060
1061 var targetPanel = this.dock.items.get(targetId);
1062
1063 if(!targetPanel) {
1064 this.dock.body.removeClass('x-dock-body-dragover');
1065 if(this.dock.body.id === targetId) {
1066 this.panel.titleEl.removeClass('x-dock-panel-title-dragover');
1067 }
1068 }
1069 else {
1070 targetPanel.titleEl.removeClass('x-dock-panel-title-dragover');
1071 }
1072 this.currentTarget = null;
1073 }
1074 // }}}
1075 // {{{
1076 /**
1077 * Default DDProxy onDrag override
1078 *
1079 * It's called while dragging
1080 * @param {Event} e used to get coordinates
1081 */
1082 , onDrag: function(e) {
1083
1084 // get source (original) and proxy (ghost) elements
1085 var srcEl = Ext.get(this.getEl());
1086 var dragEl = Ext.get(this.getDragEl());
1087
1088 if(!dragEl.isVisible()) {
1089 dragEl.show();
1090 }
1091
1092 var y = e.getPageY();
1093
1094 this.movingUp = this.lastY > y;
1095 this.lastY = y;
1096
1097 } // end of function onDrag
1098 // }}}
1099 // {{{
1100 /**
1101 * Default DDProxy endDrag override
1102 *
1103 * Called when dragging is finished
1104 */
1105 , endDrag: function() {
1106
1107 // get the source (original) and proxy (ghost) elements
1108 var srcEl = Ext.get(this.getEl());
1109 var dragEl = Ext.get(this.getDragEl());
1110
1111 // get box and hide the ghost
1112 var box = dragEl.getBox();
1113
1114 // remove any dragover classes from panel title and dock
1115 this.panel.titleEl.removeClass('x-dock-panel-title-dragover');
1116 this.dock.body.removeClass('x-dock-body-dragover');
1117
1118 var targetPanel = this.dock.items.get(this.currentTarget);
1119
1120 // undock (docked panel dropped out of dock)
1121 if((this.panel.docked && !this.currentTarget) || (targetPanel && !targetPanel.docked)) {
1122 this.dock.undock(this.panel, box);
1123 }
1124
1125 // dock (undocked panel dropped on dock)
1126 else if(this.currentTarget) {
1127 if(targetPanel) {
1128 targetPanel.titleEl.removeClass('x-dock-panel-title-dragover');
1129 }
1130 else {
1131 this.dock.body.removeClass('x-dock-body-dragover');
1132 }
1133 this.dock.dock(this.panel, this.currentTarget);
1134 }
1135
1136 // just free dragging
1137 if(!this.panel.docked) {
1138 this.panel.setBox(box);
1139 srcEl.show();
1140 }
1141
1142 // clear the ghost content, hide id and move it off screen
1143 dragEl.hide();
1144 dragEl.update('');
1145 dragEl.applyStyles({
1146 top:'-9999px'
1147 , left:'-9999px'
1148 , height:'0px'
1149 , width:'0px'
1150 });
1151
1152 // update order of docked panels
1153 this.dock.updateOrder();
1154
1155 // repair the expanded/collapsed states of panels in the dock
1156 if(!this.panel.collapsed && !this.dock.independent && this.panel.docked) {
1157 this.dock.collapseAll(false, this.panel);
1158 this.dock.expanded = this.panel;
1159 }
1160
1161 // let the state manager know the new panel position
1162 this.dock.fireEvent('panelbox', this.panel, {x:box.x, y:box.y, width:box.width, height:box.height});
1163
1164 } // end of function endDrag
1165 // }}}
1166
1167 });
1168 // }}}
1169 // {{{
1170 /**
1171 * Private class for keeping and restoring state of the Accordion
1172 */
1173 Ext.ux.AccordionStateManager = function(dock) {
1174 this.state = {
1175 dock: {}
1176 , panels: {}
1177 };
1178 };
1179
1180 Ext.ux.AccordionStateManager.prototype = {
1181
1182 // {{{
1183 init: function(dock, provider) {
1184 this.provider = provider;
1185 var panel;
1186 var state = provider.get(dock.id + '-dock-state');
1187 if(state) {
1188
1189 // {{{
1190 // handle dock
1191 if(undefined !== state.dock.independent) {
1192 dock.setIndependent(state.dock.independent);
1193 }
1194
1195 if(undefined !== state.dock.undockable) {
1196 dock.setUndockable(state.dock.undockable);
1197 }
1198
1199 if(undefined !== state.dock.useShadow) {
1200 dock.setShadow(state.dock.useShadow);
1201 }
1202
1203 if('object' === typeof state.dock.order && state.dock.order.length) {
1204 dock.setOrder(state.dock.order);
1205 }
1206 // }}}
1207 // {{{
1208 // handle panels
1209 for(var panelId in state.panels) {
1210 panel = dock.items.get(panelId);
1211 if(panel) {
1212 // {{{
1213 // we need collapsed state early
1214 panel.collapsed =
1215 (undefined === typeof state.panels[panelId].collapsed)
1216 ? true
1217 : state.panels[panelId].collapsed
1218 ;
1219 // }}}
1220 // {{{
1221 // handle docked/undocked
1222 if(undefined !== typeof state.panels[panelId].docked) {
1223 if(!state.panels[panelId].docked && 'object' === typeof state.panels[panelId].box) {
1224 dock.undock(panel, state.panels[panelId].box);
1225 }
1226 panel.docked = state.panels[panelId].docked;
1227 }
1228 // }}}
1229 // {{{
1230 // handle pinned state
1231 if(undefined !== typeof state.panels[panelId].pinned) {
1232 panel.pinned = state.panels[panelId].pinned;
1233 }
1234 // }}}
1235 // {{{
1236 // handle collapsed state
1237 if(panel.collapsed) {
1238 panel.collapse();
1239 }
1240 else {
1241 if(!dock.expanded || dock.independent || panel.pinned || !panel.docked) {
1242 panel.body.show();
1243 panel.collapsed = false;
1244 if(!panel.pinned && panel.docked) {
1245 dock.expanded = panel;
1246 }
1247 panel.updateVisuals();
1248 }
1249 }
1250 // }}}
1251
1252 }
1253 }
1254 // }}}
1255
1256 // dock.setSize(dock.body.getWidth(), dock.body.getHeight());
1257 dock.setPanelHeight(dock.expanded);
1258
1259 this.state = state;
1260 }
1261 this.dock = dock;
1262
1263 // install event handlers on dock
1264 dock.on({
1265 panelcollapse: {scope: this, fn: this.onPanelCollapse}
1266 , panelexpand: {scope: this, fn: this.onPanelCollapse}
1267 , panelpinned: {scope: this, fn: this.onPanelPinned}
1268 , independent: {scope: this, fn: this.onIndependent}
1269 , orderchange: {scope: this, fn: this.onOrderChange}
1270 , undockable: {scope: this, fn: this.onUndockable}
1271 , paneldock: {scope: this, fn: this.onPanelUnDock}
1272 , panelundock: {scope: this, fn: this.onPanelUnDock}
1273 , panelbox: {scope: this, fn: this.onPanelUnDock}
1274 , boxchange: {scope: this, fn: this.onPanelUnDock}
1275 , useshadow: {scope: this, fn: this.onUseShadow}
1276 });
1277
1278 }
1279 // }}}
1280 // {{{
1281 , onPanelCollapse: function(panel) {
1282 this.state.panels[panel.id] = this.state.panels[panel.id] || {};
1283 this.state.panels[panel.id].collapsed = panel.collapsed;
1284 this.storeState();
1285 }
1286 // }}}
1287 // {{{
1288 , onPanelPinned: function(panel, pinned) {
1289 this.state.panels[panel.id] = this.state.panels[panel.id] || {};
1290 this.state.panels[panel.id].pinned = pinned;
1291 this.storeState();
1292 }
1293 // }}}
1294 // {{{
1295 , onPanelUnDock: function(panel, box) {
1296 this.state.panels[panel.id] = this.state.panels[panel.id] || {};
1297 this.state.panels[panel.id].docked = panel.docked;
1298 this.state.panels[panel.id].box = box || null;
1299 this.storeState();
1300 }
1301 // }}}
1302 // {{{
1303 , onIndependent: function(dock, independent) {
1304 this.state.dock.independent = independent;
1305 this.storeState();
1306 }
1307 // }}}
1308 // {{{
1309 , onOrderChange: function(dock, order) {
1310 this.state.dock.order = order;
1311 this.storeState();
1312 }
1313 // }}}
1314 // {{{
1315 , onUndockable: function(dock, undockable) {
1316 this.state.dock.undockable = undockable;
1317 this.storeState();
1318 }
1319 // }}}
1320 // {{{
1321 , onUseShadow: function(dock, shadow) {
1322 this.state.dock.useShadow = shadow;
1323 this.storeState();
1324 }
1325 // }}}
1326 // {{{
1327 , storeState: function() {
1328 this.provider.set.defer(700, this, [this.dock.id + '-dock-state', this.state]);
1329 }
1330 // }}}
1331
1332 }; // end of Ext.ux.AccordionStateManager.prototype
1333 // }}}
1334
1335 // end of file