User:Mike Dillon/Scripts/easydom.js

From Wiktionary, the free dictionary
Jump to navigation Jump to search

Note – after saving, you may have to bypass your browser’s cache to see the changes.

  • Mozilla / Firefox / Safari: hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Command-R on a Macintosh);
  • Konqueror and Chrome: click Reload or press F5;
  • Opera: clear the cache in Tools → Preferences;
  • Internet Explorer: hold Ctrl while clicking Refresh, or press Ctrl-F5.

function buildEasyDomNamespace(namespace, options) {
    var isType = function (o, t) { return typeof o == t };
    var isBool = function (o) { return isType(o, typeof true); };
    var isString = function (o) { return isType(o, typeof ''); };
    var isNumber = function (o) { return isType(o, typeof 0); };
    var isFunction = function (o) { return isType(o, typeof function () {}); };
    var isObject = function (o) { return isType(o, typeof new Object()); };
    var isUndefined = function (o) { return isType(o, (function (x) { return typeof x })()); };
    var isDefined = function (o) { return !isUndefined(o); }; // NOTE: null is "defined"
    var isPrimitive = function (o) {
        return isString(o) || isNumber(o) || isBool(o) || isFunction(o);
    }
    var isNode = function (o) { return isDefined(o) && o != null && isNumber(o.nodeType); }

    // Default tag names to be installed into the namespace as functions
    var defaultTagNames = [
        "bdo", "script", "style", "object", "param", "iframe", "link", "meta", "p",
        "pre", "a", "div", "span", "ul", "ol", "li", "img", "hr", "br", "em", "strong",
        "sup", "sub", "tt", "abbr", "acronym", "del", "ins", "cite", "blockquote",
        "code", "table", "tbody", "tfoot", "tr", "th", "td", "col", "colgroup", "caption",
        "form", "input", "select", "option", "optgroup", "button", "textarea",
        "h1", "h2", "h3", "h4", "h5", "h6", "label", "canvas", "fieldset", "legend"
    ];

    // Default event types
    var defaultEventTypes = [
        // HTML 4.0
        "Abort", "Blur", "Change", "Click", "DblClick", "DragDrop", "Error",
        "Focus", "KeyDown", "KeyPress", "KeyUp", "Load", "MouseDown",
        "MouseMove", "MouseOut", "MouseOver", "MouseUp", "Move", "Reset",
        "Resize", "Select", "Submit", "Unload"
    ];

    // Create an anonymous namespace if none was provided
    if (isUndefined(namespace)) namespace = {};

    // Settings
    var settings = {
        "namespaceUri": "http://www.w3.org/1999/xhtml",
        "invokeFunctions": true,
        "tagNames": defaultTagNames,
        "eventTypes": defaultEventTypes
    };

    // Override default settings with specified options
    if (options) {
        for (var p in options) {
            settings[p] = options[p];
        }
    }

    // If the browser doesn't understand createElementNS, fake it (God help them...)
    if (isUndefined(document.createElementNS)) {
        document.createElementNS = function (ns, name) {
            return document.createElement(name);
        };
    }

    // Creates the DOM element
    var createDomElement = function(name) {
        return document.createElementNS(settings.namespaceUri, name);
    };

    var defaultAttributeHandler = function (elem, attrName, attrValue) {
        // Invoke function callbacks of zero or one argument and use their result as the new attrValue
        if (settings.invokeFunctions && isFunction(attrValue) && attrValue.length <= 1) {
            attrValue = attrValue(elem);
        }

        // Skip null values
        if (attrValue == null) {
            return;
        }

        // Stringify non-string values
        if (!isString(attrValue)) {
            attrValue = attrValue.toString();
        }

        // Set the attribute
        elem.setAttribute(attrName, attrValue);
    };

    var createAttributeOverrideHandler = function (overrideName) {
        return function (elem, attrName, attrValue) {
            defaultAttributeHandler(elem, overrideName, attrValue);
        };
    };

    var createEventHandlerAttributeHandler = function (overrideName) {
        return function (elem, attrName, attrValue) {
            if (!isFunction(attrValue)) {
                attrValue = new Function(attrValue);
            }
            elem[overrideName || attrName] = attrValue;
        };
    };

    var attributeHandlers = {};

    for (var i in settings.eventTypes) {
        var handlerName = "on" + settings.eventTypes[i];
        var internalName = handlerName.toLowerCase();
        // Force lower case
        attributeHandlers[internalName] = createEventHandlerAttributeHandler();
        // Allow mixed case (with lower case internal name)
        attributeHandlers[handlerName] = createEventHandlerAttributeHandler(internalName);
    }

    // Conditionally add I.E. name overrides
    /*@cc_on
    attributeHandlers["for"] = createAttributeOverrideHandler("htmlFor");
    attributeHandlers["maxlength"] = createAttributeOverrideHandler("maxLength");
    attributeHandlers["class"] = createAttributeOverrideHandler("className");
    attributeHandlers["accesskey"] = createAttributeOverrideHandler("accessKey");

    attributeHandlers["style"] = function (elem, attrName, attrValue) {
        elem.style.cssText = attrValue;
    };
    @*/

    // Detects if the first element is a hash of attributes and if so,
    // uses it to set attributes on the DOM node
    //
    // Returns the number of elements processed to let the caller know
    // how many of the arguments to skip
    var processDomAttributes = function(elem, args) {
        if (args.length == 0) {
            return 0;
        }

        // No attributes to process if null is the first argument
        if (args[0] == null) {
            return 0;
        }

        // No attributes to process if a "primitive" is the first argument
        if (isPrimitive(args[0])) {
            return 0;
        }

        // No attributes to process if a DOM node is the first argument
        if (isNode(args[0])) {
            return 0;
        }

        // Process the first argument as a hash of attributes
        var attrs = args[0];
        for (var attrName in attrs) {
            if (isUndefined(attributeHandlers[attrName])) {
                defaultAttributeHandler(elem, attrName, attrs[attrName]);
            } else {
                attributeHandlers[attrName](elem, attrName, attrs[attrName]);
            }
        }

        // Return the number of arguments processed
        return 1;
    };

    // Create the function that creates new DOM element builders
    var createDomElementBuilder = function (name) {
        return function() {
            var elem = createDomElement(name);

            // Process attribute hash, if any and skip the argument count returned
            var firstChild = processDomAttributes(elem, arguments);

            // Process the remaining children, if any
            for (var i = firstChild; i < arguments.length; i++) {
                var child = arguments[i];
                if (child == null) {
                    continue;
                }
                // Convert any non-DOM nodes to text nodes with toString()
                if (!isNode(child)) {
                    child = document.createTextNode(child.toString());
                }
                elem.appendChild(child);
            }

            return elem;
        };
    };

    // Populate the namespace
    for (var i in settings.tagNames) {
        var tagName = settings.tagNames[i];
        namespace[tagName] = createDomElementBuilder(tagName);
    }

    // Return the namespace for those relying on anonymous creation
    return namespace;
}

// Build the Easy DOM functions in an anonymous namespace
easyDom = buildEasyDomNamespace();

// Namespace pollution
easydom = easyDOM = easyDom;