Mercurial > eldonilo > barbecue
changeset 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 | 6ef82d6818f4 |
files | index.xhtml script.js style.css |
diffstat | 3 files changed, 162 insertions(+), 102 deletions(-) [+] |
line wrap: on
line diff
--- a/index.xhtml +++ b/index.xhtml @@ -1,11 +1,10 @@ -<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> + <meta charset="UTF-8" /> <title> barbecue </title> - <meta charset="UTF-8" /> <link type="text/css" href="style.css" rel="stylesheet" /> - <script type="text/javascript" src="script.js"/> + <script type="text/javascript" src="script.js"></script> </head> <body> <form id="toolbar"> @@ -85,7 +84,7 @@ </dl> <pre id="execCommand" /> </form> - <pre id="breadcrumb"> </pre> + <pre id="breadcrumb"> </pre> <section contenteditable="true"> <h2> Go ahead, edit away! </h2>
--- a/script.js +++ b/script.js @@ -1,100 +1,173 @@ -// - Mutation Events: -// - https://developer.mozilla.org/en/XUL/Events#Mutation_DOM_events +/** Copyright (c) 2010-2011 Fabien Cazenave and Sonny Piers. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ -var gBreadCrumb = null; -function updateBreadCrumb(node, action) { - if (!gBreadCrumb || !node || (node == document.body)) return; +/** + * File : barbecue.js + * Author : Fabien Cazenave <fabien@cazenave.cc> + * Version : 0.1 + * License : MIT + * Last Modified : 2011-12-19 + */ + +/** wysiwyg Editor + * expected compatibility: all browsers except IE<9. + * + * This HTML content editor is not intended to compete with CKEditor or + * TinyMCE: it is designed to *test* the various implementations of + * contentEditable and execCommand in modern browsers. + * + * Note: a specific micro-format is expected for the format toolbar. + */ +!function(window, document, undefined) { var - body = document.body, - text = node.nodeName, - tmp = node.parentNode; - while (tmp && tmp != body) { - text = tmp.nodeName + " > " + text; - tmp = tmp.parentNode; + gActiveEditor = null, // active editing host + gCommandDump = null; // command dump field (debug) + + function ExecCommand(toolbarElement) { + var argVal, argStr, + type = toolbarElement.getAttribute("type"), + command = toolbarElement.getAttribute("data-command"); + + // get the execCommand argument according to the button type + switch (type) { + case "button": // toolbar button: no argument + argVal = argStr = false; + break; + case "checkbox": // styleWithCSS: boolean argument + argVal = argStr = "" + toolbarElement.checked + ""; + break; + default: // <select> menu: string argument + if (!toolbarElement.selectedIndex) return; + argVal = toolbarElement.value; + argStr = "'" + argVal.replace("<", "<").replace(">", ">") + "'"; + toolbarElement.selectedIndex = 0; // reset drop-down list + } + + // send requested action and re-focus the editable element + document.execCommand(command, false, argVal); + if (gActiveEditor) gActiveEditor.focus(); + + // debug + gCommandDump.innerHTML = "document.execCommand('" + command + "', false, '" + argStr + "');"; } - text = action + ": " + text; - gBreadCrumb.innerHTML = text; - console.log(text); -} + + window.addEventListener("DOMContentLoaded", function() { + var i, + buttons = document.querySelectorAll("*[data-command]"), + editors = document.querySelectorAll("*[contenteditable]"); -// retrieve the startContainer of the current selection -var gLastFocusNode = null; -function getSelectionStart() { - var node = document.getSelection().anchorNode; - return (node.nodeName == "#text" ? node.parentNode : node); -} -function onCaretMove(event, action) { - var selection = document.getSelection(); - var node = selection.isCollapsed ? selection.focusNode - : selection.getRangeAt(0).commonAncestorContainer; - if (node != gLastFocusNode) { - updateBreadCrumb(node, action); - gLastFocusNode = node; + // initialize all toolbar buttons + for (i = 0; i < buttons.length; i++) { + buttons[i].onclick = function() { ExecCommand(this); }; + buttons[i].onchange = function() { ExecCommand(this); }; + } + + // remember last-focused editable element + for (i = 0; i < editors.length; i++) + editors[i].onfocus = function() { gActiveEditor = this; }; + + // debug + gCommandDump = document.querySelector("#execCommand"); + ExecCommand(document.querySelector("#useCSS")); + }, false); +} (this, document); + +/** DOMNodeFocused + * expected compatibility: all browsers except IE<9. + * + * This custom (non-standard) event is fired when a node in an editable element + * gets the user focus. + */ +!function(window, document, undefined) { + var gLastFocusNode = null; + + function onCaretMove(event) { + var selection = document.getSelection(); + var node = selection.isCollapsed ? selection.focusNode + : selection.getRangeAt(0).commonAncestorContainer; + if (node != gLastFocusNode) { + gLastFocusNode = node; + // fire a 'DOMNodeFocused' mutation event with bubbling to set the .target + //var evtObject = document.createEvent("MutationEvent"); + //evtObject.initMutationEvent("DOMNodeFocused", true, false, node, "", "", "", 0); + // note that a standard event works fine, too :-) + var evtObject = document.createEvent("Event"); + evtObject.initEvent("DOMNodeFocused", true, false); + node.dispatchEvent(evtObject); + } } -} -window.addEventListener("DOMContentLoaded", function() { - gBreadCrumb = document.querySelector("#breadcrumb"); + // trigger a 'DOMNodeFocused' event when the user moves the caret + window.addEventListener("DOMContentLoaded", function() { + var i, editors = document.querySelectorAll("*[contenteditable]"); + for (i = 0; i < editors.length; i++) { + editors[i].addEventListener("keyup", onCaretMove, false); + editors[i].addEventListener("mouseup", onCaretMove, false); + } + }, false); +} (this, document); - function onAttrModified(event) { updateBreadCrumb(event.target, "keypress"); } - function onNodeInserted(event) { updateBreadCrumb(event.target, "inserted"); } - function onNodeRemoved(event) { updateBreadCrumb(event.target, "removed "); } - - function onKeyUp(event) { onCaretMove(event, "keyup"); } - function onClick(event) { onCaretMove(event, "click"); } +/** Mutation Events -- demo! + * expected compatibility: Firefox Aurora, possibly other modern browsers. + * reference: https://developer.mozilla.org/en/XUL/Events#Mutation_DOM_events + * http://help.dottoro.com/ljifcdwx.php + * + * SXE requires these (standard) mutation events: + * - 'new': DOMNodeInserted + * - 'remove': DOMNodeRemoved + * - 'set': DOMSubtreeModified, DOMAttrModified, DOMCharacterDataModified + * + * Note: webkit doesn't support DOMAttrModified + * https://bugs.webkit.org/show_bug.cgi?id=8191 + */ +window.addEventListener("DOMContentLoaded", function() { + var breadcrumb = document.querySelector("#breadcrumb"); + function updateBreadCrumb(event) { + if (!breadcrumb || !event || !event.target) return; + var + body = document.body, + node = event.target, + text = node.nodeName, + tmp = node.parentNode; + while (tmp && tmp != body) { + text = tmp.nodeName + " > " + text; + tmp = tmp.parentNode; + } + text = event.type + ": " + text; + breadcrumb.innerHTML = text; + console.log(text); + } // listening to mutation events on 'document' freezes Firefox :-/ var i, editors = document.querySelectorAll("*[contenteditable]"); for (i = 0; i < editors.length; i++) { - // mutation events -- do we really need them? - editors[i].addEventListener("DOMAttrModified", onAttrModified, false); - editors[i].addEventListener("DOMNodeInserted", onNodeInserted, false); - editors[i].addEventListener("DOMNodeRemoved", onNodeRemoved, false); - // caret movements - //editors[i].addEventListener("textinput", onKeyUp, false); - editors[i].addEventListener("keyup", onKeyUp, false); - editors[i].addEventListener("mouseup", onClick, false); + // set + editors[i].addEventListener("DOMAttrModified", updateBreadCrumb, false); + editors[i].addEventListener("DOMSubtreeModified", updateBreadCrumb, false); + editors[i].addEventListener("DOMCharacterDataModified", updateBreadCrumb, false); + // new, remove + editors[i].addEventListener("DOMNodeInserted", updateBreadCrumb, false); + editors[i].addEventListener("DOMNodeRemoved", updateBreadCrumb, false); + // focus (non-standard but can be useful) + editors[i].addEventListener("DOMNodeFocused", updateBreadCrumb, false); } }, false); - - -// editor - -var - gActiveEditor = null, // active editing host - gCommandDump = null; // command dump field -function ExecCommand(toolbarElement) { - var argVal, argStr, - type = toolbarElement.getAttribute("type"), - command = toolbarElement.getAttribute("data-command"); - switch (type) { // get the execCommand argument according to the button type - case "button": // toolbar button: no argument - argVal = argStr = false; - break; - case "checkbox": // styleWithCSS: boolean argument - argVal = argStr = "" + toolbarElement.checked + ""; - break; - default: // <select> menu: string argument - if (!toolbarElement.selectedIndex) return; - argVal = toolbarElement.value; - argStr = "'" + argVal.replace("<", "<").replace(">", ">") + "'"; - toolbarElement.selectedIndex = 0; // reset drop-down list - } - document.execCommand(command, false, argVal); // send requested action - if (gActiveEditor) gActiveEditor.focus(); // re-focus the editable element - gCommandDump.innerHTML = "document.execCommand('" + command + "', false, '" + argStr + "');"; -} -window.addEventListener("DOMContentLoaded", function() { - var i, - buttons = document.querySelectorAll("*[data-command]"), - editors = document.querySelectorAll("*[contenteditable]"); - for (i = 0; i < buttons.length; i++) { - buttons[i].onclick = function() { ExecCommand(this); }; - buttons[i].onchange = function() { ExecCommand(this); }; - } - for (i = 0; i < editors.length; i++) - editors[i].onfocus = function() { gActiveEditor = this; }; - gCommandDump = document.querySelector("#execCommand"); - ExecCommand(document.querySelector("#useCSS")); -}, false);