|
1 /** |
|
2 * NodeChange.js |
|
3 * |
|
4 * Copyright, Moxiecode Systems AB |
|
5 * Released under LGPL License. |
|
6 * |
|
7 * License: http://www.tinymce.com/license |
|
8 * Contributing: http://www.tinymce.com/contributing |
|
9 */ |
|
10 |
|
11 /** |
|
12 * This class handles the nodechange event dispatching both manual and though selection change events. |
|
13 * |
|
14 * @class tinymce.NodeChange |
|
15 * @private |
|
16 */ |
|
17 define("tinymce/NodeChange", [ |
|
18 "tinymce/dom/RangeUtils", |
|
19 "tinymce/Env" |
|
20 ], function(RangeUtils, Env) { |
|
21 return function(editor) { |
|
22 var lastRng, lastPath = []; |
|
23 |
|
24 /** |
|
25 * Returns true/false if the current element path has been changed or not. |
|
26 * |
|
27 * @private |
|
28 * @return {Boolean} True if the element path is the same false if it's not. |
|
29 */ |
|
30 function isSameElementPath(startElm) { |
|
31 var i, currentPath; |
|
32 |
|
33 currentPath = editor.$(startElm).parentsUntil(editor.getBody()).add(startElm); |
|
34 if (currentPath.length === lastPath.length) { |
|
35 for (i = currentPath.length; i >= 0; i--) { |
|
36 if (currentPath[i] !== lastPath[i]) { |
|
37 break; |
|
38 } |
|
39 } |
|
40 |
|
41 if (i === -1) { |
|
42 lastPath = currentPath; |
|
43 return true; |
|
44 } |
|
45 } |
|
46 |
|
47 lastPath = currentPath; |
|
48 |
|
49 return false; |
|
50 } |
|
51 |
|
52 // Gecko doesn't support the "selectionchange" event |
|
53 if (!('onselectionchange' in editor.getDoc())) { |
|
54 editor.on('NodeChange Click MouseUp KeyUp Focus', function(e) { |
|
55 var nativeRng, fakeRng; |
|
56 |
|
57 // Since DOM Ranges mutate on modification |
|
58 // of the DOM we need to clone it's contents |
|
59 nativeRng = editor.selection.getRng(); |
|
60 fakeRng = { |
|
61 startContainer: nativeRng.startContainer, |
|
62 startOffset: nativeRng.startOffset, |
|
63 endContainer: nativeRng.endContainer, |
|
64 endOffset: nativeRng.endOffset |
|
65 }; |
|
66 |
|
67 // Always treat nodechange as a selectionchange since applying |
|
68 // formatting to the current range wouldn't update the range but it's parent |
|
69 if (e.type == 'nodechange' || !RangeUtils.compareRanges(fakeRng, lastRng)) { |
|
70 editor.fire('SelectionChange'); |
|
71 } |
|
72 |
|
73 lastRng = fakeRng; |
|
74 }); |
|
75 } |
|
76 |
|
77 // IE has a bug where it fires a selectionchange on right click that has a range at the start of the body |
|
78 // When the contextmenu event fires the selection is located at the right location |
|
79 editor.on('contextmenu', function() { |
|
80 editor.fire('SelectionChange'); |
|
81 }); |
|
82 |
|
83 // Selection change is delayed ~200ms on IE when you click inside the current range |
|
84 editor.on('SelectionChange', function() { |
|
85 var startElm = editor.selection.getStart(true); |
|
86 |
|
87 // IE 8 will fire a selectionchange event with an incorrect selection |
|
88 // when focusing out of table cells. Click inside cell -> toolbar = Invalid SelectionChange event |
|
89 if (!Env.range && editor.selection.isCollapsed()) { |
|
90 return; |
|
91 } |
|
92 |
|
93 if (!isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) { |
|
94 editor.nodeChanged({selectionChange: true}); |
|
95 } |
|
96 }); |
|
97 |
|
98 // Fire an extra nodeChange on mouseup for compatibility reasons |
|
99 editor.on('MouseUp', function(e) { |
|
100 if (!e.isDefaultPrevented()) { |
|
101 // Delay nodeChanged call for WebKit edge case issue where the range |
|
102 // isn't updated until after you click outside a selected image |
|
103 if (editor.selection.getNode().nodeName == 'IMG') { |
|
104 setTimeout(function() { |
|
105 editor.nodeChanged(); |
|
106 }, 0); |
|
107 } else { |
|
108 editor.nodeChanged(); |
|
109 } |
|
110 } |
|
111 }); |
|
112 |
|
113 /** |
|
114 * Distpaches out a onNodeChange event to all observers. This method should be called when you |
|
115 * need to update the UI states or element path etc. |
|
116 * |
|
117 * @method nodeChanged |
|
118 * @param {Object} args Optional args to pass to NodeChange event handlers. |
|
119 */ |
|
120 this.nodeChanged = function(args) { |
|
121 var selection = editor.selection, node, parents, root; |
|
122 |
|
123 // Fix for bug #1896577 it seems that this can not be fired while the editor is loading |
|
124 if (editor.initialized && selection && !editor.settings.disable_nodechange && !editor.settings.readonly) { |
|
125 // Get start node |
|
126 root = editor.getBody(); |
|
127 node = selection.getStart() || root; |
|
128 node = node.ownerDocument != editor.getDoc() ? editor.getBody() : node; |
|
129 |
|
130 // Edge case for <p>|<img></p> |
|
131 if (node.nodeName == 'IMG' && selection.isCollapsed()) { |
|
132 node = node.parentNode; |
|
133 } |
|
134 |
|
135 // Get parents and add them to object |
|
136 parents = []; |
|
137 editor.dom.getParent(node, function(node) { |
|
138 if (node === root) { |
|
139 return true; |
|
140 } |
|
141 |
|
142 parents.push(node); |
|
143 }); |
|
144 |
|
145 args = args || {}; |
|
146 args.element = node; |
|
147 args.parents = parents; |
|
148 |
|
149 editor.fire('NodeChange', args); |
|
150 } |
|
151 }; |
|
152 }; |
|
153 }); |