1/** 2 * Copyright (c) 2006-2017, JGraph Ltd 3 * Copyright (c) 2006-2017, Gaudenz Alder 4 */ 5/** 6 * Removes all labels, user objects and styles from the given node in-place. 7 */ 8EditorUi.DIFF_INSERT = 'i'; 9 10/** 11 * Removes all labels, user objects and styles from the given node in-place. 12 */ 13EditorUi.DIFF_REMOVE = 'r'; 14 15/** 16 * Removes all labels, user objects and styles from the given node in-place. 17 */ 18EditorUi.DIFF_UPDATE = 'u'; 19 20/** 21 * Shared codec. 22 */ 23EditorUi.prototype.codec = new mxCodec(); 24 25/** 26 * Contains all view state properties that should not be ignored in diff sync. 27 */ 28EditorUi.prototype.viewStateProperties = {background: true, backgroundImage: true, shadowVisible: true, 29 foldingEnabled: true, pageScale: true, mathEnabled: true, pageFormat: true, extFonts: true}; 30 31/** 32 * Contains all known cell properties that should be ignored for a generic cell diff. 33 */ 34EditorUi.prototype.cellProperties = {id: true, value: true, xmlValue: true, vertex: true, edge: true, 35 visible: true, collapsed: true, connectable: true, parent: true, children: true, previous: true, 36 source: true, target: true, edges: true, geometry: true, style: true, 37 mxObjectId: true, mxTransient: true}; 38 39/** 40 * Removes all labels, user objects and styles from the given node in-place. 41 */ 42EditorUi.prototype.patchPages = function(pages, diff, markPages, resolver, updateEdgeParents) 43{ 44 var resolverLookup = {}; 45 var newPages = []; 46 var inserted = {}; 47 var removed = {}; 48 var lookup = {}; 49 var moved = {}; 50 51 if (resolver != null && resolver[EditorUi.DIFF_UPDATE] != null) 52 { 53 for (var id in resolver[EditorUi.DIFF_UPDATE]) 54 { 55 resolverLookup[id] = resolver[EditorUi.DIFF_UPDATE][id]; 56 } 57 } 58 59 if (diff[EditorUi.DIFF_REMOVE] != null) 60 { 61 for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++) 62 { 63 removed[diff[EditorUi.DIFF_REMOVE][i]] = true; 64 } 65 } 66 67 if (diff[EditorUi.DIFF_INSERT] != null) 68 { 69 for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++) 70 { 71 inserted[diff[EditorUi.DIFF_INSERT][i].previous] = diff[EditorUi.DIFF_INSERT][i]; 72 } 73 } 74 75 if (diff[EditorUi.DIFF_UPDATE] != null) 76 { 77 for (var id in diff[EditorUi.DIFF_UPDATE]) 78 { 79 var pageDiff = diff[EditorUi.DIFF_UPDATE][id]; 80 81 if (pageDiff.previous != null) 82 { 83 moved[pageDiff.previous] = id; 84 } 85 } 86 } 87 88 // Restores existing order and creates lookup 89 if (pages != null) 90 { 91 var prev = ''; 92 93 for (var i = 0; i < pages.length; i++) 94 { 95 var pageId = pages[i].getId(); 96 lookup[pageId] = pages[i]; 97 98 if (moved[prev] == null && !removed[pageId] && 99 (diff[EditorUi.DIFF_UPDATE] == null || 100 diff[EditorUi.DIFF_UPDATE][pageId] == null || 101 diff[EditorUi.DIFF_UPDATE][pageId].previous == null)) 102 { 103 moved[prev] = pageId; 104 } 105 106 prev = pageId; 107 } 108 } 109 110 // FIXME: Workaround for possible duplicate pages 111 var added = {}; 112 113 var addPage = mxUtils.bind(this, function(page) 114 { 115 var id = (page != null) ? page.getId() : ''; 116 117 if (page != null && !added[id]) 118 { 119 added[id] = true; 120 newPages.push(page); 121 var pageDiff = (diff[EditorUi.DIFF_UPDATE] != null) ? 122 diff[EditorUi.DIFF_UPDATE][id] : null; 123 124 if (pageDiff != null) 125 { 126 this.updatePageRoot(page); 127 128 if (pageDiff.name != null) 129 { 130 page.setName(pageDiff.name); 131 } 132 133 if (pageDiff.view != null) 134 { 135 this.patchViewState(page, pageDiff.view); 136 } 137 138 if (pageDiff.cells != null) 139 { 140 this.patchPage(page, pageDiff.cells, 141 resolverLookup[page.getId()], 142 updateEdgeParents); 143 } 144 145 if (markPages && (pageDiff.cells != null || 146 pageDiff.view != null)) 147 { 148 page.needsUpdate = true; 149 } 150 } 151 } 152 153 var mov = moved[id]; 154 155 if (mov != null) 156 { 157 delete moved[id]; 158 addPage(lookup[mov]); 159 } 160 161 var ins = inserted[id]; 162 163 if (ins != null) 164 { 165 delete inserted[id]; 166 insertPage(ins); 167 } 168 }); 169 170 var insertPage = mxUtils.bind(this, function(ins) 171 { 172 var diagram = mxUtils.parseXml(ins.data).documentElement; 173 var newPage = new DiagramPage(diagram); 174 this.updatePageRoot(newPage); 175 var page = lookup[newPage.getId()]; 176 177 if (page == null) 178 { 179 addPage(newPage); 180 } 181 else 182 { 183 // Updates root if page already in UI 184 page.root = newPage.root; 185 186 if (this.currentPage == page) 187 { 188 this.editor.graph.model.setRoot(page.root); 189 } 190 else if (markPages) 191 { 192 page.needsUpdate = true; 193 } 194 } 195 }); 196 197 addPage(); 198 199 // Handles orphaned moved pages 200 for (var id in moved) 201 { 202 addPage(lookup[moved[id]]); 203 delete moved[id]; 204 } 205 206 // Handles orphaned inserted pages 207 for (var id in inserted) 208 { 209 insertPage(inserted[id]); 210 delete inserted[id]; 211 } 212 213 return newPages; 214}; 215 216/** 217 * Removes all labels, user objects and styles from the given node in-place. 218 */ 219EditorUi.prototype.patchViewState = function(page, diff) 220{ 221 if (page.viewState != null && diff != null) 222 { 223 if (page == this.currentPage) 224 { 225 page.viewState = this.editor.graph.getViewState(); 226 } 227 228 for (var key in diff) 229 { 230 try 231 { 232 page.viewState[key] = JSON.parse(diff[key]); 233 } 234 catch(e) {} //Ignore TODO Is this correct, we encountered an undefined value for a key (extFonts) 235 } 236 237 if (page == this.currentPage) 238 { 239 this.editor.graph.setViewState(page.viewState, true); 240 } 241 } 242}; 243 244/** 245 * Removes all labels, user objects and styles from the given node in-place. 246 */ 247EditorUi.prototype.createParentLookup = function(model, diff) 248{ 249 var parentLookup = {}; 250 251 function getLookup(id) 252 { 253 var result = parentLookup[id]; 254 255 if (result == null) 256 { 257 result = {inserted: [], moved: {}}; 258 parentLookup[id] = result; 259 } 260 261 return result; 262 }; 263 264 if (diff[EditorUi.DIFF_INSERT] != null) 265 { 266 for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++) 267 { 268 var temp = diff[EditorUi.DIFF_INSERT][i]; 269 var par = (temp.parent != null) ? temp.parent : ''; 270 var prev = (temp.previous != null) ? temp.previous : ''; 271 getLookup(par).inserted[prev] = temp; 272 } 273 } 274 275 if (diff[EditorUi.DIFF_UPDATE] != null) 276 { 277 for (var id in diff[EditorUi.DIFF_UPDATE]) 278 { 279 var temp = diff[EditorUi.DIFF_UPDATE][id]; 280 281 if (temp.previous != null) 282 { 283 var par = temp.parent; 284 285 if (par == null) 286 { 287 var cell = model.getCell(id); 288 289 if (cell != null) 290 { 291 var parent = model.getParent(cell); 292 293 if (parent != null) 294 { 295 par = parent.getId(); 296 } 297 } 298 } 299 300 if (par != null) 301 { 302 getLookup(par).moved[temp.previous] = id; 303 } 304 } 305 } 306 } 307 308 return parentLookup; 309}; 310 311/** 312 * Removes all labels, user objects and styles from the given node in-place. 313 */ 314EditorUi.prototype.patchPage = function(page, diff, resolver, updateEdgeParents) 315{ 316 var model = (page == this.currentPage) ? this.editor.graph.model : new mxGraphModel(page.root); 317 var parentLookup = this.createParentLookup(model, diff); 318 319 model.beginUpdate(); 320 try 321 { 322 // Disables or delays update of edge parents to after patch 323 var prev = model.updateEdgeParent; 324 var dict = new mxDictionary(); 325 var pendingUpdates = []; 326 327 model.updateEdgeParent = function(edge, root) 328 { 329 if (!dict.get(edge) && updateEdgeParents) 330 { 331 dict.put(edge, true); 332 pendingUpdates.push(edge); 333 } 334 }; 335 336 // Handles new root cells 337 var temp = parentLookup['']; 338 var cellDiff = (temp != null && temp.inserted != null) ? temp.inserted[''] : null; 339 var root = null; 340 341 if (cellDiff != null) 342 { 343 root = this.getCellForJson(cellDiff); 344 } 345 346 // Handles cells becoming root (very unlikely but possible) 347 if (root == null) 348 { 349 var id = (temp != null && temp.moved != null) ? temp.moved[''] : null; 350 351 if (id != null) 352 { 353 root = model.getCell(id); 354 } 355 } 356 357 if (root != null) 358 { 359 model.setRoot(root); 360 page.root = root; 361 } 362 363 // Inserts and updates previous and parent (hierarchy update) 364 this.patchCellRecursive(page, model, model.root, parentLookup, diff); 365 366 // Removes cells after parents have been updated above 367 if (diff[EditorUi.DIFF_REMOVE] != null) 368 { 369 for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++) 370 { 371 var cell = model.getCell(diff[EditorUi.DIFF_REMOVE][i]); 372 373 if (cell != null) 374 { 375 model.remove(cell); 376 } 377 } 378 } 379 380 // Updates cell states and terminals 381 if (diff[EditorUi.DIFF_UPDATE] != null) 382 { 383 var res = (resolver != null && resolver.cells != null) ? 384 resolver.cells[EditorUi.DIFF_UPDATE] : null; 385 386 for (var id in diff[EditorUi.DIFF_UPDATE]) 387 { 388 this.patchCell(model, model.getCell(id), 389 diff[EditorUi.DIFF_UPDATE][id], 390 (res != null) ? res[id] : null); 391 } 392 } 393 394 // Updates terminals for inserted cells 395 if (diff[EditorUi.DIFF_INSERT] != null) 396 { 397 for (var i = 0; i < diff[EditorUi.DIFF_INSERT].length; i++) 398 { 399 var cellDiff = diff[EditorUi.DIFF_INSERT][i]; 400 var cell = model.getCell(cellDiff.id); 401 402 if (cell != null) 403 { 404 model.setTerminal(cell, model.getCell(cellDiff.source), true); 405 model.setTerminal(cell, model.getCell(cellDiff.target), false); 406 } 407 } 408 } 409 410 // Delayed update of edge parents 411 model.updateEdgeParent = prev; 412 413 if (updateEdgeParents && pendingUpdates.length > 0) 414 { 415 for (var i = 0; i < pendingUpdates.length; i++) 416 { 417 if (model.contains(pendingUpdates[i])) 418 { 419 model.updateEdgeParent(pendingUpdates[i]); 420 } 421 } 422 } 423 } 424 finally 425 { 426 model.endUpdate(); 427 } 428}; 429 430/** 431 * Removes all labels, user objects and styles from the given node in-place. 432 */ 433EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup, diff) 434{ 435 if (cell != null) 436 { 437 var temp = parentLookup[cell.getId()]; 438 var inserted = (temp != null && temp.inserted != null) ? temp.inserted : {}; 439 var moved = (temp != null && temp.moved != null) ? temp.moved : {}; 440 var index = 0; 441 442 // Restores existing order 443 var childCount = model.getChildCount(cell); 444 var prev = ''; 445 446 for (var i = 0; i < childCount; i++) 447 { 448 var cellId = model.getChildAt(cell, i).getId(); 449 450 if (moved[prev] == null && 451 (diff[EditorUi.DIFF_UPDATE] == null || 452 diff[EditorUi.DIFF_UPDATE][cellId] == null || 453 (diff[EditorUi.DIFF_UPDATE][cellId].previous == null && 454 diff[EditorUi.DIFF_UPDATE][cellId].parent == null))) 455 { 456 moved[prev] = cellId; 457 } 458 459 prev = cellId; 460 } 461 462 var addCell = mxUtils.bind(this, function(child, insert) 463 { 464 var id = (child != null) ? child.getId() : ''; 465 466 // Ignores the insert if the cell is already in the model 467 if (child != null && insert) 468 { 469 var ex = model.getCell(id); 470 471 if (ex != null && ex != child) 472 { 473 child = null; 474 } 475 } 476 477 if (child != null) 478 { 479 if (model.getChildAt(cell, index) != child) 480 { 481 model.add(cell, child, index); 482 } 483 484 this.patchCellRecursive(page, model, 485 child, parentLookup, diff); 486 index++; 487 } 488 489 return id; 490 }); 491 492 // Uses stack to avoid recursion for children 493 var children = [null]; 494 495 while (children.length > 0) 496 { 497 var entry = children.shift(); 498 var child = (entry != null) ? entry.child : null; 499 var insert = (entry != null) ? entry.insert : false; 500 var id = addCell(child, insert); 501 502 // Move and insert are mutually exclusive per predecessor 503 // since an insert changes the predecessor of existing cells 504 // and is therefore ignored in the loop above where the order 505 // for existing cells is added to the moved object 506 var mov = moved[id]; 507 508 if (mov != null) 509 { 510 delete moved[id]; 511 children.push({child: model.getCell(mov)}); 512 } 513 514 var ins = inserted[id]; 515 516 if (ins != null) 517 { 518 delete inserted[id]; 519 children.push({child: this.getCellForJson(ins), insert: true}); 520 } 521 522 // Orphaned moves and inserts are operations where the previous cell vanished 523 // in the local model so their position in the child array cannot be determined. 524 // In this case those cells are appended. Dependencies between orphans are 525 // maintained because for-in loops enumerate the IDs in order of insertion. 526 if (children.length == 0) 527 { 528 // Handles orphaned moved pages 529 for (var id in moved) 530 { 531 children.push({child: model.getCell(moved[id])}); 532 delete moved[id]; 533 } 534 535 // Handles orphaned inserted pages 536 for (var id in inserted) 537 { 538 children.push({child: this.getCellForJson(inserted[id]), insert: true}); 539 delete inserted[id]; 540 } 541 } 542 } 543 } 544}; 545 546/** 547 * Removes all labels, user objects and styles from the given node in-place. 548 */ 549EditorUi.prototype.patchCell = function(model, cell, diff, resolve) 550{ 551 if (cell != null && diff != null) 552 { 553 // Last write wins for value except if label is empty 554 if (resolve == null || (resolve.xmlValue == null && 555 (resolve.value == null || resolve.value == ''))) 556 { 557 if ('value' in diff) 558 { 559 model.setValue(cell, diff.value); 560 } 561 else if (diff.xmlValue != null) 562 { 563 model.setValue(cell, mxUtils.parseXml(diff.xmlValue).documentElement); 564 } 565 } 566 567 // Last write wins for style 568 if ((resolve == null || resolve.style == null) && diff.style != null) 569 { 570 model.setStyle(cell, diff.style); 571 } 572 573 if (diff.visible != null) 574 { 575 model.setVisible(cell, diff.visible == 1); 576 } 577 578 if (diff.collapsed != null) 579 { 580 model.setCollapsed(cell, diff.collapsed == 1); 581 } 582 583 if (diff.vertex != null) 584 { 585 // Changes vertex state in-place 586 cell.vertex = diff.vertex == 1; 587 } 588 589 if (diff.edge != null) 590 { 591 // Changes edge state in-place 592 cell.edge = diff.edge == 1; 593 } 594 595 if (diff.connectable != null) 596 { 597 // Changes connectable state in-place 598 cell.connectable = diff.connectable == 1; 599 } 600 601 if (diff.geometry != null) 602 { 603 model.setGeometry(cell, this.codec.decode(mxUtils.parseXml( 604 diff.geometry).documentElement)); 605 } 606 607 if (diff.source != null) 608 { 609 model.setTerminal(cell, model.getCell(diff.source), true); 610 } 611 612 if (diff.target != null) 613 { 614 model.setTerminal(cell, model.getCell(diff.target), false); 615 } 616 617 for (var key in diff) 618 { 619 if (!this.cellProperties[key]) 620 { 621 cell[key] = diff[key]; 622 } 623 } 624 } 625}; 626 627/** 628 * Gets a file node that is comparable with a remote file node 629 * so that using isEqualNode returns true if the files can be 630 * considered equal. 631 */ 632EditorUi.prototype.getPagesForNode = function(node, nodeName) 633{ 634 var tmp = this.editor.extractGraphModel(node, true, true); 635 636 if (tmp != null) 637 { 638 node = tmp; 639 } 640 641 var diagrams = node.getElementsByTagName(nodeName || 'diagram'); 642 var pages = []; 643 644 if (diagrams.length > 0) 645 { 646 for (var i = 0; i < diagrams.length; i++) 647 { 648 var page = new DiagramPage(diagrams[i]); 649 this.updatePageRoot(page, true); 650 pages.push(page); 651 } 652 } 653 else if (node.nodeName == 'mxGraphModel') 654 { 655 var graph = this.editor.graph; 656 var page = new DiagramPage(node.ownerDocument.createElement('diagram')); 657 page.setName(mxResources.get('pageWithNumber', [1])); 658 mxUtils.setTextContent(page.node, Graph.compressNode(node, true)); 659 pages.push(page); 660 } 661 662 return pages; 663}; 664 665/** 666 * Removes all labels, user objects and styles from the given node in-place. 667 */ 668EditorUi.prototype.diffPages = function(oldPages, newPages) 669{ 670 var graph = this.editor.graph; 671 var inserted = []; 672 var removed = []; 673 var result = {}; 674 var lookup = {}; 675 var diff = {}; 676 var prev = null; 677 678 for (var i = 0; i < newPages.length; i++) 679 { 680 lookup[newPages[i].getId()] = {page: newPages[i], prev: prev}; 681 prev = newPages[i]; 682 } 683 684 prev = null; 685 686 for (var i = 0; i < oldPages.length; i++) 687 { 688 var id = oldPages[i].getId(); 689 var newPage = lookup[id]; 690 691 if (newPage == null) 692 { 693 removed.push(id); 694 } 695 else 696 { 697 var temp = this.diffPage(oldPages[i], newPage.page); 698 var pageDiff = {}; 699 700 if (Object.keys(temp).length > 0) 701 { 702 pageDiff.cells = temp; 703 } 704 705 var view = this.diffViewState(oldPages[i], newPage.page); 706 707 if (Object.keys(view).length > 0) 708 { 709 pageDiff.view = view; 710 } 711 712 if (((newPage.prev != null) ? prev == null : prev != null) || 713 (prev != null && newPage.prev != null && 714 prev.getId() != newPage.prev.getId())) 715 { 716 pageDiff.previous = (newPage.prev != null) ? newPage.prev.getId() : ''; 717 } 718 719 // FIXME: Check why names can be null in newer files 720 // ignore in hash and do not diff null names for now 721 if (newPage.page.getName() != null && 722 oldPages[i].getName() != newPage.page.getName()) 723 { 724 pageDiff.name = newPage.page.getName(); 725 } 726 727 if (Object.keys(pageDiff).length > 0) 728 { 729 diff[id] = pageDiff; 730 } 731 } 732 733 delete lookup[oldPages[i].getId()]; 734 prev = oldPages[i]; 735 } 736 737 for (var id in lookup) 738 { 739 var newPage = lookup[id]; 740 inserted.push({data: mxUtils.getXml(newPage.page.node), 741 previous: (newPage.prev != null) ? 742 newPage.prev.getId() : ''}); 743 } 744 745 if (Object.keys(diff).length > 0) 746 { 747 result[EditorUi.DIFF_UPDATE] = diff; 748 } 749 750 if (removed.length > 0) 751 { 752 result[EditorUi.DIFF_REMOVE] = removed; 753 } 754 755 if (inserted.length > 0) 756 { 757 result[EditorUi.DIFF_INSERT] = inserted; 758 } 759 760 return result; 761}; 762 763/** 764 * Removes all labels, user objects and styles from the given node in-place. 765 */ 766EditorUi.prototype.createCellLookup = function(cell, prev, lookup) 767{ 768 lookup = (lookup != null) ? lookup : {}; 769 lookup[cell.getId()] = {cell: cell, prev: prev}; 770 771 var childCount = cell.getChildCount(); 772 prev = null; 773 774 for (var i = 0; i < childCount; i++) 775 { 776 var child = cell.getChildAt(i); 777 this.createCellLookup(child, prev, lookup); 778 prev = child; 779 } 780 781 return lookup; 782}; 783 784/** 785 * Removes all labels, user objects and styles from the given node in-place. 786 */ 787EditorUi.prototype.diffCellRecursive = function(cell, prev, lookup, diff, removed) 788{ 789 diff = (diff != null) ? diff : {}; 790 var newCell = lookup[cell.getId()]; 791 delete lookup[cell.getId()]; 792 793 if (newCell == null) 794 { 795 removed.push(cell.getId()); 796 } 797 else 798 { 799 var temp = this.diffCell(cell, newCell.cell); 800 801 if (temp.parent != null || 802 (((newCell.prev != null) ? prev == null : prev != null) || 803 (prev != null && newCell.prev != null && 804 prev.getId() != newCell.prev.getId()))) 805 { 806 temp.previous = (newCell.prev != null) ? newCell.prev.getId() : ''; 807 } 808 809 if (Object.keys(temp).length > 0) 810 { 811 diff[cell.getId()] = temp; 812 } 813 } 814 815 var childCount = cell.getChildCount(); 816 prev = null; 817 818 for (var i = 0; i < childCount; i++) 819 { 820 var child = cell.getChildAt(i); 821 this.diffCellRecursive(child, prev, lookup, diff, removed); 822 prev = child; 823 } 824 825 return diff; 826}; 827 828/** 829 * Removes all labels, user objects and styles from the given node in-place. 830 */ 831EditorUi.prototype.diffPage = function(oldPage, newPage) 832{ 833 var inserted = []; 834 var removed = []; 835 var result = {}; 836 837 this.updatePageRoot(oldPage); 838 this.updatePageRoot(newPage); 839 840 var lookup = this.createCellLookup(newPage.root); 841 var diff = this.diffCellRecursive(oldPage.root, null, lookup, diff, removed); 842 843 for (var id in lookup) 844 { 845 var newCell = lookup[id]; 846 inserted.push(this.getJsonForCell(newCell.cell, newCell.prev)); 847 } 848 849 if (Object.keys(diff).length > 0) 850 { 851 result[EditorUi.DIFF_UPDATE] = diff; 852 } 853 854 if (removed.length > 0) 855 { 856 result[EditorUi.DIFF_REMOVE] = removed; 857 } 858 859 if (inserted.length > 0) 860 { 861 result[EditorUi.DIFF_INSERT] = inserted; 862 } 863 864 return result; 865}; 866 867/** 868 * Removes all labels, user objects and styles from the given node in-place. 869 */ 870EditorUi.prototype.diffViewState = function(oldPage, newPage) 871{ 872 var source = oldPage.viewState; 873 var target = newPage.viewState; 874 var result = {}; 875 876 if (newPage == this.currentPage) 877 { 878 target = this.editor.graph.getViewState(); 879 } 880 881 if (source != null && target != null) 882 { 883 for (var key in this.viewStateProperties) 884 { 885 // LATER: Check if normalization is needed for 886 // object attribute order to compare JSON 887 var old = JSON.stringify(source[key]); 888 var now = JSON.stringify(target[key]); 889 890 if (old != now) 891 { 892 result[key] = now; 893 } 894 } 895 } 896 897 return result; 898}; 899 900/** 901 * Removes all labels, user objects and styles from the given node in-place. 902 */ 903EditorUi.prototype.getCellForJson = function(json) 904{ 905 var geometry = (json.geometry != null) ? this.codec.decode( 906 mxUtils.parseXml(json.geometry).documentElement) : null; 907 var value = json.value; 908 909 if (json.xmlValue != null) 910 { 911 value = mxUtils.parseXml(json.xmlValue).documentElement; 912 } 913 914 var cell = new mxCell(value, geometry, json.style); 915 cell.connectable = json.connectable != 0; 916 cell.collapsed = json.collapsed == 1; 917 cell.visible = json.visible != 0; 918 cell.vertex = json.vertex == 1; 919 cell.edge = json.edge == 1; 920 cell.id = json.id; 921 922 for (var key in json) 923 { 924 if (!this.cellProperties[key]) 925 { 926 cell[key] = json[key]; 927 } 928 } 929 930 return cell; 931}; 932 933/** 934 * Removes all labels, user objects and styles from the given node in-place. 935 */ 936EditorUi.prototype.getJsonForCell = function(cell, previous) 937{ 938 var result = {id: cell.getId()}; 939 940 if (cell.vertex) 941 { 942 result.vertex = 1; 943 } 944 945 if (cell.edge) 946 { 947 result.edge = 1; 948 } 949 950 if (!cell.connectable) 951 { 952 result.connectable = 0; 953 } 954 955 if (cell.parent != null) 956 { 957 result.parent = cell.parent.getId(); 958 } 959 960 if (previous != null) 961 { 962 result.previous = previous.getId(); 963 } 964 965 if (cell.source != null) 966 { 967 result.source = cell.source.getId(); 968 } 969 970 if (cell.target != null) 971 { 972 result.target = cell.target.getId(); 973 } 974 975 if (cell.style != null) 976 { 977 result.style = cell.style; 978 } 979 980 if (cell.geometry != null) 981 { 982 result.geometry = mxUtils.getXml(this.codec.encode(cell.geometry)); 983 } 984 985 if (cell.collapsed) 986 { 987 result.collapsed = 1; 988 } 989 990 if (!cell.visible) 991 { 992 result.visible = 0; 993 } 994 995 if (cell.value != null) 996 { 997 if (typeof cell.value === 'object' && typeof cell.value.nodeType === 'number' && 998 typeof cell.value.nodeName === 'string' && typeof cell.value.getAttribute === 'function') 999 { 1000 result.xmlValue = mxUtils.getXml(cell.value); 1001 } 1002 else 1003 { 1004 result.value = cell.value; 1005 } 1006 } 1007 1008 for (var key in cell) 1009 { 1010 if (!this.cellProperties[key] && 1011 typeof cell[key] !== 'function') 1012 { 1013 result[key] = cell[key]; 1014 } 1015 } 1016 1017 return result; 1018}; 1019 1020/** 1021 * Removes all labels, user objects and styles from the given node in-place. 1022 */ 1023EditorUi.prototype.diffCell = function(oldCell, newCell) 1024{ 1025 var diff = {}; 1026 1027 if (oldCell.vertex != newCell.vertex) 1028 { 1029 diff.vertex = (newCell.vertex) ? 1 : 0; 1030 } 1031 1032 if (oldCell.edge != newCell.edge) 1033 { 1034 diff.edge = (newCell.edge) ? 1 : 0; 1035 } 1036 1037 if (oldCell.connectable != newCell.connectable) 1038 { 1039 diff.connectable = (newCell.connectable) ? 1 : 0; 1040 } 1041 1042 if (((oldCell.parent != null) ? newCell.parent == null : newCell.parent != null) || 1043 (oldCell.parent != null && newCell.parent != null && 1044 oldCell.parent.getId() != newCell.parent.getId())) 1045 { 1046 diff.parent = (newCell.parent != null) ? newCell.parent.getId() : ''; 1047 } 1048 1049 if (((oldCell.source != null) ? newCell.source == null : newCell.source != null) || 1050 (oldCell.source != null && newCell.source != null && 1051 oldCell.source.getId() != newCell.source.getId())) 1052 { 1053 diff.source = (newCell.source != null) ? newCell.source.getId() : ''; 1054 } 1055 1056 if (((oldCell.target != null) ? newCell.target == null : newCell.target != null) || 1057 (oldCell.target != null && newCell.target != null && 1058 oldCell.target.getId() != newCell.target.getId())) 1059 { 1060 diff.target = (newCell.target != null) ? newCell.target.getId() : ''; 1061 } 1062 1063 function isNode(value) 1064 { 1065 return value != null && typeof value === 'object' && typeof value.nodeType === 'number' && 1066 typeof value.nodeName === 'string' && typeof value.getAttribute === 'function'; 1067 }; 1068 1069 if (isNode(oldCell.value) && isNode(newCell.value)) 1070 { 1071 if (!oldCell.value.isEqualNode(newCell.value)) 1072 { 1073 diff.xmlValue = mxUtils.getXml(newCell.value); 1074 } 1075 } 1076 else if (oldCell.value != newCell.value) 1077 { 1078 if (isNode(newCell.value)) 1079 { 1080 diff.xmlValue = mxUtils.getXml(newCell.value); 1081 } 1082 else 1083 { 1084 diff.value = (newCell.value != null) ? newCell.value : null; 1085 } 1086 } 1087 1088 if (oldCell.style != newCell.style) 1089 { 1090 // LATER: Split into keys and do fine-grained diff 1091 diff.style = newCell.style; 1092 } 1093 1094 if (oldCell.visible != newCell.visible) 1095 { 1096 diff.visible = (newCell.visible) ? 1 : 0; 1097 } 1098 1099 if (oldCell.collapsed != newCell.collapsed) 1100 { 1101 diff.collapsed = (newCell.collapsed) ? 1 : 0; 1102 } 1103 1104 // FIXME: Proto only needed because source.geometry has no constructor (wrong type?) 1105 if (!this.isObjectEqual(oldCell.geometry, newCell.geometry, new mxGeometry())) 1106 { 1107 var node = this.codec.encode(newCell.geometry); 1108 1109 if (node != null) 1110 { 1111 diff.geometry = mxUtils.getXml(node); 1112 } 1113 } 1114 1115 // Compares all keys from oldCell to newCell and uses null in the diff 1116 // to force the attribute to be removed in the receiving client 1117 for (var key in oldCell) 1118 { 1119 if (!this.cellProperties[key] && typeof oldCell[key] !== 'function' && 1120 typeof newCell[key] !== 'function' && oldCell[key] != newCell[key]) 1121 { 1122 diff[key] = (newCell[key] === undefined) ? null : newCell[key]; 1123 } 1124 } 1125 1126 // Compares the remaining keys in newCell with oldCell 1127 for (var key in newCell) 1128 { 1129 if (!(key in oldCell) && 1130 !this.cellProperties[key] && typeof oldCell[key] !== 'function' && 1131 typeof newCell[key] !== 'function' && oldCell[key] != newCell[key]) 1132 { 1133 diff[key] = (newCell[key] === undefined) ? null : newCell[key]; 1134 } 1135 } 1136 1137 return diff; 1138}; 1139 1140/** 1141 * 1142 */ 1143EditorUi.prototype.isObjectEqual = function(source, target, proto) 1144{ 1145 if (source == null && target == null) 1146 { 1147 return true; 1148 } 1149 else if ((source != null) ? target == null : target != null) 1150 { 1151 return false; 1152 } 1153 else 1154 { 1155 var replacer = function(key, value) 1156 { 1157 return (proto == null || proto[key] != value) ? ((value === true) ? 1 : value) : undefined; 1158 }; 1159 1160 //console.log('eq', JSON.stringify(source, replacer), JSON.stringify(target, replacer)); 1161 1162 return JSON.stringify(source, replacer) == JSON.stringify(target, replacer); 1163 } 1164}; 1165