718cd775bc9f0d9f4773dd131e9e471ec9144684
[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     },
510     
511     
512     mergeBelow : function()
513     {
514         var table = this.toTableArray();
515         if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
516             return; // no row below
517         }
518         if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
519             return; // nothing right?
520         }
521         var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
522         
523         if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
524             return; // right hand side is not same rowspan.
525         }
526         this.node.innerHTML =  this.node.innerHTML + rc.cell.innerHTML ;
527         rc.cell.parentNode.removeChild(rc.cell);
528         this.rowspan += rc.rowspan;
529         this.node.setAttribute('rowspan', this.rowspan);
530     },
531     
532     split: function()
533     {
534         if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
535             return;
536         }
537         var table = this.toTableArray();
538         var cd = this.cellData;
539         this.rowspan = 1;
540         this.colspan = 1;
541         
542         for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
543             
544             
545             
546             for(var c = cd.col; c < cd.col + cd.colspan; c++) {
547                 if (r == cd.row && c == cd.col) {
548                     this.node.removeAttribute('rowspan');
549                     this.node.removeAttribute('colspan');
550                     continue;
551                 }
552                  
553                 var ntd = this.node.cloneNode(); // which col/row should be 0..
554                 ntd.removeAttribute('id'); //
555                 //ntd.style.width  = '';
556                 ntd.innerHTML = '';
557                 table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
558             }
559             
560         }
561         this.redrawAllCells(table);
562         
563          
564         
565     },
566     
567     
568     
569     redrawAllCells: function(table)
570     {
571         
572          
573         var tab = this.node.closest('tr').closest('table');
574         var ctr = tab.rows[0].parentNode;
575         Array.from(tab.rows).forEach(function(r, ri){
576             
577             Array.from(r.cells).forEach(function(ce, ci){
578                 ce.parentNode.removeChild(ce);
579             });
580             r.parentNode.removeChild(r);
581         });
582         for(var r = 0 ; r < table.length; r++) {
583             var re = tab.rows[r];
584             
585             var re = tab.ownerDocument.createElement('tr');
586             ctr.appendChild(re);
587             for(var c = 0 ; c < table[r].length; c++) {
588                 if (table[r][c].cell === false) {
589                     continue;
590                 }
591                 
592                 re.appendChild(table[r][c].cell);
593                  
594                 table[r][c].cell = false;
595             }
596         }
597         
598     },
599     updateWidths : function(table)
600     {
601         console.log("htmleditor.BlockTd updateWidths");
602         console.log("TABLE");
603         console.log(table);
604         for(var r = 0 ; r < table.length; r++) {
605            
606             for(var c = 0 ; c < table[r].length; c++) {
607                 if (table[r][c].cell === false) {
608                     continue;
609                 }
610                 
611                 if (this.colWidths[0] != false && table[r][c].colspan < 2) {
612                     console.log("CELL");
613                     console.log(table[r][c].cell);
614                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
615                     el.width = Math.floor(this.colWidths[c])  +'%';
616                     el.updateElement(el.node);
617                 }
618                 table[r][c].cell = false; // done
619             }
620         }
621     },
622     normalizeWidths : function(table)
623     {
624     
625         if (this.colWidths[0] === false) {
626             var nw = 100.0 / this.colWidths.length;
627             this.colWidths.forEach(function(w,i) {
628                 this.colWidths[i] = nw;
629             },this);
630             return;
631         }
632     
633         var t = 0, missing = [];
634         
635         this.colWidths.forEach(function(w,i) {
636             //if you mix % and
637             this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
638             var add =  this.colWidths[i];
639             if (add > 0) {
640                 t+=add;
641                 return;
642             }
643             missing.push(i);
644             
645             
646         },this);
647         var nc = this.colWidths.length;
648         if (missing.length) {
649             var mult = (nc - missing.length) / (1.0 * nc);
650             var t = mult * t;
651             var ew = (100 -t) / (1.0 * missing.length);
652             this.colWidths.forEach(function(w,i) {
653                 if (w > 0) {
654                     this.colWidths[i] = w * mult;
655                     return;
656                 }
657                 
658                 this.colWidths[i] = ew;
659             }, this);
660             // have to make up numbers..
661              
662         }
663         // now we should have all the widths..
664         
665     
666     },
667     
668     shrinkColumn : function()
669     {
670         var table = this.toTableArray();
671         this.normalizeWidths(table);
672         var col = this.cellData.col;
673         var nw = this.colWidths[col] * 0.8;
674         if (nw < 5) {
675             return;
676         }
677         var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
678         this.colWidths.forEach(function(w,i) {
679             if (i == col) {
680                  this.colWidths[i] = nw;
681                 return;
682             }
683             this.colWidths[i] += otherAdd
684         }, this);
685         this.updateWidths(table);
686          
687     },
688     growColumn : function()
689     {
690         var table = this.toTableArray();
691         this.normalizeWidths(table);
692         var col = this.cellData.col;
693         var nw = this.colWidths[col] * 1.2;
694         if (nw > 90) {
695             return;
696         }
697         var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
698         this.colWidths.forEach(function(w,i) {
699             if (i == col) {
700                 this.colWidths[i] = nw;
701                 return;
702             }
703             this.colWidths[i] -= otherSub
704         }, this);
705         this.updateWidths(table);
706          
707     },
708     deleteRow : function()
709     {
710         // delete this rows 'tr'
711         // if any of the cells in this row have a rowspan > 1 && row!= this row..
712         // then reduce the rowspan.
713         var table = this.toTableArray();
714         // this.cellData.row;
715         for (var i =0;i< table[this.cellData.row].length ; i++) {
716             var c = table[this.cellData.row][i];
717             if (c.row != this.cellData.row) {
718                 
719                 c.rowspan--;
720                 c.cell.setAttribute('rowspan', c.rowspan);
721                 continue;
722             }
723             if (c.rowspan > 1) {
724                 c.rowspan--;
725                 c.cell.setAttribute('rowspan', c.rowspan);
726             }
727         }
728         table.splice(this.cellData.row,1);
729         this.redrawAllCells(table);
730         
731     },
732     deleteColumn : function()
733     {
734         var table = this.toTableArray();
735         
736         for (var i =0;i< table.length ; i++) {
737             var c = table[i][this.cellData.col];
738             if (c.col != this.cellData.col) {
739                 table[i][this.cellData.col].colspan--;
740             } else if (c.colspan > 1) {
741                 c.colspan--;
742                 c.cell.setAttribute('colspan', c.colspan);
743             }
744             table[i].splice(this.cellData.col,1);
745         }
746         
747         this.redrawAllCells(table);
748     }
749     
750     
751     
752     
753 })
754