Mercurial > eldonilo > barbecue
comparison script.js @ 2:683f56999fb3
'DOMNodeFocused' pseudo-event + Mutation Event Demo
author | Fabien Cazenave <fabien@cazenave.cc> |
---|---|
date | Mon, 19 Dec 2011 18:50:06 +0100 |
parents | eb3679d92996 |
children |
comparison
equal
deleted
inserted
replaced
1:eb3679d92996 | 2:683f56999fb3 |
---|---|
1 // - Mutation Events: | 1 /** Copyright (c) 2010-2011 Fabien Cazenave and Sonny Piers. |
2 // - https://developer.mozilla.org/en/XUL/Events#Mutation_DOM_events | 2 * |
3 * Permission is hereby granted, free of charge, to any person obtaining a copy | |
4 * of this software and associated documentation files (the "Software"), to | |
5 * deal in the Software without restriction, including without limitation the | |
6 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or | |
7 * sell copies of the Software, and to permit persons to whom the Software is | |
8 * furnished to do so, subject to the following conditions: | |
9 * | |
10 * The above copyright notice and this permission notice shall be included in | |
11 * all copies or substantial portions of the Software. | |
12 * | |
13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
14 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
15 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
16 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
17 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
18 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
19 * IN THE SOFTWARE. | |
20 */ | |
3 | 21 |
4 var gBreadCrumb = null; | 22 /** |
5 function updateBreadCrumb(node, action) { | 23 * File : barbecue.js |
6 if (!gBreadCrumb || !node || (node == document.body)) return; | 24 * Author : Fabien Cazenave <fabien@cazenave.cc> |
25 * Version : 0.1 | |
26 * License : MIT | |
27 * Last Modified : 2011-12-19 | |
28 */ | |
29 | |
30 /** wysiwyg Editor | |
31 * expected compatibility: all browsers except IE<9. | |
32 * | |
33 * This HTML content editor is not intended to compete with CKEditor or | |
34 * TinyMCE: it is designed to *test* the various implementations of | |
35 * contentEditable and execCommand in modern browsers. | |
36 * | |
37 * Note: a specific micro-format is expected for the format toolbar. | |
38 */ | |
39 !function(window, document, undefined) { | |
7 var | 40 var |
8 body = document.body, | 41 gActiveEditor = null, // active editing host |
9 text = node.nodeName, | 42 gCommandDump = null; // command dump field (debug) |
10 tmp = node.parentNode; | 43 |
11 while (tmp && tmp != body) { | 44 function ExecCommand(toolbarElement) { |
12 text = tmp.nodeName + " > " + text; | 45 var argVal, argStr, |
13 tmp = tmp.parentNode; | 46 type = toolbarElement.getAttribute("type"), |
47 command = toolbarElement.getAttribute("data-command"); | |
48 | |
49 // get the execCommand argument according to the button type | |
50 switch (type) { | |
51 case "button": // toolbar button: no argument | |
52 argVal = argStr = false; | |
53 break; | |
54 case "checkbox": // styleWithCSS: boolean argument | |
55 argVal = argStr = "" + toolbarElement.checked + ""; | |
56 break; | |
57 default: // <select> menu: string argument | |
58 if (!toolbarElement.selectedIndex) return; | |
59 argVal = toolbarElement.value; | |
60 argStr = "'" + argVal.replace("<", "<").replace(">", ">") + "'"; | |
61 toolbarElement.selectedIndex = 0; // reset drop-down list | |
62 } | |
63 | |
64 // send requested action and re-focus the editable element | |
65 document.execCommand(command, false, argVal); | |
66 if (gActiveEditor) gActiveEditor.focus(); | |
67 | |
68 // debug | |
69 gCommandDump.innerHTML = "document.execCommand('" + command + "', false, '" + argStr + "');"; | |
14 } | 70 } |
15 text = action + ": " + text; | |
16 gBreadCrumb.innerHTML = text; | |
17 console.log(text); | |
18 } | |
19 | 71 |
20 // retrieve the startContainer of the current selection | 72 window.addEventListener("DOMContentLoaded", function() { |
21 var gLastFocusNode = null; | 73 var i, |
22 function getSelectionStart() { | 74 buttons = document.querySelectorAll("*[data-command]"), |
23 var node = document.getSelection().anchorNode; | 75 editors = document.querySelectorAll("*[contenteditable]"); |
24 return (node.nodeName == "#text" ? node.parentNode : node); | 76 |
25 } | 77 // initialize all toolbar buttons |
26 function onCaretMove(event, action) { | 78 for (i = 0; i < buttons.length; i++) { |
27 var selection = document.getSelection(); | 79 buttons[i].onclick = function() { ExecCommand(this); }; |
28 var node = selection.isCollapsed ? selection.focusNode | 80 buttons[i].onchange = function() { ExecCommand(this); }; |
29 : selection.getRangeAt(0).commonAncestorContainer; | 81 } |
30 if (node != gLastFocusNode) { | 82 |
31 updateBreadCrumb(node, action); | 83 // remember last-focused editable element |
32 gLastFocusNode = node; | 84 for (i = 0; i < editors.length; i++) |
85 editors[i].onfocus = function() { gActiveEditor = this; }; | |
86 | |
87 // debug | |
88 gCommandDump = document.querySelector("#execCommand"); | |
89 ExecCommand(document.querySelector("#useCSS")); | |
90 }, false); | |
91 } (this, document); | |
92 | |
93 /** DOMNodeFocused | |
94 * expected compatibility: all browsers except IE<9. | |
95 * | |
96 * This custom (non-standard) event is fired when a node in an editable element | |
97 * gets the user focus. | |
98 */ | |
99 !function(window, document, undefined) { | |
100 var gLastFocusNode = null; | |
101 | |
102 function onCaretMove(event) { | |
103 var selection = document.getSelection(); | |
104 var node = selection.isCollapsed ? selection.focusNode | |
105 : selection.getRangeAt(0).commonAncestorContainer; | |
106 if (node != gLastFocusNode) { | |
107 gLastFocusNode = node; | |
108 // fire a 'DOMNodeFocused' mutation event with bubbling to set the .target | |
109 //var evtObject = document.createEvent("MutationEvent"); | |
110 //evtObject.initMutationEvent("DOMNodeFocused", true, false, node, "", "", "", 0); | |
111 // note that a standard event works fine, too :-) | |
112 var evtObject = document.createEvent("Event"); | |
113 evtObject.initEvent("DOMNodeFocused", true, false); | |
114 node.dispatchEvent(evtObject); | |
115 } | |
33 } | 116 } |
34 } | |
35 | 117 |
118 // trigger a 'DOMNodeFocused' event when the user moves the caret | |
119 window.addEventListener("DOMContentLoaded", function() { | |
120 var i, editors = document.querySelectorAll("*[contenteditable]"); | |
121 for (i = 0; i < editors.length; i++) { | |
122 editors[i].addEventListener("keyup", onCaretMove, false); | |
123 editors[i].addEventListener("mouseup", onCaretMove, false); | |
124 } | |
125 }, false); | |
126 } (this, document); | |
127 | |
128 /** Mutation Events -- demo! | |
129 * expected compatibility: Firefox Aurora, possibly other modern browsers. | |
130 * reference: https://developer.mozilla.org/en/XUL/Events#Mutation_DOM_events | |
131 * http://help.dottoro.com/ljifcdwx.php | |
132 * | |
133 * SXE requires these (standard) mutation events: | |
134 * - 'new': DOMNodeInserted | |
135 * - 'remove': DOMNodeRemoved | |
136 * - 'set': DOMSubtreeModified, DOMAttrModified, DOMCharacterDataModified | |
137 * | |
138 * Note: webkit doesn't support DOMAttrModified | |
139 * https://bugs.webkit.org/show_bug.cgi?id=8191 | |
140 */ | |
36 window.addEventListener("DOMContentLoaded", function() { | 141 window.addEventListener("DOMContentLoaded", function() { |
37 gBreadCrumb = document.querySelector("#breadcrumb"); | 142 var breadcrumb = document.querySelector("#breadcrumb"); |
38 | 143 function updateBreadCrumb(event) { |
39 function onAttrModified(event) { updateBreadCrumb(event.target, "keypress"); } | 144 if (!breadcrumb || !event || !event.target) return; |
40 function onNodeInserted(event) { updateBreadCrumb(event.target, "inserted"); } | 145 var |
41 function onNodeRemoved(event) { updateBreadCrumb(event.target, "removed "); } | 146 body = document.body, |
42 | 147 node = event.target, |
43 function onKeyUp(event) { onCaretMove(event, "keyup"); } | 148 text = node.nodeName, |
44 function onClick(event) { onCaretMove(event, "click"); } | 149 tmp = node.parentNode; |
150 while (tmp && tmp != body) { | |
151 text = tmp.nodeName + " > " + text; | |
152 tmp = tmp.parentNode; | |
153 } | |
154 text = event.type + ": " + text; | |
155 breadcrumb.innerHTML = text; | |
156 console.log(text); | |
157 } | |
45 | 158 |
46 // listening to mutation events on 'document' freezes Firefox :-/ | 159 // listening to mutation events on 'document' freezes Firefox :-/ |
47 var i, editors = document.querySelectorAll("*[contenteditable]"); | 160 var i, editors = document.querySelectorAll("*[contenteditable]"); |
48 for (i = 0; i < editors.length; i++) { | 161 for (i = 0; i < editors.length; i++) { |
49 // mutation events -- do we really need them? | 162 // set |
50 editors[i].addEventListener("DOMAttrModified", onAttrModified, false); | 163 editors[i].addEventListener("DOMAttrModified", updateBreadCrumb, false); |
51 editors[i].addEventListener("DOMNodeInserted", onNodeInserted, false); | 164 editors[i].addEventListener("DOMSubtreeModified", updateBreadCrumb, false); |
52 editors[i].addEventListener("DOMNodeRemoved", onNodeRemoved, false); | 165 editors[i].addEventListener("DOMCharacterDataModified", updateBreadCrumb, false); |
53 // caret movements | 166 // new, remove |
54 //editors[i].addEventListener("textinput", onKeyUp, false); | 167 editors[i].addEventListener("DOMNodeInserted", updateBreadCrumb, false); |
55 editors[i].addEventListener("keyup", onKeyUp, false); | 168 editors[i].addEventListener("DOMNodeRemoved", updateBreadCrumb, false); |
56 editors[i].addEventListener("mouseup", onClick, false); | 169 // focus (non-standard but can be useful) |
170 editors[i].addEventListener("DOMNodeFocused", updateBreadCrumb, false); | |
57 } | 171 } |
58 }, false); | 172 }, false); |
59 | 173 |
60 | |
61 | |
62 // editor | |
63 | |
64 var | |
65 gActiveEditor = null, // active editing host | |
66 gCommandDump = null; // command dump field | |
67 function ExecCommand(toolbarElement) { | |
68 var argVal, argStr, | |
69 type = toolbarElement.getAttribute("type"), | |
70 command = toolbarElement.getAttribute("data-command"); | |
71 switch (type) { // get the execCommand argument according to the button type | |
72 case "button": // toolbar button: no argument | |
73 argVal = argStr = false; | |
74 break; | |
75 case "checkbox": // styleWithCSS: boolean argument | |
76 argVal = argStr = "" + toolbarElement.checked + ""; | |
77 break; | |
78 default: // <select> menu: string argument | |
79 if (!toolbarElement.selectedIndex) return; | |
80 argVal = toolbarElement.value; | |
81 argStr = "'" + argVal.replace("<", "<").replace(">", ">") + "'"; | |
82 toolbarElement.selectedIndex = 0; // reset drop-down list | |
83 } | |
84 document.execCommand(command, false, argVal); // send requested action | |
85 if (gActiveEditor) gActiveEditor.focus(); // re-focus the editable element | |
86 gCommandDump.innerHTML = "document.execCommand('" + command + "', false, '" + argStr + "');"; | |
87 } | |
88 window.addEventListener("DOMContentLoaded", function() { | |
89 var i, | |
90 buttons = document.querySelectorAll("*[data-command]"), | |
91 editors = document.querySelectorAll("*[contenteditable]"); | |
92 for (i = 0; i < buttons.length; i++) { | |
93 buttons[i].onclick = function() { ExecCommand(this); }; | |
94 buttons[i].onchange = function() { ExecCommand(this); }; | |
95 } | |
96 for (i = 0; i < editors.length; i++) | |
97 editors[i].onfocus = function() { gActiveEditor = this; }; | |
98 gCommandDump = document.querySelector("#execCommand"); | |
99 ExecCommand(document.querySelector("#useCSS")); | |
100 }, false); |