# Yahoo! UI Library

### widget 3.1.1

Yahoo! UI Library > widget > value-change.js (source view)
Search:
Filters Show Private Show Protected Show Deprecated

    /**
     * <p>Event that fires when a text or input field has changed value as a result of a keystroke.
     * Attaches a timed-out listener on the keydown event, and keeps the value to provide support
     * for multi-keystroke character sets.</p>
     * <p>This event fires when the value of the element changes, either as a result of
     * a keystroke, or an IME input event.</p>
     * <p>This does not replace the DOM onchange event, but rather augments it to do onchange-like
     * logic as a result of key presses, even in multi-stroke character sets.</p>
     *
     * <p>Known issue: If attaching to elements that are not yet available, then only the first
     * node will be guaranteed to have the event handler attached.</p>
     *
     * @event valueChange
     * @for YUI
     * @param type {String} 'valueChange'
     * @param fn {Function} the callback function
     * @param el {String|Node|etc} the element(s) to bind
     * @param o {Object} optional context object
     * @param args 0..n additional arguments that should be provided
     * to the listener.
     * @return {Event.Handle} the detach handle
     **/

    function toNodeList (el) { return (
        (Y.Lang.isString(el) || Y.Lang.isArray(el))
            ? Y.all(el)
        : (el instanceof Y.Node)
            ? Y.all([el._node])
        : (el instanceof Y.NodeList)
            ? el
        : Y.all([el])
    )};

    function onAvail (el, args) {
        var h = Y.on("available", function () {
            h.handle = Y.on.apply(Y, args);
        }, el);
        return h;
    };

    function attachProxy (node, args) {
        // args = [type, fn, el, o, ...]
        // node.on(type, fn, o, ...);
        args[0] = ceName(node);
        args.splice(2,1);

        // if event-custom is loaded, then this is gonna do something.
        // otherwise, it's basically just a no-op.
        node.publish(args[0], {
            broadcast : true,
            emitFacade : true
        });

        var registry = attachTriggers(node),
            proxyHandle = node.on.apply(node, args);

        return proxyHandle;
    };

    function ceName (node) {
        return Y.stamp(node) + "-" + eventName;
    };

    function attachDomEventHandlers (handlers, node) {
        var handles = {};
        for (var i in handlers) {
            handles[i] = handleDom(i, handlers[i], node);
        }
        return handles;
    };

    // attach the dom events that will trigger the CE
    function attachTriggers (node) {
        var key = ceName(node);
        var reg = registry[ key ] = registry[ key ] || {
            count : 0,
            handles : attachDomEventHandlers(domEventHandlers, node)
        };
        reg.count++;
        return reg;
    };

    function handleDom (event, handler, node) {
        var handle = Y.on(event, handler, node);
        Y.after(Y.bind(afterDetach, null, node, true), handle, "detach");
        return handle;
    };

    // call this after detaching a CE handler
    function afterDetach (node, force) {
        var reg = registry[ ceName(node) ];
        if (!reg) return;
        reg.count --;
        if (force) reg.count = 0;
        if (reg.count <= 0) {
            delete registry[ ceName(node) ];
            for (var i in reg.handles) reg.handles[i].detach();
        }
    };

    var registry = {},
        event = {
            on : function (type, fn, el, o) {
                var args = Y.Array(arguments, 0, true),
                    nodeList = toNodeList(el);
                if (nodeList.size() === 0) return onAvail(el, args);

                args[3] = o = o || ((nodeList.size() === 1) ? nodeList.item(0) : nodeList);

                var handles = [];
                nodeList.each(function (node) {
                    var proxyHandle = attachProxy(node, args);
                    handles.push(proxyHandle);
                    // hook into the detach event to remove it from that node.
                    Y.after(Y.bind(afterDetach, null, node), proxyHandle, "detach");
                });
                // return a handle
                return { evt:handles, sub:nodeList, detach:function () {
                    Y.Array.each(handles, function (h) { h.detach() });
                }};
            }
        },

        // IMPLEMENTATION SPECIFIC
        eventName = "valueChange",
        domEventHandlers = (function () {
            var valueHistory = {}, intervals = {}, timeouts = {};

            function startPolling (node, e) {
                var key = ceName(node);
                // avoid duplicates
                stopPolling(node);
                intervals[key] = setInterval(Y.bind(poller, null, node, e), 50);
                timeouts[key] = setTimeout(Y.bind(stopPolling, null, node), 10000);
            };
            function stopPolling (node) {
                var key = ceName(node);
                clearTimeout(timeouts[key]);
                clearInterval(intervals[key]);
            };
            function poller (node, e) {
                var key = ceName(node);
                var value = node.get("value");
                if (value === valueHistory[key]) return;
                node.fire(key, {
                    type : eventName,
                    value : value,
                    oldValue : valueHistory[key],
                    _event : e,
                    target : node,
                    currentTarget : node
                });

                valueHistory[key] = node.get("value");
                startPolling(node, e);

            };

            function keyUpHandler (e) {
                // indications that an IME has started.
                // poll for 10 seconds.
                if (e.charCode === 229 || e.charCode === 197) startPolling(
                    e.currentTarget, e
                );
            };
            function blurHandler (e) {
                stopPolling(e.currentTarget);
            };
            function keyDownHandler (e) {
                startPolling(e.currentTarget, e);
            };

            return {
                keyup : keyUpHandler,
                blur : blurHandler,
                keydown : keyDownHandler
            };
        })();
        // /IMPLEMENTATION SPECIFIC

    Y.Env.evt.plugins[eventName] = event;
    if (Y.Node) Y.Node.DOM_EVENTS[eventName] = event;

Copyright © 2010 Yahoo! Inc. All rights reserved.