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