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