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