7bf9facdff1572c4b1bcb78128ca29e9537e20d5
[roojs1] / Roo / bootstrap / Card.js
1 /*
2  *  - LGPL
3  *
4  *  This is BS4's Card element.. - similar to our containers probably..
5  * 
6  */
7 /**
8  * @class Roo.bootstrap.Card
9  * @extends Roo.bootstrap.Component
10  * Bootstrap Card class
11  *
12  *
13  * possible... may not be implemented..
14  * @cfg {String} header_image  src url of image.
15  * @cfg {String|Object} header
16  * @cfg {Number} header_size (0|1|2|3|4|5) H1 or H2 etc.. 0 indicates default
17  * 
18  * @cfg {String} title
19  * @cfg {String} subtitle
20  * @cfg {String|Boolean} html -- html contents - or just use children.. use false to hide it..
21  * @cfg {String} footer
22  
23  * @cfg {String} weight (primary|warning|info|danger|secondary|success|light|dark)
24  * 
25  * @cfg {String} margin (0|1|2|3|4|5|auto)
26  * @cfg {String} margin_top (0|1|2|3|4|5|auto)
27  * @cfg {String} margin_bottom (0|1|2|3|4|5|auto)
28  * @cfg {String} margin_left (0|1|2|3|4|5|auto)
29  * @cfg {String} margin_right (0|1|2|3|4|5|auto)
30  * @cfg {String} margin_x (0|1|2|3|4|5|auto)
31  * @cfg {String} margin_y (0|1|2|3|4|5|auto)
32  *
33  * @cfg {String} padding (0|1|2|3|4|5)
34  * @cfg {String} padding_top (0|1|2|3|4|5)
35  * @cfg {String} padding_bottom (0|1|2|3|4|5)
36  * @cfg {String} padding_left (0|1|2|3|4|5)
37  * @cfg {String} padding_right (0|1|2|3|4|5)
38  * @cfg {String} padding_x (0|1|2|3|4|5)
39  * @cfg {String} padding_y (0|1|2|3|4|5)
40  *
41  * @cfg {String} display (none|inline|inline-block|block|table|table-cell|table-row|flex|inline-flex)
42  * @cfg {String} display_xs (none|inline|inline-block|block|table|table-cell|table-row|flex|inline-flex)
43  * @cfg {String} display_sm (none|inline|inline-block|block|table|table-cell|table-row|flex|inline-flex)
44  * @cfg {String} display_lg (none|inline|inline-block|block|table|table-cell|table-row|flex|inline-flex)
45  * @cfg {String} display_xl (none|inline|inline-block|block|table|table-cell|table-row|flex|inline-flex)
46  
47  * @config {Boolean} dragable  if this card can be dragged.
48  * @config {String} drag_group  group for drag
49  * @config {Boolean} dropable  if this card can recieve other cards being dropped onto it..
50  * @config {String} drop_group  group for drag
51  * 
52  * @config {Boolean} collapsable can the body be collapsed.
53  * @config {Boolean} collapsed is the body collapsed when rendered...
54  * @config {Boolean} rotateable can the body be rotated by clicking on it..
55  * @config {Boolean} rotated is the body rotated when rendered...
56  * 
57  * @constructor
58  * Create a new Container
59  * @param {Object} config The config object
60  */
61
62 Roo.bootstrap.Card = function(config){
63     Roo.bootstrap.Card.superclass.constructor.call(this, config);
64     
65     this.addEvents({
66          // raw events
67         /**
68          * @event drop
69          * When a element a card is dropped
70          * @param {Roo.bootstrap.Element} this
71          * @param {Roo.Element} n the node being dropped?
72          * @param {Object} dd Drag and drop data
73          * @param {Roo.EventObject} e
74          * @param {Roo.EventObject} data  the data passed via getDragData
75          */
76         'drop' : true,
77          /**
78          * @event rotate
79          * When a element a card is rotate
80          * @param {Roo.bootstrap.Element} this
81          * @param {Roo.Element} n the node being dropped?
82          * @param {Boolean} rotate status
83          */
84         'rotate' : true
85         
86     });
87 };
88
89
90 Roo.extend(Roo.bootstrap.Card, Roo.bootstrap.Component,  {
91     
92     
93     weight : '',
94     
95     margin: '', /// may be better in component?
96     margin_top: '', 
97     margin_bottom: '', 
98     margin_left: '',
99     margin_right: '',
100     margin_x: '',
101     margin_y: '',
102     
103     padding : '',
104     padding_top: '', 
105     padding_bottom: '', 
106     padding_left: '',
107     padding_right: '',
108     padding_x: '',
109     padding_y: '',
110     
111     display: '', 
112     display_xs: '', 
113     display_sm: '', 
114     display_lg: '',
115     display_xl: '',
116  
117     header_image  : '',
118     header : '',
119     header_size : 0,
120     title : '',
121     subtitle : '',
122     html : '',
123     footer: '',
124
125     collapsable : false,
126     collapsed : false,
127     rotateable : false,
128     rotated : false,
129     
130     dragable : false,
131     drag_group : false,
132     dropable : false,
133     drop_group : false,
134     childContainer : false,
135     dropEl : false, /// the dom placeholde element that indicates drop location.
136     
137     layoutCls : function()
138     {
139         var cls = '';
140         var t = this;
141         Roo.log(this.margin_bottom.length);
142         ['', 'top', 'bottom', 'left', 'right', 'x', 'y' ].forEach(function(v) {
143             // in theory these can do margin_top : ml-xs-3 ??? but we don't support that yet
144             
145             if (('' + t['margin' + (v.length ? '_' : '') + v]).length) {
146                 cls += ' m' +  (v.length ? v[0]  : '') + '-' +  t['margin' + (v.length ? '_' : '') + v];
147             }
148             if (('' + t['padding' + (v.length ? '_' : '') + v]).length) {
149                 cls += ' p' +  (v.length ? v[0]  : '') + '-' +  t['padding' + (v.length ? '_' : '') + v];
150             }
151         });
152         
153         ['', 'xs', 'sm', 'lg', 'xl'].forEach(function(v) {
154             if (('' + t['display' + (v.length ? '_' : '') + v]).length) {
155                 cls += ' d' +  (v.length ? '-' : '') + v + '-' + t['margin' + (v.length ? '_' : '') + v]
156             }
157         });
158         
159         // more generic support?
160         if (this.hidden) {
161             cls += ' d-none';
162         }
163         
164         return cls;
165     },
166  
167        // Roo.log("Call onRender: " + this.xtype);
168         /*  We are looking at something like this.
169 <div class="card">
170     <img src="..." class="card-img-top" alt="...">
171     <div class="card-body">
172         <h5 class="card-title">Card title</h5>
173          <h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
174
175         >> this bit is really the body...
176         <div> << we will ad dthis in hopefully it will not break shit.
177         
178         ** card text does not actually have any styling...
179         
180             <p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
181         
182         </div> <<
183           <a href="#" class="card-link">Card link</a>
184           
185     </div>
186     <div class="card-footer">
187         <small class="text-muted">Last updated 3 mins ago</small>
188     </div>
189 </div>
190          */
191     getAutoCreate : function(){
192         
193         var cfg = {
194             tag : 'div',
195             cls : 'card',
196             cn : [ ]
197         };
198         
199         if (this.weight.length && this.weight != 'light') {
200             cfg.cls += ' text-white';
201         } else {
202             cfg.cls += ' text-dark'; // need as it's nested..
203         }
204         if (this.weight.length) {
205             cfg.cls += ' bg-' + this.weight;
206         }
207         
208         cfg.cls += this.layoutCls(); 
209         
210         var hdr = false;
211         if (this.header.length) {
212             hdr = {
213                 tag : this.header_size > 0 ? 'h' + this.header_size : 'div',
214                 cls : 'card-header',
215                 cn : []
216             };
217             cfg.cn.push(hdr);
218             hdr_ctr = hdr;
219         } else {
220             hdr = {
221                 tag : 'div',
222                 cls : 'card-header d-none',
223                 cn : []
224             };
225             cfg.cn.push(hdr);
226         }
227         if (this.collapsable) {
228             hdr_ctr = {
229             tag : 'a',
230             cls : 'd-block user-select-none',
231             cn: [
232                     {
233                         tag: 'i',
234                         cls : 'roo-collapse-toggle fa fa-chevron-down float-right ' + (this.collapsed ? 'collapsed' : '')
235                     }
236                    
237                 ]
238             };
239             hdr.cn.push(hdr_ctr);
240         }
241         
242         hdr_ctr.cn.push(        {
243             tag: 'span',
244             cls: 'roo-card-header-ctr' + ( this.header.length ? '' : ' d-none'),
245             html : this.header
246         })
247         
248         
249         if (this.header_image.length) {
250             cfg.cn.push({
251                 tag : 'img',
252                 cls : 'card-img-top',
253                 src: this.header_image // escape?
254             });
255         } else {
256             cfg.cn.push({
257                     tag : 'div',
258                     cls : 'card-img-top d-none' 
259                 });
260         }
261             
262         var body = {
263             tag : 'div',
264             cls : 'card-body' + (this.html === false  ? ' d-none' : ''),
265             cn : []
266         };
267         var obody = body;
268         if (this.collapsable || this.rotateable) {
269             obody = {
270                 tag: 'div',
271                 cls : 'roo-collapsable collapse ' + (this.collapsed || this.rotated ? '' : 'show'),
272                 cn : [  body ]
273             };
274         }
275         
276         cfg.cn.push(obody);
277         
278         if (this.title.length) {
279             body.cn.push({
280                 tag : 'div',
281                 cls : 'card-title',
282                 src: this.title // escape?
283             });
284         }  
285         
286         if (this.subtitle.length) {
287             body.cn.push({
288                 tag : 'div',
289                 cls : 'card-title',
290                 src: this.subtitle // escape?
291             });
292         }
293         
294         body.cn.push({
295             tag : 'div',
296             cls : 'roo-card-body-ctr'
297         });
298         
299         if (this.html.length) {
300             body.cn.push({
301                 tag: 'div',
302                 html : this.html
303             });
304         }
305         // fixme ? handle objects?
306         
307         if (this.footer.length) {
308            
309             cfg.cn.push({
310                 cls : 'card-footer ' + (this.rotated ? 'd-none' : ''),
311                 html : this.footer
312             });
313             
314         } else {
315             cfg.cn.push({cls : 'card-footer d-none'});
316         }
317         
318         // footer...
319         
320         return cfg;
321     },
322     
323     
324     getCardHeader : function()
325     {
326         var  ret = this.el.select('.card-header',true).first();
327         if (ret.hasClass('d-none')) {
328             ret.removeClass('d-none');
329         }
330         
331         return ret;
332     },
333     getCardFooter : function()
334     {
335         var  ret = this.el.select('.card-footer',true).first();
336         if (ret.hasClass('d-none')) {
337             ret.removeClass('d-none');
338         }
339         
340         return ret;
341     },
342     getCardImageTop : function()
343     {
344         var  ret = this.el.select('.card-img-top',true).first();
345         if (ret.hasClass('d-none')) {
346             ret.removeClass('d-none');
347         }
348             
349         return ret;
350     },
351     
352     getChildContainer : function()
353     {
354         
355         if(!this.el){
356             return false;
357         }
358         return this.el.select('.roo-card-body-ctr',true).first();    
359     },
360     
361     initEvents: function() 
362     {
363         
364         this.bodyEl = this.getChildContainer();
365         if(this.dragable){
366             this.dragZone = new Roo.dd.DragZone(this.getEl(), {
367                     containerScroll: true,
368                     ddGroup: this.drag_group || 'default_card_drag_group'
369             });
370             this.dragZone.getDragData = this.getDragData.createDelegate(this);
371         }
372         if (this.dropable) {
373             this.dropZone = new Roo.dd.DropZone(this.el.select('.card-body',true).first() , {
374                 containerScroll: true,
375                 ddGroup: this.drop_group || 'default_card_drag_group'
376             });
377             this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
378             this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
379             this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
380             this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
381             this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
382         }
383         
384         if (this.collapsable) {
385             this.el.select('.card-header',true).on('click', this.onToggleCollapse, this);
386         }
387         if (this.rotateable) {
388             this.el.select('.card-header',true).on('click', this.onToggleRotate, this);
389         }
390         this.collapsableEl = this.el.select('.roo-collapsable').first();
391          
392         this.footerEl = this.el.select('.card-footer').first();
393         this.collapsableToggleEl = this.el.select('.roo-collapse-toggle');
394         this.headerEl = this.el.select('.roo-card-header-ctr').first();
395         
396         if (this.rotated) {
397             this.el.addClass('roo-card-rotated');
398             this.fireEvent('rotate', this, true);
399         }
400         
401     },
402     getDragData : function(e)
403     {
404         var target = this.getEl();
405         if (target) {
406             //this.handleSelection(e);
407             
408             var dragData = {
409                 source: this,
410                 copy: false,
411                 nodes: this.getEl(),
412                 records: []
413             };
414             
415             
416             dragData.ddel = target.dom ;    // the div element
417             Roo.log(target.getWidth( ));
418             dragData.ddel.style.width = target.getWidth() + 'px';
419             
420             return dragData;
421         }
422         return false;
423     },
424     /**
425     *    Part of the Roo.dd.DropZone interface. If no target node is found, the
426     *    whole Element becomes the target, and this causes the drop gesture to append.
427     */
428     getTargetFromEvent : function(e, dragged_card_el)
429     {
430         var target = e.getTarget();
431         while ((target !== null) && (target.parentNode != this.bodyEl.dom)) {
432             target = target.parentNode;
433         }
434         
435         var ret = {
436             position: '',
437             cards : [],
438             card_n : -1,
439             items_n : -1,
440             card : false,
441         };
442         
443         //Roo.log([ 'target' , target ? target.id : '--nothing--']);
444         // see if target is one of the 'cards'...
445         
446         
447         //Roo.log(this.items.length);
448         var pos = false;
449         
450         var last_card_n = 0;
451         var cards_len  = 0;
452         for (var i = 0;i< this.items.length;i++) {
453             
454             if (!this.items[i].el.hasClass('card')) {
455                  continue;
456             }
457             pos = this.getDropPoint(e, this.items[i].el.dom);
458             
459             cards_len = ret.cards.length;
460             //Roo.log(this.items[i].el.dom.id);
461             ret.cards.push(this.items[i]);
462             last_card_n  = i;
463             if (ret.card_n < 0 && pos == 'above') {
464                 ret.position = cards_len > 0 ? 'below' : pos;
465                 ret.items_n = i > 0 ? i - 1 : 0;
466                 ret.card_n  = cards_len  > 0 ? cards_len - 1 : 0;
467                 ret.card = ret.cards[ret.card_n];
468             }
469         }
470         if (!ret.cards.length) {
471             ret.card = true;
472             ret.position = 'below';
473             ret.items_n;
474             return ret;
475         }
476         // could not find a card.. stick it at the end..
477         if (ret.card_n < 0) {
478             ret.card_n = last_card_n;
479             ret.card = ret.cards[last_card_n];
480             ret.items_n = this.items.indexOf(ret.cards[last_card_n]);
481             ret.position = 'below';
482         }
483         
484         if (this.items[ret.items_n].el == dragged_card_el) {
485             return false;
486         }
487         
488         if (ret.position == 'below') {
489             var card_after = ret.card_n+1 == ret.cards.length ? false : ret.cards[ret.card_n+1];
490             
491             if (card_after  && card_after.el == dragged_card_el) {
492                 return false;
493             }
494             return ret;
495         }
496         
497         // its's after ..
498         var card_before = ret.card_n > 0 ? ret.cards[ret.card_n-1] : false;
499         
500         if (card_before  && card_before.el == dragged_card_el) {
501             return false;
502         }
503         
504         return ret;
505     },
506     
507     onNodeEnter : function(n, dd, e, data){
508         return false;
509     },
510     onNodeOver : function(n, dd, e, data)
511     {
512        
513         var target_info = this.getTargetFromEvent(e,data.source.el);
514         if (target_info === false) {
515             this.dropPlaceHolder('hide');
516             return false;
517         }
518         Roo.log(['getTargetFromEvent', target_info ]);
519         
520          
521         this.dropPlaceHolder('show', target_info,data);
522         
523         return false; 
524     },
525     onNodeOut : function(n, dd, e, data){
526         this.dropPlaceHolder('hide');
527      
528     },
529     onNodeDrop : function(n, dd, e, data)
530     {
531         
532         // call drop - return false if
533         
534         // this could actually fail - if the Network drops..
535         // we will ignore this at present..- client should probably reload
536         // the whole set of cards if stuff like that fails.
537         
538         
539         var info = this.getTargetFromEvent(e,data.source.el);
540         if (info === false) {
541             return false;
542         }
543         
544         if (this.fireEvent("drop", this, n, dd, e, data) === false) {
545             return false;
546         }
547          
548         this.dropPlaceHolder('hide');
549         
550         // do the dom manipulation first..
551         var dom = data.source.el.dom;
552         dom.parentNode.removeChild(dom);
553         
554         
555         if (info.card !== true) {
556             var cardel = info.card.el.dom;
557             
558             if (info.position == 'above') {
559                 cardel.parentNode.insertBefore(dom, cardel);
560             } else if (cardel.nextSibling) {
561                 cardel.parentNode.insertBefore(dom,cardel.nextSibling);
562             } else {
563                 cardel.parentNode.append(dom);
564             }
565         } else {
566             // card container???
567             this.bodyEl.dom.append(dom);
568         }
569         
570         //FIXME HANDLE card = true 
571         
572         // add this to the correct place in items.
573         
574         
575         
576         // remove Card from items.
577         
578         var old_parent = data.source.parent();
579         
580         old_parent.items = old_parent.items.filter(function(e) { return e != data.source });
581         
582         if (this.items.length) {
583             var nitems = [];
584             Roo.log([info.items_n, info.position, this.items.length])
585             for (var i =0; i < this.items.length; i++) {
586                 if (i == info.items_n && info.position == 'above') {
587                     nitems.push(data.source);
588                 }
589                 nitems.push(this.items[i]);
590                 if (i == info.items_n && info.position == 'below') {
591                     nitems.push(data.source);
592                 }
593             }
594             this.items = nitems;
595             Roo.log(this.items);
596         } else {
597             this.items.push(data.source);
598         }
599         
600         data.source.parentId = this.id;
601         
602         return true;
603     },
604     
605     /**    Decide whether to drop above or below a View node. */
606     getDropPoint : function(e, n, dd)
607     {
608         if (dd) {
609              return false;
610         }
611         if (n == this.bodyEl.dom) {
612             return "above";
613         }
614         var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
615         var c = t + (b - t) / 2;
616         var y = Roo.lib.Event.getPageY(e);
617         if(y <= c) {
618             return "above";
619         }else{
620             return "below";
621         }
622     },
623     onToggleCollapse : function(e)
624         {
625         if (this.collapsed) {
626             this.el.select('.roo-collapse-toggle').removeClass('collapsed');
627             this.collapsableEl.addClass('show');
628             this.collapsed = false;
629             return;
630         }
631         this.el.select('.roo-collapse-toggle').addClass('collapsed');
632         this.collapsableEl.removeClass('show');
633         this.collapsed = true;
634         
635     
636     },
637     
638     onToggleRotate : function(e)
639     {
640         this.collapsableEl.removeClass('show');
641         this.footerEl.removeClass('d-none');
642         this.el.removeClass('roo-card-rotated');
643         this.el.removeClass('d-none');
644         if (this.rotated) {
645             
646             this.collapsableEl.addClass('show');
647             this.rotated = false;
648             this.fireEvent('rotate', this, this.rotated);
649             return;
650         }
651         this.el.addClass('roo-card-rotated');
652         this.footerEl.addClass('d-none');
653         this.el.select('.roo-collapsable').removeClass('show');
654         
655         this.rotated = true;
656         this.fireEvent('rotate', this, this.rotated);
657     
658     },
659     
660     dropPlaceHolder: function (action, info, data)
661     {
662         if (this.dropEl === false) {
663             this.dropEl = Roo.DomHelper.append(this.bodyEl, {
664             cls : 'd-none'
665             },true);
666         }
667         this.dropEl.removeClass(['d-none', 'd-block']);        
668         if (action == 'hide') {
669             
670             this.dropEl.addClass('d-none');
671             return;
672         }
673         // FIXME - info.card == true!!!
674         this.dropEl.dom.parentNode.removeChild(this.dropEl.dom);
675         
676         if (info.card !== true) {
677             var cardel = info.card.el.dom;
678             
679             if (info.position == 'above') {
680                 cardel.parentNode.insertBefore(this.dropEl.dom, cardel);
681             } else if (cardel.nextSibling) {
682                 cardel.parentNode.insertBefore(this.dropEl.dom,cardel.nextSibling);
683             } else {
684                 cardel.parentNode.append(this.dropEl.dom);
685             }
686         } else {
687             // card container???
688             this.bodyEl.dom.append(this.dropEl.dom);
689         }
690         
691         this.dropEl.addClass('d-block roo-card-dropzone');
692         
693         this.dropEl.setHeight( Roo.get(data.ddel).getHeight() );
694         
695         
696     
697     
698     
699     },
700     setHeaderText: function(html)
701     {
702         this.headerEl.dom.innerHTML = html;
703     }
704
705     
706 });
707