MsoParagraphList is sometimes used when there is no list.
[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         
343         var ret = {
344             tag : 'td',
345             contenteditable : 'true', // this stops cell selection from picking the table.
346             'data-block' : 'Td',
347             valign : this.valign,
348             style : {  
349                 'text-align' :  this.textAlign,
350                 border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
351                 'border-collapse' : 'collapse',
352                 padding : '6px', // 8 for desktop / 4 for mobile
353                 'vertical-align': this.valign
354             },
355             html : this.html
356         };
357         if (this.width != '') {
358             ret.width = this.width;
359             ret.style.width = this.width;
360         }
361         
362         
363         if (this.colspan > 1) {
364             ret.colspan = this.colspan ;
365         } 
366         if (this.rowspan > 1) {
367             ret.rowspan = this.rowspan ;
368         }
369         
370            
371         
372         return ret;
373          
374     },
375     
376     readElement : function(node)
377     {
378         node  = node ? node : this.node ;
379         this.width = node.style.width;
380         this.colspan = Math.max(1,1*node.getAttribute('colspan'));
381         this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
382         this.html = node.innerHTML;
383         
384         
385     },
386      
387     // the default cell object... at present...
388     emptyCell : function() {
389         return {
390             colspan :  1,
391             rowspan :  1,
392             textAlign : 'left',
393             html : "&nbsp;" // is this going to be editable now?
394         };
395      
396     },
397     
398     removeNode : function()
399     {
400         return this.node.closest('table');
401          
402     },
403     
404     cellData : false,
405     
406     colWidths : false,
407     
408     toTableArray  : function()
409     {
410         var ret = [];
411         var tab = this.node.closest('tr').closest('table');
412         Array.from(tab.rows).forEach(function(r, ri){
413             ret[ri] = [];
414         });
415         var rn = 0;
416         this.colWidths = [];
417         var all_auto = true;
418         Array.from(tab.rows).forEach(function(r, ri){
419             
420             var cn = 0;
421             Array.from(r.cells).forEach(function(ce, ci){
422                 var c =  {
423                     cell : ce,
424                     row : rn,
425                     col: cn,
426                     colspan : ce.colSpan,
427                     rowspan : ce.rowSpan
428                 };
429                 if (ce.isEqualNode(this.node)) {
430                     this.cellData = c;
431                 }
432                 // if we have been filled up by a row?
433                 if (typeof(ret[rn][cn]) != 'undefined') {
434                     while(typeof(ret[rn][cn]) != 'undefined') {
435                         cn++;
436                     }
437                     c.col = cn;
438                 }
439                 
440                 if (typeof(this.colWidths[cn]) == 'undefined') {
441                     this.colWidths[cn] =   ce.style.width;
442                     if (this.colWidths[cn] != '') {
443                         all_auto = false;
444                     }
445                 }
446                 
447                 
448                 if (c.colspan < 2 && c.rowspan < 2 ) {
449                     ret[rn][cn] = c;
450                     cn++;
451                     return;
452                 }
453                 for(var j = 0; j < c.rowspan; j++) {
454                     if (typeof(ret[rn+j]) == 'undefined') {
455                         continue; // we have a problem..
456                     }
457                     ret[rn+j][cn] = c;
458                     for(var i = 0; i < c.colspan; i++) {
459                         ret[rn+j][cn+i] = c;
460                     }
461                 }
462                 
463                 cn += c.colspan;
464             }, this);
465             rn++;
466         }, this);
467         
468         // initalize widths.?
469         // either all widths or no widths..
470         if (all_auto) {
471             this.colWidths[0] = false; // no widths flag.
472         }
473         
474         
475         return ret;
476         
477     },
478     
479     
480     
481     
482     mergeRight: function()
483     {
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             for(var c = cd.col; c < cd.col + cd.colspan; c++) {
546                 if (r == cd.row && c == cd.col) {
547                     this.node.removeAttribute('rowspan');
548                     this.node.removeAttribute('colspan');
549                     continue;
550                 }
551                  
552                 var ntd = this.node.cloneNode(); // which col/row should be 0..
553                 ntd.removeAttribute('id'); //
554                 //ntd.style.width  = '';
555                 ntd.innerHTML = '';
556                 table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
557             }
558             
559         }
560         this.redrawAllCells(table);
561         
562          
563         
564     },
565     
566     
567     
568     redrawAllCells: function(table)
569     {
570         
571          
572         var tab = this.node.closest('tr').closest('table');
573         var ctr = tab.rows[0].parentNode;
574         Array.from(tab.rows).forEach(function(r, ri){
575             
576             Array.from(r.cells).forEach(function(ce, ci){
577                 ce.parentNode.removeChild(ce);
578             });
579             r.parentNode.removeChild(r);
580         });
581         for(var r = 0 ; r < table.length; r++) {
582             var re = tab.rows[r];
583             
584             var re = tab.ownerDocument.createElement('tr');
585             ctr.appendChild(re);
586             for(var c = 0 ; c < table[r].length; c++) {
587                 if (table[r][c].cell === false) {
588                     continue;
589                 }
590                 
591                 re.appendChild(table[r][c].cell);
592                  
593                 table[r][c].cell = false;
594             }
595         }
596         
597     },
598     updateWidths : function(table)
599     {
600         for(var r = 0 ; r < table.length; r++) {
601            
602             for(var c = 0 ; c < table[r].length; c++) {
603                 if (table[r][c].cell === false) {
604                     continue;
605                 }
606                 
607                 if (this.colWidths[0] != false && table[r][c].colspan < 2) {
608                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
609                     el.width = Math.floor(this.colWidths[c])  +'%';
610                     el.updateElement(el.node);
611                 }
612                 table[r][c].cell = false; // done
613             }
614         }
615     },
616     normalizeWidths : function(table)
617     {
618     
619         if (this.colWidths[0] === false) {
620             var nw = 100.0 / this.colWidths.length;
621             this.colWidths.forEach(function(w,i) {
622                 this.colWidths[i] = nw;
623             },this);
624             return;
625         }
626     
627         var t = 0, missing = [];
628         
629         this.colWidths.forEach(function(w,i) {
630             //if you mix % and
631             this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
632             var add =  this.colWidths[i];
633             if (add > 0) {
634                 t+=add;
635                 return;
636             }
637             missing.push(i);
638             
639             
640         },this);
641         var nc = this.colWidths.length;
642         if (missing.length) {
643             var mult = (nc - missing.length) / (1.0 * nc);
644             var t = mult * t;
645             var ew = (100 -t) / (1.0 * missing.length);
646             this.colWidths.forEach(function(w,i) {
647                 if (w > 0) {
648                     this.colWidths[i] = w * mult;
649                     return;
650                 }
651                 
652                 this.colWidths[i] = ew;
653             }, this);
654             // have to make up numbers..
655              
656         }
657         // now we should have all the widths..
658         
659     
660     },
661     
662     shrinkColumn : function()
663     {
664         var table = this.toTableArray();
665         this.normalizeWidths(table);
666         var col = this.cellData.col;
667         var nw = this.colWidths[col] * 0.8;
668         if (nw < 5) {
669             return;
670         }
671         var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
672         this.colWidths.forEach(function(w,i) {
673             if (i == col) {
674                  this.colWidths[i] = nw;
675                 return;
676             }
677             this.colWidths[i] += otherAdd
678         }, this);
679         this.updateWidths(table);
680          
681     },
682     growColumn : function()
683     {
684         var table = this.toTableArray();
685         this.normalizeWidths(table);
686         var col = this.cellData.col;
687         var nw = this.colWidths[col] * 1.2;
688         if (nw > 90) {
689             return;
690         }
691         var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
692         this.colWidths.forEach(function(w,i) {
693             if (i == col) {
694                 this.colWidths[i] = nw;
695                 return;
696             }
697             this.colWidths[i] -= otherSub
698         }, this);
699         this.updateWidths(table);
700          
701     },
702     deleteRow : function()
703     {
704         // delete this rows 'tr'
705         // if any of the cells in this row have a rowspan > 1 && row!= this row..
706         // then reduce the rowspan.
707         var table = this.toTableArray();
708         // this.cellData.row;
709         for (var i =0;i< table[this.cellData.row].length ; i++) {
710             var c = table[this.cellData.row][i];
711             if (c.row != this.cellData.row) {
712                 
713                 c.rowspan--;
714                 c.cell.setAttribute('rowspan', c.rowspan);
715                 continue;
716             }
717             if (c.rowspan > 1) {
718                 c.rowspan--;
719                 c.cell.setAttribute('rowspan', c.rowspan);
720             }
721         }
722         table.splice(this.cellData.row,1);
723         this.redrawAllCells(table);
724         
725     },
726     deleteColumn : function()
727     {
728         var table = this.toTableArray();
729         
730         for (var i =0;i< table.length ; i++) {
731             var c = table[i][this.cellData.col];
732             if (c.col != this.cellData.col) {
733                 table[i][this.cellData.col].colspan--;
734             } else if (c.colspan > 1) {
735                 c.colspan--;
736                 c.cell.setAttribute('colspan', c.colspan);
737             }
738             table[i].splice(this.cellData.col,1);
739         }
740         
741         this.redrawAllCells(table);
742     }
743     
744     
745     
746     
747 })
748