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("<", "&lt;").replace(">", "&gt;") + "'";
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("<", "&lt;").replace(">", "&gt;") + "'";
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);