Changeset 1064
- Timestamp:
- 10/06/08 16:05:33 (11 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/plugins/TableOperations/table-operations.js
r1044 r1064 15 15 // Object that will encapsulate all the table operations provided by 16 16 // HTMLArea-3.0 (except "insert table" which is included in the main file) 17 Xinha.Config.prototype.TableOperations = 18 { 17 Xinha.Config.prototype.TableOperations = { 19 18 'showButtons' : true // Set to false to hide all but inserttable and toggleborders buttons on the toolbar 20 21 19 // this is useful if you have the ContextMenu plugin and want to save toolbar space 20 // (the context menu can perform all the button operations) 22 21 } 23 22 24 23 function TableOperations(editor) { 25 26 27 28 29 30 31 24 this.editor = editor; 25 26 var cfg = editor.config; 27 var bl = TableOperations.btnList; 28 var self = this; 29 30 // register the toolbar buttons provided by this plugin 32 31 33 32 // Remove existing inserttable and toggleborders, we will replace it in our group 34 33 cfg.removeToolbarElement(' inserttable toggleborders '); 35 34 36 37 38 39 40 41 42 if(cfg.TableOperations.showButtons)toolbar.push("separator");43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 35 var toolbar = ["linebreak", "inserttable", "toggleborders"]; 36 37 38 for (var i = 0; i < bl.length; ++i) { 39 var btn = bl[i]; 40 if (!btn) { 41 if(cfg.TableOperations.showButtons) toolbar.push("separator"); 42 } else { 43 var id = "TO-" + btn[0]; 44 cfg.registerButton(id, Xinha._lc(btn[2], "TableOperations"), editor.imgURL(btn[0] + ".gif", "TableOperations"), false, 45 function(editor, id) { 46 // dispatch button press event 47 self.buttonPress(editor, id); 48 }, btn[1]); 49 if(cfg.TableOperations.showButtons) toolbar.push(id); 50 } 51 } 52 53 54 // add a new line in the toolbar 55 cfg.toolbar.push(toolbar); 56 57 if ( typeof PopupWin == 'undefined' ) 59 58 { 60 61 } 62 59 Xinha._loadback(_editor_url + 'modules/Dialogs/popupwin.js'); 60 } 61 if ( typeof Xinha.InlineStyler == 'undefined' ) 63 62 { 64 65 } 66 63 Xinha._loadback(_editor_url + 'modules/InlineStyler/InlineStyler.js'); 64 } 65 67 66 } 68 67 69 68 TableOperations._pluginInfo = { 70 71 72 73 74 75 76 77 69 name : "TableOperations", 70 version : "1.0", 71 developer : "Mihai Bazon", 72 developer_url : "http://dynarch.com/mishoo/", 73 c_owner : "Mihai Bazon", 74 sponsor : "Zapatec Inc.", 75 sponsor_url : "http://www.bloki.com", 76 license : "htmlArea" 78 77 }; 79 78 80 79 TableOperations.prototype._lc = function(string) { 81 80 return Xinha._lc(string, 'TableOperations'); 82 81 }; 83 82 … … 89 88 // ancestors of the current selection/caret. 90 89 TableOperations.prototype.getClosest = function(tagName) { 91 92 93 94 95 96 97 98 99 100 101 102 90 var editor = this.editor; 91 var ancestors = editor.getAllAncestors(); 92 var ret = null; 93 tagName = ("" + tagName).toLowerCase(); 94 for (var i = 0; i < ancestors.length; ++i) { 95 var el = ancestors[i]; 96 if (el.tagName.toLowerCase() == tagName) { 97 ret = el; 98 break; 99 } 100 } 101 return ret; 103 102 }; 104 103 … … 106 105 // was pressed. 107 106 TableOperations.prototype.buttonPress = function(editor, button_id) { 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 var tr = rows[i]; 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 var cell_index = td.cellIndex; 370 371 372 373 374 375 376 377 378 379 tr2 = td2.parentNode; 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 107 this.editor = editor; 108 var mozbr = Xinha.is_gecko ? "<br />" : ""; 109 110 // helper function that clears the content in a table row 111 function clearRow(tr) { 112 var tds = tr.getElementsByTagName("td"); 113 for (var i = tds.length; --i >= 0;) { 114 var td = tds[i]; 115 td.rowSpan = 1; 116 td.innerHTML = mozbr; 117 } 118 } 119 120 function splitRow(td) { 121 var n = parseInt("" + td.rowSpan); 122 var nc = parseInt("" + td.colSpan); 123 td.rowSpan = 1; 124 tr = td.parentNode; 125 var itr = tr.rowIndex; 126 var trs = tr.parentNode.rows; 127 var index = td.cellIndex; 128 while (--n > 0) { 129 tr = trs[++itr]; 130 var otd = editor._doc.createElement("td"); 131 otd.colSpan = td.colSpan; 132 otd.innerHTML = mozbr; 133 tr.insertBefore(otd, tr.cells[index]); 134 } 135 editor.forceRedraw(); 136 editor.updateToolbar(); 137 } 138 139 function splitCol(td) { 140 var nc = parseInt("" + td.colSpan); 141 td.colSpan = 1; 142 tr = td.parentNode; 143 var ref = td.nextSibling; 144 while (--nc > 0) { 145 var otd = editor._doc.createElement("td"); 146 otd.rowSpan = td.rowSpan; 147 otd.innerHTML = mozbr; 148 tr.insertBefore(otd, ref); 149 } 150 editor.forceRedraw(); 151 editor.updateToolbar(); 152 } 153 154 function splitCell(td) { 155 var nc = parseInt("" + td.colSpan); 156 splitCol(td); 157 var items = td.parentNode.cells; 158 var index = td.cellIndex; 159 while (nc-- > 0) { 160 splitRow(items[index++]); 161 } 162 } 163 164 function selectNextNode(el) { 165 var node = el.nextSibling; 166 while (node && node.nodeType != 1) { 167 node = node.nextSibling; 168 } 169 if (!node) { 170 node = el.previousSibling; 171 while (node && node.nodeType != 1) { 172 node = node.previousSibling; 173 } 174 } 175 if (!node) { 176 node = el.parentNode; 177 } 178 editor.selectNodeContents(node); 179 } 180 181 function cellMerge(table, cell_index, row_index, no_cols, no_rows) { 182 var rows = []; 183 var cells = []; 184 try { 185 for (i=row_index; i<row_index+no_rows; i++) { 186 var row = table.rows[i]; 187 for (j=cell_index; j<cell_index+no_cols; j++) { 188 if (row.cells[j].colSpan > 1 || row.cells[j].rowSpan > 1) { 189 splitCell(row.cells[j]); 190 } 191 cells.push(row.cells[j]); 192 } 193 if (cells.length > 0) { 194 rows.push(cells); 195 cells = []; 196 } 197 } 198 } catch(e) { 199 alert("Invalid selection"); 200 return false; 201 } 202 var row_index1 = rows[0][0].parentNode.rowIndex; 203 var row_index2 = rows[rows.length-1][0].parentNode.rowIndex; 204 var row_span2 = rows[rows.length-1][0].rowSpan; 205 var HTML = ""; 206 for (i = 0; i < rows.length; ++i) { 207 var cells = rows[i]; 208 for (var j = 0; j < cells.length; ++j) { 209 var cell = cells[j]; 210 HTML += cell.innerHTML; 211 (i || j) && (cell.parentNode.removeChild(cell)); 212 } 213 } 214 var td = rows[0][0]; 215 td.innerHTML = HTML; 216 td.rowSpan = row_index2 - row_index1 + row_span2; 217 var col_span = 0; 218 for(j=0; j<rows[0].length; j++) { 219 col_span += rows[0][j].colSpan; 220 } 221 td.colSpan = col_span; 222 editor.selectNodeContents(td); 223 editor.forceRedraw(); 224 editor.focusEditor(); 225 } 226 227 switch (button_id) { 228 // ROWS 229 230 case "TO-row-insert-above": 231 case "TO-row-insert-under": 232 var tr = this.getClosest("tr"); 233 if (!tr) { 234 break; 235 } 236 var otr = tr.cloneNode(true); 237 clearRow(otr); 238 tr.parentNode.insertBefore(otr, /under/.test(button_id) ? tr.nextSibling : tr); 239 editor.forceRedraw(); 240 editor.focusEditor(); 241 break; 242 case "TO-row-delete": 243 var tr = this.getClosest("tr"); 244 if (!tr) { 245 break; 246 } 247 var par = tr.parentNode; 248 if (par.rows.length == 1) { 249 alert(Xinha._lc("Xinha cowardly refuses to delete the last row in table.", "TableOperations")); 250 break; 251 } 252 // set the caret first to a position that doesn't 253 // disappear. 254 selectNextNode(tr); 255 par.removeChild(tr); 256 editor.forceRedraw(); 257 editor.focusEditor(); 258 editor.updateToolbar(); 259 break; 260 case "TO-row-split": 261 var td = this.getClosest("td"); 262 if (!td) { 263 break; 264 } 265 splitRow(td); 266 break; 267 268 // COLUMNS 269 270 case "TO-col-insert-before": 271 case "TO-col-insert-after": 272 var td = this.getClosest("td"); 273 if (!td) { 274 break; 275 } 276 var rows = td.parentNode.parentNode.rows; 277 var index = td.cellIndex; 278 var lastColumn = (td.parentNode.cells.length == index + 1); 279 for (var i = rows.length; --i >= 0;) { 280 var tr = rows[i]; 281 var otd = editor._doc.createElement("td"); 282 otd.innerHTML = mozbr; 283 if (lastColumn && Xinha.is_ie) 284 { 285 tr.insertBefore(otd); 286 } 287 else 288 { 289 var ref = tr.cells[index + (/after/.test(button_id) ? 1 : 0)]; 290 tr.insertBefore(otd, ref); 291 } 292 } 293 editor.focusEditor(); 294 break; 295 case "TO-col-split": 296 var td = this.getClosest("td"); 297 if (!td) { 298 break; 299 } 300 splitCol(td); 301 break; 302 case "TO-col-delete": 303 var td = this.getClosest("td"); 304 if (!td) { 305 break; 306 } 307 var index = td.cellIndex; 308 if (td.parentNode.cells.length == 1) { 309 alert(Xinha._lc("Xinha cowardly refuses to delete the last column in table.", "TableOperations")); 310 break; 311 } 312 // set the caret first to a position that doesn't disappear 313 selectNextNode(td); 314 var rows = td.parentNode.parentNode.rows; 315 for (var i = rows.length; --i >= 0;) { 316 var tr = rows[i]; 317 tr.removeChild(tr.cells[index]); 318 } 319 editor.forceRedraw(); 320 editor.focusEditor(); 321 editor.updateToolbar(); 322 break; 323 324 // CELLS 325 326 case "TO-cell-split": 327 var td = this.getClosest("td"); 328 if (!td) { 329 break; 330 } 331 splitCell(td); 332 break; 333 case "TO-cell-insert-before": 334 case "TO-cell-insert-after": 335 var td = this.getClosest("td"); 336 if (!td) { 337 break; 338 } 339 var tr = td.parentNode; 340 var otd = editor._doc.createElement("td"); 341 otd.innerHTML = mozbr; 342 tr.insertBefore(otd, /after/.test(button_id) ? td.nextSibling : td); 343 editor.forceRedraw(); 344 editor.focusEditor(); 345 break; 346 case "TO-cell-delete": 347 var td = this.getClosest("td"); 348 if (!td) { 349 break; 350 } 351 if (td.parentNode.cells.length == 1) { 352 alert(Xinha._lc("Xinha cowardly refuses to delete the last cell in row.", "TableOperations")); 353 break; 354 } 355 // set the caret first to a position that doesn't disappear 356 selectNextNode(td); 357 td.parentNode.removeChild(td); 358 editor.forceRedraw(); 359 editor.updateToolbar(); 360 break; 361 case "TO-cell-merge": 362 //Mozilla, as opposed to IE, allows the selection of several cells, which is fine :) 363 var sel = editor._getSelection(); 364 if (!Xinha.is_ie && sel.rangeCount > 1) { 365 var range = sel.getRangeAt(0); 366 var td = range.startContainer.childNodes[range.startOffset]; 367 var tr = td.parentNode; 368 var cell_index = td.cellIndex; 369 var row_index = tr.rowIndex; 370 var row_index2 = 0; 371 var rownum = row_index; 372 var no_cols = 0; 373 var row_colspan = 0; 374 var td2, tr2; 375 for(i=0; i<sel.rangeCount; i++) { 376 range = sel.getRangeAt(i); 377 td2 = range.startContainer.childNodes[range.startOffset]; 378 tr2 = td2.parentNode; 379 if(tr2.rowIndex != rownum) { 380 rownum = tr2.rowIndex; 381 row_colspan = 0; 382 } 383 row_colspan += td2.colSpan; 384 if(row_colspan > no_cols) { 385 no_cols = row_colspan; 386 } 387 if(tr2.rowIndex + td2.rowSpan - 1 > row_index2) { 388 row_index2 = tr2.rowIndex + td2.rowSpan - 1; 389 } 390 } 391 var no_rows = row_index2 - row_index + 1; 392 var table = tr.parentNode; 393 cellMerge(table, cell_index, row_index, no_cols, no_rows); 394 } else { 395 // Internet Explorer "browser" or not more than one cell selected in Moz 396 var td = this.getClosest("td"); 397 if (!td) { 398 alert(Xinha._lc("Please click into some cell", "TableOperations")); 399 break; 400 } 401 var tr = td.parentNode; 402 var cell_index = td.cellIndex; 403 var row_index = tr.rowIndex; 404 // pass cellMerge and the indices so apply() can call cellMerge and know 405 // what cell was selected when the dialog was opened 406 this.dialogMerge(cellMerge, cell_index, row_index); 407 } 408 break; 409 410 // PROPERTIES 411 412 case "TO-table-prop": 413 this.dialogTableProperties(); 414 break; 415 416 case "TO-row-prop": 417 this.dialogRowCellProperties(false); 418 break; 419 420 case "TO-cell-prop": 421 this.dialogRowCellProperties(true); 422 break; 423 424 default: 425 alert("Button [" + button_id + "] not yet implemented"); 426 } 428 427 }; 429 428 430 429 // the list of buttons added by this plugin 431 430 TableOperations.btnList = [ 432 433 434 null,// separator435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 431 // table properties button 432 ["table-prop", "table", "Table properties"], 433 null, // separator 434 435 // ROWS 436 ["row-prop", "tr", "Row properties"], 437 ["row-insert-above", "tr", "Insert row before"], 438 ["row-insert-under", "tr", "Insert row after"], 439 ["row-delete", "tr", "Delete row"], 440 ["row-split", "td[rowSpan!=1]", "Split row"], 441 null, 442 443 // COLS 444 ["col-insert-before", "td", "Insert column before"], 445 ["col-insert-after", "td", "Insert column after"], 446 ["col-delete", "td", "Delete column"], 447 ["col-split", "td[colSpan!=1]", "Split column"], 448 null, 449 450 // CELLS 451 ["cell-prop", "td", "Cell properties"], 452 ["cell-insert-before", "td", "Insert cell before"], 453 ["cell-insert-after", "td", "Insert cell after"], 454 ["cell-delete", "td", "Delete cell"], 455 ["cell-merge", "tr", "Merge cells"], 456 ["cell-split", "td[colSpan!=1,rowSpan!=1]", "Split cell"] 457 ]; 459 458 460 459 TableOperations.prototype.dialogMerge = function(merge_func, cell_index, row_index) { … … 489 488 TableOperations.prototype.dialogTableProperties = function() { 490 489 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 490 var table = this.getClosest("table"); 491 var self = this; 492 var editor = this.editor; 493 494 if(!this.dialogTablePropertiesHtml){ // retrieve the raw dialog contents 495 Xinha._getback( Xinha.getPluginDir("TableOperations") + '/popups/dialogTable.html', function(getback) { self.dialogTablePropertiesHtml = getback; self.dialogTableProperties(); }); 496 return; 497 } 498 if (!this.dialogTable) { 499 // Now we have everything we need, so we can build the dialog. 500 this.dialogTable = new Xinha.Dialog(editor, this.dialogTablePropertiesHtml, 'TableOperations',{width:440}) 501 this.dialogTable.getElementById('cancel').onclick = function() { self.dialogTable.hide()}; 502 } 503 var dialog = this.dialogTable; 504 505 var Styler = new Xinha.InlineStyler(table, this.editor); 506 507 function apply() { 508 var params = dialog.hide(); 509 Styler.applyStyle(params); 510 511 for (var i in params) { 512 if(typeof params[i] == 'function') continue; 513 var val = params[i]; 514 if (typeof val == 'object' && val.tagName) val = val.value; 515 switch (i) { 516 case "caption": 517 if (/\S/.test(val)) { 518 // contains non white-space characters 519 var caption = table.getElementsByTagName("caption")[0]; 520 if (!caption) { 521 caption = dialog.editor._doc.createElement("caption"); 522 table.insertBefore(caption, table.firstChild); 523 } 524 caption.innerHTML = val; 525 } else { 526 // search for caption and delete it if found 527 var caption = table.getElementsByTagName("caption")[0]; 528 if (caption) { 529 caption.parentNode.removeChild(caption); 530 } 531 } 532 break; 533 case "summary": 534 table.summary = val; 535 break; 536 case "width": 537 table.style.width = ("" + val) + params.f_unit; 538 break; 539 case "align": 540 table.align = val; 541 break; 542 case "spacing": 543 table.cellSpacing = val; 544 break; 545 case "padding": 546 table.cellPadding = val; 547 break; 548 case "borders": 549 table.border = val; 550 break; 551 case "frames": 552 table.frame = val; 553 break; 554 case "rules": 555 table.rules = val; 556 break; 557 } 558 } 559 560 // various workarounds to refresh the table display (Gecko, 561 // what's going on?! do not disappoint me!) 562 self.editor.forceRedraw(); 563 self.editor.focusEditor(); 564 self.editor.updateToolbar(); 565 var save_collapse = table.style.borderCollapse; 566 table.style.borderCollapse = "collapse"; 567 table.style.borderCollapse = "separate"; 568 table.style.borderCollapse = save_collapse; 569 } 570 571 var st_layout = Styler.createStyleLayoutFieldset(); 572 var p = dialog.getElementById("TO_layout"); 573 p.replaceChild(st_layout,p.firstChild); 574 575 var st_prop = Styler.createStyleFieldset(); 576 p = dialog.getElementById("TO_style"); 577 p.replaceChild(st_prop,p.firstChild); 578 579 this.dialogTable.getElementById('ok').onclick = apply; 580 581 // gather element's values 582 var values = {}; 583 var capel = table.getElementsByTagName("caption")[0]; 584 if (capel) { 585 values['caption'] = capel.innerHTML; 586 } 587 else values['caption'] = ""; 588 values['summary'] = table.summary; 589 590 values['spacing'] = table.cellSpacing; 591 values['padding'] = table.cellPadding; 592 var f_borders = table.border; 593 594 values['frames'] = table.frame; 595 values['rules'] = table.rules; 596 597 this.dialogTable.show(values); 599 598 }; 600 599 601 600 TableOperations.prototype.dialogRowCellProperties = function(cell) { 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 601 // retrieve existing values 602 var element = this.getClosest(cell ? "td" : "tr"); 603 var table = this.getClosest("table"); 604 605 var self = this; 606 var editor = this.editor; 607 608 if(!self.dialogRowCellPropertiesHtml) // retrieve the raw dialog contents 609 { 610 Xinha._getback( Xinha.getPluginDir("TableOperations") + '/popups/dialogRowCell.html', function(getback) { self.dialogRowCellPropertiesHtml = getback; self.dialogRowCellProperties(cell); }); 611 return; 612 } 613 if (!this.dialogRowCell) { 614 // Now we have everything we need, so we can build the dialog. 615 this.dialogRowCell = new Xinha.Dialog(editor, self.dialogRowCellPropertiesHtml, 'TableOperations',{width:440}) 616 this.dialogRowCell.getElementById('cancel').onclick = function() { self.dialogRowCell.hide()}; 617 } 618 619 var dialog = this.dialogRowCell; 620 dialog.getElementById('title').innerHTML = cell ? Xinha._lc("Cell Properties", "TableOperations") : Xinha._lc("Row Properties", "TableOperations"); 622 621 var Styler = new Xinha.InlineStyler(element, self.editor); 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 622 623 function apply() { 624 var params = dialog.hide(); 625 Styler.applyStyle(); 626 627 // various workarounds to refresh the table display (Gecko, 628 // what's going on?! do not disappoint me!) 629 self.editor.forceRedraw(); 630 self.editor.focusEditor(); 631 self.editor.updateToolbar(); 632 var save_collapse = table.style.borderCollapse; 633 table.style.borderCollapse = "collapse"; 634 table.style.borderCollapse = "separate"; 635 table.style.borderCollapse = save_collapse; 636 } 637 638 var st_layout = Styler.createStyleLayoutFieldset(); 639 var p = dialog.getElementById("TO_layout"); 640 641 p.replaceChild(st_layout,p.firstChild); 642 643 var st_prop = Styler.createStyleFieldset(); 644 p = dialog.getElementById("TO_style"); 645 p.replaceChild(st_prop,p.firstChild); 646 647 this.dialogRowCell.getElementById('ok').onclick = apply; 648 this.dialogRowCell.show(); 650 649 };
Note: See TracChangeset
for help on using the changeset viewer.