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