About a week ago, Nik, posed a question about how to record edits in his data table widget. Responding to his inquiry, inspired todays article, which covers the "Element Editor" object (a work in progress). The ultimate goal of the project is to develop a widget that can be used to dynamically update the content of any DOM node, by inserting an editable form field (input, textarea, select, etc.). In version 1, "Element Editor" can replace a DOM node with a textarea or an input, allowing a client to replace the text inside of the DOM node. The widget contains a singleton Custom Event (1 Custom Event shared by all ElementEditor
instances), which can be subscribed to, that fires each time a user changes the DOM node content.
Example 1: Element Editor
Core.Widget.ElementEditorEvents = new YAHOO.util.EventProvider(); Core.Widget.ElementEditor = function(elem, conf) { var YE = YAHOO.util.Event, YD = YAHOO.util.Dom, YK = YAHOO.util.KeyListener.KEY, CE = Core.Widget.ElementEditorEvents; YE.off = YE.removeListener; var cfg = conf || {}, F = function() {}, node = YD.get(elem), text = , that = null; // configure if (! cfg.tagName) {cfg.tagName =textarea;} if (! cfg.type) {cfg.type =text;} if (! cfg.cols) {cfg.cols = 5;} if (! cfg.rows) {cfg.rows = 5;} if (! cfg.name) {cfg.name = ;} // event namespace var E = { onClick: function(e) { text = node.innerHTML; YE.stopPropagation(e); var dim = YD.getRegion(node), child = document.createElement(cfg.tagName); node.innerHTML = ; if (cfg.fitToParent) { YD.setStyle(child,height, dim.bottom - dim.top +px); YD.setStyle(child,width, dim.right - dim.left +px); } node.appendChild(child); child.value = text; if (YAHOO.lang.isFunction(child.focus)) {child.focus();} // focus on the new element child.name = cfg.name; // special-case for each tag name switch (cfg.tagName) { casetextarea: child.cols = cfg.cols; child.rows = cfg.rows; break; caseinput: child.type = cfg.type; if (! cfg.size) {child.size = cfg.size;} if (! cfg.maxLength) {child.setAttribute(maxLength, cfg.maxLength);} break; default: } YE.off(node,click, E.onClick); YE.on(child,blur, E.onBlur); YE.on(node,keydown, E.onKeyDispatcher); }, onBlur: function(e) { var s = node.firstChild.value; YE.stopPropagation(e); YE.on(node,click, E.onClick); YE.off(node,keydown, E.onKeyDispatcher); YE.off(node.firstChild,blur, E.onBlur); node.innerHTML = s; if (text !== s) {CE.fireEvent(that.CE_CHANGE, {id: cfg.id, element: node, value: s});} }, onKeyDispatcher: function(e) { switch (YE.getCharCode(e)) { case YK.ENTER: YE.stopEvent(e); E.onBlur(e); break; case YK.ESCAPE: node.firstChild.value = text; E.onBlur(e); break; default: } } }; YE.on(node,click, E.onClick); // public namespace F.prototype = { CE_CHANGE:elementEditorSave, subscribe: function(p_type, p_fn, p_obj, p_override) { CE.subscribe(p_type, p_fn, p_obj, p_override); } }; that = new F(); // lazily inialize custom events if (! CE.hasEvent(that.CE_CHANGE)) { CE.createEvent(that.CE_CHANGE); } return that; };
First, the singleton event provider is initialized. In your own projects, if you already use a global event provider, you can just hook into it. The ElementEditor
object is instantiated by passing a reference to the DOM node and an optional configuration parameter. When initializing, the object first creates local pointers to YUI, initializes some internal variables, then defines default configuration. The configuration is still basic, allowing the engineer to specify several attributes of the editable field created by the widget, but will become more complex and less restrictive in future versions. Next the event callbacks are defined, the click
event is subscribed too, and a public subscribe
method is defined to expose the Custom Event. Lastly, if the Custom Event singleton is not already defined, we go ahead and initialize it.
All the magic begins, when the client clicks on the node that was passed in to initialize the ElementEditor
. After clicking: the content of the node is stored into a text
variable, the editable field replaces all children of the node, and the click
event is unsubscribed whilst blur
and keydown
events are subscribed to. If the fitToParent
parent configuration is set, then the new edit element will be sized to fill the parent node. Also, if the native JavaScript focus
function is available on the newly created field (textareas and inputs), then we focus on the field as well.
The keydown
callback listens for the escape key and the enter key, dispatching the right actions accordingly. Escape will restore the previous content and ignore any changes that the user has made. Whilst the enter key will forward to the blur
event handler. The blur
callback unsubscribes the keydown
and blur
events, before resubscribing the click
event. It also, replaces content of the node with the client generated value in the editable field. Lastly, it will fire the save Custom Event (CE_CHANGE
) if the client has made any changes. Any callback methods subscribing to CE_CHANGE
will be passed an object as the first (and only) parameter, containing the keys: id
, element
, value
, where id
is a unique key defined by the object configuration, element
is the node, and value
is the client generated content.
This widget can be used with a table cell in a data grid, but the goal is to allow the editing of any DOM node. Here is a couple of examples of how to use "Element Editor".