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