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