0d5b6cebf0f1b202607787244394f4d7daf8c4bd
[roojs1] / Roo / bootstrap / Popover.js
1 /*
2  * - LGPL
3  *
4  * element
5  * 
6  */
7
8 /**
9  * @class Roo.bootstrap.Popover
10  * @extends Roo.bootstrap.Component
11  * @builder-top
12  * @parent none
13  * @children Roo.bootstrap.Component
14  * Bootstrap Popover class
15  * @cfg {String} html contents of the popover   (or false to use children..)
16  * @cfg {String} title of popover (or false to hide)
17  * @cfg {String|function} (right|top|bottom|left|auto) placement how it is placed
18  * @cfg {String} trigger click || hover (or false to trigger manually)
19  * @cfg {Boolean} modal - popovers that are modal will mask the screen, and must be closed with another event.
20  * @cfg {String|Boolean|Roo.Element} add click hander to trigger show over what element
21  *      - if false and it has a 'parent' then it will be automatically added to that element
22  *      - if string - Roo.get  will be called 
23  * @cfg {Number} delay - delay before showing
24  
25  * @constructor
26  * Create a new Popover
27  * @param {Object} config The config object
28  */
29
30 Roo.bootstrap.Popover = function(config){
31     Roo.bootstrap.Popover.superclass.constructor.call(this, config);
32     
33     this.addEvents({
34         // raw events
35          /**
36          * @event show
37          * After the popover show
38          * 
39          * @param {Roo.bootstrap.Popover} this
40          */
41         "show" : true,
42         /**
43          * @event hide
44          * After the popover hide
45          * 
46          * @param {Roo.bootstrap.Popover} this
47          */
48         "hide" : true
49     });
50 };
51
52 Roo.extend(Roo.bootstrap.Popover, Roo.bootstrap.Component,  {
53     
54     title: false,
55     html: false,
56     
57     placement : 'right',
58     trigger : 'hover', // hover
59     modal : false,
60     delay : 0,
61     
62     over: false,
63     
64     can_build_overlaid : false,
65     
66     maskEl : false, // the mask element
67     headerEl : false,
68     contentEl : false,
69     alignEl : false, // when show is called with an element - this get's stored.
70     
71     getChildContainer : function()
72     {
73         return this.contentEl;
74         
75     },
76     getPopoverHeader : function()
77     {
78         this.title = true; // flag not to hide it..
79         this.headerEl.addClass('p-0');
80         return this.headerEl
81     },
82     
83     
84     getAutoCreate : function(){
85          
86         var cfg = {
87            cls : 'popover roo-dynamic shadow roo-popover' + (this.modal ? '-modal' : ''),
88            style: 'display:block',
89            cn : [
90                 {
91                     cls : 'arrow'
92                 },
93                 {
94                     cls : 'popover-inner ',
95                     cn : [
96                         {
97                             tag: 'h3',
98                             cls: 'popover-title popover-header',
99                             html : this.title === false ? '' : this.title
100                         },
101                         {
102                             cls : 'popover-content popover-body '  + (this.cls || ''),
103                             html : this.html || ''
104                         }
105                     ]
106                     
107                 }
108            ]
109         };
110         
111         return cfg;
112     },
113     /**
114      * @param {string} the title
115      */
116     setTitle: function(str)
117     {
118         this.title = str;
119         if (this.el) {
120             this.headerEl.dom.innerHTML = str;
121         }
122         
123     },
124     /**
125      * @param {string} the body content
126      */
127     setContent: function(str)
128     {
129         this.html = str;
130         if (this.contentEl) {
131             this.contentEl.dom.innerHTML = str;
132         }
133         
134     },
135     // as it get's added to the bottom of the page.
136     onRender : function(ct, position)
137     {
138         Roo.bootstrap.Component.superclass.onRender.call(this, ct, position);
139         
140         
141         
142         if(!this.el){
143             var cfg = Roo.apply({},  this.getAutoCreate());
144             cfg.id = Roo.id();
145             
146             if (this.cls) {
147                 cfg.cls += ' ' + this.cls;
148             }
149             if (this.style) {
150                 cfg.style = this.style;
151             }
152             //Roo.log("adding to ");
153             this.el = Roo.get(document.body).createChild(cfg, position);
154 //            Roo.log(this.el);
155         }
156         
157         this.contentEl = this.el.select('.popover-content',true).first();
158         this.headerEl =  this.el.select('.popover-title',true).first();
159         
160         var nitems = [];
161         if(typeof(this.items) != 'undefined'){
162             var items = this.items;
163             delete this.items;
164
165             for(var i =0;i < items.length;i++) {
166                 nitems.push(this.addxtype(Roo.apply({}, items[i])));
167             }
168         }
169
170         this.items = nitems;
171         
172         this.maskEl = Roo.DomHelper.append(document.body, {tag: "div", cls:"x-dlg-mask"}, true);
173         Roo.EventManager.onWindowResize(this.resizeMask, this, true);
174         
175         
176         
177         this.initEvents();
178     },
179     
180     resizeMask : function()
181     {
182         this.maskEl.setSize(
183             Roo.lib.Dom.getViewWidth(true),
184             Roo.lib.Dom.getViewHeight(true)
185         );
186     },
187     
188     initEvents : function()
189     {
190         
191         if (!this.modal) { 
192             Roo.bootstrap.Popover.register(this);
193         }
194          
195         this.arrowEl = this.el.select('.arrow',true).first();
196         this.headerEl.setVisibilityMode(Roo.Element.DISPLAY); // probably not needed as it's default in BS4
197         this.el.enableDisplayMode('block');
198         this.el.hide();
199  
200         
201         if (this.over === false && !this.parent()) {
202             return; 
203         }
204         if (this.triggers === false) {
205             return;
206         }
207          
208         // support parent
209         var on_el = (this.over == 'parent' || this.over === false) ? this.parent().el : Roo.get(this.over);
210         var triggers = this.trigger ? this.trigger.split(' ') : [];
211         Roo.each(triggers, function(trigger) {
212         
213             if (trigger == 'click') {
214                 on_el.on('click', this.toggle, this);
215             } else if (trigger != 'manual') {
216                 var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin';
217                 var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout';
218       
219                 on_el.on(eventIn  ,this.enter, this);
220                 on_el.on(eventOut, this.leave, this);
221             }
222         }, this);
223     },
224     
225     
226     // private
227     timeout : null,
228     hoverState : null,
229     
230     toggle : function () {
231         this.hoverState == 'in' ? this.leave() : this.enter();
232     },
233     
234     enter : function () {
235         
236         clearTimeout(this.timeout);
237     
238         this.hoverState = 'in';
239     
240         if (!this.delay || !this.delay.show) {
241             this.show();
242             return;
243         }
244         var _t = this;
245         this.timeout = setTimeout(function () {
246             if (_t.hoverState == 'in') {
247                 _t.show();
248             }
249         }, this.delay.show)
250     },
251     
252     leave : function() {
253         clearTimeout(this.timeout);
254     
255         this.hoverState = 'out';
256     
257         if (!this.delay || !this.delay.hide) {
258             this.hide();
259             return;
260         }
261         var _t = this;
262         this.timeout = setTimeout(function () {
263             if (_t.hoverState == 'out') {
264                 _t.hide();
265             }
266         }, this.delay.hide)
267     },
268     
269     /**
270      * update the position of the dialog
271      * normally this is needed if the popover get's bigger - due to a Table reload etc..
272      * 
273      *
274      */
275     
276     doAlign : function()
277     {
278         
279         if (this.alignEl) {
280             this.updatePosition(this.placement, true);
281              
282         } else {
283             // this is usually just done by the builder = to show the popoup in the middle of the scren.
284             var es = this.el.getSize();
285             var x = Roo.lib.Dom.getViewWidth()/2;
286             var y = Roo.lib.Dom.getViewHeight()/2;
287             this.el.setXY([ x-(es.width/2),  y-(es.height/2)] );
288             
289         }
290
291          
292          
293         
294         
295     },
296     
297     /**
298      * Show the popover
299      * @param {Roo.Element|string|Boolean} - element to align and point to. (set align to [ pos, offset ])
300      * @param {string} (left|right|top|bottom) position
301      */
302     show : function (on_el, placement)
303     {
304         this.placement = typeof(placement) == 'undefined' ?  this.placement   : placement;
305         on_el = on_el || false; // default to false
306          
307         if (!on_el) {
308             if (this.parent() && (this.over == 'parent' || (this.over === false))) {
309                 on_el = this.parent().el;
310             } else if (this.over) {
311                 on_el = Roo.get(this.over);
312             }
313             
314         }
315         
316         this.alignEl = Roo.get( on_el );
317
318         if (!this.el) {
319             this.render(document.body);
320         }
321         
322         
323          
324         
325         if (this.title === false) {
326             this.headerEl.hide();
327         }
328         
329        
330         this.el.show();
331         this.el.dom.style.display = 'block';
332          
333         this.doAlign();
334         
335         //var arrow = this.el.select('.arrow',true).first();
336         //arrow.set(align[2], 
337         
338         this.el.addClass('in');
339         
340          
341         
342         this.hoverState = 'in';
343         
344         if (this.modal) {
345             this.maskEl.setSize(Roo.lib.Dom.getViewWidth(true),   Roo.lib.Dom.getViewHeight(true));
346             this.maskEl.setStyle('z-index', Roo.bootstrap.Popover.zIndex++);
347             this.maskEl.dom.style.display = 'block';
348             this.maskEl.addClass('show');
349         }
350         this.el.setStyle('z-index', Roo.bootstrap.Popover.zIndex++);
351  
352         this.fireEvent('show', this);
353         
354     },
355     /**
356      * fire this manually after loading a grid in the table for example
357      * @param {string} (left|right|top|bottom) where to try and put it (use false to use the last one)
358      * @param {Boolean} try and move it if we cant get right position.
359      */
360     updatePosition : function(placement, try_move)
361     {
362         // allow for calling with no parameters
363         placement = placement   ? placement :  this.placement;
364         try_move = typeof(try_move) == 'undefined' ? true : try_move;
365         
366         this.el.removeClass([
367             'fade','top','bottom', 'left', 'right','in',
368             'bs-popover-top','bs-popover-bottom', 'bs-popover-left', 'bs-popover-right'
369         ]);
370         this.el.addClass(placement + ' bs-popover-' + placement);
371         
372         if (!this.alignEl ) {
373             return false;
374         }
375         
376         switch (placement) {
377             case 'right':
378                 var exact = this.el.getAlignToXY(this.alignEl, 'tl-tr', [10,0]);
379                 var offset = this.el.getAlignToXY(this.alignEl, 'tl-tr?',[10,0]);
380                 if (!try_move || exact.equals(offset) || exact[0] == offset[0] ) {
381                     //normal display... or moved up/down.
382                     this.el.setXY(offset);
383                     var xy = this.alignEl.getAnchorXY('tr', false);
384                     xy[0]+=2;xy[1]+=5;
385                     this.arrowEl.setXY(xy);
386                     return true;
387                 }
388                 // continue through...
389                 return this.updatePosition('left', false);
390                 
391             
392             case 'left':
393                 var exact = this.el.getAlignToXY(this.alignEl, 'tr-tl', [-10,0]);
394                 var offset = this.el.getAlignToXY(this.alignEl, 'tr-tl?',[-10,0]);
395                 if (!try_move || exact.equals(offset) || exact[0] == offset[0] ) {
396                     //normal display... or moved up/down.
397                     this.el.setXY(offset);
398                     var xy = this.alignEl.getAnchorXY('tl', false);
399                     xy[0]-=10;xy[1]+=5; // << fix me
400                     this.arrowEl.setXY(xy);
401                     return true;
402                 }
403                 // call self...
404                 return this.updatePosition('right', false);
405             
406             case 'top':
407                 var exact = this.el.getAlignToXY(this.alignEl, 'b-t', [0,-10]);
408                 var offset = this.el.getAlignToXY(this.alignEl, 'b-t?',[0,-10]);
409                 if (!try_move || exact.equals(offset) || exact[1] == offset[1] ) {
410                     //normal display... or moved up/down.
411                     this.el.setXY(offset);
412                     var xy = this.alignEl.getAnchorXY('t', false);
413                     xy[1]-=10; // << fix me
414                     this.arrowEl.setXY(xy);
415                     return true;
416                 }
417                 // fall through
418                return this.updatePosition('bottom', false);
419             
420             case 'bottom':
421                  var exact = this.el.getAlignToXY(this.alignEl, 't-b', [0,10]);
422                 var offset = this.el.getAlignToXY(this.alignEl, 't-b?',[0,10]);
423                 if (!try_move || exact.equals(offset) || exact[1] == offset[1] ) {
424                     //normal display... or moved up/down.
425                     this.el.setXY(offset);
426                     var xy = this.alignEl.getAnchorXY('b', false);
427                      xy[1]+=2; // << fix me
428                     this.arrowEl.setXY(xy);
429                     return true;
430                 }
431                 // fall through
432                 return this.updatePosition('top', false);
433                 
434             
435         }
436         
437         
438         return false;
439     },
440     
441     hide : function()
442     {
443         this.el.setXY([0,0]);
444         this.el.removeClass('in');
445         this.el.hide();
446         this.hoverState = null;
447         this.maskEl.hide(); // always..
448         this.fireEvent('hide', this);
449     }
450     
451 });
452
453
454 Roo.apply(Roo.bootstrap.Popover, {
455
456     alignment : {
457         'left' : ['r-l', [-10,0], 'left bs-popover-left'],
458         'right' : ['l-br', [10,0], 'right bs-popover-right'],
459         'bottom' : ['t-b', [0,10], 'top bs-popover-top'],
460         'top' : [ 'b-t', [0,-10], 'bottom bs-popover-bottom']
461     },
462     
463     zIndex : 20001,
464
465     clickHander : false,
466     
467     
468
469     onMouseDown : function(e)
470     {
471         if (this.popups.length &&  !e.getTarget(".roo-popover")) {
472             /// what is nothing is showing..
473             this.hideAll();
474         }
475          
476     },
477     
478     
479     popups : [],
480     
481     register : function(popup)
482     {
483         if (!Roo.bootstrap.Popover.clickHandler) {
484             Roo.bootstrap.Popover.clickHandler = Roo.get(document).on("mousedown", Roo.bootstrap.Popover.onMouseDown, Roo.bootstrap.Popover);
485         }
486         // hide other popups.
487         popup.on('show', Roo.bootstrap.Popover.onShow,  popup);
488         popup.on('hide', Roo.bootstrap.Popover.onHide,  popup);
489         this.hideAll(); //<< why?
490         //this.popups.push(popup);
491     },
492     hideAll : function()
493     {
494         this.popups.forEach(function(p) {
495             p.hide();
496         });
497     },
498     onShow : function() {
499         Roo.bootstrap.Popover.popups.push(this);
500     },
501     onHide : function() {
502         Roo.bootstrap.Popover.popups.remove(this);
503     } 
504
505 });