Ext.ux.InfoPanel extension class code:
1 // vim: ts=4:sw=4:nu:fdc=4:nospell
2
3 // Create user extensions namespace (Ext.ux)
4 Ext.namespace('Ext.ux');
5
6 /**
7 * Ext.ux.InfoPanel Extension Class
8 *
9 * @author Ing. Jozef Sakalos
10 * @version $Id: Ext.ux.InfoPanel.js 153 2007-08-24 10:46:19Z jozo $
11 *
12 * @class Ext.ux.InfoPanel
13 * @extends Ext.ContentPanel
14 * @constructor
15 * Creates new Ext.ux.InfoPanel
16 * @param {String/HTMLElement/Element} el The container element for this panel
17 * @param {String/Object} config A string to set only the title or a config object
18 * @param {String} content (optional) Set the HTML content for this panel
19 * @cfg {Boolean} animate set to true to switch animation of expand/collapse on (defaults to undefined)
20 * @cfg {String} bodyClass css class added to the body in addition to the default class(es)
21 * @cfg {String/HTMLElement/Element} bodyEl This element is used as body of panel.
22 * @cfg {String} buttonPosition set this to 'left' to place expand button to the left of titlebar
23 * @cfg {Boolean} collapsed false to start with the expanded body (defaults to true)
24 * @cfg {String} collapsedIcon Path for icon to display in the title when panel is collapsed
25 * @cfg {Boolean} collapseOnUnpin unpinned panel is collapsed when possible (defaults to true)
26 * @cfg {Boolean} collapsible false to disable collapsibility (defaults to true)
27 * @cfg {Boolean} draggable true to allow panel dragging (defaults to undefined)
28 * @cfg {Float} duration Duration of animation in seconds (defaults to 0.35)
29 * @cfg {String} easingCollapse Easing to use for collapse animation (e.g. 'backIn')
30 * @cfg {String} easingExpand Easing to use for expand animation (e.g. 'backOut')
31 * @cfg {String} expandedIcon Path for icon to display in the title when panel is expanded
32 * @cfg {String} icon Path for icon to display in the title
33 * @cfg {Integer} minWidth minimal width in pixels of the resizable panel (defaults to 0)
34 * @cfg {Integer} maxWidth maximal width in pixels of the resizable panel (defaults to 9999)
35 * @cfg {Integer} minHeight minimal height in pixels of the resizable panel (defaults to 50)
36 * @cfg {Integer} maxHeight maximal height in pixels of the resizable panel (defaults to 9999)
37 * @cfg {String} panelClass Set to override the default 'x-dock-panel' class.
38 * @cfg {Boolean} pinned true to start in pinned state (implies collapsed:false) (defaults to false)
39 * @cfg {Boolean} resizable true to allow use resize width of the panel. (defaults to undefined)
40 * Handles are transparent. (defaults to false)
41 * @cfg {String} shadowMode defaults to 'sides'.
42 * @cfg {Boolean} showPin Show the pin button - makes sense only if panel is part of Accordion
43 * @cfg {String} trigger 'title' or 'button'. Click where expands/collapses the panel (defaults to 'title')
44 * @cfg {Boolean} useShadow Use shadows for undocked panels or panels w/o dock. (defaults to undefined = don't use)
45 */
46 Ext.ux.InfoPanel = function(el, config, content) {
47
48 config = config || el;
49 // {{{
50 // basic setup
51 var oldHtml = content || null;
52 if(config && config.content) {
53 oldHtml = oldHtml || config.content;
54 delete(config.content);
55 }
56
57 // save autoScroll to this.bodyScroll
58 if(config && config.autoScroll) {
59 this.bodyScroll = config.autoScroll;
60 delete(config.autoScroll);
61 }
62
63 var url;
64 if(el && el.url) {
65 url = el.url;
66 delete(el.url);
67 }
68 if(config && config.url) {
69 url = config.url;
70 delete(config.url);
71 }
72
73 // call parent constructor
74 Ext.ux.InfoPanel.superclass.constructor.call(this, el, config);
75
76 this.desktop = Ext.get(this.desktop) || Ext.get(document.body);
77
78 // shortcut of DomHelper
79 var dh = Ext.DomHelper, oldTitleEl;
80
81 this.el.clean();
82 this.el.addClass(this.panelClass);
83
84 // handle autoCreate
85 if(this.autoCreate) {
86 oldHtml = this.el.dom.innerHTML;
87 this.el.update('');
88 this.desktop.appendChild(this.el);
89 this.el.removeClass('x-layout-inactive-content');
90 }
91 // handle markup
92 else {
93 this.el.clean();
94 if(this.el.dom.firstChild && !this.bodyEl) {
95 this.title = this.title || this.el.dom.firstChild.innerHTML;
96 if(this.el.dom.firstChild.nextSibling) {
97 this.body = Ext.get(this.el.dom.firstChild.nextSibling);
98 }
99 oldTitleEl = this.el.dom.firstChild;
100 oldTitleEl = oldTitleEl.parentNode.removeChild(oldTitleEl);
101 oldTitleEl = null;
102 }
103 }
104
105 // get body element
106 if(this.bodyEl) {
107 this.body = Ext.get(this.bodyEl);
108 this.el.appendChild(this.body);
109 }
110 // }}}
111 // {{{
112 // create title element
113 var create;
114 if('left' === this.buttonPosition ) {
115 create = {
116 tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
117 {tag:'table', cellspacing:0, children: [
118 {tag:'tr', children: [
119 {tag:'td', children:[
120 {tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
121 ]}
122 , {tag:'td', width:'100%', children: [
123 {tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
124 ]}
125 , {tag:'td', cls:'x-dock-panel-title-icon-ct', children: [
126 {tag:'img', alt:'', cls:'x-dock-panel-title-icon'}
127 ]}
128 ]}
129 ]}
130 ]};
131 }
132 else {
133 create = {
134 tag:'div', unselectable:'on', cls:'x-unselectable x-layout-panel-hd x-dock-panel-title', children: [
135 {tag:'table', cellspacing:0, children: [
136 {tag:'tr', children: [
137 {tag:'td', cls:'x-dock-panel-title-icon-ct', children: [
138 {tag:'img', alt:'', cls:'x-dock-panel-title-icon'}
139 ]}
140 , {tag:'td', width:'100%', children: [
141 {tag:'div', cls:'x-dock-panel x-layout-panel-hd-text x-dock-panel-title-text'}
142 ]}
143 , {tag:'td', children:[
144 {tag:'div', cls:'x-dock-panel x-dock-panel-tools'}
145 ]}
146 ]}
147 ]}
148 ]};
149 }
150 this.titleEl = dh.insertFirst(this.el.dom, create, true);
151 this.iconImg = this.titleEl.select('img.x-dock-panel-title-icon').item(0);
152 this.titleEl.addClassOnOver('x-dock-panel-title-over');
153 this.titleEl.enableDisplayMode();
154 this.titleTextEl = Ext.get(this.titleEl.select('.x-dock-panel-title-text').elements[0]);
155 this.tools = Ext.get(this.titleEl.select('.x-dock-panel-tools').elements[0]);
156 if('right' === this.titleTextAlign) {
157 this.titleTextEl.addClass('x-dock-panel-title-right');
158 }
159
160 this.tm = Ext.util.TextMetrics.createInstance(this.titleTextEl);
161 // }}}
162 // {{{
163 // set title
164 if(this.title) {
165 this.setTitle(this.title);
166 }
167 // }}}
168 // {{{
169 // create pin button
170 if(this.showPin) {
171 this.stickBtn = this.createTool(this.tools.dom, 'x-layout-stick');
172 this.stickBtn.enableDisplayMode();
173 this.stickBtn.on('click', function(e, target) {
174 e.stopEvent();
175 this.pinned = ! this.pinned;
176 this.updateVisuals();
177 this.fireEvent('pinned', this, this.pinned);
178 }, this);
179 this.stickBtn.hide();
180 }
181 // }}}
182 // {{{
183 // create collapse button
184 if(this.collapsible) {
185 this.collapseBtn = this.createTool(this.tools.dom
186 , (this.collapsed ? 'x-layout-collapse-east' : 'x-layout-collapse-south')
187 );
188 this.collapseBtn.enableDisplayMode();
189 if('title' === this.trigger) {
190 this.titleEl.addClass('x-window-header-text');
191 this.titleEl.on({
192 click:{scope: this, fn:this.toggle}
193 , selectstart:{scope: this, fn: function(e) {
194 e.preventDefault();
195 return false;
196 }}
197 }, this);
198 }
199 else {
200 this.collapseBtn.on("click", this.toggle, this);
201 }
202 }
203 // }}}
204 // {{{
205 // create body if it doesn't exist yet
206 if(!this.body) {
207 this.body = dh.append(this.el, {
208 tag: 'div'
209 , cls: this.bodyClass || null
210 , html: oldHtml || ''
211 }, true);
212 }
213 this.body.enableDisplayMode();
214 if(this.collapsed && !this.pinned) {
215 this.body.hide();
216 }
217 else if(this.pinned) {
218 this.body.show();
219 this.collapsed = false;
220 }
221 this.body.addClass(this.bodyClass);
222 this.body.addClass('x-dock-panel-body-undocked');
223
224 // bodyScroll
225
226 this.scrollEl = this.body;
227
228 // autoScroll -> bodyScroll is experimental due to IE bugs
229 this.scrollEl.setStyle('overflow',
230 this.bodyScroll === true && !this.collapsed ? 'auto' : 'hidden');
231 // }}}
232
233 if(this.fixedHeight) {
234 this.setHeight(this.fixedHeight);
235 }
236
237 if(url) {
238 this.setUrl(url, this.params, this.loadOnce);
239 }
240
241 // install hook for title context menu
242 if(this.titleMenu) {
243 this.setTitleMenu(this.titleMenu);
244 }
245
246 // install hook for icon menu
247 if(this.iconMenu) {
248 this.setIconMenu(this.iconMenu);
249 }
250
251 // {{{
252 // add events
253 this.addEvents({
254 /**
255 * @event beforecollapse
256 * Fires before collapse is taking place. Return false to cancel collapse
257 * @param {Ext.ux.InfoPanel} this
258 */
259 beforecollapse: true
260 /**
261 * @event collapse
262 * Fires after collapse
263 * @param {Ext.ux.InfoPanel} this
264 */
265 , collapse: true
266 /**
267 * @event beforecollapse
268 * Fires before expand is taking place. Return false to cancel expand
269 * @param {Ext.ux.InfoPanel} this
270 */
271 , beforeexpand: true
272 /**
273 * @event expand
274 * Fires after expand
275 * @param {Ext.ux.InfoPanel} this
276 */
277 , expand: true
278 /**
279 * @event pinned
280 * Fires when panel is pinned/unpinned
281 * @param {Ext.ux.InfoPanel} this
282 * @param {Boolean} pinned true if the panel is pinned
283 */
284 , pinned: true
285 /**
286 * @event animationcompleted
287 * Fires when animation is completed
288 * @param {Ext.ux.InfoPanel} this
289 */
290 , animationcompleted: true
291 /**
292 * @event boxchange
293 * Fires when the panel is resized
294 * @param {Ext.ux.InfoPanel} this
295 * @param {Object} box
296 */
297 , boxchange: true
298
299 /**
300 * @event redize
301 * Fires when info panel is resized
302 * @param {Ext.ux.InfoPanel} this
303 * @param {Integer} width New width
304 * @param {Integer} height New height
305 */
306 , resize: true
307
308 /**
309 * @event destroy
310 * Fires after the panel is destroyed
311 * @param {Ext.ux.InfoPanel} this
312 */
313 , destroy: true
314
315 });
316 // }}}
317 // {{{
318 // setup dragging, resizing, and shadow
319 this.setDraggable(this.draggable);
320 this.setResizable(!this.collapsed);
321 this.setShadow(this.useShadow);
322
323 // }}}
324
325 this.el.setStyle('z-index', this.zindex);
326 this.updateVisuals();
327
328 this.id = this.id || this.el.id;
329
330 }; // end of constructor
331
332 // extend
333 Ext.extend(Ext.ux.InfoPanel, Ext.ContentPanel, {
334
335 // {{{
336 // defaults
337 adjustments: [0,0]
338 , collapsible: true
339 , collapsed: true
340 , collapseOnUnpin: true
341 , pinned: false
342 , trigger: 'title'
343 , animate: undefined
344 , duration: 0.35
345 , draggable: undefined
346 , resizable: undefined
347 , docked: false
348 , useShadow: undefined
349 , bodyClass: 'x-dock-panel-body'
350 , panelClass: 'x-dock-panel'
351 , shadowMode: 'sides'
352 , dragPadding: {
353 left:8
354 , right:16
355 , top:0
356 , bottom:8
357 }
358 , lastWidth: 0
359 , lastHeight: 0
360 , minWidth: 0
361 , maxWidth: 9999
362 , minHeight: 50
363 , maxHeight: 9999
364 , autoScroll: false
365 , fixedHeight: undefined
366 , zindex: 10000
367 // }}}
368 // {{{
369 /**
370 * Called internally to create collapse button
371 * Calls utility method of Ext.LayoutRegion createTool
372 * @param {Element/HTMLElement/String} parentEl element to create the tool in
373 * @param {String} className class of the tool
374 */
375 , createTool : function(parentEl, className){
376 return Ext.LayoutRegion.prototype.createTool(parentEl, className);
377 }
378 // }}}
379 // {{{
380 /**
381 * Set title of the InfoPanel
382 * @param {String} title Title to set
383 * @return {Ext.ux.InfoPanel} this
384 */
385 , setTitle: function(title) {
386 this.title = title;
387 this.titleTextEl.update(title);
388 this.setIcon();
389 return this;
390 }
391 // }}}
392 // {{{
393 /**
394 * Set the icon to display in title
395 * @param {String} iconPath path to use for src property of icon img
396 */
397 , setIcon: function(iconPath) {
398 iconPath = iconPath || (this.collapsed ? this.collapsedIcon : this.expandedIcon) || this.icon;
399 if(iconPath) {
400 this.iconImg.dom.src = iconPath;
401 }
402 else {
403 this.iconImg.dom.src = Ext.BLANK_IMAGE_URL;
404 }
405 }
406 // }}}
407 // {{{
408 /**
409 * Assigns menu to title icon
410 * @param {Ext.menu.Menu} menu menu to assign
411 */
412 , setIconMenu: function(menu) {
413 if(this.iconMenu) {
414 this.iconImg.removeAllListeners();
415 }
416 menu.panel = this;
417 this.iconImg.on({
418 click: {
419 scope: this
420 , fn: function(e, target) {
421 e.stopEvent();
422 menu.showAt(e.xy);
423 }}
424 });
425 this.iconMenu = menu;
426 }
427 // }}}
428 // {{{
429 /**
430 * private - title menu click handler
431 * @param {Ext.Event} e event
432 * @param {Element} target target
433 */
434 , onTitleMenu: function(e, target) {
435 e.stopEvent();
436 e.preventDefault();
437 this.titleMenu.showAt(e.xy);
438 }
439 // }}}
440 // {{{
441 /**
442 * Assigns context menu (right click) to the title
443 * @param {Ext.menu.Menu} menu menu to assign
444 */
445 , setTitleMenu: function(menu) {
446 if(this.titleMenu) {
447 this.titleEl.un('contextmenu', this.onTitleMenu, this);
448 }
449 menu.panel = this;
450 this.titleEl.on('contextmenu', this.onTitleMenu, this);
451 this.titleMenu = menu;
452 }
453 // }}}
454 // {{{
455 /**
456 * Get current title
457 * @return {String} Current title
458 */
459 , getTitle: function() {
460 return this.title;
461 }
462 // }}}
463 // {{{
464 /**
465 * Returns body element
466 * This overrides the ContentPanel getEl for convenient access to the body element
467 * @return {Element} this.body
468 */
469 , getEl: function() {
470 return this.body;
471 }
472 // }}}
473 // {{{
474 /**
475 * Returns title height
476 * @return {Integer} title height
477 */
478 , getTitleHeight: function() {
479 return this.titleEl.getComputedHeight();
480 }
481 // }}}
482 // {{{
483 /**
484 * Returns body height
485 * @return {Integer} body height
486 */
487 , getBodyHeight: function() {
488 return this.body.getComputedHeight();
489 }
490 // }}}
491 // {{{
492 /**
493 * Returns panel height
494 * @return {Integer} panel height
495 */
496 , getHeight: function() {
497 return this.getBodyHeight() + this.getTitleHeight();
498 }
499 // }}}
500 // {{{
501 /**
502 * Returns body client height
503 * @return {Integer} body client height
504 */
505 , getBodyClientHeight: function() {
506 return this.body.getHeight(true);
507 }
508 // }}}
509 // {{{
510 /**
511 * Update the innerHTML of this element, optionally searching for and processing scripts
512 * @param {String} html The new HTML
513 * @param {Boolean} loadScripts (optional) true to look for and process scripts
514 * @param {Function} callback For async script loading you can be noticed when the update completes
515 * @return {Ext.Element} this
516 */
517 , update: function(html, loadScripts, callback) {
518 this.body.update(html, loadScripts, callback);
519 return this;
520 }
521 // }}}
522 // {{{
523 /**
524 * Updates this panel's element
525 * @param {String} content The new content
526 * @param {Boolean} loadScripts (optional) true to look for and process scripts
527 */
528 , setContent: function(content, loadScripts) {
529 this.body.update(content, loadScripts);
530 }
531 // }}}
532 // {{{
533 /**
534 * Get the {@link Ext.UpdateManager} for this panel. Enables you to perform Ajax updates.
535 * @return {Ext.UpdateManager} The UpdateManager
536 */
537 , getUpdateManager: function() {
538 return this.body.getUpdateManager();
539 }
540 // }}}
541 // {{{
542 /**
543 * The only required property is url. The optional properties nocache, text and scripts
544 * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their associated property on this panel UpdateManager instance.
545 * @param {String/Object} params (optional) The parameters to pass as either a url encoded string "param1=1&param2=2" or an object {param1: 1, param2: 2}
546 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess, oResponse)
547 * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used url. If true, it will not store the url.
548 * @return {Ext.ContentPanel} this
549 */
550 , load: function() {
551 var um = this.getUpdateManager();
552 um.update.apply(um, arguments);
553 return this;
554 }
555 // }}}
556 // {{{
557 /**
558 * Set a URL to be used to load the content for this panel. When this panel is activated, the content will be loaded from that URL.
559 * @param {String/Function} url The url to load the content from or a function to call to get the url
560 * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Ext.UpdateManager#update} for more details. (Defaults to null)
561 * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
562 * @return {Ext.UpdateManager} The UpdateManager
563 */
564 , setUrl: function(url, params, loadOnce) {
565 if(this.refreshDelegate){
566 this.removeListener("expand", this.refreshDelegate);
567 }
568 this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
569 this.on("expand", this.refreshDelegate);
570 this.on({
571 beforeexpand: {
572 scope: this
573 , single: this.loadOnce ? true : false
574 , fn: function() {
575 this.body.update('');
576 }}
577 });
578 return this.getUpdateManager();
579 }
580 // }}}
581 // {{{
582 , _handleRefresh: function(url, params, loadOnce) {
583 var updater;
584 if(!loadOnce || !this.loaded){
585 updater = this.getUpdateManager();
586 updater.on({
587 update: {
588 scope: this
589 , single: true
590 , fn: function() {
591 if(true === this.useShadow && this.shadow) {
592 this.shadow.show(this.el);
593 }
594 }}
595 });
596 updater.update(url, params, this._setLoaded.createDelegate(this));
597 }
598 }
599 // }}}
600 // {{{
601 , _setLoaded: function() {
602 this.loaded = true;
603 }
604 // }}}
605 // {{{
606 /**
607 * Force a content refresh from the URL specified in the setUrl() method.
608 * Will fail silently if the setUrl method has not been called.
609 * This does not activate the panel, just updates its content.
610 */
611 , refresh: function() {
612 if(this.refreshDelegate){
613 this.loaded = false;
614 this.refreshDelegate();
615 }
616 }
617 // }}}
618 // {{{
619 /**
620 * Expands the panel
621 * @param {Boolean} skipAnimation Set to true to skip animation
622 * @return {Ext.ux.InfoPanel} this
623 */
624 , expand: function(skipAnimation) {
625
626 // do nothing if already expanded
627 if(!this.collapsed) {
628 return this;
629 }
630
631 // fire beforeexpand event
632 if(false === this.fireEvent('beforeexpand', this)) {
633 return this;
634 }
635
636 if(Ext.isGecko) {
637 this.autoScrolls = this.body.select('{overflow=auto}');
638 this.autoScrolls.setStyle('overflow', 'hidden');
639 }
640
641 // reset collapsed flag
642 this.collapsed = false;
643
644 this.autoSize();
645
646 // hide shadow
647 if(!this.docked) {
648 this.setShadow(false);
649 }
650
651 // enable resizing
652 if(this.resizer && !this.docked) {
653 this.setResizable(true);
654 }
655
656 if(Ext.isIE) {
657 this.body.setWidth(this.el.getWidth());
658 }
659
660 // animate expand
661 if(true === this.animate && true !== skipAnimation) {
662 this.body.slideIn('t', {
663 easing: this.easingExpand || null
664 , scope: this
665 , duration: this.duration
666 , callback: this.updateVisuals
667 });
668 }
669
670 // don't animate, just show
671 else {
672 this.body.show();
673 this.updateVisuals();
674 this.fireEvent('animationcompleted', this);
675 }
676
677 // fire expand event
678 this.fireEvent('expand', this);
679
680 return this;
681
682 }
683 // }}}
684 // {{{
685 /**
686 * Toggles the expanded/collapsed states
687 * @param {Boolean} skipAnimation Set to true to skip animation
688 * @return {Ext.ux.InfoPanel} this
689 */
690 , toggle: function(skipAnimation) {
691 if(this.collapsed) {
692 this.expand(skipAnimation);
693 }
694 else {
695 this.collapse(skipAnimation);
696 }
697 return this;
698 }
699 // }}}
700 // {{{
701 /**
702 * Collapses the panel
703 * @param {Boolean} skipAnimation Set to true to skip animation
704 * @return {Ext.ux.InfoPanel} this
705 */
706 , collapse: function(skipAnimation) {
707
708 // do nothing if already collapsed or pinned
709 if(this.collapsed || this.pinned) {
710 return this;
711 }
712
713 // fire beforecollapse event
714 if(false === this.fireEvent('beforecollapse', this)) {
715 return this;
716 }
717
718 if(Ext.isGecko) {
719 this.autoScrolls = this.body.select('{overflow=auto}');
720 this.autoScrolls.setStyle('overflow', 'hidden');
721 }
722
723 if(this.bodyScroll /*&& !Ext.isIE*/) {
724 this.scrollEl.setStyle('overflow','hidden');
725 }
726
727 // set collapsed flag
728 this.collapsed = true;
729
730 // hide shadow
731 this.setShadow(false);
732
733 // disable resizing of collapsed panel
734 if(this.resizer) {
735 this.setResizable(false);
736 }
737
738 // animate collapse
739 if(true === this.animate && true !== skipAnimation) {
740 this.body.slideOut('t', {
741 easing: this.easingCollapse || null
742 , scope: this
743 , duration: this.duration
744 , callback: this.updateVisuals
745 });
746 }
747
748 // don't animate, just hide
749 else {
750 this.body.hide();
751 this.updateVisuals();
752 this.fireEvent('animationcompleted', this);
753 }
754
755 // fire collapse event
756 this.fireEvent('collapse', this);
757
758 return this;
759
760 }
761 // }}}
762 // {{{
763 /**
764 * Called internally to update class of the collapse button
765 * as part of expand and collapse methods
766 *
767 * @return {Ext.ux.InfoPanel} this
768 */
769 , updateVisuals: function() {
770
771 // handle collapsed state
772 if(this.collapsed) {
773 if(this.showPin) {
774 if(this.collapseBtn) {
775 this.collapseBtn.show();
776 }
777 if(this.stickBtn) {
778 this.stickBtn.hide();
779 }
780 }
781 if(this.collapseBtn) {
782 Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
783 'x-layout-collapse-south', 'x-layout-collapse-east'
784 );
785 }
786 this.body.replaceClass('x-dock-panel-body-expanded', 'x-dock-panel-body-collapsed');
787 this.titleEl.replaceClass('x-dock-panel-title-expanded', 'x-dock-panel-title-collapsed');
788 }
789
790 // handle expanded state
791 else {
792 if(this.showPin) {
793 if(this.pinned) {
794 if(this.stickBtn) {
795 Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stick', 'x-layout-stuck');
796 }
797 this.titleEl.addClass('x-dock-panel-title-pinned');
798 }
799 else {
800 if(this.stickBtn) {
801 Ext.fly(this.stickBtn.dom.firstChild).replaceClass('x-layout-stuck', 'x-layout-stick');
802 }
803 this.titleEl.removeClass('x-dock-panel-title-pinned');
804 }
805 if(this.collapseBtn) {
806 this.collapseBtn.hide();
807 }
808 if(this.stickBtn) {
809 this.stickBtn.show();
810 }
811 }
812 else {
813 if(this.collapseBtn) {
814 Ext.fly(this.collapseBtn.dom.firstChild).replaceClass(
815 'x-layout-collapse-east', 'x-layout-collapse-south'
816 );
817 }
818 }
819 this.body.replaceClass('x-dock-panel-body-collapsed', 'x-dock-panel-body-expanded');
820 this.titleEl.replaceClass('x-dock-panel-title-collapsed', 'x-dock-panel-title-expanded');
821 }
822
823 // show shadow if necessary
824 if(!this.docked) {
825 this.setShadow(true);
826 }
827
828 if(this.autoScrolls) {
829 this.autoScrolls.setStyle('overflow', 'auto');
830 }
831
832 this.setIcon();
833
834 if(this.bodyScroll && !this.docked && !this.collapsed /*&& !Ext.isIE*/) {
835 this.scrollEl.setStyle('overflow', 'auto');
836 }
837
838 this.constrainToDesktop();
839
840 // fire animationcompleted event
841 this.fireEvent('animationcompleted', this);
842
843 // clear visibility style of body's children
844 var kids = this.body.select('div[className!=x-grid-viewport],input{visibility}');
845 kids.setStyle.defer(1, kids, ['visibility','']);
846
847 // restore body overflow
848 if(this.bodyScroll && !this.collapsed /*&& !Ext.isIE*/) {
849 this.setHeight(this.getHeight());
850 this.scrollEl.setStyle('overflow','auto');
851 }
852
853 return this;
854 }
855 // }}}
856 // {{{
857 /**
858 * Creates toolbar
859 * @param {Array} config Configuration for Ext.Toolbar
860 * @param {Boolean} bottom true to create bottom toolbar. (defaults to false = top toolbar)
861 * @return {Ext.Toolbar} Ext.Toolbar object
862 */
863 , createToolbar: function(config, bottom) {
864
865 // we need clean body
866 this.body.clean();
867
868 // copy body to new container
869 this.scrollEl = Ext.DomHelper.append(document.body, {tag:'div'}, true);
870 var el;
871 while(el = this.body.down('*')) {
872 this.scrollEl.appendChild(el);
873 }
874
875 if(this.bodyScroll) {
876 this.body.setStyle('overflow', '');
877 if(!this.collapsed) {
878 this.scrollEl.setStyle('overflow', 'auto');
879 }
880 }
881
882 var create = {tag:'div'}, tbEl;
883 config = config || null;
884 if(bottom) {
885 this.body.appendChild(this.scrollEl);
886 tbEl = Ext.DomHelper.append(this.body, create, true);
887 tbEl.addClass('x-dock-panel-toolbar-bottom');
888 }
889 else {
890 tbEl = Ext.DomHelper.insertFirst(this.body, create, true);
891 tbEl.addClass('x-dock-panel-toolbar');
892 this.body.appendChild(this.scrollEl);
893 }
894 this.toolbar = new Ext.Toolbar(tbEl, config);
895 this.setHeight(this.getHeight());
896 return this.toolbar;
897 }
898 // }}}
899 // {{{
900 /**
901 * Set the panel draggable
902 * Uses lazy creation of dd object
903 * @param {Boolean} enable pass false to disable dragging
904 * @return {Ext.ux.InfoPanel} this
905 */
906 , setDraggable: function(enable) {
907
908 if(true !== this.draggable) {
909 return this;
910 }
911
912 // lazy create proxy
913 var dragTitleEl;
914 if(!this.proxy) {
915 this.proxy = this.el.createProxy('x-dlg-proxy');
916
917 // setup title
918 dragTitleEl = Ext.DomHelper.append(this.proxy, {tag:'div'}, true);
919 dragTitleEl.update(this.el.dom.firstChild.innerHTML);
920 dragTitleEl.dom.className = this.el.dom.firstChild.className;
921 if(this.collapsed && Ext.isIE) {
922 dragTitleEl.dom.style.borderBottom = "0";
923 }
924
925 this.proxy.hide();
926 this.proxy.setOpacity(0.5);
927 this.dd = new Ext.dd.DDProxy(this.el.dom, 'PanelDrag', {
928 dragElId: this.proxy.id
929 , scroll: false
930 });
931 this.dd.scroll = false;
932 this.dd.afterDrag = function() {
933 this.panel.moveToViewport();
934 if(this.panel && this.panel.shadow && !this.panel.docked) {
935 this.panel.shadow.show(this.panel.el);
936 }
937 };
938
939 this.constrainToDesktop();
940 Ext.EventManager.onWindowResize(this.moveToViewport, this);
941 }
942
943 this.dd.panel = this;
944 this.dd.setHandleElId(this.titleEl.id);
945 if(false === enable) {
946 this.dd.lock();
947 }
948 else {
949 this.dd.unlock();
950 }
951
952 return this;
953 }
954 // }}}
955 // {{{
956 /**
957 * Set the panel resizable
958 * Uses lazy creation of the resizer object
959 * @param {Boolean} pass false to disable resizing
960 * @return {Ext.ux.InfoPanel} this
961 */
962 , setResizable: function(enable) {
963
964 if(true !== this.resizable) {
965 return this;
966 }
967
968 // {{{
969 // lazy create resizer
970 if(!this.resizer) {
971
972 // {{{
973 // create resizer
974 this.resizer = new Ext.Resizable(this.el, {
975 handles: 's w e sw se'
976 , minWidth: this.minWidth || this.tm.getWidth(this.getTitle()) + 56 || 48
977 , maxWidth: this.maxWidth
978 , minHeight: this.minHeight
979 , maxHeight: this.maxHeight
980 , transparent: true
981 , draggable: false
982 });
983 // }}}
984 // {{{
985 // install event handlers
986 this.resizer.on({
987 beforeresize: {
988 scope:this
989 , fn: function(resizer, e) {
990 var viewport = this.getViewport();
991 var box = this.getBox();
992
993 var pos = resizer.activeHandle.position;
994
995 // left constraint
996 if(pos.match(/west/)) {
997 resizer.minX = viewport.x + (this.dragPadding.left || 8);
998 }
999
1000 // down constraint
1001 var maxH;
1002 if(pos.match(/south/)) {
1003 resizer.oldMaxHeight = resizer.maxHeight;
1004 maxH = viewport.y + viewport.height - box.y - (this.dragPadding.bottom || 8);
1005 resizer.maxHeight = maxH < resizer.maxHeight ? maxH : resizer.maxHeight;
1006 }
1007
1008 // right constraint
1009 var maxW;
1010 if(pos.match(/east/)) {
1011 resizer.oldMaxWidth = resizer.maxWidth;
1012 maxW = viewport.x + viewport.width - box.x - (this.dragPadding.right || 10);
1013 resizer.maxWidth = maxW < resizer.maxWidth ? maxW : resizer.maxWidth;
1014 }
1015 }}
1016 , resize: {
1017 scope: this
1018 , fn: function(resizer, width, height, e) {
1019 resizer.maxHeight = resizer.oldMaxHeight || resizer.maxHeight;
1020 resizer.maxWidth = resizer.oldMaxWidth || resizer.maxWidth;
1021 this.setSize(width, height);
1022 this.constrainToDesktop();
1023 this.fireEvent('boxchange', this, this.el.getBox());
1024 this.fireEvent('resize', this, width, height);
1025 this.lastHeight = height;
1026 this.lastWidth = width;
1027 }}
1028 });
1029 // }}}
1030
1031 }
1032 // }}}
1033
1034 this.resizer.enabled = enable;
1035
1036 // this is custom override of Ext.Resizer
1037 this.resizer.showHandles(enable);
1038
1039 return this;
1040 }
1041 // }}}
1042 // {{{
1043 /**
1044 * Called internally to clip passed width and height to viewport
1045 * @param {Integer} w width
1046 * @param {Integer} h height
1047 * @return {Object} {width:safeWidth, height:safeHeight}
1048 */
1049 , safeSize: function(w, h) {
1050 var viewport = this.getViewport();
1051 var box = this.getBox();
1052 var gap = 0;
1053 var safeSize = {width:w, height:h};
1054
1055 safeSize.height =
1056 box.y + h + this.dragPadding.bottom + gap > viewport.height + viewport.y
1057 ? viewport.height - box.y + viewport.y - this.dragPadding.bottom - gap
1058 : safeSize.height
1059 ;
1060
1061 safeSize.width =
1062 box.x + w + this.dragPadding.right + gap > viewport.width + viewport.x
1063 ? viewport.width - box.x + viewport.x - this.dragPadding.right - gap
1064 : safeSize.width
1065 ;
1066
1067 return safeSize;
1068 }
1069 // }}}
1070 // {{{
1071 /**
1072 * Called internally to get current viewport
1073 * @param {Element/HTMLElement/String} desktop Element to get size and position of
1074 * @return {Object} viewport {x:x, y:y, width:width, height:height} x and y are page coords
1075 */
1076 , getViewport: function(desktop) {
1077
1078 desktop = desktop || this.desktop || document.body;
1079 var viewport = Ext.get(desktop).getViewSize();
1080 var xy;
1081 if(document.body === desktop.dom) {
1082 viewport.x = 0;
1083 viewport.y = 0;
1084 }
1085 else {
1086 xy = desktop.getXY();
1087 viewport.x = isNaN(xy[0]) ? 0 : xy[0];
1088 viewport.y = isNaN(xy[1]) ? 0 : xy[1];
1089 }
1090
1091 return viewport;
1092 }
1093 // }}}
1094 // {{{
1095 /**
1096 * Sets the size of the panel. Demanded size is clipped to the viewport
1097 *
1098 * @param {Integer} w width to set
1099 * @param {Integer} h height to set
1100 * @return {Ext.ux.InfoPanel} this
1101 */
1102 , setSize: function(w, h) {
1103 var safeSize = this.safeSize(w, h);
1104 this.setWidth(safeSize.width);
1105 this.setHeight(safeSize.height);
1106 if(Ext.isIE) {
1107 this.body.setWidth(safeSize.width);
1108 }
1109
1110 if(!this.docked) {
1111 this.setShadow(true);
1112 }
1113 }
1114 // }}}
1115 // {{{
1116 /**
1117 * Sets the width of the panel. Demanded width is clipped to the viewport
1118 *
1119 * @param {Integer} w width to set
1120 * @return {Ext.ux.InfoPanel} this
1121 */
1122 , setWidth: function(w) {
1123 this.el.setWidth(w);
1124 this.body.setStyle('width','');
1125 if(!this.docked) {
1126 this.setShadow(true);
1127 }
1128 this.lastWidth = w;
1129
1130 return this;
1131 }
1132 // }}}
1133 // {{{
1134 /**
1135 * Sets the height of the panel. Demanded height is clipped to the viewport
1136 *
1137 * @param {Integer} h height to set
1138 * @return {Ext.ux.InfoPanel} this
1139 */
1140 , setHeight: function(h) {
1141 var newH = h - this.getTitleHeight();
1142 var scrollH = newH;
1143 if(1 < newH) {
1144 if(this.scrollEl !== this.body) {
1145 scrollH -= this.toolbar ? this.toolbar.getEl().getHeight() : 0;
1146 // scrollH -= 27;
1147 scrollH -= this.adjustments[1] || 0;
1148 this.scrollEl.setHeight(scrollH);
1149 }
1150 this.body.setHeight(newH);
1151 }
1152 else {
1153 this.body.setStyle('height','');
1154 }
1155
1156 if(!this.docked) {
1157 this.setShadow(true);
1158 }
1159 // this.lastHeight = h;
1160 this.el.setStyle('height','');
1161
1162 return this;
1163 }
1164 // }}}
1165 // {{{
1166 /**
1167 * Called internally to set x, y, width and height of the panel
1168 *
1169 * @param {Object} box
1170 * @return {Ext.ux.InfoPanel} this
1171 */
1172 , setBox: function(box) {
1173 this.el.setBox(box);
1174 this.moveToViewport();
1175 this.setSize(box.width, box.height);
1176
1177 return this;
1178 }
1179 // }}}
1180 // {{{
1181 /**
1182 * Called internally to get the box of the panel
1183 *
1184 * @return {Object} box
1185 */
1186 , getBox: function() {
1187 return this.el.getBox();
1188 }
1189 // }}}
1190 // {{{
1191 , autoSize: function() {
1192
1193 var width = 0;
1194 var height = this.fixedHeight || 0;
1195 var dock = this.dock;
1196
1197 // docked
1198 if(this.docked && this.dock) {
1199 if(dock.fitHeight) {
1200 height = dock.getPanelBodyHeight() + this.getTitleHeight();
1201 }
1202 }
1203
1204 // undocked
1205 else {
1206 // height logic
1207 height = this.lastHeight || this.fixedHeight || 0;
1208 height = height < this.maxHeight ? height : (this.maxHeight < 9999 ? this.maxHeight : 0);
1209 height = (height && height < this.minHeight ) ? this.minHeight : height;
1210 this.lastHeight = height ? height : this.lastHeight;
1211 }
1212
1213 this.setHeight(height);
1214
1215 }
1216 // }}}
1217 // {{{
1218 /**
1219 * Turns shadow on/off
1220 * Uses lazy creation of the shadow object
1221 * @param {Boolean} shadow pass false to hide, true to show the shadow
1222 * @return {Ext.ux.InfoPanel} this
1223 */
1224 , setShadow: function(shadow) {
1225
1226 // if I have shadow but shouldn't use it
1227 if(this.shadow && true !== this.useShadow) {
1228 this.shadow.hide();
1229 return this;
1230 }
1231
1232 // if I shouldn't use shadow
1233 if(true !== this.useShadow) {
1234 return this;
1235 }
1236
1237 // if I don't have shadow
1238 if(!this.shadow) {
1239 this.shadow = new Ext.Shadow({mode:this.shadowMode});
1240 }
1241
1242 // show or hide
1243 var zindex;
1244 if(shadow) {
1245 this.shadow.show(this.el);
1246
1247 // fix the Ext shadow z-index bug
1248 zindex = parseInt(this.el.getStyle('z-index'), 10);
1249 zindex = isNaN(zindex) ? '' : zindex - 1;
1250 this.shadow.el.setStyle('z-index', zindex);
1251 }
1252 else {
1253 this.shadow.hide();
1254 }
1255
1256 return this;
1257
1258 }
1259 // }}}
1260 // {{{
1261 /**
1262 * Show the panel
1263 * @param {Boolean} show (optional) if false hides the panel instead of show
1264 * @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
1265 * @return {Ext.ux.InfoPanel} this
1266 */
1267 , show: function(show, alsoUndocked) {
1268
1269 // ignore undocked panels if not forced to
1270 if(!this.docked && true !== alsoUndocked) {
1271 return this;
1272 }
1273
1274 show = (false === show ? false : true);
1275 if(!this.docked) {
1276 this.setShadow(show);
1277 }
1278
1279 this.el.setStyle('display', show ? '' : 'none');
1280 return this;
1281 }
1282 // }}}
1283 // {{{
1284 /**
1285 * Hide the panel
1286 * @param {Boolean} alsoUndocked show/hide also undocked panel (defaults to false)
1287 * @return {Ext.ux.InfoPanel} this
1288 */
1289 , hide: function(alsoUndocked) {
1290 this.show(false, alsoUndocked);
1291 }
1292 // }}}
1293 // {{{
1294 /**
1295 * Constrains dragging of this panel to desktop boundaries
1296 * @param {Element} desktop the panel is to be constrained to
1297 * @return {Ext.ux.InfoPanel} this
1298 */
1299 , constrainToDesktop: function(desktop) {
1300 desktop = desktop || this.desktop;
1301 if(desktop && this.dd) {
1302 this.dd.constrainTo(desktop, this.dragPadding, false);
1303 }
1304 return this;
1305 }
1306 // }}}
1307 // {{{
1308 /**
1309 * Called internally to move the panel to the viewport.
1310 * Also constrains the dragging to the desktop
1311 *
1312 * @param {Object} viewport (optional) object {x:x, y:y, width:width, height:height}
1313 * @return {Ext.ux.InfoPanel} this
1314 */
1315 , moveToViewport: function(viewport) {
1316 viewport = viewport && !isNaN(viewport.x) ? viewport : this.getViewport();
1317 var box = this.getBox();
1318 var moved = false;
1319 var gap = 10;
1320
1321 // horizontal
1322 if(box.x + box.width + this.dragPadding.right > viewport.x + viewport.width) {
1323 moved = true;
1324 box.x = viewport.width + viewport.x - box.width - this.dragPadding.right - gap;
1325 }
1326 if(box.x - this.dragPadding.left < viewport.x) {
1327 moved = true;
1328 box.x = viewport.x + this.dragPadding.left + gap;
1329 }
1330
1331 // vertical
1332 if(box.y + box.height + this.dragPadding.bottom > viewport.y + viewport.height) {
1333 moved = true;
1334 box.y = viewport.height + viewport.y - box.height - this.dragPadding.bottom - gap;
1335 }
1336 if(box.y - this.dragPadding.top < viewport.y) {
1337 moved = true;
1338 box.y = viewport.y + this.dragPadding.top + gap;
1339 }
1340
1341 var oldOverflow;
1342 if(moved) {
1343 // sanity clip
1344 box.x = box.x < viewport.x ? viewport.x : box.x;
1345 box.y = box.y < viewport.y ? viewport.y : box.y;
1346
1347 // prevent scrollbars from appearing
1348 this.desktop.oldOverflow = this.desktop.oldOverflow || this.desktop.getStyle('overflow');
1349 this.desktop.setStyle('overflow', 'hidden');
1350
1351 // set position
1352 this.el.setXY([box.x, box.y]);
1353
1354 // restore overflow
1355 this.desktop.setStyle.defer(100, this.desktop, ['overflow', this.desktop.oldOverflow]);
1356
1357 if(!this.docked) {
1358 this.setShadow(true);
1359 }
1360 }
1361
1362 this.constrainToDesktop();
1363
1364 return this;
1365 }
1366 // }}}
1367 // {{{
1368 /**
1369 * destroys the panel
1370 */
1371 , destroy: function() {
1372 if(this.shadow) {
1373 this.shadow.hide();
1374 }
1375 if(this.collapsible) {
1376 this.collapseBtn.removeAllListeners();
1377 this.titleEl.removeAllListeners();
1378 }
1379
1380 if(this.resizer) {
1381 this.resizer.destroy();
1382 }
1383 if(this.dd) {
1384 if(this.proxy) {
1385 this.proxy.removeAllListeners();
1386 this.proxy.remove();
1387 }
1388 this.dd.unreg();
1389 this.dd = null;
1390 }
1391 if(this.dock) {
1392 this.dock.detach(this);
1393 }
1394
1395 this.body.removeAllListeners();
1396
1397 // call parent destroy
1398 Ext.ux.InfoPanel.superclass.destroy.call(this);
1399
1400 this.fireEvent('destroy', this);
1401
1402 }
1403 // }}}
1404
1405 }); // end of extend
1406
1407 // {{{
1408 // show/hide resizer handles override
1409 Ext.override(Ext.Resizable, {
1410
1411 /**
1412 * Hide resizer handles
1413 */
1414 hideHandles: function() {
1415 this.showHandles(false);
1416 } // end of function hideHandles
1417
1418 /**
1419 * Show resizer handles
1420 *
1421 * @param {Boolean} show (true = show, false = hide)
1422 */
1423 , showHandles: function(show) {
1424 show = (false === show ? false : true);
1425 var pos;
1426 for(var p in Ext.Resizable.positions) {
1427 pos = Ext.Resizable.positions[p];
1428 if(this[pos]) {
1429 this[pos].el.setStyle('display', show ? '' : 'none');
1430 }
1431 }
1432 } // end of function showHandles
1433 // }}}
1434
1435 });
1436
1437 // end of file