7ad1a52bffd873b4e2158ad6bf36e5a5f783883c
[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      * Show the popover
270      * @param {Roo.Element|string|Boolean} - element to align and point to. (set align to [ pos, offset ])
271      * @param {string} (left|right|top|bottom) position
272      */
273     show : function (on_el, placement)
274     {
275         this.placement = typeof(placement) == 'undefined' ?  this.placement   : placement;
276         on_el = on_el || false; // default to false
277          
278         if (!on_el) {
279             if (this.parent() && (this.over == 'parent' || (this.over === false))) {
280                 on_el = this.parent().el;
281             } else if (this.over) {
282                 on_el = Roo.get(this.over);
283             }
284             
285         }
286         
287         this.alignEl = Roo.get( on_el );
288
289         if (!this.el) {
290             this.render(document.body);
291         }
292         
293         
294          
295         
296         if (this.title === false) {
297             this.headerEl.hide();
298         }
299         
300        
301         this.el.show();
302         this.el.dom.style.display = 'block';
303          
304  
305         if (this.alignEl) {
306             this.updatePosition(this.placement, true);
307              
308         } else {
309             // this is usually just done by the builder = to show the popoup in the middle of the scren.
310             var es = this.el.getSize();
311             var x = Roo.lib.Dom.getViewWidth()/2;
312             var y = Roo.lib.Dom.getViewHeight()/2;
313             this.el.setXY([ x-(es.width/2),  y-(es.height/2)] );
314             
315         }
316
317         
318         //var arrow = this.el.select('.arrow',true).first();
319         //arrow.set(align[2], 
320         
321         this.el.addClass('in');
322         
323          
324         
325         this.hoverState = 'in';
326         
327         if (this.modal) {
328             this.maskEl.setSize(Roo.lib.Dom.getViewWidth(true),   Roo.lib.Dom.getViewHeight(true));
329             this.maskEl.setStyle('z-index', Roo.bootstrap.Popover.zIndex++);
330             this.maskEl.dom.style.display = 'block';
331             this.maskEl.addClass('show');
332         }
333         this.el.setStyle('z-index', Roo.bootstrap.Popover.zIndex++);
334  
335         this.fireEvent('show', this);
336         
337     },
338     /**
339      * fire this manually after loading a grid in the table for example
340      * @param {string} (left|right|top|bottom) where to try and put it (use false to use the last one)
341      * @param {Boolean} try and move it if we cant get right position.
342      */
343     updatePosition : function(placement, try_move)
344     {
345         // allow for calling with no parameters
346         placement = placement   ? placement :  this.placement;
347         try_move = typeof(try_move) == 'undefined' ? true : try_move;
348         
349         this.el.removeClass([
350             'fade','top','bottom', 'left', 'right','in',
351             'bs-popover-top','bs-popover-bottom', 'bs-popover-left', 'bs-popover-right'
352         ]);
353         this.el.addClass(placement + ' bs-popover-' + placement);
354         
355         if (!this.alignEl ) {
356             return false;
357         }
358         
359         switch (placement) {
360             case 'right':
361                 var exact = this.el.getAlignToXY(this.alignEl, 'tl-tr', [10,0]);
362                 var offset = this.el.getAlignToXY(this.alignEl, 'tl-tr?',[10,0]);
363                 if (!try_move || exact.equals(offset) || exact[0] == offset[0] ) {
364                     //normal display... or moved up/down.
365                     this.el.setXY(offset);
366                     var xy = this.alignEl.getAnchorXY('tr', false);
367                     xy[0]+=2;xy[1]+=5;
368                     this.arrowEl.setXY(xy);
369                     return true;
370                 }
371                 // continue through...
372                 return this.updatePosition('left', false);
373                 
374             
375             case 'left':
376                 var exact = this.el.getAlignToXY(this.alignEl, 'tr-tl', [-10,0]);
377                 var offset = this.el.getAlignToXY(this.alignEl, 'tr-tl?',[-10,0]);
378                 if (!try_move || exact.equals(offset) || exact[0] == offset[0] ) {
379                     //normal display... or moved up/down.
380                     this.el.setXY(offset);
381                     var xy = this.alignEl.getAnchorXY('tl', false);
382                     xy[0]-=10;xy[1]+=5; // << fix me
383                     this.arrowEl.setXY(xy);
384                     return true;
385                 }
386                 // call self...
387                 return this.updatePosition('right', false);
388             
389             case 'top':
390                 var exact = this.el.getAlignToXY(this.alignEl, 'b-t', [0,-10]);
391                 var offset = this.el.getAlignToXY(this.alignEl, 'b-t?',[0,-10]);
392                 if (!try_move || exact.equals(offset) || exact[1] == offset[1] ) {
393                     //normal display... or moved up/down.
394                     this.el.setXY(offset);
395                     var xy = this.alignEl.getAnchorXY('t', false);
396                     xy[1]-=10; // << fix me
397                     this.arrowEl.setXY(xy);
398                     return true;
399                 }
400                 // fall through
401                return this.updatePosition('bottom', false);
402             
403             case 'bottom':
404                  var exact = this.el.getAlignToXY(this.alignEl, 't-b', [0,10]);
405                 var offset = this.el.getAlignToXY(this.alignEl, 't-b?',[0,10]);
406                 if (!try_move || exact.equals(offset) || exact[1] == offset[1] ) {
407                     //normal display... or moved up/down.
408                     this.el.setXY(offset);
409                     var xy = this.alignEl.getAnchorXY('b', false);
410                      xy[1]+=2; // << fix me
411                     this.arrowEl.setXY(xy);
412                     return true;
413                 }
414                 // fall through
415                 return this.updatePosition('top', false);
416                 
417             
418         }
419         
420         
421         return false;
422     },
423     
424     hide : function()
425     {
426         this.el.setXY([0,0]);
427         this.el.removeClass('in');
428         this.el.hide();
429         this.hoverState = null;
430         this.maskEl.hide(); // always..
431         this.fireEvent('hide', this);
432     }
433     
434 });
435
436
437 Roo.apply(Roo.bootstrap.Popover, {
438
439     alignment : {
440         'left' : ['r-l', [-10,0], 'left bs-popover-left'],
441         'right' : ['l-br', [10,0], 'right bs-popover-right'],
442         'bottom' : ['t-b', [0,10], 'top bs-popover-top'],
443         'top' : [ 'b-t', [0,-10], 'bottom bs-popover-bottom']
444     },
445     
446     zIndex : 20001,
447
448     clickHander : false,
449     
450     
451
452     onMouseDown : function(e)
453     {
454         if (this.popups.length &&  !e.getTarget(".roo-popover")) {
455             /// what is nothing is showing..
456             this.hideAll();
457         }
458          
459     },
460     
461     
462     popups : [],
463     
464     register : function(popup)
465     {
466         if (!Roo.bootstrap.Popover.clickHandler) {
467             Roo.bootstrap.Popover.clickHandler = Roo.get(document).on("mousedown", Roo.bootstrap.Popover.onMouseDown, Roo.bootstrap.Popover);
468         }
469         // hide other popups.
470         popup.on('show', Roo.bootstrap.Popover.onShow,  popup);
471         popup.on('hide', Roo.bootstrap.Popover.onHide,  popup);
472         this.hideAll(); //<< why?
473         //this.popups.push(popup);
474     },
475     hideAll : function()
476     {
477         this.popups.forEach(function(p) {
478             p.hide();
479         });
480     },
481     onShow : function() {
482         Roo.bootstrap.Popover.popups.push(this);
483     },
484     onHide : function() {
485         Roo.bootstrap.Popover.popups.remove(this);
486     } 
487
488 });