38bceb2b32157ac2cb1c7dc0b9faaa7a3f630dff
[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         var ret = [];
415         var tab = this.node.closest('tr').closest('table');
416         Array.from(tab.rows).forEach(function(r, ri){
417             ret[ri] = [];
418         });
419         var rn = 0;
420         this.colWidths = [];
421         var all_auto = true;
422         Array.from(tab.rows).forEach(function(r, ri){
423             
424             var cn = 0;
425             Array.from(r.cells).forEach(function(ce, ci){
426                 var c =  {
427                     cell : ce,
428                     row : rn,
429                     col: cn,
430                     colspan : ce.colSpan,
431                     rowspan : ce.rowSpan
432                 };
433                 if (ce.isEqualNode(this.node)) {
434                     this.cellData = c;
435                 }
436                 // if we have been filled up by a row?
437                 if (typeof(ret[rn][cn]) != 'undefined') {
438                     while(typeof(ret[rn][cn]) != 'undefined') {
439                         cn++;
440                     }
441                     c.col = cn;
442                 }
443                 
444                 if (typeof(this.colWidths[cn]) == 'undefined') {
445                     this.colWidths[cn] =   ce.style.width;
446                     if (this.colWidths[cn] != '') {
447                         all_auto = false;
448                     }
449                 }
450                 
451                 
452                 if (c.colspan < 2 && c.rowspan < 2 ) {
453                     ret[rn][cn] = c;
454                     cn++;
455                     return;
456                 }
457                 for(var j = 0; j < c.rowspan; j++) {
458                     if (typeof(ret[rn+j]) == 'undefined') {
459                         continue; // we have a problem..
460                     }
461                     ret[rn+j][cn] = c;
462                     for(var i = 0; i < c.colspan; i++) {
463                         ret[rn+j][cn+i] = c;
464                     }
465                 }
466                 
467                 cn += c.colspan;
468             }, this);
469             rn++;
470         }, this);
471         
472         // initalize widths.?
473         // either all widths or no widths..
474         if (all_auto) {
475             this.colWidths[0] = false; // no widths flag.
476         }
477         
478         
479         return ret;
480         
481     },
482     
483     
484     
485     
486     mergeRight: function()
487     {
488          
489         // get the contents of the next cell along..
490         var tr = this.node.closest('tr');
491         var i = Array.prototype.indexOf.call(tr.childNodes, this.node);
492         if (i >= tr.childNodes.length - 1) {
493             return; // no cells on right to merge with.
494         }
495         var table = this.toTableArray();
496         
497         if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
498             return; // nothing right?
499         }
500         var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan];
501         // right cell - must be same rowspan and on the same row.
502         if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) {
503             return; // right hand side is not same rowspan.
504         }
505         
506         
507         
508         this.node.innerHTML += ' ' + rc.cell.innerHTML;
509         tr.removeChild(rc.cell);
510         this.colspan += rc.colspan;
511         this.node.setAttribute('colspan', this.colspan);
512
513     },
514     
515     
516     mergeBelow : function()
517     {
518         var table = this.toTableArray();
519         if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
520             return; // no row below
521         }
522         if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
523             return; // nothing right?
524         }
525         var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
526         
527         if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
528             return; // right hand side is not same rowspan.
529         }
530         this.node.innerHTML =  this.node.innerHTML + rc.cell.innerHTML ;
531         rc.cell.parentNode.removeChild(rc.cell);
532         this.rowspan += rc.rowspan;
533         this.node.setAttribute('rowspan', this.rowspan);
534     },
535     
536     split: function()
537     {
538         if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
539             return;
540         }
541         var table = this.toTableArray();
542         var cd = this.cellData;
543         this.rowspan = 1;
544         this.colspan = 1;
545         
546         for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
547             
548             
549             
550             for(var c = cd.col; c < cd.col + cd.colspan; c++) {
551                 if (r == cd.row && c == cd.col) {
552                     this.node.removeAttribute('rowspan');
553                     this.node.removeAttribute('colspan');
554                     continue;
555                 }
556                  
557                 var ntd = this.node.cloneNode(); // which col/row should be 0..
558                 ntd.removeAttribute('id'); //
559                 //ntd.style.width  = '';
560                 ntd.innerHTML = '';
561                 table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
562             }
563             
564         }
565         this.redrawAllCells(table);
566         
567          
568         
569     },
570     
571     
572     
573     redrawAllCells: function(table)
574     {
575         
576          
577         var tab = this.node.closest('tr').closest('table');
578         var ctr = tab.rows[0].parentNode;
579         Array.from(tab.rows).forEach(function(r, ri){
580             
581             Array.from(r.cells).forEach(function(ce, ci){
582                 ce.parentNode.removeChild(ce);
583             });
584             r.parentNode.removeChild(r);
585         });
586         for(var r = 0 ; r < table.length; r++) {
587             var re = tab.rows[r];
588             
589             var re = tab.ownerDocument.createElement('tr');
590             ctr.appendChild(re);
591             for(var c = 0 ; c < table[r].length; c++) {
592                 if (table[r][c].cell === false) {
593                     continue;
594                 }
595                 
596                 re.appendChild(table[r][c].cell);
597                  
598                 table[r][c].cell = false;
599             }
600         }
601         
602     },
603     updateWidths : function(table)
604     {
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                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
614                     el.width = Math.floor(this.colWidths[c])  +'%';
615                     el.updateElement(el.node);
616                 }
617                 table[r][c].cell = false; // done
618             }
619         }
620     },
621     normalizeWidths : function(table)
622     {
623     
624         if (this.colWidths[0] === false) {
625             var nw = 100.0 / this.colWidths.length;
626             this.colWidths.forEach(function(w,i) {
627                 this.colWidths[i] = nw;
628             },this);
629             return;
630         }
631     
632         var t = 0, missing = [];
633         
634         this.colWidths.forEach(function(w,i) {
635             //if you mix % and
636             this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
637             var add =  this.colWidths[i];
638             if (add > 0) {
639                 t+=add;
640                 return;
641             }
642             missing.push(i);
643             
644             
645         },this);
646         var nc = this.colWidths.length;
647         if (missing.length) {
648             var mult = (nc - missing.length) / (1.0 * nc);
649             var t = mult * t;
650             var ew = (100 -t) / (1.0 * missing.length);
651             this.colWidths.forEach(function(w,i) {
652                 if (w > 0) {
653                     this.colWidths[i] = w * mult;
654                     return;
655                 }
656                 
657                 this.colWidths[i] = ew;
658             }, this);
659             // have to make up numbers..
660              
661         }
662         // now we should have all the widths..
663         
664     
665     },
666     
667     shrinkColumn : function()
668     {
669         var table = this.toTableArray();
670         this.normalizeWidths(table);
671         var col = this.cellData.col;
672         var nw = this.colWidths[col] * 0.8;
673         if (nw < 5) {
674             return;
675         }
676         var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
677         this.colWidths.forEach(function(w,i) {
678             if (i == col) {
679                  this.colWidths[i] = nw;
680                 return;
681             }
682             this.colWidths[i] += otherAdd
683         }, this);
684         this.updateWidths(table);
685          
686     },
687     growColumn : function()
688     {
689         var table = this.toTableArray();
690         this.normalizeWidths(table);
691         var col = this.cellData.col;
692         var nw = this.colWidths[col] * 1.2;
693         if (nw > 90) {
694             return;
695         }
696         var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
697         this.colWidths.forEach(function(w,i) {
698             if (i == col) {
699                 this.colWidths[i] = nw;
700                 return;
701             }
702             this.colWidths[i] -= otherSub
703         }, this);
704         this.updateWidths(table);
705          
706     },
707     deleteRow : function()
708     {
709         // delete this rows 'tr'
710         // if any of the cells in this row have a rowspan > 1 && row!= this row..
711         // then reduce the rowspan.
712         var table = this.toTableArray();
713         // this.cellData.row;
714         for (var i =0;i< table[this.cellData.row].length ; i++) {
715             var c = table[this.cellData.row][i];
716             if (c.row != this.cellData.row) {
717                 
718                 c.rowspan--;
719                 c.cell.setAttribute('rowspan', c.rowspan);
720                 continue;
721             }
722             if (c.rowspan > 1) {
723                 c.rowspan--;
724                 c.cell.setAttribute('rowspan', c.rowspan);
725             }
726         }
727         table.splice(this.cellData.row,1);
728         this.redrawAllCells(table);
729         
730     },
731     deleteColumn : function()
732     {
733         var table = this.toTableArray();
734         
735         for (var i =0;i< table.length ; i++) {
736             var c = table[i][this.cellData.col];
737             if (c.col != this.cellData.col) {
738                 table[i][this.cellData.col].colspan--;
739             } else if (c.colspan > 1) {
740                 c.colspan--;
741                 c.cell.setAttribute('colspan', c.colspan);
742             }
743             table[i].splice(this.cellData.col,1);
744         }
745         
746         this.redrawAllCells(table);
747     }
748     
749     
750     
751     
752 })
753