Roo/htmleditor/BlockTd.js
[roojs1] / Roo / htmleditor / BlockTd.js
1 /**
2  *
3  * editing a TD?
4  *
5  * since selections really work on the table cell, then editing really should work from there
6  *
7  * The original plan was to support merging etc... - but that may not be needed yet..
8  *
9  * So this simple version will support:
10  *   add/remove cols
11  *   adjust the width +/-
12  *   reset the width...
13  *   
14  *
15  */
16
17
18  
19
20 /**
21  * @class Roo.htmleditor.BlockTable
22  * Block that manages a table
23  * 
24  * @constructor
25  * Create a new Filter.
26  * @param {Object} config Configuration options
27  */
28
29 Roo.htmleditor.BlockTd = function(cfg)
30 {
31     if (cfg.node) {
32         this.readElement(cfg.node);
33         this.updateElement(cfg.node);
34     }
35     Roo.apply(this, cfg);
36      
37     
38     
39 }
40 Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, {
41  
42     node : false,
43     
44     width: '',
45     textAlign : 'left',
46     valign : 'top',
47     
48     colspan : 1,
49     rowspan : 1,
50     
51     
52     // used by context menu
53     friendly_name : 'Table Cell',
54     deleteTitle : false, // use our customer delete
55     
56     // context menu is drawn once..
57     
58     contextMenu : function(toolbar)
59     {
60         console.log("htmleditor.BlockTd contextMenu");
61         console.log(toolbar);
62         
63         var cell = function() {
64             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
65         };
66         
67         var table = function() {
68             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table'));
69         };
70         
71         var lr = false;
72         var saveSel = function()
73         {
74             lr = toolbar.editorcore.getSelection().getRangeAt(0);
75         }
76         var restoreSel = function()
77         {
78             if (lr) {
79                 (function() {
80                     toolbar.editorcore.focus();
81                     var cr = toolbar.editorcore.getSelection();
82                     cr.removeAllRanges();
83                     cr.addRange(lr);
84                     toolbar.editorcore.onEditorEvent();
85                 }).defer(10, this);
86                 
87                 
88             }
89         }
90         
91         var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
92         
93         var syncValue = toolbar.editorcore.syncValue;
94         
95         var fields = {};
96         
97         return [
98             {
99                 xtype : 'Button',
100                 text : 'Edit Table',
101                 listeners : {
102                     click : function() {
103                         var t = toolbar.tb.selectedNode.closest('table');
104                         toolbar.editorcore.selectNode(t);
105                         toolbar.editorcore.onEditorEvent();                        
106                     }
107                 }
108                 
109             },
110               
111            
112              
113             {
114                 xtype : 'TextItem',
115                 text : "Column Width: ",
116                  xns : rooui.Toolbar 
117                
118             },
119             {
120                 xtype : 'Button',
121                 text: '-',
122                 listeners : {
123                     click : function (_self, e)
124                     {
125                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
126                         cell().shrinkColumn();
127                         syncValue();
128                          toolbar.editorcore.onEditorEvent();
129                     }
130                 },
131                 xns : rooui.Toolbar
132             },
133             {
134                 xtype : 'Button',
135                 text: '+',
136                 listeners : {
137                     click : function (_self, e)
138                     {
139                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
140                         cell().growColumn();
141                         syncValue();
142                         toolbar.editorcore.onEditorEvent();
143                     }
144                 },
145                 xns : rooui.Toolbar
146             },
147             
148             {
149                 xtype : 'TextItem',
150                 text : "Vertical Align: ",
151                 xns : rooui.Toolbar  //Boostrap?
152             },
153             {
154                 xtype : 'ComboBox',
155                 allowBlank : false,
156                 displayField : 'val',
157                 editable : true,
158                 listWidth : 100,
159                 triggerAction : 'all',
160                 typeAhead : true,
161                 valueField : 'val',
162                 width : 100,
163                 name : 'valign',
164                 listeners : {
165                     select : function (combo, r, index)
166                     {
167                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
168                         var b = cell();
169                         b.valign = r.get('val');
170                         b.updateElement();
171                         syncValue();
172                         toolbar.editorcore.onEditorEvent();
173                     }
174                 },
175                 xns : rooui.form,
176                 store : {
177                     xtype : 'SimpleStore',
178                     data : [
179                         ['top'],
180                         ['middle'],
181                         ['bottom'] // there are afew more... 
182                     ],
183                     fields : [ 'val'],
184                     xns : Roo.data
185                 }
186             },
187             
188             {
189                 xtype : 'TextItem',
190                 text : "Merge Cells: ",
191                  xns : rooui.Toolbar 
192                
193             },
194             
195             
196             {
197                 xtype : 'Button',
198                 text: 'Right',
199                 listeners : {
200                     click : function (_self, e)
201                     {
202                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
203                         cell().mergeRight();
204                         //block().growColumn();
205                         syncValue();
206                         toolbar.editorcore.onEditorEvent();
207                     }
208                 },
209                 xns : rooui.Toolbar
210             },
211              
212             {
213                 xtype : 'Button',
214                 text: 'Below',
215                 listeners : {
216                     click : function (_self, e)
217                     {
218                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
219                         cell().mergeBelow();
220                         //block().growColumn();
221                         syncValue();
222                         toolbar.editorcore.onEditorEvent();
223                     }
224                 },
225                 xns : rooui.Toolbar
226             },
227             {
228                 xtype : 'TextItem',
229                 text : "| ",
230                  xns : rooui.Toolbar 
231                
232             },
233             
234             {
235                 xtype : 'Button',
236                 text: 'Split',
237                 listeners : {
238                     click : function (_self, e)
239                     {
240                         //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
241                         cell().split();
242                         syncValue();
243                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
244                         toolbar.editorcore.onEditorEvent();
245                                              
246                     }
247                 },
248                 xns : rooui.Toolbar
249             },
250             {
251                 xtype : 'Fill',
252                 xns : rooui.Toolbar 
253                
254             },
255         
256           
257             {
258                 xtype : 'Button',
259                 text: 'Delete',
260                  
261                 xns : rooui.Toolbar,
262                 menu : {
263                     xtype : 'Menu',
264                     xns : rooui.menu,
265                     items : [
266                         {
267                             xtype : 'Item',
268                             html: 'Column',
269                             listeners : {
270                                 click : function (_self, e)
271                                 {
272                                     var t = table();
273                                     
274                                     cell().deleteColumn();
275                                     syncValue();
276                                     toolbar.editorcore.selectNode(t.node);
277                                     toolbar.editorcore.onEditorEvent();   
278                                 }
279                             },
280                             xns : rooui.menu
281                         },
282                         {
283                             xtype : 'Item',
284                             html: 'Row',
285                             listeners : {
286                                 click : function (_self, e)
287                                 {
288                                     var t = table();
289                                     cell().deleteRow();
290                                     syncValue();
291                                     
292                                     toolbar.editorcore.selectNode(t.node);
293                                     toolbar.editorcore.onEditorEvent();   
294                                                          
295                                 }
296                             },
297                             xns : rooui.menu
298                         },
299                        {
300                             xtype : 'Separator',
301                             xns : rooui.menu
302                         },
303                         {
304                             xtype : 'Item',
305                             html: 'Table',
306                             listeners : {
307                                 click : function (_self, e)
308                                 {
309                                     var t = table();
310                                     var nn = t.node.nextSibling || t.node.previousSibling;
311                                     t.node.parentNode.removeChild(t.node);
312                                     if (nn) { 
313                                         toolbar.editorcore.selectNode(nn, true);
314                                     }
315                                     toolbar.editorcore.onEditorEvent();   
316                                                          
317                                 }
318                             },
319                             xns : rooui.menu
320                         }
321                     ]
322                 }
323             }
324             
325             // align... << fixme
326             
327         ];
328         
329     },
330     
331     
332   /**
333      * create a DomHelper friendly object - for use with
334      * Roo.DomHelper.markup / overwrite / etc..
335      * ?? should it be called with option to hide all editing features?
336      */
337  /**
338      * create a DomHelper friendly object - for use with
339      * Roo.DomHelper.markup / overwrite / etc..
340      * ?? should it be called with option to hide all editing features?
341      */
342     toObject : function()
343     {
344         console.log("htmleditor.BlockTd toObject");
345         var ret = {
346             tag : 'td',
347             contenteditable : 'true', // this stops cell selection from picking the table.
348             'data-block' : 'Td',
349             valign : this.valign,
350             style : {  
351                 'text-align' :  this.textAlign,
352                 border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
353                 'border-collapse' : 'collapse',
354                 padding : '6px', // 8 for desktop / 4 for mobile
355                 'vertical-align': this.valign
356             },
357             html : this.html
358         };
359         if (this.width != '') {
360             ret.width = this.width;
361             ret.style.width = this.width;
362         }
363         
364         
365         if (this.colspan > 1) {
366             ret.colspan = this.colspan ;
367         } 
368         if (this.rowspan > 1) {
369             ret.rowspan = this.rowspan ;
370         }
371         
372            
373         
374         return ret;
375          
376     },
377     
378     readElement : function(node)
379     {
380         console.log("htmleditor.BlockTd readElement");
381         console.log(node);
382         node  = node ? node : this.node ;
383         this.width = node.style.width;
384         this.colspan = Math.max(1,1*node.getAttribute('colspan'));
385         this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
386         this.html = node.innerHTML;
387         
388         
389     },
390      
391     // the default cell object... at present...
392     emptyCell : function() {
393         return {
394             colspan :  1,
395             rowspan :  1,
396             textAlign : 'left',
397             html : "&nbsp;" // is this going to be editable now?
398         };
399      
400     },
401     
402     removeNode : function()
403     {
404         return this.node.closest('table');
405          
406     },
407     
408     cellData : false,
409     
410     colWidths : false,
411     
412     toTableArray  : function()
413     {
414         console.log("htmleditor.BlockTd toTableArray");
415         var ret = [];
416         var tab = this.node.closest('tr').closest('table');
417         Array.from(tab.rows).forEach(function(r, ri){
418             ret[ri] = [];
419         });
420         var rn = 0;
421         this.colWidths = [];
422         var all_auto = true;
423         Array.from(tab.rows).forEach(function(r, ri){
424             
425             var cn = 0;
426             Array.from(r.cells).forEach(function(ce, ci){
427                 var c =  {
428                     cell : ce,
429                     row : rn,
430                     col: cn,
431                     colspan : ce.colSpan,
432                     rowspan : ce.rowSpan
433                 };
434                 if (ce.isEqualNode(this.node)) {
435                     this.cellData = c;
436                 }
437                 // if we have been filled up by a row?
438                 if (typeof(ret[rn][cn]) != 'undefined') {
439                     while(typeof(ret[rn][cn]) != 'undefined') {
440                         cn++;
441                     }
442                     c.col = cn;
443                 }
444                 
445                 if (typeof(this.colWidths[cn]) == 'undefined') {
446                     this.colWidths[cn] =   ce.style.width;
447                     console.log("colWidths " + cn);
448                     console.log(ce.style.width);
449                     if (this.colWidths[cn] != '') {
450                         all_auto = false;
451                     }
452                 }
453                 
454                 
455                 if (c.colspan < 2 && c.rowspan < 2 ) {
456                     ret[rn][cn] = c;
457                     cn++;
458                     return;
459                 }
460                 for(var j = 0; j < c.rowspan; j++) {
461                     if (typeof(ret[rn+j]) == 'undefined') {
462                         continue; // we have a problem..
463                     }
464                     ret[rn+j][cn] = c;
465                     for(var i = 0; i < c.colspan; i++) {
466                         ret[rn+j][cn+i] = c;
467                     }
468                 }
469                 
470                 cn += c.colspan;
471             }, this);
472             rn++;
473         }, this);
474         
475         // initalize widths.?
476         // either all widths or no widths..
477         if (all_auto) {
478             this.colWidths[0] = false; // no widths flag.
479         }
480         
481         
482         return ret;
483         
484     },
485     
486     
487     
488     
489     mergeRight: function()
490     {
491         console.log("htmleditor.BlockTd mergeRight");
492          
493         // get the contents of the next cell along..
494         var tr = this.node.closest('tr');
495         var i = Array.prototype.indexOf.call(tr.childNodes, this.node);
496         if (i >= tr.childNodes.length - 1) {
497             return; // no cells on right to merge with.
498         }
499         var table = this.toTableArray();
500         
501         if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
502             return; // nothing right?
503         }
504         var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan];
505         // right cell - must be same rowspan and on the same row.
506         if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) {
507             return; // right hand side is not same rowspan.
508         }
509         
510         
511         
512         this.node.innerHTML += ' ' + rc.cell.innerHTML;
513         tr.removeChild(rc.cell);
514         this.colspan += rc.colspan;
515         this.node.setAttribute('colspan', this.colspan);
516
517     },
518     
519     
520     mergeBelow : function()
521     {
522         var table = this.toTableArray();
523         if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
524             return; // no row below
525         }
526         if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
527             return; // nothing right?
528         }
529         var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
530         
531         if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
532             return; // right hand side is not same rowspan.
533         }
534         this.node.innerHTML =  this.node.innerHTML + rc.cell.innerHTML ;
535         rc.cell.parentNode.removeChild(rc.cell);
536         this.rowspan += rc.rowspan;
537         this.node.setAttribute('rowspan', this.rowspan);
538     },
539     
540     split: function()
541     {
542         if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
543             return;
544         }
545         var table = this.toTableArray();
546         var cd = this.cellData;
547         this.rowspan = 1;
548         this.colspan = 1;
549         
550         for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
551             
552             
553             
554             for(var c = cd.col; c < cd.col + cd.colspan; c++) {
555                 if (r == cd.row && c == cd.col) {
556                     this.node.removeAttribute('rowspan');
557                     this.node.removeAttribute('colspan');
558                     continue;
559                 }
560                  
561                 var ntd = this.node.cloneNode(); // which col/row should be 0..
562                 ntd.removeAttribute('id'); //
563                 //ntd.style.width  = '';
564                 ntd.innerHTML = '';
565                 table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
566             }
567             
568         }
569         this.redrawAllCells(table);
570         
571          
572         
573     },
574     
575     
576     
577     redrawAllCells: function(table)
578     {
579         
580          
581         var tab = this.node.closest('tr').closest('table');
582         var ctr = tab.rows[0].parentNode;
583         Array.from(tab.rows).forEach(function(r, ri){
584             
585             Array.from(r.cells).forEach(function(ce, ci){
586                 ce.parentNode.removeChild(ce);
587             });
588             r.parentNode.removeChild(r);
589         });
590         for(var r = 0 ; r < table.length; r++) {
591             var re = tab.rows[r];
592             
593             var re = tab.ownerDocument.createElement('tr');
594             ctr.appendChild(re);
595             for(var c = 0 ; c < table[r].length; c++) {
596                 if (table[r][c].cell === false) {
597                     continue;
598                 }
599                 
600                 re.appendChild(table[r][c].cell);
601                  
602                 table[r][c].cell = false;
603             }
604         }
605         
606     },
607     updateWidths : function(table)
608     {
609         for(var r = 0 ; r < table.length; r++) {
610            
611             for(var c = 0 ; c < table[r].length; c++) {
612                 if (table[r][c].cell === false) {
613                     continue;
614                 }
615                 
616                 if (this.colWidths[0] != false && table[r][c].colspan < 2) {
617                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
618                     el.width = Math.floor(this.colWidths[c])  +'%';
619                     el.updateElement(el.node);
620                 }
621                 table[r][c].cell = false; // done
622             }
623         }
624     },
625     normalizeWidths : function(table)
626     {
627         console.log("htmleditor.BlockTd normalizeWidths");
628         console.log("colWidths 0");
629         console.log(this.colWidths[0]);
630         console.log("colWidths 1");
631         console.log(this.colWidths[1]);
632     
633         if (this.colWidths[0] === false) {
634             var nw = 100.0 / this.colWidths.length;
635             this.colWidths.forEach(function(w,i) {
636                 this.colWidths[i] = nw;
637             },this);
638             return;
639         }
640     
641         var t = 0, missing = [];
642         
643         this.colWidths.forEach(function(w,i) {
644             //if you mix % and
645             this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
646             var add =  this.colWidths[i];
647             if (add > 0) {
648                 t+=add;
649                 return;
650             }
651             missing.push(i);
652             
653             
654         },this);
655         var nc = this.colWidths.length;
656         if (missing.length) {
657             var mult = (nc - missing.length) / (1.0 * nc);
658             var t = mult * t;
659             var ew = (100 -t) / (1.0 * missing.length);
660             this.colWidths.forEach(function(w,i) {
661                 if (w > 0) {
662                     this.colWidths[i] = w * mult;
663                     return;
664                 }
665                 
666                 this.colWidths[i] = ew;
667             }, this);
668             // have to make up numbers..
669              
670         }
671         // now we should have all the widths..
672         
673     
674     },
675     
676     shrinkColumn : function()
677     {
678         console.log("htmleditor.BlockTd shrinkColumn");
679         var table = this.toTableArray();
680         console.log(table);
681         this.normalizeWidths(table);
682         var col = this.cellData.col;
683         var nw = this.colWidths[col] * 0.8;
684         if (nw < 5) {
685             return;
686         }
687         var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
688         this.colWidths.forEach(function(w,i) {
689             if (i == col) {
690                  this.colWidths[i] = nw;
691                 return;
692             }
693             this.colWidths[i] += otherAdd
694         }, this);
695         this.updateWidths(table);
696          
697     },
698     growColumn : function()
699     {
700         console.log("htmleditor.BlockTd growColumn");
701         var table = this.toTableArray();
702         console.log(table);
703         this.normalizeWidths(table);
704         var col = this.cellData.col;
705         var nw = this.colWidths[col] * 1.2;
706         if (nw > 90) {
707             return;
708         }
709         var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
710         this.colWidths.forEach(function(w,i) {
711             if (i == col) {
712                 this.colWidths[i] = nw;
713                 return;
714             }
715             this.colWidths[i] -= otherSub
716         }, this);
717         this.updateWidths(table);
718          
719     },
720     deleteRow : function()
721     {
722         // delete this rows 'tr'
723         // if any of the cells in this row have a rowspan > 1 && row!= this row..
724         // then reduce the rowspan.
725         var table = this.toTableArray();
726         // this.cellData.row;
727         for (var i =0;i< table[this.cellData.row].length ; i++) {
728             var c = table[this.cellData.row][i];
729             if (c.row != this.cellData.row) {
730                 
731                 c.rowspan--;
732                 c.cell.setAttribute('rowspan', c.rowspan);
733                 continue;
734             }
735             if (c.rowspan > 1) {
736                 c.rowspan--;
737                 c.cell.setAttribute('rowspan', c.rowspan);
738             }
739         }
740         table.splice(this.cellData.row,1);
741         this.redrawAllCells(table);
742         
743     },
744     deleteColumn : function()
745     {
746         var table = this.toTableArray();
747         
748         for (var i =0;i< table.length ; i++) {
749             var c = table[i][this.cellData.col];
750             if (c.col != this.cellData.col) {
751                 table[i][this.cellData.col].colspan--;
752             } else if (c.colspan > 1) {
753                 c.colspan--;
754                 c.cell.setAttribute('colspan', c.colspan);
755             }
756             table[i].splice(this.cellData.col,1);
757         }
758         
759         this.redrawAllCells(table);
760     }
761     
762     
763     
764     
765 })
766