define(function(require, module, exports) {
    main.consumes = ["ext"];
    main.provides = ["apf"]
    return main;

    function main(options, imports, register) {
        imports.ext.on("register", function(e) {
            apf.nameserver.register("all", e.plugin.name, e.plugin)
        });
        imports.ext.on("unregister", function(e) {
            apf.nameserver.remove("all", e.plugin)
        });



/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @class apf
 * The Ajax.org Platform.
 *
 * @author    Ruben Daniels (ruben AT ajax DOT org)
 * @version   3.0
 * @default_private
 *
 */
/**
 * @event domready      Fires when the browsers' DOM is ready to be manipulated.
 */
/** 
 * @event movefocus         Fires when the focus moves from one element to another.
 * @param {apf.AmlElement} toElement The element that receives the focus.
 */
/** 
 * @event exit              Fires when the application wants to exit.
 * @cancelable  Prevents the application from exiting. The return value of the event object is displayed in a popup, asking the user for permission.
 */
/** 
 * @event keyup         Fires when the user stops pressing a key (by lifting up)
 * @cancelable Prevents the keypress.
 * @param {Object} e An object containing the following properties:
 *  - keyCode ([[Number]]): The character code of the pressed key.
 *  - ctrlKey ([[Boolean]]): Whether the [[keys: Ctrl]] key was pressed.
 *  - shiftKey ([[Boolean]]): Whether the [[keys: Shift]] key was pressed.
 *  - altKey ([[Boolean]]): Whether the [[keys: Alt]] key was pressed.
 *  - htmlEvent ([[Object]]): The html event object.
 */
/** 
 * @event mousescroll   Fires when the user scrolls the mouse
 * @cancelable Prevents the container from scrolling
 * @param {Object} e An object containing the following properties:
 *  - htmlEvent ([[Object]]): The HTML event object
 *  - amlElement ([[apf.AmlElement]]): The element which was clicked.
 *  - delta ([[Number]]): The scroll impulse.
 */
/** 
 * @event hotkey        Fires when the user presses a hotkey
 * @bubbles
 * @cancelable Prevents the default hotkey behavior.
 * @param {Object} e An object containing the following properties:
 *  - keyCode ([[Number]]): The character code of the pressed key.
 *  - ctrlKey ([[Boolean]]): Whether the [[keys: Ctrl]] key was pressed.
 *  - shiftKey ([[Boolean]]): Whether the [[keys: Shift]] key was pressed.
 *  - altKey ([[Boolean]]): Whether the [[keys: Alt]] key was pressed.
 *  - htmlEvent ([[Object]]): The html event object.
 */
/** 
 * @event keydown       Fires when the user presses down on a key
 * @bubbles
 * @cancelable Prevents the default hotkey behavior.
 * @param {Object} e An object containing the following properties:
 *  - keyCode ([[Number]]): The character code of the pressed key.
 *  - ctrlKey ([[Boolean]]): Whether the [[keys: Ctrl]] key was pressed.
 *  - shiftKey ([[Boolean]]): Whether the [[keys: Shift]] key was pressed.
 *  - altKey ([[Boolean]]): Whether the [[keys: Alt]] key was pressed.
 *  - htmlEvent ([[Object]]): The html event object.
 */
/** 
 * @event mousedown     Fires when the user presses a mouse button
 * @param {Object} e An object containing the following properties:
 *  - htmlEvent ([[Object]]): The HTML event object
 *  - amlElement ([[apf.AmlElement]]): The element which was clicked.
 */
/** 
 * @event onbeforeprint Fires before the application prints.
 */
/** 
 * @event onafterprint  Fires after the application prints.
 */
/** 
 * @event load          Fires after the application is loaded.
 */
/** 
 * @event error         Fires when a communication error has occured while making a request for this element.
 * @cancelable Prevents the error from being thrown.
 * @bubbles
 * @param {Object} e An object containing the following properties:
 *   - error ([[Error]]): The error object that is thrown when the event's callback doesn't return `false`.
 *   - state ([[Number]]): The state of the call. Possible values include:
 *     - `apf.SUCCESS` : The request was successful
 *     - `apf.TIMEOUT`:  The request timed out
 *     - `apf.ERROR`:    An error occurred while making the request
 *     - `apf.OFFLINE`:  The request was made while the application was offline.
 *   - userdata (`Mixed`): Data that the caller made available in the callback of the HTTP request.
 *   - http ([[XMLHttpRequest]]): The object that executed the actual http request
 *   - url ([[String]]): The url that was requested
 *   - tpModule ([[apf.http]]): The teleport module that is making the request
 *   - id ([[Number]]): The id of the request
 *   - message ([[String]]): The error message
 * 
 */
 apf = {
VERSION: '3.0beta',
    // Content Distribution Network URL:
    
    getPlugin: function(name) {
        return apf.nameserver.get("all", name);
    },
    
    /**
     * The url to the content delivery network.
     * @type {String}
     */
    CDN: "",
    

    /**
     * Specifies whether apf is ready for DOM operations.
     * @type {Boolean}
     */
    READY: false,

    //AML nodeFunc constants
    /**
     * A constant for the hidden AML element.
     * @type {Number}
     */
    NODE_HIDDEN: 101,
    /**
     * A constant for a visible AML element.
     * @type {Number}
     */
    NODE_VISIBLE: 102,
    /**
     * A constant for an o3 widget.
     * @type {Number}
     */
    NODE_O3 : 103,

    /**
     * A constant for specifying that a widget is using only the keyboard to receive focus.
     * @type {Number}
     * @see apf.GuiElement@focus
     */
    KEYBOARD: 2,
    /**
     * A constant for specifying that a widget is using the keyboard or the mouse to receive focus.
     * @type {Boolean}
     * @see apf.GuiElement@focus
     */
    KEYBOARD_MOUSE: true,
    /**
     * A constant for specifying that a widget is a menu
     * @type {Number}
     */
    MENU: 3,

    /**
     * A constant for specifying success.
     * @type {Number}
     */
    SUCCESS: 1,
    /**
     * A constant for specifying a timeout.
     * @type {Number}
     */
    TIMEOUT: 2,
    /**
     * A constant for specifying an error.
     * @type {Number}
     */
    ERROR: 3,
    /**
     * A constant for specifying the application is offline.
     * @type {Number}
     */
    OFFLINE: 4,

    
    debug: false,
    

    includeStack: [],
    initialized: false,
    AppModules: [],
    
    /**
     * Specifies whether APF tries to load a skin from skins.xml when no skin element is specified.
     * @type {Boolean}
     */
    autoLoadSkin: false,
    /**
     * Specifies whether APF has started loading scripts and started the init process.
     * @type {Boolean}
     */
    started: false,
    /**
     * The namespace for all crypto libraries included with Ajax.org Platform.
     * @type {Object}
     */
    crypto: {}, //namespace
    config: {},
    _GET: {},
    $asyncObjects: {"apf.oHttp" : 1, "apf.ajax": 1},
    
    /**
     * A string specifying the basepath for loading APF from seperate files.
     * @type {String}
     */
    basePath: "",

    
    /**
     * Contains several known and often used namespace URI's.
     * @type {Object}
     * @private
     */
    ns: {
        apf: "http://ajax.org/2005/aml",
        aml: "http://ajax.org/2005/aml",
        xsd: "http://www.w3.org/2001/XMLSchema",
        xhtml: "http://www.w3.org/1999/xhtml",
        xslt: "http://www.w3.org/1999/XSL/Transform",
        xforms: "http://www.w3.org/2002/xforms",
        ev: "http://www.w3.org/2001/xml-events"
    },
    
    
    xPathAxis: {"self":1, "following-sibling":1, "ancestor":1}, //@todo finish list
    
    hasRequireJS: typeof requirejs !== "undefined",

    availHTTP: [],
    /**
     * @private
     */
    releaseHTTP: function(http) {
        if (apf.brokenHttpAbort) 
            return;
        if (self.XMLHttpRequestUnSafe && http.constructor == XMLHttpRequestUnSafe) 
            return;
        
        http.onreadystatechange = function(){};
        
        http.abort();
        this.availHTTP.push(http);
    },

    /**
     * @private
     */
    browserDetect: function(){
        if (this.$bdetect)
            return;
        
        /* Browser -  platform and feature detection, based on prototype's and mootools 1.3.
         *
         * Major browser/engines flags
         *
         * 'Browser.name' reports the name of the Browser as string, identical to the property names of the following Boolean values:
         *  - Browser.ie - (boolean) True if the current browser is Internet Explorer.
         *  - Browser.firefox - (boolean) True if the current browser is Firefox.
         *  - Browser.safari - (boolean) True if the current browser is Safari.
         *  - Browser.chrome - (boolean) True if the current browser is Chrome.
         *  - Browser.opera - (boolean) True if the current browser is Opera.
         *
         * In addition to one of the above properties a second property consisting of the name
         * and the major version is provided ('Browser.ie6', 'Browser.chrome15', ...).
         * If 'Browser.chrome' is True, all other possible properties, like 'Browser.firefox', 'Browser.ie', ... , will be undefined.
         *
         * 'Browser.version' reports the version of the Browser as number.
         *
         * 'Browser.Plaform' reports the platform name:
         *  - Browser.Platform.mac - (boolean) True if the platform is Mac.
         *  - Browser.Platform.win - (boolean) True if the platform is Windows.
         *  - Browser.Platform.linux - (boolean) True if the platform is Linux.
         *  - Browser.Platform.ios - (boolean) True if the platform is iOS.
         *  - Browser.Platform.android - (boolean) True if the platform is Android
         *  - Browser.Platform.webos - (boolean) True if the platform is WebOS
         *  - Browser.Platform.other - (boolean) True if the platform is neither Mac, Windows, Linux, Android, WebOS nor iOS.
         *  - Browser.Platform.name - (string) The name of the platform.
         */
        var Browser = this.$bdetect = (function() {
            
            var ua = navigator.userAgent.toLowerCase(),
                platform = navigator.platform.toLowerCase(),
                UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
                mode = UA[1] == 'ie' && document.documentMode;

            var b = {

                name: (UA[1] == 'version') ? UA[3] : UA[1],

                version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),

                Platform: {
                    name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
                },

                Features: {
                    xpath: !!(document.evaluate),
                    air: !!(window.runtime),
                    query: !!(document.querySelector),
                    json: !!(window.JSON)
                },

                Plugins: {}
            };

            b[b.name] = true;
            b[b.name + parseInt(b.version, 10)] = true;
            b.Platform[b.Platform.name] = true;
            
            return b;
            
        })();

        var UA = navigator.userAgent.toLowerCase();
        
        this.isGecko = !!Browser.firefox;
        this.isChrome = !!Browser.chrome;
        this.isSafari = !!Browser.safari;
        this.isSafariOld = Browser.safari && Browser.version === 2.4;
        this.isWebkit = this.isSafari || this.isChrome || UA.indexOf("konqueror") != -1;
        this.isOpera = !!Browser.opera;
        this.isIE = !!Browser.ie;
        
        this.isWin = Browser.Platform.win;
        this.isMac = Browser.Platform.mac;
        this.isLinux = Browser.Platform.linux;
        this.isIphone = Browser.Platform.ios || UA.indexOf("aspen simulator") != -1;
        this.isAIR = Browser.Features.air;
        
        // @deprecated, cleanup in apf modules 
        this.versionWebkit = this.isWebkit ? Browser.version : null;
        this.versionGecko = this.isGecko ? Browser.version : null;
        // @deprecated, cleanup in apf modules 
        this.isGecko3 = Browser.firefox3;
        this.isGecko35 = this.isGecko3 && Browser.version >= 3.5;
        // @deprecated, cleanup in apf modules 
        this.versionFF = this.isGecko ? Browser.version : null;
        this.versionSafari = this.isSafari ? Browser.version : null;
        this.versionChrome = this.isChrome ? Browser.version : null;
        this.versionOpera = this.isOpera ? Browser.version : null;
        // bad logic, needs review among apf modules 
        this.isIE6 = this.isIE && Browser.ie6;
        this.isIE7 = this.isIE && Browser.ie7;
        this.isIE8 = this.isIE && Browser.ie8;
        this.isIE7Emulate = this.isIE && document.documentMode && Browser.ie7;
        this.isIE = this.isIE ? Browser.version : null;

        
        
        
    },

    /**
     * @private
     */
    setCompatFlags: function(){
        //Set Compatibility
        this.TAGNAME = apf.isIE ? "baseName" : "localName";
        this.styleSheetRules = apf.isIE ? "rules" : "cssRules";
        this.brokenHttpAbort = apf.isIE6;
        this.canUseHtmlAsXml = apf.isIE;
        this.supportNamespaces = !apf.isIE;
        this.cannotSizeIframe = apf.isIE;
        this.hasConditionCompilation = apf.isIE;
        this.supportOverflowComponent = apf.isIE;
        // http://robertnyman.com/2010/12/02/css3-flexible-box-layout-module-aka-flex-box-introduction-and-demostest-cases/
        this.hasFlexibleBox = apf.versionGecko >= 3 || (apf.isWebkit && apf.versionWebkit >= 3.2);
        this.hasFileApi = !!(window["File"] && window["FileReader"] && window["Blob"] && window["FileError"]);
        this.hasEventSrcElement = apf.isIE;
        this.canHaveHtmlOverSelects = !apf.isIE6 && !apf.isIE5;
        this.hasInnerText = apf.isIE;
        this.hasMsRangeObject = apf.isIE;
        this.descPropJs = apf.isIE;
        this.hasClickFastBug = apf.isIE;
        this.hasExecScript = window.execScript ? true : false;
        this.canDisableKeyCodes = apf.isIE;
        this.hasTextNodeWhiteSpaceBug = apf.isIE || apf.isIE >= 8;
        this.hasCssUpdateScrollbarBug = apf.isIE;
        this.canUseInnerHtmlWithTables = !apf.isIE;
        this.hasSingleResizeEvent = !apf.isIE;
        this.hasStyleFilters = apf.isIE;
        this.supportOpacity = !apf.isIE || apf.isIE >= 9;
        this.supportPng24 = !apf.isIE6 && !apf.isIE5;
        this.cantParseXmlDefinition = apf.isIE50;
        this.hasDynamicItemList = !apf.isIE || apf.isIE >= 7;
        this.canImportNode = apf.isIE;
        this.hasSingleRszEvent = !apf.isIE;
        this.hasXPathHtmlSupport = !apf.isIE;
        this.hasFocusBug = apf.isIE;
        this.hasHeightAutoDrawBug = apf.isIE && apf.isIE < 8;
        //this.hasIndexOfNodeList = !apf.isIE;
        this.hasReadyStateBug = apf.isIE50;
        this.dateSeparator = apf.isIE ? "-" : "/";
        this.canCreateStyleNode = !apf.isIE;
        this.supportFixedPosition = !apf.isIE || apf.isIE >= 7;
        this.hasHtmlIdsInJs = apf.isIE && apf.isIE < 8 || apf.isWebkit;
        this.needsCssPx = !apf.isIE;
        this.hasCSSChildOfSelector = !apf.isIE || apf.isIE >= 8;
        this.hasStyleAnchors = !apf.isIE || apf.isIE >= 8;
        this.styleAttrIsObj = apf.isIE < 8;
        this.hasAutocompleteXulBug = apf.isGecko;
        this.loadsLocalFilesSync = apf.isIE || apf.isGecko;
        this.mouseEventBuffer = apf.isIE ? 20 : 6;
        this.hasComputedStyle = typeof document.defaultView != "undefined"
                                           && typeof document.defaultView.getComputedStyle != "undefined";
        this.w3cRange = Boolean(window["getSelection"]);
        this.locale = (apf.isIE
                                            ? navigator.userLanguage
                                            : navigator.language).toLowerCase();
        this.characterSet = document.characterSet || document.defaultCharset || "utf-8";
        var t = document.createElement("div");
        this.hasContentEditable = (typeof t.contentEditable == "string"
                                       || typeof t.contentEditable == "boolean");
        apf.hasContentEditableContainerBug = apf.isWebkit;
        // Try transform first for forward compatibility
        var props = ["transform", "OTransform", "KhtmlTransform", "MozTransform", "WebkitTransform"],
            props2 = ["transition", "OTransition", "KhtmlTransition", "MozTransition", "WebkitTransition"],
            prefixR = ["", "O", "Khtml", "Moz", "Webkit"],
            prefixC = ["", "o-", "khtml-", "moz-", "webkit-"],
            events = ["transitionend", "transitionend", "transitionend", "transitionend", "webkitTransitionEnd"],
            i = 0,
            l = 5;
        this.supportCSSAnim = false;
        this.supportCSSTransition = false;
        for (; i < l && !this.supportCSSAnim; ++i) {
            if (typeof t.style[props[i]] == "undefined") continue;
            this.supportCSSAnim = props[i];
            this.supportCSSTransition = props2[i];
            this.runtimeStylePrefix = prefixR[i];
            this.classNamePrefix = prefixC[i];
            this.cssAnimEvent = events[i];
        }
        t = null;

        this.supportVML = apf.isIE;
        this.supportSVG = !apf.isIE || apf.isIE > 8;
        this.supportCanvas = !!document.createElement("canvas").getContext;
        this.supportCanvasText = !!(this.supportCanvas
            && typeof document.createElement("canvas").getContext("2d").fillText == "function")

        this.hasVideo = !!document.createElement("video")["canPlayType"];
        this.hasAudio = !!document.createElement("audio")["canPlayType"];
        this.supportHashChange = ("onhashchange" in self) && (!apf.isIE || apf.isIE >= 8);

        if (self.XMLHttpRequest) {
            var xhr = new XMLHttpRequest();
            this.hasXhrProgress = !!xhr.upload;
            if (this.hasXhrBinary = !!(xhr.sendAsBinary || xhr.upload)) {
                this.hasHtml5File = !!(File && File.prototype.getAsDataURL);
                this.hasHtml5FileSlice = !!(File && File.prototype.slice);
            }
        }
        else {
            this.hasXhrProgress = this.hasXhrBinary = this.hasHtml5File 
                = this.hasHtml5FileSlice = false;
        }

        this.windowHorBorder = 
        this.windowVerBorder = apf.isIE8 && (!self.frameElement 
            || parseInt(self.frameElement.frameBorder)) ? 4 : 0;
        
        

        this.enableAnim = !apf.isIE || apf.isIE > 8;
        this.animSteps = apf.isIE ? 0.3 : 1;
        this.animInterval = apf.isIE ? 7 : 1;

        this.CSSFLOAT = apf.isIE ? "styleFloat" : "cssFloat";
        this.CSSPREFIX = apf.isGecko ? "Moz" : (apf.isWebkit ? "webkit" : "");
        this.CSSPREFIX2 = apf.isGecko ? "-moz" : (apf.isWebkit ? "-webkit" : "");
        this.INLINE = apf.isIE && apf.isIE < 8 ? "inline" : "inline-block";
        this.needZoomForLayout = apf.isIE && apf.isIE < 8;

        //Other settings
        this.maxHttpRetries = apf.isOpera ? 0 : 3;

        
        this.percentageMatch = new RegExp();
        this.percentageMatch.compile("([\\-\\d\\.]+)\\%", "g");
        
        
        this.reMatchXpath = new RegExp();
        this.reMatchXpath.compile("(^|\\|)(?!\\@|[\\w-]+::)", "g");

        
    },

    hasGeoLocation: function() {
        
        return false;
        
    },

    

    /**
     * Extends an object with one or more other objects by copying all of its
     * properties.
     * @param {Object} dest The destination object
     * @param {Object} src The object that is copied from
     * @return {Object} The destination object
     */
    extend: function(dest, src) {
        var prop, i, x = !dest.notNull;
        if (arguments.length == 2) {
            for (prop in src) {
                if (x || src[prop])
                    dest[prop] = src[prop];
            }
            return dest;
        }

        for (i = 1; i < arguments.length; i++) {
            src = arguments[i];
            for (prop in src) {
                if (x || src[prop])
                    dest[prop] = src[prop];
            }
        }
        return dest;
    },
    
    $extend: function(dest, src) {
        for (var prop in src) {
            dest[prop] = src[prop];
        }
        return dest;
    },
    
    
    /**
     * Sends and retrieves data from remote locations over http.
     * 
     * #### Example
     * 
     * ```javascript
     *  var content = apf.ajax("http://www.ajax.org", {
     *      method   : "POST",
     *      data     : "<data />",
     *      async    : false,
     *      callback : function( data, state ) {
     *          if (state == apf.SUCCESS)
     *              alert("Success");
     *          else
     *              alert("Failure")
     *      }
     *  });
     *  alert(content);
     * ```
     *
     * @param {String}   url       The url that is accessed.
     * @param {Object}   options   The options for the HTTP request. It has the following properties:
     *   - async ([[Boolean]]): Whether the request is sent asynchronously. Defaults to true.
     *   - userdata (`Mixed`): Custom data that is available to the callback function.
     *   - method ([[String]]): The request method (`POST`|`GET`|`PUT`|`DELETE`). Defaults to `GET`.
     *   - nocache ([[Boolean]]): Specifies whether browser caching is prevented.
     *   - data ([[String]]): The data sent in the body of the message.
     *   - useXML ([[Boolean]]): Specifies whether the result should be interpreted as xml.
     *   - autoroute ([[Boolean]]): Specifies whether the request can fallback to a server proxy.
     *   - caching ([[Boolean]]): Specifies whether the request should use internal caching.
     *   - ignoreOffline ([[Boolean]]): Specifies whether to ignore offline catching.
     *   - contentType ([[String]]): The MIME type of the message
     *   - callback ([[Function]]): The handler that gets called whenever the
     *                            request completes successfully, with an error, or times out.
     */
    ajax: (function(){
        var f = function(){
            return this.oHttp.get.apply(this.oHttp, arguments);
        };
        
        f.exec = function(method, args, callback, options) {
            if (method == "ajax" && args[0]) {
                var opt = args[1] || {};
                return this.oHttp.exec(opt.method || "GET", [args[0]], 
                    opt.callback, apf.extend(options || {}, opt));
            }
        };

        return f;
    })(),
    

    /**
     * Starts the application.
     * @private
     */
    start: function(){
        this.started = true;
        var sHref = location.href.split("#")[0].split("?")[0];

        //Set Variables
        this.host = location.hostname && sHref.replace(/(\/\/[^\/]*)\/.*$/, "$1");
        this.hostPath = sHref.replace(/\/[^\/]*$/, "") + "/";

        

        //mozilla root detection
        //try{ISROOT = !window.opener || !window.opener.apf}catch(e){ISROOT = true}

        //Browser Specific Stuff
        //this.browserDetect();
        this.setCompatFlags();

        if (apf.onstart && apf.onstart() === false)
            return false;

        

        //Load Browser Specific Code
        
        if (this.isIE) apf.runIE();
            //this.importClass(apf.runIE, true, self);
        
        
        if (apf.isWebkit) apf.runWebkit();
            //this.importClass(apf.runSafari, true, self);
        
        
        
        if (this.isGecko || !this.isIE && !apf.isWebkit && !this.isOpera)
            apf.runGecko();
            //this.importClass(apf.runGecko, true, self);
        
        
        

        
        // Start HTTP object
        this.oHttp = new this.http();
        
        
        
        // Load user defined includes
        this.Init.addConditional(this.parseAppMarkup, apf, ["body"]);
        //@todo, as an experiment I removed 'HTTP' and 'Teleport'
        

        //IE fix
        try {
            if (apf.isIE)
                document.execCommand("BackgroundImageCache", false, true);
        }
        catch (e) {}

        
        //apf.window.init();
        

        this.started = true;
        
        
        // DOMReady already fired, so plz continue the loading and parsing
        if (this.load_done)
            this.execDeferred();
        

        //try{apf.root = !window.opener || !window.opener.apf;}
        //catch(e){apf.root = false}
        this.root = true;
        
        
        for (var i = 0; i < apf.$required.length; i++) {
            apf.include(apf.$required[i]);
        }
        apf.require = apf.include;
        
        
        

    },

    nsqueue: {},

    
    /**
     * @private
     */
    findPrefix: function(xmlNode, xmlns) {
        var docEl;
        if (xmlNode.nodeType == 9) {
            if (!xmlNode.documentElement)
                return false;
            if (xmlNode.documentElement.namespaceURI == xmlns)
                return xmlNode.prefix || xmlNode.scopeName;
            docEl = xmlNode.documentElement;
        }
        else {
            if (xmlNode.namespaceURI == xmlns)
                return xmlNode.prefix || xmlNode.scopeName;
            docEl = xmlNode.ownerDocument.documentElement;
            if (docEl && docEl.namespaceURI == xmlns)
                return xmlNode.prefix || xmlNode.scopeName;

            while (xmlNode.parentNode) {
                xmlNode = xmlNode.parentNode;
                if (xmlNode.namespaceURI == xmlns)
                    return xmlNode.prefix || xmlNode.scopeName;
            }
        }

        if (docEl) {
            for (var i = 0; i < docEl.attributes.length; i++) {
                if (docEl.attributes[i].nodeValue == xmlns)
                    return docEl.attributes[i][apf.TAGNAME]
            }
        }

        return false;
    },
    

    /**
     * @private
     */
    importClass: function(ref, strip, win) {
        if (!ref)
            throw new Error(apf.formatErrorString(1018, null,
                "importing class",
                "Could not load reference. Reference is null"));

        if (!strip)
            return apf.jsexec(ref.toString(), win);

        var q = ref.toString().replace(/^\s*function\s*\w*\s*\([^\)]*\)\s*\{/, "")
                              .replace(/\}\s*$/, "");

        return apf.jsexec(q, win);
    },

    /**
    * This method returns a string representation of the object
    * @return {String}    Returns a string representing the object.
    */
    toString: function(){
        return "[Ajax.org Platform (apf)]";
    },

    all: [],

    /**
    * This method implements all the properties and methods to this object from another class
    * @param {Function}    classRef    The class reference to implement
    * @private
    */
    implement: function(classRef) {
        // for speed, we check for the most common  case first
        if (arguments.length == 1) {
            
            classRef.call(this);//classRef
        }
        else {
            for (var a, i = 0, l = arguments.length; i < l; i++) {
                a = arguments[i];
                
                arguments[i].call(this);//classRef
            }
        }

        return this;
    },

    /**
     * @private
     */
    uniqueHtmlIds: 0,

    /**
     * Adds a unique id attribute to an HTML element.
     * @param {HTMLElement} oHtml the object getting the attribute.
     */
    setUniqueHtmlId: function(oHtml) {
        var id;
        oHtml.setAttribute("id", id = "q" + this.uniqueHtmlIds++);
        return id;
    },

    /**
     * Retrieves a new unique id
     * @returns {Number} A number representing the new ID.
     */
    getUniqueId: function(){
        return this.uniqueHtmlIds++;
    },

    /**
     * Finds an AML element based on its unique id.
     * @param {Number} uniqueId The unique id to search on.
     * @returns {apf.AmlElement} The returned element.
     */
    lookup: function(uniqueId) {
        return this.all[uniqueId];
    },

    /**
     * Searches in the HTML tree from a certain point to find the
     * AML element that is responsible for rendering a specific html
     * element.
     * @param {HTMLElement} oHtml The html context to start the search from.
     * @returns {apf.AmlElement} The parent HTML element
     */
    findHost: function(o) {
        while (o && o.parentNode) { //!o.host && 
            try {
                if ((o.host || o.host === false) && typeof o.host != "string")
                    return o.host;
            }
            catch (e) {}
            
            o = o.parentNode;
        }
        
        return null;
    },

    /**
     * Sets a reference to an object (by name) in the global JavaScript space.
     * @param {String} name The name of the reference.
     * @param {Mixed}  o    The reference to the object subject to the reference.
     */
    setReference: function(name, o) {
        return self[name] && self[name].hasFeature
            ? 0
            : (self[name] = o);
    },

    /*
     * The console outputs to the debug screen and offers differents ways to do
     * this.
     */
    console: {
        

        /**
         * Writes a message to the console.
         * @param {String} msg      The message to display in the console.
         * @param {String} subtype  The category for this message. This is used for filtering the messages.
         * @param {String} data     Extra data that might help in debugging.
         */
        debug: function(msg, subtype, data) {
            
        },

        /**
         * Writes a message to the console with the time icon next to it.
         * @param {String} msg      The message to display in the console.
         * @param {String} subtype  The category for this message. This is used for filtering the messages.
         * @param {String} data     Extra data that might help in debugging.
         */
        time: function(msg, subtype, data) {
            
        },

        /**
         * Writes a message to the console.
         * @param {String} msg      The message to display in the console.
         * @param {String} subtype  The category for this message. This is used for filtering the messages.
         * @param {String} data     Extra data that might help in debugging.
         */
        log: function(msg, subtype, data) {
            
        },

        /**
         * Writes a message to the console with the visual "info" icon and color
         * coding.
         * @param {String} msg      The message to display in the console.
         * @param {String} subtype  The category for this message. This is used for filtering the messages.
         * @param {String} data     Extra data that might help in debugging.
         */
        info: function(msg, subtype, data) {
            
        },

        /**
         * Writes a message to the console with the visual "warning" icon and
         * color coding.
         * @param {String} msg      The message to display in the console.
         * @param {String} subtype  The category for this message. This is used for filtering the messages.
         * @param {String} data     Extra data that might help in debugging.
         */
        warn: function(msg, subtype, data) {
            
        },

        /**
         * Writes a message to the console with the visual "error" icon and
         * color coding.
         * @param {String} msg      The message to display in the console.
         * @param {String} subtype  The category for this message. This is used for filtering the messages.
         * @param {String} data     Extra data that might help in debugging.
         */
        error: function(msg, subtype, data) {
            
        },

        /**
         * Prints a listing of all properties of the object.
         * @param {Mixed} obj The object whose properties you want displayed.
         */
        dir: function(obj) {
            var s = apf.$debugwin.$serializeObject(obj, "Inspected via apf.console.dir");
            if (typeof s == "string") {
                this.write(s, "custom", null, null, null, true);
            }
            else {
                this.write(obj
                    ? "Could not serialize object: " + s.message
                    : obj, "error", null, null, null, true);
            }
            
            //this.info(apf.vardump(obj, null, false).replace(/ /g, "&nbsp;").replace(/</g, "&lt;"));
        }
        
        
    },

    html_entity_decode: function(s){return s},
    htmlentities: function(s){return s},

    /**
     * Formats an Ajax.org Platform error message.
     * @param {Number}      number      The number of the error. This can be used to look up more information about the error.
     * @param {apf.AmlElement}  control     The aml element that will throw the error.
     * @param {String}      process     The action that was being executed.
     * @param {String}      message     The actual error message.
     * @param {XMLElement}  amlContext  The XML relevant to the error. For instance, this could be a piece of Ajax.org Markup Language XML.
     */
    formatErrorString: function(number, control, process, message, amlContext, outputname, output) {
        
        apf.lastErrorMessage = message;
        return message;
        
    },

    /* Init */

    /**
     * Returns the directory portion of a URL.
     * @param {String} url The URL to retrieve from
     * @returns {String} The directory portion of a URL
     */
    getDirname: function(url) {
        //(?:\w+\:\/\/)?
        return ((url || "").match(/^([^#]*\/)[^\/]*(?:$|\#)/) || {})[1]; //Mike will check out how to optimize this line
    },
    
    /**
     * Returns the file portion of a URL.
     * @param {String} url The URL to retrieve from.
     * @return {String} The file portion of a URL.
     */
    getFilename: function(url) {
        return ((url || "").split("?")[0].match(/(?:\/|^)([^\/]+)$/) || {})[1];
    },
    
    /**
     * Returns an absolute url based on url.
     * @param {String} base The start of the URL where relative URLs work.
     * @param {String} url  The URL to transform.
     * @return {String} The absolute URL.
     */
    getAbsolutePath: function(base, url) {
        return url && url.charAt(0) == "/"
            ? url
            : (!url || !base || url.match(/^\w+\:\/\//) ? url : base.replace(/\/$/, "") + "/" + url.replace(/^\//, ""));
    },
    
    getCtrlKey: function(event) {
        return apf.isMac ? event.metaKey : event.ctrlKey;
    },

    /**
     * Loads Javascript from a specific URL.
     * 
     * @param {String}    sourceFile The URL where the JavaScript is located.
     * @param {Boolean}   [doBase]   Checks for a base path via [[apf.getAbsolutePath]]
     * @param {String}    [type]     Sets the type of a script tag, for later use
     * @param {String}    [text]     
     * @param {Function}  [callback] Calls this function after the script is loaded
     * @returns {String} The constructed script tag    
     */
    include: function(sourceFile, doBase, type, text, callback) {
        
        
        var sSrc = doBase ? apf.getAbsolutePath(apf.basePath || "", sourceFile) : sourceFile;
        var head = document.getElementsByTagName("head")[0],//$("head")[0]
            elScript = document.createElement("script");
        //elScript.defer = true;
        if (type)
            elScript.setAttribute("_apf_type", type);
        if (text) {
            if (apf.isIE)
                window.execScript(text);
            else
                elScript.text = text;
        }
        else 
            elScript.src = sSrc;
        head.appendChild(elScript);

        if (callback)
            elScript[apf.isIE ? "onreadystatechange" : "onload"] = callback;
        
        return elScript;
    },
    
    $required: [],
    require: function(){
        var dir = apf.getDirname(location.href),
            i = 0,
            l = arguments.length;
        for (; i < l; i++)
            this.$required.push(apf.getAbsolutePath(dir, arguments[i]));
    },

    /**
     * @private
     */
    Init: {
        queue: [],
        cond: {
            combined: []
        },
        done: {},

        add: function(func, o) {
            if (this.inited)
                func.call(o);
            else if (func)
                this.queue.push([func, o]);
        },

        addConditional: function(func, o, strObj) {
            if (typeof strObj != "string") {
                if (this.checkCombined(strObj))
                    return func.call(o);
                this.cond.combined.push([func, o, strObj]);
            }
            else if (self[strObj]) {
                func.call(o);
            }
            else {
                if (!this.cond[strObj])
                    this.cond[strObj] = [];
                this.cond[strObj].push([func, o]);

                this.checkAllCombined();
            }
        },

        checkAllCombined: function(){
            for (var i = 0; i < this.cond.combined.length; i++) {
                if (!this.cond.combined[i]) continue;

                if (this.checkCombined(this.cond.combined[i][2])) {
                    this.cond.combined[i][0].call(this.cond.combined[i][1])
                    this.cond.combined[i] = null;
                }
            }
        },
        
        checkCombined: function(arr) {
            for (var i = 0; i < arr.length; i++) {
                if (!this.done[arr[i]])
                    return false;
            }

            return true;
        },

        run: function(strObj) {
            this.inited = this.done[strObj] = true;

            this.checkAllCombined();

            var data = strObj ? this.cond[strObj] : this.queue;
            if (!data) return;
            for (var i = 0; i < data.length; i++)
                data[i][0].call(data[i][1]);
        }
    },

    
    
    

    /**
     * Determines the way APF tries to render this application. Set this value
     * before APF is starts parsing.
     *   
     * Possible values include:
     *   - 0    Auto (The default)
     *   - 1    Partial
     *   - 11   Partial from a comment
     *   - 2    Full from serialized document or file fallback
     *   - 21   Full from file
     * @type {Number}
     */
    parseStrategy: 0,

    

    /**
     * @private
     */
    parseAppMarkup: function(docElement) {
        var isEmptyDocument = false;
        
        if (document.documentElement.getAttribute("skipParse") == "true") {
            return;
        }
        
        

        

        

        
    },
    
    namespaces: {},
    setNamespace: function(namespaceURI, oNamespace) {
        this.namespaces[namespaceURI] = oNamespace;
        oNamespace.namespaceURI = namespaceURI;
    },

    /**
     * @private
     */
    initialize: function(xmlStr) {
        
        
        

        apf.console.info("Initializing...");
        clearInterval(apf.Init.interval);

        // Run Init
        apf.Init.run(); //Process load dependencies
        
        
        
        var bodyMarginTop = parseFloat(apf.getStyle(document.body, "marginTop"));
        apf.doesNotIncludeMarginInBodyOffset = (document.body.offsetTop !== bodyMarginTop);

        
        {
            apf.window.init(xmlStr);
        }
    },

    
    /**
     * @private
     */
    execDeferred: function() {
        // execute each function in the stack in the order they were added
        var len = apf.load_events.length;
        while (len--)
            (apf.load_events.shift())();
    },

    load_events: [],
    load_timer: null,
    load_done: false,
    load_init: null,

    /**
     * @private
     */
    addDomLoadEvent: function(func) {
        if (!this.$bdetect)
            this.browserDetect();

        if (apf.load_done)
            return func();

        // create event function stack
        //apf.done = arguments.callee.done;
        if (!apf.load_init) {
            apf.load_init = function() {
                if (apf.load_done) return;
                // kill the timer
                clearInterval(apf.load_timer);
                apf.load_timer = null;
                apf.load_done = true;
                if (apf.started)
                    apf.execDeferred();
            };
        }

        apf.load_events.push(func);

        if (func && apf.load_events.length == 1) {
            // Catch cases where addDomLoadEvent() is called after the browser
            // event has already occurred.
            var doc = document, UNDEF = "undefined";
            if ((typeof doc.readyState != UNDEF && doc.readyState == "complete")
              || (doc.getElementsByTagName("body")[0] || doc.body))
                return apf.load_init();

            // for Mozilla/Opera9.
            // Mozilla, Opera (see further below for it) and webkit nightlies
            // currently support this event
            if (doc.addEventListener && !apf.isOpera) {
                // We're using "window" and not "document" here, because it results
                // in a memory leak, especially in FF 1.5:
                // https://bugzilla.mozilla.org/show_bug.cgi?id=241518
                // See also:
                // http://bitstructures.com/2007/11/javascript-method-callbacks
                // http://www-128.ibm.com/developerworks/web/library/wa-memleak/
                window.addEventListener("DOMContentLoaded", apf.load_init, false);
            }
            // If IE is used and is not in a frame
            else if (apf.isIE && window == top) {
                apf.load_timer = setInterval(function() {
                    try {
                        // If IE is used, use the trick by Diego Perini
                        // http://javascript.nwbox.com/IEContentLoaded/
                        doc.documentElement.doScroll("left");
                    }
                    catch (ex) {
                        $setTimeout(arguments.callee, 0);
                        return;
                    }
                    // no exceptions anymore, so we can call the init!
                    apf.load_init();
                }, 10);
            }
            else if (apf.isOpera) {
                doc.addEventListener("DOMContentLoaded", function() {
                    apf.load_timer = setInterval(function() {
                        for (var i = 0, l = doc.styleSheets.length; i < l; i++) {
                            if (doc.styleSheets[i].disabled)
                                return;
                        }
                        // all is fine, so we can call the init!
                        apf.load_init();
                    }, 10);
                }, false);
            }
            else if (apf.isWebkit && !apf.isIphone) {
                var aSheets = doc.getElementsByTagName("link"),
                    i = aSheets.length,
                    iSheets;
                for (; i >= 0; i++) {
                    if (!aSheets[i] || aSheets[i].getAttribute("rel") != "stylesheet")
                        aSheets.splice(i, 0);
                }
                iSheets = aSheets.length;
                apf.load_timer = setInterval(function() {
                    if (/loaded|complete/.test(doc.readyState)
                      && doc.styleSheets.length == iSheets)
                        apf.load_init(); // call the onload handler
                }, 10);
            }
            // for other browsers set the window.onload, but also execute the
            // old window.onload
            else {
                var old_onload = window.onload;
                window.onload = function () {
                    apf.load_init();
                    if (old_onload)
                        old_onload();
                };
            }
        }
    },
    
    
    fireEvent: function(el, type, e, capture) {
        if (el.dispatchEvent)
            el.dispatchEvent(type, e, capture);
        else
            el.fireEvent("on" + type, e);
    },
    
    addListener: function(el, type, fn, capture) {
        if (el.addEventListener)
            el.addEventListener(type, fn, capture || false);
        else if (el.attachEvent)
            el.attachEvent("on" + type, fn);
        return this;
    },
    
    removeListener: function(el, type, fn, capture) {
        if (el.removeEventListener)
            el.removeEventListener(type, fn, capture || false);
        else if (el.detachEvent)
            el.detachEvent("on" + type, fn);
        return this;
    },

    stopEvent: function(e) {
        this.stopPropagation(e).preventDefault(e);
        return false;
    },

    stopPropagation: function(e) {
        if (e.stopPropagation)
            e.stopPropagation();
        else
            e.cancelBubble = true;
        return this;
    },

    preventDefault: function(e) {
        if (e.preventDefault)
            e.preventDefault();
        else
            e.returnValue = false;
        return this;
    },

    /* Destroy */

    /**
     * Unloads the aml application.
     */
    unload: function(exclude) {
        
    }
};

/*
 * Replacement for getElementsByTagNameNS because some browsers don't support
 * this call yet.
 */
var $xmlns = function(xmlNode, tag, xmlns, prefix) {
    if (!apf.supportNamespaces) {
        if (!prefix)
            prefix = apf.findPrefix(xmlNode, xmlns);

        if (xmlNode.style || xmlNode == document)
            return xmlNode.getElementsByTagName(tag)
        else {
            if (prefix)
                (xmlNode.nodeType == 9 ? xmlNode : xmlNode.ownerDocument)
                    .setProperty("SelectionNamespaces",
                        "xmlns:" + prefix + "='" + xmlns + "'");

            return xmlNode.selectNodes(".//" + (prefix ? prefix + ":" : "") + tag);
        }
    }
    return xmlNode.getElementsByTagNameNS(xmlns, tag);
};

var $setTimeout = setTimeout;
var $setInterval = setInterval;

apf.setTimeout = function(f, t) {
    apf.$eventDepth++;
    return $setTimeout(function(){
        f();
        
        if (--apf.$eventDepth == 0)
            apf.queue.empty();
    }, t);
}

/*$setTimeout = function(f, ms) {
    setTimeout(function(){
        console.log(f.toString());
        if (typeof f == "string") eval(f)
        else f();
    }, ms);
}*/

document.documentElement.className += " has_apf";
document.body && (document.body.style.display = "none");

apf.browserDetect();
apf.Init.run("apf");










/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * All elements that implemented this {@link term.baseclass baseclass} have
 * {@link term.propertybinding property binding},
 * event handling and constructor & destructor hooks. The event system is
 * implemented following the W3C specification, similar to the
 * {@link http://en.wikipedia.org/wiki/DOM_Events event system of the HTML DOM}.
 *
 * @class apf.Class
 *
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */

/**
 * @event propertychange Fires when a property changes.
 * @param {Object} e An object containing the following properties:
 * - name ([[String]]): The name of the changed property
 * - originalvalue (`Mixed`): The value it had before the change
 * - value (`Mixed`): The value it has after the change
 *
 */
apf.Class = function(){};

apf.Class.prototype = new (function(){
    // privates
    var FUN = "function",
        OBJ = "object",
        UNDEF = "undefined",
        SEL = "model", //selected|selection|properties|
        PROP = "prop.",
        MODEL = "model",
        VALUE = "value";

    this.$regbase = 0;
    /**
     * Tests whether this object has implemented a {@link term.baseclass baseclass}.
     * @param {Number} test The unique number of the {@link term.baseclass baseclass}.
     */
    this.hasFeature = function(test) {
        return this.$regbase & test;
    };

    this.$initStack = [];
    this.$bufferEvents = [];
    this.$init = function(callback, nodeFunc, struct) {
        if (typeof callback == FUN || callback === true) {
            this.$bufferEvents = this.$bufferEvents.slice();

            if (callback === true)
                return this;

            this.$initStack = this.$initStack.slice(); //Our own private stack
            this.$initStack.push(callback);

            return this;
        }

        this.addEventListener = realAddEventListener;
        //this.$removalQueue = [];

        if (this.nodeType != 2) //small little hack
            this.$uniqueId = apf.all.push(this) - 1;

        this.$captureStack = {};
        this.$eventsStack = {};
        this.$funcHandlers = {};

        var i = 0, l = this.$initStack.length;
        for (; i < l; i++)
            this.$initStack[i].apply(this, arguments);

        for (i = 0, l = this.$bufferEvents.length; i < l; i++)
            this.addEventListener.apply(this, this.$bufferEvents[i]);

        delete realAddEventListener;
        delete this.$initStack;
        delete this.$bufferEvents;

        if (struct && (struct.htmlNode || this.nodeFunc == apf.NODE_HIDDEN)) {
            this.$pHtmlNode = struct.htmlNode;

            
                if (this.ownerDocument && this.ownerDocument.$domParser)
                    this.ownerDocument.$domParser.$continueParsing(this);

                
                apf.queue.empty();
                
            
        }

        return this;
    };

    this.implement = apf.implement;

    // **** Property Binding **** //

    this.$handlePropSet = function(prop, value) {
        this[prop] = value;
    };

    

    /**
     * Binds a property of another compontent to a property of this element.
     *
     * @param {String}  myProp           the name of the property of this element
     *                                   of which the value is communicated to
     *                                   `bObject`.
     * @param {Class}   bObject          the object which will receive the property
     *                                   change message.
     * @param {String}  bProp            the property of `bObject` which
     *                                   will be set using the value of
     *                                   `myProp` optionally
     *                                   processed using `strDynamicProp`.
     * @param {String}  [strDynamicProp] a javascript statement which contains the
     *                                   value of `myProp`. The string
     *                                   is used to calculate a new value.
     * @private
     */
    this.$bindProperty = function(myProp, bObject, bProp, fParsed, bRecip) {
        if (!fParsed)
            return bObject.$handlePropSet(bProp, this[myProp]);

        var eventName = PROP + myProp, eFunc, isBeingCalled, isLang;
        (this.$eventsStack[eventName] || (this.$eventsStack[eventName] = [])).push(eFunc = function(e) {
            if (isBeingCalled) //Prevent circular refs
                return;

            
            isBeingCalled = true;

            try {
                if (fParsed.asyncs) { //if async
                    return fParsed.call(bObject, bObject.xmlRoot, function(value) {
                        bObject.setProperty(bProp, value, true, false, 10);

                        

                        isBeingCalled = false;
                    });
                }
                else {
                    var value = fParsed.call(bObject, bObject.xmlRoot);
                }
            }
            catch (e) {
                apf.console.warn("[331] Could not execute binding for property "
                    + bProp + "\n\n" + e.message);

                isBeingCalled = false;

                return;
            }

            //Can't do this when using xml nodes, doesnt seem needed anyway
            //if (bObject[bProp] != value)
                bObject.setProperty(bProp, value, true, false, 10);//e.initial ? 0 :

            

            isBeingCalled = false;
        });

        //Bi-directional property binding
        if (bRecip) {
            eventName = PROP + bProp;
            var _self = this;
            // add bidirectional binding to funcHandlers for visualconnect
            

            (bObject.$eventsStack[eventName] || (bObject.$eventsStack[eventName] = [])).push(
                eFunc.recip = function(){
                    if (isBeingCalled) //Prevent circular refs
                        return;

                    isBeingCalled = true;
                    _self.setProperty(myProp, bObject[bProp], false, false, 10);//e.initial ? 0 :
                    isBeingCalled = false;
                });
        };

        //eFunc({initial: true});

        return eFunc;
    };

    /**
     * Sets a dynamic property from a string.
     *
     * The string used for this function is the same as used in AML to set a
     * dynamic property:
     * ```xml
     *  <a:button visible="{rbTest.value == 'up'}" />
     *  <a:textbox id="rbTest" value="" />
     * ```
     *
     * @param {String}   prop   The name of the property of this element to set
     *                          using a dynamic rule.
     * @param {String}   pValue The dynamic property binding rule.
     */
    this.$attrExcludePropBind = false;
    this.$setDynamicProperty = function(prop, pValue) {
        var exclNr = this.$attrExcludePropBind[prop],
            options;

        //@todo apf3.0, please generalize this - cache objects, seems slow
        if (SEL.indexOf(prop) > -1 || exclNr == 3) {
            options = {
                xpathmode: 2
                //parsecode : true //@todo is this also good for exclNr 3 ?
            }
        }
        else if (exclNr == 2) {
            options = {nostring : true};
        }
        else if (exclNr === 0) {
            options = {
                parsecode: true
                
            };
        }

        if (this.liveedit)
            (options || (options = {})).liveedit = true;

        

        //Compile pValue through JSLT parser
        
        {
            var fParsed = apf.lm.compile(pValue, options);
        }

        //Special case for model due to needed extra signalling
        if (prop == MODEL)
            (this.$modelParsed = fParsed).instruction = pValue
        

        //if it's only text return setProperty()
        if (fParsed.type == 2) {
            this[prop] = !pValue; //@todo apf3.0 is this needed?
            return this.setProperty(prop, fParsed.str, null, null, 10); //@todo is 10 here right?
        }

        //if there's xpath: Add apf.DataBinding if not inherited.
        //Add compiled binding rule. Load databinding if not loaded.
        
        var check = 1;
        if (exclNr == 2 || fParsed.xpaths.length && exclNr != 1) {
            if (!this.hasFeature(apf.__DATABINDING__)) {
                this.implement(apf.StandardBinding);
                if (this.$attrExcludePropBind[prop] == 1)
                    check = 0;
            }

            if (check)
                this.$addAttrBind(prop, fParsed, pValue);
        }
        

        //if there's prop binding: Add generated function to each obj/prop in the list
        var matches = exclNr && exclNr != 3 && prop != MODEL ? {} : fParsed.props, //@todo apf3.0 sign of broken abstraction, please fix this with a bit mask
            found = false,
            _self = this,
            o, node, bProp, p;

        for (p in matches) {
            

            o = p.split(".");
            if (o.length > 2) { //apf.offline.syncing
                bProp = o.pop();
                try{
                    try { node = eval(o.join(".")); } catch (e) {}
                }
                catch (e) {
                    if (arguments[2]) {
                        apf.console.warn("[287] Could not execute binding test : "
                            + pValue.replace(/</g, "&lt;") + "\n\n" + e.message);
                    }
                    else {
                        apf.queue.add(prop + ":" + this.$uniqueId, function(){
                            _self.$clearDynamicProperty(prop);
                            _self.$setDynamicProperty(prop, pValue, true);
                        });
                    }
                    continue;
                }

                if (!node || typeof node != OBJ || (!node.$regbase && node.$regbase !== 0)) {
                    bProp = o[1];
                    node = self[o[0]] || apf.nameserver.get("all", o[0]);
                }
                else {
                    o.push(bProp);
                }
            }
            else {
                bProp = o[1];
                node = self[o[0]] || apf.nameserver.get("all", o[0]) || o[0] == "this" && this;
            }

            if (!node) {
                if (arguments[2]) {
                    apf.console.warn("[287] Could not create property binding: "
                        + " '"  + o[0] + "' does not exist. \n"
                        + pValue.replace(/</g, "&lt;").substr(0, 400));

                    var _self = this;
                    apf.nameserver.waitFor(o[0], function(){
                        _self.$setDynamicProperty(prop, pValue);
                    })
                    return;
                }
                else {
                    //@todo this is sloppy and not efficient - shouldn't clear
                    //and reset and should check if was changed or removed when
                    //it's set
                    apf.queue.add(prop + ":" + this.$uniqueId, function(){
                        _self.$clearDynamicProperty(prop);
                        _self.$setDynamicProperty(prop, pValue, true);
                    });
                    return;
                }
            }

            if (!node.$bindProperty)
                continue;  //return

            if (!this.$funcHandlers[prop])
                this.$funcHandlers[prop] = [];

            var last;
            this.$funcHandlers[prop].push(last = {
                amlNode: node,
                prop: bProp,
                handler: node.$bindProperty(bProp, this, prop, fParsed,
                    //@todo check if it breaks something. I removed
                    // "&& exclNr != 3" from the expression to enable two way
                    // binding of selections
                    fParsed.type == 4 && SEL.indexOf(prop) == -1) /*,
                bidir   :
                  && this.$bindProperty(prop, node, bProp, function(){
                    return _self[prop];
                  })*/
            });

            found = true;
        }

        if (found) {
            last.handler({initial: true});
        }
        else {
            //@todo optimize this
            if (exclNr)
                return this.setProperty(prop, pValue, null, null, 10); //@todo is 10 here right?

            

            try {
                if (fParsed.asyncs) { //if async
                    return fParsed.call(this, this.xmlRoot, function(value) {
                        _self.setProperty(prop, value, true, null, 10); //@todo is 10 here right?

                        
                    });
                }
                else {
                    var value = fParsed.call(this, this.xmlRoot);
                }
            }
            catch (e) {
                apf.console.warn("[331] Could not execute binding test or: "
                    + pValue.replace(/</g, "&lt;") + "\n\n" + e.message);
                return;
            }

            this[prop] = !value; //@todo isnt this slow and unneccesary?
            this.setProperty(prop, value, true, null, 10); //@todo is 10 here right?

            
        }
    };

    //@todo setAttribute should delete this from apf.language when not doing
    //$setDynamicProperty
    this.$clearDynamicProperty = function(prop) {
        if (this.$removeAttrBind)
            this.$removeAttrBind(prop);

        

        if (this.$inheritProperties)
            delete this.$inheritProperties[prop];

        if (prop == MODEL)
            this.$modelParsed = null;

        //Remove any bounds if relevant
        var f, i, l, h = this.$funcHandlers[prop];
        if (h && typeof h != FUN) {
            for (i = 0, l = h.length; i < l; i++) {
                (f = h[i]).amlNode.removeEventListener(PROP + f.prop, f.handler);
                if (f.handler && f.handler.recip) //@todo handler shouldn't be unset - how does this happen?
                    this.removeEventListener(PROP + prop, f.handler.recip);
            }
            delete this.$funcHandlers[prop];
        }
    };

    

    

    /**
     * Gets an array of properties for this element which can be bound.
     * @returns {Array}
     */
    this.getAvailableProperties = function(){
        return this.$supportedProperties.slice();
    };

    /**
     * Sets the value of a property of this element.
     *
     * Note: The value is the only thing set. Dynamic properties remain bound and the
     * value will be overridden.
     *
     * @param {String}   prop        The name of the property of this element to
     *                               set using a dynamic rule.
     * @param {String}   value       The value of the property to set.
     * @param {Boolean}  [forceOnMe] Specifies whether the property should be set even when
     *                               it has the same value.
     */
    this.setProperty = function(prop, value, forceOnMe, setAttr, inherited) {
        var s, r, arr, e, i, l,
            oldvalue = this[prop],
            eventName = PROP + prop;//@todo prop event should be called too;

        //Try catch here, because comparison of a string with xmlnode gives and error in IE
        try{
            var isChanged = (typeof value == OBJ)
                ? value != (typeof oldvalue == OBJ ? oldvalue : null)
                : (this.$booleanProperties && this.$booleanProperties[prop]
                    ? oldvalue != apf.isTrue(value)
                    : String(oldvalue) !== String(value));
        } catch (e) {
            var isChanged = true;
        }

        //Check if property has changed
        if (isChanged) {
            if (!forceOnMe) { //Recursion protection
                //Check if this property is bound to data
                if (typeof value != OBJ //this.xmlRoot &&
                  && (!(s = this.$attrExcludePropBind[prop]))// || s == 2
                  && (r = (this.$attrBindings && this.$attrBindings[prop]
                  || prop != VALUE && this.xmlRoot && this.$bindings[prop]
                  && this.$bindings[prop][0]))) {

                    //Check if rule has single xpath
                    if (r.cvalue.type == 3) {
                        

                        //Set the xml value - this should probably use execProperty
                        return apf.setNodeValue(
                            this.$getDataNode(prop.toLowerCase(), this.xmlRoot, true),
                            value, true);
                    }
                }
                
            }

            if (setAttr && !this.$funcHandlers[prop])
                this.setAttribute(prop, value, true);

            if (this.$handlePropSet(prop, value, forceOnMe) === false)
                return;

            

            value = this[prop];
        }

        //Optimized event calling
        if ((arr = this.$eventsStack[eventName]) && isChanged) {
            /*for (i = 0, l = arr.length; i < l; i++) {
                if (arr[i].call(this, e || (e = new apf.AmlEvent(eventName, {
                    prop: prop,
                    value: value,
                    oldvalue: oldvalue
                }))) === false) {
                    e.returnValue = false;
                }
            }*/
            if (this.dispatchEvent(eventName, {
                prop: prop,
                value: value,
                oldvalue: oldvalue,
                changed: isChanged
            }) === false) {
                e.returnValue = false;
            }
        }

        
        /*
            States:
                    -1 Set
             undefined Pass through
                     2 Inherited
                     3 Semi-inherited
                    10 Dynamic property
        */
        //@todo this whole section should be about attribute inheritance and moved
        //      to AmlElement
        if ((aci || (aci = apf.config.$inheritProperties))[prop]) {
            //@todo this is actually wrong. It should be about removing attributes.
            var resetting = value === "" || typeof value == "undefined";
            if (inherited != 10 && !value) {
                delete this.$inheritProperties[prop];
                if (this.$setInheritedAttribute && this.$setInheritedAttribute(prop))
                    return;
            }
            else if (inherited != 10) { //Keep the current setting (for dynamic properties)
                this.$inheritProperties[prop] = inherited || -1;
            }

            //cancelable, needed for transactions
            //@todo the check on $amlLoaded is not as optimized as can be because $loadAml is not called yet
            if (this.$amlLoaded && (!e || e.returnValue !== false) && this.childNodes) {
                var inheritType = aci[prop];

                (function recur(nodes) {
                    var i, l, node, n;
                    for (i = 0, l = nodes.length; i < l; i++) {
                        node = nodes[i];
                        if (node.nodeType != 1 && node.nodeType != 7)
                            continue;

                        //Pass through
                        n = node.$inheritProperties[prop];
                        if (inheritType == 1 && !n)
                            recur(node.childNodes);

                        //Set inherited property
                        //@todo why are dynamic properties overwritten??
                        else if (!(n < 0)) {//Will also pass through undefined - but why??? @todo seems inefficient
                            if (n == 3 || inherited == 3) { //Because when parent sets semi-inh. prop the value can be the same
                                var sameValue = node[prop];
                                node[prop] = null;
                            }
                            node.setProperty(prop, n != 3
                                ? value
                                : sameValue, false, false, n || 2); //This is recursive already
                        }
                    }
                })(this.childNodes);
            }
        }
        

        return value;
    };
    var aci;

    /**
     * Gets the value of a property of this element.
     *
     * @param {String}   prop   The name of the property of this element for which to get the value.
     */
    this.getProperty = function(prop) {
        return this[prop];
    };

    // *** Event Handling ****/

    apf.$eventDepth = 0;
    this.$eventDepth = 0;

    /**
     * Calls all functions that are registered as listeners for an event.
     *
     * @param {String}   eventName  The name of the event to dispatch.
     * @param {Object}   [options]  The properties of the event object that will be created and passed through. These can be:
     *  - bubbles ([[Boolean]]): Specifies whether the event should bubble up to it's parent
     *  - captureOnly ([[Boolean]]): Specifies whether only the captured event handlers should be executed
     * @return {Mixed} return value of the event
     */
    this.dispatchEvent = function(eventName, options, e) {
    //var allowEvents = {"DOMNodeInsertedIntoDocument":1,"DOMNodeRemovedFromDocument":1};
        var arr, result, rValue, i, l;

        if (!apf.AmlEvent)
            return;

        apf.$eventDepth++;
        this.$eventDepth++;

        e = options && options.name ? options : e;

        /*if (this.disabled && !allowEvents[eventName]) {
            result = false;
        }
        else {*/
            

            //@todo rewrite this and all dependencies to match w3c
            if ((!e || !e.currentTarget) && (!options || !options.currentTarget)) {
                if (!(options || (options = {})).currentTarget)
                    options.currentTarget = this;

                //Capture support
                if (arr = this.$captureStack[eventName]) {
                    for (i = 0, l = arr.length; i < l; i++) {
                        rValue = arr[i].call(this, e || (e = new apf.AmlEvent(eventName, options)));
                        if (typeof rValue != UNDEF)
                            result = rValue;
                    }
                }
            }

            //@todo this should be the bubble point

            if (options && options.captureOnly) {
                return e && typeof e.returnValue != UNDEF ? e.returnValue : result;
            }
            else {
                if (this["on" + eventName]) {
                    result = this["on" + eventName].call(this, e
                        || (e = new apf.AmlEvent(eventName, options))); //Backwards compatibility
                }

                if (arr = this.$eventsStack[eventName]) {
                    for (i = 0, l = arr.length; i < l; i++) {
                        if (!arr[i]) continue;
                        rValue = arr[i].call(this, e
                            || (e = new apf.AmlEvent(eventName, options)));
                        if (typeof rValue != UNDEF)
                            result = rValue;
                    }
                }
            }
        //}

        /*var p;
        while (this.$removalQueue.length) {
            p = this.$removalQueue.shift();
            p[0].remove(p[1]);
        }*/

        
        if ((e && e.bubbles && !e.cancelBubble || !e && options && options.bubbles) && this != apf) {
            rValue = (this.parentNode || this.ownerElement || apf).dispatchEvent(eventName, options, e);
            // || (e = new apf.AmlEvent(eventName, options))

            if (typeof rValue != UNDEF)
                result = rValue;
        }
        

        if (--apf.$eventDepth == 0 && this.ownerDocument
          && !this.ownerDocument.$domParser.$parseContext
          && !apf.isDestroying && apf.loaded
          
          && apf.queue
        ) {
            apf.queue.empty();
        }

        this.$eventDepth--;

        

        if (options) {
            try {
                delete options.currentTarget;
            }
            catch (ex) {
                options.currentTarget = null;
            }
        }

        return e && typeof e.returnValue != UNDEF ? e.returnValue : result;
    };

    /**
     * Adds a function to be called when a event is called.
     *
     * @param {String}    eventName The name of the event for which to register
     *                              a function.
     * @param {Function}  callback  The code to be called when an event is dispatched.
     */
    this.addEventListener = function(a, b, c) {
        this.$bufferEvents.push([a,b,c]);
    };

    var realAddEventListener = function(eventName, callback, useCapture) {
        

        if (eventName.substr(0, 2) == "on")
            eventName = eventName.substr(2);

        var s, stack = useCapture ? this.$captureStack : this.$eventsStack;
        if (!(s = stack[eventName]))
            s = stack[eventName] = [];

        if (s.indexOf(callback) > -1)
            return;

        s.unshift(callback);

        var f;
        if (f = this.$eventsStack["$event." + eventName])
            f[0].call(this, callback);
    };

    /**
     * Removes a function registered for an event.
     *
     * @param {String}    eventName The name of the event for which to unregister
     *                              a function.
     * @param {Function}  callback  The function to be removed from the event stack.
     */
    this.removeEventListener = function(eventName, callback, useCapture) {
        var stack = (useCapture ? this.$captureStack : this.$eventsStack)[eventName];

        //@todo is this the best way?
        if (stack) {
            if (this.$eventDepth)
                stack = (useCapture ? this.$captureStack : this.$eventsStack)[eventName] = stack.slice()

            stack.remove(callback);
            if (!stack.length)
                delete (useCapture ? this.$captureStack : this.$eventsStack)[eventName];
        }
    };

    /**
     * Checks if there is an event listener specified for the event.
     *
     * @param {String}   eventName  The name of the event to check.
     * @return {Boolean} Specifies whether the event has listeners
     */
    this.hasEventListener = function(eventName) {
        return (this.$eventsStack[eventName] && this.$eventsStack[eventName].length > 0);
    };

    /**
     * The destructor of a Class.
     * Calls all the destructor functions, and removes all memory leaking references.
     * This function is called when exiting the application or closing the window.
     * @param {Boolean} deep whether the children of this element should be destroyed.
     * @param {Boolean} [clean]
     */
    this.destroy = function(deep, clean) {
        //Remove from apf.all
        if (typeof this.$uniqueId == UNDEF && this.nodeType != 2)
            return;

        this.$amlLoaded = false;
        this.$amlDestroyed = true;

        if (this.$destroy)
            this.$destroy();

        this.dispatchEvent("DOMNodeRemoved", {
            relatedNode: this.parentNode,
            bubbles: !apf.isDestroying
        });
        this.dispatchEvent("DOMNodeRemovedFromDocument");

        apf.all[this.$uniqueId] = undefined;

        // != 2 && this.nodeType != 3
        if (!this.nodeFunc && !this.nodeType) { //If this is not a AmlNode, we're done.
            //Remove id from global js space
            try {
                if (this.id || this.name)
                    self[this.id || this.name] = null;
            }
            catch (ex) {}
            return;
        }

        if (this.$ext && !this.$ext.isNative) { // && this.$ext.nodeType == 1
            if (this.nodeType == 1 && this.localName != "a")
                this.$ext.oncontextmenu = this.$ext.host = null;
            if (clean) {
                if (this.localName != "collection" && this.$ext.parentNode)
                    this.$ext.parentNode.removeChild(this.$ext);
            }
        }
        if (this.$int && !this.$int.isNative && this.$int.nodeType == 1 && this.localName != "a")
            this.$int.host = null;

        //if (this.$aml && this.$aml.parentNode)
            //this.$aml.parentNode.removeChild(this.$aml);
        this.$aml = null;

        //Clear all children too
        if (deep && this.childNodes) {
            var nodes = this.childNodes;
            for (i = nodes.length - 1; i >= 0; i--) {
                if (nodes[i].destroy)
                    nodes[i].destroy(true, clean && this.localName == "collection");
            }
            this.childNodes = null;
        }

        //Remove from DOM tree if we are still connected
        if (this.parentNode && this.removeNode)
            this.removeNode();
        else if (this.ownerElement && !this.ownerElement.$amlDestroyed)
            this.ownerElement.removeAttributeNode(this);

        //Remove from focus list - Should be in AmlNode
        
        if (this.$focussable && this.focussable)
            apf.window.$removeFocus(this);
        

        
        //Remove dynamic properties
        /*var f, i, l, h;
        for (prop in this.$funcHandlers) {
            h = this.$funcHandlers[prop];

            //Remove any bounds if relevant
            if (h && typeof h != FUN) {
                for (i = 0, l = h.length; i < l; i++) {
                    (f = h[i]).amlNode.removeEventListener(PROP + f.prop, f.handler);
                }
            }
        }*/
        

        if (this.attributes) {
            var attr = this.attributes;
            for (var i = attr.length - 1; i >= 0; i--) {
                
                this.$clearDynamicProperty(attr[i].nodeName);
                
                attr[i].destroy();
            }
        }

        

        //Remove id from global js space
        try {
            if (this.id || this.name)
                delete self[this.id || this.name];
        }
        catch (ex) {}

        for (var prop in this.$captureStack) this.$captureStack[prop] = null;
        for (var prop in this.$eventsStack) this.$eventsStack[prop] = null;
        for (var prop in this.$funcHandlers) this.$funcHandlers[prop] = null;

        if (this.$bufferEvents) {
            for (var i = this.$bufferEvents.length - 1; i >= 0; i--)
                this.$bufferEvents = null;
        }

        
        apf.nameserver.remove(this.localName, this);
        
    };
})();

apf.extend(apf, new apf.Class().$init());
apf.Init.run("class");





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.color = {
/*
    colors: {
        aliceblue: "#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",
        aquamarine: "#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",
        black: "#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",
        blueviolet: "#8a2be2",brown:"#a52a2a",burlywood:"#deb887",
        cadetblue: "#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",
        coral: "#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",
        crimson: "#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",
        darkgoldenrod: "#b8860b",darkgray:"#a9a9a9",darkgrey:"#a9a9a9",
        darkgreen: "#006400",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",
        darkolivegreen: "#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",
        darkred: "#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",
        darkslateblue: "#483d8b",darkslategray:"#2f4f4f",
        darkslategrey: "#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",
        deeppink: "#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",
        dimgrey: "#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",
        floralwhite: "#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",
        gainsboro: "#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",
        goldenrod: "#daa520",gray:"#808080",grey:"#808080",green:"#008000",
        greenyellow: "#adff2f",honeydew:"#f0fff0",hotpink:"#ff69b4",
        indianred: "#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",
        lavender: "#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",
        lemonchiffon: "#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",
        lightcyan: "#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",
        lightgrey: "#d3d3d3",lightgreen:"#90ee90",lightpink:"#ffb6c1",
        lightsalmon: "#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",
        lightslategray: "#778899",lightslategrey:"#778899",
        lightsteelblue: "#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",
        limegreen: "#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",
        mediumaquamarine: "#66cdaa",mediumblue:"#0000cd",
        mediumorchid: "#ba55d3",mediumpurple:"#9370d8",mediumseagreen:"#3cb371",
        mediumslateblue: "#7b68ee",mediumspringgreen:"#00fa9a",
        mediumturquoise: "#48d1cc",mediumvioletred:"#c71585",
        midnightblue: "#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",
        moccasin: "#ffe4b5",navajowhite:"#ffdead",navy:"#000080",
        oldlace: "#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",
        orangered: "#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",
        palegreen: "#98fb98",paleturquoise:"#afeeee",palevioletred:"#d87093",
        papayawhip: "#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",
        plum: "#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",
        rosybrown: "#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",
        salmon: "#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",
        seashell: "#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",
        slateblue: "#6a5acd",slategray:"#708090",slategrey:"#708090",
        snow: "#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",
        teal: "#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",
        violet: "#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",
        yellow: "#ffff00",yellowgreen:"#9acd32"
    },*/
    colorshex: {
        aliceblue: 0xf0f8ff,antiquewhite:0xfaebd7,aqua:0x00ffff,
        aquamarine: 0x7fffd4,azure:0xf0ffff,beige:0xf5f5dc,bisque:0xffe4c4,
        black: 0x000000,blanchedalmond:0xffebcd,blue:0x0000ff,
        blueviolet: 0x8a2be2,brown:0xa52a2a,burlywood:0xdeb887,
        cadetblue: 0x5f9ea0,chartreuse:0x7fff00,chocolate:0xd2691e,
        coral: 0xff7f50,cornflowerblue:0x6495ed,cornsilk:0xfff8dc,
        crimson: 0xdc143c,cyan:0x00ffff,darkblue:0x00008b,darkcyan:0x008b8b,
        darkgoldenrod: 0xb8860b,darkgray:0xa9a9a9,darkgrey:0xa9a9a9,
        darkgreen: 0x006400,darkkhaki:0xbdb76b,darkmagenta:0x8b008b,
        darkolivegreen: 0x556b2f,darkorange:0xff8c00,darkorchid:0x9932cc,
        darkred: 0x8b0000,darksalmon:0xe9967a,darkseagreen:0x8fbc8f,
        darkslateblue: 0x483d8b,darkslategray:0x2f4f4f,
        darkslategrey: 0x2f4f4f,darkturquoise:0x00ced1,darkviolet:0x9400d3,
        deeppink: 0xff1493,deepskyblue:0x00bfff,dimgray:0x696969,
        dimgrey: 0x696969,dodgerblue:0x1e90ff,firebrick:0xb22222,
        floralwhite: 0xfffaf0,forestgreen:0x228b22,fuchsia:0xff00ff,
        gainsboro: 0xdcdcdc,ghostwhite:0xf8f8ff,gold:0xffd700,
        goldenrod: 0xdaa520,gray:0x808080,grey:0x808080,green:0x008000,
        greenyellow: 0xadff2f,honeydew:0xf0fff0,hotpink:0xff69b4,
        indianred: 0xcd5c5c,indigo:0x4b0082,ivory:0xfffff0,khaki:0xf0e68c,
        lavender: 0xe6e6fa,lavenderblush:0xfff0f5,lawngreen:0x7cfc00,
        lemonchiffon: 0xfffacd,lightblue:0xadd8e6,lightcoral:0xf08080,
        lightcyan: 0xe0ffff,lightgoldenrodyellow:0xfafad2,lightgray:0xd3d3d3,
        lightgrey: 0xd3d3d3,lightgreen:0x90ee90,lightpink:0xffb6c1,
        lightsalmon: 0xffa07a,lightseagreen:0x20b2aa,lightskyblue:0x87cefa,
        lightslategray: 0x778899,lightslategrey:0x778899,
        lightsteelblue: 0xb0c4de,lightyellow:0xffffe0,lime:0x00ff00,
        limegreen: 0x32cd32,linen:0xfaf0e6,magenta:0xff00ff,maroon:0x800000,
        mediumaquamarine: 0x66cdaa,mediumblue:0x0000cd,
        mediumorchid: 0xba55d3,mediumpurple:0x9370d8,mediumseagreen:0x3cb371,
        mediumslateblue: 0x7b68ee,mediumspringgreen:0x00fa9a,
        mediumturquoise: 0x48d1cc,mediumvioletred:0xc71585,
        midnightblue: 0x191970,mintcream:0xf5fffa,mistyrose:0xffe4e1,
        moccasin: 0xffe4b5,navajowhite:0xffdead,navy:0x000080,
        oldlace: 0xfdf5e6,olive:0x808000,olivedrab:0x6b8e23,orange:0xffa500,
        orangered: 0xff4500,orchid:0xda70d6,palegoldenrod:0xeee8aa,
        palegreen: 0x98fb98,paleturquoise:0xafeeee,palevioletred:0xd87093,
        papayawhip: 0xffefd5,peachpuff:0xffdab9,peru:0xcd853f,pink:0xffc0cb,
        plum: 0xdda0dd,powderblue:0xb0e0e6,purple:0x800080,red:0xff0000,
        rosybrown: 0xbc8f8f,royalblue:0x4169e1,saddlebrown:0x8b4513,
        salmon: 0xfa8072,sandybrown:0xf4a460,seagreen:0x2e8b57,
        seashell: 0xfff5ee,sienna:0xa0522d,silver:0xc0c0c0,skyblue:0x87ceeb,
        slateblue: 0x6a5acd,slategray:0x708090,slategrey:0x708090,
        snow: 0xfffafa,springgreen:0x00ff7f,steelblue:0x4682b4,tan:0xd2b48c,
        teal: 0x008080,thistle:0xd8bfd8,tomato:0xff6347,turquoise:0x40e0d0,
        violet: 0xee82ee,wheat:0xf5deb3,white:0xffffff,whitesmoke:0xf5f5f5,
        yellow: 0xffff00,yellowgreen:0x9acd32
    },
    fixHSB: function (hsb) {
        return {
            h: Math.min(360, Math.max(0, hsb.h)),
            s: Math.min(100, Math.max(0, hsb.s)),
            b: Math.min(100, Math.max(0, hsb.b))
        };
    },

    fixRGB: function (rgb) {
        return {
            r: Math.min(255, Math.max(0, rgb.r)),
            g: Math.min(255, Math.max(0, rgb.g)),
            b: Math.min(255, Math.max(0, rgb.b))
        };
    },

    fixHex: function (hex, asBrowser) {
        hex = hex.toLowerCase().replace(/[^a-f0-9]/g, "");
        var len = 6 - hex.length;
        if (len > 0) {
            var ch = "0";
            var o = [];
            var i = 0;
            if (asBrowser) {
                ch = hex.charAt(hex.length - 1);
                o.push(hex);
            }
            for (; i < len; i++)
                o.push(ch);
            if (!asBrowser)
                o.push(hex);
            hex = o.join("");
        }
        return hex;
    },
    
    hexToRGB: function (hex) {
        hex = parseInt(((hex.indexOf("#") > -1) ? hex.substring(1) : hex), 16);
        return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
    },

    hexToHSB: function (hex) {
        return this.RGBToHSB(this.hexToRGB(hex));
    },

    RGBToHSB: function (rgb) {
        var hsb = {
            h: 0,
            s: 0,
            b: 0
        };
        var min = Math.min(rgb.r, rgb.g, rgb.b),
            max = Math.max(rgb.r, rgb.g, rgb.b),
            delta = max - min;
        hsb.b = max;
        if (max != 0) { }
        hsb.s = max != 0 ? 255 * delta / max : 0;
        if (hsb.s != 0) {
            if (rgb.r == max)
                hsb.h = (rgb.g - rgb.b) / delta;
            else if (rgb.g == max)
                hsb.h = 2 + (rgb.b - rgb.r) / delta;
            else
                hsb.h = 4 + (rgb.r - rgb.g) / delta;
        }
        else
            hsb.h = -1;
        hsb.h *= 60;
        if (hsb.h < 0)
            hsb.h += 360;
        hsb.s *= 100/255;
        hsb.b *= 100/255;
        return hsb;
    },
    
    HSBToRGB: function(hsb) {
        var rgb = {},
            h = Math.round(hsb.h),
            s = Math.round(hsb.s * 255 / 100),
            v = Math.round(hsb.b * 255 / 100);
        if (s == 0)
            rgb.r = rgb.g = rgb.b = v;
        else {
            var t1 = v,
                t2 = (255 - s) * v / 255,
                t3 = (t1 - t2) * (h % 60)/60;
            if (h == 360)
                h = 0;
            if (h < 60)
                rgb.r = t1, rgb.b = t2, rgb.g = t2 + t3;
            else if (h < 120)
                rgb.g = t1, rgb.b = t2, rgb.r = t1 - t3;
            else if (h < 180)
                rgb.g = t1, rgb.r = t2, rgb.b = t2 + t3;
            else if (h < 240)
                rgb.b = t1, rgb.r = t2, rgb.g = t1 - t3;
            else if (h < 300)
                rgb.b = t1, rgb.g = t2, rgb.r = t2 + t3;
            else if (h < 360)
                rgb.r = t1, rgb.g = t2, rgb.b = t1 - t3;
            else
                rgb.r = 0, rgb.g = 0, rgb.b = 0;
        }
        return {r: Math.round(rgb.r), g: Math.round(rgb.g), b: Math.round(rgb.b)};
    },

    RGBToHex: function(rgb) {
        return ('00000'+(rgb.r<<16 | rgb.g<<8 | rgb.b).toString(16)).slice(-6);
    },

    HSBToHex: function(hsb) {
        return this.RGBToHex(this.HSBToRGB(hsb));
    }
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
  





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/**
 * Performs an async function in serial on each of the list items.
 * 
 * @param {Array} list A list of elements to iterate over asynchronously
 * @param {Function} async An ssync function of the form `function(item, callback)`
 * @param {Function} callback A function of the form `function(error)`, which is
 *      called after all items have been processed
 */
apf.asyncForEach = function(list, async, callback) {
    var i = 0;
    var len = list.length;

    if (!len) return callback(null, []);

    async(list[i], function handler(err) {
        if (err) return callback(err);
        i++;

        if (i < len) {
            async(list[i], handler, i);
        } else {
            callback(null);
        }
    }, i);
};

/**
 * Performs an async function in serial, as long as the function 'condition' (first 
 * argument) evaluates to true.
 * 
 * @param {Function} condition A function that returns a [Boolean], which determines
 *                             if the loop should continue
 * @param {Function} async     async A function of the form `function(iteration_no, callback)`
 * @param {Function} callback  A function of the form `function(error)`, which is
 *                             called after all items have been processed
 */
apf.asyncWhile = function(condition, async, callback) {
    var i = 0;
    async(i, function handler(err) {
        if (err)
            return callback ? callback(err, i) : null;

        ++i;
        if (condition(i))
            async(i, handler);
        else
            callback && callback(null, i);
    });
};

/**
 * Maps each element from the list to the result returned by the async mapper
 * function. 
 *
 * The mapper takes an element from the list and a callback as arguments.
 * After completion, the mapper has to call the callback with an (optional) error
 * object as the first argument, and the result of the map as second argument. After all
 * list elements have been processed, the last callback is called with the mapped
 * array as second argument.
 * 
 * @param {Array} list A list of elements to iterate over asynchronously
 * @param {Function}  mapper A function of the form `function(item, next)`
 * @param {Function} callback A function of the form `function(error, result)`
 */
apf.asyncMap = function(list, mapper, callback) {
    var i = 0;
    var len = list.length;

    if (!len) return callback(null, []);
    var map = [];

    async(list[i], function handler(err, value) {
        if (err) return callback(err);
        
        map[i] = value;
        i++;

        if (i < len) {
            async(list[i], handler);
        } else {
            callback(null, map);
        }
    });
};


/**
 * Chains an array of functions. 
 *
 * Each of the functions (except the last one) must
 * have exactly one `callback` argument, which must be called after the functions has
 * finished. If the callback fails, it must pass a non-null error object as the
 * first argument to the callback.
 * 
 * @param {Array} funcs An array of functions to chain together.
 */
apf.asyncChain = function(funcs) {
    var i = 0;
    var len = funcs.length;
    
    function next() {
        var f = funcs[i++];
        if (i == len)
            f()
        else
            f(next)
    }
    
    next();
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
 


/**
 * Sets a name/value pair which is stored in the browser and sent to the server
 * with every request. This is also known as a cookie. 
 * Warning: Be careful setting cookies, because they can take up a lot of bandwidth, 
 * especially for Ajax applications.
 * 
 * @param {String}  name     The cookie name
 * @param {String}  value    The cookie value
 * @param {Date}    expire   The expiration date representing the number of milliseconds
 *                            since 1 January 1970 00:00:00 UTC.
 * @param {String}  path     The path name
 * @param {String}  domain   The domain name
 * @param {Boolean} secure   If true, the secure attribute of the cookie will be set and 
 *                            the cookie transmission will require a secure protocol (like HTTPS)
 * @return {String} Returns the cookie name.
 */
apf.setcookie = function(name, value, expire, path, domain, secure) {
    var ck = name + "=" + escape(value) + ";";
    if (expire) ck += "expires=" + new Date(expire
        + new Date().getTimezoneOffset() * 60).toGMTString() + ";";
    if (path)   ck += "path=" + path + ";";
    if (domain) ck += "domain=" + domain + ";";
    if (secure) ck += "secure";

    document.cookie = ck;
    return value
};

/**
 * Gets the value of a stored name/value cookie pair.
 * 
 * @param {String} name The name of the stored cookie.
 * @return {String} Returns a value of the cookie, or the empty string if it isn't found
 */
apf.getcookie = function(name) {
  var aCookie = document.cookie.split("; ");
  for (var i = 0; i < aCookie.length; i++) {
      var aCrumb = aCookie[i].split("=");
      if (name == aCrumb[0])
          return unescape(aCrumb[1]);
  }

  return "";
};

/**
 * Deletes a stored name/value pair called a cookie.
 * 
 * @param {String} name     The name of the stored cookie
 * @param {String} domain   The name of the domain of stored cookie
 */
apf.delcookie = function (name, domain) {
    document.cookie = name + "=blah; expires=Fri, 31 Dec 1999 23:59:59 GMT;"
        + (domain ? 'domain='+domain : '');
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



// start closure:
//(function(){

if (typeof isFinite == "undefined") {
    function isFinite(val) {
        return val + 1 != val;
    }
}

apf.NUMBER = 1;
apf.BOOLEAN = 2;
apf.STRING = 3;
apf.ARRAY = 4;
apf.DATE = 5;
apf.REGEXP = 6;
apf.FUNCTION = 7;

Array.prototype.dataType = apf.ARRAY;
Number.prototype.dataType = apf.NUMBER;
Date.prototype.dataType = apf.DATE;
Boolean.prototype.dataType = apf.BOOLEAN;
String.prototype.dataType = apf.STRING;
RegExp.prototype.dataType = apf.REGEXP;
Function.prototype.dataType = apf.FUNCTION;

/**
 * Converts a JavaScript object to a CGI string.
 *
 * @param {Object} args The object to convert
 * @param {Boolean} [multicall] Indicates if there will be a function call name
 * @param {Function} [mcallname] A name to append to the CGI string, prefixed with `func=`
 * @returns {Object} The converted object, joined with `"&"`
 *
 * @see apf.convertXml
 */
apf.getCgiString = function(args, multicall, mcallname) {
    var prop, vars = [];

    function recur(o, stack) {
        if (apf.isArray(o)) {
            for (var j = 0; j < o.length; j++)
                recur(o[j], stack + "%5B%5D");//" + j + "
        }
        else if (typeof o == "object") {
            for (prop in o) {
                if (apf.isSafariOld && (!o[prop] || typeof o[prop] != "object"))
                    continue;

                if (typeof o[prop] == "function")
                    continue;
                recur(o[prop], stack + "%5B" + encodeURIComponent(prop) + "%5D");
            }
        }
        else
            vars.push(stack + "=" + encodeURIComponent(o));
    };

    if (multicall) {
        vars.push("func=" + mcallname);
        for (var i = 0; i < args[0].length; i++)
            recur(args[0][i], "f%5B" + i + "%5D");
    }
    else {
        for (prop in args) {
            if (apf.isSafariOld && (!args[prop] || typeof args[prop] == "function"))
                continue;

            recur(args[prop], prop);
        }
    }

    return vars.join("&");
};

/**
 * Converts a CGI string to a JavaScript object.
 *
 * @param {String} args The argument string to convert, separated by `"&"`
 * @returns {Object} The converted object
 *
 * @see apf.convertXml
 */
apf.fromCgiString = function(args) {
    if (!args)
        return false;

    var obj = {};
    args = args.split("&");
    for (var data, i = 0; i < args.length; i++) {
        data = args[i].split("=");
        data[0] = decodeURIComponent(data[0]);
        var path = data[0].replace(/\]/g, "").split("[");

        var spare = obj;
        for (var j = 0; j < path.length; j++) {
            if (spare[path[j]])
                spare = spare[path[j]];
            else if (path.length == j+1) {
                if (path[j])
                    spare[path[j]] = decodeURIComponent(data[1]);
                else
                    spare.push(decodeURIComponent(data[1]));
                break; //assuming last
            }
            else{
                spare[path[j]] = !path[j+1] ? [] : {};
                spare = spare[path[j]];
            }
        }
    }

    return obj;
}




/*
 * Extends a Function object with properties from other objects, specified as
 * arguments.
 *
 * @param {Mixed} obj1, obj2, obj3, etc.
 * @type Function
 * @see apf.extend
 */
Function.prototype.extend = function() {
    apf.extend.apply(this, [this].concat(Array.prototype.slice.call(arguments)));
    return this;
};

/*
 * Attach a Function object to an event as handler method. If apf.AbstractEvent
 * is available, the active event is extended with convinience accessors as
 * declared in apf.AbstractEvent
 *
 * @param {Object} The context the execute the Function within
 * @param {Boolean} Whether the passed event object should be extended with AbstractEvent
 * @param {Mixed}  param1, param2, param3, etc.
 * @type Function
 * @see apf.AbstractEvent
 */
Function.prototype.bindWithEvent = function() {
    var __method = this,
        args = Array.prototype.slice.call(arguments),
        o = args.shift(),
        ev = args.shift();
    return function(event) {
        if (!event)
            event = window.event;
        
        return __method.apply(o, [event].concat(args)
            .concat(Array.prototype.slice.call(arguments)));
    }
};

/*
 * The bind function creates a new function (a bound function) that calls the
 * function that is its this value (the bound function's target function) with
 * a specified this parameter, which cannot be overridden. bind also accepts
 * leading default arguments to provide to the target function when the bound
 * function is called.  A bound function may also be constructed using the new
 * operator: doing so acts as though the target function had instead been
 * constructed.  The provided this value is ignored, while prepended arguments
 * are provided to the emulated function.
 *
 * @param {Object} context The 'this' context of the bound function
 * @type Function
 */
if (!Function.prototype.bind)
    Function.prototype.bind = function(context /*, arg1, arg2... */) {
        if (typeof this !== 'function') throw new TypeError();
        var _arguments = Array.prototype.slice.call(arguments, 1),
            _this = this,
            _concat = Array.prototype.concat,
            _function = function() {
                return _this.apply(this instanceof _dummy ? this : context,
                    _concat.apply(_arguments, arguments));
            },
            _dummy = function() {};
        _dummy.prototype = _this.prototype;
        _function.prototype = new _dummy();
        return _function;
};

/*
 * Copy an array, like this statement would: 'this.concat([])', but then do it
 * recursively.
 */
Array.prototype.copy = function(){
    var ar = [];
    for (var i = 0, j = this.length; i < j; i++)
        ar[i] = this[i] && this[i].copy ? this[i].copy() : this[i];

    return ar;
};

/*
 * Concatenate the current Array instance with one (or more) other Arrays, like
 * Array.concat(), but return the current Array instead of a new one that
 * results from the merge.
 *
 * @param {Array} array1, array2, array3, etc.
 * @type  {Array}
 */
Array.prototype.merge = function(){
    for (var i = 0, k = arguments.length; i < k; i++) {
        for (var j = 0, l = arguments[i].length; j < l; j++) {
            this.push(arguments[i][j]);
        }
    }
};

/*
 * Add the values of one or more arrays to the current instance by using the
 * '+=' operand on each value.
 *
 * @param {Array} array1, array2, array3, etc.
 * @type  {Array}
 * @see Array.copy
 */
Array.prototype.arrayAdd = function(){
    var s = this.copy();
    for (var i = 0, k = arguments.length; i < k; i++) {
        for (var j = 0, l = s.length; j < l; j++) {
            s[j] += arguments[i][j];
        }
    }

    return s;
};

/*
 * Check if an object is contained within the current Array instance.
 *
 * @param {Mixed}   obj The value to check for inside the Array
 * @type  {Boolean}
 */
Array.prototype.equals = function(obj) {
    for (var i = 0, j = this.length; i < j; i++)
        if (this[i] != obj[i])
            return false;
    return true;
};

/*
 * Make sure that an array instance contains only unique values (NO duplicates).
 * Elaborate implementation to allow for O(n) time complexity compared to O(n^2)
 * time complexity when using Array.prototype.indexOf.
 * @see http://bbenvie.com/articles/2012-06-10/Array-prototype-unique-in-O-n-time-complexity
 * @see http://jsperf.com/array-unique2/9
 *
 * @type {Array}
 */
var uniqueBenvie = function(){
    var hasOwn = {}.hasOwnProperty,
        uids = {};

    // use hash for primitives and tagging for objects
    function uid(){
        var chars = [], i = 20, num;
        while (i--) {
            num = Math.random() * 52 | 0;
            chars[i] = String.fromCharCode(num + (num >= 26 ? 71 : 65));
        }
        chars = chars.join("");

        if (chars in uids)
            return uid();

        uids[chars] = true;
        return chars;
    }

    function unique(array) {
        var strings = {}, numbers = {}, others = {},
            tagged = [], failed = [],
            count = 0, i = array.length,
            item, type;

        var id = uid();

        while (i--) {
            item = array[i];
            type = typeof item;
            if (item === null || type !== "object" && type !== "function") {
                // primitive
                switch (type) {
                    case "string":
                        strings[item] = true;
                        break;
                    case "number":
                        numbers[item] = true;
                        break;
                    default:
                        others[item] = item;
                        break;
                }
            }
            else {
                // object
                if (!hasOwn.call(item, id)) {
                    try {
                        item[id] = true;
                        tagged[count++] = item;
                    }
                    catch (e) {
                        if (failed.indexOf(item) === -1)
                            failed[failed.length] = item;
                    }
                }
            }
        }

        // remove the tags
        while (count--)
            delete tagged[count][id];

        tagged = tagged.concat(failed);
        count = tagged.length;

        // append primitives to results
        for (i in strings)
            if (hasOwn.call(strings, i))
                tagged[count++] = i;

        for (i in numbers)
            if (hasOwn.call(numbers, i))
                tagged[count++] = +i;

        for (i in others)
            if (hasOwn.call(others, i))
                tagged[count++] = others[i];

        return tagged;
    }

    return unique;
}();

if (typeof Set !== "undefined") {
    Array.prototype.makeUnique = function(){
        var out = [],
            seen = new Set,
            i = this.length;

        while (i--) {
            if (!seen.has(this[i])) {
                out[out.length] = this[i];
                seen.add(this[i]);
            }
        }

        return out;
    }
}
else {
    Array.prototype.makeUnique = function(){
        return uniqueBenvie(this);
    };
}

/*
 * Check if this array instance contains a value 'obj'.
 *
 * @param {Mixed}  obj    The value to check for inside the array
 * @param {Number} [from] Left offset index to start the search from
 * @type  {Boolean}
 * @see Array.indexOf
 */
Array.prototype.contains = function(obj, from) {
    return this.indexOf(obj, from) != -1;
};

/*
 * Search for the index of the first occurence of a value 'obj' inside an array
 * instance.
 * July 29, 2008: added 'from' argument support to indexOf()
 *
 * @param {Mixed}  obj    The value to search for inside the array
 * @param {Number} [from] Left offset index to start the search from
 * @type  {Number}
 */
Array.prototype.indexOf = Array.prototype.indexOf || function(obj, from) {
    var len = this.length;
    for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++) {
        if (this[i] === obj)
            return i;
    }
    return -1;
};

/*
 * Search for the index of the last occurence of a value 'obj' inside an array
 * instance.
 *
 * @param {Mixed}  obj    The value to search for inside the array
 * @param {Number} [from] Left offset index to start the search from
 * @type  {Number}
 */
Array.prototype.lastIndexOf = Array.prototype.lastIndexOf || function(obj, from) {
    //same as indexOf(), but in reverse loop, JS spec 1.6
    var len = this.length;
    for (var i = (from >= len) ? len - 1 : (from < 0) ? from + len : len - 1; i >= 0; i--) {
        if (this[i] === obj)
            return i;
    }
    return -1;
};

/*
 * Like Array.push, but only invoked when the value 'item' is already present
 * inside the array instance.
 *
 * @param {Mixed} item, item, ...
 * @type  {Array}
 */
Array.prototype.pushUnique = function(){
    var item,
        i = 0,
        l = arguments.length;
    for (; i < l; ++i) {
        item = arguments[i];
    if (this.indexOf(item) == -1)
        this.push(item);
    }
    return this;
};

/*
 * @todo: Ruben: could you please comment on this function? Seems to serve a very
 * specific purpose...
 *
 * I also could not find an occurrence in our codebase.
 */
Array.prototype.search = function(){
    for (var i = 0, length = arguments.length; i < length; i++) {
        if (typeof this[i] != "array")
            continue;
        for (var j = 0; j < length; j++) {
            if (this[i][j] != arguments[j])
                break;
            else if (j == (length - 1))
                return this[i];
        }
    }
};

/*
 * Iterate through each value of an array instance from left to right (front to
 * back) and execute a callback Function for each value.
 *
 * @param {Function} fn
 * @type  {Array}
 */
Array.prototype.each =
Array.prototype.forEach = Array.prototype.forEach || function(fn) {
    for (var i = 0, l = this.length; i < l; i++)
        if (fn.call(this, this[i], i, this) === false)
            break;
    return this;
}

/*
 * Search for a value 'obj' inside an array instance and remove it when found.
 *
 * @type {Mixed} obj
 * @type {Array}
 */
Array.prototype.remove = function(obj) {
    for (var i = this.length - 1; i >= 0; i--) {
        if (this[i] != obj)
            continue;

        this.splice(i, 1);
    }

    return this;
};

/*
 * Remove an item from an array instance which can be identified with key 'i'
 *
 * @param {Number}  i
 * @return {Mixed}  The removed item
 */
Array.prototype.removeIndex = function(i) {
    if (!this.length) return null;
    return this.splice(i, 1);
};

/*
 * Insert a new value at a specific object; alias for Array.splice.
 *
 * @param {Mixed}  obj Value to insert
 * @param {Number} i   Index to insert 'obj' at
 * @type  {Number}
 */
Array.prototype.insertIndex = function(obj, i) {
    this.splice(i, 0, obj);
};

/*
 * Reverses the order of the elements of an array; the first becomes the last,
 * and the last becomes the first.
 *
 * @type {Array}
 */
Array.prototype.invert =
Array.prototype.reverse = Array.prototype.reverse || function(){
    var l = this.length - 1;
    for (var temp, i = 0; i < Math.ceil(0.5 * l); i++) {
        temp = this[i];
        this[i] = this[l - i]
        this[l - i] = temp;
    }

    return this;
};



/*
 * Attempt to fully comply (in terms of functionality) with the JS specification,
 * up 'till version 1.7:
 * @link http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Array
 */

/*
 * Creates a new array with all of the elements of this array for which the
 * provided filtering function returns true.
 *
 * @param {Function} fn   Function to test each element of the array.
 * @param {Object}   bind Object to use as this when executing callback.
 * @type  {Array}
 */
Array.prototype.filter = Array.prototype.filter || function(fn, bind) {
    var results = [];
    for (var i = 0, l = this.length; i < l; i++) {
        if (fn.call(bind, this[i], i, this))
            results.push(this[i]);
    }
    return results;
};

/*
 * Returns true if every element in this array satisfies the provided testing
 * function.
 *
 * @param {Function} fn   Function to test for each element.
 * @param {Object}   bind Object to use as this when executing callback.
 * @type  {Boolean}
 */
Array.prototype.every = Array.prototype.every || function(fn, bind) {
    for (var i = 0, l = this.length; i < l; i++) {
        if (!fn.call(bind, this[i], i, this))
            return false;
    }
    return true;
};

/*
 * Creates a new array with the results of calling a provided function on every
 * element in this array.
 *
 * @param {Function} fn   Function that produces an element of the new Array from an element of the current one.
 * @param {Object}   bind Object to use as this when executing callback.
 * @type  {Array}
 */
Array.prototype.map = Array.prototype.map || function(fn, bind) {
    var results = [];
    for (var i = 0, l = this.length; i < l; i++)
        results[i] = fn.call(bind, this[i], i, this);
    return results;
};

/*
 * Tests whether some element in the array passes the test implemented by the
 * provided function.
 *
 * @param {Function} fn   Function to test for each element.
 * @param {Object}   bind Object to use as this when executing callback.
 * @type  {Boolean}
 */
Array.prototype.some = Array.prototype.some || function(fn, bind) {
    for (var i = 0, l = this.length; i < l; i++) {
        if (fn.call(bind, this[i], i, this))
            return true;
    }
    return false;
};



/*
 * Transform a number to a string and pad it with a zero digit its length is one.
 *
 * @type {String}
 */
Number.prototype.toPrettyDigit = Number.prototype.toPrettyDigit || function() {
    var n = this.toString();
    return (n.length == 1) ? "0" + n : n;
};

RegExp.prototype.getNativeFlags = function() {
    return (this.global     ? "g" : "") +
           (this.ignoreCase ? "i" : "") +
           (this.multiline  ? "m" : "") +
           (this.extended   ? "x" : "") +
           (this.sticky     ? "y" : "");
};

/*
 * Accepts flags; returns a new XRegExp object generated by recompiling
 * the regex with the additional flags (may include non-native flags).
 * the original regex object is not altered.
 */
RegExp.prototype.addFlags = function(flags) {
    return new RegExp(this.source, (flags || "") + this.getNativeFlags());
};

/*
 * Casts the first character in a string to uppercase.
 *
 * @type {String}
 */
String.prototype.uCaseFirst = function(){
    return this.substr(0, 1).toUpperCase() + this.substr(1)
};

/*
 * Removes spaces and other space-like characters from the left and right ends
 * of a string
 *
 * @type {String}
 */
String.prototype.trim = function(){
    return this.replace(/[\s\n\r]*$/, "").replace(/^[\s\n\r]*/, "");
};

/*
 * Concatenate a string with itself n-times.
 *
 * @param {Number} times Number of times to repeat the String concatenation
 * @type  {String}
 */
String.prototype.repeat = function(times) {
    return Array(times + 1).join(this);
};

/*
 * Count the number of occurences of substring 'str' inside a string
 *
 * @param {String} str
 * @type  {Number}
 */
String.prototype.count = function(str) {
    return this.split(str).length - 1;
};

/*
 * Remove HTML or any XML-like tags from a string
 *
 * @type {String}
 */
String.prototype.stripTags = function() {
    return this.replace(/<\/?[^>]+>/gi, "");
};

/*
 * Wrapper for the global 'escape' function for strings
 *
 * @type {String}
 */
String.prototype.escape = function() {
    return escape(this);
};

/*
 * Returns an xml document
 * @type {XMLElement}
 */
String.prototype.toXml = function(){
    var node = apf.getXml("<root>" + this + "</root>");
    if (node.childNodes.length == 1) {
        return node.childNodes[0];
    }
    else {
        var docFrag = node.ownerDocument.createDocumentFragment(),
            nodes = node.childNodes;
        while (nodes.length)
            docFrag.appendChild(nodes[0]);
        return docFrag;
    }
};


if (typeof window != "undefined" && typeof window.document != "undefined"
  && typeof window.document.createElement == "function") {
    /*
     * Encode HTML entities to its HTML equivalents, like '&amp;' to '&amp;amp;'
     * and '&lt;' to '&amp;lt;'.
     *
     * @type {String}
     * @todo is this fast?
     */
    String.prototype.escapeHTML = function() {
        this.escapeHTML.text.data = this;
        return this.escapeHTML.div.innerHTML;
    };

    /*
     * Decode HTML equivalent entities to characters, like '&amp;amp;' to '&amp;'
     * and '&amp;lt;' to '&lt;'.
     *
     * @type {String}
     */
    String.prototype.unescapeHTML = function() {
        var div = document.createElement("div");
        div.innerHTML = this.stripTags();
        if (div.childNodes[0]) {
            if (div.childNodes.length > 1) {
                var out = [];
                for (var i = 0; i < div.childNodes.length; i++)
                    out.push(div.childNodes[i].nodeValue);
                return out.join("");
            }
            else
                return div.childNodes[0].nodeValue;
        }
        return "";
    };

    String.prototype.escapeHTML.div = document.createElement("div");
    String.prototype.escapeHTML.text = document.createTextNode("");
    String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

    if ("<\n>".escapeHTML() !== "&lt;\n&gt;")
        String.prototype.escapeHTML = null;

    if ("&lt;\n&gt;".unescapeHTML() !== "<\n>")
        String.prototype.unescapeHTML = null;
}

if (!String.prototype.escapeHTML) {
    String.prototype.escapeHTML = function() {
        return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
    };
}

if (!String.prototype.unescapeHTML) {
    String.prototype.unescapeHTML = function() {
        return this.stripTags().replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&");
    };
}

/*
 * Trim a string down to a specific number of characters. Optionally, append an
 * ellipsis ('...') as a suffix.
 *
 * @param {Number}  nr
 * @param {Boolean} [ellipsis] Append an ellipsis
 * @type  {String}
 */
String.prototype.truncate = function(nr, ellipsis) {
    return this.length >= nr
        ? this.substring(0, nr - (ellipsis ? 4 : 1)) + (ellipsis ? "..." : "")
        : this;
};

/*
 * Pad a string at the right or left end with a string 'pad' to a specific
 * number of characters. Highly optimized version for speed, not readability.
 *
 * @param {Number}  len   Specifies the amount of characters required to pad to.
 * @param {String}  pad   Specifies the character(s) to pad the string with
 * @param {Boolean} [dir] Specifies at which end to append the 'pad' character (left or right).
 * @type  {String}
 */
String.prototype.pad = function(len, pad, dir) {
    return dir ? (this + Array(len).join(pad)).slice(0, len)
        : (Array(len).join(pad) + this).slice(-len);
};

apf.PAD_LEFT = false;
apf.PAD_RIGHT = true;

/*
 * Special String.split; optionally lowercase a string and trim all results from
 * the left and right.
 *
 * @param {String}  separator
 * @param {Number}  limit      Maximum number of items to return
 * @param {Boolean} bLowerCase Flag to lowercase the string prior to split
 * @type  {String}
 */
String.prototype.splitSafe = function(separator, limit, bLowerCase) {
    return (bLowerCase && this.toLowerCase() || this)
        .replace(/(?:^\s+|\n|\s+$)/g, "")
        .split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999);
};

/*
 * Appends a random number with a specified length to this String instance.
 *
 * @see randomGenerator
 * @param {Number} length
 * @type  {String}
 */
String.prototype.appendRandomNumber = function(length) {
    for (var arr = [], i = 1; i <= length; i++)
        arr.push(apf.randomGenerator.generate(1, 9));
    // Create a new string from the old one, don't just create a copy
    return this.toString() + arr.join("");
};

/*
 * Prepends a random number with a specified length to this String instance.
 *
 * @see randomGenerator
 * @param {Number} length
 * @type  {String}
 */
String.prototype.prependRandomNumber = function(length) {
    for (var arr = [], i = 1; i <= length; i++)
        arr.push(apf.randomGenerator.generate(1, 9));
    // Create a new string from the old one, don't just create a copy
    return arr.join("") + this.toString();
};

/*
 * Returns a string produced according to the formatting string. It replaces
 * all <i>%s</i> occurrences with the arguments provided.
 *
 * @link http://www.php.net/sprintf
 * @type {String}
 */
String.prototype.sprintf = function() {
    // Create a new string from the old one, don't just create a copy
    var str = this.toString(),
        i = 0,
        inx = str.indexOf("%s");
    while (inx >= 0) {
        var replacement = arguments[i++] || " ";
        str = str.substr(0, inx) + replacement + str.substr(inx + 2);
        inx = str.indexOf("%s");
    }
    return str;
};

/*
 * The now method returns the milliseconds elapsed since
 * 1 January 1970 00:00:00 UTC up until now as a number.
 *
 * @type {Number}
 */
if (!Date.now) {
    Date.now = function now() {
        return +new Date();
    };
}

//})(); //end closure






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */







//@todo maybe generalize this to pub/sub event system??
/**
 * @private
 */
apf.hotkeys = {};
(function() {
    /**
     * @private
     */
    var keyMods = {"ctrl": 1, "alt": 2, "option" : 2, "shift": 4, "meta": 8, "command": 8};

    /**
     * @private
     */
    this.keyNames = {
        "8"  : "Backspace",
        "9"  : "Tab",
        "13" : "Enter",
        "27" : "Esc",
        "32" : "Space",
        "33" : "PageUp",
        "34" : "PageDown",
        "35" : "End",
        "36" : "Home",
        "37" : "Left",
        "38" : "Up",
        "39" : "Right",
        "40" : "Down",
        "45" : "Insert",
        "46" : "Del",
        "107": "+",
        "112": "F1",
        "113": "F2",
        "114": "F3",
        "115": "F4",
        "116": "F5",
        "117": "F6",
        "118": "F7",
        "119": "F8",
        "120": "F9",
        "121": "F10",
        "122": "F11",
        "123": "F12",
        "188": ",",
        "219": "[",
        "221": "]"
    };

    var macUnicode = {
        "meta"     : "\u2318", // ⌘
        "command"  : "\u2318",
        "alt"      : "\u2325", // ⌥
        "option"   : "\u2325",
        "shift"    : "\u21E7", // ⇧
        //"esc"      : "\u238B", // ⎋
        "ctrl"     : "\u2303", // ⌃
        "backspace": "\u232B", // ⌫
        "del"      : "\u2326", // ⌦
        "enter"    : "\u21A9"  // ↩
    };
    
    var macUnicodeHtml = {
        "meta"     : "&#8984;", // ⌘
        "command"  : "&#8984;",
        "alt"      : "&#8997;", // ⌥
        "option"   : "&#8997;",
        "shift"    : "&#8679;", // ⇧
        //"esc"      : "&#9099;", // ⎋
        "ctrl"     : "&#2303;", // ⌃ TODO
        "backspace": "&#232B;", // ⌫ TODO
        "del"      : "&#2326;", // ⌦ TODO
        "enter"    : "&#21A9;"  // ↩ TODO
    };

    // hash to store the hotkeys in
    this.$keys = {};

    var _self = this, trace = 0;
    
    function register(hotkey, handler, remove) {
        var key,
            hashId = 0,
            keys = hotkey.splitSafe("\\-", null, true),
            i = 0,
            l = keys.length;

        for (; i < l; ++i) {
            if (keyMods[keys[i]])
                hashId = hashId | keyMods[keys[i]];
            else
                key = keys[i] || "-"; //when empty, the splitSafe removed a '-'
        }

        
        if (!key) return;
        

        if (!_self.$keys[hashId])
            _self.$keys[hashId] = {};

        if (remove) {
            if (handler == _self.$keys[hashId][key])
                _self.$keys[hashId][key] = null;
        }
        else
            _self.$keys[hashId][key] = handler;
    }

    /**
     * Registers a hotkey handler to a key combination.
     * 
     * #### Example:
     * ```javascript
     *   apf.registerHotkey('Ctrl-Z', undoHandler);
     * ```
     * @param {String}   hotkey  The key combination to user. This is a
     * combination of [[keys: Ctrl]], [[keys: Alt]], [[keys: Shift]] and a normal key to press. Use `+` to
     * seperate the keys.
     * @param {Function} handler The code to be executed when the key
     * combination is pressed.
     */
    apf.registerHotkey = this.register = function(hotkey, handler) {
        var parts = hotkey.split("|"),
            i = 0,
            l = parts.length;
        for (; i < l; ++i)
            register(parts[i], handler);
    };

    this.$exec = function(eInfo) {
        var handler
        var hashId = 0 | (eInfo.ctrlKey ? 1 : 0) | (eInfo.altKey ? 2 : 0)
            | (eInfo.shiftKey ? 4 : 0) | (eInfo.metaKey ? 8 : 0);
        var code = eInfo.keyCode;

        var key = _self.keyNames[code] 
            || (code && code > 46 && code != 91 ? String.fromCharCode(code) : null);
        if (!hashId && (!key || !key.match(/^F\d{1,2}$/)) || !key) //Hotkeys should always have one of the modifiers
            return;

        if (_self.$keys[hashId] && (handler = _self.$keys[hashId][key.toLowerCase()])) {
            handler(eInfo.htmlEvent);
            eInfo.returnValue = false;
            
            apf.queue.empty();
            
        }

        return eInfo.returnValue;
    };

    /**
     * Removes a registered hotkey.
     * @param {String} hotkey The hotkey combination to remove
     * @param {Function} handler The code to be executed when the key
     * combination is pressed.
     */
    apf.removeHotkey = this.remove = this.unregister = function(hotkey, handler) {
        var parts = hotkey.split("|"),
            i = 0,
            l = parts.length;
        for (; i < l; ++i)
            register(parts[i], handler, true);
    };
    
    function toMacNotation(hotkey, bHtml) {
        var t,
            keys = hotkey.splitSafe("\\-"),
            i = 0,
            l = keys.length;

        for (; i < l; ++i) {
            if (!keys[i]) continue;
            if (t = (bHtml ? macUnicodeHtml : macUnicode)[keys[i].toLowerCase()])
                keys[i] = t;
        }
        return keys.join(" ");
    }

    this.toMacNotation = function(hotkey, bHtml) {
        var parts = hotkey.split("|"),
            i = 0,
            l = parts.length,
            res = [];
        for (; i < l; ++i)
            res.push(toMacNotation(parts[i], bHtml));
        return res.join(" | ");
    };

    apf.addEventListener("keydown", function(eInfo) {
        var e = eInfo.htmlEvent;
        //Hotkey
        if (/*!eInfo.isTextInput && */_self.$exec(eInfo) === false
          || eInfo.returnValue === false) {
            apf.stopEvent(e);
            if (apf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch (e) {}
            }
            return false;
        }

        
    });
}).call(apf.hotkeys);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */








/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @private
 */
apf.nameserver = {
    lookup: {},
    
    add: function(type, item) {
        if (!this.lookup[type])
            this.lookup[type] = [];
        
        
        
        return this.lookup[type].push(item) - 1;
    },
    
    register: function(type, id, item) {
        if (!this.lookup[type])
            this.lookup[type] = {};

        
        
        if (this.waiting[id]) {
            var list = this.waiting[id];
            for (var i = 0; i < list.length; i++) {
                list[i]();
            }
            delete this.waiting[id];
        }

        return (this.lookup[type][id] = item);
    },
    
    waiting: {},
    waitFor: function(name, callback) {
        (this.waiting[name] || (this.waiting[name] = [])).push(callback);
    },
    
    remove: function(type, item) {
        var list = this.lookup[type];
        if (list) {
            for (var prop in list) {
                if (list[prop] == item) {
                    delete list[prop];
                }
            }
        }
    },
    
    get: function(type, id) {
        return this.lookup[type] ? this.lookup[type][id] : null;
    },
    
    getAll: function(type) {
        var name, arr = [], l = this.lookup[type];
        if (!l) return arr;
        
        if (l.dataType == apf.ARRAY) {
            for (var i = 0; i < l.length; i++) {
                arr.push(l[i]);
            }
        }
        else {
            for (name in l) {
                
                
                
                arr.push(l[name]);
            }
        }
        
        return arr;
    }, 
    
    getAllNames: function(type) {
        var name, arr = [];
        for (name in this.lookup[type]){
            if (parseInt(name) == name) continue;
            arr.push(name);
        }
        return arr;
    }
};








/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * @todo needs refactor
 * @private
 */
apf.plane = {
    $set: [],
    $lookup: {},
    $find: function(id) {
        if (this.$lookup[id])
            return this.$lookup[id];
        
        var item = this.$set.pop();
        if (!item)
            item = this.$factory();
        
        //item.id = id;
        this.$lookup[id] = item;
        
        return item;
    },
    
    get: function(options) {
        return this.$find(options && options.protect || "default");
    },
    
    show: function(o, reAppend, copyCursor, useRealSize, options) {
        this.options = options || {};
        var item = this.$find(options && options.protect || "default");
        item.show(o, reAppend, copyCursor, useRealSize, options);
    },
    
    hide: function(protect, noAnim) {
        var item = this.$lookup[protect || "default"];
        if (item) {
            item.hide(noAnim);
            delete this.$lookup[protect || "default"];
            this.$set.push(item);
        }
    },

    $factory: function(){
        var _self = this,
            spacerPath = "url(" + (apf.skins.skins["default"] 
            ? apf.skins.skins["default"].mediaPath + "spacer.gif" : "images/spacer.gif") + ")";
        
        function getCover(){
            var obj = document.createElement("DIV");
            
            if (!_self.options || !_self.options.customCover)
                return obj;
            
            obj.innerHTML = apf.getXmlString(_self.options.customCover);
            return obj.firstChild;
        }
        
        function createCover(){
            var cover = document.body.appendChild(getCover());
            if (!_self.options.customCover)
                cover.style.background = spacerPath;

            cover.style.position = "fixed";
            cover.style.left = 0;
            cover.style.top = 0;
            cover.host = false;
            
            return cover;
        }
        
        var plane = createCover();
        
        return {
            host: this,
            plane: plane,
            lastCursor: null,
            lastCoverType: "default",
            
            show: function(o, reAppend, copyCursor, useRealSize, options) {
                var coverType = options && options.customCover ? "custom" : "default",
                    plane;
                
                if (coverType == "custom" || this.lastCoverType != coverType)
                    this.plane = createCover();
                
                plane = this.plane;
            
                if (!options || !options.customCover)
                    this.plane.style.background = options && options.color || spacerPath;
                
                this.animate = options && options.animate;
                this.protect = options && options.protect;
                
                if (this.protect)
                    apf.setProperty("planes", (apf.planes || 0) + 1);
                
                if (o) { //@experimental
                    this.current = o;
                    if (reAppend) { 
                        this.$originalPlace = [o.parentNode, o.nextSibling];
                        this.plane.appendChild(o);
                    }
                }
                apf.window.zManager.set("plane", this.plane, !reAppend && o);
                
                useRealSize = apf.isIE;
                var pWidth = (plane.parentNode == document.body
                    ? useRealSize ? document.documentElement.offsetWidth : apf.getWindowWidth()
                    : plane.parentNode.offsetWidth);
         
                var pHeight = (plane.parentNode == document.body
                    ? useRealSize ? document.documentElement.offsetHeight : apf.getWindowHeight()
                    : plane.parentNode.offsetHeight);
                
                if (copyCursor) {
                    if (this.lastCursor === null)
                        this.lastCursor = document.body.style.cursor;
                    document.body.style.cursor = apf.getStyle(o, "cursor");
                }
                
                this.plane.style.display = "block";
                //this.plane.style.left = p.scrollLeft;
                //this.plane.style.top = p.scrollTop;
                
                var toOpacity = parseFloat(options && options.opacity) || 1;
                if (this.animate) {
                    var _self = this;
                    apf.setOpacity(this.plane, 0);
                    setTimeout(function(){
                        apf.tween.single(_self.plane, {
                            steps: 5,
                            interval: 10,
                            type: "fade",
                            from: 0,
                            to: toOpacity
                        });
                    }, 100);
                }
                else
                    apf.setOpacity(this.plane, toOpacity);
                
                var diff = apf.getDiff(plane);
                this.plane.style.width = "100%";//(pWidth - diff[0]) + "px";
                this.plane.style.height = "100%";//(pHeight - diff[1]) + "px";
        
                this.lastCoverType = options && options.customCover ? "custom" : "default";
        
                return plane;
            },
        
            hide: function(noAnim) {
                if (this.protect)
                    apf.setProperty("planes", apf.planes - 1);
                
                var isChild; // try...catch block is needed to work around a FF3 Win issue with HTML elements
                try {
                    isChild = apf.isChildOf(this.plane, document.activeElement);
                }
                catch (ex) {
                    isChild = false;
                }
                if (this.current && this.current.parentNode == this.plane)
                    this.$originalPlace[0].insertBefore(this.current, this.$originalPlace[1]);
                
                if (this.animate && !noAnim) {
                    var _self = this;
                    setTimeout(function(){
                        apf.tween.single(_self.plane, {
                            steps: 5,
                            interval: 10,
                            type: "fade",
                            from: apf.getOpacity(_self.plane),
                            to: 0,
                            onfinish: function(){
                                _self.plane.style.display = "none";
                                
                                if (_self.current)
                                    apf.window.zManager.clear(_self.current);
                            }
                        });
                    }, 100);
                }
                else {
                    apf.setOpacity(this.plane, 0);
                    if (this.current)
                        apf.window.zManager.clear(this.plane, this.current);
                    this.plane.style.display = "none";
                }
                
                if (isChild && apf.document.activeElement) {
                    if (!apf.isIE)
                        document.activeElement.focus();
                    apf.document.activeElement.$focus();
                }
                
                this.current = null;
                
                if (this.lastCursor !== null) {
                    document.body.style.cursor = this.lastCursor;
                    this.lastCursor = null;
                }
                
                return this.plane;
            }
        };
    }
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @private
 */
apf.popup = {
    cache: {},
    focusFix: {"INPUT":1,"TEXTAREA":1,"SELECT":1},
    
    setContent: function(cacheId, content, style, width, height) {
        if (!this.popup) this.init();

        this.cache[cacheId] = {
            content: content,
            style: style,
            width: width,
            height: height
        };
        content.style.position = "absolute";
        //if(content.parentNode) content.parentNode.removeChild(content);
        //if(style) apf.importCssString(style, this.popup.document);
        
        content.onmousedown = function(e) {
            if (!e) e = event;

            
            
            //@todo can this cancelBubble just go?
            //apf.cancelBubble(e, null, true);
            //e.cancelBubble = true;
        };
        
        return content.ownerDocument;
    },
    
    removeContent: function(cacheId) {
        this.cache[cacheId] = null;
        delete this.cache[cacheId];
    },
    
    init: function(){
        //consider using iframe
        this.popup = {};
        
        apf.addEventListener("hotkey", function(e) {
            if (e.keyCode == "27" || e.altKey) 
                apf.popup.forceHide();
        });
    },
    
    show: function(cacheId, options) {
        if (!this.popup) this.init();
        
        options = apf.extend({
            x: 0,
            y: 0,
            animate: false,
            ref: null,
            width: null,
            height: null,
            callback: null,
            draggable: false,
            resizable: false,
            allowTogether: false,
            autoCorrect: true,
            noleft: false,
            setZindex: true
        }, options);
        
        if ((!options.allowTogether 
          || options.allowTogether !== true && options.allowTogether != this.last) 
          && this.last != cacheId
          && this.cache[this.last]
          && (!this.cache[this.last].options || this.cache[this.last].options.autohide !== false))
            this.hide();

        var o = this.cache[cacheId];
        o.options = options;
        //if(this.last != cacheId) 
        //this.popup.document.body.innerHTML = o.content.outerHTML;

        var dp,
            popup = o.content,
            moveUp = false,
            moveLeft = false,
            fixed = false;

        if (options.setZindex)
            apf.window.zManager.set("popup", o.content);
        
        if ((dp = o.content.style.display) && dp.indexOf("none") > -1)
            o.content.style.display = "";

        var x = options.x;
        var y = options.y;

        var refNode = options.ref;
        while (refNode && refNode.nodeType == 1) {
            if (fixed = apf.getStyle(refNode, "position") == "fixed")
                break;
            refNode = refNode.parentNode || refNode.$parentNode;
        }

        if (!fixed) {
            if (refNode) {
                var pos = apf.getAbsolutePosition(options.ref, 
                    o.content.offsetParent || o.content.parentNode);
                x = (x || 0) + pos[0];
                y = (y || 0) + pos[1];
            }
            
            if (options.width || o.width)
                popup.style.width = ((options.width || o.width) - 3) + "px";

            popup.style.position = "absolute";
            
            var parentMenu = this.cache[options.allowTogether];
            var pOverflow = apf.getOverflowParent(o.content);
            var edgeY = (pOverflow == document.documentElement
                ? (apf.isIE 
                    ? pOverflow.offsetHeight 
                    : (window.innerHeight + window.pageYOffset)) + pOverflow.scrollTop
                : pOverflow.offsetHeight + pOverflow.scrollTop);
            moveUp = options.up || options.autoCorrect && (y
                + (options.height || o.height || o.content.offsetHeight))
                > edgeY;

            if (moveUp) {
                var value;
                if (options.ref)
                    value = (pos[1] - (options.height || o.height || o.content.offsetHeight)) + 3;
                else
                    value = Math.max(0, edgeY - (options.height || o.height || o.content.offsetHeight));
                
                if (!options.up && value < 0) {
                    moveUp = false;
                    popup.style.top = y + "px";
                }
                else {
                    popup.style.top = value + "px";
                }
            }
            else {
                popup.style.top = y + "px";
            }
            
            if (!options.noleft) {
                var edgeX = (pOverflow == document.documentElement
                    ? (apf.isIE 
                        ? pOverflow.offsetWidth
                        : (window.innerWidth + window.pageXOffset)) + pOverflow.scrollLeft
                    : pOverflow.offsetWidth + pOverflow.scrollLeft);
                moveLeft = options.autoCorrect && (x
                    + (options.width || o.width || o.content.offsetWidth))
                    > edgeX;

                if (moveLeft) {
                    var value;
                    if (options.ref) {
                        value = (pos[0] - (options.width || o.width || o.content.offsetWidth))
                                + (options.ref.offsetWidth);
                    }
                    else {
                        value = (edgeX - (options.width || o.width || o.content.offsetWidth) 
                                - (parentMenu ? (parentMenu.width || parentMenu.content.offsetWidth) : 0));
                    }
                    popup.style.left = value < 0 ? x : (value - 1) + "px";
                }
                else {
                    popup.style.left = x + "px";
                }
            }
        }
        else {
            pos = apf.getAbsolutePosition(options.ref, refNode);
            y = (y || 0) + pos[1] + refNode.offsetTop;
            pos[0] += refNode.offsetLeft;
            popup.style.position = "fixed";
            popup.style.top = y + "px";
            
            if (!options.noleft)
                popup.style.left = x + "px";
        }

        
        // set a className that specifies the direction, to help skins with
        // specific styling options.
        apf.setStyleClass(popup, moveUp ? "upward" : "downward", [moveUp ? "downward" : "upward"]);
        apf.setStyleClass(popup, moveLeft ? "moveleft" : "moveright", [moveLeft ? "moveright" : "moveleft"]);
        

        if (options.animate) {
            if (options.animate == "fade") {
                apf.tween.single(popup, {
                    type: 'fade',
                    from: 0,
                    to: 1,
                    anim: apf.tween.NORMAL,
                    steps: options.steps || 15 * apf.animSteps
                });
            }
            else {
                var iVal, steps = apf.isIE8 ? 5 : 7, i = 0;
                iVal = setInterval(function(){
                    var value = ++i * ((options.height || o.height) / steps);

                    popup.style.height = value + "px";
                    if (moveUp)
                        popup.style.top = (y - value - (options.y || 0)) + "px";
                    else
                        (options.container || popup).scrollTop = -1 * (i - steps) * ((options.height || o.height) / steps);
                    popup.style.display = "block";

                    if (i >= steps) {
                        clearInterval(iVal)
                        
                        if (options.callback)
                            options.callback(popup);
                    }
                }, 10);
            }
        }
        else {
            if (!refNode) {
                if (options.height || o.height)
                    popup.style.height = (options.height || o.height) + "px";
                value = (edgeY - (options.height || o.height || o.content.offsetHeight));
                popup.style.top = y + (options.height || o.height || o.content.offsetHeight) < edgeY 
                                    ? y 
                                    : value 
                                  + "px";
            }
            popup.style.display = "block";
            
            if (options.callback)
               options.callback(popup);
        }

        $setTimeout(function(){
            apf.popup.last = cacheId;
        });

        if (options.draggable) {
            options.id = cacheId;
            this.makeDraggable(options);
        }
    },
    
    hide: function(){
        if (this.isDragging) return;

        var o = this.cache[this.last];
        if (o) {
            if (o.content)
                o.content.style.display = "none";

            if (o.options && o.options.onclose) {
                o.options.onclose(apf.extend(o.options, {htmlNode: o.content}));
                o.options.onclose = false;
            }
        }
    },
    
    isShowing: function(cacheId) {
        return this.last && this.last == cacheId 
            && this.cache[this.last]
            && this.cache[this.last].content.style.display != "none";
    },

    isDragging: false,

    makeDraggable: function(options) {
        if (!apf.Interactive || this.cache[options.id].draggable) 
            return;

        var oHtml = this.cache[options.id].content;
        this.cache[options.id].draggable = true;
        var o = {
            $propHandlers: {},
            minwidth: 10,
            minheight: 10,
            maxwidth: 10000,
            maxheight: 10000,
            dragOutline: false,
            resizeOutline: false,
            draggable: true,
            resizable: options.resizable,
            $ext: oHtml,
            oDrag: oHtml.firstChild
        };

        oHtml.onmousedown =
        oHtml.firstChild.onmousedown = function(e) {
            if (!e) e = event;
            
            
            
            (e || event).cancelBubble = true;
        }

        apf.implement.call(o, apf.Interactive);

        o.$propHandlers["draggable"].call(o, true);
        o.$propHandlers["resizable"].call(o, true);
    },
    
    getCurrentElement: function(){
        return typeof this.last == "number" && apf.lookup(this.last);
    },
    
    $mousedownHandler: function(amlNode, e) {
        if (!this.last || (amlNode && this.last == amlNode.$uniqueId) || !this.cache[this.last])
          return;

        var htmlNode = e.srcElement || e.target;
        
        var uId = this.last;
        
        while (this.cache[uId]) {
            if (apf.isChildOf(this.cache[uId].content, htmlNode, true))
                return;
            
            if (!this.cache[uId].options)
                return;
            
            uId = this.cache[uId].options.allowTogether;
        }
        
        this.forceHide();
    },
    
    forceHide: function(){
        if (this.last 
          
          && !apf.plane.current
          
          && this.isShowing(this.last)
          && this.cache[this.last]
          && this.cache[this.last].options
          && this.cache[this.last].options.autohide !== false) {
            var o = apf.lookup(this.last);
            if (!o)
                this.last = null;
            else if (o.dispatchEvent("popuphide") !== false)
                this.hide();
        }
    },

    destroy: function(){
        for (var cacheId in this.cache) {
            if (this.cache[cacheId]) {
                this.cache[cacheId].content.onmousedown = null;
                apf.destroyHtmlNode(this.cache[cacheId].content);
                this.cache[cacheId].content = null;
                this.cache[cacheId] = null;
            }
        }
        
        if (!this.popup) return;
        //this.popup.document.body.c = null;
        //this.popup.document.body.onmouseover = null;
    }
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This method sets a single CSS rule.
 * @param {String} name         The CSS name of the rule (i.e. `.cls` or `#id`).
 * @param {String} type         The CSS property to change.
 * @param {String} value        The CSS value of the property.
 * @param {String} [stylesheet] The name of the stylesheet to change.
 * @param {Object} [win]        A reference to a window
 */
apf.setStyleRule = function(name, type, value, stylesheet, win) {
    name = name.toLowerCase();
    
    if (!stylesheet) {
        var sheets = (win || self).document.styleSheets;
        for (var j = sheets.length - 1; j >= 0; j--) {
            try {
                var rules = sheets[j][apf.styleSheetRules];
                if (!rules) return false;
                
                for (var i = 0; i < rules.length; i++) {
                    if (rules.item(i).selectorText && rules.item(i).selectorText.toLowerCase() == name) {
                        rules.item(i).style[type] = value;
                        return true;
                    }
                }
            }
            catch (e) {}
        }
    }
    else {
        if (!stylesheet)
            stylesheet = (win || self).document.styleSheets[0];
        
        var rules = stylesheet[apf.styleSheetRules];
        if (!rules) return false;
        
        for (var i = 0; i < rules.length; i++) {
            if (rules.item(i).selectorText && rules.item(i).selectorText.toLowerCase() == name) {
                rules.item(i).style[type] = value;
                return true;
            }
        }
    }
    
    return false;
};

/**
 * This method gets a single CSS rule.
 * @param {String} name         The CSS name of the rule (i.e. `.cls` or `#id`).
 * @param {String} type         The CSS property to change.
 * @param {String} [stylesheet] The name of the stylesheet to change.
 * @param {Object} [win]        A reference to a window
 */
apf.getStyleRule = function(name, type, stylesheet, win) {
    name = name.toLowerCase();
    
    if (!stylesheet) {
        var sheets = (win || self).document.styleSheets;
        for (var j = sheets.length - 1; j >= 0; j--) {
            try {
                var rules = sheets[j][apf.styleSheetRules];
                for (var i = 0; i < rules.length; i++) {
                    if (rules.item(i).selectorText && rules.item(i).selectorText.toLowerCase() == name) {
                        return rules.item(i).style[type];
                    }
                }
            }
            catch (e) {}
        }
    }
    else {
        var rules = (win || self).document.styleSheets[stylesheet || 0][apf.styleSheetRules];
        for (var i = 0; i < rules.length; i++) {
            if (rules.item(i).selectorText && rules.item(i).selectorText.toLowerCase() == name) {
                return rules.item(i).style[type];
            }
        }
    }
    
    return false;
};

/**
 * This method adds one class name to an HTMLElement. It can also remove classes.
 * @param {HTMLElement} oHtml        The HTMLElement to apply the CSS class to.
 * @param {String}      className    The name of the CSS class to apply.
 * @param {Array}       [exclusion]  A list of strings specifying names of CSS classes to remove.
 * @returns {HTMLElement} The modified `oHtml` element.
 */
apf.setStyleClass = function(oHtml, className, exclusion, userAction) {
    if (!oHtml || userAction && this.disabled)
        return;

    

    if (className) {
        if (exclusion)
            exclusion[exclusion.length] = className;
        else
            exclusion = [className];
    }

    //Create regexp to remove classes
    //var re = new RegExp("(?:(^| +)" + (exclusion ? exclusion.join("|") : "") + "($| +))", "gi");
    var re = new RegExp("(^| +)(?:" + (exclusion ? exclusion.join("|") : "") + ")", "gi");

    //Set new class
    oHtml.className != null
        ? (oHtml.className = oHtml.className.replace(re, " ") + (className ? " " + className : ""))
        : oHtml.setAttribute("class", (oHtml.getAttribute("class") || "")
            .replace(re, " ") + (className ? " " + className : ""));

    return oHtml;
};

/**
 * This method imports a CSS stylesheet from a string.
 * @param {String} cssString  The CSS definition
 * @param {Object} [doc]      The reference to the document where the CSS is applied on
 * @param {String} [media]    The media to which this CSS applies (_i.e._ `print` or `screen`)
 */
apf.importCssString = function(cssString, doc, media) {
    doc = doc || document;
    var htmlNode = doc.getElementsByTagName("head")[0];//doc.documentElement.getElementsByTagName("head")[0];

    

    if (apf.canCreateStyleNode) {
        //var head = document.getElementsByTagName("head")[0];
        var style = doc.createElement("style");
        style.appendChild(doc.createTextNode(cssString));
        if (media)
            style.setAttribute('media', media);
        htmlNode.appendChild(style);
    }
    else {
        htmlNode.insertAdjacentHTML("beforeend", ".<style media='"
         + (media || "all") + "'>" + cssString + "</style>");

        /*if(document.body) {
            document.body.style.height = "100%";
            $setTimeout('document.body.style.height = "auto"');
        }*/
    }
};

/**
 * This method retrieves the current value of a property on a HTML element
 * recursively. If the style isn't found on the element itself, its parent is
 * checked.
 * @param {HTMLElement} el    The element to read the property from
 * @param {String}      prop  The property to read
 * @returns {String} The retrieved value
 */
apf.getStyleRecur = function(el, prop) {
    var value = apf.hasComputedStyle
        ? document.defaultView.getComputedStyle(el,'').getPropertyValue(
            prop.replace(/([A-Z])/g, function(m, m1) {
                return "-" + m1.toLowerCase();
            }))
        : el.currentStyle[prop]

    return ((!value || value == "transparent" || value == "inherit")
      && el.parentNode && el.parentNode.nodeType == 1)
        ? this.getStyleRecur(el.parentNode, prop)
        : value;
};

/**
 * This method imports a stylesheet defined by a multidimensional array. 
 * @param {Array}    def  A multidimensional array specifying stylesheets to import
 * @param {Object}   [win] A reference to a window
 * @method
 * @deprecated
 */    
apf.importStylesheet = function (def, win, stylesheet) {
    if (!def.length)
        return;
    
    if (!stylesheet) {
        var re = new RegExp("^" + document.domain, 'g');
        var doc = (win || window).document;
        for (var index=document.styleSheets.length - 1; index >= 0; index--) {
            if (!doc.styleSheets[index].href || doc.styleSheets[index].href.match(re)) {
                break;
            }
        }
        stylesheet = doc.styleSheets[index];
    }
    
    if (!stylesheet)
        stylesheet = apf.createStylesheet(win);
    
    for (var i = 0; i < def.length; i++) {
        if (!def[i][1])
            continue;

        if (apf.isIE)
            stylesheet.addRule(def[i][0], def[i][1]);
        else {
            var rule = def[i][0] + " {" + def[i][1] + "}";
            try {
                stylesheet.insertRule(rule, 0);
            }
            catch (e) {
                stylesheet = new StyleSheet();
                stylesheet.insertRule(rule, 0);
            }
        }
    }
};

/**
 * This method constructs a stylesheet.
 * @param {Object}  [win] A reference to a window
 * @returns {String} The created CSS stylesheet
 */ 
apf.createStylesheet = function(win) {
    var doc = (win || window).document;
    
    if (doc.createStyleSheet)
        return doc.createStyleSheet();
    else {
        var elem = doc.createElement("style");
        elem.type = "text/css";
        doc.getElementsByTagName("head")[0].appendChild(elem);
        return elem.sheet;
    }
};

/**
 * This method determines if specified coordinates are within the HTMLElement.
 * @param {HTMLElement} el  The element to check
 * @param {Number}      x   The x-coordinate in pixels
 * @param {Number}      y   The y-coordinate in pixels
 * @returns {Boolean} `true` if the coordinates are within the element.
 */
apf.isInRect = function(oHtml, x, y) {
    var pos = this.getAbsolutePosition(oHtml);
    if (x < pos[0] || y < pos[1] || x > oHtml.offsetWidth + pos[0] - 10
      || y > oHtml.offsetHeight + pos[1] - 10)
        return false;
    return true;
};

/**
 * Retrieves the parent providing the rectangle to which the HTMLElement is
 * bound and cannot escape. In CSS, this is accomplished by having the overflow
 * property set to `"hidden"` or `"auto"`.
 * @param {HTMLElement} o  The element to check
 * @returns {HTMLElement} The parent element
 */
apf.getOverflowParent = function(o) {
    //not sure if this is the correct way. should be tested

    o = o.offsetParent;
    while (o && (this.getStyle(o, "overflow") != "hidden"
      || "absolute|relative".indexOf(this.getStyle(o, "position")) == -1)) {
        o = o.offsetParent;
    }
    return o || document.documentElement;
};

/**
 * Retrieves the first parent element which has a position `absolute` or
 * `relative` set.
 * @param {HTMLElement} o  The element to check
 * @returns {HTMLElement} The parent element
 */
apf.getPositionedParent = function(o) {
    o = o.offsetParent;
    while (o && o.tagName.toLowerCase() != "body"
      && "absolute|relative".indexOf(this.getStyle(o, "position")) == -1) {
        o = o.offsetParent;
    }
    return o || document.documentElement;
};

/**
 * Retrieves the absolute x- and y-coordinates, relative to the browser's
 * drawing area or the specified `refParent`.
 * @param {HTMLElement} o           The element to check
 * @param {HTMLElement} [refParent] The reference parent
 * @param {Boolean}     [inclSelf]  Whether to include the position of the element to check in the return value.
 * @returns {Array} The x- and y-coordinates of `oHtml`.
 */
apf.getAbsolutePosition = function(o, refParent, inclSelf) {
    if ("getBoundingClientRect" in document.documentElement) { 
        if (apf.doesNotIncludeMarginInBodyOffset && o == document.body) {
            return [
                o.offsetLeft + (parseFloat(apf.getStyle(o, "marginLeft")) || 0),
                  + (o.scrollLeft || 0),
                o.offsetTop  + (parseFloat(apf.getStyle(o, "marginTop")) || 0)
                  + (o.scrollTop || 0)
            ];
        }
        
        var box = o.getBoundingClientRect(), 
            top = box.top,
            left = box.left,
            corr = (apf.isIE && apf.isIE < 8);

        if (refParent && refParent != document.body) {
            var pos = apf.getAbsolutePosition(refParent, null, true);
            top -= pos[1];
            left -= pos[0];
        }
        
        if (!(apf.isIE && o == document.documentElement)) {
            left += (refParent || document.body).scrollLeft || document.documentElement.scrollLeft || 0;
            top  += (refParent || document.body).scrollTop  || document.documentElement.scrollTop  || 0;
        }
        
        if (inclSelf && !refParent) {
            left += parseInt(apf.getStyle(o, "borderLeftWidth")) || 0
            top  += parseInt(apf.getStyle(o, "borderTopWidth")) || 0;
        }

        return [left - (corr ? 2 : 0), top - (corr ? 2 : 0)];
    }
    
    //@todo code below might be deprecated one day
    var wt = inclSelf ? 0 : o.offsetLeft, ht = inclSelf ? 0 : o.offsetTop;
    o = inclSelf ? o : o.offsetParent || o.parentNode ;

    if (apf.isIE8 && refParent) {
        bw = this.getStyle(o, "borderLeftWidth");
        wt -= (apf.isIE && o.currentStyle.borderLeftStyle != "none" 
          && bw == "medium" ? 2 : parseInt(bw) || 0);
        bh = this.getStyle(o, "borderTopWidth");
        ht -= (apf.isIE && o.currentStyle.borderTopStyle != "none" 
          && bh == "medium" ? 2 : parseInt(bh) || 0);
    }

    var bw, bh, fl;
    while (o && o != refParent) {//&& o.tagName.toLowerCase() != "html"
        //Border - Left
        bw = apf.isOpera || apf.isIE8 ? 0 : this.getStyle(o, "borderLeftWidth");

        wt += (apf.isIE && o.currentStyle.borderLeftStyle != "none" && bw == "medium"
            ? 2
            : parseInt(bw) || 0) + o.offsetLeft;

        if (apf.isIE && !apf.isIE8 && apf.getStyle(o, "styleFloat") == "none" 
          && apf.getStyle(o, "position") == "relative") {
            var q = o.previousSibling;
            while (q) {
                if (q.nodeType == 1) {
                    fl = apf.getStyle(q, "styleFloat");
                    if (fl == "left") {
                        wt -= parseInt(apf.getStyle(o, "marginLeft")) 
                            || 0;//-1 * (o.parentNode.offsetWidth - o.offsetWidth)/2; //assuming auto
                        break;
                    }
                    else if (fl == "right")
                        break;
                }
                q = q.previousSibling;
            }
        }

        //Border - Top
        bh = apf.isOpera || apf.isIE8 ? 0 : this.getStyle(o, "borderTopWidth");
        ht += (apf.isIE && o.currentStyle.borderTopStyle != "none" && bh == "medium"
            ? 2
            : parseInt(bh) || 0) + o.offsetTop;

        //Scrolling
        if (!apf.isGecko && o != refParent && (o.tagName != "HTML" || o.ownerDocument != document)) {
            wt -= o.scrollLeft;
            ht -= o.scrollTop;
        }
        
        //Table support
        if (o.tagName.toLowerCase() == "table") {
            ht -= parseInt(o.border || 0) + parseInt(o.cellSpacing || 0);
            wt -= parseInt(o.border || 0) + parseInt(o.cellSpacing || 0) * 2;
        }
        else if (o.tagName.toLowerCase() == "tr") {
            var cp;
            ht -= (cp = parseInt(o.parentNode.parentNode.cellSpacing));
            while (o.previousSibling)
                ht -= (o = o.previousSibling).offsetHeight + cp;
        }

        if (apf.isIE && !o.offsetParent && o.parentNode.nodeType == 1) {
            wt -= o.parentNode.scrollLeft;
            ht -= o.parentNode.scrollTop;
        }

        o = o.offsetParent;
    }

    return [wt, ht];
};

//@todo its much faster to move these to browser specific files and eliminate apf.getStyle()
/**
 * Returns the distance between the border left and border right values of an element.
 * @param {HTMLElement} oHtml The element to check
 * @returns {Number} The final calculation, or 0, if there's no difference
 * @see apf.getWidthDiff
 */
apf.getHorBorders = function(oHtml) {
    return Math.max(0,
          (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0));
};

/**
 * Returns the distance between the border top and border bottom values of an element.
 * @param {HTMLElement} oHtml The element to check
 * @returns {Number} The final calculation, or 0, if there's no difference
 */
apf.getVerBorders = function(oHtml) {
    return Math.max(0,
          (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0));
};

/**
 * Returns the distance between the border left and border right values of an element, taking padding into consideration.
 * @param {HTMLElement} oHtml The element to check
 * @returns {Number} The final calculation, or 0, if there's no difference
 * @see apf.getHorBorders
 */
apf.getWidthDiff = function(oHtml) {
    if (apf.hasFlexibleBox 
      && apf.getStyle(oHtml, apf.CSSPREFIX + "BoxSizing") != "content-box")
        return 0;
    
    return Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingLeft")) || 0)
        + (parseInt(apf.getStyle(oHtml, "paddingRight")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0));
};

/**
 * Returns the distance between the border top and border bottom values of an element, taking padding into consideration.
 * @param {HTMLElement} oHtml The element to check
 * @returns {Number} The final calculation, or 0, if there's no difference
 */
apf.getHeightDiff = function(oHtml) {
    if (apf.hasFlexibleBox 
      && apf.getStyle(oHtml, apf.CSSPREFIX + "BoxSizing") != "content-box")
        return 0;
    
    return Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingTop")) || 0)
        + (parseInt(apf.getStyle(oHtml, "paddingBottom")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0));
};

/**
 * Returns an array with two elements. The first is the distance between the border top and border bottom values of an element, taking padding into consideration; 
 * the second is the distance between the border top and border bottom values of an element, taking padding into consideration.
 * @param {HTMLElement} oHtml The element to check
 * @returns {[Number]} An array containing the differences
 */
apf.getDiff = function(oHtml) {
    if (apf.hasFlexibleBox 
      && apf.getStyle(oHtml, apf.CSSPREFIX + "BoxSizing") != "content-box")
        return [0,0];
    
    return [Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingLeft")) || 0)
        + (parseInt(apf.getStyle(oHtml, "paddingRight")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0)),
        Math.max(0, (parseInt(apf.getStyle(oHtml, "paddingTop")) || 0)
        + (parseInt(apf.getStyle(oHtml, "paddingBottom")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)
        + (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0))];
};

/**
 * Returns an array with two elements. The first is the distance between the margin left and margin right values of an element; 
 * the second is the distance between the margin top top and margin bottom values of an element.
 * @param {HTMLElement} oHtml The element to check
 * @returns {[Number]} An array containing the differences
 */
apf.getMargin = function(oHtml) {
    return [(parseInt(apf.getStyle(oHtml, "marginLeft")) || 0)
        + (parseInt(apf.getStyle(oHtml, "marginRight")) || 0),
      (parseInt(apf.getStyle(oHtml, "marginTop")) || 0)
        + (parseInt(apf.getStyle(oHtml, "marginBottom")) || 0)]
};

/**
 * Returns the difference between an element's `offsetWidth`, with its border left and border right widths removed. 
 * @param {HTMLElement} oHtml The element to check
 * @returns {Number} The final calculation
 */
apf.getHtmlInnerWidth = function(oHtml) {
    return (oHtml.offsetWidth
        - (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0)
        - (parseInt(apf.getStyle(oHtml, "borderRightWidth")) || 0));
};

/**
 * Returns the difference between an element's `offsetWidth`, with its border top and border bottom widths removed. 
 * @param {HTMLElement} oHtml The element to check
 * @returns {Number} The final calculation
 */
apf.getHtmlInnerHeight = function(oHtml) {
    return (oHtml.offsetHeight
        - (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)
        - (parseInt(apf.getStyle(oHtml, "borderBottomWidth")) || 0));
};

/**
 * Returns the viewport of a window.
 *
 * @param {WindowImplementation}  [win] The window to take the measurements of.
 * @returns {Object}                    Viewport object with  x, y, w, and h properties.
 */
apf.getViewPort = function(win) {
    win = win || window;
    var doc = (!win.document.compatMode
      || win.document.compatMode == "CSS1Compat")
        //documentElement for an iframe
        ? win.document.html || win.document.documentElement
        : win.document.body;

    // Returns viewport size excluding scrollbars
    return {
        x: win.pageXOffset || doc.scrollLeft,
        y: win.pageYOffset || doc.scrollTop,
        width: win.innerWidth  || doc.clientWidth,
        height: win.innerHeight || doc.clientHeight
    };
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Opens a window with the string in it.
 * @param {String} str The HTML string to display in the new window.
 */
apf.pasteWindow = function(str) {
    var win = window.open("about:blank");
    win.document.write(str);
};



/**
 * Escapes HTML from a string.
 * @param {String} str The html to be escaped.
 * @return {String} The escaped string.
 */
apf.htmlentities = function(str) {
    return str.escapeHTML();
};

/**
 * Escape an XML string, making it ascii compatible.
 * @param {String} str The xml string to escape.
 * @return {String} The escaped string.
 * @method xmlentities
 *
 */
 /* @todo This function does something completely different from htmlentities, 
 *       the name is confusing and misleading.
 */
apf.xmlentities = apf.escapeXML;

/**
 * Unescape an HTML string.
 * @param {String} str The string to unescape.
 * @return {String} The unescaped string.
 */
apf.html_entity_decode = function(str) {
    return (str || "").replace(/\&\#38;/g, "&").replace(/&lt;/g, "<")
        .replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&nbsp;/g, " ");
};



/**
 * Determines whether the keyboard input was a character that can influence
 * the value of an element (like a textbox).
 * @param {Number} charCode The ascii character code
 * @returns {Boolean} `true` if it was a character
 */
apf.isCharacter = function(charCode) {
    return (charCode < 112 || charCode > 122)
      && (charCode == 32 || charCode > 42 || charCode == 8);
};

/**
 * This random number generator has been added to provide a more robust and
 * reliable random number spitter than the native Ecmascript Math.random()
 * function.
 * 
 * It is an implementation of the Park-Miller algorithm. (See 'Random Number
 * Generators: Good Ones Are Hard to Find', by Stephen K. Park and Keith W.
 * Miller, Communications of the ACM, 31(10):1192-1201, 1988.)
 * @class apf.randomGenerator
 * @author David N. Smith of IBM's T. J. Watson Research Center.
 * @author Mike de Boer (mike AT c9 DOT io)
 * 
 */
apf.randomGenerator = {
    d: new Date(),
    seed: null,
    A: 48271,
    M: 2147483647,
    Q: null,
    R: null,
    oneOverM: null,

    /**
     * Generates a random [[Number]] between a lower and upper boundary.
     * The algorithm uses the system time, in minutes and seconds, to 'seed'
     * itself, that is, to create the initial values from which it will generate
     * a sequence of numbers. If you are familiar with random number generators,
     * you might have reason to use some other value for the seed. Otherwise,
     * you should probably not change it.
     * @param {Number} lnr Lower boundary
     * @param {Number} unr Upper boundary
     * @returns A random number between `lnr` and`unr`
     * @type Number
     */
    generate: function(lnr, unr) {
        if (this.seed == null)
            this.seed = 2345678901 + (this.d.getSeconds() * 0xFFFFFF) + (this.d.getMinutes() * 0xFFFF);
        this.Q = this.M / this.A;
        this.R = this.M % this.A;
        this.oneOverM = 1.0 / this.M;
        return Math.floor((unr - lnr + 1) * this.next() + lnr);
    },

    /**
     * Returns a new random number, based on the 'seed', generated by the
     * [[apf.randomGenerator.generate]] method.
     * @type Number
     */
    next: function() {
        var hi = this.seed / this.Q;
        var lo = this.seed % this.Q;
        var test = this.A * lo - this.R * hi;
        if (test > 0)
            this.seed = test;
        else
            this.seed = test + this.M;
        return (this.seed * this.oneOverM);
    }
};

/**
 * Adds a time stamp to the URL to prevent the browser from caching it.
 * @param {String} url The URL to add the timestamp to.
 * @return {String} The url... with a timestamp!
 */
apf.getNoCacheUrl = function(url) {
    return url
        + (url.indexOf("?") == -1 ? "?" : "&")
        + "nocache=" + new Date().getTime();
};

/**
 * Checks if the string contains curly braces at the start and end. If so, it's
 * processed as Javascript via `eval()`. Otherwise, the original string is returned.
 * @param {String} str The string to parse.
 * @return {String} The result of the parsing.
 */
apf.parseExpression = function(str) {
    if (!apf.parseExpression.regexp.test(str))
        return str;

    
        return eval(RegExp.$1);
    
};
apf.parseExpression.regexp = /^\{([\s\S]*)\}$/;

/**
 * @private
 */
apf.formatNumber = function(num, prefix) {
    var nr = parseFloat(num);
    if (!nr) return num;

    var str = new String(Math.round(nr * 100) / 100).replace(/(\.\d?\d?)$/, function(m1) {
        return m1.pad(3, "0", apf.PAD_RIGHT);
    });
    if (str.indexOf(".") == -1)
        str += ".00";

    return prefix + str;
};

/**
 * Execute a script in the global scope.
 *
 * @param {String} str  The JavaScript code to execute
 * @param {Object} [win] A reference to a window
 * @return {String} The executed JavaScript code
 */
apf.jsexec = function(str, win) {
    if (!str)
        return str;
    if (!win)
        win = self;

    if (apf.isO3)
        eval(str, self);
    else if (apf.hasExecScript) {
        win.execScript(str);
    }
    else {
        var head = win.document.getElementsByTagName("head")[0];
        if (head) {
            var script = win.document.createElement('script');
            script.setAttribute('type', 'text/javascript');
            script.text = str;
            head.appendChild(script);
            head.removeChild(script);
        } else
            eval(str, win);
    }

    return str;
};

/*
 * Shorthand for an empty function.
 */
apf.K = function(){};



/**
 * Reliably determines whether a variable is a Number.
 *
 * @param {Mixed}   value The variable to check
 * @type  {Boolean} `true` if the argument is a number
 */
apf.isNumber = function(value) {
    return parseFloat(value) == value;
};

/**
 * Reliably determines whether a variable is an array. For more information, see 
 * <http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/>
 *
 * @param {Mixed}   value The variable to check
 * @type  {Boolean} `true` if the argument is an array
 */
apf.isArray = function(value) {
    return Object.prototype.toString.call(value) === "[object Array]";
};

/**
 * Determines whether a string is true (in the HTML attribute sense).
 * @param {Mixed} value The variable to check. Possible truth values include:
 *  - true  
 *  - 'true'
 *  - 'on'  
 *  - 1     
 *  - '1'   
 * @return {Boolean} Whether the string is considered to imply truth.
 */
apf.isTrue = function(c) {
    return (c === true || c === "true" || c === "on" || typeof c == "number" && c > 0 || c === "1");
};

/**
 * Determines whether a string is false (in the HTML attribute sense).
 * @param {Mixed} value The variable to check. Possible false values include:
 *   - false   
 *   - 'false' 
 *   - 'off'   
 *   - 0       
 *   - '0'     
 * @return {Boolean} whether the string is considered to imply untruth.
 */
apf.isFalse = function(c) {
    return (c === false || c === "false" || c === "off" || c === 0 || c === "0");
};

/**
 * Determines whether a value should be considered false. This excludes, amongst
 * others, the number 0.
 * @param {Mixed} value The variable to check
 * @return {Boolean} Whether the variable is considered false.
 */
apf.isNot = function(c) {
    // a var that is null, false, undefined, Infinity, NaN and c isn't a string
    return (!c && typeof c != "string" && c !== 0 || (typeof c == "number" && !isFinite(c)));
};

/**
 * Creates a relative URL based on an absolute URL.
 * @param {String} base The start of the URL to which relative URL's work.
 * @param {String} url  The URL to transform.
 * @return {String} The relative URL.
 */
apf.removePathContext = function(base, url) {
    if (!url)  return "";

    if (url.indexOf(base) > -1)
        return url.substr(base.length);

    return url;
};

/*
 * @private
 * @todo why is this done like this?
 */
apf.cancelBubble = function(e, o, noPropagate) {
    if (e.stopPropagation)
        e.stopPropagation()
    else 
        e.cancelBubble = true;
    
    //if (o.$focussable && !o.disabled)
        //apf.window.$focus(o);
    
    
    /*if (apf.isIE)
        o.$ext.fireEvent("on" + e.type, e);
    else 
        o.$ext.dispatchEvent(e.name, e);*/
    
    if (!noPropagate) {
        if (o && o.$ext && o.$ext["on" + (e.type || e.name)])
            o.$ext["on" + (e.type || e.name)](e);
        apf.window.$mousedown(e);
    }
    //if (apf.isGecko)
        //apf.window.$mousedown(e);
    
    
};



/*
 * Attempt to fix memory leaks
 * @private
 */
apf.destroyHtmlNode = function (element) {
    if (!element) return;

    if (!apf.isIE || element.ownerDocument != document) {
        if (element.parentNode)
            element.parentNode.removeChild(element);
        return;
    }

    var garbageBin = document.getElementById('IELeakGarbageBin');
    if (!garbageBin) {
        garbageBin = document.createElement('DIV');
        garbageBin.id = 'IELeakGarbageBin';
        garbageBin.style.display = 'none';
        document.body.appendChild(garbageBin);
    }

    // move the element to the garbage bin
    garbageBin.appendChild(element);
    garbageBin.innerHTML = '';
};


/**
 * @private
 */
apf.getRules = function(node) {
    var rules = {};

    for (var w = node.firstChild; w; w = w.nextSibling) {
        if (w.nodeType != 1)
            continue;
        else {
            if (!rules[w[apf.TAGNAME]])
                rules[w[apf.TAGNAME]] = [];
            rules[w[apf.TAGNAME]].push(w);
        }
    }

    return rules;
};


apf.isCoord = function (n) {
    return n || n === 0;
};

apf.getCoord = function (n, other) {
    return n || n === 0 ? n : other;
};

/**
 * @private
 */
apf.getBox = function(value, base) {
    if (!base) base = 0;

    if (value == null || (!parseInt(value) && parseInt(value) != 0))
        return [0, 0, 0, 0];

    var x = String(value).splitSafe(" ");
    for (var i = 0; i < x.length; i++)
        x[i] = parseInt(x[i]) || 0;
    switch (x.length) {
        case 1:
            x[1] = x[0];
            x[2] = x[0];
            x[3] = x[0];
            break;
        case 2:
            x[2] = x[0];
            x[3] = x[1];
            break;
        case 3:
            x[3] = x[1];
            break;
    }

    return x;
};

/**
 * @private
 */
apf.getNode = function(data, tree) {
    var nc = 0;//nodeCount
    //node = 1
    if (data != null) {
        for (var i = 0; i < data.childNodes.length; i++) {
            if (data.childNodes[i].nodeType == 1) {
                if (nc == tree[0]) {
                    data = data.childNodes[i];
                    if (tree.length > 1) {
                        tree.shift();
                        data = this.getNode(data, tree);
                    }
                    return data;
                }
                nc++
            }
        }
    }

    return null;
};

/**
 * Retrieves the first XML node with a `nodeType` of 1 from the children of an XML element.
 * @param {XMLElement} xmlNode The XML element that is the parent of the element to select.
 * @return {XMLElement} The first child element of the XML parent.
 * @throws An error when no child element is found.
 */
apf.getFirstElement = function(xmlNode) {
    

    return xmlNode.firstChild.nodeType == 1
        ? xmlNode.firstChild
        : xmlNode.firstChild.nextSibling;
};

/**
 * Retrieves the last XML node with `nodeType` of 1 from the children of an XML element.
 * @param {XMLElement} xmlNode The XML element that is the parent of the element to select.
 * @return {XMLElement} The last child element of the XML parent.
 * @throw An error when no child element is found.
 */
apf.getLastElement = function(xmlNode) {
    

    return xmlNode.lastChild.nodeType == 1
        ? xmlNode.lastChild
        : xmlNode.lastChild.previousSibling;
};

/**
 * Selects the content of an HTML element. This currently only works in
 * Internet Explorer.
 * @param {HTMLElement} oHtml The container in which the content receives the selection.
 */
apf.selectTextHtml = function(oHtml) {
    if (!apf.hasMsRangeObject) return;// oHtml.focus();

    var r = document.selection.createRange();
    try {r.moveToElementText(oHtml);} catch (e) {}
    r.select();
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Manages visibility hooks for elements that need to be visible to set their
 * layout.
 *
 * @private
 */
apf.visibilitymanager = function(){
    var tree = {};
    var _self = this;
    var inited = false;
    
    this.check = function(amlNode, type, callback) {
        if (amlNode.$ext.offsetHeight || amlNode.$ext.offsetWidth)
            return true;

        if (amlNode.$visibleCheck) {
            if (amlNode.$visibleCheck[type])
                return;
        }
        else
            amlNode.$visibleCheck = {};

        function cleanup(setInsertion) {
            var p = amlNode;
            while (p) {
                p.removeEventListener("prop.visible", check);
                p.removeEventListener("DOMNodeRemoved", remove); 
                p.removeEventListener("DOMNodeRemovedFromDocument", remove); 
                if (setInsertion)
                    p.addEventListener("DOMNodeInserted", add);
                p = p.parentNode || p.$parentNode;
            }
            
            delete amlNode.$visibleCheck[type];
        }

        function check(e) {
            //apf.isTrue(e.value)
            if (!amlNode.$ext.offsetHeight && !amlNode.$ext.offsetWidth)
                return;
                
            callback.call(amlNode);
            cleanup();
        }
        
        function remove(e) {
            if (e.currentTarget != this)
                return;
            
            cleanup(e.name == "DOMNodeRemoved");
        }

        function add(){
            //Set events on the parent tree
            var p = amlNode;
            while (p) {
                p.addEventListener("prop.visible", check);
                p.addEventListener("DOMNodeRemoved", remove); 
                p.addEventListener("DOMNodeRemovedFromDocument", remove); 
                p.removeEventListener("DOMNodeInserted", add);
                p = p.parentNode || p.$parentNode;
            }
            
            amlNode.$visibleCheck[type] = true;
        }
        
        add();
        
        return false;
    }
    
    this.permanent = function(amlNode, show, hide) {
        var state = amlNode.$ext && (amlNode.$ext.offsetHeight || amlNode.$ext.offsetWidth);
        function check(e) {
            var newState = amlNode.$ext && (amlNode.$ext.offsetHeight || amlNode.$ext.offsetWidth);
            if (newState == state)
                return;
            
            if (newState) show();
            else hide();
            
            state = newState;
        }

        //Set events on the parent tree
        /*var p = amlNode;
        while (p) {
            p.addEventListener("prop.visible", check);
            p = p.parentNode || p.$parentNode;
        }*/
        
        function cleanup(setInsertion) {
            var p = amlNode;
            while (p) {
                p.removeEventListener("prop.visible", check);
                p.removeEventListener("DOMNodeRemoved", remove); 
                p.removeEventListener("DOMNodeRemovedFromDocument", remove); 
                if (setInsertion)
                    p.addEventListener("DOMNodeInserted", add);
                p = p.parentNode || p.$parentNode;
            }
            
            check();
        }

        function remove(e) {
            if (e.currentTarget != this)
                return;
            
            cleanup(e.name == "DOMNodeRemoved");
        }

        function add(){
            //Set events on the parent tree
            var p = amlNode;
            while (p) {
                p.addEventListener("prop.visible", check);
                p.addEventListener("DOMNodeRemoved", remove); 
                p.addEventListener("DOMNodeRemovedFromDocument", remove); 
                p.removeEventListener("DOMNodeInserted", add);
                p = p.parentNode || p.$parentNode;
            }
            
            check();
        }
        
        add();

        return state;
    }
    
    this.removePermanent = function(amlNode) {
        
    }
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Determines whether a node is a child of another node.
 *
 * @param {DOMNode} pNode      The potential parent element.
 * @param {DOMNode} childnode  The potential child node.
 * @param {Boolean} [orItself] Whether the method also returns `true` when `pNode` is the `childnode`.
 * @return {Boolean} `false` if the second argument is not a child of the first.
 */
apf.isChildOf = function(pNode, childnode, orItself) {
    if (!pNode || !childnode)
        return false;

    if (childnode.nodeType == 2)
        childnode = childnode.ownerElement || childnode.selectSingleNode("..");

    if (orItself && pNode == childnode)
        return true;

    var loopnode = childnode.parentNode;
    while (loopnode) {
        if (loopnode == pNode)
            return true;
        loopnode = loopnode.parentNode;
    }

    return false;
};

apf.xmlEntityMap = {
    "quot": "34", "amp": "38", "apos": "39", "lt": "60", "gt": "62",
    "nbsp": "160", "iexcl": "161", "cent": "162", "pound": "163", "curren": "164",
    "yen": "165", "brvbar": "166", "sect": "167", "uml": "168", "copy": "169",
    "ordf": "170", "laquo": "171", "not": "172", "shy": "173", "reg": "174",
    "macr": "175", "deg": "176", "plusmn": "177", "sup2": "178", "sup3": "179",
    "acute": "180", "micro": "181", "para": "182", "middot": "183", "cedil": "184",
    "sup1": "185", "ordm": "186", "raquo": "187", "frac14": "188", "frac12": "189",
    "frac34": "190", "iquest": "191", "agrave": ["192", "224"], "aacute": ["193", "225"],
    "acirc": ["194", "226"], "atilde": ["195", "227"], "auml": ["196", "228"],
    "aring": ["197", "229"], "aelig": ["198", "230"], "ccedil": ["199", "231"],
    "egrave": ["200", "232"], "eacute": ["201", "233"], "ecirc": ["202", "234"],
    "euml": ["203", "235"], "igrave": ["204", "236"], "iacute": ["205", "237"],
    "icirc": ["206", "238"], "iuml": ["207", "239"], "eth": ["208", "240"],
    "ntilde": ["209", "241"], "ograve": ["210", "242"], "oacute": ["211", "243"],
    "ocirc": ["212", "244"], "otilde": ["213", "245"], "ouml": ["214", "246"],
    "times": "215", "oslash": ["216", "248"], "ugrave": ["217", "249"],
    "uacute": ["218", "250"], "ucirc": ["219", "251"], "uuml": ["220", "252"],
    "yacute": ["221", "253"], "thorn": ["222", "254"], "szlig": "223", "divide": "247",
    "yuml": ["255", "376"], "oelig": ["338", "339"], "scaron": ["352", "353"],
    "fnof": "402", "circ": "710", "tilde": "732", "alpha": ["913", "945"],
    "beta": ["914", "946"], "gamma": ["915", "947"], "delta": ["916", "948"],
    "epsilon": ["917", "949"], "zeta": ["918", "950"], "eta": ["919", "951"],
    "theta": ["920", "952"], "iota": ["921", "953"], "kappa": ["922", "954"],
    "lambda": ["923", "955"], "mu": ["924", "956"], "nu": ["925", "957"],
    "xi": ["926", "958"], "omicron": ["927", "959"], "pi": ["928", "960"],
    "rho": ["929", "961"], "sigma": ["931", "963"], "tau": ["932", "964"],
    "upsilon": ["933", "965"], "phi": ["934", "966"], "chi": ["935", "967"],
    "psi": ["936", "968"], "omega": ["937", "969"], "sigmaf": "962", "thetasym": "977",
    "upsih": "978", "piv": "982", "ensp": "8194", "emsp": "8195", "thinsp": "8201",
    "zwnj": "8204", "zwj": "8205", "lrm": "8206", "rlm": "8207", "ndash": "8211",
    "mdash": "8212", "lsquo": "8216", "rsquo": "8217", "sbquo": "8218", "ldquo": "8220",
    "rdquo": "8221", "bdquo": "8222", "dagger": ["8224", "8225"], "bull": "8226",
    "hellip": "8230", "permil": "8240", "prime": ["8242", "8243"], "lsaquo": "8249",
    "rsaquo": "8250", "oline": "8254", "frasl": "8260", "euro": "8364",
    "image": "8465", "weierp": "8472", "real": "8476", "trade": "8482",
    "alefsym": "8501", "larr": ["8592", "8656"], "uarr": ["8593", "8657"],
    "rarr": ["8594", "8658"], "darr": ["8595", "8659"], "harr": ["8596", "8660"],
    "crarr": "8629", "forall": "8704", "part": "8706", "exist": "8707", "empty": "8709",
    "nabla": "8711", "isin": "8712", "notin": "8713", "ni": "8715", "prod": "8719",
    "sum": "8721", "minus": "8722", "lowast": "8727", "radic": "8730", "prop": "8733",
    "infin": "8734", "ang": "8736", "and": "8743", "or": "8744", "cap": "8745",
    "cup": "8746", "int": "8747", "there4": "8756", "sim": "8764", "cong": "8773",
    "asymp": "8776", "ne": "8800", "equiv": "8801", "le": "8804", "ge": "8805",
    "sub": "8834", "sup": "8835", "nsub": "8836", "sube": "8838", "supe": "8839",
    "oplus": "8853", "otimes": "8855", "perp": "8869", "sdot": "8901", "lceil": "8968",
    "rceil": "8969", "lfloor": "8970", "rfloor": "8971", "lang": "9001", "rang": "9002",
    "loz": "9674", "spades": "9824", "clubs": "9827", "hearts": "9829", "diams": "9830"
};

/**
 * Escapes "&amp;", greater than, less than signs, quotation marks, and others into
 * the proper XML entities.
 * 
 * @param {String} str The XML string to escape.
 * @param {Boolean} strictMode By default, this function attempts to NOT double-escape XML entities. This flag turns that behavior off when set to `true`.
 * @return {String} The escaped string
 */
apf.escapeXML = function(str, strictMode) {
    if (typeof str != "string")
        return str;
    if (strictMode)
        str = (str || "").replace(/&/g, "&#38;");
    else
        str = (str || "").replace(/&(?!#[0-9]{2,5};|[a-zA-Z]{2,};)/g, "&#38;");
    var map = apf.xmlEntityMap;
    var isArray = apf.isArray;
    return str
        .replace(/"/g, "&#34;")
        .replace(/</g, "&#60;")
        .replace(/>/g, "&#62;")
        .replace(/'/g, "&#39;")
        .replace(/&([a-zA-Z]+);/gi, function(a, m) {
            var x = map[m.toLowerCase()];
            if (x)
                return "&#" + (isArray(x) ? x[0] : x) + ";";
            return a;
        });
};

/**
 * Unescapes `"&#38;"` and other similar XML entities into HTML entities, and then replaces
 * 'special' ones (`&apos;`, `&gt;`, `&lt;`, `&quot;`, `&amp;`) into characters
 * (`'`, `>`, `<`, `"`, `&`).
 *
 * @param {String} str The XML string to unescape
 * @return {String} The unescaped string
 */
apf.unescapeXML = function(str) {
    if (typeof str != "string")
        return str;
    var map = apf.xmlEntityMapReverse;
    var isArray = apf.isArray;
    if (!map) {
        map = apf.xmlEntityMapReverse = {};
        var origMap = apf.xmlEntityMap;
        var keys = Object.keys(origMap);
        for (var val, j, l2, i = 0, l = keys.length; i < l; ++i) {
            val = origMap[keys[i]];
            if (isArray(val)) {
                for (j = 0, l2 = val.length; j < l2; ++j)
                    map[val[j]] = keys[i];
            }
            else
                map[val] = keys[i];
        }
    }
    return str
        .replace(/&#([0-9]{2,5});/g, function(a, m) {
            var x = map[m];
            if (x)
                return "&" + x + ";";
            return a;
        })
        .replace(/&apos;/gi, "'")
        .replace(/&gt;/gi, ">")
        .replace(/&lt;/gi, "<")
        .replace(/&quot;/gi, "\"")
        .replace(/&amp;/gi, "&");
};

/**
 * Determines whether a node is its parent's only child.
 * @param {DOMNode} node     The potential only child.
 * @param {Array}   nodeType List of the node types that this child can be.
 * @returns {Boolean} Whether the node is the only child and of one of the specified node types.
 */
apf.isOnlyChild = function(node, nodeType) {
    if (!node || !node.parentNode || nodeType && nodeType.indexOf(node.nodeType) == -1)
        return false;

    var i, l, cnode, nodes = node.parentNode.childNodes;
    for (i = 0, l = nodes.length; i < l; i++) {
        cnode = nodes[i];
        if (cnode.nodeType == 1 && cnode != node)
            return false;
        if (cnode.nodeType == 3 && !cnode.nodeValue.trim())
            return false;
    }

    return true;
};

/**
 * Gets the position of a DOM node within the list of child nodes of its
 * parent.
 *
 * @param {DOMNode} node The node for which the child position is being determined.
 * @return {Number} The child position of the node.
 */
apf.getChildNumber = function(node, fromList) {
    if (!node) return -1;

    var p = node.parentNode, j = 0;
    if (!p) return -1;
    if (!fromList)
        fromList = p.childNodes;

    if (fromList.indexOf) {
        var idx = fromList.indexOf(node);
        return idx == -1 ? fromList.length : idx;
    }

    for (var i = 0, l = fromList.length; i < l; i++) {
        if (fromList[i] == node)
            return j;
        j++;
    }
    return -1;
};


 // @todo More information will follow....when?
/**
 * Integrates nodes as children of a parent. Optionally, attributes are
 * copied as well.
 *
 * @param {XMLNode} xmlNode The data to merge.
 * @param {XMLNode} parent  The node to merge on.
 * @param {Object}  options An object with the following optional properties:
 *   - [copyAttributes] ([[Boolean]]): Whether the attributes of `xmlNode` are copied as well.
 *   - [clearContents] ([[Boolean]]): Whether the contents of parent is cleared.
 *   - [start] ([[Number]]): This feature is used for the virtual viewport. More information will follow.
 *   - [length] ([[Number]]): This feature is used for the virtual viewport. More information will follow.
 *   - [documentId] ([[Number]]): This feature is used for the virtual viewport. More information will follow.
 *   - [marker] ([[XMLElement]]): This feature is used for the virtual viewport. More information will follow.
 * @return  {XMLNode}  The created xml node
 */
apf.mergeXml = function(XMLRoot, parentNode, options) {
    if (typeof parentNode != "object")
        parentNode = apf.xmldb.getElementById(parentNode);

    if (options && options.clearContents) {
        //Signal listening elements
        var node, j, i,
            nodes = parentNode.selectNodes("descendant::node()[@" + apf.xmldb.xmlListenTag + "]");
        for (i = nodes.length - 1; i >= 0; i--) {
            var s = nodes[i].getAttribute(apf.xmldb.xmlListenTag).split(";");
            for (j = s.length - 1; j >= 0; j--) {
                node = apf.all[s[j]];
                if (!node) continue;
                if (node.dataParent && node.dataParent.xpath)
                    node.dataParent.parent.signalXmlUpdate[node.$uniqueId] = true;
                else if (node.$model)
                    node.$model.$waitForXml(node);
            }
        }

        //clean parent
        nodes = parentNode.childNodes;
        for (i = nodes.length - 1; i >= 0; i--)
            parentNode.removeChild(nodes[i]);
    }

    
    if (options && options.start) { //Assuming each node is in count
        var reserved, beforeNode, nodes, doc, i, l, marker = options.marker;
        if (!marker) {
            //optionally find marker
        }

        //This code assumes that the dataset fits inside this marker

        //Start of marker
        if (marker.getAttribute("start") - options.start == 0) {
            marker.setAttribute("start", options.start + options.length);
            reserved = parseInt(marker.getAttribute("reserved"), 10);
            marker.setAttribute("reserved", reserved + options.length);
            beforeNode = marker;
        }
        //End of marker
        else if (options.start + options.length == marker.getAttribute("end")) {
            marker.setAttribute("end", options.start + options.length);
            beforeNode = marker.nextSibling;
            reserved = parseInt(marker.getAttribute("reserved"), 10) +
                parseInt(marker.getAttribute("end"), 10) - options.length;
        }
        //Middle of marker
        else {
            var m2 = marker.parentNode.insertBefore(marker.cloneNode(true), marker);
            m2.setAttribute("end", options.start - 1);
            marker.setAttribute("start", options.start + options.length);
            reserved = parseInt(marker.getAttribute("reserved"), 10);
            marker.setAttribute("reserved", reserved + options.length);
            beforeNode = marker;
        }

        nodes = XMLRoot.childNodes;

        if (parentNode.ownerDocument.importNode) {
            doc = parentNode.ownerDocument;
            for (i = 0, l = nodes.length; i < l; i++) {
                parentNode.insertBefore(doc.importNode(nodes[i], true), beforeNode)
                  .setAttribute(apf.xmldb.xmlIdTag, options.documentId + "|" + (reserved + i));
            }
        }
        else {
            for (i = nodes.length - 1; i >= 0; i--) {
                parentNode.insertBefore(nodes[0], beforeNode)
                  .setAttribute(apf.xmldb.xmlIdTag, options.documentId + "|" + (reserved + i));
            }
        }
    }
    else
    
    {
        beforeNode = options && options.beforeNode ? options.beforeNode : apf.getNode(parentNode, [0]);
        nodes = XMLRoot.childNodes;
        
        if (options.filter)
            nodes = options.filter(parentNode, nodes);

        if (parentNode.ownerDocument.importNode) {
            doc = parentNode.ownerDocument;
            for (i = 0, l = nodes.length; i < l; i++)
                parentNode.insertBefore(doc.importNode(nodes[i], true), beforeNode);
        }
        else
            for (i = nodes.length - 1; i >= 0; i--)
                parentNode.insertBefore(nodes[0], beforeNode);
    }

    if (options && options.copyAttributes) {
        var attr = XMLRoot.attributes;
        for (i = 0; i < attr.length; i++)
            if (attr[i].nodeName != apf.xmldb.xmlIdTag)
                parentNode.setAttribute(attr[i].nodeName, attr[i].nodeValue);
    }

    return parentNode;
};

/**
 * Sets the node value of a DOM node.
 *
 * @param {XMLElement} xmlNode       The XML node that should receive the node value.
 *                                   When an element node is passed the first text node is set.
 * @param {String}     nodeValue     The value to set.
 * @param {Boolean}    applyChanges  Whether the changes are propagated to the databound elements.
 * @param {apf.UndoData}    undoObj       The undo object that is responsible for archiving the changes.
 */
apf.setNodeValue = function(xmlNode, nodeValue, applyChanges, options) {
    if (!xmlNode)
        return;

    var undoObj, xpath, newNodes;
    if (options) {
        undoObj = options.undoObj;
        xpath = options.xpath;
        newNodes = options.newNodes;

        undoObj.extra.oldValue = options.forceNew
            ? ""
            : apf.queryValue(xmlNode, xpath);

        undoObj.xmlNode = xmlNode;
        if (xpath) {
            xmlNode = apf.createNodeFromXpath(xmlNode, xpath, newNodes, options.forceNew);
        }

        undoObj.extra.appliedNode = xmlNode;
    }

    if (xmlNode.nodeType == 1) {
        if (!xmlNode.firstChild)
            xmlNode.appendChild(xmlNode.ownerDocument.createTextNode(""));

        xmlNode.firstChild.nodeValue = apf.isNot(nodeValue) ? "" : nodeValue;

        if (applyChanges)
            apf.xmldb.applyChanges("text", xmlNode, undoObj);
    }
    else {
        // @todo: this should be fixed in libxml
        if (apf.isO3 && xmlNode.nodeType == 2)
            nodeValue = nodeValue.replace(/&/g, "&amp;");

        var oldValue = xmlNode.nodeValue;
        xmlNode.nodeValue = nodeValue === undefined || nodeValue === null ||
            nodeValue == NaN ? "" : String(nodeValue);

        if (undoObj) {
            undoObj.name = xmlNode.nodeName;
        }

        //AML support - getters/setters would be awesome
        if (xmlNode.$triggerUpdate)
            xmlNode.$triggerUpdate(null, oldValue);

        if (applyChanges) {
            apf.xmldb.applyChanges(xmlNode.nodeType == 2 ? "attribute" : "text", xmlNode.parentNode ||
                xmlNode.ownerElement || xmlNode.selectSingleNode(".."), undoObj);
        }
    }

    
    if (applyChanges) {
        var node;
        if (xpath) {
            var node = undoObj.xmlNode;//.selectSingleNode(newNodes.foundpath);
            if (node.nodeType == 9) {
                node = node.documentElement;
                xpath = xpath.replace(/^[^\/]*\//, "");//xpath.substr(newNodes.foundpath.length);
            }
        }
        else
            node = xmlNode;

        apf.xmldb.applyRDB(["setValueByXpath", node, nodeValue, xpath,
            options && options.forceNew],
            undoObj || {xmlNode: xmlNode}
        );
    }
    
};

/**
 * Sets a value of an XML node based on an xpath statement executed on a referenced XMLNode.
 *
 * @param {XMLNode}   xmlNode  The reference XML node.
 * @param {String}   xpath  The xpath used to select a XML node.
 * @param {String}   value  The value to set.
 * @param {Boolean}   local  Whether the call updates databound UI.
 * @return  {XMLNode}  The changed XML node
 */
apf.setQueryValue = function(xmlNode, xpath, value, local) {
    var node = apf.createNodeFromXpath(xmlNode, xpath);
    if (!node)
        return null;

    apf.setNodeValue(node, value, !local);
    //apf.xmldb.setTextNode(node, value);
    return node;
};

/**
 * Removes an XML node based on an xpath statement executed on a referenced XML node.
 *
 * @param {XMLNode}   xmlNode  The reference XML node.
 * @param {String}   xpath  The xpath used to select a XML node.
 * @return  {XMLNode}  The changed XML node
 */
apf.removeQueryNode = function(xmlNode, xpath, local) {
    var node = apf.queryNode(xmlNode, xpath);
    if (!node)
        return false;

    if (local)
        node.parentNode.removeChild(node);
    else
        apf.xmldb.removeNode(node);

    return node;
};

/**
 * Queries an XML node using xpath for a single string value.
 * @param {XMLElement} xmlNode The XML element to query
 * @param {String}     xpath   The xpath query
 * @return {String} The value of the query result or empty string
 */
apf.queryValue = function (xmlNode, xpath) {
    if (!xmlNode)
        return "";
    if (xmlNode.nodeType == 2)
        return xmlNode.nodeValue;

    if (xpath) {
        xmlNode = xmlNode.selectSingleNode(xpath);
        if (!xmlNode)
            return "";
    }
   return xmlNode.nodeType == 1
        ? (!xmlNode.firstChild ? "" : xmlNode.firstChild.nodeValue)
        : xmlNode.nodeValue;
};

/**
 * Queries an xml node using xpath for multiple string value.
 * @param {XMLElement} xmlNode The xml element to query
 * @param {String}     xpath   The xpath query
 * @return {Array} A list of values resulting from the query
 */
apf.queryValues = function(xmlNode, xpath) {
    var out = [];
    if (!xmlNode) return out;

    var nodes = xmlNode.selectNodes(xpath);
    if (!nodes.length) return out;

    for (var i = 0; i < nodes.length; i++) {
        var n = nodes[i];
        if (n.nodeType == 1)
            n = n.firstChild;
        out.push(n.nodeValue || "");
    }
    return out;
};

/**
 * Executes an xpath expression on any DOM node. This is especially useful
 * for DOM nodes that don't have a good native xpath processor, such as HTML
 * in some versions of Internet Explorer and XML in Webkit.
 *
 * @param {DOMNode} contextNode  The XML node that is subject to the query
 * @param {String}  sExpr        The xpath expression
 * @returns {Array} A list of found XML nodes. The list can be empty
 */
apf.queryNodes = function(contextNode, sExpr) {
    if (contextNode && (apf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style))
        return contextNode.selectNodes(sExpr); //IE55
    //if (contextNode.ownerDocument != document)
    //    return contextNode.selectNodes(sExpr);

    return apf.XPath.selectNodes(sExpr, contextNode);
};

/**
 * Executes an xpath expression on any DOM node. 
 * This is especially useful for DOM nodes that don't have a good native 
 * xpath processor such as html in some versions of internet explorer and XML in
 * webkit. This function only returns the first node found.
 *
 * @param {DOMNode} contextNode  The DOM node that is subject to the query.
 * @param {String}  sExpr        The xpath expression.
 * @returns {XMLNode} The DOM node, or `null` if none was found.
 */
apf.queryNode = function(contextNode, sExpr) {
    if (contextNode && (apf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style))
        return contextNode.selectSingleNode(sExpr); //IE55
    //if (contextNode.ownerDocument != document)
    //    return contextNode.selectSingleNode(sExpr);

    var nodeList = apf.queryNodes(contextNode ? contextNode : null,
        sExpr + (apf.isIE ? "" : "[1]"));
    return nodeList.length > 0 ? nodeList[0] : null;
};

/**
 * Retrieves the attribute of an XML node, or the first parent node that has
 * that attribute set. If no attribute is set, the value is searched for on
 * the appsettings element.
 *
 * @param {XMLElement} xml    The XML node that is the starting point of the search.
 * @param {String}     attr   The name of the attribute.
 * @param {Function}   [func] A callback that is run for every node that is searched.
 * @return {String} The found value, or empty string if none was found.
 */
apf.getInheritedAttribute = function(xml, attr, func) {
    var result, avalue;

    //@todo optimize this and below
    if (xml.nodeType != 1)
        xml = xml.parentNode;

    if (avalue === "")
        return "";

    return !result && attr && apf.config
        ? apf.config[attr]
        : result;
};


/**
 * Creates an XML node based on an xpath statement.
 *
 * @param {DOMNode} contextNode  The DOM node that is subject to the query
 * @param {String}  xPath        The xpath query
 * @param {Array}   [addedNodes] An array that is filled with the added nodes
 * @param {Boolean} [forceNew]   Defines whether a new node is always created
 * @return {DOMNode} The last element found
 */
apf.createNodeFromXpath = function(contextNode, xPath, addedNodes, forceNew) {
    // @todo generalize this to include attributes in if format []
    var xmlNode, foundpath = "", paths = xPath.replace(/('.*?')|(".*?")|\|/g, function(m, m1, m2) {
        if (m1 || m2) return m1 || m2;
        return "-%-|-%-";
    }).split("-%-|-%-")[0].replace(/('.*?')|(".*?")|\/(?!\/)/g, function(m, m1, m2) {
        if (m1 || m2) return m1 || m2;
        return "-%-|-%-";
    }).split("-%-|-%-");
    
    if (!forceNew && (xmlNode = contextNode.selectSingleNode(xPath)))
        return xmlNode;

    var len = paths.length - 1;
    if (forceNew) {
        if (paths[len].trim().match(/^\@(.*)$|^text\(\)$/))
            len--;
    }

    //Directly forwarding to the document element because of a bug in the o3 xml lib
    if (!paths[0]) {
        contextNode = contextNode.ownerDocument.documentElement;
        paths.shift();paths.shift();
        len--;len--;
    }

    for (var addedNode, isAdding = false, i = 0; i < len; i++) {
        if (!isAdding && contextNode.selectSingleNode(foundpath
          + (i != 0 ? "/" : "") + paths[i])) {
            foundpath += (i != 0 ? "/" : "") + paths[i];// + "/";
            continue;
        }

        //Temp hack
        var isAddId = paths[i].match(/(\w+)\[@([\w-]+)=(\w+)\]/);
        

        if (isAddId)
            paths[i] = isAddId[1];

        isAdding = true;
        addedNode = contextNode.selectSingleNode(foundpath || ".")
            .appendChild(contextNode.ownerDocument.createElement(paths[i]));

        if (isAddId) {
            addedNode.setAttribute(isAddId[2], isAddId[3]);
            foundpath += (foundpath ? "/" : "") + isAddId[0];// + "/";
        }
        else
            foundpath += (foundpath ? "/" : "") + paths[i];// + "/";

        if (addedNodes)
            addedNodes.push(addedNode);
    }

    if (!foundpath)
        foundpath = ".";
    if (addedNodes)
        addedNodes.foundpath = foundpath;

    var newNode, lastpath = paths[len],
        doc = contextNode.nodeType == 9 ? contextNode : contextNode.ownerDocument;
    do {
        if (lastpath.match(/^\@(.*)$/)) {
            (newNode || contextNode.selectSingleNode(foundpath))
                .setAttributeNode(newNode = doc.createAttribute(RegExp.$1));
        }
        else if (lastpath.trim() == "text()") {
            newNode = (newNode || contextNode.selectSingleNode(foundpath))
                .appendChild(doc.createTextNode(""));
        }
        else {
            var hasId = lastpath.match(/(\w+)\[@([\w-]+)=(\w+)\]/);
            if (hasId) lastpath = hasId[1];
            newNode = (newNode || contextNode.selectSingleNode(foundpath))
                .appendChild(doc.createElement(lastpath));
            if (hasId)
                newNode.setAttribute(hasId[2], hasId[3]);
        }

        if (addedNodes)
            addedNodes.push(newNode);

        foundpath += (foundpath ? "/" : "") + paths[len];
    } while ((lastpath = paths[++len]));

    return newNode;
};

/*
 * @private
 * @class apf.convertMethods
 */
apf.convertMethods = {
    /**
     * Gets a JSON object containing all the name/value pairs of the elements
     * using this element as it's validation group.
     *
     * @return  {String}  the string representation of a the json object
     */
    "json": function(xml) {
        return JSON.stringify(apf.xml2json(xml));
        /*
        var result = {}, filled = false, nodes = xml.childNodes;
        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 1)
                continue;
            var name = nodes[i].tagName;
            filled = true;

            //array
            var sameNodes = xml.selectNodes(x);
            if (sameNodes.length > 1) {
                var z = [];
                for (var j = 0; j < sameNodes.length; j++) {
                    z.push(this.json(sameNodes[j], result));
                }
                result[name] = z;
            }
            else //single value
                result[name] = this.json(sameNodes[j], result);
        }

        return filled ? result : apf.queryValue(xml, "text()");*/
    },

    "cgivars": function(xml, basename) {
        if (!basename)
            basename = "";

        var value, name, sameNodes, j, l2,
            str = [],
            nodes = xml.childNodes,
            done = {},
            i = 0,
            l = nodes.length;
        for (; i < l; ++i) {
            if (nodes[i].nodeType != 1)
                continue;
            name = nodes[i].tagName;
            if (done[name])
                continue;

            //array
            sameNodes = xml.selectNodes(name);
            if (sameNodes.length > 1) {
                done[name] = true;
                for (j = 0, l2 = sameNodes.length; j < l2; j++) {
                    value = this.cgivars(sameNodes[j], basename + name + "[" + j + "]");
                    if (value)
                        str.push(value);
                }
            }
            else { //single value
                value = this.cgivars(nodes[i], basename + name);
                if (value)
                    str.push(value);
            }
        }

        var attr = xml.attributes;
        for (i = 0, l = attr.length; i < l; i++) {
            if (attr[i].nodeValue) {
                if (basename)
                    str.push(basename + "[" + attr[i].nodeName + "]="
                        + escape(attr[i].nodeValue));
                else
                    str.push(attr[i].nodeName + "=" + escape(attr[i].nodeValue));
            }
        }

        if (str.length)
            return str.join("&");

        value = apf.queryValue(xml, "text()");
        if (basename && value)
            return basename + "=" + escape(value);

        return "";
    },

    "cgiobjects": function(xml, basename, isSub, includeEmpty) {
        if (!basename)
            basename = "";

        var node, name, value, a, i, j, attr, attr_len, isOnly,
            nodes = xml.childNodes,
            output = [],
            tagNames = {},
            nm = "";

        for (i = 0; i < nodes.length; i++) {
            node = nodes[i];

            if (node.nodeType == 1) {
                name = node.tagName;

                isOnly = node.parentNode.selectNodes(name).length == 1
                    ? true
                    : false;

                if (typeof tagNames[name] == "undefined") {
                    tagNames[name] = 0;
                }

                nm = basename
                   + (isSub ? "[" : "") + name + (isSub ? "]" : "")
                   + (isOnly ? "" : "[" + tagNames[name] + "]");

                attr = node.attributes;
                attr_len = node.attributes.length;

                if (attr_len > 0) {
                    for (j = 0; j < attr_len; j++) {
                        if (!(a = attr[j]).nodeValue)
                            continue;

                        output.push(nm + "[_" + a.nodeName + "]="
                            + escape(a.nodeValue.trim()));
                    }
                }

                value = this.cgiobjects(node, nm, true);

                if (value.dataType !== 1) {
                    output.push(value);
                }
                else {
                    if (node.firstChild && node.firstChild.nodeValue.trim()) {
                        output.push(nm + (attr_len > 0 ? "[_]=" : "=")
                            + escape(node.firstChild.nodeValue.trim()));
                    }
                    else {
                        if (attr_len == 0) {
                            if (includeEmpty) {
                                output.push(nm);
                            }
                        }
                    }
                }

                tagNames[name]++;
            }
            //@todo, that's that ??
            //handle node values (for form submission)
            else if (node.nodeType == 3 && isSub) {
                var nval = node.nodeValue;

                if (nval && nval.trim() !== "") {
                    output.push(basename + "=" + escape(nval));
                }

                //was: output = node.nodeType;
            }
        }

        if (!isSub && xml.getAttribute("id"))
            output.push("id=" + escape(xml.getAttribute("id")));

        if (output.length)
            return output.join("&");

        return output;
    }
};

/**
 * Converts XML to another format.
 *
 * @param {XMLElement} xml  The [[term.datanode data node]] to convert.
 * @param {String}     to   The format to convert the XML to. It can be one of the following values:
 *   - `"json"`:       converts to a json string
 *   - `"cgivars"`:    converts to cgi string
 *   - `"cgiobjects"`: converts to cgi object
 * @return {String} The result of the conversion.
 */
apf.convertXml = function(xml, to) {
    return apf.convertMethods[to](xml);
};

/**
 * Returns the first text or cdata child of a [[term.datanode data node]].
 *
 * @param {XMLElement} x The XML node to search.
 * @return {XMLNode} The found XML node, or `null`.
 */
apf.getTextNode = function(x) {
    for (var i = 0, l = x.childNodes.length; i < l; ++i) {
        if (x.childNodes[i].nodeType == 3 || x.childNodes[i].nodeType == 4)
            return x.childNodes[i];
    }
    return false;
};

/**
 * @private
 */
apf.getBoundValue = function(amlNode, xmlRoot, applyChanges) {
    if (!xmlRoot && !amlNode.xmlRoot)
        return "";

    var xmlNode = amlNode.$getDataNode("value", amlNode.xmlRoot);

    return xmlNode ? apf.queryValue(xmlNode) : "";
};

/**
 * @private
 */
apf.getArrayFromNodelist = function(nodelist) {
    for (var nodes = [], j = 0, l = nodelist.length; j < l; ++j)
        nodes.push(nodelist[j]);
    return nodes;
};

/**
 * Serializes the children of a node into a string.
 *
 * @param {XMLElement} xmlNode The XML node to serialize.
 * @return {String} The children as a string
 */
apf.serializeChildren = function(xmlNode) {
    var node,
        s = [],
        nodes = xmlNode.childNodes,
        i = 0,
        l = nodes.length;
    for (; i < l; ++i) {
        s[i] = (node = nodes[i]).nodeType == 1
            ? node.xml || node.serialize()
            : (node.nodeType == 8 ? "" : node.nodeValue);
    }
    return s.join("");
};

/**
 * Returns a string version of the {@link term.datanode data node}.
 *
 * @param {XMLElement} xmlNode The {@link term.datanode data node} to serialize.
 * @return {String} The serialized version of the {@link term.datanode data node}.
 */
apf.getXmlString = function(xmlNode) {
    var xml = apf.xmldb.cleanNode(xmlNode.cloneNode(true));
    return xml.xml || xml.serialize();
};

/**
 * Creates XML nodes from an XML string recursively.
 *
 * @param {String}  strXml     The XML definition
 * @param {Boolean} [noError]  Whether an exception should be thrown by the parser
 *                             when the XML is not valid
 * @param {Boolean} [preserveWhiteSpace]  Whether whitespace that is present between
 *                                        XML elements should be preserved
 * @return {XMLNode} The created XML node
 */
apf.getXml = function(strXml, noError, preserveWhiteSpace) {
    return apf.getXmlDom(strXml, noError, preserveWhiteSpace).documentElement;
};

/**
 * Formats an XML string with proper indentation. Also known as pretty printing.
 * @param {String} strXml The XML string to format.
 * @return {String} The formatted string.
 */
apf.formatXml = function(strXml) {
    strXml = strXml.trim();

    var lines = strXml.split("\n"),
        depth = 0,
        i = 0,
        l = lines.length;
    for (; i < l; ++i)
        lines[i] = lines[i].trim();
    lines = lines.join("\n").replace(/\>\n/g, ">").replace(/\>/g, ">\n")
        .replace(/\n\</g, "<").replace(/\</g, "\n<").split("\n");
    lines.removeIndex(0);//test if this is actually always fine
    lines.removeIndex(lines.length);

    for (i = 0, l = lines.length; i < l; i++)
        lines[i] = "    ".repeat((lines[i].match(/^\s*\<\//)
            ? (depth==0)?0:--depth
            : (lines[i].match(/^\s*\<[^\?][^>]+[^\/]\>/) ? depth++ : depth))) + lines[i];
    if (!strXml)
        return "";

    return lines.join("\n");
};

//@todo this function needs to be 100% proof, it's the core of the system
//for RDB: xmlNode --> Xpath statement
apf.xmlToXpath = function(xmlNode, xmlContext, useAID) {
    if (!xmlNode) //@todo apf3.0
        return "";

    if (useAID === true && xmlNode.nodeType == 1 && xmlNode.getAttribute(apf.xmldb.xmlIdTag)) {
        return "//node()[@" + apf.xmldb.xmlIdTag + "='"
            + xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "']";
    }

    if (apf != this && this.lookup && this.select) {
        var unique, def = this.lookup[xmlNode.tagName];
        if (def) {
            //unique should not have ' in it... -- can be fixed...
            unique = xmlNode.selectSingleNode(def).nodeValue;
            return "//" + xmlNode.tagName + "[" + def + "='" + unique + "']";
        }

        for (var i = 0, l = this.select.length; i < l; i++) {
            if (xmlNode.selectSingleNode(this.select[i][0])) {
                unique = xmlNode.selectSingleNode(this.select[i][1]).nodeValue;
                return "//" + this.select[i][0] + "[" + this.select[i][1]
                    + "='" + unique + "']";
            }
        }
    }

    if (xmlNode == xmlContext)
        return ".";

    if (xmlNode.nodeType != 2 && !xmlNode.parentNode && !xmlNode.ownerElement) {
        

        return false;
    }

    var str = [], lNode = xmlNode;
    if (lNode.nodeType == 2) {
        str.push("@" + lNode.nodeName);
        lNode = lNode.ownerElement || xmlNode.selectSingleNode("..");
    }

    var id;//, pfx = "";
    while (lNode && lNode.nodeType == 1) {
        if (lNode == xmlContext) {
            //str.unshift("/");//pfx = "//";
            break;
        }
        str.unshift((lNode.nodeType == 1 ? lNode.tagName : "text()") +
            "[" + (useAID && (id = lNode.nodeType == 1 && lNode.getAttribute(apf.xmldb.xmlIdTag))
                ? "@" + apf.xmldb.xmlIdTag + "='" + id + "'"
                : (apf.getChildNumber(lNode, lNode.parentNode.selectNodes(lNode.nodeType == 1 ? lNode.tagName : "text()")) + 1)) +
            "]");
        lNode = lNode.parentNode;
    };

    return (str[0] == "/" || xmlContext && xmlContext.nodeType == 1 ? "" : "/") + str.join("/"); //pfx +
};

//for RDB: Xpath statement --> xmlNode
apf.xpathToXml = function(xpath, xmlNode) {
    if (!xmlNode) {
        

        return false;
    }

    return xmlNode.selectSingleNode(xpath);
};


apf.n = function(xml, xpath) {
    return new apf.xmlset(xml, xpath, true);
};

apf.b = function(xml, xpath) {
    return new apf.xmlset(xml, xpath);
};

apf.b.$queue = [];
apf.b.$state = 0;

/*
 * Naive jQuery like set implementation
 * @todo add dirty flags
 * @todo add query queue
 * @todo rewrite to use arrays
 */
apf.xmlset = function(xml, xpath, local, previous) {
    if (typeof xml == "string")
        xml = apf.getXml(xml);

    this.$xml = xml;
    if (xml)
        this.$nodes = xml.dataType == apf.ARRAY ? xml : (xpath ? xml.selectNodes(xpath) : [xml]);
    this.$xpath = xpath || ".";
    this.$local = local;
    this.$previous = previous;
};

(function(){
    this.add = function(){}; //@todo not implemented

    this.begin = function(){
        apf.b.$state = 1;
        return this;
    };

    this.commit = function(at, rmt, uri) {
        if (apf.b.$queue.length) {
            if (rmt) {
                var _self = this;
                rmt.addEventListener("rdbsend", function(e) {
                    if (!uri || e.uri == uri) {
                        _self.rdbstack = e.message;
                        rmt.removeEventListener("rdbsend", arguments.callee);
                    }
                });
            }

            at.execute({
                action: 'multicall',
                args: apf.b.$queue
            });

            if (rmt)
                rmt.$processQueue(rmt);
        }

        apf.b.$queue = [];
        apf.b.$state = 0;
        return this;
    };

    this.rollback = function(){
        apf.b.$queue = [];
        apf.b.$state = 0;
        return this;
    };

    this.getRDBMessage = function(){
        return this.rdbstack || [];
    };

    this.before = function(el) {
        el = typeof el == "function" ? el(i) : el;

        for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
            node = this.$nodes[i];
            if (this.$local)
                node.parentNode.insertBefore(el, node);
            else
                apf.xmldb.appendChild(node.parentNode, el, node);
        }
        return this;
    };

    this.after = function(el) {
        el = typeof el == "function" ? el(i) : el;

        for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
            node = this.$nodes[i];
            if (this.$local)
                node.parentNode.insertBefore(el, node.nextSibling);
            else
                apf.xmldb.appendChild(node.parentNode, el, node.nextSibling);
        }

        return this;
    };

    this.andSelf = function(){};

    this.append = function(el) {
        for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) {
            node = this.$nodes[i];
            child = typeof el == "function" ? el(i, node) : el;

            if (apf.b.$state)
                apf.b.$queue.push({
                    action: 'appendChild',
                    args: [node, child]
                });
            else if (this.$local)
                node.appendChild(child);
            else
                apf.xmldb.appendChild(node, child);

        }

        return this;
    };

    this.appendTo = function(target) {
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            target.appendChild(this.$nodes[i]);
        }
        return this;
    };

    this.prepend = function(el) {
        for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
            node = this.$nodes[i];
            node.insertBefore(typeof el == "function" ? el(i, node) : el, node.firstChild);
        }

        return this;
    };

    this.prependTo = function(target) {
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            target.insertBefore(this.$nodes[i], target.firstChild);
        }
        return this;
    };

    this.attr = function(attrName, value) {
        if (arguments.length === 1) {
            return this.$nodes && this.$nodes[0] && this.$nodes[0].getAttribute(attrName) || "";
        }
        else {
            for (var i = 0, l = this.$nodes.length; i < l; i++) {
                if (apf.b.$state)
                    apf.b.$queue.push({
                        action: 'setAttribute',
                        args: [this.$nodes[i], attrName, value]
                    });
                else if (this.$local)
                    this.$nodes[i].setAttribute(attrName, value);
                else
                    apf.xmldb.setAttribute(this.$nodes[i], attrName, value);
            }
        }

        return this;
    };

    this.removeAttr = function(attrName) {
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            if (apf.b.$state)
                apf.b.$queue.push({
                    action: 'removeAttribute',
                    args: [this.$nodes[i], attrName]
                });
            else if (this.$local)
                this.$nodes[i].removeAttribute(attrName);
            else
                apf.xmldb.removeAttribute(this.$nodes[i], attrName);
        }

        return this;
    };

    this.xml = function(){
        var str = [];
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            str.push(this.$nodes[i].xml);
        }
        return str.join("\n");
    };

    this.get = 
    this.index = function(idx) {
        idx = idx || 0;
        return apf.getChildNumber(this.$nodes[idx], this.$nodes[idx].parentNode.getElementsByTagName("*"))
    };

    this.eq = function(index) {
        return index < 0 ? this.$nodes[this.$nodes.length - index] : this.$nodes[index];
    };

    this.size = 
    this.length = function(){
        return this.$nodes.length;
    };

    this.load = function(url) {

    };

    this.next = function(selector) {
        if (!selector)
            selector = "node()[local-name()]";
        return new apf.xmlset(this.$xml, "((following-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
            ")[1])[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
    };

    this.nextAll = function(selector) {
        if (!selector)
            selector = "node()[local-name()]";
        return new apf.xmlset(this.$xml, "(following-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
            ")[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
    };

    this.nextUntil = function(){};

    this.prev = function(selector) {
        if (!selector)
            selector = "node()[local-name()]";
        return new apf.xmlset(this.$xml, "((preceding-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
            ")[1])[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
    };

    this.prevAll = function(selector) {
        if (!selector)
            selector = "node()[local-name()]";
        return new apf.xmlset(this.$xml, "(preceding-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
            ")[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
    };

    this.not = function(){};

    this.parent = function(selector) {
        return new apf.xmlset(this.$xml.parentNode, this.$local, this);
    };

    this.parents = function(selector) {};
    this.pushStack = function(){};
    this.replaceAll = function(){};
    this.replaceWith = function(el) {
        for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) {
            node = this.$nodes[i];
            child = typeof el == "function" ? el(i, node) : el;

            if (apf.b.$state)
                apf.b.$queue.push({
                    action: 'replaceNode',
                    args: [child, node]
                });
            else if (this.$local)
                node.parentNode.replaceChild(child, node);
            else
                apf.xmldb.replaceNode(child, node);

        }

        return this;
    };

    this.siblings = function(selector) {
        //preceding-sibling::
        //return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/node()[self::" + selector.split("|").join("|self::") + "]");
    };

    this.text = function(){

    };

    this.toArray = function(){
        var arr = [];
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            arr.push(this.$nodes[i]);
        }
        return arr;
    };

    this.detach = function(selector) {
        var items = [];

        for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
            node = this.$nodes[i];
            if (!node.selectSingleNode("self::node()[" + selector + "]"))
                continue;

            if (apf.b.$state)
                apf.b.$queue.push({
                    action: 'removeNode',
                    args: [node]
                });
            else if (this.$local)
                node.parentNode.removeChild(node);
            else
                apf.xmldb.removeNode(node);

            items.push(node);
        }

        return new apf.xmlset(items, "", this.$local, this);
    };

    this.remove = function(selector) {
        for (var node, n = this.$nodes, i = n.length - 1; i >= 0; i--) {
            node = n[i];
            if (selector && !node.selectSingleNode("self::node()[" + selector + "]"))
                continue;

            if (apf.b.$state)
                apf.b.$queue.push({
                    action: 'removeNode',
                    args: [node]
                });
            else if (this.$local)
                node.parentNode.removeChild(node);
            else
                apf.xmldb.removeNode(node);
        }

        return this;
    };

    this.children = function(selector) {
        var nodes = [];
        for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
            var list = (node = this.$nodes[i]).selectNodes(selector);
            for (var j = 0, jl = list.length; j < jl; j++) {
                nodes.push(list[j]);
            }
        }
        return new apf.xmlset(nodes, null, this.$local, this);
    };

    this.children2 = function(selector) {
        return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/node()[self::" +
            selector.split("|").join("|self::") + "]", this.$local, this);
    };

    this.has = 
    this.find = function(path) {
        return new apf.xmlset(this.$xml, "(" + this.$xpath + ")//" +
            path.split("|").join("|self::"), this.$local, this);
    };

    this.query = function(path) {
        return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/" +
            path.split("|").join("|(" + this.$xpath + ")/"), this.$local, this);
    };

    this.filter = function(filter) {
        var newList = [];
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            if (this.$nodes[i].selectSingleNode("self::node()[" + filter + "]"))
                newList.push(this.$nodes[i]);
        }
        return new apf.xmlset(newList, null, this.$local, this);
    };

    this.end = function(){
        return this.$previous || this;
    };

    this.is = function(selector) {
        return this.filter(selector) ? true : false;
    };

    this.contents = function(){
        return this.children("node()");
    };

    this.has = function(){
        //return this.children(
    };

    this.val = function(value) {
        if (arguments.length) {
            apf.setQueryValue(this.$xml, this.$xpath, value);
            return this;
        }
        else
            return apf.queryValue(this.$xml, this.$xpath);
    };

    this.vals = function(){
        return apf.queryValues(this.$xml, this.$xpath);
    };

    this.node = function(){
        return apf.queryNode(this.$xml, this.$xpath);
    };

    this.nodes = function(){
        return apf.queryNodes(this.$xml, this.$xpath);
    };

    this.clone = function(deep) {
        if (this.$nodes.length == 1)
            return new apf.xmlset(this.$nodes[0].cloneNode(true), "", this.$local, this);

        var nodes = [];
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            nodes.push(this.$nodes[i].cloneNode(deep == undefined ? true : deep));
        }

        return new apf.xmlset(nodes, "", this.$local, this);
    };

    this.context = function(){
        return this.$xml;
    };

    this.data = function(data) {
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            apf.setQueryValue(this.$nodes[i], ".", data);
        }
        return this;
    };

    this.each = function(func) {
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            func.call(this.$nodes[i], i);
        }
        return this;
    };

    this.eachrev = function(func) {
        for (var i = this.$nodes.length - 1; i >= 0; i--) {
            func.call(this.$nodes[i], i);
        }
        return this;
    };

    this.map = function(callback) {
        var values = [];
        for (var i = 0, l = this.$nodes.length; i < l; i++) {
            values.push(callback(this.$nodes[i]));
        }
        return new apf.xmlset(values, "", this.$local, this); //blrghhh
    };

    this.empty = function(){
        this.children().detach();
        return this;
    };

    this.first = function(){
        return new apf.xmlset(this.$xml, "(" + this.$xpath + ")[1]", this.$local, this);
    };

    this.last = function(){
        return new apf.xmlset(this.$xml, "(" + this.$xpath + ")[last()]", this.$local, this);
    };
}).call(apf.xmlset.prototype);








/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Manages the z-index of all elements in the UI. It takes care of different
 * regions in the z-dimension preserved for certain common UI scenarios.
 *
 * #### Remarks
 * 
 *  The following regions are defined:
 *  From:         To:           For:
 *           10        10000  Common Elements (each element a unique z index)
 *       100000       110000  Plane (Modal Windows / Maximized Panels) (latest shown highest)
 *       200000       210000  Popup (Menus / Dropdown Containers) (latest shown highest)
 *       300000       310000  Notifiers
 *       400000       410000  Drag Indicators
 *      1000000      1100000  Print
 *
 * @private
 */
apf.zmanager = function(){
    var count = {
        "default" : {
            level: 10
        },
        "plane" : {
            level: 100000
        },
        "popup" : {
            level: 200000
        },
        "notifier" : {
            level: 300000
        },
        "drag" : {
            level: 400000
        },
        "print" : {
            level: 1000000
        }
    };
    
    this.set = function(type, main, companion) {
        main.style.zIndex = count[type].level++;
        if (companion) {
            //if (companion.$storedZ == undefined)
                companion.$storedZ = companion.style.zIndex;
            companion.style.zIndex = count[type].level++
        }
    }
    
    this.clear = function(main, companion) {
        if (companion.style.zIndex == parseInt(main.style.zIndex) + 1)
            companion.style.zIndex = companion.$storedZ;
        companion.$storedZ = undefined;
    }
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @class apf.history
 *
 * Implements a hash change listener. The 'hash' is the part of the
 * location string in your browser that takes care of pointing to a section
 * within the current application.
 * 
 * #### Example
 * ```
 *  www.example.com/index.php#products
 * ```
 * 
 * #### Remarks
 * 
 * In modern browsers (after 2009) the location hash can be set by script and
 * {@link apf.history@hashchange} is called when it's changed by using the back or forward
 * button of the browsers. In most of the current (2009) browsers this is not the case.
 * This object handles that logic for those browsers in such a way that the user
 * of the application can use the back and forward buttons in an intuitive manner.
 *
 * Note: For Internet Explorer 8, when switching between the IE7 compatibility mode
 * and IE8 mode the history navigation breaks. A browser restart is then 
 * required to fix it. Individually history navigation works fine in each mode.
 *
 */
/**
 * @event hashchange Fires when the hash changes. This can be either by setting
 * a new hash value or when a user uses the back or forward button. Typing a
 * new hash value in the location bar will also trigger this function.
 *
 * #### Example
 *
 * ```javascript
 *  apf.addEventListener("hashchange", function(e) {
 *      var info = e.tab.split(":");
 *
 *      switch(info[0]) {
 *          case "product": //hash is for instance 'product:23849'
 *              //Sets the application state to display product info
 *              //For more information see apf.state
 *              stProduct.activate(); 
 *              //Loads a product by id
 *              loadProduct(info[1]); 
 *              break;
 *          case "news":
 *              stNews.activate();
 *              break;
 *      }
 *  });
 * ```
 *
 * @default_private
 */
apf.history = {
    inited: false,
    tab: null,
    past: [],
    future: [],
    delay: 1,

    init: function(defName, getVar, delay) {
        if (this.inited || window.history.pushState)
            return;

        if (delay)
            this.delay = delay;

        this.inited = true;

        var name, _self = this;
        function preInit() {
            name = apf.dispatchEvent("hashinit")
              || location.href.match(/#(.*)$/) && decodeURI(RegExp.$1)
              || apf._GET[getVar || -1] || defName;


            location.hash = name;
            _self.hasChanged(name || null);
        }

        if (apf.supportHashChange) {
            $setTimeout(function() {
                preInit();

                window.onhashchange = function(){
                    var tab = location.hash.replace("#", "");
                    apf.history.hasChanged(decodeURI(tab));
                };
            });
        }
        else if (apf.isIE) {
            preInit();
            var str =
                "<style>\
                    BODY, HTML{margin:0}\
                    h1{height:100px; margin:0; padding:0; overflow:hidden}\
                </style>\
                <body>\
                    <h1 id='" + name + "'>0</h1>\
                </body>\
                <script>\
                    var lastURL = -1;\
                    if (document.all)\
                        document.body.onscroll = checkUrl;\
                    else\
                        setInterval('checkUrl()', 200);\
                    \
                    function checkUrl(){\
                        var iScr = (document.all ? document.body : document.documentElement).scrollTop;\
                        if (lastURL == iScr) return;\
                        top.apf.history.hasChanged(document.getElementsByTagName('h1')[Math.round(iScr / 100)].id, true);\
                        lastURL = iScr;\
                        }\
                    checkUrl();\
                </script>";

            if (top == self) {
                document.body.insertAdjacentHTML("beforeend",
                    "<iframe name='nav' style2='position:absolute;left:10px;top:10px;height:100px;width:100px;z-index:1000'\
                       style='width:1px;height:1px;' src='about:blank'></iframe>");
                document.frames["nav"].document.open();
                document.frames["nav"].document.write(str);
                document.frames["nav"].document.close();
            }

            this.iframe = document.frames["nav"];// : document.getElementById("nav").contentWindow;
            //Check to see if url has been manually changed
            this.timer2 = setInterval(function(){
                if (!apf.history.changingHash && location.hash != "#" + apf.history.tab) {
                    var name = location.hash.replace(/^#/, "");
                    var tab = apf.history.tab;
                    apf.history.setHash(name, true, true);
                    apf.history.tab = tab;
                    apf.history.hasChanged(name);
                }
            }, apf.history.delay || 1);
        }
        else {
            preInit();
            apf.history.lastUrl = location.href.toString();
            this.timer2 = setInterval(function(){
                if (apf.history.lastUrl == location.href.toString())
                    return;

                apf.history.lastUrl = location.href.toString();
                //var tab = location.href.replace(/^.*#(.*)$/, "$1")
                var tab = location.hash.replace("#", "");//.replace(/^.*#(.*)$/,"$1");
                apf.history.hasChanged(decodeURI(tab));
            }, 20);
        }
    },
    to_name: null,
    
    /**
     * Sets the hash value of the location bar in the browser. This is used
     * to represent the state of the application for use by the back and forward
     * buttons, as well as for use when bookmarking or sharing URLs.
     * @param {String}  name    The new hash value
     * @param {Boolean} timed   Whether to add a delay to setting the value
     * @param {Boolean} force   Forces the change to overwrite any existing value
     */
    setHash: function(name, timed, force) {
        if (this.changing || this.tab == name || !force
          && decodeURIComponent(location.hash) == "#" + decodeURIComponent(name)) {
            this.to_name = name;
            return;
        }

        if (!apf.supportHashChange && apf.isIE  && !timed) {
            this.to_name = name;
            return $setTimeout(function(){
                apf.history.setHash(apf.history.to_name, true, force);
            }, 200);
        }

        this.changePage(name);
        if (!this.inited)
            return this.init(name);

        if (!apf.supportHashChange && apf.isIE) {
            var h = this.iframe.document.body
                .appendChild(this.iframe.document.createElement('h1'));
            h.id = name;
            h.innerHTML = "1";
        }

        (!apf.supportHashChange && apf.isIE ? this.iframe : window).location.href = "#" + name;
        
        if (!apf.isIE && !apf.isGecko && !apf.isIphone)
            apf.history.lastUrl = location.href.toString();
        //else if (apf.isIE8)
    },

    timer: null,
    changePage: function(tab, force) {
        if (!apf.supportHashChange && apf.isIE) {
            this.tab = tab;
            this.changingHash = true;
            clearTimeout(this.timer);
            this.timer = $setTimeout(function(){
                location.hash = tab;
                apf.history.changingHash = false;
            }, 1);
        }
    },

    update: function(tab) {
        var i, l, idx = 0;

        // check past:
        for (i = 0, l = this.past.length; i < l && idx === 0; i++) {
            if (this.past[i] == tab)
                idx = i + 1;
        }
        if (idx > 0) {
            // part of past up till tab (Array.slice), EXCLUDING tab
            this.future = this.past.slice(idx, this.past.length - 1)
                                   .concat(this.future).makeUnique();
            this.past.splice(idx, this.past.length - (idx));
            idx = -idx;
        }
        else {
            // check future:
            for (i = 0, l = this.future.length; i < l && idx === 0; i++) {
                if (this.future[i] == tab) {
                    idx = i + 1;
                    // current past + part of the future up till tab
                    // (Array.splice), INCLUDING tab
                    this.past = this.past.concat(this.future
                        .splice(0, this.future.length - idx)).makeUnique();
                }
            }
            if (idx === 0) {
                this.past.push(tab);
                idx = 1;
            }
        }

        return idx;
    },

    hasChanged: function(tab, force) {
        if (tab == this.tab && !force) 
            return;
        this.changePage(tab, force);

        this.changing = true;
        if (apf.dispatchEvent("hashchange", {
            oldURL: this.tab,
            newURL: tab,
            tab: tab, 
            index: this.update(tab)
        }) === false) {
            tab = location.hash = this.tab;
        };
        this.changing = false;

        this.tab = tab;
    }
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.config = new apf.Class().$init();
apf.extend(apf.config, {
    //Defaults
    disableRightClick: false,
    allowSelect: false,
    allowBlur: true,
    autoDisableActions: true,
    autoDisable: false, /** @todo fix this to only autodisable when createmodel is not true */
    disableF5          : true,
    autoHideLoading: true,
    disableSpace: true,
    defaultPage: "home",
    disableBackspace: true,
    undokeys: false,
    outline: false,
    dragOutline: false,
    resizeOutline: false,
    autoDisableNavKeys: true,
    disableTabbing: false,
    resourcePath: null,
    initdelay: true,
    liveText: false,
    
    
    
    skinset: "default",
    name: apf.isO3 ? "o3App" : self.window && window.location.href.replace(/[^0-9A-Za-z_]/g, "_"),

    tags: {},
    defaults: {},
    baseurl: "",
    
    "empty-message"    : "No items",
    "loading-message"  : "Loading...",
    "offline-message"  : "You are currently offline.",
    
    setDefaults: function(){
        
    },

    getDefault: function(type, prop) {
        var d = this.defaults[type];
        if (!d)
            return;

        for (var i = d.length - 1; i >= 0; i--) {
            if (d[i][0] == prop)
                return d[i][1];
        }
    },

    $handlePropSet: function(name, value) {
        //this[name] = value;
        //@todo I dont want to go through all the code again, maybe later
        this[name.replace(/-(\w)/g, function(m, m1) {
            return m1.toUpperCase()
        })] = this[name] = value;
        
        (this.$propHandlers && this.$propHandlers[name]
          || apf.GuiElement.propHandlers[name] || apf.K).call(this, value);
    },
    
    $inheritProperties: {},
    
    $propHandlers: {
        "baseurl" : function(value) {
            this.baseurl = apf.parseExpression(value);
        },
        "language" : function(value) {
            
        },
        "resource-path" : function(value) {
            this.resourcePath = apf.parseExpression(value || "")
              .replace(/resources\/?|\/$/g, '');
        },
        
        
        "skinset" : function(value) {
            if (this.$amlLoaded)
                apf.skins.changeSkinset(value);
        },
        
        
        "outline" : function(value) {
            this.dragOutline = 
            this.resizeOutline = 
            this.outline = apf.isTrue(value);
        },
        "drag-outline" : function(value) {
            this.dragOutline = value
              ? apf.isTrue(value)
              : false;
        },
        "resize-outline" : function(value) {
            this.resizeOutline = value
              ? !apf.isFalse(value)
              : false;
        },
        
        
        "login" : function(value, x) {
            apf.auth.init(x);
        },
        
        
        
        
        
        "debug" : function(value) {
            
            apf.debug = value;
        }
    }
});


if (apf.history)
    apf.addEventListener("load", function(){
        apf.history.init(apf.config.defaultPage, "tab");
    });







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.offline = {
    onLine: true
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




/**
 * Stores data using a {@link term.datainstruction data instruction}.
 *
 * 
 * @param {String}      instruction  The {@link term.datainstruction data instruction} to be used to store the data.
 * @param {Object}      [options]    The options for this instruction. Available properties include:
 *   - multicall ([[Boolean]]): Whether this call should not be executed immediately, but saved for later sending using the `purge()` command
 *   - userdata (`Mixed`): Any data that is useful to access in the callback function
 *   - args ([[Array]]): The arguments of the call, overriding any specified in the data instruction
 *   - [xmlContext] ([[XMLElement]]): The subject of the xpath queries
 *   - [callback] ([[Function]]): The code that is executed when the call returns, either successfully or not
 *   {: #saveDataOptions}
 */
apf.saveData = 

/**
 * Retrieves data using a {@link term.datainstruction data instruction}.
 * 
 * #### Example
 * 
 * Here are several uses for data instructions:
 * 
 * ```xml
 *  <!-- loading aml from an xml file -->
 *  <a:bar aml="moreaml.xml" />
 *
 *  <a:bindings>
 *    <!-- loads data using an remote procedure protocol -->
 *    <a:load   get = "{comm.getData()}" />
 *
 *    <!-- inserts data using an remote procedure protocol -->
 *    <a:insert get = "{comm.getSubData([@id])}" />
 *  </a:bindings>
 *
 *  <a:actions>
 *    <!-- notifies the server that a file is renamed -->
 *    <a:rename set = "update_file.jsp?id=[@id]&name=[@name]" />
 *
 *    <!-- adds a node by retrieving it's xml from the server. -->
 *    <a:add    get = "new_user.xml" />
 *  </a:actions>
 *
 *  <!-- creates a model which is loaded into a list -->
 *  <a:list model="{webdav.getRoot()}" />
 *
 *  <!-- loads data into a model and when submitted sends the altered data back -->
 *  <a:model load="load_contact.jsp" submission="save_contact.jsp" />
 * ```
 *
 * @method getData
 * @param {String}      instruction  The {@link term.datainstruction data instruction} to be used to retrieve the data.
 * @param {XMLElement}  [xmlContext] The subject of the xpath queries
 * @param {Object}      [options]    The options for this instruction. Available properties include:
 *   - multicall ([[Boolean]]): Whether this call should not be executed immediately, but saved for later sending using the `purge()` command
 *   - userdata (`Mixed`): Any data that is useful to access in the callback function
 *   - args ([[Array]]): The arguments of the call, overriding any specified in the data instruction
 *   - [xmlContext] ([[XMLElement]]): The subject of the xpath queries
 *   - [callback] ([[Function]]): The code that is executed when the call returns, either successfully or not
 * @param {Function}    [callback]   The code that is executed when the call returns, either successfully or not
 */
apf.getData = function(instruction, options) {
    if (!instruction) return false;

    //Instruction type detection
    var result, chr = instruction.charAt(0), callback = options.callback;

    
    var gCallback = 
    

    function(data, state, extra) {
        var callback = options.callback
        
        if (state != apf.SUCCESS)
            return callback(data, state, extra || {});

        //Change this to warning?
        /*if (!data) {
            throw new Error(apf.formatErrorString(0, null,
                "Loading new data", "Could not load data. \n\
                Data instruction: '" + instruction + "'"));
        }*/

        return callback(data, state, extra || {});
    }
    
    if (!options) options = {}; //@todo optimize?
    var fParsed = options.fParsed || (instruction.indexOf("{") > -1 || instruction.indexOf("[") > -1
        ? apf.lm.compile(instruction, {
            withopt: true, 
            precall: options.precall,
            alwayscb: true, 
            simplexpath: true
          })
        : {str: instruction, type: 2}); 

    if (options.precall && !options._pc) {
        options.asyncs = fParsed.asyncs;
        options._pc = 1;
    }

    //@todo hack because we're not using compileNode.. an imperfection..
    if (fParsed.type == 3) {
        if (fParsed.xpaths[0]) {
            var model = fParsed.xpaths[0], xpath = fParsed.xpaths[1];
            
            //@todo can this be async?
            if (model == "#" || xpath == "#") { //When there is a set model and not a generated xpath
                var m = (apf.lm.compile(instruction, {
                    xpathmode: 5
                }))();
                
                //@todo apf3 this needs to be fixed in live markup
                if (typeof m != "string") {
                    model = m.model && m.model.$isModel && m.model;
                    if (model)
                        xpath = m.xpath;
                    else if (m.model) {
                        model = apf.xmldb.findModel(m.model);
                        xpath = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better
                    }
                    else {
                        //Model is not yet available. When it comes available we will be recalled (at least for prop binds)
                        return;
                    }
                }
                else model = null;
            }
            else {
                
                model = apf.nameserver.get("model", model)
                
            }
            
            
        
            return gCallback(model.data.selectSingleNode(xpath), apf.SUCCESS, {});
        }
        else {
            
            
            return gCallback(options.xmlNode.data.selectSingleNode(fParsed.xpaths[1]), apf.SUCCESS, {});
        }
    }
    
    //xml generation
    if (chr == "<") {
        //string only
        if (fParsed.type == 2)
            result = fParsed.str;
        else
            return fParsed(options.xmlNode, gCallback, options);
    }
    //jslt fetching data
    else if ((chr == "[" || chr == "{")) { //(fParsed.asyncs || fParsed.models) && 
        return fParsed(options.xmlNode, gCallback, options);
    }
    //url
    else {
        if (fParsed.type == 1 || fParsed.type == 3) {
            var callback2 = callback;
            callback = options.callback = function(data, state, extra) {
                if (options._pc === true)
                    return;
                
                if (state != apf.SUCCESS)
                    return callback2.apply(this, arguments);

                var url = data.split(" "), method = "get";
                if (url.length > 1 && url[0].length < 10) {
                    method = url.shift();
                    url = url.join(" ");
                }
                else url = data;
                
                callback = options.callback = callback2;
                apf.oHttp.exec(method, [url], gCallback, options);
            }
            fParsed(options.xmlNode, gCallback, options);
        }
        else {
            if (options._pc === true)
                return;
            
            var url = instruction.split(" "), method = "get";
            if (url.length > 1 && url[0].length < 10) {
                method = url.shift();
                url = url.join(" ");
            }
            else {
                url = instruction;
            }
            
            apf.oHttp.exec(method, [url.replace(/\\/g, "")], gCallback, options);
        }
    }
    
    if (result) {
        if (callback)
            gCallback(result, apf.SUCCESS, {});
        else {
            //apf.console.warn("Returning data directly in apf.getData(). \
                //This means that all callback communication ends in void!");
            return result;
        }
    }
};


/**
 * Creates a model object based on a {@link term.datainstruction data instruction}.
 *
 * @param {String} instruction  The {@link term.datainstruction data instruction} to be used to retrieve the data for the model
 * @param {apf.AmlNode} amlNode     The element the model is added to
 */
apf.setModel = function(instruction, amlNode) {
    if (!instruction) return;

    //Find existing model
    var fParsed = instruction.indexOf("{") > -1 || instruction.indexOf("[") > -1
        ? apf.lm.compile(instruction, {
            //precall  : false, 
            alwayscb: true
        })
        : {
            type: 2,
            str: instruction
        };

    if (instruction == "@default" || fParsed.type == 2) {
        
        var model = apf.nameserver.get("model", instruction);
        if (model)
            return model.register(amlNode);
        else
        
            if (instruction == "@default")
            return;
        
        //@todo apf3.0 check here if string is valid url (relative or absolute)
        if (instruction.indexOf(".") == -1 && instruction.indexOf("/") == -1) {
            
            return;
        }
    }

    //Just an xpath doesnt work. We don't have context
    //var l, x;
    if (fParsed.type == 3) {//This won't work for complex xpaths
        if (fParsed.models) { //check for # in xpaths[i] to determine if its calculated
            if (fParsed.xpaths.length == 2 && fParsed.xpaths[0] != '#' && fParsed.xpaths [1] != '#') {
                
                
                
                apf.nameserver.get("model", fParsed.xpaths[0]).register(amlNode, fParsed.xpaths[1]);
                
                return;
            }
        }
        
    }

    if (amlNode.clear)
        amlNode.clear("loading");

    //Complex data fetch (possibly async) - data is loaded only once. 
    //Potential property binding has to take of the rest
    apf.getData(instruction, {
      parsed: fParsed, 
      xmlNode: amlNode && amlNode.xmlRoot,
      callback: function(data, state, extra) {
        //@todo apf3.0 call onerror on amlNode
        if (state != apf.SUCCESS) {
            throw new Error(apf.formatErrorString(0, null,
                "Loading new data", "Could not load data into model. \
                \nMessage: " + extra.message + "\
                \nInstruction: '" + instruction + "'"));
        }
        
        if (!data)
            return amlNode.clear && amlNode.clear();

        if (typeof data == "string") {
            if (data.charAt(0) == "<")
                data = apf.getXml(data);
            else {
                //Assuming web service returned url
                if (data.indexOf("http://") == 0)
                    return apf.setModel(data, amlNode);
                else {
                    throw new Error("Invalid data from server");//@todo apf3.0 make proper apf error handling. apf.onerror
                }
            }
        }
        
        if (data.nodeFunc) { //Assuming a model was passed -- data.localName == "model" && 
            data.register(amlNode);
            return;
        }
        
        var model = apf.xmldb.findModel(data); //See if data is already loaded into a model
        if (model)
            model.register(amlNode, apf.xmlToXpath(data, model.data)); //@todo move function to xml library
        else
            new apf.model().register(amlNode).load(data);
    }});
};







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

/*
 * @version: 1.0 Alpha-1
 * @author: Coolite Inc. http://www.coolite.com/
 * @date: 2008-04-13
 * @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved.
 * @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. 
 * @website: http://www.datejs.com/
 */
 
(function () {
    var $C = {
        /* Culture Name */
        name: "en-US",
        englishName: "English (United States)",
        nativeName: "English (United States)",
        
        /* Day Name Strings */
        dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
        abbreviatedDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
        shortestDayNames: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
        firstLetterDayNames: ["S", "M", "T", "W", "T", "F", "S"],
        
        /* Month Name Strings */
        monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
        abbreviatedMonthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
    
        /* AM/PM Designators */
        amDesignator: "AM",
        pmDesignator: "PM",
    
        firstDayOfWeek: 0,
        twoDigitYearMax: 2029,
        
        /**
         * The dateElementOrder is based on the order of the 
         * format specifiers in the formatPatterns.DatePattern. 
         *
         * Example:
         <pre>
         shortDatePattern    dateElementOrder
         ------------------  ---------------- 
         "M/d/yyyy"          "mdy"
         "dd/MM/yyyy"        "dmy"
         "yyyy-MM-dd"        "ymd"
         </pre>
         *
         * The correct dateElementOrder is required by the parser to
         * determine the expected order of the date elements in the
         * string being parsed.
         */
        dateElementOrder: "mdy",
        
        /* Standard date and time format patterns */
        formatPatterns: {
            shortDate: "M/d/yyyy",
            longDate: "dddd, MMMM dd, yyyy",
            shortTime: "h:mm tt",
            longTime: "h:mm:ss tt",
            fullDateTime: "dddd, MMMM dd, yyyy h:mm:ss tt",
            sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
            universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
            rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
            monthDay: "MMMM dd",
            yearMonth: "MMMM, yyyy"
        },
    
        /**
         * NOTE: If a string format is not parsing correctly, but
         * you would expect it parse, the problem likely lies below. 
         * 
         * The following regex patterns control most of the string matching
         * within the parser.
         * 
         * The Month name and Day name patterns were automatically generated
         * and in general should be (mostly) correct. 
         *
         * Beyond the month and day name patterns are natural language strings.
         * Example: "next", "today", "months"
         *
         * These natural language string may NOT be correct for this culture. 
         * If they are not correct, please translate and edit this file
         * providing the correct regular expression pattern. 
         *
         * If you modify this file, please post your revised CultureInfo file
         * to the Datejs Forum located at http://www.datejs.com/forums/.
         *
         * Please mark the subject of the post with [CultureInfo]. Example:
         *    Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
         * 
         * We will add the modified patterns to the master source files.
         *
         * As well, please review the list of "Future Strings" section below. 
         */    
        regexPatterns: {
            jan: /^jan(uary)?/i,
            feb: /^feb(ruary)?/i,
            mar: /^mar(ch)?/i,
            apr: /^apr(il)?/i,
            may: /^may/i,
            jun: /^jun(e)?/i,
            jul: /^jul(y)?/i,
            aug: /^aug(ust)?/i,
            sep: /^sep(t(ember)?)?/i,
            oct: /^oct(ober)?/i,
            nov: /^nov(ember)?/i,
            dec: /^dec(ember)?/i,
    
            sun: /^su(n(day)?)?/i,
            mon: /^mo(n(day)?)?/i,
            tue: /^tu(e(s(day)?)?)?/i,
            wed: /^we(d(nesday)?)?/i,
            thu: /^th(u(r(s(day)?)?)?)?/i,
            fri: /^fr(i(day)?)?/i,
            sat: /^sa(t(urday)?)?/i,
    
            future: /^next/i,
            past: /^last|past|prev(ious)?/i,
            add: /^(\+|aft(er)?|from|hence)/i,
            subtract: /^(\-|bef(ore)?|ago)/i,
            
            yesterday: /^yes(terday)?/i,
            today: /^t(od(ay)?)?/i,
            tomorrow: /^tom(orrow)?/i,
            now: /^n(ow)?/i,
            
            millisecond: /^ms|milli(second)?s?/i,
            second: /^sec(ond)?s?/i,
            minute: /^mn|min(ute)?s?/i,
            hour: /^h(our)?s?/i,
            week: /^w(eek)?s?/i,
            month: /^m(onth)?s?/i,
            day: /^d(ay)?s?/i,
            year: /^y(ear)?s?/i,
    
            shortMeridian: /^(a|p)/i,
            longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i,
            timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
            ordinalSuffix: /^\s*(st|nd|rd|th)/i,
            timeContext: /^\s*(\:|a(?!u|p)|p)/i
        },
    
        timezones: [
            {name:"UTC", offset:"-000"},
            {name:"GMT", offset:"-000"},
            {name:"EST", offset:"-0500"},
            {name:"EDT", offset:"-0400"},
            {name:"CST", offset:"-0600"},
            {name:"CDT", offset:"-0500"},
            {name:"MST", offset:"-0700"},
            {name:"MDT", offset:"-0600"},
            {name:"PST", offset:"-0800"},
            {name:"PDT", offset:"-0700"}
        ]
    };
    
    var $D = Date, 
        $P = $D.prototype,
        p = function(s, l) {
            if (!l)
                l = 2;
            return ("000" + s).slice(l * -1);
        };

    /**
     * Resets the time of this Date object to 12:00 AM (00:00), which is the 
     * start of the day.
     * @return {Date}    this
     */
    $P.clearTime = function() {
        this.setHours(0);
        this.setMinutes(0);
        this.setSeconds(0);
        this.setMilliseconds(0);
        return this;
    };

    /**
     * Resets the time of this Date object to the current time ('now').
     * @return {Date}    this
     */
    $P.setTimeToNow = function() {
        var n = new Date();
        this.setHours(n.getHours());
        this.setMinutes(n.getMinutes());
        this.setSeconds(n.getSeconds());
        this.setMilliseconds(n.getMilliseconds());
        return this;
    };

    /** 
     * Gets a date that is set to the current date. The time is set to the start 
     * of the day (00:00 or 12:00 AM).
     * @return {Date}    The current date.
     */
    $D.today = function() {
        return new Date().clearTime();
    };

    /**
     * Compares the first date to the second date and returns an number indication 
     * of their relative values.  
     * @param {Date}     First Date object to compare [Required].
     * @param {Date}     Second Date object to compare to [Required].
     * @return {Number}  -1 = date1 is lessthan date2. 0 = values are equal. 
     *                    1 = date1 is greaterthan date2.
     */
    $D.compare = function(date1, date2) {
        if (isNaN(date1) || isNaN(date2))
            throw new Error(date1 + " - " + date2); 
        else if (date1 instanceof Date && date2 instanceof Date)
            return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
        else
            throw new TypeError(date1 + " - " + date2); 
    };
    
    /**
     * Compares the first Date object to the second Date object and returns true 
     * if they are equal.  
     * @param {Date}     First Date object to compare [Required]
     * @param {Date}     Second Date object to compare to [Required]
     * @return {Boolean} true if dates are equal. false if they are not equal.
     */
    $D.equals = function(date1, date2) { 
        return (date1.compareTo(date2) === 0); 
    };

    /**
     * Gets the day number (0-6) if given a CultureInfo specific string which is 
     * a valid dayName, abbreviatedDayName or shortestDayName (two char).
     * @param {String}   The name of the day (eg. "Monday, "Mon", "tuesday", "tue", "We", "we").
     * @return {Number}  The day number
     */
    $D.getDayNumberFromName = function(name) {
        var n = $C.dayNames,
            m = $C.abbreviatedDayNames,
            o = $C.shortestDayNames,
            s = name.toLowerCase();
        for (var i = 0; i < n.length; i++) { 
            if (n[i].toLowerCase() == s || m[i].toLowerCase() == s || o[i].toLowerCase() == s)
                return i; 
        }
        return -1;  
    };
    
    /**
     * Gets the month number (0-11) if given a Culture Info specific string which 
     * is a valid monthName or abbreviatedMonthName.
     * @param {String}   The name of the month (eg. "February, "Feb", "october", "oct").
     * @return {Number}  The day number
     */
    $D.getMonthNumberFromName = function(name) {
        var n = $C.monthNames,
            m = $C.abbreviatedMonthNames,
            s = name.toLowerCase();
        for (var i = 0; i < n.length; i++) {
            if (n[i].toLowerCase() == s || m[i].toLowerCase() == s)
                return i; 
        }
        return -1;
    };

    /**
     * Determines if the current date instance is within a LeapYear.
     * @param {Number}   The year.
     * @return {Boolean} true if date is within a LeapYear, otherwise false.
     */
    $D.isLeapYear = function(year) { 
        return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); 
    };

    /**
     * Gets the number of days in the month, given a year and month value. 
     * Automatically corrects for LeapYear.
     * @param {Number}   The year.
     * @param {Number}   The month (0-11).
     * @return {Number}  The number of days in the month.
     */
    $D.getDaysInMonth = function(year, month) {
        return [31, ($D.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
    };
 
    $D.getTimezoneAbbreviation = function(offset) {
        var z = $C.timezones, p;
        for (var i = 0; i < z.length; i++) {
            if (z[i].offset === offset)
                return z[i].name;
        }
        return null;
    };
    
    $D.getTimezoneOffset = function(name) {
        var z = $C.timezones, p;
        for (var i = 0; i < z.length; i++) {
            if (z[i].name === name.toUpperCase())
                return z[i].offset;
        }
        return null;
    };

    /**
     * Returns a new Date object that is an exact date and time copy of the 
     * original instance.
     * @return {Date}    A new Date instance
     */
    $P.clone = function() {
        return new Date(this.getTime()); 
    };

    /**
     * Compares this instance to a Date object and returns an number indication 
     * of their relative values.  
     * @param {Date}     Date object to compare [Required]
     * @return {Number}  -1 = this is lessthan date. 0 = values are equal.
     *                    1 = this is greaterthan date.
     */
    $P.compareTo = function(date) {
        return Date.compare(this, date);
    };

    /**
     * Compares this instance to another Date object and returns true if they are equal.  
     * @param {Date}     Date object to compare. If no date to compare, new Date() 
     *                   [now] is used.
     * @return {Boolean} true if dates are equal. false if they are not equal.
     */
    $P.equals = function(date) {
        return Date.equals(this, date || new Date());
    };

    /**
     * Determines if this instance is between a range of two dates or equal to 
     * either the start or end dates.
     * @param {Date}     Start of range [Required]
     * @param {Date}     End of range [Required]
     * @return {Boolean} true is this is between or equal to the start and end 
     *                   dates, else false
     */
    $P.between = function(start, end) {
        return this.getTime() >= start.getTime() && this.getTime() <= end.getTime();
    };

    /**
     * Determines if this date occurs after the date to compare to.
     * @param {Date}     Date object to compare. If no date to compare, new Date() 
     *                   ("now") is used.
     * @return {Boolean} true if this date instance is greater than the date to 
     *                   compare to (or "now"), otherwise false.
     */
    $P.isAfter = function(date) {
        return this.compareTo(date || new Date()) === 1;
    };

    /**
     * Determines if this date occurs before the date to compare to.
     * @param {Date}     Date object to compare. If no date to compare, new Date()
     *                   ("now") is used.
     * @return {Boolean} true if this date instance is less than the date to 
     *                   compare to (or "now").
     */
    $P.isBefore = function(date) {
        return (this.compareTo(date || new Date()) === -1);
    };

    /**
     * Determines if the current Date instance occurs on the same Date as the supplied 'date'. 
     * If no 'date' to compare to is provided, the current Date instance is compared to 'today'. 
     * @param {Date}     Date object to compare. If no date to compare, the current Date ("now") is used.
     * @return {Boolean} true if this Date instance occurs on the same Day as the supplied 'date'.
     */
    $P.isToday = $P.isSameDay = function(date) {
        return this.clone().clearTime().equals((date || new Date()).clone().clearTime());
    };
    
    /**
     * Adds the specified number of milliseconds to this instance. 
     * @param {Number}   The number of milliseconds to add. The number can be 
     *                   positive or negative [Required]
     * @return {Date}    this
     */
    $P.addMilliseconds = function(value) {
        this.setMilliseconds(this.getMilliseconds() + value * 1);
        return this;
    };

    /**
     * Adds the specified number of seconds to this instance. 
     * @param {Number}   The number of seconds to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addSeconds = function(value) { 
        return this.addMilliseconds(value * 1000); 
    };

    /**
     * Adds the specified number of seconds to this instance. 
     * @param {Number}   The number of seconds to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addMinutes = function(value) { 
        return this.addMilliseconds(value * 60000); /* 60*1000 */
    };

    /**
     * Adds the specified number of hours to this instance. 
     * @param {Number}   The number of hours to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addHours = function(value) { 
        return this.addMilliseconds(value * 3600000); /* 60*60*1000 */
    };

    /**
     * Adds the specified number of days to this instance. 
     * @param {Number}   The number of days to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addDays = function(value) {
        this.setDate(this.getDate() + value * 1);
        return this;
    };

    /**
     * Adds the specified number of weeks to this instance. 
     * @param {Number}   The number of weeks to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addWeeks = function(value) { 
        return this.addDays(value * 7);
    };

    /**
     * Adds the specified number of months to this instance. 
     * @param {Number}   The number of months to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addMonths = function(value) {
        var n = this.getDate();
        this.setDate(1);
        this.setMonth(this.getMonth() + value * 1);
        this.setDate(Math.min(n, $D.getDaysInMonth(this.getFullYear(), this.getMonth())));
        return this;
    };

    /**
     * Adds the specified number of years to this instance. 
     * @param {Number}   The number of years to add. The number can be positive 
     *                   or negative [Required]
     * @return {Date}    this
     */
    $P.addYears = function(value) {
        return this.addMonths(value * 12);
    };

    /**
     * Adds (or subtracts) to the value of the years, months, weeks, days, hours, 
     * minutes, seconds, milliseconds of the date instance using given configuration 
     * object. Positive and Negative values allowed.
     * Example
    <pre><code>
    Date.today().add( { days: 1, months: 1 } )
     
    new Date().add( { years: -1 } )
    </code></pre> 
     * @param {Object}   Configuration object containing attributes (months, days, etc.)
     * @return {Date}    this
     */
    $P.add = function(config) {
        if (typeof config == "number") {
            this._orient = config;
            return this;    
        }
        
        var x = config;
        
        if (x.milliseconds)
            this.addMilliseconds(x.milliseconds); 
        if (x.seconds)
            this.addSeconds(x.seconds); 
        if (x.minutes)
            this.addMinutes(x.minutes); 
        if (x.hours)
            this.addHours(x.hours); 
        if (x.weeks)
            this.addWeeks(x.weeks); 
        if (x.months)
            this.addMonths(x.months); 
        if (x.years)
            this.addYears(x.years); 
        if (x.days)
            this.addDays(x.days); 
        return this;
    };
    
    var $y, $m, $d;
    
    /**
     * Get the week number. Week one (1) is the week which contains the first 
     * Thursday of the year. Monday is considered the first day of the week.
     * This algorithm is a JavaScript port of the work presented by Claus 
     * Tøndering at http://www.tondering.dk/claus/cal/node8.html#SECTION00880000000000000000
     * .getWeek() Algorithm Copyright (c) 2008 Claus Tondering.
     * The .getWeek() function does NOT convert the date to UTC. The local datetime 
     * is used. Please use .getISOWeek() to get the week of the UTC converted date.
     * @return {Number}  1 to 53
     */
    $P.getWeek = function() {
        var a, b, c, d, e, f, g, n, s, w;
        
        $y = (!$y) ? this.getFullYear() : $y;
        $m = (!$m) ? this.getMonth() + 1 : $m;
        $d = (!$d) ? this.getDate() : $d;

        if ($m <= 2) {
            a = $y - 1;
            b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
            c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
            s = b - c;
            e = 0;
            f = $d - 1 + (31 * ($m - 1));
        } else {
            a = $y;
            b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
            c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
            s = b - c;
            e = s + 1;
            f = $d + ((153 * ($m - 3) + 2) / 5) + 58 + s;
        }
        
        g = (a + b) % 7;
        d = (f + g - e) % 7;
        n = (f + 3 - d) | 0;

        if (n < 0) {
            w = 53 - ((g - s) / 5 | 0);
        } else if (n > 364 + s) {
            w = 1;
        } else {
            w = (n / 7 | 0) + 1;
        }
        
        $y = $m = $d = null;
        
        return w;
    };
    
    /**
     * Get the ISO 8601 week number. Week one ("01") is the week which contains the 
     * first Thursday of the year. Monday is considered the first day of the week.
     * The .getISOWeek() function does convert the date to it's UTC value. 
     * Please use .getWeek() to get the week of the local date.
     * @return {String}  "01" to "53"
     */
    $P.getISOWeek = function() {
        $y = this.getUTCFullYear();
        $m = this.getUTCMonth() + 1;
        $d = this.getUTCDate();
        return p(this.getWeek());
    };

    /**
     * Moves the date to Monday of the week set. Week one (1) is the week which 
     * contains the first Thursday of the year.
     * @param {Number}   A Number (1 to 53) that represents the week of the year.
     * @return {Date}    this
     */    
    $P.setWeek = function(n) {
        return this.moveToDayOfWeek(1).addWeeks(n - this.getWeek());
    };

    // private
    var validate = function(n, min, max, name) {
        if (typeof n == "undefined")
            return false;
        else if (typeof n != "number")
            throw new TypeError(n + " is not a Number.");
        else if (n < min || n > max)
            throw new RangeError(n + " is not a valid value for " + name + "."); 
        return true;
    };

    /**
     * Validates the number is within an acceptable range for milliseconds [0-999].
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateMillisecond = function(value) {
        return validate(value, 0, 999, "millisecond");
    };

    /**
     * Validates the number is within an acceptable range for seconds [0-59].
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateSecond = function(value) {
        return validate(value, 0, 59, "second");
    };

    /**
     * Validates the number is within an acceptable range for minutes [0-59].
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateMinute = function(value) {
        return validate(value, 0, 59, "minute");
    };

    /**
     * Validates the number is within an acceptable range for hours [0-23].
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateHour = function(value) {
        return validate(value, 0, 23, "hour");
    };

    /**
     * Validates the number is within an acceptable range for the days in a month 
     * [0 - MaxDaysInMonth].
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateDay = function(value, year, month) {
        return validate(value, 1, $D.getDaysInMonth(year, month), "day");
    };

    /**
     * Validates the number is within an acceptable range for months [0-11].
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateMonth = function(value) {
        return validate(value, 0, 11, "month");
    };

    /**
     * Validates the number is within an acceptable range for years.
     * @param {Number}   The number to check if within range.
     * @return {Boolean} true if within range, otherwise false.
     */
    $D.validateYear = function(value) {
        return validate(value, 0, 9999, "year");
    };

    /**
     * Set the value of year, month, day, hour, minute, second, millisecond of 
     * date instance using given configuration object.
     * Example
    <pre><code>
    Date.today().set( { day: 20, month: 1 } )

    new Date().set( { millisecond: 0 } )
    </code></pre>
     * 
     * @param {Object}   Configuration object containing attributes (month, day, etc.)
     * @return {Date}    this
     */
    $P.set = function(config) {
        if ($D.validateMillisecond(config.millisecond))
            this.addMilliseconds(config.millisecond - this.getMilliseconds()); 
        
        if ($D.validateSecond(config.second))
            this.addSeconds(config.second - this.getSeconds()); 
        
        if ($D.validateMinute(config.minute))
            this.addMinutes(config.minute - this.getMinutes()); 
        
        if ($D.validateHour(config.hour))
            this.addHours(config.hour - this.getHours()); 
        
        if ($D.validateMonth(config.month))
            this.addMonths(config.month - this.getMonth()); 

        if ($D.validateYear(config.year))
            this.addYears(config.year - this.getFullYear()); 
        
        /* day has to go last because you can't validate the day without first knowing the month */
        if ($D.validateDay(config.day, this.getFullYear(), this.getMonth()))
            this.addDays(config.day - this.getDate()); 
        
        if (config.timezone)
            this.setTimezone(config.timezone); 
        
        if (config.timezoneOffset)
            this.setTimezoneOffset(config.timezoneOffset); 

        if (config.week && validate(config.week, 0, 53, "week"))
            this.setWeek(config.week);
        
        return this;
    };

    /**
     * Moves the date to the first day of the month.
     * @return {Date}    this
     */
    $P.moveToFirstDayOfMonth = function() {
        return this.set({ day: 1 });
    };

    /**
     * Moves the date to the last day of the month.
     * @return {Date}    this
     */
    $P.moveToLastDayOfMonth = function() { 
        return this.set({ day: $D.getDaysInMonth(this.getFullYear(), this.getMonth())});
    };

    /**
     * Moves the date to the next n'th occurrence of the dayOfWeek starting from 
     * the beginning of the month. The number (-1) is a magic number and will return 
     * the last occurrence of the dayOfWeek in the month.
     * @param {Number}   The dayOfWeek to move to
     * @param {Number}   The n'th occurrence to move to. Use (-1) to return the 
     *                   last occurrence in the month
     * @return {Date}    this
     */
    $P.moveToNthOccurrence = function(dayOfWeek, occurrence) {
        var shift = 0;
        if (occurrence > 0) {
            shift = occurrence - 1;
        }
        else if (occurrence === -1) {
            this.moveToLastDayOfMonth();
            if (this.getDay() !== dayOfWeek)
                this.moveToDayOfWeek(dayOfWeek, -1);
            return this;
        }
        return this.moveToFirstDayOfMonth().addDays(-1)
                   .moveToDayOfWeek(dayOfWeek, +1).addWeeks(shift);
    };

    /**
     * Move to the next or last dayOfWeek based on the orient value.
     * @param {Number}   The dayOfWeek to move to
     * @param {Number}   Forward (+1) or Back (-1). Defaults to +1. [Optional]
     * @return {Date}    this
     */
    $P.moveToDayOfWeek = function(dayOfWeek, orient) {
        var diff = (dayOfWeek - this.getDay() + 7 * (orient || +1)) % 7;
        return this.addDays((diff === 0) ? diff += 7 * (orient || +1) : diff);
    };

    /**
     * Move to the next or last month based on the orient value.
     * @param {Number}   The month to move to. 0 = January, 11 = December
     * @param {Number}   Forward (+1) or Back (-1). Defaults to +1. [Optional]
     * @return {Date}    this
     */
    $P.moveToMonth = function(month, orient) {
        var diff = (month - this.getMonth() + 12 * (orient || +1)) % 12;
        return this.addMonths((diff === 0) ? diff += 12 * (orient || +1) : diff);
    };

    /**
     * Get the Ordinal day (numeric day number) of the year, adjusted for leap year.
     * @return {Number} 1 through 365 (366 in leap years)
     */
    $P.getOrdinalNumber = function() {
        return Math.ceil((this.clone().clearTime() 
            - new Date(this.getFullYear(), 0, 1)) / 86400000) + 1;
    };

    /**
     * Get the time zone abbreviation of the current date.
     * @return {String} The abbreviated time zone name (e.g. "EST")
     */
    $P.getTimezone = function() {
        return $D.getTimezoneAbbreviation(this.getUTCOffset());
    };

    $P.setTimezoneOffset = function(offset) {
        var here = this.getTimezoneOffset(), there = Number(offset) * -6 / 10;
        return this.addMinutes(there - here); 
    };

    $P.setTimezone = function(offset) { 
        return this.setTimezoneOffset($D.getTimezoneOffset(offset)); 
    };

    /**
     * Indicates whether Daylight Saving Time is observed in the current time zone.
     * @return {Boolean} true|false
     */
    $P.hasDaylightSavingTime = function() { 
        return (Date.today().set({month: 0, day: 1}).getTimezoneOffset() 
            !== Date.today().set({month: 6, day: 1}).getTimezoneOffset());
    };
    
    /**
     * Indicates whether this Date instance is within the Daylight Saving Time 
     * range for the current time zone.
     * @return {Boolean} true|false
     */
    $P.isDaylightSavingTime = function() {
        return Date.today().set({month: 0, day: 1}).getTimezoneOffset() != this.getTimezoneOffset();
    };

    /**
     * Get the offset from UTC of the current date.
     * @return {String} The 4-character offset string prefixed with + or - (e.g. "-0500")
     */
    $P.getUTCOffset = function() {
        var n = this.getTimezoneOffset() * -10 / 6, r;
        if (n < 0) { 
            r = (n - 10000).toString(); 
            return r.charAt(0) + r.substr(2); 
        }
        else { 
            r = (n + 10000).toString();  
            return "+" + r.substr(1); 
        }
    };
    
    $P.getUTCTime = function() {
        //Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
        return Date.UTC(this.getUTCFullYear(), this.getUTCMonth(), this.getUTCDate(),
            this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds(),
            this.getUTCMilliseconds());
    };

    /**
     * Returns the number of milliseconds between this date and date.
     * @param {Date} Defaults to now
     * @return {Number} The diff in milliseconds
     */
    $P.getElapsed = function(date) {
        return (date || new Date()) - this;
    };

    if (!$P.toISOString) {
        /**
         * Converts the current date instance into a string with an ISO 8601 format. 
         * The date is converted to it's UTC value.
         * @return {String}  ISO 8601 string of date
         */
        $P.toISOString = function() {
            // From http://www.json.org/json.js. Public Domain. 
            function f(n) {
                return n < 10 ? '0' + n : n;
            }

            return '"' + this.getUTCFullYear()   + '-' +
                f(this.getUTCMonth() + 1) + '-' +
                f(this.getUTCDate())      + 'T' +
                f(this.getUTCHours())     + ':' +
                f(this.getUTCMinutes())   + ':' +
                f(this.getUTCSeconds())   + 'Z"';
        };
    }
    
    // private
    $P._toString = $P.toString;

    /**
     * Converts the value of the current Date object to its equivalent string representation.
     * Format Specifiers
    <pre>
    CUSTOM DATE AND TIME FORMAT STRINGS
    Format  Description                                                                  Example
    ------  ---------------------------------------------------------------------------  -----------------------
     s      The seconds of the minute between 0-59.                                      "0" to "59"
     ss     The seconds of the minute with leading zero if required.                     "00" to "59"
     
     m      The minute of the hour between 0-59.                                         "0"  or "59"
     mm     The minute of the hour with leading zero if required.                        "00" or "59"
     
     h      The hour of the day between 1-12.                                            "1"  to "12"
     hh     The hour of the day with leading zero if required.                           "01" to "12"
     
     H      The hour of the day between 0-23.                                            "0"  to "23"
     HH     The hour of the day with leading zero if required.                           "00" to "23"
     
     d      The day of the month between 1 and 31.                                       "1"  to "31"
     dd     The day of the month with leading zero if required.                          "01" to "31"
     ddd    Abbreviated day name. $C.abbreviatedDayNames.                                "Mon" to "Sun" 
     dddd   The full day name. $C.dayNames.                                              "Monday" to "Sunday"
     
     M      The month of the year between 1-12.                                          "1" to "12"
     MM     The month of the year with leading zero if required.                         "01" to "12"
     MMM    Abbreviated month name. $C.abbreviatedMonthNames.                            "Jan" to "Dec"
     MMMM   The full month name. $C.monthNames.                                          "January" to "December"

     yy     The year as a two-digit number.                                              "99" or "08"
     yyyy   The full four digit year.                                                    "1999" or "2008"
     
     t      Displays the first character of the A.M./P.M. designator.                    "A" or "P"
            $C.amDesignator or $C.pmDesignator
     tt     Displays the A.M./P.M. designator.                                           "AM" or "PM"
            $C.amDesignator or $C.pmDesignator
     
     S      The ordinal suffix ("st, "nd", "rd" or "th") of the current day.            "st, "nd", "rd" or "th"

|| *Format* || *Description* || *Example* ||
|| d      || The CultureInfo shortDate Format Pattern                                     || "M/d/yyyy" ||
|| D      || The CultureInfo longDate Format Pattern                                      || "dddd, MMMM dd, yyyy" ||
|| F      || The CultureInfo fullDateTime Format Pattern                                  || "dddd, MMMM dd, yyyy h:mm:ss tt" ||
|| m      || The CultureInfo monthDay Format Pattern                                      || "MMMM dd" ||
|| r      || The CultureInfo rfc1123 Format Pattern                                       || "ddd, dd MMM yyyy HH:mm:ss GMT" ||
|| s      || The CultureInfo sortableDateTime Format Pattern                              || "yyyy-MM-ddTHH:mm:ss" ||
|| t      || The CultureInfo shortTime Format Pattern                                     || "h:mm tt" ||
|| T      || The CultureInfo longTime Format Pattern                                      || "h:mm:ss tt" ||
|| u      || The CultureInfo universalSortableDateTime Format Pattern                     || "yyyy-MM-dd HH:mm:ssZ" ||
|| y      || The CultureInfo yearMonth Format Pattern                                     || "MMMM, yyyy" ||
     

    STANDARD DATE AND TIME FORMAT STRINGS
    Format  Description                                                                  Example ("en-US")
    ------  ---------------------------------------------------------------------------  -----------------------
     d      The CultureInfo shortDate Format Pattern                                     "M/d/yyyy"
     D      The CultureInfo longDate Format Pattern                                      "dddd, MMMM dd, yyyy"
     F      The CultureInfo fullDateTime Format Pattern                                  "dddd, MMMM dd, yyyy h:mm:ss tt"
     m      The CultureInfo monthDay Format Pattern                                      "MMMM dd"
     r      The CultureInfo rfc1123 Format Pattern                                       "ddd, dd MMM yyyy HH:mm:ss GMT"
     s      The CultureInfo sortableDateTime Format Pattern                              "yyyy-MM-ddTHH:mm:ss"
     t      The CultureInfo shortTime Format Pattern                                     "h:mm tt"
     T      The CultureInfo longTime Format Pattern                                      "h:mm:ss tt"
     u      The CultureInfo universalSortableDateTime Format Pattern                     "yyyy-MM-dd HH:mm:ssZ"
     y      The CultureInfo yearMonth Format Pattern                                     "MMMM, yyyy"
    </pre>
     * @param {String}   A format string consisting of one or more format spcifiers [Optional].
     * @return {String}  A string representation of the current Date object.
     */
    $P.toString = function(format) {
        var x = this;
        
        // Standard Date and Time Format Strings. Formats pulled from CultureInfo file and
        // may vary by culture. 
        if (format && format.length == 1) {
            var c = $C.formatPatterns;
            x.t = x.toString;
            switch (format) {
            case "d": 
                return x.t(c.shortDate);
            case "D":
                return x.t(c.longDate);
            case "F":
                return x.t(c.fullDateTime);
            case "m":
                return x.t(c.monthDay);
            case "r":
                return x.t(c.rfc1123);
            case "s":
                return x.t(c.sortableDateTime);
            case "t":
                return x.t(c.shortTime);
            case "T":
                return x.t(c.longTime);
            case "u":
                return x.t(c.universalSortableDateTime);
            case "y":
                return x.t(c.yearMonth);
            }    
        }
        
        var ord = function (n) {
                switch (n * 1) {
                case 1: 
                case 21: 
                case 31: 
                    return "st";
                case 2: 
                case 22: 
                    return "nd";
                case 3: 
                case 23: 
                    return "rd";
                default: 
                    return "th";
                }
            };
        
        return format ? format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g, 
        function (m) {
            if (m.charAt(0) === "\\") {
                return m.replace("\\", "");
            }
            x.h = x.getHours;
            switch (m) {
            case "hh":
                return p(x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12));
            case "h":
                return x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12);
            case "HH":
                return p(x.h());
            case "H":
                return x.h();
            case "mm":
                return p(x.getMinutes());
            case "m":
                return x.getMinutes();
            case "ss":
                return p(x.getSeconds());
            case "s":
                return x.getSeconds();
            case "yyyy":
                return p(x.getFullYear(), 4);
            case "yy":
                return p(x.getFullYear());
            case "dddd":
                return $C.dayNames[x.getDay()];
            case "ddd":
                return $C.abbreviatedDayNames[x.getDay()];
            case "dd":
                return p(x.getDate());
            case "d":
                return x.getDate();
            case "MMMM":
                return $C.monthNames[x.getMonth()];
            case "MMM":
                return $C.abbreviatedMonthNames[x.getMonth()];
            case "MM":
                return p((x.getMonth() + 1));
            case "M":
                return x.getMonth() + 1;
            case "t":
                return x.h() < 12 ? $C.amDesignator.substring(0, 1) : $C.pmDesignator.substring(0, 1);
            case "tt":
                return x.h() < 12 ? $C.amDesignator : $C.pmDesignator;
            case "S":
                return ord(x.getDate());
            default: 
                return m;
            }
        }
        ) : this._toString();
    };
}());





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: socket://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @class apf.layout
 *
 * Takes care of the spatial order of elements within the display area
 * of the browser. Layouts can be saved to XML and loaded again. Window
 * elements are dockable, which means the user can change the layout as s/he
 * wishes. The state of the layout can be saved as XML at any time.
 *
 * #### Example
 * 
 * This example shows five windows which have a layout defined in layout.xml.
 * 
 * ```xml
 *  <a:appsettings layout="[mdlLayouts::layout[1]]" />
 *  <a:model id="mdlLayouts" src="layout.xml" />
 *  
 *  <a:window title="Main Window" id="b1" />
 *  <a:window title="Tree Window" id="b2" />
 *  <a:window title="Window of Oppertunity" id="b3" />
 *  <a:window title="Small window" id="b4" />
 *  <a:window title="Some Window" id="b5" />
 * ```
 *
 * This is the layout file containing two layouts (_layout.xml_):
 * 
 * ```xml
 *  <layouts>
 *      <layout name="Layout 1" margin="2,2,2,2">
 *          <vbox edge="splitter">
 *              <node name="b1" edge="2"/>
 *              <hbox edge="2">
 *                  <vbox weight="1">
 *                      <node name="b2"/>
 *                      <node name="b3"/>
 *                  </vbox>
 *                  <node name="b4" weight="1" />
 *              </hbox>
 *              <node name="b5" height="20" />
 *          </vbox>
 *      </layout>
 *
 *      <layout name="Layout 2">
 *          <vbox edge="splitter">
 *              <node name="b1" edge="2" />
 *              <node name="b2" height="100" />
 *              <hbox edge="2">
 *                  <node name="b3" width="20%" />
 *                  <node name="b4" width="100" />
 *              </hbox>
 *              <node name="b5" height="20" />
 *          </vbox>
 *      </layout>
 *  </layouts>
 * ```
 *
 * By binding on the _layout.xml_ you can easily create a layout manager.
 * 
 * ```xml
 *  <a:list id="lstLayouts"
 *    model = "mdlLayouts"
 *    allowdeselect = "false"
 *    onafterselect = "
 *      if (!this.selected || apf.layout.isLoadedXml(this.selected))
 *          return;
 *     
 *      apf.layout.saveXml();
 *      apf.layout.loadXml(this.selected);
 *    "
 *    onbeforeremove = "return confirm('Do you want to delete this layout?')">
 *      <a:bindings>
 *          <a:caption match="[@name]" />
 *          <a:icon value="layout.png" />
 *          <a:each match="[layout]" />
 *      </a:bindings>
 *      <a:actions>
 *          <a:rename match="[.]" />
 *          <a:remove match="[.]" />
 *      </a:actions>
 *  </a:list>
 *  <a:button
 *    onclick = "
 *      if (!lstLayouts.selected)
 *          return;
 *     
 *      var newLayout = apf.layout.getXml(document.body);
 *      newLayout.setAttribute('name', 'New');
 *      apf.xmldb.appendChild(lstLayouts.selected.parentNode, newLayout);
 *      lstLayouts.select(newLayout, null, null, null, null, true);
 *      apf.layout.loadXml(newLayout);
 *      lstLayouts.startRename();
 *    ">
 *    Add Layout
 *  </a:button>
 * ```
 *
 * @default_private
 */
 // @todo a __WITH_DOM_REPARENTING should be added which can remove many of the functions of this element.

apf.layout = {
    compile: function(oHtml) {
        var l = this.layouts[oHtml.getAttribute("id")];
        if (!l) return false;

        var root = l.root.copy();//is there a point to copying?
        
        l.layout.compile(root);
        l.layout.reset();
    },

    removeAll: function(aData) {
        aData.children.length = null

        var htmlId = this.getHtmlId(aData.pHtml);
        if (!this.rules[htmlId])
            delete this.qlist[htmlId];
    },
    
    timer: null,
    qlist: {},
    dlist: [],
    $hasQueue: false,
    
    queue: function(oHtml, obj, compile, q) {
        if (!q) {
            this.$hasQueue = true;
            q = this.qlist;
        }
        
        var id;
        if (!(id = this.getHtmlId(oHtml)))
            id = apf.setUniqueHtmlId(oHtml);
            
        if (q[id]) {
            if (obj)
                q[id][2].push(obj);
            if (compile)
                q[id][1] = compile;
            return;
        }

        q[id] = [oHtml, compile, [obj]];

        if (!this.timer)
            this.timer = apf.setZeroTimeout(function(){
                apf.layout.processQueue();
            });
    },

    processQueue: function(){
        var i, id, l, qItem, list;

        for (i = 0; i < this.dlist.length; i++) {
            if (this.dlist[i].hidden)
                this.dlist[i].hide();
            else
                this.dlist[i].show();
        }

        do {
            var newq = {};
            var qlist = this.qlist;
            this.qlist = {};
            
            this.$hasQueue = false;
            
            for (id in qlist) {
                qItem = qlist[id];
    
                if (qItem[1])
                    apf.layout.compileAlignment(qItem[1]);
    
                list = qItem[2];
                for (i = 0, l = list.length; i < l; i++) {
                    if (list[i]) {
                        if (list[i].$amlDestroyed)
                            continue;
                        //if (list[i].$amlLoaded)
                            list[i].$updateLayout();
                        /*else
                            this.queue(qItem[0], list[i], null, newq);*/
                    }
                }
    
                apf.layout.activateRules(qItem[0]);
            }
        } while (this.$hasQueue);
        
        if (apf.hasSingleRszEvent)
            apf.layout.forceResize();

        this.dlist = [];
        
        apf.setZeroTimeout.clearTimeout(this.timer);
        this.timer = null;
    },
    
    rules: {},
    onresize: {},

    getHtmlId: function(oHtml) {
        return oHtml.getAttribute ? oHtml.getAttribute("id") : 1;
    },

    /**
     * Adds layout rules to the resize event of the browser. Use this instead
     * of `"onresize"` events to add rules that specify determine the layout.
     * @param {HTMLElement} oHtml       The element that triggers the execution of the rules.
     * @param {String}      id          The identifier for the rules within the resize function of this element. Use this to easily update or remove the rules added.
     * @param {String}      rules       The JavaScript code that is executed when the html element resizes.
     * @param {Boolean}     [overwrite] Whether the rules are added to the resize function or overwrite the previous set rules with the specified id.
     */
    setRules: function(oHtml, id, rules, overwrite) {
        if (!this.getHtmlId(oHtml))
            apf.setUniqueHtmlId(oHtml);
        if (!this.rules[this.getHtmlId(oHtml)])
            this.rules[this.getHtmlId(oHtml)] = {};

        var ruleset = this.rules[this.getHtmlId(oHtml)][id];
        if (!overwrite && ruleset) {
            this.rules[this.getHtmlId(oHtml)][id] = rules + "\n" + ruleset;
        }
        else
            this.rules[this.getHtmlId(oHtml)][id] = rules;
    },

    /**
     * Retrieves the rules set for the `"resize"` event of an HTML element specified by an identifier
     * @param {HTMLElement} oHtml       The element that triggers the execution of the rules.
     * @param {String}      id          The identifier for the rules within the resize function of this element.
     */
    getRules: function(oHtml, id) {
        return id
            ? this.rules[this.getHtmlId(oHtml)][id]
            : this.rules[this.getHtmlId(oHtml)];
    },

    /**
     * Removes the rules set for the `"resize"` event of an html element specified by an identifier
     * @param {HTMLElement} oHtml       The element that triggers the execution of the rules.
     * @param {String}      id          The identifier for the rules within the resize function of this element.
     */
    removeRule: function(oHtml, id) {
        var htmlId = this.getHtmlId(oHtml);
        if (!this.rules[htmlId])
            return;

        var ret = this.rules[htmlId][id] ||  false;
        delete this.rules[htmlId][id];

        var prop;
        for (prop in this.rules[htmlId]) {

        }
        if (!prop)
            delete this.rules[htmlId]

        if (apf.hasSingleRszEvent) {
            if (this.onresize[htmlId])
                this.onresize[htmlId] = null;
            else {
                var p = oHtml.parentNode;
                while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) {
                    p = p.parentNode;
                }
    
                if (p && p.nodeType == 1) {
                    var x = this.onresize[p.getAttribute("id")];
                    if (x.children)
                        delete x.children[htmlId]
                }
            }
        }
        
        return ret;
    },

    /**
     * Activates the rules set for an HTML element
     * @param {HTMLElement} oHtml       The element that triggers the execution of the rules.
     * @param {Boolean} [no_exec]       
     */
    activateRules: function(oHtml, no_exec) {
        if (!oHtml) { //!apf.hasSingleRszEvent &&
            var prop, obj;
            for (prop in this.rules) {
                obj = document.getElementById(prop);
                if (!obj || obj.onresize) // || this.onresize[prop]
                    continue;
                this.activateRules(obj);
            }

             if (apf.hasSingleRszEvent && apf.layout.$onresize)
                apf.layout.$onresize();
            return;
        }

        var rsz, id, rule, rules, strRules = [];
        if (!apf.hasSingleRszEvent) {
            rules = this.rules[this.getHtmlId(oHtml)];
            if (!rules) {
                oHtml.onresize = null;
                return false;
            }

            for (id in rules) { //might need optimization using join()
                if (typeof rules[id] != "string")
                    continue;
                strRules.push(rules[id]);
            }

            //apf.console.info(strRules.join("\n"));
            rsz = apf.needsCssPx
                ? new Function(strRules.join("\n"))
                : new Function(strRules.join("\n").replace(/ \+ 'px'|try\{\}catch\(e\)\{\}\n/g,""))

            oHtml.onresize = rsz;
            if (!no_exec) 
                rsz();
        }
        else {
            var htmlId = this.getHtmlId(oHtml);
            rules = this.rules[htmlId];
            if (!rules) {
                //@todo keep .children
                //delete this.onresize[htmlId];
                return false;
            }

            for (id in rules) { //might need optimization using join()
                if (typeof rules[id] != "string")
                    continue;
                strRules.push(rules[id]);
            }
            
            var p = oHtml.parentNode;
            while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) {
                p = p.parentNode;
            }

            var f = new Function(strRules.join("\n"));//.replace(/try\{/g, "").replace(/}catch\(e\)\{\s*\}/g, "\n")
            if (this.onresize[htmlId])
                f.children = this.onresize[htmlId].children;
            
            if (p && p.nodeType == 1) {
                var x = this.onresize[p.getAttribute("id")];
                this.onresize[htmlId] = (x.children || (x.children = {}))[htmlId] = f;
            }
            else {
                this.onresize[htmlId] = f;
            }
            if (!no_exec)
                f();

            if (!apf.layout.$onresize) {
                var rsz = function(f) {
                    //@todo fix this
                    try{
                        var c = [];
                        for (var name in f)
                            if (f[name])
                                c.unshift(f[name]);
                        for (var i = 0; i < c.length; i++){
                            c[i]();
                            if (c[i].children) {
                                rsz(c[i].children);
                            }
                        }
                    }
                    catch (e) {
                        
                    }
                }
                
                apf.addListener(window, "resize", apf.layout.$onresize = function(){
                    if (apf.config.resize !== false) {
                        rsz(apf.layout.onresize);
                    }
                });
            }
        }
    },

    /**
     * Forces calling the resize rules for an HTML element
     * @param {HTMLElement} oHtml  The element for which the rules are executed.
     */
    forceResize: function(oHtml, force) {
        if (!force) return;
        
        if (apf.hasSingleRszEvent)
            return apf.layout.$onresize && apf.layout.$onresize();

        /* @todo this should be done recursive, old way for now
        apf.hasSingleRszEvent
            ? this.onresize[this.getHtmlId(oHtml)]
            :
        */

        var rsz = oHtml.onresize;
        if (rsz)
            rsz();

        var els = oHtml.getElementsByTagName("*");
        for (var i = 0, l = els.length; i < l; i++) {
            if (els[i] && els[i].onresize)
                els[i].onresize();
        }
    },

    paused: {},

    /**
     * Temporarily disables the resize rules for the HTML element.
     * @param {HTMLElement} oHtml  The element for which the rules are paused.
     * @param {Function}    func   The resize code that is used temporarily for resize of the HTML element.
     */
    pause: function(oHtml, replaceFunc) {
        if (apf.hasSingleRszEvent) {
            var htmlId = this.getHtmlId(oHtml);
            this.paused[htmlId] = this.onresize[htmlId] || true;

            if (replaceFunc) {
                this.onresize[htmlId] = replaceFunc;
                this.onresize[htmlId].children = this.paused[htmlId].children;
                replaceFunc();
            }
            else
                delete this.onresize[htmlId];
        }
        else {
            this.paused[this.getHtmlId(oHtml)] = oHtml.onresize || true;

            if (replaceFunc) {
                oHtml.onresize = replaceFunc;
                replaceFunc();
            }
            else
                oHtml.onresize = null;
        }
    },

    /**
     * Enables paused resize rules for the HTML element
     * @param {HTMLElement} oHtml  The element for which the rules were paused.
     */
    play: function(oHtml) {
        if (!this.paused[this.getHtmlId(oHtml)])
            return;

        if (apf.hasSingleRszEvent) {
            var htmlId = this.getHtmlId(oHtml);
            var oldFunc = this.paused[htmlId];
            if (typeof oldFunc == "function") {
                this.onresize[htmlId] = oldFunc;
                //oldFunc();
            }
            else
                delete this.onresize[htmlId];

            if (apf.layout.$onresize)
                apf.layout.$onresize();

            this.paused[this.getHtmlId(oHtml)] = null;
        }
        else {
            var oldFunc = this.paused[this.getHtmlId(oHtml)];
            if (typeof oldFunc == "function") {
                oHtml.onresize = oldFunc;
                oldFunc();
            }
            else
                oHtml.onresize = null;

            this.paused[this.getHtmlId(oHtml)] = null;
        }
    }
};


/**
 * @private
 */
apf.getWindowWidth = function(){
    return apf.isIE ? document.documentElement.offsetWidth - apf.windowHorBorder : window.innerWidth;
};
/**
 * @private
 */
apf.getWindowHeight = function(){
    return apf.isIE ? document.documentElement.offsetHeight - apf.windowVerBorder : window.innerHeight;
};



/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



// Only add setZeroTimeout to the window object, and hide everything
// else in a closure.
apf.setZeroTimeout = !window.postMessage
  ? (function() {
        function setZeroTimeout() {
            return $setTimeout.apply(null, arguments);
        }
        setZeroTimeout.clearTimeout = function() {
             return clearTimeout.apply(null, arguments);
        };
        return setZeroTimeout;
    })()
  : (function() {
        var timeouts = [];
        var messageName = "zero-timeout-message";

        // Like setTimeout, but only takes a function argument.  There's
        // no time argument (always zero) and no arguments (you have to
        // use a closure).
        function setZeroTimeout(fn) {
            var id = timeouts.push(fn);
            window.postMessage(messageName, "*");
            return id;
        }
        
        setZeroTimeout.clearTimeout = function(id) {
            timeouts[id] = null;
        }

        function handleMessage(e) {
            if (!e) e = event;
            if (e.source == window && e.data == messageName) {
                apf.stopPropagation(e);
                if (timeouts.length > 0 && (t = timeouts.shift()))
                    t();
            }
        }

        apf.addListener(window, "message", handleMessage, true);

        // Add the one thing we want added to the window object.
        return setZeroTimeout;
    })();




/*
 *
 */
apf.queue = {
    //@todo apf3.0
    q: {},

    timer: null,
    add: function(id, f) {
        this.q[id] = f;
        if (!this.timer)
            
            this.timer = apf.setZeroTimeout(function(){
                apf.queue.empty();
            });
            
    },

    remove: function(id) {
        delete this.q[id];
    },

    empty: function(prop) {
        
        apf.setZeroTimeout.clearTimeout(this.timer);
        
        this.timer = null;

        
        if (apf.layout && apf.layout.$hasQueue)
            apf.layout.processQueue();
        
        
        if (apf.xmldb && apf.xmldb.$hasQueue)
            apf.xmldb.notifyQueued();
        

        var q = this.q;
        this.q = {};
        for (var prop in q) {
            var f = q[prop];
            if (f) {
                delete q[prop];
                f();
            }
        }
    }
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This abstraction is using for resizing block elements. Resizing is allowed
 * with square elements in vertical, horizontal or both planes. Symmetric
 * resizing is possible with SHIFT button.
 * 
 * @private
 * @default_private
 * @constructor
 * 
 * @author      Lukasz Lipinski
 * @version     %I%, %G%
 * @since       1.0
 * 
 */

apf.resize = function() {
    /** 
     *     {Boolean} scalex       resizing in horizontal plane, default is true
     *         Possible values:
     *         true   resizing in horizontal plane is allowed
     *         false  resizing in horizontal plane is not allowed
     *     {Boolean} scaley       resizing in vertical plane, default is true
     *         Possible values:
     *         true   resizing in vertical plane is allowed
     *         false  resizing in vertical plane is not allowed
     *     {Boolean} scaleratio   resizing in horizontal or vertical plane only is not allowed. Resizing in two dimensions plane at the same time is allowed.
     *         Possible values:
     *         true   resizing in two dimensions plane at the same time is allowed
     *         false  Resizing in two dimensions plane at the same time is not allowed
     *     {Number}  dwidth       the minimal horizontal size of Block element, default is 56 pixels
     *     {Number}  dheight      the minimal vertical size of Block element, default is 56 pixels
     */
    this.scales = {
        scalex: false,
        scaley: false,
        scaleratio: false,
        dwidth: 0,
        dheight: 0,
        snap: false,
        gridW: 48,
        gridH: 48
    };

    /*
     * html representation of resized block element
     */
    this.htmlElement;

    /**
     * store object representations of inputs elements
     */
    var squares = [];

    this.init = function() {
        squares = [
            new apf.resize.square("top",    "left",   this),
            new apf.resize.square("top",    "middle", this),
            new apf.resize.square("top",    "right",  this),
            new apf.resize.square("middle", "left",   this),
            new apf.resize.square("middle", "right",  this),
            new apf.resize.square("bottom", "left",   this),
            new apf.resize.square("bottom", "middle", this),
            new apf.resize.square("bottom", "right",  this)];
    };
    
    /**
     * Links block element with resize feature
     * 
     * @param {HTMLElement}   oHtml    html representation of block element
     * @param {Object}        scales   blocks scale settings
     */
    this.grab = function(oHtml, scales) {
        this.htmlElement = oHtml;
        this.scales = scales;

        if (!squares.length)
            this.init();
        this.show();
    };

    /**
     * Hides all block squares
     */
    this.hide = function() {
        for (var i = 0, l = squares.length; i < l; i++) {
            squares[i].visible = false;
            squares[i].repaint();
        }
    };

    /**
     * Shows all block squares
     */
    this.show = function() {
        var sx = this.scales.scalex;
        var sy = this.scales.scaley;
        var sr = this.scales.scaleratio;

        for (var i = 0, l = squares.length, s; i < l; i++) {
            s = squares[i];
            s.visible = sx && sy
                ? true
                : (sy && !sx
                    ? (s.posX == "middle"
                        ? true
                        : false)
                    : (sx && !sy
                        ? (s.posY == "middle"
                            ? true
                            : false)
                        : (sr
                            ? ((s.posY == "top" || s.posY == "bottom")
                              && s.posX !== "middle"
                                ? true
                                : false)
                            : false)));
            
            s.repaint();
        }
    };

    /**
     * Destroys all block squares
     */
    this.destroy = function(){
        for (var i = 0; i < squares.length; i++) {
            squares[i].destroy();
        }
    };
};

/*
 * Creates html and object representation for square element. Square is used for
 * resizing block elements.
 * 
 * @param {String}   posY        square vertical align relative to resized block element
 *     Possible values:
 *     top      square is on top of resized block element
 *     middle   square is in the middle of the resized block element
 *     bottom   square is on the bottom of resized block element
 * @param {String}   posX        square vertical align relative to resized block element
 *     Possible values:
 *     left     square is on the left of resized block element
 *     middle   square is in the middle of the resized block element
 *     right    square is on the right of resized block element
 * @param {Object}   objResize   object of resize class
 * @constructor
 */
apf.resize.square = function(posY, posX, objResize) {
    /*
     * Square visibility
     */
    this.visible = true;
    /*
     * square vertical align relative to resized block element
     */
    this.posX = posX;
    /*
     * square vertical align relative to resized block element
     */
    this.posY = posY;

    var margin = 0;
    var _self = this;

    /*
     * html represenation of square element
     */
    this.htmlElement = objResize.htmlElement.parentNode.appendChild(document.createElement('div'));
    apf.setStyleClass(this.htmlElement, "square");

    /*
     * Repaints square
     */
    this.repaint = function() {
        if (this.visible) {
            var block = objResize.htmlElement;
            this.htmlElement.style.display = "block";

            var bw = parseInt(block.style.width) + apf.getDiff(block)[0];
            var bh = parseInt(block.style.height) + apf.getDiff(block)[1];
            var bt = parseInt(block.style.top);
            var bl = parseInt(block.style.left);

            var sw = this.htmlElement.offsetWidth;
            var sh = this.htmlElement.offsetHeight;

            var t = posY == "top"
                ? bt - margin - sh
                : posY == "middle"
                    ? bt + bh/2 - sh/2
                    : bt + bh + margin;
            var l = posX == "left"
                ? bl - margin - sw
                : posX == "middle"
                    ? bl + bw/2 - sw/2
                    : bl + bw + margin;

            var c = (posY == "middle" 
                ? "w-resize"
                : (posX == "middle"
                     ? "n-resize"
                     : (posY + posX == "topleft"
                       || posY + posX == "bottomright") 
                         ? "nw-resize" 
                         : "ne-resize"));

            this.htmlElement.style.top = (t - 1) + "px";
            this.htmlElement.style.left = (l - 1) + "px";
            this.htmlElement.style.cursor = c;
        }
        else {
            //IE bug
            var sw = this.htmlElement.offsetWidth;
            this.htmlElement.style.display = 'none';
        }
    };

    this.destroy = function(){
        apf.destroyHtmlNode(this.htmlElement);
    };

    /* Events */
    this.htmlElement.onmouseover = function(e) {
        apf.setStyleClass(_self.htmlElement, "squareHover");
    };

    this.htmlElement.onmouseout = function(e) {
        apf.setStyleClass(_self.htmlElement, "", ["squareHover"]);
    };

    this.htmlElement.onmousedown = function(e) {
        e = (e || event);

        var block = objResize.htmlElement,

            sx = e.clientX,
            sy = e.clientY,

            pt = block.parentNode.offsetTop,
            pl = block.parentNode.offsetLeft,

            dw = objResize.scales.dwidth,
            dh = objResize.scales.dheight,
            
            snap = objResize.scales.snap,
            gridH = objResize.scales.gridH,
            gridW = objResize.scales.gridW,

            objBlock = apf.flow.isBlock(block),
            r = objBlock.other.ratio,

            posX = _self.posX,
            posY = _self.posY,

            width, height, top, left, dx, dy,
            prev_w, prev_h,

            l = parseInt(block.style.left),
            t = parseInt(block.style.top),
            w = parseInt(block.style.width),
            h = parseInt(block.style.height),
            resized = false;
            
        objResize.onresizedone(w, h, t, l);

        if (e.preventDefault) {
            e.preventDefault();
        }

        document.onmousemove = function(e) {
            e = (e || event);

            dx = e.clientX - sx;
            dy = e.clientY - sy;
            var shiftKey = e.shiftKey,
                proportion = r;

            if (shiftKey) {
                if (posX == "right" && posY == "bottom") {
                    width = w + dx;
                    height = width/proportion;
                    left = l;
                    top = t;
                }
                else if (posX == "right" && posY == "top") {
                    width = w + dx;
                    height = width/proportion;
                    left = l;
                    top = t - dx/proportion;
                }
                else if (posX == "left" && posY == "bottom") {
                    width = w - dx;
                    height = width/proportion;
                    left = l + dx;
                    top = t;
                }
                else if (posX == "left" && posY == "top") {
                    width = w - dx;
                    height = width/proportion;
                    left = l + dx;
                    top = t + dx/proportion;
                }

                /* Keep minimal size */
                if (width >= dw && height >= dh) {
                    width = prev_w = Math.max(dw, width);
                    height = prev_h = Math.max(dh, height);
                }
                else {
                    width = prev_w;
                    height = prev_h;
                    return false;
                }
            }
            else {
                width = posX == "right"
                    ? w + dx
                    : (posX == "left"
                        ? w - dx
                        : w);
                height = posY == "bottom"
                    ? h + dy
                    : (posY == "top"
                        ? h - dy
                        : h);
                left = posX == "right"
                    ? l
                    : (posX == "left"
                        ? Math.min(l + w - dw, l + dx)
                        : l);
                top = posY == "bottom"
                    ? t
                    : (posY == "top"
                        ? Math.min(t + h - dh, t + dy)
                        : t);

                /* Keep minimal size */
                width = Math.max(dw, width);
                height = Math.max(dh, height);
            }

            if (snap) {
                left = Math.floor(left / gridW) * gridW;
                top = Math.floor(top / gridH) * gridH;
                width = Math.ceil(width / gridW) * gridW;
                height = Math.ceil(height / gridH) * gridH;
            }

            if (objResize.onresize) {
                objResize.onresize(block, top, left, width, height);
            }

            objResize.show();
            
            resized = true;
        };

        document.onmouseup = function(e) {
            document.onmousemove = null;
            if (objResize.onresizedone && resized) {
                objResize.onresizedone(width, height, top, left);
                objBlock.other.ratio = width / height;
                resized = false;
            }
        };
    };
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * 
 * Controls the skinning modifications for AML.
 *
 * @private
 */
apf.skins = {
    skins: {},
    css: [],
    // @TODO Doc these ?
    _events: ["onmousemove", "onmousedown", "onmouseup", "onmouseout",
        "onclick", "ondragcopy", "ondragstart", "ondblclick"],

    /* ***********
     Init
     ************/
    Init: function(xmlNode, refNode, path) {
        /*
         get data from refNode || xmlNode
         - name
         - icon-path
         - media-path

         all paths of the xmlNode are relative to the src attribute of refNode
         all paths of the refNode are relative to the index.html
         images/ is replaced if there is a refNode to the relative path from index to the skin + /images/
         */
        var name = (refNode ? refNode.getAttribute("id") : null)
            || xmlNode.getAttribute("id");
        var base = (refNode ? (refNode.getAttribute("src") || "").match(/\//) || path : "")
            ? (path || refNode.getAttribute("src")).replace(/\/[^\/]*$/, "") + "/"
            : ""; //@todo make this absolute?

        var mediaPath = null, iconPath = null;
        mediaPath = xmlNode.getAttribute("media-path");
        if (mediaPath !== null)
            mediaPath = apf.getAbsolutePath(base || apf.hostPath, mediaPath);
        else if (refNode) {
            mediaPath = refNode.getAttribute("media-path");
            if (mediaPath !== null)
                mediaPath = apf.getAbsolutePath(apf.hostPath, mediaPath);
            else
                mediaPath = apf.getAbsolutePath(base || apf.hostPath, "images/");
        }
        
        iconPath = xmlNode.getAttribute("icon-path");
        if (iconPath !== null)
            iconPath = apf.getAbsolutePath(base || apf.hostPath, iconPath);
        else if (refNode) {
            iconPath = refNode.getAttribute("icon-path");
            if (iconPath !== null)
                iconPath = apf.getAbsolutePath(apf.hostPath, iconPath);
            else
                iconPath = apf.getAbsolutePath(base || apf.hostPath, "icons/");
        }
        
        if (!name)
            name = "default";

        if (xmlNode.getAttribute("id"))
            document.body.className += " " + xmlNode.getAttribute("id");

        var names = name.split("|");
        name = names[0];

        if (!this.skins[name] || name == "default") {
            this.skins[name] = {
                base: base,
                name: name,
                iconPath: iconPath,
                mediaPath: mediaPath,
                templates: {},
                originals: {},
                xml: xmlNode
            }
            
            if (names.length > 1) {
                for (var i = 0; i < names.length; i++)
                    this.skins[names[i]] = this.skins[name];
            }
        }
        
        if (!this.skins["default"] && this.$first == refNode)
            this.skins["default"] = this.skins[name];

        var nodes = xmlNode.childNodes;
        for (var i = nodes.length - 1; i >= 0; i--) {
            if (nodes[i].nodeType != 1)
                continue;

            //this.templates[nodes[i].tagName] = nodes[i];
            this.skins[name].templates[nodes[i].getAttribute("name")] = nodes[i];
            if (nodes[i].ownerDocument)
                this.importSkinDef(nodes[i], base, name);
        }

        this.purgeCss(mediaPath, iconPath);
        
        if (this.queue[name]) {
            for (var prop in this.queue[name]) {
                this.queue[name][prop]();
            }
        }
    },

    /**
     * Loads a stylesheet from a URL.
     * @param {String}    filename  The url to load the stylesheet from
     * @param {String}    [title]  Title of the stylesheet to load
     * @method loadStylesheet
     */
    loadStylesheet: function(filename, title) {
        var o;
        with (o = document.getElementsByTagName("head")[0].appendChild(document.createElement("LINK"))) {
            rel = "stylesheet";
            type = "text/css";
            href = filename;
            title = title;
        }

        return o;
    },

    /* ***********
     Import
     ************/
    importSkinDef: function(xmlNode, basepath, name) {
        var i, l, nodes = $xmlns(xmlNode, "style", apf.ns.aml), tnode, node;
        for (i = 0, l = nodes.length; i < l; i++) {
            node = nodes[i];

            if (node.getAttribute("src"))
                this.loadStylesheet(apf.getAbsolutePath(basepath, node.getAttribute("src")));
            else {
                var test = true;
                if (node.getAttribute("condition")) {
                    try {
                        test = eval(node.getAttribute("condition"));
                    }
                    catch (e) {
                        test = false;
                    }
                }

                if (test) {
                    //#-ifndef __PROCESSED
                    tnode = node.firstChild;
                    while (tnode) {
                        this.css.push(tnode.nodeValue);
                        tnode = tnode.nextSibling;
                    }
                    /*#-else
                    this.css.push(nodes[i].firstChild.nodeValue);
                    #-endif*/
                }
            }
        }

        nodes = $xmlns(xmlNode, "alias", apf.ns.apf);
        var t = this.skins[name].templates;
        for (i = 0; i < nodes.length; i++) {
            if (!nodes[i].firstChild)
                continue;
            t[nodes[i].firstChild.nodeValue.toLowerCase()] = xmlNode;
        }
    },

    loadedCss: "",
    purgeCss: function(imagepath, iconpath) {
        if (!this.css.length)
            return;

        var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath);
        apf.importCssString(cssString);

        

        this.css = [];
    },

    loadCssInWindow: function(skinName, win, imagepath, iconpath) {
        this.css = [];
        var name = skinName.split(":");
        var skin = this.skins[name[0]];
        var template = skin.templates[name[1]];
        this.importSkinDef(template, skin.base, skin.name);
        var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath);
        apf.importCssString(cssString);

        this.css = [];
    },

    /* ***********
     Retrieve
     ************/
    setSkinPaths: function(skinName, amlNode) {
        skinName = skinName.split(":");
        var name = skinName[0];
        var type = skinName[1];

        

        amlNode.iconPath = this.skins[name].iconPath;
        amlNode.mediaPath = this.skins[name].mediaPath;
    },

    getTemplate: function(skinName, noError) {
        skinName = skinName.split(":");
        var name = skinName[0];
        var type = skinName[1];

        if (!this.skins[name]) {
            if (noError)
                return false;
            
            
            
            return false;
        }

        if (!this.skins[name].templates[type])
            return false;

        var skin = this.skins[name].templates[type];
        var originals = this.skins[name].originals[type];
        if (!originals) {
            originals = this.skins[name].originals[type] = {};

            

            var nodes = $xmlns(skin, "presentation", apf.ns.aml)[0].childNodes;
            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i].nodeType != 1) continue;
                originals[nodes[i].baseName || nodes[i][apf.TAGNAME]] = nodes[i];
            }
        }

        /*for (var item in originals) {
            pNodes[item] = originals[item];
        }*/

        return originals;
    },

    getCssString: function(skinName) {
        return apf.queryValue($xmlns(this.skins[skinName.split(":")[0]].xml,
            "style", apf.ns.aml)[0], "text()");
    },

    
    changeSkinset: function(value) {
        var node = apf.document.documentElement;
        while (node) {
            if (node && node.nodeFunc == apf.NODE_VISIBLE
              && node.hasFeature(apf.__PRESENTATION__) && !node.skinset) {
                node.$propHandlers["skinset"].call(node, value);//$forceSkinChange
                node.skinset = null;
            }

            //Walk tree
            if (node.firstChild || node.nextSibling) {
                node = node.firstChild || node.nextSibling;
            }
            else {
                do {
                    node = node.parentNode;
                } while (node && !node.nextSibling)

                if (node)
                    node = node.nextSibling;
            }
        }
    },
    
    
    queue: {},
    waitForSkin: function(skinset, id, callback) {
        if (this.skins[skinset])
            return;
        
        (this.queue[skinset] || (this.queue[skinset] = {}))[id] = callback;
        return true;
    },

    

    setIcon: function(oHtml, strQuery, iconPath) {
        if (!strQuery) {
            oHtml.style.backgroundImage = "";
            return;
        }

        if (oHtml.tagName.toLowerCase() == "img") {
            oHtml.setAttribute("src", strQuery
                ? (iconPath || "") + strQuery
                : "");
            return;
        }

        

        //Assuming image url
        {
            

            oHtml.style.backgroundImage = "url(" + (strQuery.substr(0, 4) == "http" ? "" : iconPath || "")
                + strQuery + ")";
        }
    }
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Object handling sorting in a similar way as xslt.
 *
 * @constructor
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 *
 * @private
 */
apf.Sort = function(xmlNode) {
    var settings = {};
    
    //use this function to parse the each node
    this.parseXml = function(xmlNode, clear) {
        if (clear) settings = {};

        settings.order = xmlNode.order;
        settings.getValue = xmlNode.csort || xmlNode.$compile("sort");
        settings.getNodes = self[xmlNode["nodes-method"]];

        settings.ascending = (settings.order || "").indexOf("desc") == -1;
        settings.order = null;

        if (xmlNode["data-type"])
            settings.method = sort_methods[xmlNode["data-type"]];
        else if (xmlNode["sort-method"]) {
            settings.method = self[xmlNode["sort-method"]];
            
            
        }
        else
            settings.method = sort_methods["alpha"];
        
        var str = xmlNode["date-format"];
        if (str) {
            settings.sort_dateFmtStr = str;
            settings.method = sort_methods["date"];
            var result = str.match(/(D+|Y+|M+|h+|m+|s+)/g);
            if (result) {
                for (var pos = {}, i = 0; i < result.length; i++) 
                    pos[result[i].substr(0, 1)] = i + 1;
                settings.dateFormat = new RegExp(str.replace(/([^\sDYMhms])/g, '\\$1')
                    .replace(/YYYY/, "(\\d\\d\\d\\d)")
                    .replace(/(DD|YY|MM|hh|mm|ss)/g, "(\\d\\d)"));
                settings.dateReplace = "$" + pos["M"] + "/$" + pos["D"] + "/$" + pos["Y"];
                if (pos["h"]) 
                    settings.dateReplace += " $" + pos["h"] + ":$" + pos["m"] + ":$" + pos["s"];
            }
        }
    };
    
    this.set = function(struct, clear) {
        if (clear) settings = {};
        
        apf.extend(settings, struct);

        if (settings.ascending == undefined)
            settings.ascending = struct.order 
                ? struct.order.indexOf("desc") == -1
                : true;
        
        settings.order = null;
        
        if (struct["type"]) 
            settings.method = sort_methods[struct["type"]];
        else if (struct["method"])
            settings.method = self[struct["method"]];
        else if (!settings.method) 
            settings.method = sort_methods["alpha"];
        
        if (struct.format) {
            settings.sort_dateFmtStr = struct.format;
            //settings.method = sort_methods["date"];
            var result = str.match(/(D+|Y+|M+|h+|m+|s+)/g);
            if (result) {
                for (var pos = {}, i = 0; i < result.length; i++) 
                    pos[result[i].substr(0, 1)] = i + 1;
                settings.dateFormat = new RegExp(str.replace(/([^\sDYMhms])/g, '\\$1')
                    .replace(/YYYY/, "(\\d\\d\\d\\d)")
                    .replace(/(DD|YY|MM|hh|mm|ss)/g, "(\\d\\d)"));
                settings.dateReplace = "$" + pos["M"] + "/$" + pos["D"] + "/$" + pos["Y"];
                if (pos["h"]) 
                    settings.dateReplace += " $" + pos["h"] + ":$" + pos["m"] + ":$" + pos["s"];
            }
        }
        
        if (!settings.getValue) {
            settings.getValue = function(item) {
                return apf.queryValue(item, settings.xpath);
            }
        }
    };
    
    this.get = function(){
        return apf.extend({}, settings);
    };
    
    //use this function in __xmlUpdate [this function isnt done yet]
    this.findSortSibling = function(pNode, xmlNode) {
        var nodes = getNodes ? getNodes(pNode, xmlNode) : this.getTraverseNodes(pNode);
        
        for (var i = 0; i < nodes.length; i++) 
            if (!compare(xmlNode, nodes[i], true, sortSettings)) 
                return nodes[i];
        
        return null;
    };
    
    // Sorting methods for sort()
    var sort_intmask = ["", "0", "00", "000", "0000", "00000", "000000",
        "0000000", "00000000", "000000000", "0000000000", "00000000000",
        "000000000000", "0000000000000", "00000000000000"];
    var sort_methods = {
        "alpha" : function (n) {
            return n.toString().toLowerCase()
        },

        "number" : function (t) {
            if (!t) var t = 0;
            return (t.length < sort_intmask.length
                ? sort_intmask[sort_intmask.length - t.length]
                : "") + t;
        },

        "date" : function (t, args) {
            var sort_dateFormat = settings.dateFormat;
            var sort_dateReplace = settings.dateReplace;
            var sort_dateFmtStr = settings.sort_dateFmtStr;
            
            var d;//|| (args && sort_dateFmtStr != args[0])
            if (!sort_dateFormat) {
                d = new Date(t);
            }
            else if (sort_dateFmtStr == '*') 
                d = apf.date.getDateTime(t);
            else 
                d = (new Date(t.replace(sort_dateFormat, sort_dateReplace))).getTime();
            var t = "" + d.getTime();//parseInt(d);
            if (t == "NaN") 
                t = "0";
            return (t.length < sort_intmask.length ? sort_intmask[sort_intmask.length
                - t.length] : "") + t;
        }
    };

    /*
     sort(xpath, sort_xpath, sort_alpha, boolDesc, from, len)
     jsort(n,f,p,ps,sm,desc,sp,ep)
     */
    //var order, xpath, type, method, getNodes, dateFormat, dateReplace, sort_dateFmtStr, getValue;
    this.apply = function(n, args, func, start, len) {
        var sa = [], i = n.length;
        
        // build string-sortable list with sort method
        while (i--) {
            var v = settings.getValue(n[i]);
            if (n) 
                sa[sa.length] = {
                    toString: function(){
                        return this.v;
                    },
                    xmlNode: n[i],
                    v: (settings.method || sort_methods.alpha)(v || "", args, n[i])
                };
        }
        
        // sort it
        sa.sort();
        
        //iterate like foreach
        var end = len ? Math.min(sa.length, start + len) : sa.length;
        if (!start) 
            start = 0;
        
        if (func) {
            if (settings.ascending) 
                for (i = start; i < end; i++) 
                    f(i, end, sa[i].xmlNode, sa[i].v);
            else 
                for (i = end - 1; i >= start; i--) 
                    f(end - i - 1, end, sa[i].xmlNode, sa[i].v);
        }
        else {
            //this could be optimized by reusing n... time it later
            var res = [];
            if (settings.ascending) 
                for (i = start; i < end; i++) 
                    res[res.length] = sa[i].xmlNode;
            else 
                for (i = end - 1; i >= start; i--) 
                    res[res.length] = sa[i].xmlNode;
            return res;
        }
    };
    
    if (xmlNode) 
        this.parseXml(xmlNode);
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * The library that is used for the animations inside elements.
 *
 * @class apf.tween
 *
 * @default_private
 */
apf.tween = (function(apf) {

var modules = {
        //Animation Modules
    left: function(oHtml, value) {
        oHtml.style.left = value + PX;
    },
    right: function(oHtml, value) {
        oHtml.style.left = "";
        oHtml.style.right = value + PX;
    },
    top: function(oHtml, value) {
        oHtml.style.top = value + PX;
    },
    bottom: function(oHtml, value) {
        oHtml.style.top = "";
        oHtml.style.bottom = value + PX;
    },
    width: function(oHtml, value, center) {
        oHtml.style.width = value + PX;
    },
    height: function(oHtml, value, center) {
        oHtml.style.height = value + PX;
    },
    scrollTop: function(oHtml, value, center) {
        oHtml.scrollTop = value;
    },
    scrollLeft: function(oHtml, value, center) {
        oHtml.scrollLeft = value;
    },
    paddingTop: function(oHtml, value, center) {
        oHtml.style.paddingTop = value + "px";
    },
    boxFlex: function(oHtml, value, center) {
        oHtml.style[apf.CSSPREFIX + "BoxFlex"] = value;
    },
    "height-rsz": function(oHtml, value, center) {
        oHtml.style.height = value + PX;
        if (apf.hasSingleResizeEvent && apf.layout.$onresize)
            apf.layout.$onresize();
    },
    mwidth: function(oHtml, value, info) {
        var diff = apf.getDiff(oHtml);
        oHtml.style.width = value + PX;
        oHtml.style.marginLeft = -1 * (value / 2 + (parseInt(apf.getStyle(oHtml,
            "borderLeftWidth")) || diff[0]/2) + (info.margin || 0)) + PX;
    },
    mheight: function(oHtml, value, info) {
        var diff = apf.getDiff(oHtml);
        oHtml.style.height = value + PX;
        oHtml.style.marginTop = (-1 * value / 2 - (parseInt(apf.getStyle(oHtml,
            "borderTopWidth")) || diff[1]/2) + (info.margin || 0)) + PX;
    },
    scrollwidth: function(oHtml, value) {
        oHtml.style.width = value + PX;
        oHtml.scrollLeft = oHtml.scrollWidth;
    },
    scrollheight_old: function(oHtml, value) {
        try {
            oHtml.style.height = value + PX;
            oHtml.scrollTop = oHtml.scrollHeight;
        }
        catch (e) {
            alert(value)
        }
    },
    scrollheight: function(oHtml, value, info) {
        var diff = apf.getHeightDiff(oHtml),
            oInt = info.$int || oHtml;

        oHtml.style.height = Math.max((value + (info.diff || 0)), 0) + PX;
        oInt.scrollTop = oInt.scrollHeight - oInt.offsetHeight - diff 
            + (info.diff || 0) - (apf.isGecko ? 16 : 0); //@todo where does this 16 come from??
    },
    scrolltop: function(oHtml, value) {
        oHtml.style.height = value + PX;
        oHtml.style.top = (-1 * value - 2) + PX;
        oHtml.scrollTop = 0;//oHtml.scrollHeight - oHtml.offsetHeight;
    },
    clipright: function(oHtml, value, center) {
        oHtml.style.clip = "rect(auto, auto, auto, " + value + "px)";
        oHtml.style.marginLeft = (-1 * value) + PX;
    },
    fade: function(oHtml, value) {
        if (!apf.supportOpacity && apf.hasStyleFilters)
            oHtml.style.filter = value == 1 ? "" : "alpha(opacity=" + parseInt(value * 100) + ")";
        else
            oHtml.style.opacity = value;
    },
    bgcolor: function(oHtml, value) {
        oHtml.style.backgroundColor = value;
    },
    textcolor: function(oHtml, value) {
        oHtml.style.color = value;
    },
    htmlcss: function(oHtml, value, obj) {
        if (apf.hasStyleFilters && obj.type == "filter")
            oHtml.style.filter = value == 1 ? "" : "progid:DXImageTransform.Microsoft.Alpha(opacity=" + value + ")";
        else
            oHtml.style[obj.type] = value + (obj.needsPx ? PX : "");
    },
    transformscale: function(oHtml, value, obj) {
        oHtml.style[obj.type] = SCALEA + parseFloat(value) + SCALEB;
    },
    transformrotate: function(oHtml, value, obj) {
        oHtml.style[obj.type] = ROTATEA + parseFloat(value) + ROTATEB;
    },
    transformvalscale: function(value) {
        return SCALEA + parseFloat(value) + SCALEB;
    },
    transformvalrotate: function(value) {
        return ROTATEA + parseFloat(value) + ROTATEB;
    }
};

var ID = "id",
    PX = "px",
    NUM = "number",
    TRANSVAL = "transformval",
    TRANSFORM = "transform",
    SCALE = "scale",
    SCALEA = "scale(",
    ROTATEA = "rotate(",
    SCALEB = ")",
    ROTATEB = "deg)",
    CSSTIMING = ["linear", "ease-in", "ease-out", "ease", "ease-in-out", "cubic-bezier"],
    CSSPROPS = {
        "left"        : "left",
        "right"       : "right",
        "top"         : "top",
        "bottom"      : "bottom",
        "width"       : "width",
        "height"      : "height",
        "scrollTop"   : false,
        "scrollLeft"  : false,
        "mwidth"      : false,
        "mheight"     : false,
        "scrollwidth" : false,
        "scrollheight": false,
        "fade"        : "opacity",
        "opacity"     : "opacity",
        "bgcolor"     : "background-color",
        "textcolor"   : "color",
        "transform"   : "transform"
    },
    __pow = Math.pow,
    __round = Math.round,

    queue = {},

    current= null,

    setQueue = function(oHtml, stepFunction) {
        var id = oHtml.getAttribute(ID);
        if (!id) {
            apf.setUniqueHtmlId(oHtml);
            id = oHtml.getAttribute(ID);
        }

        if (!queue[id])
            queue[id] = [];

        queue[id].push(stepFunction);
        if (queue[id].length == 1)
            stepFunction(0);
    },

    nextQueue = function(oHtml) {
        var q = queue[oHtml.getAttribute(ID)];
        if (!q) return;

        q.shift(); //Remove current step function

        if (q.length)
            q[0](0);
    },

    clearQueue = function(oHtml, bStop) {
        var q = queue[oHtml.getAttribute(ID)];
        if (!q) return;

        if (bStop && current && current.control)
            current.control.stop = true;
        q.length = 0;
    },

    purgeQueue = function(oHtml) {
        var id = oHtml.getAttribute(ID);
        if (!id) {
            apf.setUniqueHtmlId(oHtml);
            id = oHtml.getAttribute(ID);
        }

        for (var i in queue) {
            if (i == id)
                queue[i] = [];
        }
    },

     // @TODO Doc
    /**
     * Calculates all the steps of an animation between a
     * begin and end value based on three tween strategies
     *
     * @method calcSteps
     * @param {Function}
     func * @param {String}
     fromValue * @param {String}
     fromValue * @param {Number}
     nrOfSteps */
    calcSteps = function(func, fromValue, toValue, nrOfSteps) {
        var i = 0,
            l = nrOfSteps - 1,
            steps = [fromValue];

        // backward compatibility...
        if (typeof func == NUM) {
            if (!func)
                func = apf.tween.linear;
            else if (func == 1)
                func = apf.tween.easeInCubic;
            else if (func == 2)
                func = apf.tween.easeOutCubic;
        }

        /*
        func should have the following signature:
        func(t, x_min, dx)
        where 0 <= t <= 1, dx = x_max - x_min

        easeInCubic: function(t, x_min, dx) {
            return dx * pow(t, 3) + x_min;
        }
        */
        for (i = 0; i < l; ++i)
            steps.push(func(i / nrOfSteps, fromValue, toValue - fromValue));
        steps.push(toValue);
        
        return steps;
    },

     // @TODO Doc
    /**
     * Calculates all the steps of an animation between a
     * begin and end value for colors
     *  
     * @method calcColorSteps   
     * @param {Function}
     animtype * @param {String}
     fromValue * @param {String}
     fromValue * @param {Number}
     nrOfSteps */
    calcColorSteps = function(animtype, fromValue, toValue, nrOfSteps) {
        var d2, d1,
            c = apf.color.colorshex,
            a = parseInt((c[fromValue] || fromValue).slice(1), 16),
            b = parseInt((c[toValue] || toValue).slice(1), 16),
            i = 0,
            out = [];

        for (; i < nrOfSteps; i++){
            d1 = i / (nrOfSteps - 1), d2 = 1 - d1;
            out[out.length] = "#" + ("000000" +
                ((__round((a & 0xff) * d2 + (b & 0xff) * d1) & 0xff) |
                (__round((a & 0xff00) * d2 + (b & 0xff00) * d1) & 0xff00) |
                (__round((a & 0xff0000) * d2 + (b & 0xff0000) * d1) & 0xff0000)).toString(16)).slice(-6);
        }

        return out;
    },

     // @TODO Doc wtf is stop ?
    /**
     * Tweens a single property of a single element or HTML element from a
     * start to an end value.
     * 
     * #### Example
     * 
     * ```javascript
     *  apf.tween.single(myDiv, {
     *      type : "left",
     *      from : 10,
     *      to   : 100,
     *      anim : apf.tween.EASEIN
     *  });
     * ```
     *
     * #### Example
     * 
     * Multiple animations can be run after each other
     * by calling this function multiple times.
     * 
     * ```javascript
     *  apf.tween.single(myDiv, options).single(myDiv2, options2);
     * ```
     *
     * @method single
     * @param {DOMNode}  oHtml The object to animate.
     * @param {Object}   info  The animation settings. The following properties are available:
     *   - type ([[String]]): The property to be animated. These are predefined
     *                          property handlers and can be added by adding a
     *                          method to `apf.tween` with the name of the property
     *                          modifier. There are several handlers available.
     *      - `"left"`:            Sets the left position
     *      - `"right"`:           Sets the right position
     *      - `"top"`:            Sets the top position
     *      - `"bottom"`:          Sets the bottom position
     *      - `"width"` :          Sets the horizontal size
     *      - `"height"`:          Sets the vertical size
     *      - `"scrollTop"`:       Sets the scoll position
     *      - `"mwidth"` :         Sets the width and the margin-left to width/2
     *      - `"mheight"` :        Sets the height ant the margin-top to height/2
     *      - `"scrollwidth"`:     Sets the width an sets the scroll to the maximum size
     *      - `"scrollheight"`:    Sets the height an sets the scroll to the maximum size
     *      - `"scrolltop"` :      Sets the height and the top as the negative height value
     *      - `"fade"` :           Sets the opacity property
     *      - `"bgcolor"`:         Sets the background color
     *      - `"textcolor"`:       Sets the text color
     *   - from ([[Number]] or [[String]]): The start value of the animation
     *   - to ([[Number]] or [[String]]): The end value of the animation
     *   - [steps] ([[Number]]): The number of steps to divide the tween in
     *   - [interval] ([[Number]]): The time between each step
     *   - [anim] ([[Number]]): The distribution of change between the step over the entire animation.             
     *   - [color] ([[Boolean]]): Specifies whether the specified values are colors
     *   - [userdata] (`Mixed`): Any data you would like to have available in your callback methods
     *   - [onfinish] ([[Function]]): A function that is called at the end of the animation
     *   - [oneach] ([[Function]]): A function that is called at each step of the animation
     *   - [control] ([[Object]]): An object that can stop the animation at any point
     *     Methods:
     *     stop                 set on the object passed .
     */
    single = function(oHtml, info) {
        info = apf.extend({steps: 10, interval: 5, anim: apf.tween.linear, control: {}}, info);
        info.steps = Math.ceil(info.steps * apf.animSteps);
        info.interval = Math.ceil(info.interval * apf.animInterval);

        if (oHtml.nodeFunc > 100) {
            info.$int = oHtml.$int;
            oHtml = oHtml.$ext;
        }
        try { //@TODO hack where currentStyle is still undefined
            if ("fixed|absolute|relative".indexOf(apf.getStyle(oHtml, "position")) == -1)
                oHtml.style.position = "relative";
        } catch (e) {}
        
        var useCSSAnim = (false && apf.supportCSSAnim && apf.supportCSSTransition && CSSPROPS[info.type]),
            isTransform = (info.type == TRANSFORM);

        info.method = useCSSAnim ? info.type : isTransform
            ? modules[TRANSFORM + (info.subType || SCALE)]
            : modules[info.type]
                ? modules[info.type]
                : (info.needsPx = needsPix[info.type] || false)
                    ? modules.htmlcss
                    : modules.htmlcss;

        

        if (useCSSAnim) {
            var type = CSSPROPS[info.type];
            if (type === false)
                return apf.tween;
            info.type = type || info.type;
            if (isTransform) {
                if (!info.subType)
                    info.subType = SCALE;
                info.type = apf.supportCSSAnim;
            }

            var transform = (isTransform)
                ? modules[TRANSVAL + (info.subType || SCALE)]
                : null;

            oHtml.style[info.type] = isTransform
                ? transform(info.from)
                : info.from + (needsPix[info.type] ? PX : "");
            $setTimeout(function() {
                oHtml.style[info.type] = isTransform
                    ? transform(info.to)
                    : info.to + (needsPix[info.type] ? PX : "");
                oHtml.offsetTop; //force style recalc
                oHtml.style[apf.cssPrefix + "Transition"] = info.type + " " + ((info.steps
                    * info.interval) / 1000) + "s "
                    + CSSTIMING[info.anim || 0];
                var f = function() {
                    if (info.onfinish)
                        info.onfinish(oHtml, info.userdata);
                    oHtml.style[apf.cssPrefix + "Transition"] = "";
                    oHtml.removeEventListener(apf.cssAnimEvent, f);
                };
                oHtml.addEventListener(apf.cssAnimEvent, f);
            });
            return apf.tween;
        }

        if (info.control) {
            info.control.state = apf.tween.RUNNING;
            info.control.stop = function(){
                info.control.state = apf.tween.STOPPING;
                clearQueue(oHtml);
                if (info.onstop)
                    info.onstop(oHtml, info.userdata);
            }
        }

        var steps = info.color
                ? calcColorSteps(info.anim, info.from, info.to, info.steps)
                : calcSteps(info.anim, parseFloat(info.from), parseFloat(info.to), info.steps),
            stepFunction = function(step) {
                if (info.control && info.control.state) {
                    info.control.state = apf.tween.STOPPED;
                    return;
                }
                
                current = info;

                if (info.onbeforeeach
                  && info.onbeforeeach(oHtml, info.userdata) === false)
                    return;

                try {
                   info.method(oHtml, steps[step], info);
                }
                catch (e) {}

                if (info.oneach)
                    info.oneach(oHtml, info.userdata);

                if (step < info.steps)
                    return $setTimeout(function(){stepFunction(step + 1)}, info.interval);

                current = null;
                if (info.control)
                    info.control.state = apf.tween.STOPPED;
                if (info.onfinish)
                    info.onfinish(oHtml, info.userdata);

                nextQueue(oHtml);
            };

        if (info.type.indexOf("scroll") > -1)
            purgeQueue(oHtml);
        setQueue(oHtml, stepFunction);

        return apf.tween;
    },

     // @TODO Doc wtf is stop
    /**
     * Tweens multiple properties of a single element or html element from a
     * start to an end value.
     * 
     * #### Example
     *
     * Here we are, animating both the left and width at the same time:
     *
     * ```javascript
     *  apf.tween.multi(myDiv, {
     *      anim   : apf.tween.EASEIN
     *      tweens : [{
     *          type : "left",
     *          from : 10,
     *          to   : 100,
     *      },
     *      {
     *          type : "width",
     *          from : 100,
     *          to   : 400,
     *      }]
     *  });
     * ````
     *
     * #### Example
     *
     * Multiple animations can be run after each other
     * by calling this function multiple times.
     *
     * ```javascript
     *  apf.tween.multi(myDiv, options).multi(myDiv2, options2);
     * ```
     *
     * @method multi
     * @param {DOMNode}  oHtml The object to animate.
     * @param {Object} info The settings of the animation. It contains the following properties:
     *   - [steps] ([[Number]]): The number of steps to divide the tween in
     *   - [interval] ([[Number]]): The time between each step
     *   - [anim] ([[Number]]): The distribution of change between the step over
     *                          the entire animation
     *   - [onfinish] ([[Function]]): A function that is called at the end of the animation
     *   - [oneach] ([[Function]]): A function that is called at each step of the animation
     *   - [oHtml] ([[HTMLElement]]): Another HTML element to animate.
     *   - [control] ([[Object]]): An object that can stop the animation at any point. It contains the following properties:
     *     - stop ([[Boolean]]): Specifies whether the animation should stop.
     *   - [tweens] ([[Array]]): A collection of simple objects specifying the single
     *                          value animations that are to be executed simultaneously.
     *                          (for the properties of these single tweens see the
     *                          [[apf.tween.single]] method).
     */
    multi = function(oHtml, info) { 
        info = apf.extend({steps: 10, interval: 5, anim: apf.tween.linear, control: {}}, info);
        info.steps = Math.ceil(info.steps * apf.animSteps);
        info.interval = Math.ceil(info.interval * apf.animInterval);

        if (oHtml.nodeFunc > 100) {
            info.$int = oHtml.$int;
            oHtml = oHtml.$ext;
        }

        var animCSS, isTransform,
            useCSSAnim = false && apf.supportCSSAnim && apf.supportCSSTransition,
            hasCSSAnims = false,
            cssDuration = ((info.steps * info.interval) / 1000),
            cssAnim = CSSTIMING[info.anim || 0],
            steps = [],
            stepsTo = [],
            i = 0,
            l = info.tweens.length;

        for (; i < l; i++) {
            var data = info.tweens[i];

            if (data.oHtml && data.oHtml.nodeFunc > 100) {
                data.$int = data.oHtml.$int;
                data.oHtml = data.oHtml.$ext;
            }

            animCSS = (useCSSAnim && CSSPROPS[data.type]);
            isTransform = (data.type == TRANSFORM);
            if (isTransform) {
                if (!data.subType)
                    data.subType = SCALE;
                data.type = apf.supportCSSAnim;
            }

            data.method = animCSS
                ? data.type
                : isTransform
                    ? modules[TRANSFORM + (data.subType)]
                    : modules[data.type]
                        ? modules[data.type]
                        : (data.needsPx = needsPix[data.type] || false)
                            ? modules.htmlcss
                            : modules.htmlcss;


            

            if (animCSS) {
                var type = isTransform ? data.type : CSSPROPS[data.type];
                data.type = type || data.type;
                var transform = modules[TRANSVAL + (data.subType)]

                oHtml.style[data.type] = isTransform
                    ? transform(data.from)
                    : data.from + (needsPix[data.type] ? PX : "");
                stepsTo.push([data.type, isTransform
                    ? transform(data.to)
                    : data.to + (needsPix[data.type] ? PX : "")]);
                steps.push(data.type + " " + cssDuration + "s " + cssAnim + " 0");

                hasCSSAnims = true;
            }
            else {
                steps.push(data.color
                    ? calcColorSteps(info.anim, data.from, data.to, info.steps)
                    : calcSteps(info.anim, parseFloat(data.from), parseFloat(data.to), info.steps));
            }
        }

        if (hasCSSAnims) {
            oHtml.style[apf.cssPrefix + "Transition"] = steps.join(",");
            oHtml.offsetTop; //force style recalc
            var count = 0,
                func = function() {
                    count++;
                    if (count == stepsTo.length) {
                        if (info.onfinish)
                            info.onfinish(oHtml, info.userdata);
                        oHtml.style[apf.cssPrefix + "Transition"] = "";
                        oHtml.removeEventListener(apf.cssAnimEvent, func);
                    }
                };
            oHtml.addEventListener(apf.cssAnimEvent, func, false);
            for (var k = 0, j = stepsTo.length; k < j; k++)
                oHtml.style[stepsTo[k][0]] = stepsTo[k][1];
            return apf.tween;
        }
        
        if (info.control) {
            info.control.state = apf.tween.RUNNING;
            info.control.stop = function(){
                if (info.control.state == apf.tween.STOPPED)
                    return;
                
                info.control.state = apf.tween.STOPPING;
                clearQueue(oHtml);
                if (info.onstop)
                    info.onstop(oHtml, info.userdata);
            }
        }

        var tweens = info.tweens,
            stepFunction = function(step) {
                if (info.control && info.control.state) {
                    info.control.state = apf.tween.STOPPED;
                    return;
                }
                
                current = info;

                try {
                    for (var i = 0; i < steps.length; i++) {
                        tweens[i].method(tweens[i].oHtml || oHtml,
                          steps[i][step], tweens[i]);
                    }
                } catch (e) {}

                if (info.oneach)
                    info.oneach(oHtml, info.userdata);

                if (step < info.steps)
                    return $setTimeout(function(){stepFunction(step + 1)}, info.interval);

                current = null;
                if (info.control)
                    info.control.state = apf.tween.STOPPED;
                if (info.onfinish)
                    info.onfinish(oHtml, info.userdata);

                nextQueue(oHtml);
            };

        setQueue(oHtml, stepFunction);

        return apf.tween;
    },

    /**
     * Tweens an element or HTML element from its current state to a CSS class.
     *
     * #### Example
     *
     * Multiple animations can be run after each other by calling this function
     * multiple times.
     * 
     * ```javascript
     *  apf.tween.css(myDiv, 'class1').multi(myDiv2, 'class2');
     * ```
     *
     * @method apf.tween.css
     * @param {DOMNode}  oHtml The object to animate.
     * @param {String} className The class name that defines the CSS properties to be set or removed.
     * @param {Object} info The settings of the animation. The following properties are available:
     *   Properties:
     *   - [steps] ([[Number]]): The number of steps to divide the tween in
     *   - [interval] ([[Number]]): The time between each step
     *   - [anim] ([[Number]]): The distribution of change between the step over the entire animation
     *   - [onfinish] ([[Function]]): A function that is called at the end of the animation
     *   - [oneach] ([[Function]]): A function that is called at each step of the animation
     *   - [control] ([[Object]]): An object that can stop the animation at any point. It contains the following property:
     *     - stop ([[Boolean]]): Specifies whether the animation should stop.
     * @param {Boolean} remove Specifies whether the class is set or removed from the element
     */
    css = function(oHtml, className, info, remove) {
        (info = info || {}).tweens = [];

        if (oHtml.nodeFunc > 100)
            oHtml = oHtml.$ext;

        if (remove)
            apf.setStyleClass(oHtml, "", [className]);

        var resetAnim = function(remove, callback) {
            if (remove)
                apf.setStyleClass(oHtml, "", [className]);
            else
                apf.setStyleClass(oHtml, className);

            //Reset CSS values
            for (var i = 0; i < info.tweens.length; i++){
                if (info.tweens[i].type == "filter")
                    continue;

                oHtml.style[info.tweens[i].type] = "";
            }

            if (callback)
                callback.apply(this, arguments);
        }

        var onfinish = info.onfinish,
            onstop = info.onstop;
        info.onfinish = function(){resetAnim(remove, onfinish);}
        info.onstop = function(){resetAnim(!remove, onstop);}

        var result, newvalue, curvalue, j, isColor, style, rules, i,
            tweens = {};
        for (i = 0; i < document.styleSheets.length; i++) {
            rules = document.styleSheets[i][apf.styleSheetRules];
            if (!rules || !rules.length)
                continue;
            for (j = rules.length - 1; j >= 0; j--) {
                var rule = rules[j];

                if (!rule.style || !rule.selectorText.match("\." + className + "$"))
                    continue;

                for (style in rule.style) {
                    if (!rule.style[style] || cssProps.indexOf("|" + style + "|") == -1)
                        continue;

                    if (style == "filter") {
                        if (!rule.style[style].match(/opacity\=([\d\.]+)/))
                            continue;
                        newvalue = RegExp.$1;

                        result = (apf.getStyleRecur(oHtml, style) || "")
                            .match(/opacity\=([\d\.]+)/);
                        curvalue = result ? RegExp.$1 : 100;
                        isColor = false;

                        if (newvalue == curvalue) {
                            if (remove) curvalue = 100;
                            else newvalue = 100;
                        }
                    }
                    else {
                        newvalue = remove && oHtml.style[style] || rule.style[style];
                        if (remove) oHtml.style[style] = "";
                        curvalue = apf.getStyleRecur(oHtml, style);
                        isColor = style.match(/color/i) ? true : false;
                    }

                    tweens[style] = {
                        type: style,
                        from: (isColor ? String : parseFloat)(remove
                                    ? newvalue
                                    : curvalue),
                        to: (isColor ? String : parseFloat)(remove
                                    ? curvalue
                                    : newvalue),
                        color: isColor,
                        needsPx: needsPix[style.toLowerCase()] || false
                    };
                }
            }
        }

        for (var prop in tweens)
            info.tweens.push(tweens[prop]);

        if (remove)
            apf.setStyleClass(oHtml, className);

        return multi(oHtml, info);
    },

    cssRemove = function(oHtml, className, info) {
        css(oHtml, className, info, true);
    },

    needsPix = {
        "left"        : true,
        "top"         : true,
        "bottom"      : true,
        "right"       : true,
        "width"       : true,
        "height"      : true,
        "fontSize"    : true,
        "lineHeight"  : true,
        "textIndent"  : true,
        "marginLeft"  : true,
        "marginTop"   : true,
        "marginRight" : true,
        "marginBottom": true
    },

    cssProps = "|backgroundColor|backgroundPosition|color|width|filter"
             + "|height|left|top|bottom|right|fontSize"
             + "|letterSpacing|lineHeight|textIndent|opacity"
             + "|paddingLeft|paddingTop|paddingRight|paddingBottom"
             + "|borderLeftWidth|borderTopWidth|borderRightWidth|borderBottomWidth"
             + "|borderLeftColor|borderTopColor|borderRightColor|borderBottomColor"
             + "|marginLeft|marginTop|marginRight|marginBottom"
             + "|transform|", // transforms are special and get special treatment
    cssTransforms = "|scale|rotate|";

return {
    single: single,
    multi: multi,
    css: css,
    cssRemove: cssRemove,
    clearQueue: clearQueue,
    addModule: function(name, func, force) {
        if (typeof name != "string" || typeof func != "function" || (modules[name] && !force))
            return this;
        modules[name] = func;
        return this;
    },
    /** Linear tweening method */
    NORMAL: 0,
    /** Ease-in tweening method */
    EASEIN: 1,
    /** Ease-out tweening method */
    EASEOUT: 2,
    
    RUNNING: 0,
    STOPPING: 1,
    STOPPED: 2,
    
    calcColorSteps: calcColorSteps,

    linear: function(t, x_min, dx) {
        return dx * t + x_min;
    },
    easeInQuad: function(t, x_min, dx) {
        return dx * __pow(t, 2) + x_min;
    },
    easeOutQuad: function(t, x_min, dx) {
        return -dx * t * (t - 2) + x_min;
    },
    easeInOutQuad: function(t, x_min, dx) {
        if ((t /= .5) < 1)
            return dx / 2 * t * t + x_min;
        return -dx / 2 * ((--t) * (t - 2) - 1) + x_min;
    },
    easeInCubic: function(t, x_min, dx) {
        return dx * __pow(t, 3) + x_min;
    },
    easeOutCubic: function(t, x_min, dx) {
        return dx * (__pow(t - 1, 3) + 1) + x_min;
    },
    easeInOutCubic: function(t, x_min, dx) {
        if ((t /= .5) < 1)
            return dx / 2 * __pow(t, 3) + x_min;
        return dx / 2 * (__pow(t - 2, 3) + 2) + x_min;
    },
    easeInQuart: function(t, x_min, dx) {
        return dx * __pow(t, 4) + x_min;
    },
    easeOutQuart: function(t, x_min, dx) {
        return -dx * (__pow(t - 1, 4) - 1) + x_min;
    },
    easeInOutQuart: function(t, x_min, dx) {
        if ((t /= .5) < 1)
            return dx / 2 * __pow(t, 4) + x_min;
        return -dx / 2 * (__pow(t - 2, 4) - 2) + x_min;
    },
    easeInQuint: function(t, x_min, dx) {
        return dx * __pow(t, 5) + x_min;
    },
    easeOutQuint: function(t, x_min, dx) {
        return dx * (__pow(t - 1, 5) + 1) + x_min;
    },
    easeInOutQuint: function(t, x_min, dx) {
        if ((t /= .5) < 1)
            return dx / 2 * __pow(t, 5) + x_min;
        return dx / 2 * (__pow(t - 2, 5) + 2) + x_min;
    },
    easeInSine: function(t, x_min, dx) {
        return -dx * Math.cos(t * (Math.PI / 2)) + dx + x_min;
    },
    easeOutSine: function(t, x_min, dx) {
        return dx * Math.sin(t * (Math.PI / 2)) + x_min;
    },
    easeInOutSine: function(t, x_min, dx) {
        return -dx / 2 * (Math.cos(Math.PI * t) - 1) + x_min;
    },
    easeInExpo: function(t, x_min, dx) {
        return (t == 0) ? x_min : dx * __pow(2, 10 * (t - 1)) + x_min;
    },
    easeOutExpo: function(t, x_min, dx) {
        return (t == 1) ? x_min + dx : dx * (-__pow(2, -10 * t) + 1) + x_min;
    },
    easeInOutExpo: function(t, x_min, dx) {
        if (t == 0)
            return x_min;
        if (t == 1)
            return x_min + dx;
        if ((t /= .5) < 1)
            return dx / 2 * __pow(2, 10 * (t - 1)) + x_min;
        return dx / 2 * (-__pow(2, -10 * --t) + 2) + x_min;
    },
    easeInCirc: function(t, x_min, dx) {
        return -dx * (Math.sqrt(1 - t * t) - 1) + x_min;
    },
    easeOutCirc: function(t, x_min, dx) {
        return dx * Math.sqrt(1 - (t -= 1) * t) + x_min;
    },
    easeInOutCirc: function(t, x_min, dx) {
        if ((t /= .5) < 1)
            return -dx / 2 * (Math.sqrt(1 - t * t) - 1) + x_min;
        return dx / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + x_min;
    },
    easeInElastic: function(t, x_min, dx) {
        var s = 1.70158,
            p = .3,
            a = dx;
        if (t == 0)
            return x_min;
        if (t == 1)
            return x_min + dx;
        if (!a || a < Math.abs(dx)) {
            a = dx;
            s = p / 4;
        }
        else
            s = p / (2 * Math.PI) * Math.asin (dx / a);
        return -(a * __pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)) + x_min;
    },
    easeOutElastic: function(t, x_min, dx) {
        var s = 1.70158,
            p = .3,
            a = dx;
        if (t == 0)
            return x_min;
        if (t == 1)
            return x_min + dx;
        if (a < Math.abs(dx)) {
            a = dx;
            s = p / 4;
        }
        else {
            s = p / (2 * Math.PI) * Math.asin(dx / a);
        }
        return a * __pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + dx + x_min;
    },
    easeInOutElastic: function(t, x_min, dx) {
        var s = 1.70158,
            p = 0,
            a = dx;
        if (t == 0)
            return x_min;
        if ((t / 2) == 2)
            return x_min + dx;
        if (!p)
            p = .3 * 1.5;
        if (a < Math.abs(dx)) {
            a = dx;
            s = p / 4;
        }
        else {
            s = p / (2 * Math.PI) * Math.asin(dx / a);
        }
        if (t < 1)
            return -.5 * (a * __pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)) + x_min;
        return a * __pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * .5 + dx + x_min;
    },
    easeInBack: function(t, x_min, dx) {
        var s = 1.70158;
        return dx * __pow(t, 2) * ((s + 1) * t - s) + x_min;
    },
    easeOutBack: function(t, x_min, dx) {
        var s = 1.70158;
        return dx * ((t -= 1) * t * ((s + 1) * t + s) + 1) + x_min;
    },
    easeInOutBack: function(t, x_min, dx) {
        var s = 1.70158;
        if ((t / 2) < 1)
            return dx / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + x_min;
        return dx / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + x_min;
    },
    easeInBounce: function(t, x_min, dx) {
        return dx - apf.tween.easeOutBounce(1 - t, 0, dx) + x_min;
    },
    easeOutBounce: function(t, x_min, dx) {
        if (t < (1 / 2.75))
            return dx * (7.5625 * t * t) + x_min;
        else if (t < (2 / 2.75))
            return dx * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + x_min;
        else if (t < (2.5 / 2.75))
            return dx * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + x_min;
        else
            return dx * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + x_min;
    },
    easeInOutBounce: function(t, x_min, dx) {
        if (t < 1 / 2)
            return apf.tween.easeInBounce(t * 2, 0, dx) * .5 + x_min;
        return apf.tween.easeOutBounce(t * 2 - 1, 0, dx) * .5 + dx * .5 + x_min;
    }
};

})(apf);












/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
 





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * The XML database object provides local storage for XML data. This object
 * routes all changes to the XML data to the data bound objects. It also
 * provides utility functions for XML handling.
 *
 * @class apf.xmldb
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 * @additional
 *
 * @default_private
 */
apf.xmldb = new (function(){
    var _self = this;

    this.xmlDocTag = "a_doc";
    this.xmlIdTag = "a_id";
    this.xmlListenTag = "a_listen";
    this.htmlIdTag = "id";
    this.disableRDB = false;

    this.$xmlDocLut = [];
    this.$nodeCount = {};

    var cleanRE = / (?:a_doc|a_id|a_listen|a_loaded)=(?:"|')[^'"]+(?:"|')/g,
        whiteRE = />[\s\n\r\t]+</g;

    /**
     * Clear XML document cache periodically when no model is referencing it
     */
    this.garbageCollect = function(){
        var xmlNode, cache = apf.xmldb.$xmlDocLut, docId, model;
        for (var i = 0, l = cache.length; i < l; i++) {
            xmlNode = cache[i];

            if (!xmlNode || xmlNode.nodeFunc)
                continue;

            docId = i;//xmlNode.getAttribute(apf.xmldb.xmlDocTag);
            model = apf.nameserver.get("model", docId);

            if (!model || model.data != xmlNode) {
                cache[i] = null;
            }
        }
    };

    this.$gcInterval = window.setInterval
        ? setInterval(function(){
              _self.garbageCollect();
          }, 60000)
        : null;

    /*
     * @private
     */
    this.getElementById = function(id, doc) {
        if (!doc)
            doc = this.$xmlDocLut[id.split("\|")[0]];
        if (!doc)
            return false;

        return doc.selectSingleNode("descendant-or-self::node()[@"
            + this.xmlIdTag + "='" + id + "']");
    };

    /*
     * @private
     */
    this.getNode = function(htmlNode) {
        if (!htmlNode || !htmlNode.getAttribute(this.htmlIdTag))
            return false;

        return this.getElementById(htmlNode.getAttribute(this.htmlIdTag)
            .split("\|", 2).join("|"));
    };

    /*
     * @private
     */
    this.getNodeById = function(id, doc) {
        var q = id.split("\|");
        q.pop();
        return this.getElementById(q.join("|"), doc);//id.split("\|", 2).join("|")
    };

    /*
     * @private
     */
    this.getDocumentById = function(id) {
        return this.$xmlDocLut[id];
    };

    /*
     * @private
     */
    this.getDocument = function(node) {
        return this.$xmlDocLut[node.getAttribute(this.xmlIdTag).split("\|")[0]];
    };

    /**
     * @private
     */
    this.getID = function(xmlNode, o) {
        return xmlNode.getAttribute(this.xmlIdTag) + "|" + o.$uniqueId;
    };

    /*
     * @private
     */
    this.getElement = function(parent, nr) {
        var nodes = parent.childNodes;
        for (var j = 0, i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 1)
                continue;
            if (j++ == nr)
                return nodes[i];
        }
    };

    /**
     * Sets the model of an element.
     *
     * @param {apf.model} The model to be set
     *
     */
    this.setModel = function(model) {
        
        apf.nameserver.register("model", model.data.ownerDocument
            .documentElement.getAttribute(this.xmlDocTag), model);
        
    };

    /**
     * Finds the model of an element.
     *
     * @param {XMLNode} xmlNode The {@link term.datanode data node} to find its model.
     *
     */
    this.findModel = function(xmlNode) {
        
        return apf.nameserver.get("model", xmlNode.ownerDocument
            .documentElement.getAttribute(this.xmlDocTag));
        
    };

    /*
     * @private
     */
    this.getXmlId = function(xmlNode) {
        return xmlNode.getAttribute(this.xmlIdTag) ||
          this.nodeConnect(apf.xmldb.getXmlDocId(xmlNode), xmlNode);
    };

    /**
     * Gets the HTML representation of an XML node for a certain element.
     *
     * @param {XMLNode} xmlNode  The {@link term.datanode data node} which is represented by the HTML element.
     * @param {apf.AmlNode} oComp    The element that has created the representation.
     * @return {DOMNode} The HTML node representing the XML node.
     */
    this.getHtmlNode = function(xmlNode, oComp) {
        if (xmlNode && xmlNode.nodeType == 1 && xmlNode.getAttribute(this.xmlIdTag)) {
            return oComp.$findHtmlNode(xmlNode.getAttribute(this.xmlIdTag)
                + "|" + oComp.$uniqueId);
        }
        return null;
    };

    /**
     * Finds the HTML representation of an XML node for a certain element.
     *
     * @param {XMLNode} xmlNode  The {@link term.datanode data node} which is represented by the HTML element.
     * @param {apf.AmlNode} oComp    The element that has created the representation.
     * @return {DOMNode} The HTML node representing the XML node.
     */
    this.findHtmlNode = function(xmlNode, oComp) {
        do {
            if (xmlNode.nodeType == 1 && xmlNode.getAttribute(this.xmlIdTag)) {
                return oComp.$findHtmlNode(xmlNode.getAttribute(this.xmlIdTag)
                    + "|" + oComp.$uniqueId);
            }
            if (xmlNode == oComp.xmlRoot)
                return null;

            xmlNode = xmlNode.parentNode;
        }
        while (xmlNode && xmlNode.nodeType != 9)

        return null;
    };

    /**
     * Finds the {@link term.datanode data node} that is represented by the HTML node.
     *
     * @param {DOMNode} htmlNode  The HTML node representing the an XML node.
     * @return {XMLNode} The {@link term.datanode data node} for which the HTML node is its representation.
     */
    this.findXmlNode = function(htmlNode) {
        if (!htmlNode)
            return false;

        var id;
        while (htmlNode && htmlNode.nodeType == 1 && (
          htmlNode.tagName.toLowerCase() != "body" && !(id = htmlNode.getAttribute(this.htmlIdTag))
          || (id || (id = htmlNode.getAttribute(this.htmlIdTag))) && id.match(/^q/)
        )) {
            if (htmlNode.host && htmlNode.host.$ext == htmlNode)
                return htmlNode.host.xmlRoot;

            htmlNode = htmlNode.parentNode;
        }
        if (!htmlNode || htmlNode.nodeType != 1)
            return false;

        if (htmlNode.tagName.toLowerCase() == "body")
            return false;

        return this.getNode(htmlNode);
    };

    this.getXml = apf.getXml;

    /*
     * @private
     */
    this.nodeConnect = function(documentId, xmlNode, htmlNode, o) {
        if (!this.$nodeCount[documentId])
            this.$nodeCount[documentId] = 0;

        var xmlId;
        xmlId = xmlNode.getAttribute(this.xmlIdTag)
          || xmlNode.setAttribute(this.xmlIdTag, (xmlId = documentId
               + "|" + ++this.$nodeCount[documentId])) || xmlId;

        if (!o)
            return xmlId;

        var htmlId = xmlId + "|" + o.$uniqueId;
        if (htmlNode)
            htmlNode.setAttribute(this.htmlIdTag, htmlId);

        return htmlId;
    };



    this.$listeners = [null];    
    // make sure that "0" is never a listener index    
    // @todo this is cleanup hell! Listeners should be completely rearchitected
    /*
     * @private
     * 
     */
    this.addNodeListener = function(xmlNode, o, uId) {
        

        var id, listen = String(xmlNode.getAttribute(this.xmlListenTag) || "");
        //id || (id = String(o.$uniqueId));

        if (!uId) uId = String(o.$uniqueId);
        if (uId.charAt(0) == "p") {
            var sUId = uId.split("|");
            id = this.$listeners.push(function(args) {
                //@todo apf3.0 should this be exactly like in class.js?
                //@todo optimize this to check the async flag: parsed[3] & 4

                var amlNode = apf.all[sUId[1]]; //It's possible the aml node dissapeared in this loop.
                if (amlNode) {
                    var model = apf.all[sUId[3]];
                    if (!model)
                        return;

                    if (model.$propBinds[sUId[1]][sUId[2]]) {
                        if (!apf.isChildOf(model.data, xmlNode, true))
                            return false;

                        var xpath = model.$propBinds[sUId[1]][sUId[2]].listen; //root
                        var node = xpath
                            ? apf.queryNode(model.data, xpath)
                            : xmlNode;
                    }
                    if (node)
                        amlNode.$execProperty(sUId[2], node, args[3]);
                }
            }) - 1;
            this.$listeners[uId] = id;
        }
        else {
            //@todo apf3 potential cleanup problem
            id = "e" + uId;
            if (!this.$listeners[id]) {
                this.$listeners[id] = function(args) {
                    var amlNode = apf.all[uId];
                    if (amlNode)
                        amlNode.$xmlUpdate.apply(amlNode, args);
                };
            }

            
        }

        if (!listen || listen.indexOf(";" + id + ";") == -1)
            xmlNode.setAttribute(this.xmlListenTag, (listen ? listen + id : ";" + id) + ";");

        return xmlNode;
    };
    
    this.addListener = function(xmlNode, callback) {
        var listen = String(xmlNode.getAttribute(this.xmlListenTag) || "");
        var id = this.$listeners.push(callback) - 1;
        
        (callback.ids || (callback.ids = [])).push(id);
        
        if (!listen || listen.indexOf(";" + id + ";") == -1)
            xmlNode.setAttribute(this.xmlListenTag, (listen ? listen + id : ";" + id) + ";");
    };
    
    this.removeListener = function(xmlNode, callback) {
        var listen = xmlNode.getAttribute(this.xmlListenTag);
        var nodes = (listen ? listen.split(";") : []);
    
        var i, lut = {}
        for (i = 0; i < nodes.length; i++) {
            lut[nodes[i]] = 1;
        }
        
        var ids = callback.ids;
        for (i = ids.length; i >= 0; i--) {
            if (lut[ids[i]]) {
                delete this.$listeners[ids[i]];
                delete lut[ids[1]];
            }
        }

        xmlNode.setAttribute(this.xmlListenTag, Object.keys(lut).join(";"));// + ";"
    }

    /*
     * @todo  Use this function when an element really unbinds from a
     *        piece of data and does not uses it for caching
     * @private
     */
    this.removeNodeListener = function(xmlNode, o, id) {
        var listen = xmlNode.getAttribute(this.xmlListenTag);
        var nodes = (listen ? listen.split(";") : []);
        if (id && id.charAt(0) == "p") {
            id = this.$listeners[id];
            delete this.$listeners[id];
        }
        else {
            id = "e" + o.$uniqueId;

            
        }

        for (var newnodes = [], i = 0; i < nodes.length; i++) {
            if (nodes[i] != id)
                newnodes.push(nodes[i]);
        }

        xmlNode.setAttribute(this.xmlListenTag, newnodes.join(";"));// + ";"

        return xmlNode;
    };

    /**
     * Sets the value of a text node. If the node doesn't exist, it is created.
     *
     * Changes are propagated to the databound elements listening for changes
     * on the data changed.
     *
     * @param {XMLElement} pNode     The parent of the text node.
     * @param {String}     value     The value of the text node.
     * @param {String}     [xpath]   The xpath statement which selects the text node.
     * @param {apf.UndoData}    [undoObj] The undo object that is responsible for archiving the changes.
     */
    this.setTextNode =
    apf.setTextNode = function(pNode, value, xpath, undoObj, range) {
        var tNode;

        if (xpath) {
            tNode = pNode.selectSingleNode(xpath);
            if (!tNode)
                return;
            pNode = tNode.nodeType == 1 ? tNode : null;
        }
        if (pNode.nodeType != 1)
            tNode = pNode;
        else if (pNode || !tNode) {
            tNode = pNode.selectSingleNode("text()");

            if (!tNode)
                tNode = pNode.appendChild(pNode.ownerDocument.createTextNode(""));//createCDATASection
        }

        //Action Tracker Support
        if (undoObj && !undoObj.$filled) {
            undoObj.extra.oldValue = tNode.nodeValue;
            undoObj.$filled = true;
        }

        //Apply Changes
        if (range) { //@todo apf3.0 range
            undoObj.extra.range = range;

        }
        else {
            tNode.nodeValue = value;

            if (tNode.$regbase)
                tNode.$setValue(value);
        }

        this.applyChanges("text", tNode.parentNode, undoObj);

        
        this.applyRDB(["setTextNode", pNode, value, xpath], undoObj || {xmlNode: pNode}); //@todo apf3.0 for range support
        
    };

    /**
     * Sets an attribute on a node. Changes are propagated to the databound
     * elements listening for changes on the data changed.
     *
     * @param {XMLElement} xmlNode   The XML node to set the attribute on.
     * @param {String}     name      The name of the attribute.
     * @param {String}     value     The value of the attribute.
     * @param {String}     [xpath]   The xpath statement to select the attribute.
     * @param {apf.UndoData}    [undoObj] The undo object that is responsible for archiving the changes.
     */
    this.setAttribute =
    apf.setAttribute = function(xmlNode, name, value, xpath, undoObj, range) {
        //Action Tracker Support
        if (undoObj && !undoObj.$filled) {
            undoObj.name = name;
            undoObj.$filled = true;
        }

        //Apply Changes
        if (range) { //@todo apf3.0 range
            undoObj.extra.range = range;
        }
        else
            (xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).setAttribute(name, value);

        this.applyChanges("attribute", xmlNode, undoObj);
        
        this.applyRDB(["setAttribute", xmlNode, name, value, xpath], undoObj || {xmlNode: xmlNode});  //@todo apf3.0 for range support
        
    };

    /**
     * Removes an attribute of an XML node. Changes are propagated to the
     * databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} xmlNode   The XML node to delete the attribute from
     * @param {String}     name      The name of the attribute.
     * @param {String}     [xpath]   The xpath statement to select the attribute.
     * @param {apf.UndoData}    [undoObj] The undo object that is responsible for archiving the changes.
     */
    this.removeAttribute =
    apf.removeAttribute = function(xmlNode, name, xpath, undoObj) {
        //if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;

        //Action Tracker Support
        if (undoObj && !undoObj.$filled) {
            undoObj.name = name;
            undoObj.$filled = true;
        }

        //Apply Changes
        (xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).removeAttribute(name);
        this.applyChanges("attribute", xmlNode, undoObj);

        
        this.applyRDB(["removeAttribute", xmlNode, name, xpath], undoObj || {xmlNode: xmlNode});
        
    };

    /**
     * Replace one node with another. Changes are propagated to the
     * databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} oldNode   The XML node to remove.
     * @param {XMLElement} newNode   The XML node to set.
     * @param {String}     [xpath]   The xpath statement to select the attribute.
     * @param {apf.UndoData}    [undoObj] The undo object that is responsible for archiving the changes.
     */
    this.replaceNode =
    apf.replaceNode = function(newNode, oldNode, xpath, undoObj) {
        //if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;

        //Apply Changes
        if (xpath)
            oldNode = oldNode.selectSingleNode(xpath);

        // @todo: only do this once! - should store on the undo object
        if (oldNode.ownerDocument.importNode && newNode.ownerDocument != oldNode.ownerDocument) {
            var oldNodeS = newNode;
            newNode = oldNode.ownerDocument.importNode(newNode, true); //Safari issue not auto importing nodes
            if (oldNodeS.parentNode)
                oldNodeS.parentNode.removeChild(oldNodeS);
        }

        
        this.applyRDB(["replaceNode", oldNode, this.cleanXml(newNode.xml), xpath], undoObj || {xmlNode: oldNode});
        

        //Action Tracker Support
        if (undoObj && !undoObj.$filled) {
            undoObj.$filled = true;
            undoObj.oldNode = oldNode;
            undoObj.xmlNode = newNode;
        }

        this.cleanNode(newNode);

        var parentNode = oldNode.parentNode;
        if (!parentNode)
            return;

        parentNode.replaceChild(newNode, oldNode);
        this.copyConnections(oldNode, newNode);

        this.applyChanges("replacenode", newNode, undoObj);

        return newNode;
    };

    /**
     * Creates a new element under a parent XML node. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} pNode       The parent XML node to add the new element to.
     * @param {String}     tagName     The tagName of the {@link term.datanode data node} to add.
     * @param {Array}      attr        list of the attributes to set. Each item is another array with the name and value.
     * @param {XMLElement} beforeNode  The XML node which indicates the insertion point.
     * @param {String}     [xpath]     The xpath statement to select the attribute.
     * @param {apf.UndoData}    [undoObj]   The undo object that is responsible for archiving the changes.
     */
    this.addChildNode =
    apf.addChildNode = function(pNode, tagName, attr, beforeNode, undoObj) {
        //Create New Node
        var xmlNode = pNode.insertBefore(pNode.ownerDocument
            .createElement(tagName), beforeNode);

        //Set Attributes
        for (var i = 0; i < attr.length; i++)
            xmlNode.setAttribute(attr[i][0], attr[i][1]);

        //Action Tracker Support
        if (undoObj && !undoObj.$filled) {
            undoObj.extra.addedNode = xmlNode;
            undoObj.$filled = true;
        }

        this.applyChanges("add", xmlNode, undoObj);

        
        this.applyRDB(["addChildNode", pNode, tagName, attr, beforeNode], undoObj || {xmlNode: pNode});
        

        return xmlNode;
    };

    /**
     * Appends an XML node to a parent. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} pNode       The parent XML node to add the element to.
     * @param {XMLElement} xmlNode     The XML node to insert.
     * @param {XMLElement} beforeNode  The XML node which indicates the insertion point.
     * @param {Boolean}    unique      Specifies whether the parent can only contain one element with a certain tag name.
     * @param {String}     [xpath]     The xpath statement to select the parent node.
     * @param {apf.UndoData}    [undoObj]   The undo object that is responsible for archiving the changes.
     */
    this.appendChild =
    apf.appendChild = function(pNode, xmlNode, beforeNode, unique, xpath, undoObj) {
        if (pNode == xmlNode.parentNode) //Shouldn't this be the same document?
            return apf.xmldb.moveNode(pNode, xmlNode, beforeNode, null, xpath, undoObj);

        if (unique && pNode.selectSingleNode(xmlNode.tagName))
            return false;

        // @todo: only do this once! - should store on the undo object
        if (pNode.ownerDocument.importNode && pNode.ownerDocument != xmlNode.ownerDocument) {
            var oldNode = xmlNode;
            xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes
            if (oldNode.parentNode)
                oldNode.parentNode.removeChild(oldNode);
        }

        
        this.applyRDB(["appendChild", pNode, this.cleanXml(xmlNode.xml), beforeNode, unique, xpath], undoObj || {xmlNode: pNode});
        

        //Add xmlNode to parent pNode or one selected by xpath statement
        if (xpath) {
            var addedNodes = [];
            pNode = apf.createNodeFromXpath(pNode, xpath, addedNodes);
            if (addedNodes.length) {
                pNode.appendChild(xmlNode);
                while (addedNodes.length) {
                    if (pNode == addedNodes.pop() && addedNodes.length)
                        pNode = pNode.parentNode;
                }
            }
        }
        else if (xmlNode.parentNode)
            this.removeNode(xmlNode);

        if (undoObj && !undoObj.$filled) {
            undoObj.$filled = true;
            this.cleanNode(xmlNode);
        }
        else
            this.cleanNode(xmlNode);

        pNode.insertBefore(xmlNode, beforeNode);

        //detect if xmlNode should be removed somewhere else
        //- [17-2-2004] changed pNode (2nd arg applychange) into xmlNode

        this.applyChanges("add", xmlNode, undoObj);

        return xmlNode;
    };

    /**
     * Moves an XML node to a parent node. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} pNode       The new parent XML node of the node.
     * @param {XMLElement} xmlNode     The XML node to move.
     * @param {XMLElement} beforeNode  The XML node which indicates the insertion point.
     * @param {String}     [xpath]     The xpath statement to select the parent node.
     * @param {apf.UndoData}    [undoObj]   The undo object that is responsible for archiving the changes.
     */
    this.moveNode =
    apf.moveNode = function(pNode, xmlNode, beforeNode, xpath, undoObj) {
        //Action Tracker Support
        if (!undoObj)
            undoObj = {extra:{}};

        undoObj.extra.oldParent = xmlNode.parentNode;
        undoObj.extra.beforeNode = xmlNode.nextSibling;
        undoObj.extra.parent = (xpath ? pNode.selectSingleNode(xpath) : pNode);

        this.applyChanges("move-away", xmlNode, undoObj);

        //Set new id if the node change document (for safari this should be fixed)
        //@todo I don't get this if...
        /*if (!apf.isWebkit
          && xmlNode.getAttribute(this.xmlIdTag)
          && apf.xmldb.getXmlDocId(xmlNode) != apf.xmldb.getXmlDocId(pNode)) {
            xmlNode.removeAttribute(this.xmlIdTag));
            this.nodeConnect(apf.xmldb.getXmlDocId(pNode), xmlNode);
        }*/

        // @todo: only do this once! - should store on the undo object
        if (pNode.ownerDocument.importNode && pNode.ownerDocument != xmlNode.ownerDocument) {
            var oldNode = xmlNode;
            xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes
            if (oldNode.parentNode)
                oldNode.parentNode.removeChild(oldNode);
        }

        
        this.applyRDB(["moveNode", pNode, xmlNode, beforeNode, xpath], undoObj || {xmlNode: pNode}); //note: important that transport of rdb is async
        

        undoObj.extra.parent.insertBefore(xmlNode, beforeNode);
        this.applyChanges("move", xmlNode, undoObj);
    };

    /**
     * Removes an XML node from its parent. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {XMLElement} xmlNode     The XML node to remove from the dom tree.
     * @param {String}     [xpath]     The xpath statement to select the parent node.
     * @param {apf.UndoData}    [undoObj]   The undo object that is responsible for archiving the changes.
     */
    this.removeNode =
    apf.removeNode = function(xmlNode, xpath, undoObj) {
        if (xpath)
            xmlNode = xmlNode.selectSingleNode(xpath);

        //ActionTracker Support
        if (undoObj && !undoObj.$filled) {
            undoObj.$filled = true;
            undoObj.extra.parent = xmlNode.parentNode;
            undoObj.extra.removedNode = xmlNode;
            undoObj.extra.beforeNode = xmlNode.nextSibling;
        }

        
        this.applyRDB(["removeNode", xmlNode, xpath], undoObj || {xmlNode: xmlNode}); //note: important that transport of rdb is async
        

        //Apply Changes
        this.applyChanges("remove", xmlNode, undoObj);
        var p = xmlNode.parentNode;
        if (!p)
            return;

        p.removeChild(xmlNode);
        this.applyChanges("redo-remove", xmlNode, null, p);//undoObj

        //@todo clean xmlNode after removal??
    };

    /**
     * Removes a list of XML nodes from their parent. Changes are propagated
     * to the databound elements listening for changes on the data changed.
     *
     * @param {Array}   xmlNodeList A list of XML nodes to remove.
     * @param {apf.UndoData} [undoObj]   The undo object that is responsible for archiving the changes.
     */
    this.removeNodeList =
    apf.removeNodeList = function(xmlNodeList, undoObj) {
        
        this.applyRDB(["removeNodeList", xmlNodeList, null], undoObj || {xmlNode: p});
        

        //if(xpath) xmlNode = xmlNode.selectSingleNode(xpath);
        for (var rData = [], i = 0; i < xmlNodeList.length; i++) { //This can be optimized by looping nearer to xmlUpdate
            //ActionTracker Support
            if (undoObj) {
                rData.push({
                    pNode: xmlNodeList[i].parentNode,
                    removedNode: xmlNodeList[i],
                    beforeNode: xmlNodeList[i].nextSibling
                });
            }

            //Apply Changes
            this.applyChanges("remove", xmlNodeList[i], undoObj);
            var p = xmlNodeList[i].parentNode;
            p.removeChild(xmlNodeList[i]);
            this.applyChanges("redo-remove", xmlNodeList[i], null, p);//undoObj
        }

        if (undoObj && !undoObj.$filled) {
            undoObj.$filled = true;
            undoObj.extra.removeList = rData;
        }
    };

    /*
     * Looks for this.$listeners and executes their $xmlUpdate methods.
     * @private
     */
    var notifyQueue = {}, notifyTimer;
    this.$hasQueue = false;
    this.applyChanges = function(action, xmlNode, undoObj, nextloop) {
        if (undoObj && undoObj.$dontapply) return;
        

        if (undoObj && !undoObj.xmlNode) //@todo are we sure about this?
            undoObj.xmlNode = xmlNode;

        //Set Variables
        var oParent = nextloop,
            loopNode = (xmlNode.nodeType == 1 ? xmlNode : xmlNode.parentNode);

        //var xmlId = xmlNode.getAttribute(this.xmlIdTag);

        if (!this.delayUpdate && "|remove|move-away|".indexOf("|" + action + "|") > -1)
            this.notifyQueued(); //empty queue

        var listen, uId, uIds, i, j, hash, info, amlNode, runTimer, found, done = {};
        while (loopNode && loopNode.nodeType == 1) {
            //Get List of Node this.$listeners ID's
            listen = loopNode.getAttribute(this.xmlListenTag);

            if (listen) {
                uIds = listen.split(";");

                for (i = 0; i < uIds.length; i++) {
                    uId = uIds[i];
                    if (!uId || done[uId]) continue;
                    done[uId] = true;

                    //Property support
                    /*if (uId.charAt(0) == "p") {
                        uId = uId.split("|");

                        //@todo apf3.0 should this be exactly like in class.js?
                        //@todo optimize this to check the async flag: parsed[3] & 4

                        amlNode = apf.all[uId[1]]; //It's possible the aml node dissapeared in this loop.
                        if (amlNode) {
                            var model = apf.all[uId[3]];
                            var xpath = model.$propBinds[uId[1]][uId[2]].root;

                            amlNode.$execProperty(uId[2], xpath
                                ? model.data.selectSingleNode(xpath)
                                : model.data);
                        }
                        continue;
                    }*/

                    hash = notifyQueue[uId];
                    if (!hash)
                        notifyQueue[uId] = hash = [];

                    // Filtering
                    if (!apf.isO3 && "|update|attribute|text|".indexOf("|" + action + "|") > -1) {
                        found = false;
                        for (j = 0; j < hash.length; j++) {
                            if (hash[j] && xmlNode == hash[j][1]
                              && "|update|attribute|text|"
                              .indexOf("|" + hash[j][0] + "|") > -1) {
                                hash[j] = null;
                                found = true;
                                continue;
                            }
                        }

                        hash.push([action, xmlNode, loopNode, undoObj, oParent]);
                        runTimer = true;
                        continue;
                    }

                    //!this.delayUpdate && <- that doesnt work because of information that is destroyed
                    if (apf.isO3 || "|remove|move-away|move|add|".indexOf("|" + action + "|") > -1) {
                        if (this.$listeners[uId]) {
                            this.$listeners[uId]([action, xmlNode,
                                loopNode, undoObj, oParent]);
                        }
                        /*amlNode = apf.all[uId];
                        if (amlNode)
                            amlNode.$xmlUpdate(action, xmlNode,
                                loopNode, undoObj, oParent);*/
                    }
                    else {
                        hash.push([action, xmlNode, loopNode, undoObj, oParent]);
                        runTimer = true;
                    }
                }
            }

            //Go one level up
            loopNode = loopNode.parentNode || nextloop;
            if (loopNode == nextloop)
                nextloop = null;
        }

        if (true || undoObj && !this.delayUpdate) {
            //Ok this was an action let's not delay execution
            apf.xmldb.notifyQueued();
        }
        else if (runTimer) {
            apf.setZeroTimeout.clearTimeout(notifyTimer);
            //@todo find a better solution for this (at the end of a event stack unroll)
            this.$hasQueue = true;
            notifyTimer = apf.setZeroTimeout(function(){
                //this.$hasQueue = true;
                apf.xmldb.notifyQueued();
            });
        }
    };

    /*
     *  @todo in actiontracker - add stack auto purging
     *        - when undo item is purged which was a removed, remove cache item
     *  @todo shouldn't the removeNode method remove all this.$listeners?
     *  @todo rename to processQueue
     *  @private
     */
    this.notifyQueued = function(){
        this.$hasQueue = false;

        var myQueue = notifyQueue;
        notifyQueue = {};

        apf.setZeroTimeout.clearTimeout(notifyTimer);
        for (var uId in myQueue) {
            if (!uId) continue;

            var q = myQueue[uId];
            var func = this.$listeners[uId];
            //!amlNode ||
            if (!q || !func)
                continue;

            //Run queue items
            for (var i = 0; i < q.length; i++) {
                if (!q[i])
                    continue;

                //Update xml data
                //amlNode.$xmlUpdate.apply(amlNode, q[i]);
                func(q[i]);
            }
        }


    };

    /**
     * @private
     */
    this.notifyListeners = function(xmlNode) {
        //This should be done recursive
        var listen = xmlNode.getAttribute(apf.xmldb.xmlListenTag);
        if (listen) {
            listen = listen.split(";");
            for (var j = 0; j < listen.length; j++) {
                apf.all[listen[j]].$xmlUpdate("synchronize", xmlNode, xmlNode);
                //load(xmlNode);
            }
        }
    };

    
    /*
     * Sends Message through transport to tell remote databound this.$listeners
     * that data has been changed
     * @private
     */
    this.applyRDB = function(args, undoObj) {
        return;

        var xmlNode = undoObj.localName || !undoObj.xmlNode
            ? args[1] && args[1].length && args[1][0] || args[1]
            : undoObj.xmlNode;

        if (xmlNode.nodeType == 2)
            xmlNode = xmlNode.ownerElement || xmlNode.selectSingleNode("..");
        
        var mdlId = apf.xmldb.getXmlDocId(xmlNode),
            model = apf.nameserver.get("model", mdlId);
        if (!model && apf.isO3)
            model = self[mdlId];
        if (!model) {
            if (!apf.nameserver.getAll("remote").length)
                return;
            
            return;
        }

        if (!model.rdb) return;
        var rdb = model.rdb;

        // Add the messages to the undo object
        if (undoObj.action)
            rdb.$queueMessage(args, model, undoObj);
        // Or send message now
        else {
            clearTimeout(rdb.queueTimer);

            rdb.$queueMessage(args, model, rdb);
            // use a timeout to batch consecutive calls into one RDB call
            rdb.queueTimer = $setTimeout(function() {
                rdb.$processQueue(rdb);
            });
        }
        
    };
    

    /**
     * @private
     */
    this.copyConnections = function(fromNode, toNode) {
        //This should copy recursive
        try {
            toNode.setAttribute(this.xmlListenTag, fromNode.getAttribute(this.xmlListenTag));
        }
        catch (e) {}
        try {
            toNode.setAttribute(this.xmlIdTag, fromNode.getAttribute(this.xmlIdTag));
        }
        catch (e) {}
    };

    /**
     * @private
     */
    this.cleanXml = function(xml) {
        if (typeof xml != "string")
            return xml;
        return xml.replace(cleanRE, "").replace(whiteRE, "><");
    };

    /**
     * @private
     */
    this.cleanNode = function(xmlNode) {
        try {
            var i, nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlListenTag + "]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute(this.xmlListenTag);
            nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlIdTag + "]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute(this.xmlIdTag);
            nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlDocTag + "]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute(this.xmlDocTag);
            nodes = xmlNode.selectNodes("descendant-or-self::node()[@a_loaded]");
            for (i = nodes.length - 1; i >= 0; i--)
                nodes[i].removeAttribute("a_loaded");
        }
        catch (e) {}

        return xmlNode;
    };

    /**
     * Returns a copy of the passed {@link term.datanode data node}. Bound
     * data nodes contain special attributes to track them. These attributes
     * are removed from the copied node when using this method.
     *
     * @param {XMLElement} xmlNode The {@link term.datanode data node} to copy.
     * @return {XMLElement} The copy of the {@link term.datanode data node}.
     */
    this.copy = 
    this.getCleanCopy =
    apf.getCleanCopy = function(xmlNode) {
        return apf.xmldb.cleanNode(xmlNode.cloneNode(true));
    };

    /**
     * Unbind all APF Elements from a certain Form
     * @private
     */
    this.unbind = function(frm) {
        //Loop through objects of all apf
        for (var lookup = {}, i = 0; i < frm.apf.all.length; i++)
            if (frm.apf.all[i] && frm.apf.all[i].unloadBindings)
                lookup[frm.apf.all[i].unloadBindings()] = true;

        //Remove Listen Nodes
        for (var k = 0; k < this.$xmlDocLut.length; k++) {
            
            if (!this.$xmlDocLut[k]) continue;
            

            var Nodes = this.$xmlDocLut[k].selectNodes("//self::node()[@"
                + this.xmlListenTag + "]");
            if (!Nodes) continue;

            //Loop through Nodes and rebuild listen array
            for (var i = 0; i < Nodes.length; i++) {
                var listen = Nodes[i].getAttribute(this.xmlListenTag).split(";");
                for (var nListen = [], j = 0; j < listen.length; j++)
                    if (!lookup[listen[j]])
                        nListen.push(listen[j]);

                //Optimization??
                if (nListen.length != listen.length)
                    Nodes[i].setAttribute(this.xmlListenTag, nListen.join(";"));
            }
        }

        if (window.clearInterval)
            window.clearInterval(this.$gcInterval);
    };

    /*
     * @private
     * @todo xml doc leakage
     */
    this.getXmlDocId = function(xmlNode, model) {
        var docEl = xmlNode.ownerDocument.documentElement;
        if (!apf.isChildOf(docEl, xmlNode))
            docEl = xmlNode;

        var docId = (docEl || xmlNode).getAttribute(this.xmlDocTag)
            || this.$xmlDocLut.indexOf(docEl || xmlNode.ownerDocument || xmlNode);

        if (model && apf.nameserver.get("model", docId) != model) {
            docId = null;
            docEl = xmlNode;
        }

        if (!docId || docId == -1) {
            docId = this.$xmlDocLut.push(docEl || xmlNode.ownerDocument || xmlNode) - 1;
            if (docEl)
                docEl.setAttribute(this.xmlDocTag, String(docId));
        }
        
        if (model)
            apf.nameserver.register("model", docId, model);
        

        return docId;
    };
});






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
 




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @class apf.http
 *
 * This object does what is commonly known as Ajax; it **A**synchronously 
 * communicates using **J**avaScript, **A**nd in most 
 * cases it sends or receives **X**ml. It allows for easy HTTP 
 * communication from within the browser. 
 *
 * This object provides caching on top of
 * the browser's cache. This enables you to optimize your application, because
 * this can be set on a per call basis. 
 *
 * #### Example:
 *
 * Retrieving content over HTTP synchronously:
 *
 * ```javascript
 *  var http = new apf.http();
 *  var data = http.get("http://www.example.com/mydata.jsp", {async: false});
 *  alert(data);
 * ```
 *
 * #### Example:
 *
 * Retrieving content over HTTP asynchronously:
 *
 * ```javascript
 *  var http = new apf.http();
 *  http.get("http://www.example.com/mydata.jsp", {
 *      callback: function(data, state, extra) {
 *         if (state != apf.SUCCESS)
 *             return alert('an error has occurred');
 *
 *         alert(data);
 *      }
 *  });
 * ```
 *
 * #### Example:
 *
 * An asynchronous HTTP request, with retry:
 *
 * ```javascript
 *  var http = new apf.http();
 *  http.get("http://www.example.com/mydata.jsp", {
 *      callback: function(data, state, extra) {
 *          if (state != apf.SUCCESS) {
 *              var oError = new Error(apf.formatErrorString(0, null,
 *                  "While loading data", "Could not load data\n" + extra.message));
 *
 *              if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
 *                  return true;
 *
 *              throw oError;
 *          }
 *
 *          alert(data);
 *      }
 *  });
 * ```
 */
/**
 * @event error Fires when a communication error occurs.
 * @bubbles
 * @cancelable  Prevents a communication error to be thrown.
 * @param {Object} e An object returned by the callback. It contains the following properties:
 *     - error ([[Error]]): The error object that is thrown when the event
 *                                callback doesn't return false.
 *     - state ([[Number]]): The state of the call. Possible values include:
 *       - `apf.SUCCESS`:  The request was successfull
 *       - `apf.TIMEOUT`:  The request has timed out.
 *       - `apf.ERROR`:    An error has occurred while making the request.
 *       - `apf.OFFLINE`:  The request was made while the application was offline.
 *     - userdata (`Mixed`): Data that the caller wanted to be available in
 *                                the callback of the HTTP request.
 *     - http ([[XMLHttpRequest]]): The object that executed the actual HTTP request.
 *     - url ([[String]]): The URL that was requested.
 *     - tpModule ([[apf.http]]): The teleport module that is making the request.
 *     - id ([[Number]]): The id of the request.
 *     - message ([[String]]): The error message.
 *
 * @define http
 * @default_private
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.http = function(){
    this.queue = [null];
    this.callbacks = {};
    this.cache = {};

    /**
     * Sets the timeout of http requests in milliseconds. Default is 10000ms (10s).
     * @type {Number}
     */
    this.timeout = this.timeout || 10000; //default 10 seconds
    
    /**
     * Sets whether this element routes traffic through a server proxy.
     *
     * #### Remarks
     *
     * This can also be set on a per call basis. See {@link apf.http.get}.
     *
     * 
     * @type {Boolean}
     */
    this.autoroute = this.autoroute || false;
    
    /**
     * String specifying the URL to the route script. 
     * 
     * #### Remarks
     *
     * The route script will receive the route information in three extra headers:
     *   - `X-Route-Request`     : Contains the destination URL
     *   - `X-Proxy-Request`     : Contains the proxy URL
     *   - `X-Compress-Response` : Set to 'gzip'
     *
     * @type {String}
     */
    this["route-server"] = this["route-server"] || null;

    if (!this.$uniqueId)
        this.$uniqueId = apf.all.push(this) - 1;

    this.toString = this.toString || function(){
        return "[Ajax.org Teleport Component : (HTTP)]";
    };

    

    /**
     * Makes an HTTP request that receives XML.
     * @param {String}   url       The url that is accessed.
     * @param {Object}   options   The options for the HTTP request. It contains the following properties:
     *   - async ([[Boolean]]): Specifies whether the request is sent asynchronously. Defaults to true.
     *   - userdata (`Mixed`): custom data that is available to the callback function.
     *   - method ([[String]]): The request method (`POST`|`GET`|`PUT`|`DELETE`). Defaults to `GET`.
     *   - nocache ([[Boolean]]): Specifies whether browser caching is prevented.
     *   - data ([[String]]): the data sent in the body of the message.
     *   - autoroute ([[Boolean]]): Specifies whether the request can fallback to a server proxy.
     *   - caching ([[Boolean]]): Specifies whether the request should use internal caching.
     *   - ignoreOffline ([[Boolean]]): Specifies whether to ignore offline catching.
     *   - callback ([[Function]]): The handler that gets called whenever the
     *                            request completes successfully or with an error,
     *                            or when the request times out.
     */
    this.getXml = function(url, callback, options) {
        if (!options) options = {};
        options.useXML = true;
        options.callback = callback;
        return this.get(url, options);
    };
    
    this.getJSON = function(url, callback, options) {
        if (!options) options = {};
        options.callback = callback;
        options.useJSON = true;
        return this.get(url, options);
    };

    /**
     * Makes an HTTP request.
     * @param {String}   url       The URL that is accessed.
     * @param {Object}   options   The options for the HTTP request. It contains the following properties:
     *   - async ([[Boolean]]): Specifies whether the request is sent asynchronously. Defaults to true.
     *   - userdata (`Mixed`): Custom data that is available to the callback function.
     *   - method ([[String]]): The request method (POST|GET|PUT|DELETE). Defaults to GET.
     *   - nocache ([[Boolean]]): Specifies whether browser caching is prevented.
     *   - data ([[String]]): The data sent in the body of the message.
     *   - useXML ([[Boolean]]): Specifies whether the result should be interpreted as xml.
     *   - autoroute ([[Boolean]]): Specifies whether the request can fallback to a server proxy.
     *   - caching ([[Boolean]]): Specifies whether the request should use internal caching.
     *   - ignoreOffline ([[Boolean]]): Specifies whether to ignore offline catching.
     *   - contentType ([[String]]): The mime type of the message
     *   - withCredentials ([[Boolean]]): Value of the withCredentials field for CORS requests
     *   - callback ([[Function]]): The handler that gets called whenever the
     *                            request completes successfully or with an error,
     *                            or when the request times out.
     */
    this.get = this.$get = function(url, options) {
        if (!options)
            options = {};

        var _self = this;
        var id = options.id;
        
        
        

        var binary = apf.hasXhrBinary && options.binary;
        var async = options.async = (options.async || binary 
            || typeof options.async == "undefined" || apf.isOpera || false);

        
        if (apf.isWebkit)
            url = apf.html_entity_decode(url);
        

        var data = options.data || "";

        if (apf.isNot(id)) {
            
                var http = apf.getHttpReq();

            id = this.queue.push({
                http: http,
                url: url,
                callback: options.callback,
                retries: 0,
                options: options
            }) - 1;

            
        }
        else {
            var http = this.queue[id].http;

            
                http.abort();
        }

        if (async) {
            
            {
                http.onreadystatechange = function(){
                    if (!_self.queue[id] || http.readyState != 4)
                        return;
                    if (async && arguments.callee.caller)
                        $setTimeout(function(){_self.receive(id)});
                    else
                        _self.receive(id);
                }
            }
        }

        var autoroute = this.autoroute && apf.isOpera
            ? true //Bug in opera
            : (options.autoroute || this.shouldAutoroute),
            httpUrl = autoroute ? this["route-server"] : url;

        
        var headers = [];
        
        function setRequestHeader(name, value) {
            
            http.setRequestHeader(name, value);
        }

        var errorFound = false;
        try {
            if (options.nocache)
                httpUrl = apf.getNoCacheUrl(httpUrl);

            
            
            var requestedWithParam = apf.config ? apf.config["requested-with-getparam"] : null;
            if (requestedWithParam) {
                httpUrl += (httpUrl.indexOf("?") == -1 ? "?" : "&") +
                    encodeURIComponent(requestedWithParam) + "=1";
            }
            // global support for protection against Cross Site Request Forgery
            // attacks by supplying a token to the global APF config object. This
            // token will be appended to the URL and sent for each XHR.
            // Warning: if you are doing CORS, be sure to use a different method!
            var CSRFHeader = apf.config ? apf.config["csrf-header"] : null;
            var CSRFToken = apf.config ? apf.config["csrf-token"] : null;
            if (CSRFHeader) {
                setRequestHeader("X-CSRF-Token", CSRFHeader);
            }
            else if (CSRFToken) {
                CSRFToken = CSRFToken.split("=").map(function(s) { return encodeURIComponent(s); }).join("=");
                httpUrl += (httpUrl.indexOf("?") == -1 ? "?" : "&") + CSRFToken;
            }

            var withCredentials = false;
            if ("withCredentials" in options) {
                withCredentials = options.withCredentials;
            }
            else {
                withCredentials = (apf.config && apf.config["cors-with-credentials"]) || false;
            }

            http.withCredentials = withCredentials;
            http.open(this.method || options.method || "GET", httpUrl, async);

            if (options.username) {
                setRequestHeader("Authorization", "Basic "
                    + apf.crypto.Base64.encode(options.username + ":" + options.password))
            }

            //@todo OPERA ERROR's here... on retry [is this still applicable?]
            if (!requestedWithParam)
                setRequestHeader("X-Requested-With", "XMLHttpRequest");

            if (!options.headers || !options.headers["Content-type"])
                setRequestHeader("Content-type", options.contentType || this.contentType
                    || (this.useXML || options.useXML ? "text/xml" : "text/plain"));

            if (autoroute) {
                setRequestHeader("X-Route-Request", url);
                setRequestHeader("X-Proxy-Request", url);
                setRequestHeader("X-Compress-Response", "gzip");
            }
            
            if (binary) {
                setRequestHeader("Cache-Control", "no-cache");
                setRequestHeader("X-File-Name", binary.filename);
                setRequestHeader("X-File-Size", binary.filesize);
                setRequestHeader("Content-Type", "application/octet-stream");
            }
        }
        catch (e) {
            errorFound = e.message;
        }

        if (errorFound) {
            var useOtherXH = false;

            // Retry request by routing it
            if (!useOtherXH && this.autoroute && !autoroute) {
                

                this.shouldAutoroute = true;

                options.autoroute = true;
                return this.get(url, options);
            }

            if (!useOtherXH) {
                //Routing didn't work either... Throwing error
                var noClear = options.callback ? options.callback(null, apf.ERROR, {
                    userdata: options.userdata,
                    http: http,
                    url: url,
                    tpModule: this,
                    id: id,
                    message: "Permission denied accessing remote resource: "
                              + url + "\nMessage: " + errorFound
                }) : false;
                if (!noClear)
                    this.clearQueueItem(id);

                return;
            }
        }

        if (this.$headerHook)
            this.$headerHook(http);

        //Set request headers
        if (options.headers) {
            for (var name in options.headers)
                setRequestHeader(name, options.headers[name]);
        }
        
        

        function handleError(){
            var msg = self.navigator && self.navigator.onLine
                ? "File or Resource not available " + url
                : "Browser is currently working offline";

            

            var state = self.navigator && navigator.onLine
                ? apf.ERROR
                : apf.TIMEOUT;

            // File not found
            var noClear = options.callback ? options.callback(null, state, {
                userdata: options.userdata,
                http: http,
                url: url,
                tpModule: _self,
                id: id,
                message: msg
            }) : false;
            if (!noClear)
                _self.clearQueueItem(id);
        }

        function send(isLocal) {
            var hasError;

            if (apf.isIE && isLocal) { //When local IE calls onreadystate immediately
                var oldWinOnerror = window.onerror;
                window.onerror = function(){
                    if (arguments.caller && arguments.caller.callee == send) {
                        window.onerror = oldWinOnerror;
                        //_self.receive(id);
                        //setTimeout(function(){handleError();});
                        return true;
                    }
                    else {
                        window.onerror = oldWinOnerror;
                        
                        if (oldWinOnerror)
                            return oldWinOnerror.apply(window, arguments);
                    }
                }
                http.send(data);
                window.onerror = oldWinOnerror;
            }
            else {
                try {
                    if (binary && http.sendAsBinary) {
                        binary.blob = getBinaryBlob(data, http, binary);
                        http.sendAsBinary(binary.blob.data);
                    }
                    else
                        http.send(data);
                }
                catch (e) {
                    hasError = true;
                }
            }

            if (hasError) {
                handleError();
                return;
            }
            else if (binary && http.upload) {
                http.upload.onprogress = function(e) {
                    apf.dispatchEvent("http.uploadprogress", {
                        loaded: e.loaded - binary.blob.size,
                        extra: e,
                        bubbles: true
                    });
                };
            }
        }

        if (!async) {
            send.call(this);
            return this.receive(id);
        }
        else {
            if (apf.loadsLocalFilesSync && location.protocol == "file:"
              && url.indexOf("http://") == -1) {
                $setTimeout(function(){
                    send.call(_self, true);
                });
            }
            else
                send.call(_self);

            return id;
        }
    };
    
    
    if (!this.exec) {
    /**
             * A method that all async objects should implement.
             *
     * @private
     */
        this.exec = function(method, args, callback, options) {
            if (!options)
                options = {};
            
            var url = args[0], query = "";
            if (!options.method)
                options.method = method.toUpperCase();
            if (!options.callback)
                options.callback = callback;
            
            this.contentType = "application/x-www-form-urlencoded";
            this.$get(
                apf.getAbsolutePath(apf.config.baseurl, url), 
                options.method == "GET" 
                    ? options 
                    : apf.extend({data : query}, options)
            );
        }
    }
    
    
    /**
     * Sends the binary blob to the server, and multipart encodes it if needed. This code 
     * will only be executed on Gecko since it's currently the only browser that 
     * supports direct file access.
     * @private
     */
    function getBinaryBlob(data, http, binary) {
        var boundary = "----apfbound".appendRandomNumber(5),
            dashdash = "--",
            crlf = "\r\n",
            multipartBlob = "",
            multipartSize = 0;

        // Build multipart request
        if (binary.multipart) {
            http.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
            // Build RFC2388 blob
            multipartBlob += dashdash + boundary + crlf +
                'Content-Disposition: form-data; name="' + (binary.filedataname || binary.filename)
                    + '"; filename="' + binary.filename + '"' + crlf +
                'Content-Type: application/octet-stream' + crlf + crlf +
                data + crlf +
                dashdash + boundary + dashdash + crlf;

            multipartSize = multipartBlob.length - data.length;
            data = multipartBlob;
        }
        // Send blob or multipart blob depending on config
        return {size: multipartSize, data: data};
    }

    /**
     * @private
     */
    this.receive = function(id) {
        if (!this.queue[id])
            return false;

        var qItem = this.queue[id],
            http = qItem.http,
            callback = qItem.callback;
        //if (apf.isGecko)
        //    var apf = self.apf || apf;     // needed here to fix a rare ReferenceError in FF

        

        /*if (self.navigator && navigator.onLine === false &&
          (location.protocol != "file:"
          || qItem.url.indexOf("http://") > -1))
            return false;*/

        // Test if HTTP object is ready
        if (qItem.async) {
            try {
                if (http.status) {}
            }
            catch (e) {
                var _self = this;
                return $setTimeout(function(){
                    _self.receive(id)
                }, 10);
            }
        }
        
        

        //Gonna check for validity of the http response
        var errorMessage = [],
            extra = {
                
                tpModule: this,
                http: http,
                status: http.status,
                url: qItem.url,
                callback: callback,
                id: id,
                retries: qItem.retries || 0,
                userdata: qItem.options.userdata
            };

        // Check HTTP Status
        // The message didn't receive the server. We consider this a timeout (i.e. 12027)
        if (http.status > 600)
            return this.$timeout(id);

        extra.data = qItem.options.useJSON 
            ? eval("(" + http.responseText + ")") 
            : http.responseText; //Can this error?

        if (http.status >= 400 && http.status < 600 || http.status < 10 
          && (http.status != 0 || !apf.isIE && !http.responseText)) { //qItem.url.substr(0, 6) == "file:/"
            
            //@todo This should probably have an RPC specific handler
            if (http.status == 401) {
                var auth = apf.document.getElementsByTagNameNS(apf.ns.apf, "auth")[0];
                if (auth) {
                    var wasDelayed = qItem.isAuthDelayed;
                    qItem.isAuthDelayed = true;
                    if (auth.authRequired(extra, wasDelayed) === true)
                        return;
                }
            }
            

            errorMessage.push("HTTP error [" + id + "]:" + http.status + "\n"
                + http.responseText);
        }

        // Check for XML Errors
        if (qItem.options.useXML || this.useXML) {
            /* Note (Mike, Oct 14th 2008): for WebDAV, I had to copy the lines below,
                                           it required custom responseXML handling/
                                           parsing.
                                           If you alter this code, please correct
                                           webdav.js appropriately.
            */
            if ((http.responseText || "").replace(/^[\s\n\r]+|[\s\n\r]+$/g, "") == "")
                errorMessage.push("Received an empty XML document (0 bytes)");
            else {
                try {
                    var xmlDoc = (http.responseXML && http.responseXML.documentElement)
                        ? apf.xmlParseError(http.responseXML)
                        : apf.getXmlDom(http.responseText);

                    if (!apf.supportNamespaces)
                        xmlDoc.setProperty("SelectionLanguage", "XPath");

                    extra.data = xmlDoc.documentElement;
                }
                catch (e) {
                    errorMessage.push("Received invalid XML\n\n" + e.message);
                }
            }
        }

        //Process errors if there are any
        if (errorMessage.length) {
            extra.message = errorMessage.join("\n");

            

            // Send callback error state
            if (!callback || !callback(extra.data, apf.ERROR, extra))
                this.clearQueueItem(id);

            return;
        }

        

        

        //Http call was successfull Success
        if (!callback || !callback(extra.data, apf.SUCCESS, extra))
            this.clearQueueItem(id);

        return extra.data;
    };

    this.$timeout = function(id) {
        if (!this.queue[id])
            return false;

        var qItem = this.queue[id],
            http = qItem.http;

        

        // Test if HTTP object is ready
        try {
            if (http.status) {}
        }
        catch (e) {
            var _self = this;
            return $setTimeout(function(){
                _self.$timeout(id)
            }, 10);
        }

        var callback = qItem.callback;

        http.abort();

        

        var extra;
        var noClear = callback ? callback(null, apf.TIMEOUT, extra = {
            
            userdata: qItem.options.userdata,
            http: http,
            url: qItem.url,
            tpModule: this,
            id: id,
            message: "HTTP Call timed out",
            retries: qItem.retries || 0
        }) : false;
        
        
        
        if (!noClear)
            this.clearQueueItem(id);
    };

    /**
     * Checks if the request has timed out. If so, it is retried
     * three times before an exception is thrown. Request retrying is a very
     * good way to create a robust Ajax application. In many cases, even with
     * good connections, requests still time out.
     * @param {Object}  extra      The information object given as a third
     *                             argument of the HTTP request callback.
     * @param {Number}  state      The return code of the HTTP request. It contains the following properties:
     *   - `apf.SUCCESS`:  the request was successfull
     *   - `apf.TIMEOUT`:  the request has timed out.
     *   - `apf.ERROR`:    an error occurred while making the request.
     *   - `apf.OFFLINE`:  the request was made while the application was offline.
     * @param {apf.AmlNode} [amlNode]    The element receiving the error event.
     * @param {Error}   [oError]     The error to be thrown when the request is
     *                               not retried.
     * @param {Number}  [maxRetries] The number of retries that are done before
     *                               the request times out. Default is 3.
     */
    this.retryTimeout = function(extra, state, amlNode, oError, maxRetries) {
        if (state == apf.TIMEOUT
          && extra.retries < (maxRetries || apf.maxHttpRetries))
            return extra.tpModule.retry(extra.id);

        oError = oError || new Error(apf.formatErrorString(0,
            this, "Communication " + (state == apf.TIMEOUT
                ? "timeout"
                : "error"), "Url: " + extra.url + "\nInfo: " + extra.message));

        if ((amlNode || apf).dispatchEvent("error", apf.extend({
            error: oError,
            state: state,
            extra: extra,
            bubbles: true
        }, extra)) === false)
            return 2;
    };

    /**
     * Removes the item from the queue. This is usually done automatically.
     * However, when the callback returns `true` the queue isn't cleared; for instance,
     * when a request is retried. The id of the call
     * is found on the `'extra'` object, the third argument of the callback.
     * 
     * #### Example
     *
     * ```javascript
     *  http.clearQueueItem(extra.id);
     * ```
     *
     * @param {Number} id The id of the call that should be removed from the queue.
     */
    this.clearQueueItem = function(id) {
        if (!this.queue[id])
            return false;

        

        if (apf.releaseHTTP && !apf.isGecko)
            apf.releaseHTTP(this.queue[id].http);

        this.queue[id] = null;
        delete this.queue[id];

        return true;
    };

    /**
     * Retries a call based on its id. The id of the call is found on the
     * `'extra'` object, the third argument of the callback.
     * 
     * #### Example
     *
     * ```javascript
     *  function callback(data, state, extra) {
     *      if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries)
     *          return extra.tpModule.retry(extra.id);
     *
     *      //Do stuff here
     *  }
     * ```
     *
     * @param {Number} id The id of the call that should be retried.
     */
    this.retry = function(id) {
        if (!this.queue[id])
            return false;

        var qItem = this.queue[id];

        

        

        qItem.retries++;
        qItem.options.id = id;
        this.get(qItem.url, qItem.options);

        return true;
    };

    /**
     * Cancels a call based on its id. The id of the call is found on the
     * `'extra'` object, the third argument of the callback.
     *
     * @see apf.http.clearQueueItem
     *
     * @param {Number} id The id of the call that should be canceled.
     */
    this.cancel = function(id) {
        if (id === null)
            id = this.queue.length - 1;

        if (!this.queue[id])
            return false;

        if (apf.isGecko)
            this.queue[id].http.abort();

        this.clearQueueItem(id);
    };

    if (!this.$loadAml && !this.instantiate && !this.call) {
        /**
         * @private
         */
        this.$loadAml = function(x) {
            var receive = this["receive"];

            for (var i = 0, l = this.childNodes.length; i < l; i++) {
                if (this.childNodes[i].nodeType != 1)
                    continue;

                var url = this.childNodes[i].getAttribute("url"),
                    callback = self[this.childNodes[i].getAttribute("receive") || receive],
                    options = {
                        useXML: this.childNodes[i].getAttribute("type") == "XML",
                        async: !apf.isFalse(this.childNodes[i].getAttribute("async"))
                    };

                this[this.childNodes[i].getAttribute("name")] = function(data, userdata) {
                    options.userdata = userdata;
                    options.data = data;
                    return this.get(url, options);
                }
            }
        };

        /**
         * @private
         */
        this.instantiate = function(x) {
            var url = x.getAttribute("src"),
                options = {
                    async: x.getAttribute("async") != "false",
                    nocache: true
                };

            this.getURL = function(data, userdata) {
                options.data = data;
                options.userdata = userdata;
                options.callback = this.callbacks.getURL;
                return this.get(url, options);
            }

            var name = "http" + Math.round(Math.random() * 100000);
            apf.setReference(name, this);

            return name + ".getURL()";
        };

        /**
         * @private
         */
        this.call = function(method, args) {
            this[method].call(this, args);
        };
    }
};



apf.Init.run("http");




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: socket://www.fsf.org.
 *
 */























/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * The parser of the Ajax.org Markup Language. Besides aml this parser takes care
 * of distributing parsing tasks to other parsers like the native html parser and
 * the xsd parser.
 * @parser
 * @private
 *
 * @define include element that loads another aml files.
 * Example:
 * <code>
 *   <a:include src="bindings.aml" />
 * </code>
 * @attribute {String} src the location of the aml file to include in this application.
 *
 */
apf.DOMParser = function(){};

apf.DOMParser.prototype = new (function(){
    this.caseInsensitive = true;
    this.preserveWhiteSpace = false; //@todo apf3.0 whitespace issue

    this.$waitQueue = {}
    this.$callCount = 0;

    // privates
    var RE = [
            /\<\!(DOCTYPE|doctype)[^>]*>/,
            /&nbsp;/g,
            /<\s*\/?\s*(?:\w+:\s*)[\w-]*[\s>\/]/g
        ],
        XPATH = "//@*[not(contains(local-name(), '.')) and not(translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = local-name())]";

    this.parseFromString = function(xmlStr, mimeType, options) {
        var xmlNode;
        if (this.caseInsensitive) {
            //replace(/&\w+;/, ""). replace this by something else
            //.replace(RE[1], " ")
            var str = xmlStr.replace(RE[0], "")
              .replace(RE[2], //.replace(/^[\r\n\s]*/, "")
                function(m){ return m.toLowerCase(); });

            /* @todo apf3.0 integrate this
            x.ownerDocument.setProperty("SelectionNamespaces",
                                    "xmlns:a='" + apf.ns.aml + "'");
            */

            if (!this.supportNamespaces)
                str = str.replace(/xmlns\=\"[^"]*\"/g, "");

            

            var xmlNode = apf.getXmlDom(str);
            if (apf.xmlParseError) apf.xmlParseError(xmlNode);
            xmlNode = xmlNode.documentElement;
            
        }
        else {
            xmlNode = apf.getXmlDom(xmlStr, null, this.preserveWhiteSpace || apf.debug).documentElement;
        }

        return this.parseFromXml(xmlNode, options);
    };

    //@todo prevent leakage by not recording .$aml
    this.parseFromXml = function(xmlNode, options) {
        var doc, docFrag, amlNode, beforeNode;
        if (!options)
            options = {};

        if (!options.delayedRender && !options.include) {
            //Create a new document
            if (options.doc) {
                doc = options.doc;
                docFrag = options.docFrag || doc.createDocumentFragment();
            }
            else {
                doc = new apf.AmlDocument();
                doc.$aml = xmlNode;
                doc.$domParser = this;
            }
            if (options.host)
                doc.$parentNode = options.host; //This is for sub docs that need to access the outside tree

            

            //Let's start building our tree
            amlNode = this.$createNode(doc, xmlNode.nodeType, xmlNode); //Root node
            (docFrag || doc).appendChild(amlNode);
            if (options.htmlNode)
                amlNode.$int = options.htmlNode;
        }
        else {
            amlNode = options.amlNode;
            doc = options.doc;

            if (options.include) {
                var n = amlNode.childNodes;
                var p = n.indexOf(options.beforeNode);
                var rest = p ? n.splice(p, n.length - p) : [];
            }
        }

        //Set parse context
        this.$parseContext = [amlNode, options];

        this.$addParseState(amlNode, options || {});

        //First pass - Node creation
        var nodes, nodelist = {}, prios = [], _self = this;
        var recur;
        (recur = function(amlNode, nodes) {
            var cL, newNode, node, nNodes,
                cNodes = amlNode.childNodes,
                i = 0,
                l = nodes.length;
            for (; i < l; i++) {
                //Create child
                newNode = _self.$createNode(doc, (node = nodes[i]).nodeType, node);
                if (!newNode) continue; //for preserveWhiteSpace support

                cNodes[cL = cNodes.length] = newNode; //Add to children

                //Set tree refs
                newNode.parentNode = amlNode;
                if (cL > 0)
                    (newNode.previousSibling = cNodes[cL - 1]).nextSibling = newNode;

                //Create children
                if (!newNode.render && newNode.canHaveChildren && (nNodes = node.childNodes).length)
                    recur(newNode, nNodes);

                //newNode.$aml = node; //@todo should be deprecated...

                //Store high prio nodes for prio insertion
                if (newNode.$parsePrio) {
                    if (newNode.$parsePrio == "001") {
                        newNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
                        continue;
                    }

                    (nodelist[newNode.$parsePrio] || (prios.push(newNode.$parsePrio)
                      && (nodelist[newNode.$parsePrio] = []))).push(newNode); //for second pass
                }
            }

            amlNode.firstChild = cNodes[0];
            amlNode.lastChild = cNodes[cL];
        })(amlNode, xmlNode.childNodes);

        if (options.include && rest.length) {
            var index = n.length - 1;
            n.push.apply(n, rest);
            var last = n[index];
            var next = n[index + 1];
            (next.previousSibling = last).nextSibling = next;
            amlNode.lastChild = n[n.length - 1];
        }

        if (options.delay) {
            amlNode.$parseOptions = {
                prios: prios,
                nodelist: nodelist
            };
            return (docFrag || doc);
        }

        //Second pass - Document Insert signalling
        prios.sort();
        var i, j, l, l2;
        for (i = 0, l = prios.length; i < l; i++) {
            nodes = nodelist[prios[i]];
            for (j = 0, l2 = nodes.length; j < l2; j++) {
                nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
            }
        }

        if (this.$waitQueue[amlNode.$uniqueId]
          && this.$waitQueue[amlNode.$uniqueId].$shouldWait)
            return (docFrag || doc);

        if (options.timeout) {
            $setTimeout(function(){
                _self.$continueParsing(amlNode, options);
            });
        }
        else {
            this.$continueParsing(amlNode, options);
        }

        return (docFrag || doc);
    };

    this.$isPaused = function(amlNode) {
        return this.$waitQueue[amlNode.$uniqueId] &&
          this.$waitQueue[amlNode.$uniqueId].$shouldWait > 0;
    }

    this.$addParseState = function(amlNode, options) {
        var waitQueue = this.$waitQueue[amlNode.$uniqueId]
            || (this.$waitQueue[amlNode.$uniqueId] = [])
        waitQueue.pushUnique(options);

        return waitQueue;
    }

    this.$pauseParsing = function(amlNode, options) {
        var waitQueue = this.$waitQueue[amlNode.$uniqueId];
        if (!waitQueue.$shouldWait) waitQueue.$shouldWait = 0;
        waitQueue.$shouldWait++;
    }

    this.$continueParsing = function(amlNode, options) {
        if (!amlNode)
            amlNode = apf.document.documentElement;

        var uId = amlNode.$uniqueId;
        if (uId in this.$waitQueue) {
            var item = this.$waitQueue[uId];

            if (item.$shouldWait && --item.$shouldWait)
                return false;

            var node = amlNode.parentNode;
            while (node && node.nodeType == 1) {
                if (this.$waitQueue[node.$uniqueId]
                  && this.$waitQueue[node.$uniqueId].$shouldWait)
                    return false;
                node = node.parentNode;
            }

            var parseAmlNode = apf.all[uId];
            delete this.$waitQueue[uId];
            if (parseAmlNode) {
                for (var i = 0; i < item.length; i++)
                    this.$parseState(parseAmlNode, item[i]);
            }

            //@todo Check for shouldWait here?
        }
        else
            this.$parseState(amlNode, options || {});

        delete this.$parseContext;
    }

    this.$parseState = function(amlNode, options) {
        this.$callCount++;

        if (amlNode.$parseOptions) {
            var prios = amlNode.$parseOptions.prios,
                nodelist = amlNode.$parseOptions.nodelist,
                i, j, l, l2, node;
            delete amlNode.$parseOptions;

            //Second pass - Document Insert signalling
            prios.sort();
            for (i = 0, l = prios.length; i < l; i++) {
                var nodes = nodelist[prios[i]];
                for (j = 0, l2 = nodes.length; j < l2; j++) {
                    if (!(node = nodes[j]).parentNode || node.$amlLoaded) //@todo generalize this using compareDocumentPosition
                        continue;
                    nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
                }
            }
        }

        //instead of $amlLoaded use something more generic see compareDocumentPosition
        if (!options.ignoreSelf && !amlNode.$amlLoaded)
            amlNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}

        //Recursively signal non prio nodes
        (function _recur(nodes) {
            var node, nNodes;
            for (var i = 0, l = nodes.length; i < l; i++) {
                if (!(node = nodes[i]).$amlLoaded) {
                    node.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
                }

                //Create children
                if (!node.render && (nNodes = node.childNodes).length)
                    _recur(nNodes);
            }
        })(amlNode.childNodes);

        if (!--this.$callCount && !options.delay)
            apf.queue.empty();

        if (options.callback)
            options.callback.call(amlNode.ownerDocument);
    };

    this.$createNode = function(doc, nodeType, xmlNode, namespaceURI, nodeName, nodeValue) {
        var o;

        switch (nodeType) {
            case 1:
                var id, prefix;
                if (xmlNode) {
                    if ((namespaceURI = xmlNode.namespaceURI || apf.ns.xhtml)
                      && !(prefix = doc.$prefixes[namespaceURI])) {
                        doc.$prefixes[prefix = xmlNode.prefix || xmlNode.scopeName || ""] = namespaceURI;
                        doc.$namespaceURIs[namespaceURI] = prefix;

                        if (!doc.namespaceURI && !prefix) {
                            doc.namespaceURI = namespaceURI;
                            doc.prefix = prefix;
                        }
                    }
                    nodeName = xmlNode.baseName || xmlNode.localName || xmlNode.tagName.split(":").pop();
                }
                else {
                    prefix = doc.$prefixes[namespaceURI] || "";
                }

                

                var els = apf.namespaces[namespaceURI].elements;

                

                o = new (els[nodeName] || els["@default"])(null, nodeName);

                o.prefix = prefix || "";
                o.namespaceURI = namespaceURI;
                o.tagName = prefix ? prefix + ":" + nodeName : nodeName;

                if (xmlNode) {
                    if ((id = xmlNode.getAttribute("id")) && !self[id])
                        o.$propHandlers["id"].call(o, o.id = id);

                    //attributes
                    var attr = xmlNode.attributes, n;
                    for (var a, na, i = 0, l = attr.length; i < l; i++) {
                        o.attributes.push(na = new apf.AmlAttr(o,
                            (n = (a = attr[i]).nodeName), a.nodeValue));
                        
                        if (n == "render")
                            o.render = true;
                        else
                        
                        if (n.substr(0, 2) == "on")
                            na.$triggerUpdate();
                    }
                }

                break;
            case 2:
                o = new apf.AmlAttr();
                o.name = o.nodeName = nodeName;
                if (nodeValue || (nodeValue = xmlNode && xmlNode.nodeValue))
                    o.value = o.nodeValue = nodeValue;

                if (xmlNode) {
                    if (xmlNode.namespaceURI && !(o.prefix = doc.$namespaceURIs[o.namespaceURI = xmlNode.namespaceURI]))
                        doc.$prefixes[o.prefix = xmlNode.prefix || xmlNode.scopeName] = o.namespaceURI;
                }
                else {
                    o.prefix = doc.$prefixes[namespaceURI];
                }

                break;
            case 3:
                if (xmlNode)
                    nodeValue = xmlNode && xmlNode.nodeValue;
                if (!this.preserveWhiteSpace && !(nodeValue || "").trim())
                    return;

                o = new apf.AmlText();
                o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
                break;
            case 7:
                var target = nodeName || xmlNode && xmlNode.nodeName;
                
                o = new apf.aml.processingInstructions[target]();

                o.target = o.nodeName = target;
                o.data = o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
                break;
            case 4:
                o = new apf.AmlCDATASection();
                o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
                break;
            case 5: //unsupported
                o = new apf.AmlNode();
                o.nodeType = nodeType;
                break;
            case 6: //unsupported
                o = new apf.AmlNode();
                o.nodeType = nodeType;
                break;
            case 8:
                o = new apf.AmlComment();
                o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
                break;
            case 9:
                o = new apf.AmlDocument();
                o.$domParser = this;
                break;
            case 10: //unsupported
                o = new apf.AmlNode();
                o.nodeType = nodeType;
                break;
            case 11:
                o = new apf.AmlDocumentFragment();
                break;
        }

        o.ownerDocument = doc;
        o.$aml = xmlNode;

        return o;
    };
})();

/**
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.AmlNamespace = function(){
    this.elements = {};
    this.processingInstructions = {};
};

apf.AmlNamespace.prototype = {
    setElement: function(tagName, fConstr) {
        return this.elements[tagName] = fConstr;
    },

    setProcessingInstruction: function(target, fConstr) {
        this.processingInstructions[target] = fConstr;
    }
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * The parser of the Ajax.org Markup Language. Besides aml this parser takes care
 * of distributing parsing tasks to other parsers like the native html parser and
 * the xsd parser.
 * @parser
 * @private
 *
 * @define include element that loads another aml files.
 * Example:
 * <code>
 *   <a:include src="bindings.aml" />
 * </code>
 * @attribute {String} src the location of the aml file to include in this application.
 *
 */
apf.aml = new apf.AmlNamespace();
apf.setNamespace("http://ajax.org/2005/aml", apf.aml);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__AMLNODE__ = 1 << 14;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have Document Object Model (DOM) support. The DOM
 * is the primary method for accessing and manipulating an XML document. This
 * includes HTML documents and AML documents. Every element in the ajax.org
 * markup language can be manipulated using the W3C DOM. This means
 * that every element and attribute you can set in the XML format, can be
 * changed, set, removed, reparented, _e.t.c._ at runtime. This offers a great deal of
 * flexibility. 
 *
 * Well known methods
 * from this specification are: `appendChild`, `removeChild`, `setAttribute`, and
 * `insertBefore`--to name a few. The Ajax.org Platform aims to implement DOM1
 * completely and parts of DOM2. For more information see {@link http://www.w3.org/DOM/} 
 * or {@link http://www.w3schools.com/dom/default.asp}.
 * 
 * #### Example:
 *
 * Here's a basic window using the Ajax.org Markup Language (AML): 
 *
 * ```xml
 *  <a:window id="winExample" title="Example" visible="true">
 *      <a:button id="tstButton" />
 *  </a:window>
 * ```
 *
 * 
 * Using the Document Object Model in JavaScript:
 *
 * ```javascript
 *  //The following line is only there for completeness sake. In fact apf
 *  //automatically adds a reference in javascript called winExample based
 *  //on the id it has.
 *  var winExample = apf.document.getElementById("winExample");
 *  winExample.setAttribute("title", "Example");
 *  winExample.setAttribute("icon", "icoFolder.gif");
 *  winExample.setAttribute("left", "100");
 *
 *  var lblNew = apf.document.createElement("label");
 *  winExample.appendChild(lblNew);
 *  lblNew.setAttribute("caption", "Example");
 *
 *  tstButton.setAttribute("caption", "Click me");
 * ```
 *
 * That would be the same as having the following AML:
 * 
 * ```xml
 *  <a:window id="winExample"
 *    title = "Example"
 *    icon = "icoFolder.gif"
 *    left = "100"
 *    visible = "true">
 *      <a:button id="tstButton" caption="Click me"/>
 *      <a:label caption="Example" />
 *  </a:window>
 * ```
 *
 * #### Remarks
 * Because the W3C DOM is native to all modern browsers the internet is full
 * of tutorials and documentation for this API. If you need more information,
 * it's a good idea to search for tutorials online.
 *
 * @class apf.AmlNode
 * @baseclass
 * @inherits apf.Class
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.5
 */
/**
 * @event DOMNodeInserted Fires when a DOM node is inserted.
 */
/** 
 * @event DOMNodeInsertedIntoDocument Fires when a DOM node is inserted into the document.
 */
/** 
 * @event DOMNodeRemoved Fires when a DOM node is removed.
 */
/** 
 * @event DOMNodeRemovedFromDocument Fires when a DOM node is removed from a document.
 */

apf.AmlNode = function(){
    this.$init(function(){
        /**
         * Nodelist containing all the child nodes of this element.
         */
        this.childNodes = []; //@todo AmlNodeList
    });
};

(function() {

    
    /**
     * Returns a string representation of this object.
     * @returns A string defining the object.
     */
    this.toString = function(){
        if (this.nodeName)
            return "[" + this.nodeName.uCaseFirst() + " Node]";
        
        return "[" + this.localName.uCaseFirst() + " Element Node, <" 
            + (this.prefix ? this.prefix + ":" : "") + this.localName + " "
            + this.attributes.join(" ")
            + " /> : " + (this.name || this.$uniqueId || "") + "]";
    };
    
    
    /**
     * Number specifying the type of node within the document.
     * @type {Number}
     */
    this.$regbase = this.$regbase | apf.__AMLNODE__;
    
    /**
     * The constant for a DOM element node.
     * @type {Number}
     */
    this.NODE_ELEMENT = 1;
    /**
     * The constant for a DOM attribute node.
     * @type {Number}
     */
    this.NODE_ATTRIBUTE = 2;
    /**
     * The constant for a DOM text node.
     * @type {Number}
     */
    this.NODE_TEXT = 3;
    /**
     * The constant for a DOM cdata section node.
     * @type {Number}
     */
    this.NODE_CDATA_SECTION = 4;
    /**
     * The constant for a DOM entity reference node.
     * @type {Number}
     */
    this.NODE_ENTITY_REFERENCE = 5;
    /**
     * The constant for a DOM entity node.
     * @type {Number}
     */
    this.NODE_ENTITY = 6;
    /**
     * The constant for a DOM processing instruction node.
     * @type {Number}
     */
    this.NODE_PROCESSING_INSTRUCTION = 7;
    /**
     * The constant for a DOM comment node.
     * @type {Number}
     */
    this.NODE_COMMENT = 8;
    /**
     * The constant for a DOM document node.
     * @type {Number}
     */
    this.NODE_DOCUMENT = 9;
    /**
     * The constant for a DOM document type node.
     * @type {Number}
     */
    this.NODE_DOCUMENT_TYPE = 10;
    /**
     * The constant for a DOM document fragment node.
     * @type {Number}
     */
    this.NODE_DOCUMENT_FRAGMENT = 11;
    /**
     * The constant for a DOM notation node.
     * @type {Number}
     */
    this.NODE_NOTATION = 12;
    
    

    /**
     * The document node of this application
     * @type {apf.AmlDocument}
     */
    this.ownerDocument = null;

    /**
     * Returns the value of the current node. 
     * @type {apf.AmlNode}
     */
    this.nodeValue = "";
    
    /**
     * The namespace URI of the node, or `null` if it is unspecified (read-only). 
     *
     * When the node is a document, it returns the XML namespace for the current 
     * document.
     * @type {String}
     */
    this.namespaceURI = "";
    
    /*
     * @todo
     */
    //this.baseURI = alsdjlasdj
    
    /*
     * @todo
     */
    //this.prefix = asdkljahqsdkh
        
    /**
     * 
     * @inheritdoc apf.AmlNode.insertBefore
     * 
     */
    this.appendChild =

    /**
     * Inserts an element before another element in the list of children of this
     * element. If the element was already a child of another element it is
     * removed from that parent before adding it this element.
     *
     * @method insertBefore
     * @param  {apf.AmlNode}  amlNode     The element to insert as child of this element.
     * @param  {apf.AmlNode}  beforeNode  The element which determines the insertion position of the element.
     * @return  {apf.AmlNode}  The inserted node
     */
    this.insertBefore = function(amlNode, beforeNode, noHtmlDomEdit) {
        

        if (this.nodeType == this.NODE_DOCUMENT) {
            if (this.childNodes.length) {
                throw new Error(apf.formatErrorString(0, this,
                    "Insertbefore DOM operation",
                    "Only one top level element is allowed in an AML document."));
            }
            else this.documentElement = amlNode; //@todo apf3.0 removal
        }
        
        if (amlNode == beforeNode)
            return amlNode;
        
        if (this == amlNode) {
            throw new Error(apf.formatErrorString(0, this,
                "Insertbefore DOM operation",
                "Cannot append node as a child of itself."));
        }

        if (amlNode.nodeType == this.NODE_DOCUMENT_FRAGMENT) {
            var nodes = amlNode.childNodes.slice(0);
            for (var i = 0, l = nodes.length; i < l; i++) {
                this.insertBefore(nodes[i], beforeNode);
            }
            return amlNode;
        }
        
        var isMoveWithinParent = amlNode.parentNode == this,
            oldParentHtmlNode = amlNode.$pHtmlNode,
            oldParent = amlNode.parentNode,
            index = -1,
            _self = this;
        
        if (beforeNode) {
            index = this.childNodes.indexOf(beforeNode);
            if (index < 0) {
                

                return false;
            }
        }

        if (!amlNode.ownerDocument)
            amlNode.ownerDocument = this.ownerDocument || apf.ownerDocument;

        if (amlNode.parentNode)
            amlNode.removeNode(isMoveWithinParent, true);//noHtmlDomEdit);
        amlNode.parentNode = this;

        if (beforeNode)
            index = this.childNodes.indexOf(beforeNode);

        if (beforeNode) {
            amlNode.nextSibling = beforeNode;
            amlNode.previousSibling = beforeNode.previousSibling;
            beforeNode.previousSibling = amlNode;
            if (amlNode.previousSibling)
                amlNode.previousSibling.nextSibling = amlNode;
        }

        if (index >= 0) {
            this.childNodes = this.childNodes.slice(0, index).concat(amlNode,
                this.childNodes.slice(index));
        }
        else {
            index = this.childNodes.push(amlNode) - 1;

            amlNode.nextSibling = null;
            if (index > 0) {
                amlNode.previousSibling = this.childNodes[index - 1];
                amlNode.previousSibling.nextSibling = amlNode;
            }
            else {
                amlNode.previousSibling = null;
            }
        }

        this.firstChild = this.childNodes[0];
        this.lastChild = this.childNodes[this.childNodes.length - 1];

        //@todo fix event struture, fix tree events
        var initialAppend = !amlNode.$amlLoaded;
        function triggerUpdate(){
            amlNode.$pHtmlNode = _self.canHaveChildren ? _self.$int : document.body;

            //@todo this is a hack, a good solution should be found
            if (document.adoptNode && amlNode.$ext && amlNode.$ext.nodeType == 1) {
                var reappendlist = [];
                var iframelist = apf.getArrayFromNodelist(
                    amlNode.$ext.getElementsByTagName("iframe"));
                if (amlNode.$ext.tagName == "IFRAME")
                    document.adoptNode(amlNode.$ext);
                    
                for (var i = 0; i < iframelist.length; i++) {
                    reappendlist[i] = [
                        iframelist[i].parentNode,
                        iframelist[i].nextSibling,
                        document.adoptNode(iframelist[i]),
                    ]
                }
            }

            var nextNode = beforeNode;
            if (!initialAppend && !noHtmlDomEdit && amlNode.$ext && !amlNode.$coreHtml) {
                nextNode = beforeNode;
                while (nextNode && !(nextNode.$altExt || nextNode.$ext)) {
                    nextNode = nextNode.nextSibling;
                }
                
                amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext,
                    nextNode && (nextNode.$altExt || nextNode.$ext) || null);
                    
                for (var i = reappendlist.length - 1; i >= 0; i--) {
                    reappendlist[i][0].insertBefore(
                        reappendlist[i][2],
                        reappendlist[i][1]);
                }
                reappendlist = [];
            }
            
            //Signal node and all it's ancestors
            amlNode.dispatchEvent("DOMNodeInserted", {
                $beforeNode: beforeNode,
                relatedNode: _self,
                $isMoveWithinParent: isMoveWithinParent,
                $oldParentHtmlNode: oldParentHtmlNode,
                $oldParent: oldParent,
                bubbles: true
            });
            
            if (initialAppend && !noHtmlDomEdit && beforeNode && amlNode.$ext && !amlNode.$coreHtml) {
                nextNode = beforeNode;
                while (nextNode && !(nextNode.$altExt || nextNode.$ext)) {
                    nextNode = nextNode.nextSibling;
                }
                
                amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext,
                    nextNode && (nextNode.$altExt || nextNode.$ext) || null);
                
                for (var i = reappendlist.length - 1; i >= 0; i--) {
                    reappendlist[i][0].insertBefore(
                        reappendlist[i][2],
                        reappendlist[i][1]);
                }
            }
        }

        var doc = this.nodeType == this.NODE_DOCUMENT ? this : this.ownerDocument;
        if (!doc || doc.$domParser.$isPaused(this))
            return amlNode;

        // Don't update the tree if this is a doc fragment or if this element is not inited yet
        if (this.nodeType == this.NODE_DOCUMENT_FRAGMENT || !this.$amlLoaded)
            return amlNode; 

        //@todo review this...
        if (initialAppend && !amlNode.render) { // && (nNodes = node.childNodes).length ??
            (this.ownerDocument || this).$domParser.$continueParsing(amlNode, {delay: true});
        }

        triggerUpdate();
        return amlNode;
    };

    /**
     * Removes this element from the document hierarchy. Call-chaining is
     * supported.
     *
     */
    this.removeNode = function(doOnlyAdmin, noHtmlDomEdit) {
        

        if (!this.parentNode || !this.parentNode.childNodes)
            return this;

        

        this.parentNode.childNodes.remove(this);

        //If we're not loaded yet, just remove us from the aml to be parsed
        if (this.$amlLoaded && !apf.isDestroying) {
            //this.parentNode.$aml.removeChild(this.$aml);

            this.dispatchEvent("DOMNodeRemoved", {
                relatedNode: this.parentNode,
                bubbles: true,
                $doOnlyAdmin: doOnlyAdmin
            });

            if (!noHtmlDomEdit && !doOnlyAdmin && this.$ext && this.$ext.parentNode) {
                this.$ext.parentNode.removeChild(this.$ext);
                //delete this.$ext; //WTF???
            }
        }

        if (this.parentNode.firstChild == this)
            this.parentNode.firstChild = this.nextSibling;
        if (this.parentNode.lastChild == this)
            this.parentNode.lastChild = this.previousSibling;

        if (this.nextSibling)
            this.nextSibling.previousSibling = this.previousSibling;
        if (this.previousSibling)
            this.previousSibling.nextSibling = this.nextSibling;

        this.$pHtmlNode = 
        this.parentNode = 
        this.previousSibling =
        this.nextSibling = null;

        return this;
    };

    /**
     * Removes a child from the node list of this element. Call-chaining is
     * supported.
     * @param {apf.AmlNode} childNode The child node to remove
     */
    this.removeChild = function(childNode) {
        

        childNode.removeNode();
        return this;
    };
    
    //@todo
    this.replaceChild = function(){};

    /**
     * Clones this element, creating an exact copy of it--but does not insert
     * it in the document hierarchy.
     *
     * @param {Boolean} deep Specifies whether the elements are cloned recursively.
     * @return {apf.AmlNode} The cloned element.
     */
    this.cloneNode = function(deep) {
        if (deep && this.nodeType == 1) {
            return this.ownerDocument.$domParser.parseFromXml(this, {
                doc: this.ownerDocument,
                delay: true
            }).childNodes[0];
        }
        else {
            return this.ownerDocument.$domParser.$createNode(
                this.ownerDocument, this.nodeType, this);
        }
    };
    
    //@todo
    this.canDispatch = function(namespaceURI, type) {};
    
    //@todo
    this.compareDocumentPosition = function(otherNode) {
        /*
            DOCUMENT_POSITION_DISCONNECTED = 0x01;
            DOCUMENT_POSITION_PRECEDING = 0x02;
            DOCUMENT_POSITION_FOLLOWING = 0x04;
            DOCUMENT_POSITION_CONTAINS = 0x08;
            DOCUMENT_POSITION_CONTAINED_BY = 0x10;
        */
    };
    
    this.hasAttributes = function(){
        return this.attributes && this.attributes.length;
    };
    
    this.hasChildNodes = function(){
        return this.childNodes && this.childNodes.length;
    };
    
    this.isDefaultNamespace = function(namespaceURI) {
        if (node.nodeType == 1) {
            if (!this.prefix)
                return this.namespaceURI == namespaceURI;
            
            //@todo Loop through attributes here
        }
        
        var node = this.parentNode || this.ownerElement;
        return node && node.isDefaultNamespace(namespaceURI);
    };
    
    this.lookupNamespaceURI = function(prefix) {
        if (node.nodeType == 1) {
            if (this.namespaceURI && prefix == this.prefix)
                return this.namespaceURI ;
                
            //@todo Loop through attributes here
        }
        
        var node = this.parentNode || this.ownerElement;
        return node && node.lookupNamespaceURI(prefix);
    };
    
    this.lookupPrefix = function(namespaceURI) {
        if (this.nodeType == 1) {
            if (namespaceURI == this.namespaceURI && this.prefix)
                return this.prefix;
            
            //@todo Loop through attributes here
        }
        
        var node = this.parentNode || this.ownerElement;
        return node && node.lookupPrefix(namespaceURI);    
    };
    
    this.normalize = function(){};
    
    // *** Xpath support *** //

    /**
     * Queries the AML DOM using the W3C xPath query language and returns a node
     * list. This is not an official API call, but can be useful in certain cases.
     *
     * @param {String}  sExpr          The xpath expression to query the AML DOM tree with.
     * @param {apf.AmlNode} [contextNode]  The element that serves as the starting point of the search. Defaults to this element.
     * @returns {NodeList} List of found nodes.
     */
    this.selectNodes = function(sExpr, contextNode) {
        if (!apf) return;
        
        if (!apf.XPath)
            apf.runXpath();
        return apf.XPath.selectNodes(sExpr,
            contextNode || (this.nodeType == 9 ? this.documentElement : this));
    };

    /**
     * Queries the AML dom using the W3C xPath query language and returns a single
     * node. This is not an official API call, but can be useful in certain cases.
     * 
     * @param {String}  sExpr          The xpath expression to query the AML DOM tree with.
     * @param {apf.AmlNode} [contextNode]  The element that serves as the starting point of the search. Defaults to this element.
     * @returns {apf.AmlNode} The first node that matches the query.
     */
    this.selectSingleNode = function(sExpr, contextNode) {
        if (!apf) return;
        
        if (!apf.XPath)
            apf.runXpath();
        return apf.XPath.selectNodes(sExpr,
            contextNode || (this.nodeType == 9 ? this.documentElement : this))[0];
    };
    
    /*this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        
    }, true);*/
}).call(apf.AmlNode.prototype = new apf.Class());





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

/**
 * Represents a single element within an AML node.
 *
 * @class apf.AmlElement
 * @baseclass
 * @inherits apf.AmlNode
 */


apf.AmlElement = function(struct, tagName) {
    var $init = this.$init;
    this.$init = function(tagName, nodeFunc, struct) {
        this.$supportedProperties = this.$supportedProperties.slice();
        
        var prop, p, q;
        p = this.$propHandlers;
        q = this.$propHandlers = {};
        for (prop in p)
            q[prop] = p[prop];
        
        p = this.$booleanProperties;
        q = this.$booleanProperties = {};
        for (prop in p)
            q[prop] = p[prop];
        
        return $init.call(this, tagName, nodeFunc, struct);
    };
    
    this.$init(function(tagName, nodeFunc, struct) {
        this.$events = {};
        this.$inheritProperties = {};
        
        /*
         * A node list containing all the attributes. This is implemented according to the
         * W3C specification.
         * 
         * For more information, see [[apf.AmlElement.getAttribute]] and [[apf.AmlElement.setAttribute]].
         *
         * #### Example
         * 
         * ```javascript
         *  for (var i = 0; i < obj.attributes.length; i++) {
         *      alert(obj.attributes.item(i));
         *  }
         * ```
         * @type {apf.AmlNamedNodeMap}
         */
        this.attributes = new apf.AmlNamedNodeMap(this); //@todo apf3.0 move to init?
        
        /**
         * Defines the purpose of this element. Possible values include:
         * - `apf.NODE_VISIBLE`:  This element has a GUI representation
         * - `apf.NODE_HIDDEN`:   This element does not display a GUI
         * @type {Number}
         */
        this.nodeFunc = nodeFunc;
        
        /**
         * The local name of this element
         * @type {String}
         */
        this.localName = tagName; //@todo
        
        //Parse struct to create attributes and child nodes
        if (struct) {
            var nodes, prop, i, l, attr;
            if (struct.childNodes) {
                nodes = struct.childNodes;
                delete struct.childNodes; //why delete?
            }
            
            //Attributes
            for (prop in struct) { 
                if (prop == "htmlNode") continue;
                
                attr = new apf.AmlAttr(this, prop, struct[prop]);
                
                //These exceptions should be generalized
                if (prop == "id")
                    this.$propHandlers["id"].call(this, this.id = struct.id);
                else if (prop == "hotkey")
                    this.$propHandlers["hotkey"].call(this, this.hotkey = struct.hotkey);
                else if (prop.substr(0, 2) == "on")
                    attr.$triggerUpdate();

                this.attributes.push(attr);
            }
            
            if (!this.ownerDocument) {
                this.ownerDocument = apf.document;
                this.prefix = "a";
                this.namespaceURI = apf.ns.aml;
                this.tagName = tagName;
            }
            
            if (nodes) {
                this.childNodes = nodes;

                for (i = 0, l = nodes.length; i < l; i++) {
                    nodes[i].nextSibling = nodes[i + 1] || null;
                    nodes[i].previousSibling = nodes[i - 1] || null;
                    nodes[i].parentNode = this;
                }
                this.firstChild = nodes[0] || null;
                this.lastChild = nodes[nodes.length - 1] || null;
            }

            //Temp hack
            this.$aml = apf.$emptyNode || (apf.$emptyNode = apf.getXml("<empty />"));
        }
    });
    
    if (tagName) //of typeof is not function and not true
        $init.call(this, tagName, apf.NODE_HIDDEN, struct);
};

(function(){
    /**
     * A number specifying the type of node within the document.
     * @type {Number}
     */
    this.nodeType = this.NODE_ELEMENT;
    this.canHaveChildren = true;
    
    this.$propHandlers = {
        /**
         * @attribute {String} id The identifier of this element. When set, this
         * identifier is the name of the variable in JavaScript to access this
         * element directly. This identifier is also the way to get a reference to
         * this element using `apf.document.getElementById()`.
         * 
         * #### Example
         *
         * ```xml
         *  <a:bar id="barExample" />
         *  <a:script>
         *      alert(barExample);
         *  </a:script>
         * ```
         */
        "id": function(value) {
            
            
            if (this.name == value || !value)
                return;
    
            if (self[this.name] == this) {
                self[this.name] = null;
                
                apf.nameserver.remove(this.localName, this);
                apf.nameserver.remove("all", this);
                
            }
    
            
    
            if (!self[value] || !self[value].hasFeature) {
                try {
                    self[value] = this;
                }
                catch (ex) {
                    
                }
            }
            
            
            //@todo dispatch event for new name creation.
            //@todo old name disposal
            
            apf.nameserver.register(this.localName, value, this)
            apf.nameserver.register("all", value, this)
            
            
            this.name = value;
        }
    };
    
    this.$booleanProperties = {};
    this.$inheritProperties = {};
    this.$supportedProperties = [];
    
    /**
     * Returns a list of elements with the given tag name.
     *
     * The subtree below the specified element is searched, excluding the
     * element itself.
     *
     * @param {String}   tagName  The tag name to look for. The special string "*" represents any tag name.
     * @param {Boolean} [norecur] If specified, defines whether or not to check recursively
     * @return  {NodeList}  Contains any nodes matching the search string
     */
    this.getElementsByTagName = function(tagName, norecur) {
        tagName = tagName.toLowerCase();
        var node, i, l,
            nodes = this.childNodes,
            result = [];
        for (i = 0, l = nodes.length; i < l; i++) {
            if ((node = nodes[i]).nodeType != 1)
                continue;
            
            if (node.tagName == tagName || tagName == "*")
                result.push(node);

            if (!norecur && node.nodeType == 1)
                result = result.concat(node.getElementsByTagName(tagName));
        }
        
        return result;
    };

    /**
     * Returns a list of elements with the given tag name and the specified namespace URI.
     *
     * The subtree below the specified element is searched, excluding the
     * element itself.
     *
     * @param {String}   namespaceURI  The namespace URI name to look for.
     * @param {String}   localName  The tag name to look for. The special string "*" represents any tag name.
     * @param {Boolean} [norecur] If specified, defines whether or not to check recursively
     * @return  {NodeList}  Contains any nodes matching the search string
     */    
    this.getElementsByTagNameNS = function(namespaceURI, localName, norecur) {
        localName = localName.toLowerCase();
        var node, i, l,
            nodes = this.childNodes,
            result = [];
        for (i = 0, l = nodes.length; i < l; i++) {
            if ((node = nodes[i]).nodeType != 1)
                continue;

            if (node.namespaceURI == namespaceURI && (node.localName == localName || localName == "*"))
                result.push(node);

            if (!norecur && node.nodeType == 1)
                result = result.concat(node.getElementsByTagNameNS(namespaceURI, localName));
        }
        
        return result;
    };

    /**
     * Sets an attribute on this element.
     * @chainable
     * @param {String} name The name of the attribute to which the value is set
     * @param {String} value The new value of the attribute.
     * @param {Boolean} [noTrigger] If specified, does not emit events 
     * [[apf.AmlNode@DOMNodeInsertedIntoDocument]] and [[apf.AmlNode@DOMNodeInserted]].
     */
    this.setAttribute = function(name, value, noTrigger) {
        name = name.toLowerCase();
        
        var a = this.attributes.getNamedItem(name);
        if (!a) {
            this.attributes.push(a = new apf.AmlAttr(this, name, value));
        
            if (!this.$amlLoaded && name != "id" && name != "hotkey")
                return;
            
            if (noTrigger)
                a.$setValue(value);
            else {
                //@todo apf3.0 domattr
                a.dispatchEvent("DOMNodeInsertedIntoDocument", {
                    relatedNode: this
                });
                
                //@todo apf3.0 domattr
                a.dispatchEvent("DOMNodeInserted", {
                    relatedNode: this,
                    bubbles: true
                });
            }

            return;
        }

        var oldValue = a.nodeValue;
        a.$setValue(value);
        
        if (noTrigger || !this.$amlLoaded)
            return;
        
        //@todo apf3.0 domattr
        a.$triggerUpdate(null, oldValue);
    };
    
    //@todo apf3.0 domattr
    this.setAttributeNode = function(attrNode) {
        this.attributes.setNamedItem(attrNode);
    };
    
    this.setAttributeNS = function(namespaceURI, name, value) {
        return this.setAttribute(name, value);
    };
    
    //@todo apf3.0 domattr
    this.hasAttribute = function(name) {
        return this.getAttributeNode(name) ? true : false;
    };
    
    //@todo
    this.hasAttributeNS = function(namespaceURI, name) {
        return this.hasAttribute(name);
    };
    
    /**
     * Removes an attribute from this element. 
     * @chainable
     * @param {String} name The name of the attribute to remove.
     * @returns {apf.AmlElement} The modified element.
     */
    this.removeAttribute = function(name){ //@todo apf3.0 domattr
        this.attributes.removeNamedItem(name);
        return this;
    };
    
    //@todo apf3.0 domattr
    this.removeAttributeNS = function(namespaceURI, name) {
        return this.removeAttribute(name);
    };
    
    //@todo apf3.0 domattr
    this.removeAttributeNode = function(attrNode) {
        this.attributes.removeNamedItem(attrNode.name); //@todo this should probably be slightly different.
    };

    /**
     * Retrieves the value of an attribute of this element.
     *
     * @param {String}   name       The name of the attribute for which to return the value.
     * @param {Boolean}  [inherited] if specified, takes into consideration that the attribute is inherited
     * @return {String} The value of the attribute, or `null` if none was found with the name specified.
     */
    this.getAttribute = function(name, inherited) {
        var item = this.attributes.getNamedItem(name);
        return item ? (inherited 
            ? item.inheritedValue || item.nodeValue 
            : item.nodeValue) : null;
    };
    
    /**
     * Retrieves the attribute node for a given name
     *
     * @param {String} name The name of the attribute to find.
     * @return {apf.AmlNode} The attribute node, or `null` if none was found with the name specified.
     */
    this.getAttributeNode = function(name) {
        return this.attributes.getNamedItem(name);
    };

    this.getBoundingClientRect = function(){
        return new apf.AmlTextRectangle(this);
    };
    
    //@todo
    this.querySelector = function(){
        // here we should use: http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js
    };
    
    //@todo
    this.querySelectorAll = function(){
        // here we should use: http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js
    };
    
    //@todo
    this.scrollIntoView = function(){
        
    };
    
    /**
     * Replaces the child AML elements with new AML.
     * @param {Mixed}       amlDefNode  The AML to be loaded. This can be a string or a parsed piece of XML.
     * @param {HTMLElement} oInt        The HTML parent of the created AML elements.
     */
    this.replaceMarkup = function(amlDefNode, options) {
        

        if (!options)
            options = {};

        if (!options.$intAML)
            options.$intAML = this.$aml;
        if (!options.$int)
            options.$int = this.$int;
        options.clear = true;
        
        //Remove All the childNodes
        for (var i = this.childNodes.length - 1; i >= 0; i--) {
            var oItem = this.childNodes[i];
            /*var nodes = oItem.childNodes;
            for (var k = 0; k < nodes.length; k++)
                if (nodes[k].destroy)
                    nodes[k].destroy(true);

            if (oItem.$aml && oItem.$aml.parentNode)
                oItem.$aml.parentNode.removeChild(oItem.$aml);*/

            if (oItem.destroy)
                oItem.destroy(true);

            if (oItem.$ext != this.$int)
                apf.destroyHtmlNode(oItem.$ext);
        }
        
        this.childNodes.length = 0;
        
        if (options.noLoadingMsg !== false)
            this.$int.innerHTML = "<div class='loading'>loading...</div>";

        //Do an insertMarkup
        this.insertMarkup(amlDefNode, options);
    };

    /**
     * Inserts new AML into this element.
     * @param {Mixed}       amlDefNode  The AML to be loaded. This can be a string or a parsed piece of XML.
     * @param {Object}      options     Additional options to pass. It can include the following properties:
     *                                  - callback ([[Function]]): A function to call once the insertion completes.
     *                                  - clear ([[Boolean]]): If set, the AML has the attribute "clear" attached to it
     */
    this.insertMarkup = function(amlDefNode, options) {
        

        

        var _self = this;
        var include = new apf.XiInclude();
        
        if (amlDefNode.trim().charAt(0) == "<")
            amlDefNode = apf.getXml(amlDefNode);
        
        include.setAttribute("href", amlDefNode);
        if (options && options.clear)
            include.setAttribute("clear", true);
        include.options = options;
        include.callback = function(){
            _self.dispatchEvent("afteramlinserted", {src: amlDefNode});
            options && options.callback && options.callback();
            setTimeout(function(){
                include.destroy(true, true);
            });
        };
        this.appendChild(include);
    };
    
    //@todo prefix only needs on top element
    this.serialize = function(shallow) {
        if (shallow || !this.firstChild) {
            return "<" 
                + (this.prefix 
                  ? this.prefix + ":" + this.localName + " xmlns:" 
                    + this.prefix + "=\"" + this.namespaceURI + "\""
                  : this.localName) + (this.attributes && this.attributes.length ? " " : "")
                + (this.attributes && this.attributes.join(" ") || "")
                + "/>";
        }
        else {
            var str = ["<" 
                + (this.prefix 
                  ? this.prefix + ":" + this.localName + " xmlns:" 
                    + this.prefix + "=\"" + this.namespaceURI + "\""
                  : this.localName) + (this.attributes && this.attributes.length ? " " : "")
                + (this.attributes && this.attributes.join(" ") || "")
                + ">"];
            
            for (var i = this.firstChild; i; i = i.nextSibling)
                str.push(i.serialize());
            
            return str.join("") + "</" + (this.prefix ? this.prefix 
                + ":" + this.localName : this.localName) + ">";
        }
    };
    
    this.$setInheritedAttribute = function(prop) {
        var value, node = this, isInherit = false;
        
        value = node.getAttribute(prop);
        if (!value) {
            node = node.parentNode;
            
            //Second argument fetches special inheritance value, if any
            while (node && node.nodeType == 1 && !(value = node.getAttribute(prop, true))) {
                node = node.parentNode;
            }
            
            isInherit = true;
        }
        
        if (!value && apf.config && prop)
            value = apf.config[prop];
    
        if (value) {
            
            //Remove any bounds if relevant
            this.$clearDynamicProperty(prop);
            
        }
        
        if (isInherit)
            this.$inheritProperties[prop] = 2;
        
        if (value) {
            
            if (typeof value == "string" 
              && (value.indexOf("{") > -1 || value.indexOf("[") > -1)) {
                this.$setDynamicProperty(prop, value);
            }
            else 
            
                this.setProperty(prop, value, false, false, 2);
        }
        
        return value;
    };
    
    //@todo in proper W3C implementation this needs to change
    //@todo this won't work with a combo of remove/append
    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget != this || e.$isMoveWithinParent || !e.$oldParent)
            return;

        //Check inherited attributes for reparenting
        /*
            States:
                    -1 Set
             undefined Pass through
                     2 Inherited
                     3 Semi-inherited
                    10 Dynamic property
        */
        var vOld, vNew;
        var aci = apf.config.$inheritProperties;
        for (var prop in aci) {
            vOld = apf.getInheritedAttribute(e.$oldParent, prop);
            vNew = apf.getInheritedAttribute(this.parentNode, prop);
            
            //Property has changed, lets recursively set it on inherited nodes
            if (vOld != vNew) {
                //@todo code duplication from class.js
                (function recur(nodes) {
                    var i, l, node, n;
                    for (i = 0, l = nodes.length; i < l; i++) {
                        node = nodes[i];
                        if (node.nodeType != 1 && node.nodeType != 7)
                            continue;

                        //Pass through
                        n = node.$inheritProperties[prop];
                        if (aci[prop] == 1 && !n)
                            recur(node.childNodes);
                        
                        //Set inherited property
                        //@todo why are dynamic properties overwritten??
                        else if (!(n < 0)) {//Will also pass through undefined - but why??? @todo seems inefficient
                            if (n == 3) {
                                var sameValue = node[prop];
                                node[prop] = null;
                            }
                            node.setProperty(prop, n != 3
                                ? vNew
                                : sameValue, false, false, n); //This is recursive already
                        }
                    }
                })([this]);
            }
        }
    });
    
    this.$handlePropSet = function(prop, value, force) {
        if (this.$booleanProperties[prop])
            value = apf.isTrue(value);

        

        this[prop] = value;

        var handler;
        return (handler = this.$propHandlers && this.$propHandlers[prop]
          || this.nodeFunc == apf.NODE_VISIBLE && apf.GuiElement && apf.GuiElement.propHandlers[prop] || null)
          && handler.call(this, value, prop, force);
    };
    
    //var aci = apf.config.$inheritProperties; << UNUSED
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var a, i, l, attr = this.attributes;

        

        
        
        

        //Set all attributes
        for (i = 0, l = attr.length; i < l; i++) {
            attr[i].dispatchEvent("DOMNodeInsertedIntoDocument");
        }
    }, true);
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.$amlLoaded = true;
    });
}).call(apf.AmlElement.prototype = new apf.AmlNode());





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


//@todo apf3.0 The functions seem to not set nodeValue...
apf.AmlCharacterData = function(){
    this.data = "";
    this.length = 0;
    
    this.$init(true);
    
    this.appendData = function(sValue) {
        this.dispatchEvent("DOMCharacterDataModified", {
            value: sValue
        });
    };
    
    this.deleteData = function(nOffset, nCount) {
        this.dispatchEvent("DOMCharacterDataModified", {
            offset: nOffset,
            count: nCount
        });
    };
    
    this.insertData = function(nOffset, nCount) {
        this.dispatchEvent("DOMCharacterDataModified", {
            offset: nOffset,
            count: nCount
        });
    };
    
    this.replaceData = function(nOffset, nCount, sValue) {
        this.dispatchEvent("DOMCharacterDataModified", {
            offset: nOffset,
            count: nCount,
            value: sValue
        });
    };
    
    this.substringData = function(nOffset, nCount) {};
}
apf.AmlCharacterData.prototype = new apf.AmlNode();




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlText = function(isPrototype) {
    this.$init(isPrototype);
};

(function(){
    this.nodeType = this.NODE_TEXT;
    this.nodeName = "#text";
    
    this.serialize = function(){
        return apf.escapeXML(this.nodeValue);
    };
    
    
    
    //@todo think about using this.replaceData();
    this.$setValue = function(value) {
        //if (!this.$amlLoaded)
            //return;
        
        this.dispatchEvent("DOMCharacterDataModified", {
            bubbles: true,
            prevValue: this.nodeValue,
            newValue: this.nodeValue = value
        });
        
        if (this.$amlLoaded && this.$ext)
            this.$ext.nodeValue = value;
    }

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var pHtmlNode;
        if (!(pHtmlNode = this.parentNode.$int) || this.parentNode.hasFeature(apf.__CHILDVALUE__)) 
            return;

        this.$amlLoaded = true;
        
        var nodeValue = this.nodeValue;

        //@todo optimize for inside elements?
        if (apf.config.liveText && !this.parentNode.hasFeature(apf.__CHILDVALUE__) 
          && (nodeValue.indexOf("{") > -1 || nodeValue.indexOf("[") > -1)) {
            
            //Convert to live markup pi
            this.$supportedProperties = [];
            this.$propHandlers = {};
            this.$booleanProperties = {};
            this.$inheritProperties = {};
            
            this.$propHandlers["calcdata"] = apf.LiveMarkupPi.prototype.$propHandlers["calcdata"];
            
            this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute;
            
            this.implement(apf.StandardBinding);
            
            
            pHtmlNode.appendChild(this.$ext = document.createElement("span"));
            this.$setDynamicProperty("calcdata", this.nodeValue);
            
            return;
        }

        if (apf.hasTextNodeWhiteSpaceBug) {
            var nodeValue = nodeValue.replace(/[\t\n\r ]+/g, " ");

            if (nodeValue && nodeValue != " ")
                this.$ext = pHtmlNode.appendChild(
                  pHtmlNode.ownerDocument.createTextNode(nodeValue));
        }
        else
            this.$ext = pHtmlNode.appendChild(
              pHtmlNode.ownerDocument.createTextNode(nodeValue));
    }, true);
}).call(apf.AmlText.prototype = new apf.AmlCharacterData());





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlAttr = function(ownerElement, name, value) {
    this.$init();
    
    if (ownerElement) {
        this.ownerElement = ownerElement;
        this.ownerDocument = ownerElement.ownerDocument;
    }
    
    this.nodeName = this.name = name;
    this.nodeValue = this.value = value;
};

(function(){
    this.nodeType = this.NODE_ATTRIBUTE;
    
    this.MODIFICATION = 1;
    this.ADDITION = 2;
    this.REMOVAL = 3;
    
    this.serialize = 
    this.toString = function(){
        return this.name + "=\"" + apf.escapeXML(String(this.value)) + "\"";
    };
    
    
    
    this.$setValue = function(value) {
        this.nodeValue = this.value = value;
        this.specified = true;

        //@todo apf3.0 domattr
        this.ownerElement.dispatchEvent("DOMAttrModified", {
            relatedNode: this,
            attrChange: this.MODIFICATION,
            attrName: this.name,
            newValue: value,
            prevValue: this.$lastValue || "",
            bubbles: true
        });
        
        this.$lastValue = value;
    };
    
    this.$triggerUpdate = function(e, oldValue) {
        var name = this.name,
            value = this.value || this.nodeValue,
            host = this.ownerElement,
            isEvent = name.substr(0, 2) == "on";

        if (!this.specified) {
            //@todo This should be generalized
            if (isEvent && this.$lastValue == value
              || name == "id" && host.id) {
                this.specified = true;
                return;
            }
        }

        if (isEvent) {
            if (host.$events[name])
                host.removeEventListener(name.substr(2), host.$events[name]);
            if (value)
                host.addEventListener(name, (host.$events[name] = 
                  (typeof value == "string"
                    ? 
                      new Function('event', value)
                      
                    : value)));
            return;
        }
        
        if (this.specified)
            host.$clearDynamicProperty(name);
        
        if (typeof value == "string" && (host.$attrExcludePropBind[name] || 
          (value.indexOf("{") > -1 || value.indexOf("[") > -1)))
            host.$setDynamicProperty(name, value);
        else
        
        {
            host.setProperty(name, value); //@todo apf3.0 is this a lot slower?
        }
        //host.$handlePropSet(name, value);

        if (this.specified) {
            //@todo apf3.0 domattr - slow?
            host.dispatchEvent("DOMAttrModified", { //@todo this is not good, node might not be specified at init
                relatedNode: this,
                attrChange: this.MODIFICATION,
                attrName: name,
                newValue: value,
                prevValue: this.$lastValue || "",
                bubbles: true
            });
        }
        else this.specified = true;
            
        this.$lastValue = value;
    };
    
    //@todo apf3.0 domattr
    this.addEventListener("DOMNodeInsertedIntoDocument", this.$triggerUpdate);
}).call(apf.AmlAttr.prototype = new apf.AmlNode());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlCDATASection = function(isPrototype) {
    this.nodeType = this.NODE_CDATA_SECTION;
    this.nodeName = "#cdata-section";
    
    this.$init(isPrototype);
};

apf.AmlCDATASection.prototype = new apf.AmlText(true);
apf.AmlCDATASection.prototype.serialize = function(){
    return "<![CDATA[" + this.nodeValue + "]]>";
};




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlComment = function(isPrototype) {
    this.nodeType = this.NODE_COMMENT;
    this.nodeName = "#comment";
    
    this.$init(isPrototype);
};

(function(){
    this.serialize = function(){
        return "<!--" + this.nodeValue + "-->";
    };
    
    this.$setValue = function(value) {
        this.dispatchEvent("DOMCharacterDataModified", {
            bubbles: true,
            newValue: value,
            prevValue: this.nodeValue
        });
    }
}).call(apf.AmlComment.prototype = new apf.AmlCharacterData());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlConfiguration = function(isPrototype) {
    this.parameterNames = [];

    this.$init(isPrototype);
};

(function(){
    this.setParameter = this.setProperty;
    
    this.getParameter = this.getProperty;
    
    this.canSetParameter = function(name, value){ //@todo for value
        return this.parameterNames.indexOf(name) > -1;
    };
}).call(apf.AmlConfiguration.prototype = new apf.Class());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * The AML document. This is the root of the DOM tree and has a nodeType with 
 * value 9 (`apf.NODE_DOCUMENT`). 
 *
 * @class apf.AmlDocument
 * @inherits apf.AmlNode
 * @inherits apf.Class
 * @default_private 
 * @see apf.AmlDom
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.AmlDocument = function(){
    this.$prefixes = {};
    this.$namespaceURIs = {};
    
    this.domConfig = new apf.AmlConfiguration();
    
    
    this.$init();
};

(function() {
    /**
     * The type of node within the document.
     * @type {Number}
     */
    this.nodeType = this.NODE_DOCUMENT;
    this.nodeFunc = apf.NODE_HIDDEN;
    this.nodeName = "#document";
    
    this.$amlLoaded = true;
    
    this.activeElement = null; //@todo alias of window.foccussed;
    this.doctype = null;
    this.domConfig = null;
    this.implementation = null;
    this.characterSet = apf.characterSet;
    
    /**
     * The root element node of the AML application. This is an element with
     * the tagName `'application'`. This is similar to the `'html'` element for regular HTML.
     * @type {apf.AmlNode}
     */
    this.documentElement = null;
    
    /**
     * Gets a AML element based on its id.
     * @param {String} id The id of the AML element to return.
     * @return {apf.AmlElement} The AML element with the id specified.
     */
    this.getElementById = function(id) {
        return self[id];
    };

    /**
     * Returns a list of elements with the given tag name.
     *
     * The subtree below the [[apf.AmlDocument.documentElement]] is searched, excluding the
     * element itself.
     *
     * @param {String}   tagName  The tag name to look for. The special string "*" represents any tag name.
     * @return  {NodeList}  Contains any nodes matching the search string
     */ 
    this.getElementsByTagName = function(tagName) {
        var docEl, res = (docEl = this.documentElement)
            .getElementsByTagName(tagName);

        if (tagName == "*" || docEl.tagName == tagName)
            res.unshift(docEl);
        return res;
    };

    /**
     * Returns a list of elements with the given tag name and the specified namespace URI.
     *
     * The subtree below the [[apf.AmlDocument.documentElement]] is searched, excluding the
     * element itself.
     *
     * @param {String}   namespaceURI  The namespace URI name to look for.
     * @param {String}   tagName  The tag name to look for. The special string "*" represents any tag name.
     * @return  {NodeList}  Contains any nodes matching the search string
     */ 
    this.getElementsByTagNameNS = function(nameSpaceURI, tagName) {
        var docEl,
            res = (docEl = this.documentElement)
                .getElementsByTagNameNS(nameSpaceURI, tagName);

        if (tagName == "*" || docEl.tagName == tagName && docEl.namespaceURI == nameSpaceURI)
            res.unshift(docEl);
        return res;
    };

    /**
     * Creates a new AML element.
     *
     * @param {Mixed} qualifiedName Information about the new node to create. Possible values include:
     *                              - [[String]]:     The tag name of the new element to create
     *                              - [[String]]:    The AML definition for a single or multiple elemnts
     *                              - [[XMLElement]]: The AML definition for a single or multiple elements
     * @return {apf.AmlElement} The created AML element
     */
    this.createElement = function(qualifiedName) {
        return this.$domParser.$createNode(this, this.NODE_ELEMENT, null,
            this.namespaceURI, qualifiedName);
    };

    /**
     * Creates a new AML element within the given namespace.
     *
     * @param {String}   namespaceURI  The namespace URI name to use
     * @param {Mixed} qualifiedName Information about the new node to create. Possible values include:
     *                              - [[String]]:     The tag name of the new element to create
     *                              - [[String]]:     The AML definition for a single or multiple elemnts
     *                              - [[XMLElement]]: The AML definition for a single or multiple elements
     * @return {apf.AmlElement} The created AML element
     */        
    this.createElementNS = function(namespaceURI, qualifiedName) {
        return this.$domParser.$createNode(this, this.NODE_ELEMENT, null,
            namespaceURI, qualifiedName);
    };

    /**
     * Creates a copy of a node from an external document that can be inserted into the current document.
     *
     * @param  {apf.AmlNode}  node  The node to import and copy
     * @param {Boolean} [deep]      Indicates whether the descendants of the imported node should also be imported
     * @return {apf.AmlNode} The imported node
     */     
    this.importNode = function(node, deep) {
        if (deep && node.nodeType == 1) {
            return this.$domParser.parseFromXml(node, {
                doc: this,
                delay: true
            }).childNodes[0];
        }
        else {
            return this.$domParser.$createNode(this, node.nodeType, node);
        }
    };
    
    /**
     * Creates and returns a new attribute node.
     *
     * @param {String}   nodeName  The name of the attribute
     * @return {apf.AmlNode} The attribute node
     */ 
    this.createAttribute = function(nodeName) {
        return this.$domParser.$createNode(this, this.NODE_ATTRIBUTE, null,
            this.nameSpaceURI, nodeName);
    };
    
    /**
     * Creates and returns a new attribute node, within a specified URI.
     *
     * @param {String}  nameSpaceURI  The name of the URI
     * @param {String}   nodeName  The name of the attribute
     * @return {apf.AmlNode} The attribute node
     */ 
    this.createAttributeNS = function(nameSpaceURI, nodeName) {
        return this.$domParser.$createNode(this, this.NODE_ATTRIBUTE, null,
            nameSpaceURI, nodeName);
    };
    /**
     * Creates and returns a new [[apf.AmlEvent]] .
     */     
    this.createEvent = function(){
        return new apf.AmlEvent();
    };

    /**
     * Creates and returns a new comment node.
     * @param {String} nodeValue The data to be added to the comment
     * @return {apf.AmlNode} The comment node
     */    
    this.createComment = function(nodeValue) {
        return this.$domParser.$createNode(this, this.NODE_COMMENT, null, null,
            null, nodeValue);
    };

    /**
     * Creates and returns a new processing instruction node.
     * @param {String} target The target part of the processing instruction node, like `<?_target_ ...?>`
     * @param {String} data The data to be added to the PI
     * @return {apf.AmlNode} The processing instruction node
     */     
    this.createProcessingInstruction = function(target, data) {
        return this.$domParser.$createNode(this, this.NODE_PROCESSING_INSTRUCTION,
            null, null, target, data);
    };
 
    /**
     * Creates and returns a new CDATA section node.
     * @param {String} nodeValue The data to be added to the CDATA node
     * @return {apf.AmlNode} The CDATA section node
     */     
    this.createCDATASection = function(nodeValue) {
        return this.$domParser.$createNode(this, this.NODE_CDATA_SECTION, null,
            null, null, nodeValue);
    };
  
    /**
     * Creates and returns a new Text node.
     * @param {String} nodeValue The data to be added to the text node
     * @return {apf.AmlNode} The Text node
     */      
    this.createTextNode = function(nodeValue) {
        return this.$domParser.$createNode(this, this.NODE_TEXT, null, null,
            null, nodeValue);
    };

    /**
     * Creates and returns a new document fragment.
     */ 

    this.createDocumentFragment = function(){
        return this.$domParser.$createNode(this, this.NODE_DOCUMENT_FRAGMENT);
    };

    // @todo
    this.querySelector = function(){};
 
     // @todo   
    this.querySelectorAll = function(){};

    

    // @todo
    this.hasFocus = function(){
        
    }

    
}).call(apf.AmlDocument.prototype = new apf.AmlNode());





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlDocumentFragment = function(isPrototype) {
    this.$init(isPrototype);
};

apf.AmlDocumentFragment.prototype = new apf.AmlNode();
apf.AmlDocumentFragment.prototype.nodeName = "#document-fragment";
apf.AmlDocumentFragment.prototype.nodeType = 
    apf.AmlDocumentFragment.prototype.NODE_DOCUMENT_FRAGMENT;




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Implementation of the W3C event object. An instance of this class is passed as
 * the first argument of any event handler. As per event, it contains different
 * properties giving context based information about the event.
 * @class apf.AmlEvent
 * @default_private
 */
apf.AmlEvent = function(name, data) {
    this.name = name;
    
    var prop;
    for (prop in data)
        this[prop] = data[prop];
};

apf.AmlEvent.prototype = {
    
    bubbles: false,
    cancelBubble: false,
    

    /**
     * Cancels the event (if it is cancelable), without stopping further 
     * propagation of the event. 
     */
    preventDefault: function(){
        this.returnValue = false;
    },

    
    /**
     * Prevents further propagation of the current event. 
     */
    stopPropagation: function(){
        this.cancelBubble = true;
    },
    

    stop: function() {
        this.returnValue = false;
        
        this.cancelBubble = true;
        
    }
};




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


//@todo apf3.0
apf.AmlNamedNodeMap = function(host) {
    this.$host = host;
};

(function(){
    this.getNamedItem = function(name) {
        for (var i = 0; i < this.length; i++) {
            if (this[i].name == name)
                return this[i];
        }
        return false;
    };
    
    this.setNamedItem = function(node) {
        var name = node.name;
        for (var item, i = this.length - 1; i >= 0; i--) {
            if (this[i].name == name) {
                this[i].ownerElement = null;
                this.splice(i, 1);
                break;
            }
        }
        
        this.push(node);
        
        node.ownerElement = this.$host;
        node.ownerDocument = this.$host.ownerDocument;
        node.$triggerUpdate();
    };
    
    //@todo apf3.0 domattr
    this.removeNamedItem = function(name) {
        //Should deconstruct dynamic properties
        
        for (var item, i = this.length - 1; i >= 0; i--) {
            if (this[i].name == name) {
                item = this[i];
                this.splice(i, 1);
                break;
            }
        }
        if (!item) return false;

        //@todo hack!
        //this should be done properly
        var oldValue = item.nodeValue;
        item.nodeValue = item.value = "";
        item.$triggerUpdate(null, oldValue);
        item.ownerElement = null;
        item.nodeValue = item.value = oldValue;
        
        return item;
    };
    
    this.item = function(i) {
        return this[i];
    };

    //if (apf.isIE < 8) { //Only supported by IE8 and above
        this.length = 0;
        
        this.splice = function(pos, length) {
            for (var i = pos, l = this.length - length; i < l; i++) {
                this[i] = this[i + 1];
            }
            delete this[i];
            this.length -= length;
        }
        
        this.push = function(o) {
            this[this.length++] = o;
            return this.length;
        }
    //}
    
    this.join = function(glue) {
        var x = [];
        for (var e, a, i = 0, l = this.length; i < l; i++) {
            if ((e = (a = this[i]).ownerElement) && e.$inheritProperties[a.nodeName] != 2)
                x.push(this[i]);
        }
        return x.join(glue);
    }
}).call(apf.AmlNamedNodeMap.prototype = {}); //apf.isIE < 8 ? {} : []




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlProcessingInstruction = function(isPrototype) {
    this.$init(isPrototype);
};

(function(){
    this.nodeType = this.NODE_PROCESSING_INSTRUCTION;
    
    /*
     * @todo docs
     */
    this.data = null;
    
    /*
     * @todo docs
     */
    this.target = null;
    
    this.serialize = function(){
        return "<?" + this.target + "\n" + apf.escapeXML(this.nodeValue) + "\n?>";
    };
    
    this.reload = function(){
        this.$handlePropSet("data", this.data);
    };
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        calcdata: 0 //Start in code mode
    }, this.$attrExcludePropBind);
    
    this.getAttribute = function(){};
    this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute;
    this.$supportedProperties = [];
    this.$propHandlers = {};
    this.$booleanProperties = {};
    this.$inheritProperties = {};
    
    
    
    this.$setValue = function(value) {
        this.setProperty("data", value);
    };
    
    this.$handlePropSet = function(prop, value, force) {
        this[prop] = value;
        if (prop == "data") {
            this.$clearDynamicProperty("calcdata");
            this.$setDynamicProperty("calcdata", value);
        }
        
        else if (prop == "target") {
            //not implemented
        }
        else if (this.$propHandlers[prop]) {
            this.$propHandlers[prop].call(this, value, prop);
        }
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var pHtmlNode = e.pHtmlNode;
        if (!pHtmlNode && (this.parentNode.$bindingRule 
          || !(pHtmlNode = this.parentNode.$int))) 
            return;

        pHtmlNode.appendChild(this.$ext = document.createElement("span"));
        this.$ext.host = this;

        

        this.$setDynamicProperty("calcdata", this.data);
        
        
    }, true);
    
    /*this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        this.$clearDynamicProperty("calcdata");
    });*/
    
    this.$destroy = function(){
        this.$clearDynamicProperty("calcdata");
        this.$propHandlers["calcdata"].call(this, "");
    };
}).call(apf.AmlProcessingInstruction.prototype = new apf.AmlNode());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.AmlTextRectangle = function(host) {
    var _self = this;
    function handler(){
        var pos = _self.getAbsolutePosition(_self.$ext);
        _self.setProperty("left", pos[0]);
        _self.setProperty("top", pos[1]);
        _self.setProperty("right", document.documentElement.offsetWidth - pos[0]);
        _self.setProperty("bottom", document.documentElement.offsetWidth - pos[1]);
    }
    
    host.addEventListener("prop.width", handler);
    host.addEventListener("prop.height", handler);
    host.addEventListener("prop.left", handler);
    host.addEventListener("prop.top", handler);

    handler.call(host);
};
apf.AmlTextRectangle.prototype = new apf.Class();




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/*
 * An object creating the XHTML namespace for the aml parser.
 *
 * @constructor
 * @parser
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.xhtml = new apf.AmlNamespace();
apf.setNamespace("http://www.w3.org/1999/xhtml", apf.xhtml);


/*
if (apf.getTextNode(x)) {
    var data = {
        amlNode: x,
        htmlNode: o
    }

    
}

*/



/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.XhtmlElement = function(struct, tagName) {
    this.$init(tagName || true, apf.NODE_VISIBLE, struct);
    
    this.$xoe = this.addEventListener;
    this.addEventListener = this.$xae;
    this.removeEventListener = this.$xre;
    
    var _self = this;
    this.$de = function(e) {
        _self.dispatchEvent(e.type, null, e);
    }
};

(function(){
    var excludedEvents = {
        "contextmenu": 1,
        "keydown": 1,
        "keypress": 1,
        "keyup": 1,
        "DOMNodeInserted": 2,
        "DOMNodeInsertedIntoDocument": 2,
        "DOMNodeRemoved": 2,
        "DOMNodeRemovedFromDocument": 2
    };
    
    this.$xae = function(type, fn) {
        this.$xoe.apply(this, arguments);
        
        if (excludedEvents[type] > (this.editable ? 0 : 1)
          || type.substr(0, 5) == "prop.")
            return;
        
        if (this.$ext) {
            if (type.substr(0,2) == "on")
                type = type.substr(2);
            apf.addListener(this.$ext, type, this.$de);
        }
    };
    
    this.$xre = function(type, fn) {
        apf.AmlElement.prototype.removeEventListener.apply(this, arguments);
        
        
        
        if (this.$ext)
            apf.removeListener(this.$ext, type, this.$de);
    }
    
    this.$handlePropSet = function(name, value, force, inherit) {
        if (this.$booleanProperties[name])
            value = apf.isTrue(value);

        this[name] = value;
        var handler = this.$propHandlers && this.$propHandlers[name]
          || apf.GuiElement.propHandlers[name];

        if (handler)
            handler.call(this, value, null, name);
        else if (this.$int && (force || this.$amlLoaded)) {
            this.$int.setAttribute(apf.isIE && apf.isIE < 8 && name == "class" 
                ? "className" : name, value);
        }
    };
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var pHtmlNode;
        if (!(pHtmlNode = this.$pHtmlNode = this.parentNode.$int)) 
            return;

        var str, aml = this.$aml;
        if (aml) {
            if (aml.serialize)
                str = aml.serialize();
            else {
                aml = aml.cloneNode(false);
                str = aml.xml || aml.nodeValue;
            }

            str = str.replace(/ on\w+="[^"]*"| on\w+='[^']*'/g, "");
            
            this.$ext = 
            this.$int = apf.insertHtmlNode(null, pHtmlNode, null, apf.html_entity_decode(str));
        }
        else {
            this.$ext = this.$int = 
              pHtmlNode.appendChild(document.createElement(this.localName));
        }
        
        if (this.localName != "a")
            this.$ext.host = this;

        this.style = this.$ext.style;
    }, true);
    
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.$amlLoaded = true;
        
        if (this.$setLayout)
            this.$setLayout();
    });
    
}).call(apf.XhtmlElement.prototype = new apf.AmlElement());

apf.Init.addConditional(function(){
    if (apf.isO3) return;
    var prot = apf.XhtmlElement.prototype;

    //prot.implement(apf.Interactive);
    prot.implement(
        
        apf.Anchoring
        
    );

    
    prot.$drawn = true;
    prot.$setLayout = apf.GuiElement.prototype.$setLayout;
    
    prot.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget == this 
          && "vbox|hbox|table".indexOf(this.parentNode.localName) == -1) {
            this.$setLayout();
        }
    }); 
    
}, null, ["interactive"]);

apf.xhtml.setElement("@default", apf.XhtmlElement);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.XhtmlBodyElement = function(struct, tagName) {
    this.$init(tagName || "body", apf.NODE_VISIBLE, struct);
};

(function(){
    
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (!this.ownerDocument.body)
            this.ownerDocument.body = this;
        
        this.$ext = 
        this.$int = document.body;
    }, true);
}).call(apf.XhtmlBodyElement.prototype = new apf.AmlElement());

apf.Init.addConditional(function(){
    if (apf.isO3) return;
    var prot = apf.XhtmlBodyElement.prototype;

    
}, null, ["interactive"]);

apf.xhtml.setElement("body", apf.XhtmlBodyElement);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @todo description
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.XhtmlHtmlElement = function(struct, tagName) {
    this.$init(tagName || "html", apf.NODE_VISIBLE, struct);
    
    
    
    this.$ext = document.documentElement;
    this.$ext.host = this;
    
    this.$int = document.body;
    this.$tabList = []; //Prevents documentElement from being focussed
    this.$focussable = apf.KEYBOARD;
    this.focussable = true;
    this.visible = true;
    this.$isWindowContainer = true;
    //this.focus = function(){ this.dispatchEvent("focus"); };
    //this.blur = function(){ this.dispatchEvent("blur"); };
    
    this.implement(apf.Focussable);
    
    
    apf.window.$addFocus(this);
    
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var i, l, n, a, c,
            attr = this.attributes, doc = this.ownerDocument;
        for (i = 0, l = attr.length; i < l; i++) {
            n = (a = attr[i]).nodeName.split(":");
            if (n[0] == "xmlns") {
                if (c = n[1]) {
                    doc.$prefixes[c] = a.nodeValue;
                    doc.$namespaceURIs[a.nodeValue] = c;
                }
                else {
                    doc.namespaceURI = a.nodeValue;
                }
            }
        }
        
        if (!doc.namespaceURI)
            doc.namespaceURI = apf.ns.xhtml;
    });
};
apf.XhtmlHtmlElement.prototype = new apf.XhtmlElement();

apf.xhtml.setElement("html", apf.XhtmlHtmlElement);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.XhtmlIgnoreElement = function(struct, tagName) {
    this.$init(tagName, apf.NODE_VISIBLE, struct);
};

apf.XhtmlIgnoreElement.prototype = new apf.AmlElement();

apf.xhtml.setElement("script",   apf.XhtmlIgnoreElement);
apf.xhtml.setElement("noscript", apf.XhtmlIgnoreElement);
apf.xhtml.setElement("head",     apf.XhtmlIgnoreElement);
apf.xhtml.setElement("meta",     apf.XhtmlIgnoreElement);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.XhtmlInputElement = function(struct, tagName) {
    this.$init(tagName || "input", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$xae = apf.XhtmlElement.prototype.$xae;
    this.$xre = apf.XhtmlElement.prototype.$xre;
    this.$handlePropSet = function(name, value, force) {
        if (name == "type")
            return;

        return apf.XhtmlElement.prototype.$handlePropSet.call(this, name, value, force);
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var pHtmlNode;
        if (!(pHtmlNode = this.parentNode.$int))
            return;

        if (this.$aml) {
            this.$ext =
            this.$int = apf.insertHtmlNode(this.$aml.serialize
                ? this.$aml
                : this.$aml.cloneNode(false), pHtmlNode);
        }
        else {
            this.$ext = this.$int = document.createElement(this.localName);
            if (this.getAttribute("type"))
                this.$int.setAttribute("type", this.getAttribute("type"));
            pHtmlNode.appendChild(this.$int);
        }
    }, true);
}).call(apf.XhtmlInputElement.prototype = new apf.AmlElement());

apf.xhtml.setElement("input", apf.XhtmlInputElement);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.XhtmlOptionElement = function(struct, tagName) {
    this.$init(tagName || "option", apf.NODE_VISIBLE, struct);
};

(function(){
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.$ext = 
        this.$int = this.parentNode.$int.appendChild(
          this.parentNode.$int.ownerDocument.createElement("option"));

        if (this.value)
            this.$int.setAttribute("value", this.value);
    }, true);
}).call(apf.XhtmlOptionElement.prototype = new apf.AmlElement());

apf.xhtml.setElement("option", apf.XhtmlOptionElement);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.XhtmlSkipChildrenElement = function(struct, tagName) {
    this.$init(tagName, apf.NODE_VISIBLE, struct);
};

(function(){
    this.canHaveChildren = false;
    
    this.$redraw = function(){
        var _self = this;
        apf.queue.add("redraw" + this.$uniqueId, function(){
            var pHtmlNode = _self.$ext.parentNode;
            var beforeNode = _self.$ext.nextSibling;
            pHtmlNode.removeChild(_self.$ext);
            
            _self.$ext = apf.insertHtmlNode(null, pHtmlNode, beforeNode, _self.$aml 
                ? (_self.$aml.serialize ? _self.$aml.serialize() : _self.$aml.xml)
                : _self.serialize());
        });
    }
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var pHtmlNode;
        if (!(pHtmlNode = this.parentNode.$int)) 
            return;

        this.$ext = apf.insertHtmlNode(null, pHtmlNode, null, this.$aml 
            ? (this.$aml.serialize ? this.$aml.serialize() : this.$aml.xml)
            : this.serialize());
    }, true);
}).call(apf.XhtmlSkipChildrenElement.prototype = new apf.AmlElement());

apf.xhtml.setElement("object", apf.XhtmlSkipChildrenElement);
apf.xhtml.setElement("embed", apf.XhtmlSkipChildrenElement);
apf.xhtml.setElement("table", apf.XhtmlSkipChildrenElement);

apf.xhtml.setElement("pre", apf.XhtmlSkipChildrenElement);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/* #-ifdef __WITH_HTML5
if (tagName == "input") {
    objName = apf.HTML5INPUT[objName = x.getAttribute("type")]
        || objName || "textbox";
}
//#-endif*/




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





//XForms







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Object creating the XML Include namespace for the aml parser.
 *
 * @constructor
 * @parser
 *
 * @allownode simpleType, complexType
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.xinclude = new apf.AmlNamespace();
apf.setNamespace("http://www.w3.org/2001/XInclude", apf.xinclude);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Defines a list of acceptable values
 */
apf.XiInclude = function(struct, tagName) {
    this.$init(tagName || "include", apf.NODE_HIDDEN, struct);
};

apf.xinclude.setElement("include", apf.XiInclude);
apf.aml.setElement("include", apf.XiInclude);

//@todo test defer="true" situation
(function(){
    this.$parsePrio = "002";

    //1 = force no bind rule, 2 = force bind rule 
    /*this.$attrExcludePropBind = apf.extend({
        href: 1,
        src: 1
    }, this.$attrExcludePropBind);*/

    this.$propHandlers["href"] = 
    this.$propHandlers["src"] = function(value) {
        if (typeof value != "string")
            return finish.call(this, value);
        
        if (value.trim().charAt(0) == "<") {
            loadIncludeFile.call(this, value.trim());
            return;
        }

        this.$path = value.charAt(0) == "{" //@todo this shouldn't happen anymore
          ? value
          : apf.getAbsolutePath(apf.hostPath, value);
        
        var domParser = this.ownerDocument.$domParser;
        if (!this.defer) {
            domParser.$pauseParsing.apply(domParser, 
              this.$parseContext = domParser.$parseContext || [this.parentNode]);
        }

        //var basePath = apf.hostPath;//only for recursion: apf.getDirname(xmlNode.getAttribute("filename")) || 
        loadIncludeFile.call(this, this.$path);
    };
    
    function done(xmlNode) {
        var addedNode = this.previousSibling || this.nextSibling;
        
        if (this.callback) {
            this.callback({
                xmlNode: xmlNode,
                amlNode: this.parentNode,
                addedNode: addedNode
            })
        }
        
        addedNode.dispatchEvent("DOMNodeInserted", {
            $beforeNode: addedNode.nextSibling,
            relatedNode: this.parentNode,
            $isMoveWithinParent: false,
            bubbles: true
        });
        
        //@todo hack!! this should never happen. Find out why it happens
        if (this.parentNode)
            this.parentNode.removeChild(this);
    }
    
    function finish(xmlNode) {
        var domParser = this.ownerDocument.$domParser;

        if (this.clear)
            this.parentNode.$int.innerHTML = "";

        if (xmlNode) {
            domParser.parseFromXml(xmlNode, {
                doc: this.ownerDocument,
                amlNode: this.parentNode,
                beforeNode: this,
                include: true
            });

            if (!this.defer && this.$parseContext) {
                var o = (this.$parseContext[1] || (this.$parseContext[1] = {})),
                    cb = o.callback,
                    _self = this;

                o.callback = function(){
                    done.call(_self, xmlNode);
                    if (cb)
                        cb.call(_self.ownerDocument);
                };

                //@todo this is wrong... probably based on load order of last include element. Please rearchitect parse continuation.
                if (domParser.$continueParsing(this.$parseContext[0]) === false) {
                    var o2 = (domParser.$parseContext[1] || (domParser.$parseContext[1] = {})),
                        cb2 = o.callback;
                    o2.callback = function(){
                        if (cb)
                            cb.call(_self.ownerDocument);
                        domParser.$continueParsing(_self.$parseContext[0]);
                    };
                }
            }
            else
                done.call(this, xmlNode);
        }
        else {
            if (!this.defer)
                domParser.$continueParsing(this.$parseContext[0]);
            
            done.call(this, xmlNode);
        }
    }
    
    function loadIncludeFile(path) {
        

        var _self = this;
        apf.getData(path, apf.extend(this.options || {}, {
            
            callback: function(xmlString, state, extra) {
                if (state != apf.SUCCESS) {
                    var oError = new Error(apf.formatErrorString(1007,
                        _self, "Loading Includes", "Could not load Include file '"
                        + (path || _self.src)
                        + "'\nReason: " + extra.message));

                    if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
                        return true;

                    apf.console.error(oError.message);

                    finish.call(_self, null);

                    //throw oError;
                    return;
                }

                //@todo apf3.0 please make one way of doing this
                xmlString = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "")
                    .replace(/^[\r\n\s]*/, ""); //.replace(/&nbsp;/g, " ")
                if (!apf.supportNamespaces)
                    xmlString = xmlString.replace(/xmlns\=\"[^"]*\"/g, "");
                
                if (xmlString.indexOf("<a:application") == -1)
                    xmlString = "<a:application xmlns:a='" + apf.ns.aml +"'>"
                      + xmlString + "</a:application>";

                var xmlNode = apf.getXml(xmlString, null, true);//apf.getAmlDocFromString(xmlString);
            
                if (!xmlNode) {
                    throw new Error(apf.formatErrorString(0, null,
                        "Loading include",
                        "Could not parse include file. Maybe the file does not exist?", xmlNode));
                }
                xmlNode.setAttribute("filename", extra.url);

                

                finish.call(_self, xmlNode); //@todo add recursive includes support here
            },
            async: true,
            ignoreOffline: true
        }));
    }
}).call(apf.XiInclude.prototype = new apf.AmlElement());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
apf.__LIVEEDIT__ = 1 << 23;






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__ANCHORING__ = 1 << 13;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have anchoring features. Each side of the
 * element can be attached at a certain distance to its parent's rectangle.
 *
 * When the parent is resized, the anchored side of the element stays
 * at the specified distance at all times. If both sides are anchored, the
 * element size is changed to make sure the specified distance is maintained.
 *
 * #### Example
 *
 * This example shows a bar that has a 10% margin around it, and contains a
 * frame that is displayed using different calculations and settings.
 *
 * ```xml
 *  <a:bar width="80%" height="80%" top="10%" left="10%">
 *      <a:frame 
 *        caption = "Example" 
 *        left = "50%+10"
 *        top = "100"
 *        right = "10%"
 *        bottom = "Math.round(0.232*100)" />
 *  </a:bar>
 * ```
 *
 * ### Remarks
 *
 * This is one of three positioning methods. The other two are Alignment and Grid.
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.3
 * @baseclass
 * @layout
 */
apf.Anchoring = function(){
    this.$regbase = this.$regbase | apf.__ANCHORING__;
    this.$anchors = [];

    var VERTICAL = 1;
    var HORIZONTAL = 2;

    this.$updateQueue = 0;
    this.$inited = 
    this.$parsed = 
    this.$anchoringEnabled = false;
    this.$hordiff = 
    this.$verdiff = 0;
    this.$rule_v = 
    this.$rule_h = 
    this.$rule_header = "";

    var l = apf.layout;
    
    this.$supportedProperties.push("anchors");
    
    var propHandlers = {
        "right" : function(value, prop) {
            if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
                return;
            
            if (!value && value !== 0)
                this.$ext.style[prop] = "";

            //@note Removed apf.isParsing here to activate general queuing
            if (!this.$updateQueue)
                l.queue(this.$pHtmlNode, this);
            this.$updateQueue = this.$updateQueue | HORIZONTAL;
        },

        "bottom" : function(value, prop) {
            if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
                return;

            if (!value && value !== 0)
                this.$ext.style[prop] = "";

            //@note Removed apf.isParsing here to activate general queuing            
            if (!this.$updateQueue)
                l.queue(this.$pHtmlNode, this);
            this.$updateQueue = this.$updateQueue | VERTICAL;
        }
    };
    propHandlers.left = propHandlers.width = propHandlers.right;
    propHandlers.top = propHandlers.height = propHandlers.bottom;
    
    this.$propHandlers["anchors"] = function(value) {
        this.$anchors = value ? value.splitSafe("(?:, *| )") : [];

        if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
            return;

        if (!this.$updateQueue && apf.loaded)
            l.queue(this.$pHtmlNode, this);
        this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL;
    };

    /**
     * Turns anchoring off.
     *
     */
    this.$disableAnchoring = function(activate) {
        //!this.$parsed || 
        if (!this.$inited || !this.$anchoringEnabled || !this.$pHtmlNode)
            return;

        l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
        if (l.queue)
            l.queue(this.$pHtmlNode);

        for (var prop in propHandlers) {
            delete this.$propHandlers[prop];
        }

        this.removeEventListener("DOMNodeRemoved", remove); 
        this.removeEventListener("DOMNodeInserted", reparent); 

        if (this.$ext) {
            this.$ext.style.left = 
            this.$ext.style.right = 
            this.$ext.style.top = 
            this.$ext.style.bottom = 
            this.$ext.style.width = 
            this.$ext.style.height = 
            this.$ext.style.position = "";
        }
        
        /*if (this.right)
            this.$ext.style.left = apf.getHtmlLeft(this.$ext) + "px";

        if (this.bottom)
            this.$ext.style.top = apf.getHtmlTop(this.$ext) + "px";*/

        this.removeEventListener("prop.visible", visibleHandler);

        this.$inited = false;
        this.$anchoringEnabled = false; //isn't this redundant?
    };


    /**
     * @attribute {Number | String} [left]   Sets or gets a way to determine the amount of pixels from the left border of this element to the left edge of it's parent's border. This attribute can also contain percentages, arithmetic and even full expressions.
     * 
     * #### Example
     *
     * ```xml
     * <a:bar left="(20% + 10) * SOME_JS_VAR" />
     * ```
     */
    /**
     * @attribute {Number | String} [right]  Sets or gets a way to determine the amount of pixels from the right border of this element to the right edge of its parent's border.
     *                                      This attribute can also contain percentages, arithmetic and even full expressions.
     * 
     * #### Example
     *
     * ```xml
     * <a:bar right="(20% + 10) * SOME_JS_VAR" />
     * ```
     */
    /** 
     * @attribute {Number | String} [width]  Sets or gets a way to determine the amount of pixels from the left border to the right border of this element.
     *                                      This attribute can also contain percentages, arithmetic and even full expressions.
     * 
     * #### Example
     *
     * ```xml
     * <a:bar width="(20% + 10) * SOME_JS_VAR" />
     * ```
     */
    /** 
     * @attribute {Number | String} [top]    Sets or gets a way to determine the amount of pixels from the top border of this element to the top edge of its parent's border.
     *                                      This attribute can also contain percentages, arithmetic and even full expressions.
     * 
     * #### Example
     *
     * ```xml
     * <a:bar top="(20% + 10) * SOME_JS_VAR" />
     * ```
     */
    /** 
     * @attribute {Number | String} [bottom] Sets or gets a way to determine the amount of pixels from the bottom border of this element to the bottom edge of its parent's border.
     *                                      This attribute can also contain percentages, arithmetic and even full expressions.
     * 
     * #### Example
     *
     * ```xml
     * <a:bar bottom="(20% + 10) * SOME_JS_VAR" />
     * ```
     */
    /** 
     * @attribute {Number | String} [height] Sets or gets a way to determine the amount of pixels from the top border to the bottom border of this element.
     *                                      This attribute can also contain percentages, arithmetic and even full expressions.
     * 
     * #### Example
     *
     * ```xml
     * <a:bar height="(20% + 10) * SOME_JS_VAR" />
     * ```
     */
    /*
     * Enables anchoring based on attributes set in the AML of this element
     */
    this.$enableAnchoring = function(){
        if (this.$inited) //@todo add code to reenable anchoring rules (when showing)
            return;

        // *** Properties and Attributes *** //
        apf.extend(this.$propHandlers, propHandlers);

        // *** Event handlers *** //
        this.addEventListener("DOMNodeRemoved", remove); 
        this.addEventListener("DOMNodeInserted", reparent); 
        this.addEventListener("prop.visible", visibleHandler);

        this.$updateQueue = 0 
            | ((this.left || this.width || this.right || this.anchors) && HORIZONTAL) 
            | ((this.top || this.height || this.bottom || this.anchors) && VERTICAL) ;

        if (this.$updateQueue)
            l.queue(this.$pHtmlNode, this);

        this.$inited = true;
        this.$anchoringEnabled = true;
    };
    
    function visibleHandler(e) {
        if (!(this.$rule_header || this.$rule_v || this.$rule_h) || !this.parentNode)
            return;

        if (e.value) {
            if (this.$rule_v || this.$rule_h) {
                var rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h;
                l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules);
                //this.$ext.style.display = "none";
                l.queue(this.$pHtmlNode, this);
            }
            l.processQueue();
        }
        else {
            l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
            l.queue(this.$pHtmlNode)
        }
    }
    
    function remove(e) {
        if (e && (e.$doOnlyAdmin || e.currentTarget != this))
            return;

        if (l.queue && this.$pHtmlNode) {
            l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
            l.queue(this.$pHtmlNode)
        }
    }

    function reparent(e) {
        if (!this.$amlLoaded || e.currentTarget != this)
            return;

        if (!e.$isMoveWithinParent && this.$parsed) //@todo hmm weird state check
            this.$moveAnchoringRules(e.$oldParentHtmlNode);
        //else if (e.relatedNode == this) //@todo test this
            //e.currentTarget.$setLayout("anchoring");
    }

    this.$moveAnchoringRules = function(oldParent, updateNow) {
        var rules = oldParent && l.removeRule(oldParent, this.$uniqueId + "_anchors");
        if (rules)
            l.queue(oldParent);

        if (!this.$rule_v && !this.$rule_h && !this.$rule_header)
            return;

        this.$rule_header = getRuleHeader.call(this);
        rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h;

        //@todo sometimes the content is not displayed anymore (when reparenting by xinclude)
        //this.$ext.style.display = "none";

        l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules);
        l.queue(this.$pHtmlNode, this);
    };

    this.$hasAnchorRules = function(){
        return this.$rule_v || this.$rule_h ? true : false;
    };

    function getRuleHeader(){
        if (!this.$pHtmlDoc) return "";
        return "try{\
            var oHtml = " + (apf.hasHtmlIdsInJs
                ? this.$ext.getAttribute("id")
                : "document.getElementById('"
                    + this.$ext.getAttribute("id") + "')") + ";\
            \
            var pWidth = " + (this.$pHtmlNode == this.$pHtmlDoc.body
                ? "apf.getWindowWidth()" //@todo only needed for debug?
                : "apf.getHtmlInnerWidth(oHtml.parentNode)") + ";\
            \
            var pHeight = " + (this.$pHtmlNode == this.$pHtmlDoc.body
                ? "apf.getWindowHeight()" //@todo only needed for debug?
                : "apf.getHtmlInnerHeight(oHtml.parentNode)") + ";\
            }catch(e){\
            }";
    }

    /**
     * Sets the anchoring percentage.
     * @param {String} expr An expression that's converted to a string
     * @param {Number} An integer value that's used to convert to a percentage; for example, 50 becomes .5
     * @returns {String} The anchor percentage
     */
    function setPercentage(expr, value) {
        return String(expr).replace(apf.percentageMatch, "((" + value + " * $1)/100)");
    }

     
    this.$recalcAnchoring = function(queueDelay) {
        this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL;
        this.$updateLayout();
        l.queue(this.$pHtmlNode, this);
        
        if (!queueDelay)
            l.processQueue();
    };
    

    function visCheck(){
        if (this.$updateQueue) {
            this.$updateLayout();
            apf.layout.activateRules(this.$ext.parentNode);
        }
    }

    this.$updateLayout = function(){
        if (!this.$anchoringEnabled)
            return;

        if (!apf.window.vManager.check(this, "anchoring", visCheck))
            return;

        if (!this.$parsed) {
            if (!this.$ext.getAttribute("id"))
                apf.setUniqueHtmlId(this.$ext);

            this.$rule_header = getRuleHeader.call(this);
            this.$parsed = true;
        }

        if (!this.$updateQueue) {
            if (this.visible && this.$ext.style.display == "none")
                this.$ext.style.display = "";
            return;
        }

        if (this.draggable == "relative") {
            if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1) //@todo apf3.1 the IDE doesn't like this
                this.$ext.style.position = "absolute";
        }
        else if (this.left || this.left ===  0 || this.top || this.top === 0 
          || this.right || this.right === 0 || this.bottom || this.bottom === 0 
          || this.$anchors.length) {
            if ("absolute|fixed".indexOf(apf.getStyle(this.$ext, "position")) == -1)
                this.$ext.style.position = "absolute";
        }
        else if (!this.center) {
            if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1)
                this.$ext.style.position = "relative";
            if (!this.width)
                this.$ext.style.width = "";
            if (!this.height)
                this.$ext.style.height = "";
        }

        var rules;
        if (this.$updateQueue & HORIZONTAL) {
            rules = [];
            
            this.$hordiff = apf.getWidthDiff(this.$ext);

            var left = this.$anchors[3] || this.left,
                right = this.$anchors[1] || this.right,
                width = this.width, hasLeft = left || left === 0,
                hasRight = right || right === 0, 
                hasWidth = width || width === 0;

            if (right && typeof right == "string")
                right = setPercentage(right, "pWidth");

            if (hasLeft) {
                if (parseInt(left) != left) {
                    left = setPercentage(left,  "pWidth");
                    rules.push("oHtml.style.left = (" + left + ") + 'px'");
                }
                else
                    this.$ext.style.left = left + "px";
            }
            if ((apf.hasStyleAnchors || !hasLeft) && hasRight) {
                if (parseInt(right) != right) {
                    right = setPercentage(right, "pWidth");
                    rules.push("oHtml.style.right = (" + right + ") + 'px'");
                }
                else
                    this.$ext.style.right = right + "px";
            }

            if (hasLeft && hasRight) { //right != null && left != null) {
                if (!apf.hasStyleAnchors)
                    rules.push("oHtml.style.width = (pWidth - (" + right
                        + ") - (" + left + ") - " + this.$hordiff + ") + 'px'");
                else
                    this.$ext.style.width = "";
            }
            else if (hasWidth && typeof this.maxwidth == "number" && typeof this.minwidth == "number") {
                if (parseInt(width) != width) {
                    this.width = width = (this.width || "").replace(/--(\d+)/, "-(-$1)");
                    width = setPercentage(width, "pWidth");
                    rules.push("oHtml.style.width = Math.max(" 
                        + (this.minwidth - this.$hordiff)
                        + ", Math.min(" + (this.maxwidth - this.$hordiff) + ", "
                        + width + " - " + this.$hordiff + ")) + 'px'");
                }
                else {
                    this.$ext.style.width = ((width > this.minwidth
                        ? (width < this.maxwidth
                            ? width
                            : this.maxwidth)
                        : this.minwidth) - this.$hordiff) + "px";
                }
            }

            this.$rule_h = (rules.length
                ? "try{" + rules.join(";}catch(e) {};try{") + ";}catch(e) {};"
                : "");
        }

        if (this.$updateQueue & VERTICAL) {
            rules = [];

            this.$verdiff = apf.getHeightDiff(this.$ext);

            var top = this.$anchors[0] || this.top,
                bottom = this.$anchors[2] || this.bottom,
                height = this.height, hasTop = top || top === 0,
                hasBottom = bottom || bottom === 0, 
                hasHeight = height || height === 0;

            if (bottom && typeof bottom == "string")
                bottom = setPercentage(bottom, "pHeight");

            if (hasTop) {
                if (parseInt(top) != top) {
                    top = setPercentage(top, "pHeight");
                    rules.push("oHtml.style.top = (" + top + ") + 'px'");
                }
                else
                    this.$ext.style.top = top + "px";
            }
            if ((apf.hasStyleAnchors || !hasTop) && hasBottom) {
                if (parseInt(bottom) != bottom) {
                    rules.push("oHtml.style.bottom = (" + bottom + ") + 'px'");
                }
                else
                    this.$ext.style.bottom = bottom + "px";
            }
            if (hasTop && hasBottom) { //bottom != null && top != null) {
                if (!apf.hasStyleAnchors)
                    rules.push("oHtml.style.height = (pHeight - (" + bottom +
                        ") - (" + top + ") - " + this.$verdiff + ") + 'px'");
                else
                    this.$ext.style.height = "";
            }
            else if (hasHeight && typeof this.minheight == "number") {
                if (parseInt(height) != height) {
                    height = setPercentage(height, "pHeight");
                    rules.push("oHtml.style.height = Math.max(" 
                        + (this.minheight - this.$verdiff)
                        + ", Math.min(" + (this.maxheight - this.$verdiff) + ", "
                        + height + " - " + this.$verdiff + ")) + 'px'");
                }
                else {
                    this.$ext.style.height = Math.max(0, (height > this.minheight
                        ? (height < this.maxheight
                            ? height
                            : this.maxheight)
                        : this.minheight) - this.$verdiff) + "px";
                }
            }

            this.$rule_v = (rules.length
                ? "try{" + rules.join(";}catch(e) {};try{") + ";}catch(e) {};"
                : "");
        }

        if (this.$rule_v || this.$rule_h) {
            l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors",
                this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h, true);
        }
        else {
            l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
        }

        this.$updateQueue = 0;
        
        if (this.$box && !apf.hasFlexibleBox) //temporary fix
            apf.layout.forceResize(this.$ext);
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //if (this.$updateQueue)
            //this.$updateLayout();
    });

    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        this.$disableAnchoring();
    });
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
apf.__CONTENTEDITABLE__ = 1 << 24;





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__GUIELEMENT__ = 1 << 15;
apf.__VALIDATION__ = 1 << 6;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} are an AML component.
 *
 * @class apf.GuiElement
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @baseclass
 * @inherits apf.AmlElement
 * @inherits apf.Anchoring
 * @inherits apf.DelayedRender
 * @inherits apf.DragDrop
 * @inherits apf.Focussable
 * @inherits apf.Interactive
 * @inherits apf.Validation
 *
 */
/**
 * @attribute {String} span     Sets or gets the number of columns that this element spans. Only used inside a table element.
 */
/**
 * @attribute {String | Number} margin  Sets or gets margin values. 
 *
 * Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off margins.
 */
/**
 * @attribute {String} align Sets or gets the edge of the parent to which this
 *                                  element aligns. 
 *
 * The possible values are a combination of "left", "middle", "right", "top", "bottom" and "slider" ,and optionally a size. 
 * Combinations are combined with the pipe (`"|"`) character.
 * 
 */
/**
 * @attribute {Mixed} left Sets or gets the left position of this element. Depending
 * on the choosen layout method the unit can be pixels, a percentage or an
 * expression.
 */
/**
 * @attribute {Mixed} top Sets or gets the top position of this element. Depending
 * on the choosen layout method the unit can be pixels, a percentage or an
 * expression.
 */
/**
 * @attribute {Mixed} right Sets or gets the right position of this element. Depending
 * on the choosen layout method the unit can be pixels, a percentage or an
 * expression.
 */
/**
 * @attribute {Mixed} bottom Sets or gets the bottom position of this element. Depending
 * on the choosen layout method the unit can be pixels, a percentage or an
 * expression.
 */
/**
 * @attribute {Mixed} width Sets or gets the different between the left edge and the
 * right edge of this element. Depending on the choosen layout method the
 * unit can be pixels, a percentage or an expression.
 * 
 * #### Remarks
 *
 * When used as a child of a grid element the width can also be set as '*'. 
 * This will fill the rest space.
 */
/**
 * @attribute {Mixed} height Sets or gets the different between the top edge and the
 * bottom edge of this element. Depending on the choosen layout method the
 * unit can be pixels, a percentage or an expression.
 * 
 * #### Remarks
 *
 * When used as a child of a grid element the height can also be set as '*'. 
 * This will fill the rest space.
 */
/**
 * @event resize Fires when the element changes width or height. 
 */
/** 
 * @event contextmenu Fires when the user requests a context menu, either
 * using the keyboard or mouse.
 * @bubbles
 * @cancelable Prevents the default context menu from appearing.
 * @param {Object} e The standard event object. Contains the following properties:
 *                   - x ([[Number]]): The x coordinate where the contextmenu is requested on
 *                   - y ([[Number]]): The y coordinate where the contextmenu is requested on
 *                   - htmlEvent ([[Event]]): The HTML event object that triggered this event from being called
 */
/**  
 * @event focus       Fires when this element receives focus.
 */
/** 
 * @event blur        Fires when this element loses focus.
 */
/**  
 * @event keydown     Fires when this element has focus and the user presses a key on the keyboard.
 * @bubbles
 * @cancelable Prevents the default key action.
 * @param {Object} e The standard event object. Contains the following properties:
 *                   - ctrlKey ([[Boolean]]): Specifies whether the [[keys: Ctrl]] key was pressed
 *                   - shiftKey ([[Boolean]]): Specifies whether the [[keys: Shift]] key was pressed
 *                   - altKey ([[Boolean]]): Specifies whether the [[keys: Alt ]] key was pressed
 *                   - keyCode ([[Number]]): Indicates which key was pressed. This is an ascii number
 *                   - htmlEvent ([[Event]]): the HTML event object that triggered this event from being called
 * 
 */
apf.GuiElement = function(){
    this.$init(true);
};

(function(){
    this.$regbase = this.$regbase | apf.__GUIELEMENT__;
    
    this.$focussable = apf.KEYBOARD_MOUSE; // Each GUINODE can get the focus by default
    this.visible = 2; //default value;
    
    this.minwidth = 1;
    this.minheight = 1;
    
    /*this.minwidth = 5;
    this.minheight = 5;
    this.maxwidth = 10000;
    this.maxheight = 10000;*/
    
    
    this.$booleanProperties["disable-keyboard"] = true;
    
    this.$booleanProperties["visible"] = true;
    
    /**
     * @attribute {Boolean} draggable If true, the element can be dragged around the screen.
     */    
    /**
     * @attribute {Boolean} resizable If true, the element can by resized by the user.
     * 
     */
    
    this.$supportedProperties.push("draggable", "resizable");
    
    this.$supportedProperties.push(
        "focussable", "zindex", "disabled", "tabindex",
        "disable-keyboard", "contextmenu", "visible", "autosize", 
        "loadaml", "actiontracker", "alias",
        "width", "left", "top", "height", "tooltip"
    );

    this.$setLayout = function(type, insert) {
        if (!this.$drawn || !this.$pHtmlNode)
            return false;

        if (this.parentNode) {
            
            if (this.parentNode.localName == "table") {
                if (this.$disableCurrentLayout)
                    this.$disableCurrentLayout();
                this.parentNode.register(this, insert);
                this.$disableCurrentLayout = null;
                this.$layoutType = null;
                return type == "table";
            }else
            

            
            if (this.parentNode.$box) {
                if (this.$layoutType != this.parentNode) {
                    if (this.$disableCurrentLayout)
                        this.$disableCurrentLayout();
                    this.parentNode.register(this, insert);
                    this.$disableCurrentLayout = null;
                    this.$layoutType = this.parentNode;
                }
                return type == this.parentNode.localName;
            } //else
            
        }
        
        
        if (!this.$anchoringEnabled) {
            if (this.$disableCurrentLayout)
                this.$disableCurrentLayout();
            this.$enableAnchoring();
            this.$disableCurrentLayout = this.$disableAnchoring;
            this.$layoutType = null;
        }
        return type == "anchoring";
        
    }
    
    this.addEventListener("DOMNodeInserted", function(e) {
       if (e.currentTarget == this
         && (this.parentNode.$box || "table" == this.parentNode.localName)) {
            if (!e.$oldParent) this.$layoutType = null;
            this.$setLayout(!e.$oldParent);
       }
    }); 

    this.implement(
        
        apf.Anchoring
        
        
        
    );
    
    // **** Convenience functions for gui nodes **** //

    

    // *** Geometry *** //

    /**
     * Sets the difference between the left edge and the right edge of this
     * element. 
     *
     * Depending on the choosen layout method, the unit can be
     * pixels, a percentage, or an expression. 
     *
     * @chainable
     * @param {Number | String} value The new width of this element.
     */
    this.setWidth = function(value) {
        this.setProperty("width", value, false, true);
        return this;
    };

    /**
     * Sets the different between the top edge and the bottom edge of this
     * element. 
     *
     * Depending on the choosen layout method the unit can be
     * pixels, a percentage or an expression.
     *
     * @chainable
     * @param {Number | String} value the new height of this element.
     */
    this.setHeight = function(value) {
        this.setProperty("height", value, false, true);
        return this;
    };

    /**
     * Sets the left position of this element. 
     *
     * Depending on the choosen layout method the unit can be pixels, 
     * a percentage or an expression.
     *
     * @chainable
     * @param {Number | String} value The new left position of this element.
     */
    this.setLeft = function(value) {
        this.setProperty("left", value, false, true);
        return this;
    };

    /**
     * Sets the top position of this element. 
     *
     * Depending on the choosen layout method the unit can be pixels, 
     * a percentage or an expression.
     *
     * @chainable
     * @param {Number | String} value The new top position of this element.
     */
    this.setTop = function(value) {
        this.setProperty("top", value, false, true);
        return this;
    };

    if (!this.show) {
        /**
         * Makes the elements visible. 
         * @chainable
         */
        this.show = function(){
            this.setProperty("visible", true, false, true);
            return this;
        };
    }

    if (!this.hide) {
        /**
         * Makes the elements invisible. 
         * @chainable
         */
        this.hide = function(){
            this.setProperty("visible", false, false, true);
            return this;
        };
    }

    /**
     * Retrieves the calculated width in pixels for this element.
     */
    this.getWidth = function(){
        return (this.$ext || {}).offsetWidth;
    };

    /**
     * Retrieves the calculated height in pixels for this element.
     */
    this.getHeight = function(){
        return (this.$ext || {}).offsetHeight;
    };

    /**
     * Retrieves the calculated left position in pixels for this element,
     * relative to the offsetParent.
     */
    this.getLeft = function(){
        return (this.$ext || {}).offsetLeft;
    };

    /**
     * Retrieves the calculated top position in pixels for this element,
     * relative to the offsetParent.
     */
    this.getTop = function(){
        return (this.$ext || {}).offsetTop;
    };

    // *** Disabling *** //

    /**
     * Activates the functions of this element. 
     * @chainable
     */
    this.enable = function(){
        this.setProperty("disabled", false, false, true);
        return this;
    };

    /**
     * Deactivates the functions of this element.
     * @chainable
     */
    this.disable = function(){
        this.setProperty("disabled", true, false, true);
        return this;
    };

    // *** z-Index *** //

    /**
     * Moves this element to the lowest z ordered level.
     * @chainable
     */
    this.sendToBack = function(){
        this.setProperty("zindex", 0, false, true);
        return this;
    };

    /**
     * Moves this element to the highest z ordered level.
     * @chainable
     */
    this.bringToFront = function(){
        this.setProperty("zindex", apf.all.length + 1, false, true);
        return this;
    };

    /**
     * Moves this element one z order level deeper.
     * @chainable
     */
    this.sendBackwards = function(){
        this.setProperty("zindex", this.zindex - 1, false, true);
        return this;
    };

    /**
     * Moves this element one z order level higher.
     * @chainable
     */
    this.bringForward = function(){
        this.setProperty("zindex", this.zindex + 1, false, true);
        return this;
    };

    
    
    this.hasFocus = function(){}

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var x = this.$aml;

        // will $pHtmlNode be deprecated soon?
        // check used to be:
        //if (!this.$pHtmlNode && this.parentNode)
        if (this.parentNode) {
            if (this.localName == "item" 
              && this.parentNode.hasFeature(apf.__MULTISELECT__)) //special case for item nodes, using multiselect rendering
                this.$pHtmlNode = this.parentNode.$container;
            else
                this.$pHtmlNode = this.parentNode.$int; //@todo apf3.0 change this in the mutation events
        }

        if (!this.$pHtmlNode) //@todo apf3.0 retry on DOMNodeInserted
            return;
        
        this.$pHtmlDoc = this.$pHtmlNode.ownerDocument || document;

        if (this.$initSkin)
            this.$initSkin(x);

        if (this.$draw)
            this.$draw();

        if (e.id)
            this.$ext.setAttribute("id", e.id);

        if (typeof this.visible == "undefined")
            this.visible = true;

        

        this.$drawn = true;
    }, true);
    
    var f = function(e) {
        if (!this.$pHtmlNode) //@todo apf3.0 retry on DOMInsert or whatever its called
            return;
        
        this.$setLayout(); //@todo apf3.0 moving an element minwidth/height should be recalced
        
        //@todo apf3.0 set this also for skin change
        if (this.$ext) {
            var hasPres = (this.hasFeature(apf.__PRESENTATION__)) || false;
            var type = this.$isLeechingSkin ? this.localName : "main";
            this.minwidth = Math.max(this.minwidth || 0, apf.getCoord(hasPres && parseInt(this.$getOption(type, "minwidth")), 0));
            this.minheight = Math.max(this.minheight || 0, apf.getCoord(hasPres && parseInt(this.$getOption(type, "minheight")), 0));
            if (this.maxwidth == undefined)
                this.maxwidth = apf.getCoord(hasPres && parseInt(this.$getOption(type, "maxwidth")), 10000);
            if (this.maxheight == undefined)
                this.maxheight = apf.getCoord(hasPres && parseInt(this.$getOption(type, "maxheight")), 10000);

            //--#ifdef __WITH_CONTENTEDITABLE
            //@todo slow??
            var diff = apf.getDiff(this.$ext);
            this.$ext.style.minWidth = Math.max(0, this.minwidth - diff[0]) + "px";
            this.$ext.style.minHeight = Math.max(0, this.minheight - diff[1]) + "px";
            this.$ext.style.maxWidth = Math.max(0, this.maxwidth - diff[0]) + "px";
            this.$ext.style.maxHeight = Math.max(0, this.maxheight - diff[1]) + "px";
            
            if (this.$altExt && apf.isGecko) {
                this.$altExt.style.minHeight = this.$ext.style.minHeight;
                this.$altExt.style.maxHeight = this.$ext.style.maxHeight;
                this.$altExt.style.minWidth = this.$ext.style.minWidth;
                this.$altExt.style.maxWidth = this.$ext.style.maxWidth;
            }
            //--#endif
        }
        
        if (this.$loadAml)
            this.$loadAml(this.$aml); //@todo replace by event
        
        
        if (this.$focussable && typeof this.focussable == "undefined")
            apf.GuiElement.propHandlers.focussable.call(this, true);
        
        
        
        if (setResizeEvent)
            f2();
        
    };
    
    this.addEventListener("DOMNodeInsertedIntoDocument", f);
    this.addEventListener("$skinchange", f);
    
    
    var f2, setResizeEvent;
    this.addEventListener("$event.resize", f2 = function(c) {
        if (!this.$ext) {
            setResizeEvent = true;
            return;
        }
        
        apf.layout.setRules(this.$ext, "resize", "var o = apf.all[" + this.$uniqueId + "];\
            if (o) o.dispatchEvent('resize');", true);

        apf.layout.queue(this.$ext);
        //apf.layout.activateRules(this.$ext);
        this.removeEventListener("$event.resize", f2);
    });
    

    
    this.addEventListener("contextmenu", function(e) {
        
        
        if (!this.contextmenus) return;
        
        if (this.hasFeature(apf.__DATABINDING__)) {
            var contextmenu;
            var xmlNode = this.hasFeature(apf.__MULTISELECT__)
                ? this.selected
                : this.xmlRoot;

            var i, l, m, isRef, sel, menuId, cm, result;
            for (i = 0, l = this.contextmenus.length; i < l; i++) {
                isRef = (typeof (cm = this.contextmenus[i]) == "string");
                result = null;
                if (!isRef && cm.match && xmlNode) {//@todo apf3.0 cache this statement
                    result = (cm.cmatch || (cm.cmatch = apf.lm.compile(cm.match, {
                        xpathmode: 3,
                        injectself: true
                    })))(xmlNode)
                }

                if (isRef || xmlNode && result || !cm.match) { //!xmlNode && 
                    menuId = isRef
                        ? cm
                        : cm.menu;
                        
                    var menu = cm.localName == "menu" ? cm : self[menuId];

                    if (!menu) {
                        
                        
                        return;
                    }

                    menu.display(e.x, e.y, null, this, xmlNode);

                    e.returnValue = false;//htmlEvent.
                    e.cancelBubble = true;
                    break;
                }
            }

            //IE6 compatiblity
            /*
            @todo please test that disabling this is OK
            if (!apf.config.disableRightClick) {
                document.oncontextmenu = function(){
                    document.oncontextmenu = null;
                    e.cancelBubble = true;
                    return false;
                }
            }*/
        }
        else {
            var menu;
            if (typeof this.contextmenus[0] == "string")
                menu = self[this.contextmenus[0]];
            if (this.contextmenus[0].localName == "menu")
                menu = this.contextmenus[0];
            else
                menu = self[this.contextmenus[0].getAttribute("menu")];

            if (!menu) {
                
                
                return;
            }

            menu.display(e.x, e.y, null, this);

            e.returnValue = false;//htmlEvent.
            e.cancelBubble = true;
        }
    });
    
}).call(apf.GuiElement.prototype = new apf.AmlElement());

/*
 * @for apf.amlNode
 * @private
 */
apf.GuiElement.propHandlers = {
    /**
     * @attribute {Number} minwidth Sets or gets the minimum width for this element.
     */
    /**
     * @attribute {Number} maxwidth Sets or gets the maximum width for this element.
     */
    /**
     * @attribute {Number} minheight Sets or gets the minimum height for this element.
     */
    /**
     * @attribute {Number} maxheight Sets or gets the maximum height for this element.
     */
    "minwidth": function(value){ this.$ext.style.minWidth = Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"; },
    "minheight": function(value){ this.$ext.style.minHeight = Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"; },
    "maxwidth": function(value){ this.$ext.style.maxWidth = Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"; },
    "maxheight": function(value){ this.$ext.style.maxHeight = Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"; },
    
    
    /**
     * @attribute {Boolean} focussable Sets or gets whether this element can receive the focus.
     * The focused element receives keyboard event.
     */
    "focussable": function(value) {
        this.focussable = typeof value == "undefined" || value;

        if (value == "container") {
            this.$isWindowContainer = true;
            this.focussable = true;
        }
        else
            this.focussable = apf.isTrue(value);

        if (!this.hasFeature(apf.__FOCUSSABLE__)) //@todo should this be on the prototype
            this.implement(apf.Focussable);

        if (this.focussable) {
            apf.window.$addFocus(this, this.tabindex);
            
            if (value == "container")
                this.$tabList.remove(this);
        }
        else {
            apf.window.$removeFocus(this);
        }
    },

    /**
     * @attribute {Number} tabindex Sets or gets the pane index for this element.
     */    
    "tabindex": function(value) {
        if (!this.hasFeature(apf.__FOCUSSABLE__)) 
            return;
        
        this.setTabIndex(parseInt(value) || null);
    },
    

    /**
     * @attribute {Number} zindex Sets or gets the z ordered layer in which this element is
     * drawn.
     */
    "zindex": function(value) {
        this.$ext.style.zIndex = value;
    },

    /**
     * @attribute {Boolean} visible Sets or gets whether this element is shown.
     */
    "visible": function(value) {
        if (apf.isFalse(value) || typeof value == "undefined") {
            if (this.$ext)
                this.$ext.style.display = "none";
            
            if (apf.document.activeElement == this || this.canHaveChildren == 2
              && apf.isChildOf(this, apf.document.activeElement, false)) {
                if (apf.config.allowBlur && this.hasFeature(apf.__FOCUSSABLE__))
                    this.blur();
                else
                    apf.window.moveNext();
            }
            
            this.visible = false;
        }
        else { //if (apf.isTrue(value)) default
            if (this.$ext) {
                this.$ext.style.display = ""; //Some form of inheritance detection
                if (!this.$ext.offsetHeight)
                    this.$ext.style.display = this.$display || "block";
            }
            
            
            // if (apf.layout && this.$int) //apf.hasSingleRszEvent)
            //     apf.layout.forceResize(this.$int);//this.$int
            
            
            this.visible = true;
        }
    },

    /**
     * @attribute {Boolean} disabled Sets or gets whether this element's functions are active.
     * For elements that can contain other `apf.NODE_VISIBLE` elements, this
     * attribute applies to all its children.
     */
    "disabled": function(value) {
        if (!this.$drawn) {
            var _self = this;
            //this.disabled = false;

            this.addEventListener("DOMNodeInsertedIntoDocument", 
                this.$updateDisabled || (this.$updateDisabled = function(e) {
                    apf.GuiElement.propHandlers.disabled.call(_self, _self.disabled);
                }));
            return;
        }
        else
            apf.queue.remove("disable" + this.$uniqueId);

        //For child containers we only disable its children
        if (this.canHaveChildren) {
            //@todo Fix focus here first.. else it will jump whilst looping
            if (value != -1)
                value = this.disabled = apf.isTrue(value);

            var nodes = this.childNodes;
            for (var node, i = 0, l = nodes.length; i < l; i++) {
                node = nodes[i];
                if (node.nodeFunc == apf.NODE_VISIBLE) {
                    if (value && node.disabled != -1)
                        node.$disabled = node.disabled || false;
                    node.setProperty("disabled", value ? -1 : false);
                }
            }

            //this.disabled = undefined;
            if (this.$isWindowContainer)
                return;
        }

        if (value == -1 || value == false) {
            //value = true;
        }
        else if (typeof this.$disabled == "boolean") {
            if (value === null) {
                value = this.$disabled;
                this.$disabled = null;
            }
            else {
                this.$disabled = value || false;
                return;
            }
        }

        if (apf.isTrue(value) || value == -1) {
            this.disabled = false;
            if (apf.document.activeElement == this) {
                apf.window.moveNext(true); //@todo should not include window
                if (apf.document.activeElement == this)
                    this.$blur();
            }

            if (this.hasFeature(apf.__PRESENTATION__))
                this.$setStyleClass(this.$ext, this.$baseCSSname + "Disabled");

            if (this.$disable)
                this.$disable();

            

            this.disabled = value;
        }
        else {
            if (this.hasFeature(apf.__DATABINDING__) && apf.config.autoDisable
              && !(!this.$bindings || this.xmlRoot))
                return false;

            this.disabled = false;

            if (apf.document.activeElement == this)
                this.$focus();

            if (this.hasFeature(apf.__PRESENTATION__))
                this.$setStyleClass(this.$ext, null, [this.$baseCSSname + "Disabled"]);

            if (this.$enable)
                this.$enable();

            
        }
    },

    /**
     * @attribute {Boolean} enables Sets or gets whether this element's functions are active.
     * For elements that can contain other `apf.NODE_VISIBLE` elements, this
     * attribute applies to all its children.
     */
    "enabled" : function(value) {
       this.setProperty("disabled", !value);
    },

    /**
     * @attribute {Boolean} disable-keyboard Sets or gets whether this element receives
     * keyboard input. This allows you to disable keyboard independently from
     * focus handling.
     */
    "disable-keyboard": function(value) {
        this.disableKeyboard = apf.isTrue(value);
    },
    
    /**
     * @attribute {String}  tooltip  Sets or gets the text displayed when a user hovers with 
     * the mouse over the element.
     */
    "tooltip" : function(value) {
        this.$ext.setAttribute("title", (value || "") + (this.hotkey ? " ("
            + (apf.isMac ? apf.hotkeys.toMacNotation(this.hotkey) : this.hotkey) + ")" : ""));
    },
    
    
    /**
     * @attribute {String} contextmenu Sets or gets the name of the menu element that will
     * be shown when the user right clicks or uses the context menu keyboard
     * shortcut.
     *
     * #### Example
     * 
     * ```xml
     *  <a:menu id="mnuExample">
     *      <a:item>test</a:item>
     *      <a:item>test2</a:item>
     *  </a:menu>
     *   
     *  <a:list 
     *    contextmenu = "mnuExample" 
     *    width = "200" 
     *    height = "150" />
     *  <a:bar 
     *    contextmenu = "mnuExample" 
     *    width = "200" 
     *    height = "150" />
     * ```
     */
    "contextmenu": function(value) {
        this.contextmenus = [value];
    },
    

    
    /**
     * @attribute {String} actiontracker Sets or gets the name of the [[apf.actiontracker action tracker]] that
     * is used for this element and its children. If the actiontracker doesn't
     * exist yet, it is created.
     *
     * #### Example
     *
     * In this example, the list uses a different action tracker than the two
     * textboxes which determine their actiontracker based on the one that
     * is defined on the bar.
     * 
     * ```xml
     *  <a:list actiontracker="newAT" />
     *
     *  <a:bar actiontracker="someAT">
     *      <a:textbox />
     *      <a:textbox />
     *  </a:bar>
     * ```
     */
    "actiontracker": function(value) {
        if (!value) {
            this.$at = null;
        }
        else if (typeof value == "object") {
            this.$at = value;
        }
        else {
            
            this.$at = typeof value == "string" && self[value]
              ? apf.nameserver.get("actiontracker", value) || self[value].getActionTracker()
              : apf.setReference(value,
                  apf.nameserver.register("actiontracker",
                      value, new apf.actiontracker()));

            if (!this.$at.name)
                this.$at.name = value;
            
        }
    },
    
    
    //Load subAML
    /**
     * @attribute {String} aml Sets or gets  the {@link term.datainstruction data instruction} 
     * that loads new AML as children of this element.
     */
    "aml": function(value) {
        this.replaceMarkup(value);
    }

    /*
     * @attribute {String} sets this aml element to be editable
     * that loads new aml as children of this element.
     */
    // @todo Doc WTF?
    
   
    
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__PRESENTATION__ = 1 << 9;


/**
 * All elements inheriting from this {@link term.baseclass baseclass} have skinning features. A skin is a description
 * of how the element is rendered. In the web browser, this is done using HTML
 * elements and CSS.
 *
 * #### Remarks
 *
 * The skin is set using the `skin` attribute. The skin of each element can be
 * changed at run time. Other than just changing the look of an element, a skin
 * change can help the user to perceive information in a different way. For 
 * example, a list element has a default skin, but can also use the thumbnail 
 * skin to display thumbnails of the {@link term.datanode data nodes}.
 *
 * #### Example
 *
 * A skin for an element is always built up out of a standard set of parts:
 *
 * ```xml
 *   <a:textbox name="textbox">
 *      <a:alias>
 *          ...
 *      </a:alias>
 *      <a:style><![CDATA[
 *          ...
 *      ]]></a:style>
 *  
 *      <a:presentation>
 *          <a:main>
 *              ...
 *          </a:main>
 *          ...
 *      </a:presentation>
 *   </a:textbox>
 * ```
 *
 * The alias contains a name that contains alternative names for the skin. The
 * style tags contain the CSS. The main tag contains the HTML elements that are
 * created when the component is created. Any other skin items are used to render
 * other elements of the widget. In this reference guide you will find these
 * skin items described on the pages of each widget.
 *
 * @class apf.Presentation
 * @define presentation
 * @inherits apf.GuiElement
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.5
 */
apf.Presentation = function(){
    this.$init(true);
};

(function(){
    this.$regbase = this.$regbase | apf.__PRESENTATION__;
    
    // *** Properties and Attributes *** //

    this.$supportedProperties.push("skin");
    
    /**
     * @attribute {String} skinset Sets or gets the skinset for
     * this element. If none are specified ,the `skinset` attribute
     * of the app settings is used. When that's not defined, the default skinset
     * is used.
     * 
     * #### Example
     *
     * ```xml
     *  <a:list skinset="perspex" />
     * ```
     */
    this.$propHandlers["skinset"] =

    
    /**
     * @attribute {String} skin Sets or gets the name of the skin in the skinset that defines 
     * how this element is rendered. When a skin is changed, the full state of the
     * element is kept, including its selection, all the
     * AML attributes, loaded data, and focus and disabled states.
     *
     * #### Example
     *
     * In XML:
     *
     * ```xml
     *  <a:list id="lstExample" skin="thumbnails" />
     * ```
     * 
     * Or, in JavaScript:
     *
     * ```javascript
     *  lstExample.setAttribute("skin", "list");
     * ```
     */
    this.$propHandlers["skin"] = function(value) {
        if (!this.$amlLoaded) //If we didn't load a skin yet, this will be done when we attach to a parent
            return;

        if (!this.$skinTimer) {
            var _self = this;
            clearTimeout(this.$skinTimer);
            this.$skinTimer = $setTimeout(function(){
                changeSkin.call(_self, _self.skin);
                delete _self.$skinTimer;
            });
        }
    }
    

    /**
     * @attribute {String} style Sets or gets the CSS style applied to the this element. This can be a string containing one or more CSS rules.
     */
    this.$propHandlers["style"] = function(value) {
        if (!this.styleAttrIsObj && this.$amlLoaded)
            this.$ext.setAttribute("style", value);
    }
    
    /**
     * @attribute {String} border Sets or gets border values. Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off borders.
     */
    this.$propHandlers["border"] = function(value) {
        if (!value)
            this.$ext.style.borderWidth = "";
        else
            this.$ext.style.borderWidth = apf.getBox(value).join("px ") + "px";
    }
    
    /**
     * @attribute {String | Number} margin Sets or gets margin values. Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off margins.
     */
    this.$propHandlers["margin"] = function(value) {
        if (!value)
            this.$ext.style.margin = "";
        else
            this.$ext.style.margin = apf.getBox(value).join("px ") + "px";
    }

    /**
     * @attribute {String} class Sets or gets the name of the CSS style class applied to this element.
     */
    this.$propHandlers["class"] = function(value) {
        this.$setStyleClass(this.$ext, value, this.$lastClassValue ? [this.$lastClassValue] : null);
        this.$lastClassValue = value;
    }

    
    this.$forceSkinChange = function(skin, skinset) {
        changeSkin.call(this, skin, skinset);
    }

    //@todo objects don't always have an $int anymore.. test this
    function changeSkin(skin, skinset) {
        clearTimeout(this.$skinTimer);

        //var skinName = (skinset || this.skinset || apf.config.skinset)
        //    + ":" + (skin || this.skin || this.localName);

        
        //Store selection
        if (this.selectable)
            var valueList = this.getSelection();//valueList;
        

        //Store needed state information
        var oExt = this.$ext,
            oInt = this.$int,
            pNode = this.$ext ? this.$ext.parentNode : this.$pHtmlNode,
            beforeNode = oExt && oExt.nextSibling,
            idExt = this.$ext && this.$ext.getAttribute("id"),
            idInt = this.$int && this.$int.getAttribute("id"),
            oldBase = this.$baseCSSname;

        if (oExt && oExt.parentNode)
            oExt.parentNode.removeChild(oExt);

        //@todo changing skin will leak A LOT, should call $destroy here, with some extra magic
        if (this.$destroy)
            this.$destroy(true);

        //Load the new skin
        this.skin = skin;
        this.$loadSkin(skinset ? skinset + ":" + skin : null);

        //Draw
        if (this.$draw)
            this.$draw(true);

        if (idExt)
            this.$ext.setAttribute("id", idExt);

        if (beforeNode || this.$ext && pNode != this.$ext.parentNode)
            pNode.insertBefore(this.$ext, beforeNode);

        //Style
        
        //Border
        
        //Margin

        if (this.$ext) {
            //Classes
            var i, l, newclasses = [],
                   classes = (oExt.className || "").splitSafe("\\s+");
            for (i = 0; i < classes.length; i++) {
                if (classes[i] && classes[i].indexOf(oldBase) != 0)
                    newclasses.push(classes[i].replace(oldBase, this.$baseCSSname));
            }
            apf.setStyleClass(this.$ext, newclasses.join(" "));
            
            //Copy events
            var en, ev = apf.skins.events;
            for (i = 0, l = ev.length; i < l; i++) {
                en = ev[i];
                if (typeof oExt[en] == "function" && !this.$ext[en])
                    this.$ext[en] = oExt[en];
            }
        
            //Copy css state (dunno if this is best)
            this.$ext.style.left = oExt.style.left;
            this.$ext.style.top = oExt.style.top;
            this.$ext.style.width = oExt.style.width;
            this.$ext.style.height = oExt.style.height;
            this.$ext.style.right = oExt.style.right;
            this.$ext.style.bottom = oExt.style.bottom;
            this.$ext.style.zIndex = oExt.style.zIndex;
            this.$ext.style.position = oExt.style.position;
            this.$ext.style.display = oExt.style.display;
        }
        
        //Widget specific
        //if (this.$loadAml)
            //this.$loadAml(this.$aml);
        
        if (idInt)
            this.$int.setAttribute("id", idInt);
        
        if (this.$int && this.$int != oInt) {
            var node, newNode = this.$int, nodes = oInt.childNodes;
            for (var i = nodes.length - 1; i >= 0; i--) {
                if ((node = nodes[i]).host) {
                    node.host.$pHtmlNode = newNode;
                    if (node.host.$isLeechingSkin)
                        setLeechedSkin.call(node.host);
                }
                newNode.insertBefore(node, newNode.firstChild);
            }
            //this.$int.onresize = oInt.onresize;
        }
        
        
        //DragDrop
        if (this.hasFeature(apf.__DRAGDROP__)) {
            if (document.elementFromPointAdd) {
                document.elementFromPointRemove(oExt);
                document.elementFromPointAdd(this.$ext);
            }
        }
        

        //Check disabled state
        if (this.disabled)
            this.$disable(); //@todo apf3.0 test

        //Check focussed state
        if (this.$focussable && apf.document.activeElement == this)
            this.$focus(); //@todo apf3.0 test

        //Dispatch event
        this.dispatchEvent("$skinchange", {
            ext: oExt,
            "int": oInt
        });

        
        //Reload data
        if (this.hasFeature(apf.__DATABINDING__) && this.xmlRoot)
            this.reload();
        else
        
        if (this.value)
            this.$propHandlers["value"].call(this, this.value);

        
        //Set Selection
        if (this.hasFeature(apf.__MULTISELECT__)) {
            if (this.selectable)
                this.selectList(valueList, true);
        }
        

        //Move layout rules
        if (!apf.hasSingleRszEvent) {
            apf.layout.activateRules(this.$ext);
            if (this.$int)
                apf.layout.activateRules(this.$int);
        }

/*        
        if (this.hasFeature(apf.__ANCHORING__))
            this.$recalcAnchoring();
        

        
        if (this.hasFeature(apf.__ALIGNMENT__)) {
            if (this.aData)
                this.aData.oHtml = this.$ext;

            if (this.pData) {
                this.pData.oHtml = this.$ext;
                this.pData.pHtml = this.$int;

                var nodes = this.pData.childNodes;
                for (i = 0; i < nodes.length; i++)
                    nodes[i].pHtml = this.$int; //Should this be recursive??
            }
        }
        
*/
        
        if (this.draggable && this.$propHandlers["draggable"]) //@todo move these to the event below apf3.0)
            this.$propHandlers["draggable"].call(this, this.draggable);
        if (this.resizable && this.$propHandlers["resizable"])
            this.$propHandlers["resizable"].call(this, this.resizable);
        

        
        apf.layout.forceResize(this.$ext);
        
    };
    

    // *** Private methods *** //

    this.$setStyleClass = apf.setStyleClass;

    function setLeechedSkin(e) {
        if (!this.$amlLoaded || e && (e.$isMoveWithinParent 
          || e.currentTarget != this || !e.$oldParent))
            return;

        this.$setInheritedAttribute(this, "skinset");

        if (this.attributes.getNamedItem("skin"))
            return;

        //e.relatedNode
        var skinName, pNode = this.parentNode, skinNode;
        if ((skinName = this.$canLeechSkin.dataType 
          == apf.STRING ? this.$canLeechSkin : this.localName)
          && pNode.$originalNodes 
          && (skinNode = pNode.$originalNodes[skinName])
          && skinNode.getAttribute("inherit")) {
            var link = skinNode.getAttribute("link");
            this.$isLeechingSkin = true;
            if (link) {
                this.$forceSkinChange(link);
            }
            else {
                var skin = pNode.skinName.split(":");
                this.$forceSkinChange(skin[1], skin[0]);
            }
        }
        else if (this.$isLeechingSkin) {
            delete this.skin;
            this.$isLeechingSkin = false;
            this.$forceSkinChange();
        }
    }

    //Skin Inheritance
    //@todo Probably requires some cleanup
    this.$initSkin = function(x) {
        if (this.$canLeechSkin) {
            this.addEventListener("DOMNodeInserted", setLeechedSkin);
        }
        
        if (!this.skin)
            this.skin = this.getAttribute("skin");
        
        var skinName, pNode = this.parentNode, skinNode;
        if (this.$canLeechSkin && !this.skin 
          && (skinName = this.$canLeechSkin.dataType == apf.STRING 
            ? this.$canLeechSkin 
            : this.localName)
          && pNode && pNode.$originalNodes 
          && (skinNode = pNode.$originalNodes[skinName])
          && skinNode.getAttribute("inherit")) {
            var link = skinNode.getAttribute("link");
            this.$isLeechingSkin = true;
            if (link) {
                this.skin = link;
                this.$loadSkin();
            }
            else {
                this.$loadSkin(pNode.skinName);
            }
        }
        else {
            if (!this.skinset)
                this.skinset = this.getAttribute("skinset");
            
            this.$loadSkin(null, this.$canLeechSkin);
        }
    }

    /*
     * Initializes the skin for this element when none has been set up.
     *
     * @param {String}   skinName   Identifier for the new skin (for example: 'default:List' or 'win').
     * @param {Boolean}  [noError]
     */
    this.$loadSkin = function(skinName, noError) {
        //this.skinName || //where should this go and why?
        this.baseSkin = skinName || (this.skinset 
            || this.$setInheritedAttribute("skinset")) 
            + ":" + (this.skin || this.localName);

        clearTimeout(this.$skinTimer);

        if (this.skinName) {
            this.$blur();
            this.$baseCSSname = null;
        }

        this.skinName = this.baseSkin; //Why??
        //this.skinset = this.skinName.split(":")[0];

        this.$pNodes = {}; //reset the this.$pNodes collection
        this.$originalNodes = apf.skins.getTemplate(this.skinName, true);

        if (!this.$originalNodes) {
            var skin = this.skin;
            if (skin) {
                var skinset = this.skinName.split(":")[0];
                this.baseName = this.skinName = "default:" + skin;
                this.$originalNodes = apf.skins.getTemplate(this.skinName);
                
                if (!this.$originalNodes && skinset != "default") {
                    this.baseName = this.skinName = skinset + ":" + this.localName;
                    this.$originalNodes = apf.skins.getTemplate(this.skinName, true);
                }
            }
            
            if (!this.$originalNodes) {
                this.baseName = this.skinName = "default:" + this.localName;
                this.$originalNodes = apf.skins.getTemplate(this.skinName);
            }

            if (!this.$originalNodes) {
                if (noError) {
                    return (this.baseName = this.skinName = 
                        this.originalNode = null);
                }
                
                throw new Error(apf.formatErrorString(1077, this,
                    "Presentation",
                    "Could not load skin: " + this.baseSkin));
            }
            
            //this.skinset = this.skinName.split(":")[0];
        }

        if (this.$originalNodes)
            apf.skins.setSkinPaths(this.skinName, this);
    };

    this.$getNewContext = function(type, amlNode) {
        

        this.$pNodes[type] = this.$originalNodes[type].cloneNode(true);
    };

    this.$hasLayoutNode = function(type) {
        

        return this.$originalNodes[type] ? true : false;
    };

    this.$getLayoutNode = function(type, section, htmlNode) {
        

        var node = this.$pNodes[type] || this.$originalNodes[type];
        if (!node) {
            
            return false;
        }

        if (!section)
            return htmlNode || apf.getFirstElement(node);

        var textNode = node.getAttribute(section);
        if (!textNode)
            return null;

        return (htmlNode
            ? apf.queryNode(htmlNode, textNode)
            : apf.getFirstElement(node).selectSingleNode(textNode));
    };

    this.$getOption = function(type, section) {
        type = type.toLowerCase(); //HACK: lowercasing should be solved in the comps.

        //var node = this.$pNodes[type];
        var node = this.$pNodes[type] || this.$originalNodes[type];
        if (!section || !node)
            return node;//apf.getFirstElement(node);
        var option = node.selectSingleNode("@" + section);

        return option ? option.nodeValue : "";
    };

    this.$getExternal = function(tag, pNode, func, aml) {
        if (!pNode)
            pNode = this.$pHtmlNode;
        if (!tag)
            tag = "main";
        //if (!aml)
            //aml = this.$aml;

        tag = tag.toLowerCase(); //HACK: make components case-insensitive

        this.$getNewContext(tag);
        var oExt = this.$getLayoutNode(tag);
        
        var node;
        if (node = (aml || this).getAttributeNode("style"))
            oExt.setAttribute("style", node.nodeValue);

        //if (node = (aml || this).getAttributeNode("class"))
            //this.$setStyleClass(oExt, (oldClass = node.nodeValue));

        if (func)
            func.call(this, oExt);

        oExt = apf.insertHtmlNode(oExt, pNode);
        oExt.host = this;
        if (node = (aml || this).getAttributeNode("bgimage"))
            oExt.style.backgroundImage = "url(" + apf.getAbsolutePath(
                this.mediaPath, node.nodeValue) + ")";

        if (!this.$baseCSSname)
            this.$baseCSSname = oExt.className.trim().split(" ")[0];

        return oExt;
    };

    // *** Focus *** //
    this.$focus = function(){
        if (!this.$ext)
            return;

        this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus");
    };

    this.$blur = function(){
        
        if (this.renaming)
            this.stopRename(null, true);
        

        if (!this.$ext)
            return;

        this.$setStyleClass(this.oFocus || this.$ext, "", [this.$baseCSSname + "Focus"]);
    };

    this.$fixScrollBug = function(){
        if (this.$int != this.$ext)
            this.oFocus = this.$int;
        else {
            this.oFocus =
            this.$int = 
                this.$ext.appendChild(document.createElement("div"));

            this.$int.style.height = "100%";
            this.$int.className = "focusbug"
        }
    };

    // *** Caching *** //
    /*
    this.$setClearMessage = function(msg) {};
    this.$updateClearMessage = function(){}
    this.$removeClearMessage = function(){};*/
}).call(apf.Presentation.prototype = new apf.GuiElement());

apf.config.$inheritProperties["skinset"] = 1;






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__VALIDATION__ = 1 << 6;



//if checkequal then notnull = true
apf.validator = {
    macro: {
        

        //var temp
        "pattern"     : "value.match(",
        "pattern_"    : ")",
        "custom"      : "(",
        "custom_"     : ")",
        "min"         : "parseInt(value) >= ",
        "max"         : "parseInt(value) <= ",
        "maxlength"   : "value.toString().length <= ",
        "minlength"   : "value.toString().length >= ",
        "notnull"     : "value.toString().length > 0",
        "checkequal"  : "!(temp = ",
        "checkequal_" : ").isValid() || temp.getValue() == value"
    },
    
    compile: function(options) {
        var m = this.macro, s = ["var temp, valid = true; \
            if (!validityState) \
                validityState = new apf.validator.validityState(); "];

        if (options.required) {
            s.push("if (checkRequired && (!value || value.toString().trim().length == 0)) {\
                validityState.$reset();\
                validityState.valueMissing = true;\
                valid = false;\
            }")
        }
        
        s.push("validityState.$reset();\
            if (value) {");
        
        for (prop in options) {
            if (!m[prop]) continue;
            s.push("if (!(", m[prop], options[prop], m[prop + "_"] || "", ")){\
                validityState.$set('", prop, "');\
                valid = false;\
            }");
        }

        s.push("};validityState.valid = valid; return validityState;");
        return new Function('value', 'checkRequired', 'validityState', s.join(""));
    }
};

/**
 * Object containing information about the validation state. It contains
 * properties that specify whether a certain validation was passed.
 * Remarks:
 * This is part of {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#validitystatethe HTML 5 specification}.
 */
apf.validator.validityState = function(){
    this.valueMissing = false,
    this.typeMismatch = false,
    this.patternMismatch = false,
    this.tooLong = false,
    this.rangeUnderflow = false,
    this.rangeOverflow = false,
    this.stepMismatch = false,
    this.customError = false,
    this.valid = true,

    this.$reset = function(){
        for (var prop in this) {
            if (prop.substr(0,1) == "$") 
                continue;
            this[prop] = false;
        }
        this.valid = true;
    },

    this.$set = function(type) {
        switch (type) {
            case "min"         : this.rangeUnderflow = true; break;
            case "max"         : this.rangeOverflow = true; break;
            case "minlength"   : this.tooShort = true; break;
            case "maxlength"   : this.tooLong = true; break;
            case "pattern"     : this.patternMismatch = true; break;
            case "datatype"    : this.typeMismatch = true; break;
            case "notnull"     : this.typeMismatch = true; break;
            case "checkequal"  : this.typeMismatch = true; break;
        }
    }
};

/**
 * All elements inheriting from this {@link term.baseclass baseclass} have validation support.
 *
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:bar validgroup="vgExample">
 *        <a:label>Number</a:label>
 *        <a:textbox required="true" min="3" max="10" 
 *          invalidmsg="Invalid Entry;Please enter a number between 3 and 10" />
 *        <a:label>Name</a:label>
 *        <a:textbox required="true" minlength="3" 
 *          invalidmsg="Invalid Entry;Please enter your name" />
 *        <a:label>Message</a:label>
 *        <a:textarea required="true" 
 *          invalidmsg="Invalid Message;Please enter a message!" />
 *   
 *        <a:button onclick="if(vgExample.isValid()) alert('valid!')">
 *            Validate
 *        </a:button>
 *    </a:bar>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.Validation
 * @inherits apf.AmlElement
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.5
 */
 /**
  * @event invalid    Fires when this component goes into an invalid state.
  *
  */
apf.Validation = function(){
    this.$regbase = this.$regbase | apf.__VALIDATION__;

    /**
     * Checks if this element's value is valid.
     *
     * @param {Boolean}  [checkRequired] Specifies whether this check also adheres to the `'required'` rule.
     * @returns  {Boolean} Specifies whether the value is valid
     * @see  apf.ValidationGroup
     * @see  element.submitform
     */
    this.isValid = function(checkRequired) {
        if (!this.$vOptions)
            return true;
        
        (this.$vOptions.isValid || (this.$vOptions.isValid
          = apf.validator.compile(this.$vOptions))).call(this,
            typeof this.getValue == "function" ? this.getValue(null, true) : null, 
            checkRequired, this.validityState || 
            (this.validityState = new apf.validator.validityState()));
        
        var valid = this.validityState.valid;
        
        
        
        this.dispatchEvent(!valid ? "invalid" : "valid", this.validityState);
            
        return valid;
    };

    /*
     * @private
     */
    this.setCustomValidity = function(message) {
        //do stuff
    }

    /*
     * @private
     * @todo This method should also scroll the element into view
     */
    this.showMe = function(){
        var p = this.parentNode;
        while (p) {
            if (p.show)
                p.show();
            p = p.parentNode;
        }
    };

    
    
    /**
     * Puts this element in the error state, optionally showing the
     * error box if this element is invalid.
     *
     * @method validate
     * @param {Boolean}  [ignoreReq]  Specifies whether this element required check is turned on.
     * @param {Boolean}  [nosetError] Specifies whether the error box is displayed if this component does not validate.
     * @param {Boolean}  [force]      Specifies whether this element is in the error state, and doesn't check if the element's value is invalid.
     * @return  {Boolean}  Indicates whether the value is valid
     * @see  apf.ValidationGroup
     */
    this.validate = function(ignoreReq, nosetError, force) {
        //if (!this.$validgroup) return this.isValid();

        if (force || !this.isValid(!ignoreReq) && !nosetError) {
            this.setError();
            return false;
        }
        else {
            this.clearError();
            return true;
        }
    };

    /*
     *    @private
     */
    this.setError = function(value) {
        if (!this.$validgroup)
            this.$propHandlers["validgroup"].call(this, "vg" + this.parentNode.$uniqueId);

        var errBox = this.$validgroup.getErrorBox(this);

        if (!this.$validgroup.allowMultipleErrors)
            this.$validgroup.hideAllErrors();

        errBox.setMessage(this.invalidmsg || value);
        
        apf.setStyleClass(this.$ext, this.$baseCSSname + "Error");
        this.showMe(); //@todo scroll refHtml into view

        if (this.invalidmsg || value)
            errBox.display(this);
        
        
        
        if (apf.document.activeElement && apf.document.activeElement != this)
            this.focus(null, {mouse:true}); //arguable...
    };

    /*
     *    @private
     */
    this.clearError = function(value) {
        if (this.$setStyleClass)
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Error"]);

        if (this.$validgroup) {
            var errBox = this.$validgroup.getErrorBox(null, true);
            if (!errBox || errBox.host != this)
                return;

            errBox.hide();
        }
    };

    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        if (this.$validgroup)
            this.$validgroup.unregister(this);
    });

    /**
     *
     * @attribute  {Boolean}  required     Sets or gets whether a valid value for this element is required.
     */
    /** 
     * @attribute  {RegExp}   pattern      Sets or gets the pattern tested against the value of this element to determine it's validity.
     */
    /**
     * @attribute  {String}   datatype     Sets or gets the datatype that the value of this element should adhere to. This can be any 
     * of a set of predefined types, or a simple type created by an XML Schema definition. 
     * 
     * Some possible values (all of which are [[String]]s) include:
     *   - `xsd:dateTime`
     *   - `xsd:time`
     *   - `xsd:date`
     *   - `xsd:gYearMonth`
     *   - `xsd:gYear`
     *   - `xsd:gMonthDay`
     *   - `xsd:gDay`
     *   - `xsd:gMonth`
     *   - `xsd:string`
     *   - `xsd:boolean`
     *   - `xsd:base64Binary`
     *   - `xsd:hexBinary`
     *   - `xsd:float`
     *   - `xsd:decimal`
     *   - `xsd:double`
     *   - `xsd:anyURI`
     *   - `xsd:integer`
     *   - `xsd:nonPositiveInteger`
     *   - `xsd:negativeInteger`
     *   - `xsd:long`
     *   - `xsd:int`
     *   - `xsd:short`
     *   - `xsd:byte`
     *   - `xsd:nonNegativeInteger`
     *   - `xsd:unsignedLong`
     *   - `xsd:unsignedInt`
     *   - `xsd:unsignedShort`
     *   - `xsd:unsignedByte`
     *   - `xsd:positiveInteger`
     *   - `apf:url`
     *   - `apf:website`
     *   - `apf:email`
     *   - `apf:creditcard`
     *   - `apf:expdate`
     *   - `apf:wechars`
     *   - `apf:phonenumber`
     *   - `apf:faxnumber`
     *   - `apf:mobile`
     */
    /**
     * @attribute  {Number}  min          Sets or gets the minimal value for which the value of this element is valid.
     */
    /**
     * @attribute  {Number}  max          Sets or gets the maximum value for which the value of this element is valid.
     */
    /**
     * @attribute  {Number}  minlength    Sets or gets the minimal length allowed for the value of this element.
     */
    /**
     * @attribute  {Number}  maxlength    Sets or gets the maximum length allowed for the value of this element.
     */
    /**
     * @attribute  {Boolean}  notnull      Sets or gets whether the value is filled. This rule is checked realtime when the element loses the focus.
     */
    /**
     * @attribute  {String}   checkequal   Sets or gets the id of the element to check if it has the same value as this element.
     */
    /**
     * @attribute  {String}   invalidmsg   Sets or gets the message displayed when this element has an invalid value. Use a `;` character to seperate the title from the message.
     */
    /**
     * @attribute  {String}   validgroup   Sets or gets the identifier for a group of items to be validated at the same time. This identifier can be new. It is inherited from a AML node upwards.
     */
    /**
     * @attribute  {String}   validtest    Sets or gets the instruction on how to test for success. This attribute is generally used to check the value on the server.
     * 
     * #### Example
     *
     * This example shows how to check the username on the server. In this case,
     * `comm.loginCheck` is an async RPC function that checks the availability of the
     * username. If it exists, it will return `0`--otherwise, it's `1`. The value variable
     * contains the current value of the element (in this case the textbox). It
     * can be used as a convenience variable.
     *
     * ```xml
     *  <a:label>Username</a:label>
     *  <a:textbox
     *    validtest = "{comm.loginCheck(value) == 1}"
     *    pattern = "/^[a-zA-Z0-9_\-. ]{3,20}$/"
     *    invalidmsg = "Invalid username;Please enter a valid username." />
     * ```
     */
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //this.addEventListener(this.hasFeature(apf.__MULTISELECT__) ? "onafterselect" : "onafterchange", onafterchange);
        /* Temp disabled, because I don't understand it (RLD)
        this.addEventListener("beforechange", function(){
            if (this.xmlRoot && apf.getBoundValue(this) === this.getValue())
                return false;
        });*/
        
        // validgroup
        if (!this.validgroup)
            this.$setInheritedAttribute("validgroup");
    });
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        pattern: 1,
        validtest: 3
    }, this.$attrExcludePropBind);

    this.$booleanProperties["required"] = true;
    this.$supportedProperties.push("validgroup", "required", "datatype",
        "pattern", "min", "max", "maxlength", "minlength", "validtest",
        "notnull", "checkequal", "invalidmsg", "requiredmsg");

    this.$fValidate = function(){
        if (this.liveedit)
            return;
        
        if (!this.$validgroup)
            this.validate(true);
        else {
             var errBox = this.$validgroup.getErrorBox(this);
             if (!errBox.visible || errBox.host != this)
                this.validate(true);
        }
    };
    this.addEventListener("blur", this.$fValidate);
    
    this.$propHandlers["validgroup"] = function(value) {
        if (value) {
            var vgroup;
            if (typeof value != "string") {
                this.$validgroup = value.name;
                vgroup = value;
            }
            else {
                
                vgroup = apf.nameserver.get("validgroup", value);
                
            }

            this.$validgroup = vgroup || new apf.ValidationGroup(value);
            this.$validgroup.register(this);
            /*
                @todo What about children, when created after start
                See button login action
            */
        }
        else {
            this.$validgroup.unregister(this);
            this.$validgroup = null;
        }
    };
    
    this.$propHandlers["pattern"] = function(value, prop) {
        if (value.substr(0, 1) != "/")
            value = "/" + value + "/";

        (this.$vOptions || (this.$vOptions = {}))[prop] = value;
        delete this.$vOptions.isValid;
    };
    
    
    this.$propHandlers["required"] = 
    this.$propHandlers["custom"] = 
    this.$propHandlers["min"] = 
    this.$propHandlers["max"] = 
    this.$propHandlers["maxlength"] = 
    this.$propHandlers["minlength"] = 
    this.$propHandlers["notnull"] = 
    this.$propHandlers["checkequal"] = function(value, prop) {
        (this.$vOptions || (this.$vOptions = {}))[prop] = value;
        delete this.$vOptions.isValid;
    };
    
    //@todo rewrite this for apf3.0 - it should just execute a live markup
    this.$propHandlers["validtest"] = function(value) {
        var _self = this, rvCache = {};
        /**
         * Removes the validation cache created by the validtest rule.
         */
        this.removeValidationCache = function(){
            rvCache = {};
        }
        
        this.$checkRemoteValidation = function(){
            var value = this.getValue();
            if (typeof rvCache[value] == "boolean") return rvCache[value];
            if (rvCache[value] == -1) return true;
            rvCache[value] = -1;

            apf.getData(this.validtest.toString(), {
               xmlNode: this.xmlRoot,
               value: this.getValue(),
               callback: function(data, state, extra) {
                  if (state != apf.SUCCESS) {
                      if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries)
                          return extra.tpModule.retry(extra.id);
                      else {
                          var commError = new Error(apf.formatErrorString(0, _self, 
                            "Validating entry at remote source", 
                            "Communication error: \n\n" + extra.message));

                          if (_self.dispatchEvent("error", apf.extend({
                            error: commError, 
                            state: status
                          }, extra)) !== false)
                              throw commError;
                          return;
                      }
                  }

                  rvCache[value] = apf.isTrue(data);//instr[1] ? data == instr[1] : apf.isTrue(data);
                  
                  if (!rvCache[value]){
                    if (!_self.hasFocus())
                        _self.setError();
                  }
                  else _self.clearError();
              }
            });
            
            return true;
        };
        
        (this.$vOptions || (this.$vOptions = {})).custom = "apf.lookup(" + this.$uniqueId + ").$checkRemoteValidation()";
        delete this.$vOptions.isValid;
    };
};


apf.GuiElement.propHandlers["required"] = 
apf.GuiElement.propHandlers["pattern"] = 
apf.GuiElement.propHandlers["min"] = 
apf.GuiElement.propHandlers["max"] = 
apf.GuiElement.propHandlers["maxlength"] = 
apf.GuiElement.propHandlers["minlength"] = 
apf.GuiElement.propHandlers["notnull"] = 
apf.GuiElement.propHandlers["checkequal"] = 
apf.GuiElement.propHandlers["validtest"] = function(value, prop) {
    this.implement(apf.Validation);
    this.$propHandlers[prop].call(this, value, prop);
}

/**
 * This object allows for a set of AML elements to be validated. An element that 
 * is not valid shows an errorbox.
 *
 * #### Example
 *
 * ```xml
 *  <a:bar validgroup="vgForm">
 *      <a:label>Phone number</a:label>
 *      <a:textbox id="txtPhone"
 *        required = "true"
 *        pattern = "(\d{3}) \d{4} \d{4}"
 *        invalidmsg = "Incorrect phone number entered" />
 *
 *      <a:label>Password</a:label>
 *      <a:textbox
 *        required = "true"
 *        mask = "password"
 *        minlength = "4"
 *        invalidmsg = "Please enter a password of at least four characters" />
 *  </a:bar>
 * ```
 *
 * To check if the element has valid information entered, leaving the textbox
 * (focussing another element) will trigger a check. Programmatically, a check
 * can be done using the following code:
 *
 * 
 * ```javascript
 *  txtPhone.validate();
 *
 *  // Or, use the html5 syntax
 *  txtPhone.checkValidity();
 * ```
 *
 * To check for the entire group of elements, use the validation group. For only
 * the first non-valid element the errorbox is shown. That element also receives
 * focus.
 * 
 * ```javascript
 *  vgForm.validate();
 * ```
 *
 * @class apf.ValidationGroup
 * @inherits apf.Class
 * @default_private
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 */
/**
 * @event validation Fires when the validation group isn't validated.
 */
apf.ValidationGroup = function(name) {
    this.$init();
    
    this.childNodes = [];
    
    if (name)
        apf.setReference(name, this);
    
    this.name = name || "validgroup" + this.$uniqueId;
    
    apf.nameserver.register("validgroup", this.name, this);
    
};

(function(){
    /**
     * When set to true, only visible elements are validated.
     * @type Boolean
     */
    this.validateVisibleOnly = false;
    
    /**
     * When set to true, validation doesn't stop at the first invalid element.
     * @type Boolean
     */
    this.allowMultipleErrors = false;

    /**
     * Adds an AML element to this validation group.
     * @param o {apf.AmlElement} The AML element to add
     */
    this.register = function(o) { 
        if (o.hasFeature(apf.__VALIDATION__)) 
            this.childNodes.push(o);
    };
    
    /**
     * Removes a AML element from this validation group.
     * @param o {apf.AmlElement} The AML element to remove
     */
    this.unregister = function(o) {
        this.childNodes.remove(o); 
    };

    /**
     * Returns a string representation of this object.
     */
    this.toString = function(){
        return "[APF Validation Group]";
    };

    //Shared among all validationgroups
    var errbox;
    /**
     * Retrieves the {@link apf.errorbox} used for a specified element.
     *
     * @param  {apf.AmlNode}  o An AMLNode specifying the element for which the Errorbox should be found. If none is found, an Errorbox is created. Use the {@link apf.ValidationGroup.allowMultipleErrors} to influence when Errorboxes are created.
     * @param {Boolean}   no_create    Boolean that specifies whether new Errorbox may be created when it doesn't exist already
     * @return  {apf.errorbox}  The found (or created) Errorbox
     */
    this.getErrorBox = function(o, no_create) {
        if (this.allowMultipleErrors || !errbox && !no_create) {
            errbox = new apf.errorbox();
            errbox.$pHtmlNode = o.$ext.parentNode;
            errbox.skinset = apf.getInheritedAttribute(o.parentNode, "skinset"); //@todo use skinset here. Has to be set in presentation
            errbox.dispatchEvent("DOMNodeInsertedIntoDocument");
        }
        return errbox;
    };

    /**
     * Hide all Errorboxes for the elements using this element as their validation group.
     *
     */
    this.hideAllErrors = function(){
        if (errbox && errbox.host)
            errbox.host.clearError();
    };

    function checkValidChildren(oParent, ignoreReq, nosetError) {
        var found;
        //Per Element
        for (var v, i = 0; i < oParent.childNodes.length; i++) {
            var oEl = oParent.childNodes[i];

            if (!oEl)
                continue;
            if (!oEl.disabled
              && (!this.validateVisibleOnly && oEl.visible || !oEl.$ext || oEl.$ext.offsetHeight)
              && (oEl.hasFeature(apf.__VALIDATION__) && oEl.isValid && !oEl.isValid(!ignoreReq))) {
                //|| !ignoreReq && oEl.required && (!(v = oEl.getValue()) || new String(v).trim().length == 0)
                
                if (!nosetError) {
                    if (!found) {
                        oEl.validate(true, null, true);
                        found = true;
                        if (!this.allowMultipleErrors)
                            return true; //Added (again)
                    }
                    else if (oEl.errBox && oEl.errBox.host == oEl)
                        oEl.errBox.hide();
                }
                else if (!this.allowMultipleErrors)
                    return true;
            }
            if (oEl.canHaveChildren && oEl.childNodes.length) {
                found = checkValidChildren.call(this, oEl, ignoreReq, nosetError) || found;
                if (found && !this.allowMultipleErrors)
                    return true; //Added (again)
            }
        }
        return found;
    }

    
    
    /**
     * 
     * @inheritDoc apf.ValidationGroup.isValid
     * @method
     */
    this.validate =
    
    /**
     * Checks if (part of) the set of element's registered to this element are
     * valid. When an element is found with an invalid value, the error state can
     * be set for that element.
     *
     * @method isValid
     * @param {Boolean}     [ignoreReq]  Specifies whether to adhere to the 'required' check.
     * @param {Boolean}     [nosetError  Specifies whether to not set the error state of the element with an invalid value
     * @param  {apf.AmlElement} [tab]   The tab for which the children will be checked. When not specified all elements of this validation group are checked.
     * @return  {Boolean}  Specifies whether the checked elements are valid.
     */
    this.isValid = function(ignoreReq, nosetError, tab) {
        var found = checkValidChildren.call(this, tab || this, ignoreReq,
            nosetError);

        if (tab) {
            
                if (tab.validation && !eval(tab.validation)) {
                    alert(tab.invalidmsg);
                    found = true;
                }
            
        }

        //Global Rules
        //
        //if (!found)
            //found = this.dispatchEvent("validation");

        return !found;
    };
}).call(apf.ValidationGroup.prototype = new apf.Class());

apf.config.$inheritProperties["validgroup"] = 1;






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__DATABINDING__ = 1 << 1;



/**
 * This is a baseclass that adds data binding features to this element. 
 * Databinding takes care of automatically going from data to representation and establishing a
 * permanent link between the two. In this way data that is changed will
 * change the representation as well. Furthermore, actions that are executed on
 * the representation will change the underlying data.
 * 
 * #### Example
 *
 * ```xml
 *  <a:list>
 *      <a:model>
 *          <data>
 *              <item icon="ajax_org.gif">Item 1</item>
 *              <item icon="ajax_org.gif">Item 2</item>
 *          </data>
 *      </a:model>
 *      <a:bindings>
 *          <a:icon match="[@icon]" />
 *          <a:caption match="[text()]" />
 *          <a:each match="[item]" />
 *      </a:bindings>
 *  </a:list>
 * ```
 *
 * @class apf.DataBinding
 * @inherits apf.Presentation
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 * @default_private
 */
/**
 * @event error             Fires when a communication error has occured while
 *                          making a request for this element.
 * @cancelable Prevents the error from being thrown.
 * @bubbles
 * @param {Object} e The standard event object. It contains the following properties:
 *                   - error ([[Error]]): the error object that is thrown when the event callback doesn't return false.
 *                   - state ([[Number]]): the state of the call
 *                                                - `apf.SUCCESS`:  The request was successfull
 *                                                - `apf.TIMEOUT`:  The request has timed out.
 *                                                - `apf.ERROR  `:  An error has occurred while making the request.
 *                                                - `apf.OFFLINE`:  The request was made while the application was offline.
 *                   - userdata (`Mixed`): Data that the caller wanted to be available in the callback of the http request.
 *                   - http ([[XMLHttpRequest]]): The object that executed the actual http request.
 *                   - url ([[String]]): The url that was requested.
 *                   - tpModule ([[apf.http]]): The teleport module that is making the request.
 *                   - id ([[Number]]): The ID of the request.
 *                   - message ([[String]]): The error message.
 */
/** 
 * @event beforeretrieve    Fires before a request is made to retrieve data.
 * @cancelable Prevents the data from being retrieved.
 */
/**
 * @event afterretrieve     Fires when the request to retrieve data returns both
 *                          on success and failure.
 */
/**
 * @event receive           Fires when data is successfully retrieved
 * @param {Object} e The standard event object. It contains the following properties:
 *                   - data ([[String]]): the retrieved data
 *
 */
apf.DataBinding = function(){
    this.$init(true);
    
    this.$loadqueue = 
    this.$dbTimer = null;
    this.$regbase = this.$regbase | apf.__DATABINDING__;
    this.$mainBind = "value";
    
    this.$bindings = 
    this.$cbindings = 
    this.$attrBindings = false;

    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        model: 1,
        each: 1
        //eachvalue : 1 //disabled because of line 1743 valueRule = in multiselect.js
    }, this.$attrExcludePropBind);

    // *** Public Methods *** //

    /**
     * Sets a value of an XMLNode based on an xpath statement executed on the data of this model.
     *
     * @param {String}   xpath  The xpath used to select a XMLNode
     * @param {String}   value  The value to set
     * @return  {XMLNode}  The changed XMLNode
     */
    this.setQueryValue = function(xpath, value, type) {
        var node = apf.createNodeFromXpath(this[type || 'xmlRoot'], xpath);
        if (!node)
            return null;

        apf.setNodeValue(node, value, true);
        //apf.xmldb.setTextNode(node, value);
        return node;
    };

    /**
     * Queries the bound data for a string value
     *
     * @param {String} xpath  The XPath statement which queries on the data this element is bound on.
     * @param {String} type   The node that is used as the context node for the query. It can be one of the following possible values:
     *                         - `"selected"`:    The selected data anode of this element.
     *                         - `"xmlRoot"`:   The root data node that this element is bound on.
     *                         - `"indicator"`:   The data node that is highlighted for keyboard navigation.
     * @return {String}       The value of the selected XML Node
     * 
     */
    this.queryValue = function(xpath, type) {
     /*   @todo
     *  lstRev.query('revision/text()', 'selected');
     *  lstRev.query('revision/text()', 'xmlRoot');
     *  lstRev.query('revision/text()', 'indicator');
     */
        return apf.queryValue(this[type || 'xmlRoot'], xpath );
    };
    /**
     * Queries the bound data for an array of string values
     *
     * @param {String} xpath The XPath statement which queries on the data this element is bound on.
     * @param {String} type   The node that is used as the context node for the query. It can be one of the following possible values:
     *                         - `"selected"`:    The selected data anode of this element.
     *                         - `"xmlRoot"`:   The root data node that this element is bound on.
     *                         - `"indicator"`:   The data node that is highlighted for keyboard navigation.
     * @return {String}       The value of the selected XML Node
     */
    this.queryValues = function(xpath, type) {
        return apf.queryValues(this[type || 'xmlRoot'], xpath );
    };
    
    /**
     * Executes an XPath statement on the data of this model
     *
     * @param {String} xpath The XPath statement which queries on the data this element is bound on.
     * @param {String} type   The node that is used as the context node for the query. It can be one of the following possible values:
     *                         - `"selected"`:    The selected data anode of this element.
     *                         - `"xmlRoot"`:   The root data node that this element is bound on.
     *                         - `"indicator"`:   The data node that is highlighted for keyboard navigation.
     * @return  {Mixed}  An [[XMLNode]] or [[NodeList]] with the result of the selection
     */
    this.queryNode = function(xpath, type) {
        var n = this[type||'xmlRoot'];
        return n ? n.selectSingleNode(xpath) : null;
    };

    /**
     * Executes an XPath statement on the data of this model
     *
     * @param {String}    xpath    The XPath used to select the XMLNode(s)
     * @param {String} type   The node that is used as the context node for the query. It can be one of the following possible values:
     *                         - `"selected"`:    The selected data anode of this element.
     *                         - `"xmlRoot"`:   The root data node that this element is bound on.
     *                         - `"indicator"`:   The data node that is highlighted for keyboard navigation.
     * @return  {Mixed}  An [[XMLNode]] or [[NodeList]] with the result of the selection
     */
    this.queryNodes = function(xpath, type) {
        var n = this[type||'xmlRoot'];
        return n ? n.selectNodes(xpath) : [];
    };
    
    this.$checkLoadQueue = function(){
        // Load from queued load request
        if (this.$loadqueue) {
            if (!this.caching)
                this.xmlRoot = null;
            var q = this.load(this.$loadqueue[0], {cacheId: this.$loadqueue[1]});
            if (!q || q.dataType != apf.ARRAY || q != this.$loadqueue)
                this.$loadqueue = null;
        }
        else return false;
    };
    
    //setProp
    this.$execProperty = function(prop, xmlNode, undoObj) {
        var attr = this.$attrBindings[prop];
        
        //@todo this is a hacky solution for replaceNode support - Have to rethink this.
        if (this.nodeType == 7) {
            if (xmlNode != this.xmlRoot)
                this.xmlRoot = xmlNode;
        }
        
        
        
        

        
        try {
        
            if (attr.cvalue.asyncs) { //if async
                var _self = this;
                return attr.cvalue.call(this, xmlNode, function(value) {
                    _self.setProperty(prop, value, true);
                    
                    
                
                }); 
            }
            else {
                var value = attr.cvalue.call(this, xmlNode);
            }
        
        }
        catch (e) {
            apf.console.warn("[400] Could not execute binding for property "
                + prop + "\n\n" + e.message);
            return;
        }
        
        
        this.setProperty(prop, undoObj && undoObj.extra.range || value, true); //@todo apf3.0 range
        
        
    };
    
    //@todo apf3.0 contentEditable support
    this.$applyBindRule = function(name, xmlNode, defaultValue, callback, oHtml) {
        var handler = this.$attrBindings[name] 
          && this.$attrBindings[name].cvalue || this.$cbindings[name];

        return handler ? handler.call(this, xmlNode, callback) : defaultValue || "";
    };

    
    
    this.$hasBindRule = function(name) {
        return this.$attrBindings[name] || this.$bindings 
          && this.$bindings[name];
    };
    
    this.$getBindRule = function(name, xmlNode) {
        return this.$attrBindings[name] || this.$bindings 
          && this.$bindings.getRule(name, xmlNode);
    };
    
    var ruleIsMatch = {"drag":1,"drop":1,"dragcopy":1}
    this.$getDataNode = function(name, xmlNode, createNode, ruleList, multiple) {
        var node, rule = this.$attrBindings[name];
        if (rule) { //@todo apf3.0 find out why drag and drop rules are already compiled here
            if (rule.cvalue.type != 3) //@todo warn here?
                return false;
            
            var func = rule.cvalue2 || rule.compile("value", {
                xpathmode: multiple ? 4 : 3,
                parsecode: 1,
                injectself: ruleIsMatch[name]
            });
            if (func && (node = func(xmlNode, createNode))) {
                if (ruleList)
                    ruleList.push(rule);

                return node;
            }
            
            return false;
        }
        
        return this.$bindings 
           && this.$bindings.getDataNode(name, xmlNode, createNode, ruleList, multiple);
    };
    
    
    /**
     * Sets the model of the specified element. 
     * The model acts as a datasource for this element.
     *
     * @param  {apf.model}  The model this element is going to connect to.
     * 
     */
    this.setModel = function(model) {
        this.setAttribute("model", model, false, true);
    };
    
    
    /**
     * Gets the model which this element is connected to.
     * The model acts as a datasource for this element.
     *
     * @param {Boolean} doRecur Specifies whether the model should be searched recursively up the data tree.
     * @returns  {apf.model}  The model this element is connected to.
     * @see apf.smartbinding
     */
    this.getModel = function(doRecur) {
        if (doRecur && !this.$model)
            return this.dataParent ? this.dataParent.parent.getModel(true) : null;

        return this.$model;
    };
    
    /**
     * Reloads the data in this element.
     * @method 
     */
    this.reload = this.reload || function(){
        this.load(this.xmlRoot, {cacheId: this.cacheId, force: true});
    };

    /**
     * @event beforeload  Fires before loading data in this element.
     * @cancelable Prevents the data from being loaded.
     * @param {XMLElement} xmlNode The node that is loaded as the root {@link term.datanode data node}.
     *   
     */
    /** 
     * @event afterload   Fires after loading data in this element.
     * @param {XMLElement} xmlNode The node that is loaded as the root {@link term.datanode data node}.
     */
    /**
     * Loads data into this element using binding rules to transform the
     * data into a presentation.
     * 
     * #### Example
     * 
     * ```xml 
     *  <a:list id="lstExample">
     *      <a:bindings>
     *          <a:caption match="[text()]" />
     *          <a:icon match="[@icon]" />
     *          <a:each match="[image]" />
     *      </a:bindings>
     *  </a:list>
     *  
     *  <a:script><!--
     *      apf.onload = function() {
     *      lstExample.load('<images>\
     *          <image icon="icoTest.gif">image 1</image>\
     *          <image icon="icoTest.gif">image 2</image>\
     *          <image icon="icoTest.gif">image 3</image>\
     *          </images>');
     *      }
     *  --></a:script>
     * ```
     *
     * @param {XMLElement | String}  [xmlNode] The content to load into this element. It can be one of the following values:
     *                                                - {XMLElement}: An XML element that's loaded into this element
     *                                                - {String}: Either an XML string, or, an instruction to load the data from a remote source
     *                                                - `null`: Clears this element from its data
     * @param {Object} [options] Set of additional options to pass. Properties include:
     *                           - [xmlNode] ([[XMLElement]]):   The {@link term.datanode data node} that provides
     *                                                       context to the data instruction.
     *                           - [callback] ([[Function]]): The code executed when the data request returns
     *                           - [properties] (`Mixed`): Custom properties available in the data instruction
     *                           - [cacheId] ([[String]]): The xml element to which the binding rules are applied
     *                           - [force] ([[Boolean]]): Specifies whether cache is checked before loading the data
     *                           - [noClearMsg] ([[Boolean]]): Specifies whether a message is set when clear is called
     */
    this.load = function(xmlNode, options) {
        if (options) {
            var cacheId = options.cacheId,
                forceNoCache = options.force,
                noClearMsg = options.noClearMsg;
        }
        if (cacheId && cacheId == this.cacheId && !forceNoCache)
            return;

        
        if (apf.popup.isShowing(this.$uniqueId))
            apf.popup.forceHide(); //This should be put in a more general position
        

        // Convert first argument to an xmlNode we can use;
        if (xmlNode) {
            if (typeof xmlNode == "string") {
                if (xmlNode.charAt(0) == "<")
                    xmlNode = apf.getXmlDom(xmlNode).documentElement;
                else {
                    return apf.model.prototype.$loadFrom.call(this, xmlNode, options);
                }
            }
            else if (xmlNode.nodeType == 9) {
                xmlNode = xmlNode.documentElement;
            }
            else if (xmlNode.nodeType == 3 || xmlNode.nodeType == 4) {
                xmlNode = xmlNode.parentNode;
            }
            else if (xmlNode.nodeType == 2) {
                xmlNode = xmlNode.ownerElement 
                    || xmlNode.parentNode 
                    || xmlNode.selectSingleNode("..");
            }
        }

        // If control hasn't loaded databinding yet, queue the call
        if (this.$preventDataLoad || !this.$canLoadData 
          && ((!this.$bindings && (!this.$canLoadDataAttr || !this.each)) || !this.$amlLoaded) 
          && (!this.hasFeature(apf.__MULTISELECT__) || !this.each) 
          || this.$canLoadData && !this.$canLoadData()) {
            
            if (!this.caching || !this.hasFeature(apf.__CACHE__)) {
                
                //@todo this is wrong. It is never updated when there are only
                //Property binds and then it just leaks xml nodes
                this.xmlRoot = xmlNode;
                
                
                this.setProperty("root", this.xmlRoot);
                
            }
            
            
            
            return this.$loadqueue = [xmlNode, cacheId];
        }
        this.$loadqueue = null;

        // If no xmlNode is given we clear the control, disable it and return
        if (this.dataParent && this.dataParent.xpath)
            this.dataParent.parent.signalXmlUpdate[this.$uniqueId] = !xmlNode;

        if (!xmlNode && (!cacheId || !this.$isCached || !this.$isCached(cacheId))) {
            

            this.clear(noClearMsg);

            
            if (apf.config.autoDisable && this.$createModel === false)
                this.setProperty("disabled", true);

            //@todo apf3.0 remove , true in clear above
            //this.setProperty("selected", null);
            
            return;
        }
        
        // If reloading current document, and caching is disabled, exit
        if (!this.caching && !forceNoCache && xmlNode 
          && !this.$loadqueue && xmlNode == this.xmlRoot)
            return;

        var disabled = this.disabled;
        this.disabled = false;

        //Run onload event
        if (this.dispatchEvent("beforeload", {xmlNode : xmlNode}) === false)
            return false;

        

        this.clear(true, true);

        this.cacheId = cacheId;

        if (this.dispatchEvent("$load", {
          forceNoCache: forceNoCache, 
          xmlNode: xmlNode
        }) === false) {
            //delete this.cacheId;
            return;
        }
        
        //Set usefull vars
        this.documentId = apf.xmldb.getXmlDocId(xmlNode);
        this.xmlRoot = xmlNode;
        
        
        this.setProperty("root", this.xmlRoot);
        

        

        // Draw Content
        this.$load(xmlNode);
        
        

        // Check if subtree should be loaded
        this.$loadSubData(xmlNode);

        if (this.$createModel === false) {
            this.disabled = true;
            this.setProperty("disabled", false);
        }
        else
            this.disabled = disabled;

        // Run onafteronload event
        this.dispatchEvent('afterload', {xmlNode : xmlNode});
    };
    
    // @todo Doc
    /*
     * @binding load Determines how new data is loaded data is loaded into this
     * element. Usually this is only the root node containing no children.
     * 
     * #### Example
     * 
     * This example shows a load rule in a text element. It gets its data from
     * a list. When a selection is made on the list the data is loaded into the
     * text element.
     * 
     * ```xml
     *  <a:list id="lstExample" width="200" height="200">
     *      <a:bindings>
     *          <a:caption match="[text()]" />
     *          <a:value match="[text()]" />
     *          <a:each match="[message]" />
     *      </a:bindings>
     *      <a:model>
     *          <messages>
     *              <message id="1">message 1</message>
     *              <message id="2">message 2</message>
     *          </messages>
     *      </a:model>
     *  </a:list>
     * 
     *  <a:text model="{lstExample.selected}" width="200" height="150">
     *      <a:bindings>
     *          <a:load get="http://localhost/getMessage.php?id=[@id]" />
     *          <a:contents match="[message/text()]" />
     *      </a:bindings>
     *  </a:text>
     * ```
     *
     */
     /**
      * @attribute {String} get Sets or gets the {@link term.datainstruction data instruction}
      *                     that is used to load data into the XML root of this component.
      */
    this.$loadSubData = function(xmlRootNode) {
        if (this.$hasLoadStatus(xmlRootNode) && !this.$hasLoadStatus(xmlRootNode, "potential")) 
            return;

        //var loadNode = this.$applyBindRule("load", xmlRootNode);
        var rule = this.$getBindRule("load", xmlRootNode);
        if (rule && (!rule[1] || rule[1](xmlRootNode))) {
            
            
            this.$setLoadStatus(xmlRootNode, "loading");

            if (this.$setClearMessage)
                this.$setClearMessage(this["loading-message"], "loading");

            //||apf.xmldb.findModel(xmlRootNode)
            var mdl = this.getModel(true);

            
            var amlNode = this;
            if (mdl.$insertFrom(rule.getAttribute("get"), {
              xmlNode: xmlRootNode,  //@todo apf3.0
              insertPoint: xmlRootNode, //this.xmlRoot,
              amlNode: this,
              callback: function(){
                    
                    amlNode.setProperty(amlNode.hasFeature(apf.__MULTISELECT__) 
                        ? "selected" 
                        : "root", xmlRootNode);
                    
                }
              }) === false
            ) {
                this.clear(true);
                
                if (apf.config.autoDisable)
                    this.setProperty("disabled", true);

                //amlNode.setProperty("selected", null); //@todo is this not already done in clear?
                
            }
        }
    };
    
    //@todo this function is called way too much for a single load of a tree
    //@todo should clear listener
    /*
     * Unloads data from this element and resets state displaying an empty message.
     * The empty message is set on the {@link apf.GuiElement.msg}.
     *
     * @param {Boolean} [nomsg]   Specifies whether to display the empty message.
     * @param {Boolean} [doEvent] Specifies whether to send select events.
     * @see baseclass.databinding.method.load
     * @private
     */
    this.clear = function(nomsg, doEvent, fakeClear) {
        if (!this.$container)
            return;//@todo apf3.0

        if (this.clearSelection)
            this.clearSelection(true);//!doEvent);//@todo move this to the $clear event in multiselect.js

        var lastHeight = this.$container.offsetHeight;

        if (this.dispatchEvent("$clear") !== false)
            this.$container.innerHTML = ""; //@todo apf3.0

        if (typeof nomsg == "string") {
            var msgType = nomsg;
            nomsg = false;
            
            //@todo apf3.0 please use attr. inheritance
            if (!this[msgType + "-message"]) {
                this.$setInheritedAttribute(msgType + "-message");
            }
        }
        this.$lastClearType = msgType || null;

        if (!nomsg && this.$setClearMessage) {
            this.$setClearMessage(msgType 
              ? this[msgType + "-message"] 
              : this["empty-message"], msgType || "empty", lastHeight);

            //this.setProperty("selected", null); //@todo apf3.0 get the children to show loading... as well (and for each selected, null
            //c[i].o.clear(msgType, doEvent);
        }
        else if (this.$removeClearMessage)
            this.$removeClearMessage();

        if (!fakeClear)
            this.documentId = this.xmlRoot = this.cacheId = null;

        
        if (!nomsg) {
            if (this.hasFeature(apf.__MULTISELECT__)) //@todo this is all wrong
                this.setProperty("length", 0);
            //else 
                //this.setProperty("value", ""); //@todo redo apf3.0
        }
        
    };
    
    this.clearMessage = function(msg) {
        this.customMsg = msg;
        this.clear("custom");
    };

    //@todo optimize this
    /**
     * @private
     */
    this.$setLoadStatus = function(xmlNode, state, remove) {
        var group = this.loadgroup || "default";
        var re = new RegExp("\\|(\\w+)\\:" + group + ":(\\d+)\\|");
        var loaded = xmlNode.getAttribute("a_loaded") || "";

        var m;        
        if (!remove && (m = loaded.match(re)) && m[1] != "potential" && m[2] != this.$uniqueId)
            return;
        
        //remove old status if any
        var ostatus = loaded.replace(re, "")
        if (!remove)
            ostatus += "|" + state + ":" + group + ":" + this.$uniqueId + "|";

        xmlNode.setAttribute("a_loaded", ostatus);
    };

    /**
     * @private
     */
    this.$removeLoadStatus = function(xmlNode) {
        this.$setLoadStatus(xmlNode, null, true);
    };

    /**
     * @private
     */
    this.$hasLoadStatus = function(xmlNode, state, unique) {
        if (!xmlNode)
            return false;
        var ostatus = xmlNode.getAttribute("a_loaded");
        if (!ostatus)
            return false;
    
        var group = this.loadgroup || "default";
        var re = new RegExp("\\|" + (state || "\\w+") + ":" + group + ":" + (unique ? this.$uniqueId : "\\d+") + "\\|");
        return ostatus.match(re) ? true : false;
    };

    /*
     * @event beforeinsert Fires before data is inserted.
     * @cancelable Prevents the data from being inserted.
     * @param {XMLElement} xmlParentNode The parent in which the new data is inserted
     */
     /**
      * @event afterinsert Fires after data is inserted.
     */

    /**
     * @private
     */
    this.insert = function(xmlNode, options) {
        if (typeof xmlNode == "string") {
            if (xmlNode.charAt(0) == "<") {
                
                if (options.whitespace === false)
                    xmlNode = xmlNode.replace(/>[\s\n\r]*</g, "><");
                
                xmlNode = apf.getXmlDom(xmlNode).documentElement;
            }
            else {
                if (!options.insertPoint)
                    options.insertPoint = this.xmlRoot;
                return apf.model.prototype.$insertFrom.call(this, xmlNode, options);
            }
        }
        
        var insertPoint = options.insertPoint || this.xmlRoot;

        if (this.dispatchEvent("beforeinsert", {
          xmlParentNode: insertPoint
        }) === false)
            return false;

        //Integrate XMLTree with parentNode
        if (typeof options.copyAttributes == "undefined")
            options.copyAttributes = true;
        
        if (this.filterUnique)
            options.filter = this.filterUnique;
        
        var newNode = apf.mergeXml(xmlNode, insertPoint, options);
        
        this.$isLoading = true; //Optimization for simpledata

        //Call __XMLUpdate on all listeners
        apf.xmldb.applyChanges("insert", insertPoint);
        
        this.$isLoading = false;

        //Select or propagate new data
        if (this.selectable && this.autoselect) {
            if (this.xmlNode == newNode)
                this.$selectDefault(this.xmlNode);
        }
        
        else if (this.xmlNode == newNode) {
            this.setProperty("root", this.xmlNode);
        }
        

        if (this.$hasLoadStatus(insertPoint, "loading"))
            this.$setLoadStatus(insertPoint, "loaded");

        this.dispatchEvent("afterinsert");

        //Check Connections
        //this one shouldn't be called because they are listeners anyway...(else they will load twice)
        //if(this.selected) this.setConnections(this.selected, "select");
    };
    
    /**
     * @attribute {Boolean} render-root Sets or gets whether the XML element loaded into this
     * element is rendered as well. The default is false.
     *
     * #### Example
     *
     * This example shows a tree which also renders the root element.
     * 
     * ```xml
     *  <a:tree render-root="true">
     *      <a:model>
     *          <root name="My Computer">
     *              <drive name="C">
     *                  <folder name="/Program Files" />
     *                  <folder name="/Desktop" />
     *              </drive>
     *          </root>
     *      </a:model>
     *      <a:bindings>
     *          <a:caption match="[@name]"></a:caption>
     *          <a:each match="[root|drive|folder]"></a:each>
     *      </a:bindings>
     *  </a:tree>
     * ```
     */
    this.$booleanProperties["render-root"] = true;
    this.$supportedProperties.push("empty-message", "loading-message",
        "offline-message", "render-root", "smartbinding",
        "bindings", "actions");

    /**
     * @attribute {Boolean} render-root Sets or gets whether the root node of the data loaded
     * into this element is rendered as well. 
     * @see apf.tree
     */
    this.$propHandlers["render-root"] = function(value) {
        this.renderRoot = value;
    };
    
    /**
     * @attribute {String} empty-message Sets or gets the message displayed by this element
     * when it contains no data. This property is inherited from parent nodes.
     * When none is found, it is looked for on the appsettings element. Otherwise
     * it defaults to the string "No items".
     */
    this.$propHandlers["empty-message"] = function(value) {
        this["empty-message"] = value;

        if (this.$updateClearMessage) 
            this.$updateClearMessage(this["empty-message"], "empty");
    };

    /**
     * @attribute {String} loading-message Sets or gets the message displayed by this
     * element when it's loading. This property is inherited from parent nodes.
     * When none is found, it is looked for on the appsettings element. Otherwise
     * it defaults to the string "Loading...".
     *
     * #### Example
     *
     * This example uses property bindings to update the loading message. The
     * position of the progressbar should be updated by the script taking care
     * of loading the data.
     *
     * ```xml
     *  <a:list loading-message="{'Loading ' + Math.round(progress1.value*100) + '%'}" />
     *  <a:progressbar id="progress1" />
     * ```
     *
     * #### Remarks
     *
     * Usually, a static loading message is displayed for only 100 milliseconds
     * or so, whilst loading the data from the server. For instance, this is done
     * when the load binding rule is used. In the code example below, a list
     * binds on the selection of a tree displaying folders. When the selection
     * changes, the list loads new data by extending the model. During the load
     * of this new data, the loading message is displayed.
     * 
     * ```xml
     *  <a:list model="[trFolders::element]">
     *      <a:bindings>
     *          ...
     *          <a:load get="{comm.getFiles([@path])}" />
     *      </bindings>
     *  </a:list>
     * ```
     */
    this.$propHandlers["loading-message"] = function(value) {
        this["loading-message"] = value;

        if (this.$updateClearMessage)
            this.$updateClearMessage(this["loading-message"], "loading");
    };

    /**
     * @attribute {String} offline-message Sets or gets the message displayed by this
     * element when it can't load data because the application is offline.
     * This property is inherited from parent nodes. When none is found it is
     * looked for on the appsettings element. Otherwise it defaults to the
     * string "You are currently offline...".
     */
    this.$propHandlers["offline-message"] = function(value) {
        this["offline-message"] = value;

        if (this.$updateClearMessage)
            this.$updateClearMessage(this["offline-message"], "offline");
    };

    /**
     * @attribute {String} smartbinding Sets or gets the name of the SmartBinding for this
     * element. 
     * 
     * A smartbinding is a collection of rules which define how data
     * is transformed into representation, how actions on the representation are
     * propagated to the data and it's original source, how drag&drop actions
     * change the data and where the data is loaded from. Each of these are
     * optionally defined in the smartbinding set and can exist independently
     * of the smartbinding object.
     * 
     * #### Example
     *
     * This example shows a fully specified smartbinding. Usually, only parts
     * are used. This example shows a tree with files and folders.
     * 
     * ```xml
     *  <a:tree smartbinding="sbExample" />
     * 
     *  <a:smartbinding id="sbExample">
     *      <a:bindings>
     *          <a:caption  match = "[@caption|@filename]"/>
     *          <a:icon     match = "[file]"
     *                      value = "icoFile.gif" />
     *          <a:icon     value = "icoFolder.gif" />
     *          <a:each     match = "[file|folder|drive]" />
     *          <a:drag     match = "[folder|file]" />
     *          <a:drop     match = "[folder]" 
     *                      target = "[root]"
     *                      action = "tree-append" />
     *          <a:drop     match = "[folder]" 
     *                      target = "[folder]"
     *                      action = "insert-before" />
     *          <a:drop     match = "[file]"   
     *                      target = "[folder|root]" 
     *                      action = "tree-append" />
     *          <a:drop     match = "[file]"   
     *                      target = "[file]"
     *                      action = "insert-before" />
     *      </a:bindings>
     *      <a:actions>
     *          <a:remove set = "remove.php?path=[@path]" />
     *          <a:rename set = "move.php?from=oldValue&amp;to=[@path]" />
     *      </a:actions>
     *      <a:model src="xml/filesystem.xml" />
     *  </a:smartbinding>
     * ```
     * 
     * #### Remarks
     *
     * The smartbinding parts can also be assigned to an element by adding them
     * directly as a child in aml.
     * 
     * ```xml
     *  <a:tree>
     *      <a:bindings>
     *          ...
     *      </bindings>
     *      <a:model />
     *  </a:tree>
     * </code>
     *
     * ### See Also
     *
     * There are several ways to be less verbose in assigning certain rules. For more information, see:
     *
     * * [[apf.bindings]]
     * * [[apf.actions]]
     * * [[apf.DragDrop]]
     * 
     */
    this.$propHandlers["smartbinding"] = 
    
    /**
     * @attribute {String} actions Sets or gets the id of the actions element which
     * provides the action rules for this element. Action rules are used to
     * send changes on the bound data to a server.
     *
     * #### Example
     *
     * ```xml
     *  <a:tree 
     *    id = "tree" 
     *    height = "200" 
     *    width = "250" 
     *    actions = "actExample"
     *    model = "xml/filesystem.xml"
     *    actiontracker = "atExample"
     *    startcollapsed = "false" 
     *    onerror = "alert('Sorry this action is not permitted');return false">
     *      <a:each match="[folder|drive]">
     *          <a:caption match="[@caption|@filename]" />
     *          <a:icon value="Famfolder.gif" />
     *      </a:each>
     *  </a:tree>
     *  
     *  <a:actions id="actExample">
     *      <a:rename match = "[file]"   
     *               set = "rename_folder.php?id=[@fid]" />
     *      <a:rename match = "[folder]" 
     *               set = "rename_file.php?id=[@fid]" />
     *  </a:actions>
     *  
     *  <a:button 
     *    caption = "Rename"
     *    right = "10" 
     *    top = "10"
     *    onclick = "tree.startRename()" />
     *  <a:button onclick="tree.getActionTracker().undo();">Undo</a:button>
     * ```
     */
    this.$propHandlers["actions"] = 

    /**
     * @attribute {String} bindings Sets or gets the id of the bindings element which
     * provides the binding rules for this element.
     * 
     * #### Example
     *
     * This example shows a set of binding rules that transform data into the
     * representation of a list. In this case it displays the names of
     * several email accounts, with after each account name the number of unread
     * mails in that account. It uses JSLT to transform the caption.
     * 
     * ```xml
     *  <a:model id="mdlExample">
     *      <data>
     *          <account icon="application.png">Account 1
     *              <mail read="false" />
     *              <mail read="false" />
     *              <mail read="true" />
     *          </account>
     *          <account icon="application.png">Account 2</account>
     *      </data>
     *  </a:model>
     *  <a:list bindings="bndExample" model="mdlExample" />
     * 
     *   <a:bindings id="bndExample">
     *      <a:caption>[text()] (#[mail[@read != 'true']])</a:caption>
     *      <a:icon match="[@icon]" />
     *      <a:each match="[account]" sort="[text()]" />
     *  </a:bindings>
     * ```
     * 
     * #### Remarks
     *
     * Bindings can also be assigned directly by putting the bindings tag as a
     * child of this element.
     *
     * If the rule only contains a select attribute, it can be written in a
     * short way by adding an attribute with the name of the rule to the
     * element itself:
     * 
     * ```xml
     *  <a:list 
     *    caption = "[text()] (#[mail[@read != 'true']])"
     *    icon = "[@icon]"
     *    each = "[account]"
     *    sort = "[text()]" />
     * ```
     */
    this.$propHandlers["bindings"] = function(value, prop) {
        var local = "$" + prop + "Element";
        if (this[local])
            this[local].unregister(this);
        
        if (!value)
            return;
        
        

        apf.nameserver.get(prop, value).register(this);
        
        
        if (prop != "actions" && 
          this.$checkLoadQueue() === false && this.$amlLoaded)
            1+1; //@todo add reload queue.
            //this.reload();
    };

    
    var eachBinds = {"caption":1, "icon":1, "select":1, "css":1, "sort":1,
                     "drop":2, "drag":2, "dragcopy":2, "eachvalue":1}; //Similar to apf.Class
    
    this.$addAttrBind = function(prop, fParsed, expression) {
        //Detect if it uses an external model
        if (fParsed.models) {
            
            if (this.hasFeature(apf.__MULTISELECT__)) {
                
            }
            
        }

        //Set listener for all models
        var i, xpath, modelId, model,
            paths = fParsed.xpaths,
            list = {};
        //@todo when there is no model in xpath modelId == null...
        for (i = 0; i < paths.length; i+=2) {
            if (!list[(modelId = paths[i])])
                list[modelId] = 1;
            else list[modelId]++
        }
        
        if (!this.$propsUsingMainModel)
            this.$propsUsingMainModel = {};

        var rule = (this.$attrBindings || (this.$attrBindings = {}))[prop] = {
            cvalue: fParsed,
            value: expression,
            compile: apf.BindingRule.prototype.$compile,
            models: []
        };

        delete this.$propsUsingMainModel[prop];
        for (xpath, i = 0; i < paths.length; i+=2) {
            modelId = paths[i];
            if (list[modelId] == -1)
                continue;

            xpath = paths[i + 1];

            if (modelId == "#" || xpath == "#") {
                var m = (rule.cvalue3 || (rule.cvalue3 = apf.lm.compile(rule.value, {
                    xpathmode: 5
                }))).call(this, this.xmlRoot);
                
                //@todo apf3 this needs to be fixed in live markup
                if (typeof m != "string") {
                    model = m.model && m.model.$isModel && m.model;
                    if (model)
                        xpath = m.xpath;
                    else if (m.model) {
                        model = typeof m.model == "string" ? apf.xmldb.findModel(m.model) : m.model;
                        xpath = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better
                    }
                    else {
                        //wait until model becomes available
                        this.addEventListener("prop." + prop, function(e) {
                            var m = (rule.cvalue3 || (rule.cvalue3 = apf.lm.compile(rule.value, {
                                xpathmode: 5
                            }))).call(this, this.xmlRoot);
                            
                            if (m.model) {
                                this.removeEventListener("prop." + prop, arguments.callee);
                                var _self = this;
                                $setTimeout(function(){
                                    _self.$clearDynamicProperty(prop);
                                    _self.$setDynamicProperty(prop, expression);
                                }, 10);
                            }
                        });
                        continue;
                    }
                }
                else model = null;
            }
            else model = null;

            if (!model) {
                if (modelId) {
                    
                    //@todo apf3.0 how is this cleaned up???
                    //Add change listener to the data of the model
                    model = apf.nameserver.get("model", modelId) //is model creation useful here?
                        || apf.setReference(modelId, apf.nameserver.register("model", modelId, new apf.model()));
                    
                }
                else {
                    if (!this.$model && !this.$initingModel)
                        initModel.call(this);
    
                    model = this.$model;

                    if (!this.hasFeature(apf.__MULTISELECT__) 
                      && eachBinds[prop] != 2 || !eachBinds[prop]) //@experimental - should not set this because model will load these attributes
                        this.$propsUsingMainModel[prop] = {
                            xpath: xpath,
                            optimize: list[modelId] == 1
                        };
                }
            }
            
            //@todo warn here if no model??
            if (model && (!this.hasFeature(apf.__MULTISELECT__) 
              && eachBinds[prop] != 2 || !eachBinds[prop])) {
                //Create the attribute binding
                //@todo: remove listenRoot = expression.indexOf("*[") > -1 -> because it doesnt make sense in certain context. recheck selection handling
                model.$bindXmlProperty(this, prop, xpath, list[modelId] == 1); 
                rule.models.push(model);
            }
            
            list[modelId] = -1;
        }
        
        rule.xpath = xpath;

        this.$canLoadDataAttr = eachBinds[prop] == 1; //@todo apf3.0 remove
        this.$checkLoadQueue();
    }
    
    this.$removeAttrBind = function(prop) {
        //@todo apf3.0
        //$model.$unbindXmlProperty
        var rule = this.$attrBindings[prop]
        if (!rule)
            return;
        
        delete this.$attrBindings[prop];
        delete this.$propsUsingMainModel[prop]
        
        var models = rule.models;
        if (models.length)
            for (var i = 0; i < models.length; i++) {
                models[i].$unbindXmlProperty(this, prop);
            }
        else if (this.$model)
            this.$model.$unbindXmlProperty(this, prop);
    };
    
    this.$initingModel;
    function initModel(){
        this.$initingModel = true;
        this.$setInheritedAttribute("model");
    }
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //Set empty message if there is no data
        if (!this.model && this.$setClearMessage && !this.value)
            this.$setClearMessage(this["empty-message"], "empty");
        
        this.$amlLoaded = true; //@todo this can probably be removed
        this.$checkLoadQueue();
    });
    
    

    /**
     * @attribute {String} model Sets or gets the name of the model to load data from, or a
     * datainstruction to load data.
     *
     * #### Example
     *
     * ```xml
     *  <a:model id="mdlExample" src="filesystem.xml" />
     *   <a:tree 
     *     height = "200" 
     *     width = "250" 
     *     model = "mdlExample">
     *       <a:each match="[folder|drive]">
     *           <a:caption match="[@caption]" />
     *           <a:icon value="Famfolder.gif" />
     *       </a:each>
     *   </a:tree>
     * ```
     * 
     * #### Example
     *
     * Here's an example loading from an XML source:
     *
     * ```xml
     *  <a:tree 
     *    height = "200" 
     *    width = "250" 
     *    model = "filesystem.xml">
     *      <a:each match="[folder|drive]">
     *          <a:caption match="[@caption]" />
     *          <a:icon value="Famfolder.gif" />
     *      </a:each>
     *  </a:tree>
     * ```
     * 
     * #### Example
     *
     * ```xml
     *  <a:tree 
     *     id = "tree"
     *     height = "200" 
     *     width = "250" 
     *     model = "filesystem.xml">
     *       <a:each match="[folder|drive]">
     *           <a:caption match="[@caption]" />
     *           <a:icon value="Famfolder.gif" />
     *       </a:each>
     *   </a:tree>
     *   <a:text 
     *     model = "{tree.selected}" 
     *     value = "[@caption]" 
     *     width = "250" 
     *     height = "100" />
     * ```
     * 
     * #### Example
     *
     * This example shows a dropdown from which the user can select a country.
     * The list of countries is loaded from a model. Usually this would be loaded
     * from a separate url, but for clarity it's inlined. When the user selects
     * a country in the dropdown the value of the item is stored in the second
     * model (mdlForm) at the position specified by the ref attribute. In this
     * case this is the country element.
     * 
     * ```xml
     *  <a:label>Name</a:label>
     *  <a:textbox value="[name]" model="mdlForm" />
     * 
     *  <a:label>Country</a:label>
     *  <a:dropdown
     *    value = "[mdlForm::country]"
     *    each = "[mdlCountries::country]"
     *    caption = "[text()]">
     *  </a:dropdown>
     * 
     *  <a:model id="mdlCountries">
     *      <countries>
     *          <country value="USA">USA</country>
     *          <country value="GB">Great Britain</country>
     *          <country value="NL">The Netherlands</country>
     *      </countries>
     *  </a:model>
     * 
     *  <a:model id="mdlForm">
     *      <data>
     *          <name />
     *          <country />
     *      </data>
     *  </a:model>
     * ```
     *
     * #### Remarks
     *
     * This attribute is inherited from a parent when not set. You can use this
     * to tell sets of elements to use the same model.
     * 
     * ```xml
     *  <a:bar model="mdlForm">
     *      <a:label>Name</a:label>
     *      <a:textbox value="[name]" />
     * 
     *      <a:label>Happiness</a:label>
     *      <a:slider value="[happiness]" min="0" max="10" />
     *  </a:bar>
     * 
     *  <a:model id="mdlForm">
     *      <data />
     *  </a:model>
     * ```
     *
     * When no model is specified the default model is chosen. The default
     * model is the first model that is found without a name, or if all models
     * have a name, the first model found.
     * 
     * @see apf.DataBinding.model
     */
    this.$propHandlers["model"] = function(value) {
        //Unset model
        if (!value && !this.$modelParsed) {
            if (this.$model) {
                this.clear();
                this.$model.unregister(this);
                this.$model = null;
                this.lastModelId = "";
            }
            else if (this.dataParent)
                this.dataParent.parent = null; //Should be autodisconnected by property binding

            return;
        }
        this.$initingModel = true;

        var fParsed;
        //Special case for property binding
        if ((fParsed = this.$modelParsed) && fParsed.type != 2) {
            var found, pb = fParsed.props;
            
            if (this.dataParent)
                this.dataParent = null; //Should be autodisconnected by property binding

            //Try to figure out who is the dataParent
            for (var prop in pb) {
                

                this.dataParent = {
                    parent: self[prop.split(".")[0]],
                    xpath: null,
                    model: this.$modelParsed.instruction
                };
        
                found = true;
                break; // We currently only support one data parent
            }
            
            if (found) {
                //@todo this statement doesnt make sense
                /*//Maybe a compound model is found
                if (!this.dataParent && (pb = fParsed.xpaths && fParsed.xpaths[0])) {
                    this.dataParent = {
                        parent: self[pb.split(".")[0]],
                        xpath: fParsed.xpaths[1],
                        model: this.$modelParsed.instruction
                    };
                }*/
                
                if (this.dataParent && !this.dataParent.signalXmlUpdate)
                    this.dataParent.signalXmlUpdate = {};
            }
            
            this.$modelParsed = null;
        }

        //Analyze the data
        var model;
        if (typeof value == "object") {
            if (value.dataType == apf.ARRAY) { //Optimization used for templating
                
                model = apf.nameserver.get("model", value[0]);
                model.register(this, value[1]);
                return;
                
            }
            else if (value.$isModel) { // A model node is passed
                //Convert model object to value;
                model = value;
                value = this.model = model.name;
                if (!value)
                    model.setProperty("id", value = this.model = "model" + model.$uniqueId);
                
                //@todo why not set directly here?
            }
            else { //if (this.dataParent) { //Data came through data parent
                if (this.dataParent)
                    this.model = this.dataParent.model; //reset this property

                model = apf.xmldb.findModel(value);
                if (!model) //@todo very strange, this should never happen, but it does
                    return;
                var xpath = apf.xmlToXpath(value, null, true) || ".";
                
                
                
                model.register(this, xpath);
                return;
            }
            /*else {
                //@todo Error ??
            }*/
        }
        else if (value.indexOf("[::") > -1) { //@experimental
            var model, pNode = this;
            do {
                pNode = pNode.parentNode
                model = pNode.getAttribute("model");
            }
            while (pNode.parentNode && pNode.parentNode.nodeType == 1 && (!model || model == value));

            if (model && typeof model == "object")
                model = model.id;

            this.$inheritProperties.model = 3;
            if (model) {
                value = value.replace(/\[\:\:/g, "[" + model + "::");
            }
            else {
                apf.console.warn("No found model on any of the parents for this element while trying to overload model: " + value);
                return;
            }
        }

        //Optimize xmlroot position and set model async (unset the old one)
        //@todo apf3.0 this could be optimized by using apf.queue and only when not all info is there...
        clearTimeout(this.$dbTimer);
        if (!this.$amlLoaded && this.nodeType == 1) {
            var _self = this;
            this.$dbTimer = $setTimeout(function(){
                if (!_self.$amlDestroyed)
                    apf.setModel(value, _self);
            });
        }
        else
            apf.setModel(value, this);
    };

    
    /**
     * @attribute {String} viewport Sets or gets the way this element renders its data.
     * 
     * The possible values include:
     *   - `"virtual"`:  this element only renders data that it needs to display.
     *   - `"normal`"`:  this element renders all data at startup.
     * @experimental
     */
    this.$propHandlers["viewport"] = function(value) {
        if (value != "virtual")
            return;

        this.implement(apf.VirtualViewport);
    };
    
};

    apf.DataBinding.prototype = new apf[apf.Presentation ? "Presentation" : "AmlElement"]();


apf.config.$inheritProperties["model"] = 1;
apf.config.$inheritProperties["empty-message"] = 1;
apf.config.$inheritProperties["loading-message"] = 1;
apf.config.$inheritProperties["offline-message"] = 1;
apf.config.$inheritProperties["noloading"] = 1;

apf.Init.run("databinding");






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * All elements inheriting from this {@link term.baseclass baseclass} can bind to data
 * which contains multiple nodes.
 *
 *
 *
 * @class apf.MultiselectBinding
 * @inherits apf.DataBinding
 * @baseclass
 * @default_private
 * @allowchild  item, choices
 */

/*
 * @define  choices     Container for item nodes which receive presentation.
 * This element is part of the XForms specification. It is not necesary for
 * the Ajax.org Markup Language.
 * 
 * #### Example
 *
 * ```xml
 *  <a:list>
 *      <a:choices>
 *          <a:item>red</a:item>
 *          <a:item>blue</a:item>
 *          <a:item>green</a:item>
 *      </a:choices>
 *  </a:list>
 * ```
 * @allowchild  item
 */
apf.MultiselectBinding = function(){
    if (!this.setQueryValue)
        this.implement(apf.DataBinding);

    this.$regbase = this.$regbase|apf.__MULTISELECT__; //We're pretending to have multiselect even though we might not.

    this.$init(function(){
        this.$selectTimer = {};
    });
};

(function(){
    this.length = 0;

    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        caption: 2,
        icon: 2,
        eachvalue: 2,
        select: 2,
        css: 2,
        sort: 2,
        drag: 2,
        drop: 2,
        dragcopy: 2,
        selected: 3,
        //caret     : 2,
        each: 1,
        "selection"             : 3, //only databound when has an xpath
        "selection-unique"      : 3, //only databound when has an xpath
        "selection-constructor" : 3 //only databound when has an xpath
    }, this.$attrExcludePropBind);

     
    /**
     * Change the sorting order of this element.
     *
     * @param {Object}  options  The new sort options. These are applied incrementally.
     *                           Any property that is not set is maintained unless the clear
     *                           parameter is set to `true`. The following properties are available:
     *                  - order ([[String]])
     *                  - [xpath] ([[String]])
     *                  - [type] ([[String]])
     *                  - [method] ([[String]])
     *                  - [getNodes] ([[Function]]): A function that retrieves a list of nodes.
     *                  - [dateFormat] ([[String]])
     *                  - [getValue] ([[Function]]): A function that determines the string content based
     *                                            on an XML node as it's first argument.
     * @param {Boolean} clear    Removes the current sort options.
     * @param {Boolean} noReload Specifies whether to reload the data of this component.
     */
    this.resort = function(options, clear, noReload) {
        if (!this.$sort)
            this.$sort = new apf.Sort();

        this.$sort.set(options, clear);

        if (this.clearAllCache)
            this.clearAllCache();

        if (noReload)
            return;

        
        /*if(this.hasFeature(apf.__VIRTUALVIEWPORT__)){
            this.$clearVirtualDataset(this.xmlRoot);
            this.reload();

            return;
        }*/
        

        var _self = this;
        (function sortNodes(xmlNode, htmlParent) {
            if (!xmlNode)
                return;
            var sNodes = _self.$sort.apply(
                apf.getArrayFromNodelist(xmlNode.selectNodes(_self.each)));

            for (var i = 0; i < sNodes.length; i++) {
                if (_self.$isTreeArch || _self.$withContainer) {
                    var htmlNode = apf.xmldb.findHtmlNode(sNodes[i], _self);

                    

                    var container = _self.$findContainer(htmlNode);

                    htmlParent.appendChild(htmlNode);
                    if (!apf.isChildOf(htmlNode, container, true))
                        htmlParent.appendChild(container);

                    sortNodes(sNodes[i], container);
                }
                else
                    htmlParent.appendChild(apf.xmldb.findHtmlNode(sNodes[i], _self));
            }
        })(this.xmlRoot, this.$container);

        return options;
    };

    /**
     * Change sorting from ascending to descending, and vice versa!
     */
    this.toggleSortOrder = function(){
        return this.resort({"ascending" : !this.$sort.get().ascending}).ascending;
    };

    /**
     * Retrieves the current sort options.
     *
     * @returns {Object}  The current sort options. The following properties are available:
     *                     - order ([[String]])
     *                     - xpath ([[String]])
     *                     - type ([[String]])
     *                     - method ([[String]])
     *                     - getNodes ([[Function]]): A function that retrieves a list of nodes.
     *                     - dateFormat ([[String]])
     *                     - getValue ([[Function]]): A function that determines the string content based on
     *                                               an XML node as it's first argument.
     * 
     */
    this.getSortSettings = function(){
        return this.$sort.get();
    };
    

    /*
     * Optimizes load time when the xml format is very simple.
     */
    // @todo Doc
    this.$propHandlers["simpledata"] = function(value) {
        if (value) {
            this.getTraverseNodes = function(xmlNode) {
                
                if (this.$sort && !this.$isLoading) {
                    var nodes = apf.getArrayFromNodelist((xmlNode || this.xmlRoot).childNodes);
                    return this.$sort.apply(nodes);
                }
                

                return (xmlNode || this.xmlRoot).childNodes;
            };

            this.getFirstTraverseNode = function(xmlNode) {
                return this.getTraverseNodes(xmlNode)[0];//(xmlNode || this.xmlRoot).childNodes[0];
            };

            this.getLastTraverseNode = function(xmlNode) {
                var nodes = this.getTraverseNodes(xmlNode);//(xmlNode || this.xmlRoot).childNodes;
                return nodes[nodes.length - 1];
            };

            this.getTraverseParent = function(xmlNode) {
                if (!xmlNode.parentNode || xmlNode == this.xmlRoot)
                    return false;

                return xmlNode.parentNode;
            };
        }
        else {
            delete this.getTraverseNodes;
            delete this.getFirstTraverseNode;
            delete this.getLastTraverseNode;
            delete this.getTraverseParent;
        }
    };

    /**
     * Retrieves a node list containing the {@link term.datanode data nodes} which
     * are rendered by this element.
     *
     * @param {XMLElement} [xmlNode] The parent element on which each query is applied.
     * @return {NodeList} The node list containing the data nodes
     */
    this.getTraverseNodes = function(xmlNode) {
        

        
        if (this.$sort) {
            var nodes = apf.getArrayFromNodelist((xmlNode || this.xmlRoot).selectNodes(this.each));
            return this.$sort.apply(nodes);
        }
        

        return (xmlNode || this.xmlRoot).selectNodes(this.each);
    };

    /**
     * Retrieves the first {@link term.datanode data node} which gets representation
     * in this element. 
     *
     * @param {XMLElement} [xmlNode] The parent element on which the each query is executed.
     * @return {apf.AmlNode} The first represented {@link term.datanode data node}
     */
    this.getFirstTraverseNode = function(xmlNode) {
        
        if (this.$sort) {
            var nodes = (xmlNode || this.xmlRoot).selectNodes(this.each);
            return this.$sort.apply(nodes)[0];
        }
        

        return (xmlNode || this.xmlRoot).selectSingleNode(this.each);
    };

    /**
     * Retrieves the last {@link term.datanode data node} which gets representation
     * in this element. 
     *
     * @param {XMLElement} [xmlNode] the parent element on which the each query is executed.
     * @return {XMLElement} The last represented {@link term.datanode data node}
     * 
     */
    this.getLastTraverseNode = function(xmlNode) {
        var nodes = this.getTraverseNodes(xmlNode || this.xmlRoot);
        return nodes[nodes.length-1];
    };

    /**
     * Determines whether a {@link term.datanode data node} is an each node. 
     *
     * @param {XMLElement} [xmlNode] The parent element on which the each query is executed.
     * @return  {Boolean}  Identifies whether the XML element is a each node.
     * 
     */
    this.isTraverseNode = function(xmlNode) {
        /*
            Added optimization, only when an object has a tree architecture is it
            important to go up to the each parent of the xmlNode, else the node
            should always be based on the xmlroot of this component
        */
        //this.$isTreeArch
        var nodes = this.getTraverseNodes(
          this.getTraverseParent(xmlNode) || this.xmlRoot);
        for (var i = 0; i < nodes.length; i++)
            if (nodes[i] == xmlNode)
                return true;
        return false;
    };

    /**
     * Retrieves the next `each` node to be selected from a given `each` node. 
     *
     * The method can do this in either direction and also return the Nth node for this algorithm.
     *
     * @param {XMLElement}  xmlNode  The starting point for determining the next selection.
     * @param {Boolean}     [up=false]     The direction of the selection.
     * @param {Number}     [count=1]  The distance in number of nodes.
     * @return  {XMLElement} The {@link term.datanode data node} to be selected next.
     */
    this.getNextTraverseSelected = function(xmlNode, up, count) {
        if (!xmlNode)
            xmlNode = this.selected;
        if (!count)
            count = 1;

        var i = 0;
        var nodes = this.getTraverseNodes(this.getTraverseParent(xmlNode) || this.xmlRoot);
        while (nodes[i] && nodes[i] != xmlNode)
            i++;

        var node = (up == null)
            ? nodes[i + count] || nodes[i - count]
            : (up ? nodes[i + count] : nodes[i - count]);

        //arguments[2]
        return node || count && (i < count || (i + 1) > Math.floor(nodes.length / count) * count)
            ? node
            : (up ? nodes[nodes.length-1] : nodes[0]);
    };

    /**
     * Retrieves the next `each` node. 
     * 
     * The method can do this in either direction and also return the Nth next node.
     *
     * @param {XMLElement}  xmlNode  The starting point for determining the next selection.
     * @param {Boolean}     [up=false]     The direction of the selection.
     * @param {Number}     [count=1]  The distance in number of nodes.
     * @return  {XMLElement} The {@link term.datanode data node} to be selected next.
     */
    this.getNextTraverse = function(xmlNode, up, count) {
        if (!count)
            count = 1;
        if (!xmlNode)
            xmlNode = this.selected;

        var i = 0;
        var nodes = this.getTraverseNodes(this.getTraverseParent(xmlNode) || this.xmlRoot);
        while (nodes[i] && nodes[i] != xmlNode)
            i++;

        var ind = i + (up ? -1 * count : count);
        return nodes[ind < 0 ? 0 : ind];
    };

    /**
     * Retrieves the parent each node. 
     *
     * In some cases the each rules has a complex form like 'children/item'. In
     * those cases, the generated tree has a different structure from that of the XML
     * data. For these situations, the `xmlNode.parentNode` property won't return
     * the each parent; instead, this method will give you the right parent.
     *
     * @param {XMLElement} xmlNode The node for which the parent element will be determined.
     * @return  {XMLElement} The parent node or `null` if none was found.
     */
    this.getTraverseParent = function(xmlNode) {
        if (!xmlNode.parentNode || xmlNode == this.xmlRoot)
            return false;

        //@todo this can be removed when we have a new xpath implementation
        if (xmlNode.$regbase)
            return xmlNode.parentNode;

        var x, id = xmlNode.getAttribute(apf.xmldb.xmlIdTag);
        if (!id) {
            //return false;
            xmlNode.setAttribute(apf.xmldb.xmlIdTag, "temp");
            id = "temp";
        }

        /*
        do {
            xmlNode = xmlNode.parentNode;
            if (xmlNode == this.xmlRoot)
                return false;
            if (this.isTraverseNode(xmlNode))
                return xmlNode;
        } while (xmlNode.parentNode);
        */

        //This is not 100% correct, but good enough for now

        x = xmlNode.selectSingleNode("ancestor::node()[(("
            + this.each + ")/@" + apf.xmldb.xmlIdTag + ")='"
            + id + "']");

        if (id == "temp")
            xmlNode.removeAttribute(apf.xmldb.xmlIdTag);
        return x;
    };

    if (!this.$findHtmlNode) { //overwritten by apf.Cache
        /**
         * Finds HTML presentation node in cache by ID.
         *
         * @param {String}  id  The id of the HTMLElement which is looked up.
         * @return {HTMLElement} The HTMLElement found. When no element is found, `null` is returned.
         * @private
         */
        this.$findHtmlNode = function(id) {
            return this.$pHtmlDoc.getElementById(id);
        };
    }

    this.$setClearMessage = function(msg, className, lastHeight) {
        if (this.more && this.$addMoreItem) this.$addMoreItem();
        if (!this.$empty) {
            if (!this.$hasLayoutNode("empty"))
                return;

            this.$getNewContext("empty");

            var xmlEmpty = this.$getLayoutNode("empty");
            if (!xmlEmpty) return;

            this.$empty = apf.insertHtmlNode(xmlEmpty, this.$container);
        }
        else {
            this.$container.appendChild(this.$empty);
        }

        var empty = this.$getLayoutNode("empty", "caption", this.$empty);

        if (empty)
            apf.setNodeValue(empty, msg || "");

        this.$empty.setAttribute("id", "empty" + this.$uniqueId);
        apf.setStyleClass(this.$empty, className, ["loading", "empty", "offline"]);

        //@todo apf3.0 cleanup?
        var extH = apf.getStyle(this.$ext, "height");
        this.$empty.style.height = (lastHeight && (!extH || extH == "auto") && className != "empty")
            ? (Math.max(10, (lastHeight
               - apf.getHeightDiff(this.$empty)
               - apf.getHeightDiff(this.$ext))) + "px")
            : "";
    };

    this.$updateClearMessage = function(msg, className) {
        if (!this.$empty || this.$empty.parentNode != this.$container
          || this.$empty.className.indexOf(className) == -1)
            return;

        var empty = this.$getLayoutNode("empty", "caption", this.$empty);
        if (empty)
            apf.setNodeValue(empty, msg || "");
    }

    this.$removeClearMessage = function(){
        if (!this.$empty)
            this.$empty = document.getElementById("empty" + this.$uniqueId);
        if (this.$empty && this.$empty.parentNode)
            this.$empty.parentNode.removeChild(this.$empty);
    };

    /*
     * Set listeners, calls HTML creation methods and
     * initializes select and focus states of object.
     */
    this.$load = function(XMLRoot) {
        //Add listener to XMLRoot Node
        apf.xmldb.addNodeListener(XMLRoot, this);

        this.$isLoading = true;

        var length = this.getTraverseNodes(XMLRoot).length;
        if (!this.renderRoot && !length)
            return this.clear(null, null, true); //@todo apf3.0 this should clear and set a listener


        //Traverse through XMLTree
        var nodes = this.$addNodes(XMLRoot, null, null, this.renderRoot, null, 0, "load");

        //Build HTML
        this.$fill(nodes);

        this.$isLoading = false;

        //Select First Child
        if (this.selectable) {
            
            //@todo apf3.0 optimize to not set selection when .selection or .selected is set on initial load
            if (this["default"])
                this.select(this["default"]);
            else if (this.autoselect) {
                if (!this.selected) {
                    if (this.renderRoot)
                        this.select(XMLRoot, null, null, null, true);
                    else if (nodes.length)
                        this.$selectDefault(XMLRoot);
                    //else @todo apf3.0 this one doesnt seem needed
                        //this.clearSelection();
                }
            }
            else {
                this.clearSelection(true);
                var xmlNode = this.renderRoot
                    ? this.xmlRoot
                    : this.getFirstTraverseNode(); //should this be moved to the clearSelection function?
                if (xmlNode)
                    this.setCaret(xmlNode);
                
                if (this.selected)
                    this.setProperty("selected", null);
                if (this.choosen)
                    this.setProperty("choosen", null);
                
            }
        }

        if (this.focussable)
            apf.document.activeElement == this ? this.$focus() : this.$blur();

        
        if (length != this.length)
            this.setProperty("length", length);
        
    };

    var actionFeature = {
        "insert"      : 127,//11111110
        "replacenode" : 127,//11111110
        "attribute"   : 255,//11111111
        "add"         : 251,//11110111
        "remove"      : 110, //01011110
        "redo-remove" : 79, //10011110
        "synchronize" : 127,//11111110
        "move-away"   : 297,//11010111
        "move"        : 141  //10011111
    };

    /**
     * @event xmlupdate Fires when XML of this element is updated.
     * @param {Object} e The standard event object. The following properties are available:
     *                      - action ([[String]]): The action that was executed on the XML. The following values are possible:
     *                            - `text`   :     A text node is set
     *                            - `attribute` :  An attribute is set
     *                            - `update`:      An XML node is updated
     *                            - `insert`  :    xml nodes are inserted
     *                            - `add`   :      An XML node is added
     *                            - `remove` :     An XML node is removed (parent still set)
     *                            - `redo`-remove`: An XML node is removed (parent not set)
     *                            - `synchronize`:  An unknown update
     *                            - `move-away` :  An XML node is moved (parent not set)
     *                            - `move`        An XML node is moved (parent still set)
     *                      - xmlNode ([[XMLElement]]): The node that is subject to the update
     *                      - result (`Mixed`): The result
     *                      - UndoObj ([[apf.UndoData]]): The undo information
     */
    /*
     * Loops through parents of a changed node to find the first
     * connected node. Based on the action, it will change, remove,
     * or update the representation of the data.
     */
    this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj, lastParent) {
        if (!this.xmlRoot)
            return; //@todo think about purging cache when xmlroot is removed

        var result, length, pNode, htmlNode,
            startNode = xmlNode;
        if (!listenNode)
            listenNode = this.xmlRoot;

        if (action == "redo-remove") {
            var loc = [xmlNode.parentNode, xmlNode.nextSibling];
            lastParent.appendChild(xmlNode); //ahum, i'm not proud of this one
            var eachNode = this.isTraverseNode(xmlNode);
            if (loc[0])
                loc[0].insertBefore(xmlNode, loc[1]);
            else
                lastParent.removeChild(xmlNode);

            if (!eachNode)
                xmlNode = lastParent;
        }

        //Get First ParentNode connected
        do {
            if (action == "add" && this.isTraverseNode(xmlNode)
              && startNode == xmlNode)
                break; //@todo Might want to comment this out for adding nodes under a eachd node

            if (xmlNode.getAttribute(apf.xmldb.xmlIdTag)) {
                htmlNode = this.$findHtmlNode(
                    xmlNode.getAttribute(apf.xmldb.xmlIdTag)
                    + "|" + this.$uniqueId);

                if (xmlNode == listenNode && !this.renderRoot) {
                    if (xmlNode == this.xmlRoot && action != "insert" && action != "replacenode") {
                        //@todo apf3.0 - fix this for binding on properties
                        this.dispatchEvent("xmlupdate", {
                            action: action,
                            xmlNode: xmlNode,
                            UndoObj: UndoObj
                        });
                        return;
                    }
                    break;
                }

                if (htmlNode && actionFeature[action] & 2
                  && !this.isTraverseNode(xmlNode))
                    action = "remove"; //@todo why not break here?

                else if (!htmlNode && actionFeature[action] & 4
                  && this.isTraverseNode(xmlNode)){
                    action = "add";
                    break;
                }

                else if (htmlNode
                  && (startNode != xmlNode || xmlNode == this.xmlRoot)) {
                    if (actionFeature[action] & 1)
                        action = "update";
                    else if (action == "remove")
                        return;
                }

                if (htmlNode  || action == "move")
                    break;
            }
            else if (actionFeature[action] & 8 && this.isTraverseNode(xmlNode)){
                action = "add";
                break;
            }

            if (xmlNode == listenNode) {
                if (actionFeature[action] & 128) //The change is not for us.
                    return;

                break;
            }
            xmlNode = xmlNode.parentNode;
        }
        while (xmlNode && xmlNode.nodeType != 9);

        

        
        
        // @todo Think about not having this code here
        if (this.hasFeature(apf.__VIRTUALVIEWPORT__)) {
            if (!this.$isInViewport(xmlNode)) //xmlNode is a eachd node
                return;
        }
        

        //if(xmlNode == listenNode && !action.match(/add|synchronize|insert/))
        //    return; //deleting nodes in parentData of object

        var foundNode = xmlNode;
        if (xmlNode && xmlNode.nodeType == 9)
            xmlNode = startNode;

        if (action == "replacenode") {
            //var tmpNode;
            //Case for replacing the xmlroot or its direct parent
            if (UndoObj ? UndoObj.args[1] == this.xmlRoot : !this.xmlRoot.parentNode)
                return this.load(UndoObj ? UndoObj.xmlNode : listenNode, {force: true});

            //Case for replacing a node between the xmlroot and the traverse nodes
            var nodes = this.getTraverseNodes();
            for (var i = 0, l = nodes.length; i < l; i++) {
                if (apf.isChildOf(startNode, nodes[i]))
                    return this.load(this.xmlRoot, {force: true}); //This can be more optimized by using addNodes
            }
            //if ((tmpNode = this.getFirstTraverseNode()) && apf.isChildOf(startNode, tmpNode))
        }

        //Action Tracker Support - && xmlNode correct here??? - UndoObj.xmlNode works but fishy....
        if (UndoObj && xmlNode && !UndoObj.xmlNode)
            UndoObj.xmlNode = xmlNode;

        //Check Move -- if value node isn't the node that was moved then only perform a normal update
        if (action == "move" && foundNode == startNode) {
            //if(!htmlNode) alert(xmlNode.getAttribute("id")+"|"+this.$uniqueId);
            var isInThis = apf.isChildOf(this.xmlRoot, xmlNode.parentNode, true); //@todo this.getTraverseParent(xmlNode)
            var wasInThis = apf.isChildOf(this.xmlRoot, UndoObj.extra.parent, true);

            //Move if both previous and current position is within this object
            if (isInThis && wasInThis)
                this.$moveNode(xmlNode, htmlNode, UndoObj.extra.oldParent);
            else if (isInThis) //Add if only current position is within this object
                action = "add";
            else if (wasInThis) //Remove if only previous position is within this object
                action = "remove";
        }
        else if (action == "move-away") {
            var goesToThis = apf.isChildOf(this.xmlRoot, UndoObj.extra.parent, true);
            if (!goesToThis)
                action = "remove";
        }

        //Remove loading message
        if (this.$removeClearMessage && this.$setClearMessage) {
            if (this.getFirstTraverseNode())
                this.$removeClearMessage();
            else
                this.$setClearMessage(this["empty-message"], "empty")
        }

        //Check Insert
        if (action == "insert" && (this.$isTreeArch || xmlNode == this.xmlRoot)) {
            if (!xmlNode)
                return;

            if (this.$hasLoadStatus(xmlNode) && this.$removeLoading)
                this.$removeLoading(xmlNode);

            if (this.$container.firstChild && !apf.xmldb.getNode(this.$container.firstChild)) {
                //Appearantly the content was cleared
                this.$container.innerHTML = "";

                if (!this.renderRoot) {
                    length = this.getTraverseNodes().length;
                    if (!length)
                        this.clear();
                }
            }

            result = this.$addNodes(xmlNode, null, true, false, null, null, "insert");//this.$isTreeArch??

            this.$fillParentHtml = (this.$getParentNode
                ? this.$getParentNode(htmlNode)
                : htmlNode);
            this.$fillParent = xmlNode;
            this.$fill(result);

            

            if (this.selectable && (length === 0 || !this.xmlRoot.selectSingleNode(this.each)))
                return;
        }
        else if (action == "add") {// || !htmlNode (Check Add)
            var parentHTMLNode;
            pNode = this.getTraverseParent(xmlNode) || this.xmlRoot;

            if (pNode == this.xmlRoot)
                parentHTMLNode = this.$container;

            if (!parentHTMLNode && this.$isTreeArch) {
                parentHTMLNode = this.$findHtmlNode(
                    pNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
            }

            //This should be moved into a function (used in setCache as well)
            
            if (!parentHTMLNode && this.getCacheItem)
                parentHTMLNode = this.getCacheItem(pNode.getAttribute(apf.xmldb.xmlIdTag)
                    || (pNode.getAttribute(apf.xmldb.xmlDocTag)
                         ? "doc" + pNode.getAttribute(apf.xmldb.xmlDocTag)
                         : false));
            

            //Only update if node is in current representation or in cache
            if (parentHTMLNode || this.$isTreeArch
              && pNode == this.xmlRoot) { //apf.isChildOf(this.xmlRoot, xmlNode)
                parentHTMLNode = (this.$findContainer && parentHTMLNode && parentHTMLNode.nodeType == 1
                    ? this.$findContainer(parentHTMLNode)
                    : parentHTMLNode) || this.$container;

                result = this.$addNodes(xmlNode, parentHTMLNode, true, true,
                    apf.xmldb.getHtmlNode(this.getNextTraverse(xmlNode), this));

                if (parentHTMLNode)
                    this.$fill(result);
            }
        }
        else if (action == "remove") { //Check Remove
            //&& (!xmlNode || foundNode == xmlNode && xmlNode.parentNode
            //if (!xmlNode || startNode != xmlNode) //@todo unsure if I can remove above commented out statement
                //return;
            //I've commented above code out, because it disabled removing a
            //subnode of a node that through an each rule makes the traverse
            //node no longer a traverse node.

            //Remove HTML Node
            if (htmlNode)
                this.$deInitNode(xmlNode, htmlNode);
            else if (startNode == this.xmlRoot) {
                return this.load(null, {
                    noClearMsg: !this.dataParent || !this.dataParent.autoselect
                });
            }
        }
        else if (htmlNode) {
            
            if (this.$sort)
                this.$moveNode(xmlNode, htmlNode);
            

            this.$updateNode(xmlNode, htmlNode);

            //Transaction 'niceties'
            if (action == "replacenode" && this.hasFeature(apf.__MULTISELECT__)
              && this.selected && xmlNode.getAttribute(apf.xmldb.xmlIdTag)
              == this.selected.getAttribute(apf.xmldb.xmlIdTag)) {
                this.selected = xmlNode;
            }

            //if(action == "synchronize" && this.autoselect) this.reselect();
        }
        else if (action == "redo-remove") { //Check Remove of the data (some ancestor) that this component is bound on
            var testNode = this.xmlRoot;
            while (testNode && testNode.nodeType != 9)
                testNode = testNode.parentNode;

            if (!testNode) {
                //Set Component in listening state until data becomes available again.
                var model = this.getModel(true);

                

                return model.$waitForXml(this);
            }
        }

        

        //For tree based nodes, update all the nodes up
        pNode = xmlNode ? xmlNode.parentNode : lastParent;
        if (this.$isTreeArch && !this.$preventRecursiveUpdate
          && pNode && pNode.nodeType == 1) {
            do {
                htmlNode = this.$findHtmlNode(pNode.getAttribute(
                    apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);

                if (htmlNode)
                    this.$updateNode(pNode, htmlNode);
            }
            while ((pNode = this.getTraverseParent(pNode)) && pNode.nodeType == 1);
        }

        //Make sure the selection doesn't become corrupted
        if (actionFeature[action] & 32 && this.selectable
          && startNode == xmlNode
          && (action != "insert" || xmlNode == this.xmlRoot)) {

            clearTimeout(this.$selectTimer.timer);
            // Determine next selection
            if (action == "remove" && apf.isChildOf(xmlNode, this.selected, true)
              || xmlNode == this.$selectTimer.nextNode) {
                this.$selectTimer.nextNode = this.getDefaultNext(xmlNode, this.$isTreeArch);
                if (this.$selectTimer.nextNode == this.xmlRoot && !this.renderRoot)
                    this.$selectTimer.nextNode = null;
            }

            //@todo Fix this by putting it after xmlUpdate when its using a timer
            var _self = this;
            this.$selectTimer.timer = $setTimeout(function(){
                _self.$checkSelection(_self.$selectTimer.nextNode);
                _self.$selectTimer.nextNode = null;
            });
        }

        
        //Set dynamic properties that relate to the changed content
        if (actionFeature[action] & 64) {
            if (!length)
                length = this.xmlRoot.selectNodes(this.each).length;
            if (action == "remove")
                length--;
            if (length != this.length)
                this.setProperty("length", length);
        }
        

        //Let's signal components that are waiting for xml to appear (@todo what about clearing the signalXmlUpdate)
        if (this.signalXmlUpdate && actionFeature[action] & 16) {
            var uniqueId;
            for (uniqueId in this.signalXmlUpdate) {
                if (parseInt(uniqueId, 10) != uniqueId) continue; //safari_old stuff

                var o = apf.lookup(uniqueId);
                if (!this.selected) continue;

                xmlNode = this.selected.selectSingleNode(o.dataParent.xpath);
                if (!xmlNode) continue;

                o.load(xmlNode);
            }
        }

        this.dispatchEvent("xmlupdate", {
            action: action,
            xmlNode: startNode,
            traverseNode: xmlNode,
            result: result,
            UndoObj: UndoObj
        });
    };

    /*
     * Loop through NodeList of selected Traverse Nodes
     * and check if it has representation. If it doesn't
     * representation is created via $add().
     */
    this.$addNodes = function(xmlNode, parent, checkChildren, isChild, insertBefore, depth, action) {
        

        var htmlNode, lastNode, loopNode;
        isChild = (isChild && (this.renderRoot && xmlNode == this.xmlRoot
            || this.isTraverseNode(xmlNode)));
        var nodes = isChild ? [xmlNode] : this.getTraverseNodes(xmlNode);
        /*var loadChildren = nodes.length && this.$bindings["insert"]
            ? this.$applyBindRule("insert", xmlNode)
            : false; << UNUSED */

        
        var cId, cItem;
        if (this.$isTreeArch && this.caching
          && (!this.$bindings || !this.$bindings.each || !this.$bindings.each.filter)
          && (cItem = this.cache[(cId = xmlNode.getAttribute(apf.xmldb.xmlIdTag))])) {
            if (this.$subTreeCacheContext || this.$needsDepth) {
                //@todo
                //We destroy the current items, because currently we
                //don't support multiple treecachecontexts
                //and because datagrid needs to redraw depth
                this.clearCacheItem(cId);
            }
            else {
                this.$subTreeCacheContext = {
                    oHtml: cItem,
                    container: parent,
                    parentNode: null,
                    beforeNode: null
                };

                var htmlNode;
                while (cItem.childNodes.length)
                    (parent || this.$container).appendChild(htmlNode = cItem.childNodes[0]);

                return nodes;
            }
        }
        

        if (this.$isTreeArch && depth === null && action == "insert") {
            depth = 0, loopNode = xmlNode;
            while (loopNode && loopNode != this.xmlRoot) {
                depth++;
                loopNode = this.getTraverseParent(loopNode);
            }
        }

        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 1) {
                
                continue;
            }

            if (checkChildren) {
                htmlNode = this.$findHtmlNode(nodes[i]
                    .getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
            }

            if (!htmlNode) {
                //Retrieve DataBind ID
                var Lid = apf.xmldb.nodeConnect(this.documentId, nodes[i], null, this);

                //Add Children
                var beforeNode = isChild
                        ? insertBefore
                        : (lastNode ? lastNode.nextSibling : null),//(parent || this.$container).firstChild);
                    parentNode = this.$add(nodes[i], Lid, isChild ? xmlNode.parentNode : xmlNode,
                        beforeNode ? parent || this.$container : parent, beforeNode,
                        (!beforeNode && i == nodes.length - 1), depth, nodes[i + 1], action);//Should use getTraverParent

                //Exit if component tells us its done with rendering
                if (parentNode === false) {
                    //Tag all needed xmlNodes for future reference
                    // @todo apf3.0 code below looks harmful... hence commented out (Mike)
                    /*for (var j = i; j < nodes.length; j++)
                        apf.xmldb.nodeConnect(this.documentId, nodes[j],
                            null, this);*/
                    break;
                }

                //Parse Children Recursively -> optimize: don't check children that can't exist
                //if(this.$isTreeArch) this.$addNodes(nodes[i], parentNode, checkChildren);
            }

            if (checkChildren)
                lastNode = htmlNode;// ? htmlNode.parentNode.parentNode : null;
        }

        return nodes;
    };

    this.$handleBindingRule = function(value, prop) {
        if (!value)
            this[prop] = null;

        //@todo apf3.0 fix parsing
        if (prop == "each") {
            value = value.charAt(0) == "[" && value.charAt(value.length - 1) == "]"
                ? value.replace(/^\[|\]$/g, "")
                : value;

            if (value.match(/^\w+::/)) {
                var model = value.split("::"); //@todo this is all very bad
                if (!apf.xPathAxis[model[0]]) {
                    this.setProperty("model", model[0]);
                    this.each = model[1];
                }
                else
                    this.each = value;
            }
            else
                this.each = value;

            if (this.each == this.$lastEach)
                return;

            this.$lastEach = value;

            if (!this.$model && !this.$initingModel) {
                this.$initingModel = true;
                this.$setInheritedAttribute("model");

                return; //@experimental
            }

            if (this.$checkLoadQueue() !== false) //@experimental
                return;
        }

        //@todo apf3.0 find a better heuristic (portal demo)
        if (this.xmlRoot && !this.$bindRuleTimer && this.$amlLoaded) {
            var _self = this;
            apf.queue.add("reload" + this.$uniqueId, function(){
                
                _self.reload();
            });
        }
    };

    this.$select = function(o) {
        
        if (this.renaming)
            this.stopRename(null, true);
        

        if (!o || !o.style)
            return;
        return this.$setStyleClass(o, "selected");
    };

    this.$deselect = function(o) {
        
        if (this.renaming) {
            this.stopRename(null, true);

            if (this.ctrlselect)
                return false;
        }
        

        if (!o)
            return;
        return this.$setStyleClass(o, "", ["selected", "indicate"]);
    };

    this.$indicate = function(o) {
        
        if (this.renaming)
            this.stopRename(null, true);
        

        if (!o)
            return;
        return this.$setStyleClass(o, "indicate");
    };

    this.$deindicate = function(o) {
        
        if (this.renaming)
            this.stopRename(null, true);
        

        if (!o)
            return;
        return this.$setStyleClass(o, "", ["indicate"]);
    };

    
    /**
     * @attribute {String} each Sets or gets the XPath statement that determines which
     * {@link term.datanode data nodes} are rendered by this element (also known
     * as {@link term.eachnode each nodes}. 
     * 
     *
     * #### Example
     *
     * ```xml
     *  <a:label>Country</a:label>
     *  <a:dropdown
     *      model = "mdlCountries"
     *      each = "[country]"
     *      eachvalue = "[@value]"
     *      caption = "[text()]">
     *  </a:dropdown>
     *
     *  <a:model id="mdlCountries">
     *      <countries>
     *          <country value="USA">USA</country>
     *          <country value="GB">Great Brittain</country>
     *          <country value="NL">The Netherlands</country>
     *          ...
     *      </countries>
     *  </a:model>
     * ```
     *
     * 
     */
    this.$propHandlers["each"] =

    /**
     * @attribute {String} caption Sets or gets the text displayed on the item.
     *
     * #### Example
     *
     * ```xml
     *  <a:list caption="[text()]" each="[item]" />
     * ```
     */
    this.$propHandlers["caption"] = 

    /**
     * @attribute {String} eachvalue Sets or gets the {@link term.expression}
     * that determines the value for each data nodes in the dataset of the element.
     *
     * #### Example
     *
     * ```xml
     *  <a:list value="[@value]" each="[item]" />
     * ```
     * 
     */
    this.$propHandlers["eachvalue"] = 

    /**
     * @attribute {String} icon Sets or gets the XPath statement that determines from
     * which XML node the icon URL is retrieved.
     *
     * #### Example
     *
     * ```xml
     *  <a:list icon="[@icon]" each="[item]" />
     * ```
     */
    this.$propHandlers["icon"] = 

    /**
     * @attribute {String} tooltip Sets or gets the XPath statement that determines from
     * which XML node the tooltip text is retrieved.
     *
     * #### Example
     *
     * ```xml
     *  <a:list tooltip="[text()]" each="[item]" />
     * ```
     */
    this.$propHandlers["tooltip"] = this.$handleBindingRule;

    
    /**
     * @attribute {String} sort Sets or gets the XPath statement that selects the sortable value.
     *
     * #### Example
     *
     * ```xml
     *  <a:list sort="[@name]" each="[person]" />
     * ```
     * 
     */
    this.$propHandlers["sort"] = function(value) {
        if (value) {
            this.$sort = new apf.Sort()
            this.$sort.set({
                getValue: apf.lm.compile(value)
            });
        }
        else {
            this.$sort = null;
        }
    }
    

    /**
     * @attribute {String} match Sets or gets the XPath statement that determines whether
     * this node is selectable.
     *
     * #### Example
     *
     * ```xml
     *  <a:list match="{[@disabled] != 1}" each="[item]" />
     * ```
     * 
     */
    //this.$propHandlers["select"] = 
    
}).call(apf.MultiselectBinding.prototype = new apf.DataBinding());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * The baseclass for all standard data binding rules.
 *
 * @class apf.StandardBinding
 * @private
 * @baseclass
 * @inherits apf.DataBinding
 */
apf.StandardBinding = function(){
    this.$init(true);
    
    
    if (apf.Validation)
        this.implement(apf.Validation);
    
    
    if (!this.setQueryValue)
        this.implement(apf.DataBinding);

    if (!this.defaultValue) //@todo please use this in a sentence
        this.defaultValue = "";

    /**
     * Load XML into this element
     * @private
     */
    this.$load = function(xmlNode) {
        //Add listener to XMLRoot Node
        apf.xmldb.addNodeListener(xmlNode, this);
        //Set Properties

        
        var b, lrule, rule, bRules, bRule, value;
        if (b = this.$bindings) {
            for (rule in b) {
                lrule = rule.toLowerCase();
                if (this.$supportedProperties.indexOf(lrule) > -1) {
                    bRule = (bRules = b[lrule]).length == 1 
                      ? bRules[0] 
                      : this.$getBindRule(lrule, xmlNode);

                    value = bRule.value || bRule.match;

                    
                    //Remove any bounds if relevant
                    this.$clearDynamicProperty(lrule);
            
                    if (value.indexOf("{") > -1 || value.indexOf("[") > -1)
                        this.$setDynamicProperty(lrule, value);
                    else 
                    
                    if (this.setProperty)
                        this.setProperty(lrule, value, true);
                }
            }
        }
        

        //Think should be set in the event by the Validation Class
        if (this.errBox && this.isValid && this.isValid())
            this.clearError();
    };

    /**
     * Set xml based properties of this element
     * @private
     */
    this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj) {
        //Clear this component if some ancestor has been detached
        if (action == "redo-remove") {
            var retreatToListenMode = false, model = this.getModel(true);
            if (model) {
                var xpath = model.getXpathByAmlNode(this);
                if (xpath) {
                    xmlNode = model.data.selectSingleNode(xpath);
                    if (xmlNode != this.xmlRoot)
                        retreatToListenMode = true;
                }
            }
            
            if (retreatToListenMode || this.xmlRoot == xmlNode) {
                

                //Set Component in listening state untill data becomes available again.
                return model.$waitForXml(this);
            }
        }

        //Action Tracker Support
        if (UndoObj && !UndoObj.xmlNode)
            UndoObj.xmlNode = this.xmlRoot;

        //Set Properties

        
        var b, lrule, rule, bRules, bRule, value;
        if (b = this.$bindings) {
            for (rule in b) {
                lrule = rule.toLowerCase();
                if (this.$supportedProperties.indexOf(lrule) > -1) {
                    bRule = (bRules = b[lrule]).length == 1 
                      ? bRules[0] 
                      : this.$getBindRule(lrule, xmlNode);

                    value = bRule.value || bRule.match;

                    
                    //Remove any bounds if relevant
                    this.$clearDynamicProperty(lrule);
            
                    if (value.indexOf("{") > -1 || value.indexOf("[") > -1)
                        this.$setDynamicProperty(lrule, value);
                    else 
                    
                    if (this.setProperty)
                        this.setProperty(lrule, value);
                }
            }
        }
        

        //@todo Think should be set in the event by the Validation Class
        if (this.errBox && this.isValid && this.isValid())
            this.clearError();
        
        this.dispatchEvent("xmlupdate", {
            action: action,
            xmlNode: xmlNode,
            UndoObj: UndoObj
        });
    };

    //@todo apf3.0 this is wrong
    /**
     * @event $clear Clears the data loaded into this element resetting it's value.
     */
    this.addEventListener("$clear", function(nomsg, do_event) {
        if (this.$propHandlers && this.$propHandlers["value"]) {
            this.value = -99999; //force resetting
            this.$propHandlers["value"].call(this, "");
        }
    });
};
apf.StandardBinding.prototype = new apf.DataBinding();

apf.Init.run("standardbinding");




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__MULTISELECT__ = 1 << 8;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have selection features. This includes handling
 * for multiselect and several keyboard based selection interaction. It also
 * takes care of {@link term.caret caret} handling when multiselect is enabled. Furthermore features 
 * for dealing with multinode component are included like adding and removing 
 * {@link term.datanode data nodes}.
 *
 * #### Example
 *
 * In this example the tree contains nodes that have a disabled flag set. These nodes cannot be selected.
 *
 * ```xml
 *  <a:list width="200">
 *      <a:bindings>
 *          <a:selectable match="[self::node()[not(@disabled) or @disabled != 'true']]" />
 *          <a:each match="[person]"></a:each>
 *          <a:caption match="[@name]"></a:caption>
 *      </a:bindings>
 *      <a:model>
 *          <data>
 *              <person disabled="false" name="test 5"/>
 *              <person disabled="true" name="test 3"/>
 *              <person name="test 4"/>
 *              <person disabled="true" name="test 2"/>
 *              <person disabled="true" name="test 1"/>
 *          </data>
 *      </a:model>
 *  </a:list>
 * ```
 *
 * #### Example
 *
 * ```xml
 *  <a:dropdown onafterchange="alert(this.value)">
 *      <a:bindings>
 *          <a:caption match="[text()]" />
 *          <a:value match="[@value]" />
 *          <a:each match="[item]" />
 *      </a:bindings>
 *      <a:model>
 *          <items>
 *              <item value="#FF0000">red</item>
 *              <item value="#00FF00">green</item>
 *              <item value="#0000FF">blue</item>
 *          </items>
 *      </a:model>
 *  </a:dropdown>
 * ```
 *
 * @class apf.MultiSelect
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.5
 *
 * @inherits apf.MultiselectBinding
 *
 */
/**
 *
 * @binding select Determines whether the {@link term.eachnode each node} can be selected.
 *
 */
 /**
 *
 * * @binding value  Determines the way the value for the element is retrieved
 * from the selected node. The `apf.MultiSelect.value` property contains this value.
 *
 */
apf.MultiSelect = function(){
    this.$init(function(){
        this.$valueList = [];
        this.$selectedList = [];
    });
};

//@todo investigate if selectedList can be deprecated
(function() {
    this.$regbase = this.$regbase|apf.__MULTISELECT__;

    // *** Properties *** //

    // @todo Doc is that right? 
    /**
     * The last selected item of this element.
     * @type {XMLElement} 
     */
    this.sellength = 0;
    this.selected = null;
    this.$selected = null;
    
    /**
     * The XML element that has the {@link term.caret caret}.
     * @type {XMLElement} 
     */
    this.caret = null;
    this.$caret = null;
    
    /**
     * Specifies whether to use a {@link term.caret caret} in the interaction of this element.
     * @type {Boolean} 
     */
    this.useindicator = true;

    

    /**
     * Removes a {@link term.datanode data node} from the data of this element.
     *
     * #### Example
     *
     * A simple list showing products. This list is used in all the following examples.
     * 
     * ```xml
     *  <a:list id="myList">
     *      <a:bindings>
     *          <a:caption match="[@name]" />
     *          <a:value match="[@id]" />
     *          <a:icon>[@type].png</a:icon>
     *          <a:each match="[product]" />
     *      </a:bindings>
     *      <a:model>
     *          <products>
     *              <product name="Soundblaster" type="audio"    id="product10" length="12" />
     *              <product name="Teapot"       type="3d"       id="product13" />
     *              <product name="Coprocessor"  type="chips"    id="product15" />
     *              <product name="Keyboard"     type="input"    id="product17" />
     *              <product name="Diskdrive"    type="storage"  id="product20" />
     *          </products>
     *      </a:model>
     *  </a:list>
     * ```
     *
     * #### Example
     *
     * This example selects a product by its value and then removes the selection.
     * 
     * ```xml
     *  <a:script><!--
     *      apf.onload = function() {
     *          myList.setValue("product20");
     *          myList.remove();
     *      }
     *  --></a:script>
     * ```
     *
     * #### Example
     *
     * This example gets a product by its value and then removes it.
     * 
     * ```xml
     *  <a:script>
     *      var xmlNode = myList.findXmlNodeByValue("product20");
     *      myList.remove(xmlNode);
     *  </a:script>
     * ```
     *
     * #### Example
     *
     * This example retrieves all nodes from the list. All items with a length
     * greater than 10 are singled out and removed.
     * 
     * ```xml
     *  <a:script><![CDATA[
     *      apf.onload = function() {
     *          var list = myList.getTraverseNodes();
     * 
     *          var removeList = [];
     *          for (var i = 0; i < list.length; i++) {
     *              if (list[i].getAttribute("length") > 10)
     *                  removeList.push(list[i]);
     *          }
     *          myList.remove(removeList);
     *      }
     *   ]]></a:script>
     * ```
     * 
     * #### Remarks
     *
     * Another way to trigger this method is by using the action attribute on a
     * button.
     *
     * ```xml
     *  <a:button action="remove" target="myList">Remove item</a:button>
     * ```
     *
     * Using the action methodology, you can let the original data source
     * (usually the server) know that the user removed an item:
     * 
     * ```xml
     *     <a:list>
     *         <a:bindings />
     *         <a:remove set="remove_product.php?id=[@id]" />
     *     </a:list>
     * ```
     *
     * For undo, this action should be extended and the server should maintain a
     * copy of the deleted item.
     * 
     * ```xml
     *  <a:list actiontracker="atList">
     *      <a:bindings />
     *      <a:remove set = "remove_product.php?id=[@id]"
     *                undo = "undo_remove_product.php?id=[@id]" />
     *  </a:list>
     *  <a:button 
     *    action = "remove" 
     *    target = "myList">Remove item</a:button>
     *   <a:button 
     *     caption = "Undo"
     *     disabled = "{!atList.undolength}" 
     *     onclick = "atList.undo()" />
     * ```
     *
     * @action
     * @param  {NodeList | XMLElement} [nodeList]  The {@link term.datanode data node}(s) to be removed. If none are specified, the current selection is removed.
     *
     * @return  {Boolean}  Indicates if the removal succeeded
     */
    this.remove = function(nodeList) {
        //Use the current selection if no xmlNode is defined
        if (!nodeList)
            nodeList = this.$valueList;

        //If we're an xml node let's convert
        if (nodeList.nodeType)
            nodeList = [nodeList];

        //If there is no selection we'll exit, nothing to do
        if (!nodeList || !nodeList.length)
            return;

        

        var changes = [];
        for (var i = 0; i < nodeList.length; i++) {
            changes.push({
                action: "removeNode",
                args: [nodeList[i]]
            });
        }

        if (this.$actions["removegroup"])
            return this.$executeAction("multicall", changes, "removegroup", nodeList[0]);
        else {
            return this.$executeAction("multicall", changes, "remove", 
              nodeList[0], null, null, nodeList.length > 1 ? nodeList : null);
        }
    };
    
    /**
     * Adds a {@link term.datanode data node} to the data of this element.
     *
     * #### Example
     *
     * A simple list showing products. This list is used in all following examples.
     * 
     * ```xml
     *  <a:list id="myList">
     *      <a:bindings>
     *          <a:caption match="[@name]" />
     *          <a:value match="[@id]" />
     *          <a:icon>[@type].png</a:icon>
     *          <a:each match="[product]" />
     *      </a:bindings>
     *      <a:model>
     *          <products>
     *              <product name="Soundblaster" type="audio"    id="product10" />
     *              <product name="Teapot"       type="3d"       id="product13" />
     *              <product name="Coprocessor"  type="chips"    id="product15" />
     *              <product name="Keyboard"     type="input"    id="product17" />
     *              <product name="Diskdrive"    type="storage"  id="product20" />
     *          </products>
     *      </a:model>
     *  </a:list>
     * ```
     *
     * #### Example
     *
     * This example adds a product to this element selection.
     *
     * ```xml
     *  <a:script><![CDATA[
     *      apf.onload = function() {
     *          myList.add('<product name="USB drive" type="storage" />');
     *      }
     *  ]]></a:script>
     * ```
     *
     * #### Example
     *
     * This example copys the selected product, changes its name, and then
     * adds it. After selecting the new node, the user is offered a rename input
     * box.
     * 
     * ```xml
     *  <a:script><![CDATA[
     *      apf.onload = function() {
     *          var xmlNode = apf.xmldb.copy(myList.selected);
     *          xmlNode.setAttribute("name", "New product");
     *          myList.add(xmlNode);
     *          myList.select(xmlNode);
     *          myList.startRename();
     *      }
     *  ]]></a:script>
     * ```
     * 
     * #### Remarks
     * Another way to trigger this method is by using the action attribute on a
     * button.
     *
     * ```xml
     *  <a:list>
     *      <a:bindings />
     *      <a:model />
     *      <a:actions>
     *          <a:add>
     *              <product name="New item" />
     *          </a:add>
     *      </a:actions>
     *  </a:list>
     *  <a:button action="add" target="myList">Add new product</a:button>
     * ```
     *
     * Using the action methodology you can let the original data source (usually the server) know that the user added an item.
     * 
     * ```xml
     *  <a:add get="{comm.addProduct()}" />
     * ```
     *
     * For undo, this action should be extended as follows.
     * 
     * ```xml
     *  <a:list id="myList" actiontracker="atList">
     *      <a:bindings />
     *      <a:model />
     *      <a:actions>
     *          <a:add set = "add_product.php?xml=%[.]"
     *              undo = "remove_product.php?id=[@id]">
     *              <product name="New product" id="productId" />
     *          </a:add>
     *      </a:actions>
     *  </a:list>
     *  <a:button 
     *    action = "add" 
     *    target = "myList">Add new product</a:button>
     *  <a:button
     *     caption = "Undo"
     *     disabled = "{!atList.undolength}" 
     *     onclick = "atList.undo()" />
     * ```
     *
     * In some cases the server needs to create the new product before its
     * added. This is done as follows.
     * 
     * ```xml
     *  <a:add get="{comm.createNewProduct()}" />
     * ```
     * Alternatively the template for the addition can be provided as a child of
     * the action rule.
     * ```
     *  <a:add set="add_product.php?xml=%[.]">
     *      <product name="USB drive" type="storage" />
     *  </a:add>
     * ```
     *
     * @action
     * @param {XMLElement}  [xmlNode]    The {@link term.datanode data node} which is added. If none is specified the action will use the action rule to try to retrieve a new node to add
     * @param {XMLElement}  [pNode]      The parent node of the added {@link term.datanode data node}
     * @param {XMLElement}  [beforeNode] The position where the XML element should be inserted
     * @return  {XMLElement} The added {@link term.datanode data node} or false on failure
     */
    this.add = function(xmlNode, pNode, beforeNode, userCallback) {
        var rule;

        if (this.$actions) {
            if (xmlNode && xmlNode.nodeType)
                rule = this.$actions.getRule("add", xmlNode);
            else if (typeof xmlNode == "string") {
                if (xmlNode.trim().charAt(0) == "<") {
                    xmlNode = apf.getXml(xmlNode);
                    rule = this.$actions.getRule("add", xmlNode);
                }
                else {
                    var rules = this.$actions["add"];
                    for (var i = 0, l = rules.length; i < l; i++) {
                        if (rules[i].getAttribute("type") == xmlNode) {
                            xmlNode = null;
                            rule = rules[i];
                            break;
                        }
                    }
                }
            }

            if (!rule) 
                rule = (this.$actions["add"] || {})[0];
        }
        else
            rule = null;
            
        
        
        var refNode = this.$isTreeArch ? this.selected || this.xmlRoot : this.xmlRoot,
            amlNode = this,
        callback = function(addXmlNode, state, extra) {
            if (state != apf.SUCCESS) {
                var oError;

                oError = new Error(apf.formatErrorString(1032, amlNode,
                    "Loading xml data",
                    "Could not add data for control " + amlNode.name
                    + "[" + amlNode.tagName + "] \nUrl: " + extra.url
                    + "\nInfo: " + extra.message + "\n\n" + xmlNode));

                if (extra.tpModule.retryTimeout(extra, state, amlNode, oError) === true)
                    return true;

                throw oError;
            }

            /*if (apf.supportNamespaces && node.namespaceURI == apf.ns.xhtml) {
                node = apf.getXml(node.xml.replace(/xmlns\=\"[^"]*\"/g, ""));
                //@todo import here for webkit?
            }*/

            if (typeof addXmlNode != "object")
                addXmlNode = apf.getXmlDom(addXmlNode).documentElement;
            if (addXmlNode.getAttribute(apf.xmldb.xmlIdTag))
                addXmlNode.setAttribute(apf.xmldb.xmlIdTag, "");

            var actionNode = amlNode.$actions &&
              amlNode.$actions.getRule("add", amlNode.$isTreeArch
                ? amlNode.selected
                : amlNode.xmlRoot);
            if (!pNode) {
                if (actionNode && actionNode.parent) {
                    pNode = (actionNode.cparent 
                      || actionNode.compile("parent", {
                        xpathmode: 2, 
                        injectself: true
                      }))(amlNode.$isTreeArch
                          ? amlNode.selected || amlNode.xmlRoot
                          : amlNode.xmlRoot);
                }
                else {
                    pNode = amlNode.$isTreeArch
                      ? amlNode.selected || amlNode.xmlRoot
                      : amlNode.xmlRoot
                }
            }

            if (!pNode)
                pNode = amlNode.xmlRoot;

            //Safari issue not auto importing nodes:
            if (apf.isWebkit && pNode.ownerDocument != addXmlNode.ownerDocument)
                addXmlNode = pNode.ownerDocument.importNode(addXmlNode, true); 

            

            if (amlNode.$executeAction("appendChild",
              [pNode, addXmlNode, beforeNode], "add", addXmlNode) !== false
              && amlNode.autoselect)
                amlNode.select(addXmlNode);

            if (userCallback)
                userCallback.call(amlNode, addXmlNode);

            return addXmlNode;
        };

        if (xmlNode)
            return callback(xmlNode, apf.SUCCESS);
        else {
            if (rule.get)
                return apf.getData(rule.get, {xmlNode: refNode, callback: callback})
            else {
                
            }
        }

        return addXmlNode;
    };

    if (!this.setValue) {
        /**
         * Sets the value of this element. The value
         * corresponds to an item in the list of loaded {@link term.datanode data nodes}. This
         * element will receive the selection. If no {@link term.datanode data node} is found, the
         * selection is cleared.
         *
         * @param {String}   value  The new value for this element.
         * @see apf.MultiSelect.getValue
         */
        this.setValue = function(value, disable_event) {
            // @todo apf3.0 what does noEvent do? in this scope it's useless and
            // doesn't improve codeflow with a global lookup and assignment
            noEvent = disable_event;
            this.setProperty("value", value, false, true);
            noEvent = false;
        };
    }

    /**
     * Retrieves an {@link term.datanode data node} that has a value that corresponds to the
     * string that is searched on.
     * @param {String} value The value to match.
     * @returns {XMLNode} The found node, or `false`
     */
    this.findXmlNodeByValue = function(value) {
        var nodes = this.getTraverseNodes(),
            bindSet = this.$attrBindings["eachvalue"]
                && "eachvalue" || this.$bindings["value"]
                && "value" || this.$hasBindRule("caption") && "caption";
        
        if (!bindSet)
            return false;
            
        for (var i = 0; i < nodes.length; i++) {
            if (this.$applyBindRule(bindSet, nodes[i]) == value)
                return nodes[i];
        }
    };

    if (!this.getValue) {
        /**
         * Retrieves the value of this element. This is the value of the
         * first selected {@link term.datanode data node}.
         * 
         */
        this.getValue = function(xmlNode, noError) {
            return this.value;
            /*
            if (!this.bindingRules && !this.caption) 
                return false;

            

            return this.$applyBindRule(this.$mainBind, xmlNode || this.selected, null, true)
                || this.$applyBindRule("caption", xmlNode || this.selected, null, true);
            */
        };
    }

    /**
     * Select the current selection...again.
     *
     */
    this.reselect = function(){ // @todo Add support for multiselect
        if (this.selected) this.select(this.selected, null, this.ctrlselect,
            null, true);//no support for multiselect currently.
    };

    /**
     * @event  beforeselect  Fires before a {@link apf.MultiSelect.select selection} is made
     * @param {Object} e The standard event object. It contains the following properties:
     *                     - `selected` ([[XMLElement]]): The {@link term.datanode data node} that will be selected
     *                     - `selection` ([[Array]]): An array of {@link term.datanode data nodes} that will be selected
     *                     - `htmlNode` ([[HTMLElement]]): The HTML element that visually represents the {@link term.datanode data node} 
     */
    /**
     * @event  afterselect  Fires after a {@link apf.MultiSelect.select selection} is made
     * @param {Object} e The standard event object. It contains the following properties:
     *                     - `selected` ([[XMLElement]]):    the {@link term.datanode data node} that was selected
     *                     - `selection` ([[Array]]():       an array of {@link term.datanode data node} that are selected
     *                     - `htmlNode` ([[HTMLElement]](): the HTML element that visually represents the {@link term.datanode data node}
     */
    /**
     * Selects a single, or set, of {@link term.eachnode each nodes}.
     * The selection can be visually represented in this element.
     *
     * @param {Mixed}   xmlNode      The identifier to determine the selection. It can be one of the following values:
     *                                 - ([[XMLElement]]):  The {@link term.datanode data node} to be used in the selection as a start/end point or to toggle the selection on the node.
     *                                 - ([[HTMLElement]]): The HTML element node used as visual representation of {@link term.datanode data node}. 
     *                                                 Used to determine the {@link term.datanode data node} for selection.
     *                                 - ([[String]]):      The value of the {@link term.datanode data node} to be selected.
     * @param {Boolean} [ctrlKey]    Indicates whether the [[keys: Ctrl]] key was pressed
     * @param {Boolean} [shiftKey]   Indicates whether the [[keys: Shift]]  key was pressed
     * @param {Boolean} [fakeselect] Indicates whether only visually a selection is made
     * @param {Boolean} [force]      Indicates whether reselect is forced
     * @param {Boolean} [noEvent]    Indicates whether to not call any event
     * @return  {Boolean}  Indicates whether the selection could be made
     *
     */
    this.select = function(xmlNode, ctrlKey, shiftKey, fakeselect, force, noEvent, userAction) {
        if (!this.selectable || userAction && this.disabled) 
            return;

        if (parseInt(fakeselect) == fakeselect) {
            //Don't select on context menu
            if (fakeselect == 2) {
                fakeselect = true;
                userAction = true;
            }
            else {
                fakeselect = false;
                userAction = true;
            }
        }

        if (this.$skipSelect) {
            this.$skipSelect = false;
            return;
        }

        if (this.ctrlselect && !shiftKey)
            ctrlKey = true;

        if (!this.multiselect)
            ctrlKey = shiftKey = false;
        
        // Selection buffering (for async compatibility)
        if (!this.xmlRoot) {
            if (!this.$buffered) {
                var f;
                this.addEventListener("afterload", f = function(){
                    this.select.apply(this, this.$buffered);
                    this.removeEventListener("afterload", f);
                    delete this.$buffered;
                });
            }

            this.$buffered = Array.prototype.slice.call(arguments);
            return;
        }

        var htmlNode;

        // *** Type Detection *** //
        if (!xmlNode) {
            

            return false;
        }

        if (typeof xmlNode != "object") {
            var str = xmlNode; xmlNode = null;
            if (typeof xmlNode == "string")
                xmlNode = apf.xmldb.getNodeById(str);

            //Select based on the value of the xml node
            if (!xmlNode) {
                xmlNode = this.findXmlNodeByValue(str);
                if (!xmlNode) {
                    this.clearSelection(noEvent);
                    return;
                }
            }
        }
        
        if (!(typeof (xmlNode.style || "") == "object")) {
            htmlNode = this.$findHtmlNode(xmlNode.getAttribute(
                    apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
        }
        else {
            var id = (htmlNode = xmlNode).getAttribute(apf.xmldb.htmlIdTag);
            while (!id && htmlNode.parentNode)
                id = (htmlNode = htmlNode.parentNode).getAttribute(
                    apf.xmldb.htmlIdTag);

            xmlNode = apf.xmldb.getNodeById(id);//, this.xmlRoot);
        }

        if (!shiftKey && !ctrlKey && !force && !this.reselectable 
          && this.$valueList.length <= 1 && this.$valueList.indexOf(xmlNode) > -1)
            return;

        if (this.dispatchEvent('beforeselect', {
            selected: xmlNode,
            htmlNode: htmlNode,
            ctrlKey: ctrlKey,
            shiftKey: shiftKey,
            force: force,
            captureOnly: noEvent
        }) === false)
              return false;

        // *** Selection *** //

        var lastIndicator = this.caret;
        this.caret = xmlNode;

        //Multiselect with SHIFT Key.
        if (shiftKey) {
            var range = this.$calcSelectRange(
              this.$valueList[0] || lastIndicator, xmlNode);

            if (this.$caret)
                this.$deindicate(this.$caret);

            this.selectList(range);

            this.$selected = 
            this.$caret = this.$indicate(htmlNode);
        }
        else if (ctrlKey) { //Multiselect with CTRL Key.
            //Node will be unselected
            if (this.$valueList.contains(xmlNode)) {
                if (this.selected == xmlNode) {
                    this.$deselect(this.$findHtmlNode(this.selected.getAttribute(
                        apf.xmldb.xmlIdTag) + "|" + this.$uniqueId));
                    
                    this.$deindicate(this.$caret);

                    if (this.$valueList.length && !fakeselect) {
                        //this.$selected = this.$selectedList[0];
                        this.selected = this.$valueList[0];
                    }
                }
                else
                    this.$deselect(htmlNode, xmlNode);

                if (!fakeselect) {
                    this.$selectedList.remove(htmlNode);
                    this.$valueList.remove(xmlNode);
                }

                if (htmlNode != this.$caret)
                    this.$deindicate(this.$caret);

                this.$selected = 
                this.$caret = this.$indicate(htmlNode);
            }
            // Node will be selected
            else {
                if (this.$caret)
                    this.$deindicate(this.$caret);
                this.$caret = this.$indicate(htmlNode, xmlNode);

                this.$selected = this.$select(htmlNode, xmlNode);
                this.selected = xmlNode;

                if (!fakeselect) {
                    this.$selectedList.push(htmlNode);
                    this.$valueList.push(xmlNode);
                }
            }
        }
        else if (fakeselect && htmlNode && this.$selectedList.contains(htmlNode)) {//Return if selected Node is htmlNode during a fake select
            return;
        }
        else { //Normal Selection
            //htmlNode && this.$selected == htmlNode && this.$valueList.length <= 1 && this.$selectedList.contains(htmlNode)
            if (this.$selected)
                this.$deselect(this.$selected);
            if (this.$caret)
                this.$deindicate(this.$caret);
            if (this.selected)
                this.clearSelection(true);

            this.$caret = this.$indicate(htmlNode, xmlNode);
            this.$selected = this.$select(htmlNode, xmlNode);
            this.selected = xmlNode;

            this.$selectedList.push(htmlNode);
            this.$valueList.push(xmlNode);
        }

        if (this.delayedselect && (typeof ctrlKey == "boolean")){
            var _self = this;
            $setTimeout(function(){
                if (_self.selected == xmlNode)
                    _self.dispatchEvent("afterselect", {
                        selection: _self.$valueList,
                        selected: xmlNode,
                        caret: _self.caret,
                        captureOnly: noEvent
                    });
            }, 10);
        }
        else {
            this.dispatchEvent("afterselect", {
                selection: this.$valueList,
                selected: xmlNode,
                caret: this.caret,
                captureOnly: noEvent
            });
        }

        return true;
    };

    /**
     * @event  beforechoose  Fires before a choice is made.
     * @param {Object} e The standard event object. It contains the following properties:
     *                   - `xmlNode` ([[XMLElement]]):   The {@link term.datanode data node} that was choosen
     *
     */
    /**
     * @event  afterchoose   Fires after a choice is made.
     * @param {Object} e The standard event object. It contains the following properties:
     *                   - `xmlNode` ([[XMLElement]]):   The {@link term.datanode data node} that was choosen
     */
    /**
     * Chooses a selected item. This is done by double clicking on the item or
     * pressing the Enter key.
     *
     * @param {Mixed}   xmlNode      The identifier to determine the selection. It can be one of the following values: 
     *                                - [[XMLElement]]:  The {@link term.datanode data node} to be choosen
     *                                - [[HTMLElement]]: The HTML element node used as visual representation of {@link term.datanode data node}
     *                                                 Used to determine the {@link term.datanode data node}
     *                                - [[String]] :     The value of the {@link term.datanode data node} to be choosen
     *
     */
    this.choose = function(xmlNode, userAction) {
        if (!this.selectable || userAction && this.disabled) return;

        if (this.dispatchEvent("beforechoose", {xmlNode : xmlNode}) === false)
            return false;

        if (xmlNode && !(typeof (xmlNode.style || "") == "object"))
            this.select(xmlNode);

        
        if (this.hasFeature(apf.__DATABINDING__)
          && this.dispatchEvent("afterchoose", {xmlNode : this.selected}) !== false)
            this.setProperty("chosen", this.selected);
        
    };

    /*
     * Removes the selection of one or more selected nodes.
     *
     * @param {Boolean} [noEvent]    Indicates whether or not to call any events
     */
    // @todo Doc
    this.clearSelection = function(noEvent, userAction) {
        if (!this.selectable || userAction && this.disabled || !this.$valueList.length)
            return;
        
        if (!noEvent) {
            if (this.dispatchEvent("beforeselect", {
                selection: [],
                selected: null,
                caret: this.caret
            }) === false)
                return false;
        }

        //Deselect html nodes
        var htmlNode;
        for (var i = this.$valueList.length - 1; i >= 0; i--) {
            htmlNode = this.$findHtmlNode(this.$valueList[i].getAttribute(
                    apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
            this.$deselect(htmlNode);
        }
        
        //Reset internal variables
        this.$selectedList.length = 0;
        this.$valueList.length = 0;
        this.$selected = 
        this.selected = null;

        //Redraw indicator
        if (this.caret) {
            htmlNode = this.$findHtmlNode(this.caret.getAttribute(
                    apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);

            this.$caret = this.$indicate(htmlNode);
        }

        if (!noEvent) {
            this.dispatchEvent("afterselect", {
                selection: this.$valueList,
                selected: null,
                caret: this.caret
            });
        }
    };

    /*
     * Selects a set of items
     *
     * @param {Array} xmlNodeList the {@link term.datanode data nodes} that will be selected.
     */
    //@todo Doc I think there are missing events here?
    this.selectList = function(xmlNodeList, noEvent, selected, userAction) {
        if (!this.selectable || userAction && this.disabled) return;

        if (this.dispatchEvent("beforeselect", {
            selection: xmlNodeList,
            selected: selected || xmlNodeList[0],
            caret: this.caret,
            captureOnly: noEvent
          }) === false)
            return false;

        this.clearSelection(true);

        for (var sel, i = 0; i < xmlNodeList.length; i++) {
            //@todo fix select state in unserialize after removing
            if (!xmlNodeList[i] || xmlNodeList[i].nodeType != 1) continue;
            var htmlNode,
                xmlNode = xmlNodeList[i];

            //Type Detection
            if (typeof xmlNode != "object")
                xmlNode = apf.xmldb.getNodeById(xmlNode);
            if (!(typeof (xmlNode.style || "") == "object"))
                htmlNode = this.$pHtmlDoc.getElementById(xmlNode.getAttribute(
                    apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
            else {
                htmlNode = xmlNode;
                xmlNode = apf.xmldb.getNodeById(htmlNode.getAttribute(
                    apf.xmldb.htmlIdTag));
            }

            if (!xmlNode) {
                
                continue;
            }

            //Select Node
            if (htmlNode) {
                if (!sel && selected == htmlNode)
                    sel = htmlNode;

                this.$select(htmlNode, xmlNode);
                this.$selectedList.push(htmlNode);
            }
            this.$valueList.push(xmlNode);
        }

        this.$selected = sel || this.$selectedList[0];
        this.selected = selected || this.$valueList[0];

        this.dispatchEvent("afterselect", {
            selection: this.$valueList,
            selected: this.selected,
            caret: this.caret,
            captureOnly: noEvent
        });
    };

    /**
     * @event indicate Fires when an item becomes the indicator.
     */

    /**
     * Sets the {@link term.caret caret} on an item to indicate to the user that the keyboard
     * actions are done relevant to that item. Using the keyboard,
     * a user can change the position of the indicator using the [[keys: Ctrl]] and arrow
     * keys while not making a selection. When making a selection with the mouse
     * or keyboard, the indicator is always set to the selected node. Unlike a
     * selection there can be only one indicator item.
     *
     * @param {Mixed}   xmlNode      The identifier to determine the indicator. Its possible values include:
     *                                - {XMLElement}  The {@link term.datanode data node} to be set as indicator.
     *                                - {HTMLElement} The HTML element node used as visual representation of
     *                                                   {@link term.datanode data node}. Used to determine the {@link term.datanode data node}.
     *                                - {String}      The value of the {@link term.datanode data node} to be set as an indicator.
     */
    this.setCaret = function(xmlNode) {
        if (!xmlNode) {
            if (this.$caret)
                this.$deindicate(this.$caret);
            this.caret = 
            this.$caret = null;
            return;
        }

        // *** Type Detection *** //
        var htmlNode;
        if (typeof xmlNode != "object")
            xmlNode = apf.xmldb.getNodeById(xmlNode);
        if (!(typeof (xmlNode.style || "") == "object")) {
            htmlNode = this.$findHtmlNode(xmlNode.getAttribute(
                    apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
        }
        else {
            var id = (htmlNode = xmlNode).getAttribute(apf.xmldb.htmlIdTag);
            while (!id && htmlNode.parentNode && htmlNode.parentNode.nodeType == 1)
                id = (htmlNode = htmlNode.parentNode).getAttribute(
                    apf.xmldb.htmlIdTag);
            if (!id) alert(this.$int.outerHTML);

            xmlNode = apf.xmldb.getNodeById(id);
        }

        if (this.$caret) {
            //this.$deindicate(this.$findHtmlNode(this.caret.getAttribute(
                //apf.xmldb.xmlIdTag) + "|" + this.$uniqueId));
            this.$deindicate(this.$caret);
        }
        
        this.$caret = this.$indicate(htmlNode);
        this.setProperty("caret", this.caret = xmlNode);
    };

    /*
     * @private
     */
    this.$setTempSelected = function(xmlNode, ctrlKey, shiftKey, down) {
        clearTimeout(this.timer);

        if (this.$bindings.selectable) {
            while (xmlNode && !this.$getDataNode("selectable", xmlNode)) {
                xmlNode = this.getNextTraverseSelected(xmlNode, !down);
            }
            if (!xmlNode) return;
        }

        if (!this.multiselect)
            ctrlKey = shiftKey = false;

        if (ctrlKey || this.ctrlselect) {
            if (this.$tempsel) {
                this.select(this.$tempsel);
                this.$tempsel = null;
            }

            this.setCaret(xmlNode);
        }
        else if (shiftKey) {
            if (this.$tempsel) {
                this.$selectTemp();
                this.$deselect(this.$tempsel);
                this.$tempsel = null;
            }

            this.select(xmlNode, null, shiftKey);
        }
        else if (!this.bufferselect || this.$valueList.length > 1) {
            this.select(xmlNode);
        }
        else {
            var id = apf.xmldb.getID(xmlNode, this);

            this.$deselect(this.$tempsel || this.$selected);
            this.$deindicate(this.$tempsel || this.$caret);
            this.$tempsel = this.$indicate(document.getElementById(id));
            this.$select(this.$tempsel);

            var _self = this;
            this.timer = $setTimeout(function(){
                _self.$selectTemp();
            }, 400);
        }
    };

    /*
     * @private
     */
    this.$selectTemp = function(){
        if (!this.$tempsel)
            return;

        clearTimeout(this.timer);
        this.select(this.$tempsel);

        this.$tempsel = null;
        this.timer = null;
    };

    /**
     * Selects all the {@link term.eachnode each nodes} of this element
     * 
     */
    this.selectAll = function(userAction) {
        if (!this.multiselect || !this.selectable
          || userAction && this.disabled || !this.xmlRoot)
            return;

        var nodes = this.$isTreeArch
            ? this.xmlRoot.selectNodes(".//" 
              + this.each.split("|").join("|.//"))
            : this.xmlRoot.selectNodes(this.each);
        
        this.selectList(nodes);
    };

    /**
     * Retrieves an Array or a document fragment containing all the selected
     * {@link term.datanode data nodes} from this element.
     *
     * @param {Boolean} [xmldoc] Specifies whether the method should return a document fragment.
     * @return {Mixed} The selection of this element.
     */
    this.getSelection = function(xmldoc) {
        var i, r;
        if (xmldoc) {
            r = this.xmlRoot
                ? this.xmlRoot.ownerDocument.createDocumentFragment()
                : apf.getXmlDom().createDocumentFragment();
            for (i = 0; i < this.$valueList.length; i++)
                apf.xmldb.cleanNode(r.appendChild(
                    this.$valueList[i].cloneNode(true)));
        }
        else {
            for (r = [], i = 0; i < this.$valueList.length; i++)
                r.push(this.$valueList[i]);
        }

        return r;
    };
    
    this.$getSelection = function(htmlNodes) {
        return htmlNodes ? this.$selectedList : this.$valueList;
    };

    /**
     * Selects the next {@link term.datanode data node} to be selected.
     *
     * @param {XMLElement}   xmlNode  The context {@link term.datanode data node}.
     * @param {Boolean}      [isTree] If `true`, indicates that this node is a tree, and should select children
     */
    this.defaultSelectNext = function(xmlNode, isTree) {
        var next = this.getNextTraverseSelected(xmlNode);
        //if(!next && xmlNode == this.xmlRoot) return;

        //@todo Why not use this.$isTreeArch ??
        if (next || !isTree)
            this.select(next ? next : this.getTraverseParent(xmlNode));
        else
            this.clearSelection(true);
    };

    /**
     * Selects the next {@link term.datanode data node} when available.
     */
    this.selectNext = function(){
        var xmlNode = this.getNextTraverse();
        if (xmlNode)
            this.select(xmlNode);
    };

    /**
     * Selects the previous {@link term.datanode data node} when available.
     */
    this.selectPrevious = function(){
        var xmlNode = this.getNextTraverse(null, -1);
        if (xmlNode)
            this.select(xmlNode);
    };

    /*
     * @private
     */
    this.getDefaultNext = function(xmlNode, isTree){  //@todo why is isTree an argument
        var next = this.getNextTraverseSelected(xmlNode);
        //if(!next && xmlNode == this.xmlRoot) return;

        return (next && next != xmlNode)
            ? next
            : (isTree
                ? this.getTraverseParent(xmlNode)
                : null); //this.getFirstTraverseNode()
    };

    /**
     * Determines whether a node is selected.
     *
     * @param {XMLElement}  xmlNode  The {@link term.datanode data node} to be checked
     * @return  {Boolean} Identifies if the element is selected
     */
    this.isSelected = function(xmlNode) {
        if (!xmlNode) return false;

        for (var i = 0; i < this.$valueList.length; i++) {
            if (this.$valueList[i] == xmlNode)
                return this.$valueList.length;
        }

        return false;
    };

    /*
     * This function checks whether the current selection is still correct.
     * Selection can become invalid when updates to the underlying data
     * happen. For instance when a selected node is removed.
     */
    this.$checkSelection = function(nextNode) {
        if (this.$valueList.length > 1) {
            //Fix selection if needed
            for (var lst = [], i = 0, l = this.$valueList.length; i < l; i++) {
                if (apf.isChildOf(this.xmlRoot, this.$valueList[i]))
                    lst.push(this.$valueList[i]);
            }

            if (lst.length > 1) {
                this.selectList(lst);
                if (this.caret
                  && !apf.isChildOf(this.xmlRoot, this.caret)) {
                    this.setCaret(nextNode || this.selected);
                }
                return;
            }
            else if (lst.length) {
                //this.clearSelection(true); //@todo noEvents here??
                nextNode = lst[0];
            }
        }

        if (!nextNode) {
            if (this.selected
              && !apf.isChildOf(this.xmlRoot, this.selected)) {
                nextNode = this.getFirstTraverseNode();
            }
            else if (this.selected && this.caret
              && !apf.isChildOf(this.xmlRoot, this.caret)) {
                this.setCaret(this.selected);
            }
            else if (!this.selected) {
                nextNode = this.xmlRoot
                    ? this.getFirstTraverseNode()
                    : null;
            }
            else {
                return; //Nothing to do
            }
        }

        if (nextNode) {
            if (this.autoselect) {
                this.select(nextNode);
            }
            else {
                this.clearSelection();
                this.setCaret(nextNode);
            }
        }
        else
            this.clearSelection();

        //if(action == "synchronize" && this.autoselect) this.reselect();
    };

    /**
     * @attribute {Boolean} [multiselect]   Sets or gets whether the user may select multiple items. Default is `true, but `false` for dropdown. 
     */
    /**
     * @attribute {Boolean} [autoselect]    Sets or gets whether a selection is made after data is loaded. Default is `true`, but `false` for dropdown. When the string 'all' is set, all {@link term.datanode data nodes} are selected.
     */
    /**
     *  @attribute {Boolean} [selectable]    Sets or gets whether the {@link term.datanode data nodes} of this element can be selected. Default is `true`.
     */
    /**
     *  @attribute {Boolean} [ctrlselect]    Sets or gets whether a selection is made as if the user is holding the [[keys: Ctrl]] key. When set to `true` each mouse selection will add to the current selection. Selecting an already selected element will deselect it.
     */
    /**
     *  @attribute {Boolean} [allowdeselect] Sets or gets whether the user can remove the selection of this element. When set to `true` it is possible for this element to have no selected {@link term.datanode data node}.
     */
    /**
     *  @attribute {Boolean} [reselectable]  Sets or gets whether selected nodes can be selected again, and the selection events are called again. Default is `false`. When set to `false` a selected {@link term.datanode data node} cannot be selected again.
     */
    /**
     *  @attribute {String}  [default]      Sets or gets the value that this component has when no selection is made.
     */
    /**
     *  @attribute {String}  [eachvalue]     Sets or gets the {@link term.expression expression} that determines the value for each {@link term.datanode data nodes} in the dataset of the element.
     * 
     */
    this.selectable = true;
    if (typeof this.ctrlselect == "undefined")
        this.ctrlselect = false;
    if (typeof this.multiselect == "undefined")
        this.multiselect = true;
    if (typeof this.autoselect == "undefined")
        this.autoselect = true;
    if (typeof this.delayedselect == "undefined")
        this.delayedselect = true;
    if (typeof this.allowdeselect == "undefined")
        this.allowdeselect = true;
    this.reselectable = false;

    this.$booleanProperties["selectable"] = true;
    //this.$booleanProperties["ctrlselect"] = true;
    this.$booleanProperties["multiselect"] = true;
    this.$booleanProperties["autoselect"] = true;
    this.$booleanProperties["delayedselect"] = true;
    this.$booleanProperties["allowdeselect"] = true;
    this.$booleanProperties["reselectable"] = true;

    this.$supportedProperties.push("selectable", "ctrlselect", "multiselect",
        "autoselect", "delayedselect", "allowdeselect", "reselectable", 
        "selection", "selected", "default", "value", "caret");

    /**
     * @attribute {String} [value]  Sets or gets the value of the element that is selected.
     *
     */
    //@todo add check here
    this.$propHandlers["value"] = function(value) {
        if (this.$lastValue == value) {
            delete this.$lastValue;
            return;
        }

        if (!this.$attrBindings["eachvalue"] && !this.$amlLoaded
          && this.getAttribute("eachvalue")) {
            var _self = this;
            return apf.queue.add("value" + this.$uniqueId, function(){
                _self.$propHandlers["value"].call(_self, value);
            });
        }
        
        

        if (value || value === 0 || this["default"])
            this.select(String(value) || this["default"]);
        else
            this.clearSelection();
    }
    
    this.$propHandlers["default"] = function(value, prop) {
        if (!this.value || !this.$amlLoaded && !(this.getAttribute("value") 
          || this.getAttribute("selected") || this.getAttribute("selection"))) {
            this.$propHandlers["value"].call(this, "");
        }
    }
    
    /**
     * @attribute {String} [value]   Sets or gets the caret value of the element.
     */
    //@todo fill this in
    this.$propHandlers["caret"] = function(value, prop) {
        if (value)
            this.setCaret(value);
    }
    
    
    
    //@todo optimize this thing. Also implement virtual dataset support.
    /**
     * @attribute {String} [selection]  Sets or gets the {@link term.expression expression} that determines the selection for this element. A reference to an XML nodelist can be passed as well.
     *
     */
    this.$propHandlers["selection"] = 
    
    /**
     * @attribute {String} [selected]   Sets or gets the {@link term.expression expression} that determines the selected node for this element. A reference to an XML element can be passed as well.
     * 
     */
    this.$propHandlers["selected"] = function(value, prop) {
        if (!value) value = this[prop] = null;

        if (prop == "selected" && typeof value != "string") { // && value == this.selected
            if (value && value.nodeType != 1)
                value = value.nodeValue;
            else
            //this.selected = null; //I don't remember why this is here. It removes the selected property without setting it again. (dropdown test)
                return;
        }
        
        

        if (this.$isSelecting) {
            this.selection = this.$valueList;
            return false;
        }

        var nodes, bindSet, getValue, i, j, c, d;
        //Update the selection
        if (prop == "selection") {
            if (typeof value == "object" && value == this.$valueList) {
                var pNode;
                //We're using an external model. Need to update bound nodeset
                if ((c = this.$attrBindings[prop]) && c.cvalue.models) { //added check, @todo whats up with above assumption?
                    this.$isSelecting = true; //Prevent reentrance (optimization)
    
                    bindSet = this.$attrBindings["eachvalue"] 
                        && "eachvalue" || this.$bindings["value"]
                        && "value" || this.$hasBindRule("caption") && "caption";
                    
                    if (!bindSet)
                        throw new Error("Missing bind rule set: eachvalue, value or caption");//@todo apf3.0 make this into a proper error
                    
                    //@todo this may be optimized by keeping a copy of the selection
                    var selNodes = this.$getDataNode(prop, this.xmlRoot);
                    nodes = value;
                    getValue = (d = this.$attrBindings["selection-unique"]) && d.cvalue;
                    
                    if (selNodes.length) {
                        pNode = selNodes[0].parentNode;
                    }
                    else {
                        var model, path;
                        if (c.cvalue.xpaths[0] == "#" || c.cvalue.xpaths[1] == "#") {
                            var m = (c.cvalue3 || (c.cvalue3 = apf.lm.compile(c.value, {
                                xpathmode: 5
                            })))(this.xmlRoot);
                            
                            model = m.model && m.model.$isModel && m.model;
                            if (model)
                                path = m.xpath;
                            else if (m.model) {
                                model = apf.xmldb.findModel(m.model);
                                path = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better
                            }
                            else {
                                //No selection - nothing to do
                            }
                        }
                        else {
                            
                            model = apf.nameserver.get("model", c.cvalue.xpaths[0]);
                            
                            path = c.cvalue.xpaths[1];
                        }

                        if (!model || !model.data) {
                            this.$isSelecting = false;
                            return false;
                        }
                        
                        pNode = model.queryNode(path.replace(/\/[^\/]+$|^[^\/]*$/, "") || ".");

                        if (!pNode)
                            throw new Error("Missing parent node"); //@todo apf3.0 make this into a proper error
                    }
                    
                    //Nodes removed
                    remove_loop:
                    for (i = 0; i < selNodes.length; i++) {
                        //Value is either determined by special property or in the 
                        //same way as the value for the bound node.
                        value = getValue 
                          ? getValue(selNodes[i]) 
                          : this.$applyBindRule(bindSet, selNodes[i]);
    
                        //Compare the value with the traverse nodes
                        for (j = 0; j < nodes.length; j++) {
                            if (this.$applyBindRule(bindSet, nodes[j]) == value) //@todo this could be cached
                                continue remove_loop;
                        }
                        
                        //remove node
                        apf.xmldb.removeNode(selNodes[i]);
                    }
                    
                    //Nodes added
                    add_loop:
                    for (i = 0; i < nodes.length; i++) {
                        //Value is either determined by special property or in the 
                        //same way as the value for the bound node.
                        value = this.$applyBindRule(bindSet, nodes[i]);
    
                        //Compare the value with the traverse nodes
                        for (j = 0; j < selNodes.length; j++) {
                            if (getValue 
                              ? getValue(selNodes[j]) 
                              : this.$applyBindRule(bindSet, selNodes[j]) == value) //@todo this could be cached
                                continue add_loop;
                        }
                        
                        //add node
                        var node = this.$attrBindings["selection-constructor"] 
                          && this.$getDataNode("selection-constructor", nodes[i])
                          || apf.getCleanCopy(nodes[i]);
                        apf.xmldb.appendChild(pNode, node);
                    }
                    
                    //@todo above changes should be via the actiontracker
                    this.$isSelecting = false;
                }
                
                return;
            }
            this.selection = this.$valueList;
        }
        else {
            this.selected = null;
        }
        
        if (!this.xmlRoot) {
            if (!this.$buffered) {
                var f;
                this.addEventListener("afterload", f = function(){
                    this.removeEventListener("afterload", f);
                    this.$propHandlers["selected"].call(this, value, prop);
                    delete this.$buffered;
                });
                this.$buffered = true;
            }
            this[prop] = null;
            return false;
        }

        if (!value || typeof value != "object") {
            //this[prop] = null;

            if (this.$attrBindings[prop]) {
                //Execute the selection query
                nodes = this.$getDataNode(prop, this.xmlRoot);
                if (nodes && (nodes.length || nodes.nodeType == 1)) {
                    this.setProperty("selection", nodes);
                    return;
                }
                
                if (!nodes || nodes.length === 0)
                    return;
                
                //Current model, it's an init selection, we'll clear the bind
                /*if (typeof value == "string" 
                  && !this.$attrBindings[prop].cvalue.xpaths[0]) {
                    this.$removeAttrBind(prop);
                }*/
            }
            
            if (!value) {
                this.clearSelection();
            }
            else {
                this.select(value);
            }

            return false; //Disable signalling the listeners to this property
        }
        else if (typeof value.length == "number") {
            nodes = value;
            if (!nodes.length) {
                this.selected = null;
                if (this.$valueList.length) { //dont clear selection when no selection exists (at prop init)
                    this.clearSelection();
                    return false; //Disable signalling the listeners to this property
                }
                else return;
            }
            
            //For when nodes are traverse nodes of this element
            if (this.isTraverseNode(nodes[0]) 
              && apf.isChildOf(this.xmlRoot, nodes[0])) {
                if (!this.multiselect) {
                    this.select(nodes[0]);
                }
                else {
                    //this[prop] = null; //??
                    this.selectList(nodes);
                }
                return false; //Disable signalling the listeners to this property
            }
            
            //if external model defined, loop through items and find mate by value
            if (this.$attrBindings[prop]) { //Can assume an external model is in place
                bindSet = this.$attrBindings["eachvalue"] 
                    && "eachvalue" || this.$bindings["value"]
                    && "value" || this.$hasBindRule("caption") && "caption";
                
                if (!bindSet)
                    throw new Error("Missing bind rule set: eachvalue, value or caption");//@todo apf3.0 make this into a proper error
                
                var tNodes = !this.each 
                    ? this.getTraverseNodes()
                    : this.xmlRoot.selectNodes("//" + this.each.split("|").join("|//"));
                
                getValue = (c = this.$attrBindings["selection-unique"]) && c.cvalue;
                var selList = [];
                for (i = 0; i < nodes.length; i++) {
                    //Value is either determined by special property or in the 
                    //same way as the value for the bound node.
                    value = getValue 
                        ? getValue(nodes[i]) 
                        : this.$applyBindRule(bindSet, nodes[i]);

                    //Compare the value with the traverse nodes
                    for (j = 0; j < tNodes.length; j++) {
                        if (this.$applyBindRule(bindSet, tNodes[j]) == value) //@todo this could be cached
                            selList.push(tNodes[j]);
                    }
                }
                
                //this[prop] = null; //???
                this.selectList(selList, true); //@todo noEvent to distinguish between user actions and not user actions... need to rethink this
                return false;
            }
            
            throw new Error("Show me which case this is");
        }
        else if (this.$valueList.indexOf(value) == -1) {
            //this.selected = null;
            this.select(value);
        }
    };
    
    
    
    this.$propHandlers["allowdeselect"] = function(value) {
        if (value) {
            var _self = this;
            this.$container.onmousedown = function(e) {
                if (!e)
                    e = event;
                if (e.ctrlKey || e.shiftKey)
                    return;

                var srcElement = e.srcElement || e.target;
                if (_self.allowdeselect && (srcElement == this
                  || srcElement.getAttribute(apf.xmldb.htmlIdTag)))
                    _self.clearSelection(); //hacky
            }
        }
        else {
            this.$container.onmousedown = null;
        }
    };

    this.$propHandlers["ctrlselect"] = function(value) {
        if (value != "enter")
            this.ctrlselect = apf.isTrue(value);
    }

    function fAutoselect(){
        this.selectAll();
    }
    
    this.$propHandlers["autoselect"] = function(value) {
        if (value == "all" && this.multiselect)
            this.addEventListener("afterload", fAutoselect);
        else
            this.removeEventListener("afterload", fAutoselect);
    };

    this.$propHandlers["multiselect"] = function(value) {
        if (!value && this.$valueList.length > 1)
            this.select(this.selected);

        //if (value)
            //this.bufferselect = false; //@todo doesn't return to original value
    };

    // Select Bind class
    
    this.addEventListener("beforeselect", function(e) {
        if (this.$bindings.selectable && !this.$getDataNode("selectable", e.selected))
            return false;
    }, true);
    

    
    this.addEventListener("afterselect", function (e) {
        
        var combinedvalue = null;

        
        //@todo refactor below
        /*if (this.caret == this.selected || e.list && e.list.length > 1 && hasConnections) {
            //Multiselect databinding handling... [experimental]
            if (e.list && e.list.length > 1 && this.$getConnections().length) { //@todo this no work no more apf3.0
                var oEl = this.xmlRoot.ownerDocument.createElement(this.selected.tagName);
                var attr = {};

                //Fill basic nodes
                var nodes = e.list[0].attributes;
                for (var j = 0; j < nodes.length; j++)
                    attr[nodes[j].nodeName] = nodes[j].nodeValue;

                //Remove nodes
                for (var prop, i = 1; i < e.list.length; i++) {
                    for (prop in attr) {
                        if (typeof attr[prop] != "string") continue;

                        if (!e.list[i].getAttributeNode(prop))
                            attr[prop] = undefined;
                        else if (e.list[i].getAttribute(prop) != attr[prop])
                            attr[prop] = "";
                    }
                }

                //Set attributes
                for (prop in attr) {
                    if (typeof attr[prop] != "string") continue;
                    oEl.setAttribute(prop, attr[prop]);
                }

                //missing is childnodes... will implement later when needed...

                oEl.setAttribute(apf.xmldb.xmlIdTag, this.$uniqueId);
                apf.MultiSelectServer.register(oEl.getAttribute(apf.xmldb.xmlIdTag),
                    oEl, e.list, this);
                apf.xmldb.addNodeListener(oEl, apf.MultiSelectServer);

                combinedvalue = oEl;
            }
        }*/
        
        
        //Set caret property
        this.setProperty("caret", e.caret);

        //Set selection length
        if (this.sellength != e.selection.length)
            this.setProperty("sellength", e.selection.length);
        
        //Set selection property
        delete this.selection;
        this.setProperty("selection", e.selection);
        if (!e.selection.length) {
            //Set selected property
            this.setProperty("selected", e.selected);
            
            //Set value property
            if (this.value)
                this.setProperty("value", "");
        }
        else {
            //Set selected property
            this.$chained = true;
            if (!e.force && (!this.dataParent || !this.dataParent.parent 
              || !this.dataParent.parent.$chained)) {
                var _self = this;
                $setTimeout(function(){
                    
                    if (_self.selected == e.selected) {
                        delete _self.selected;
                        _self.setProperty("selected", combinedvalue || e.selected);
                    }
                    
                    delete _self.$chained;
                }, 10);
            }
            else {
                
                this.setProperty("selected", combinedvalue || e.selected);
                
                delete this.$chained;
            }
            
            //Set value property
            var valueRule = this.$attrBindings["eachvalue"] && "eachvalue" 
                || this.$bindings["value"] && "value" 
                || this.$hasBindRule("caption") && "caption";

            if (valueRule) {
                //@todo this will call the handler again - should be optimized

                this.$lastValue = this.$applyBindRule(valueRule, e.selected)
                //this.$attrBindings["value"] && 
                if (this.$lastValue != 
                  (valueRule != "value" && (this.xmlRoot
                  && this.$applyBindRule("value", this.xmlRoot, null, true)) 
                  || this.value)) {
                    if (valueRule == "eachvalue" || this.xmlRoot != this)
                        this.change(this.$lastValue);
                    else
                        this.setProperty("value", this.$lastValue);
                }
                /*else {
                    this.setProperty("value", this.$lastValue);
                }*/
                delete this.$lastValue;
            }
        }
        
        

        
    }, true);
    
    
    

}).call(apf.MultiSelect.prototype = new apf.MultiselectBinding());



//@todo refactor below
/*
 * @private
 */
/*
apf.MultiSelectServer = {
    objects: {},

    register: function(xmlId, xmlNode, selList, jNode) {
        if (!this.$uniqueId)
            this.$uniqueId = apf.all.push(this) - 1;

        this.objects[xmlId] = {
            xml: xmlNode,
            list: selList,
            jNode: jNode
        };
    },

    $xmlUpdate: function(action, xmlNode, listenNode, UndoObj) {
        if (action != "attribute") return;

        var data = this.objects[xmlNode.getAttribute(apf.xmldb.xmlIdTag)];
        if (!data) return;

        var nodes = xmlNode.attributes;

        for (var j = 0; j < data.list.length; j++) {
            //data[j].setAttribute(UndoObj.name, xmlNode.getAttribute(UndoObj.name));
            apf.xmldb.setAttribute(data.list[j], UndoObj.name,
                xmlNode.getAttribute(UndoObj.name));
        }

        //apf.xmldb.synchronize();
    }
};
*/






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__CHILDVALUE__ = 1 << 27;


apf.ChildValue = function(){
    if (!this.$childProperty)
        this.$childProperty = "value";
    
    this.$regbase = this.$regbase | apf.__CHILDVALUE__;
    
    var f, re = /^[\s\S]*?>(<\?lm)?([\s\S]*?)(?:\?>)?<[^>]*?>$/;
    this.addEventListener("DOMCharacterDataModified", f = function(e) {
        if (e && (e.currentTarget == this 
          || e.currentTarget.nodeType == 2 && e.relatedNode == this)
          || this.$amlDestroyed)
            return;

        if (this.getAttribute(this.$childProperty))
            return;
        
        //Get value from xml (could also serialize children, but that is slower
        var m = this.serialize().match(re),
            v = m && m[2] || "";
        if (m && m[1])
            v = "{" + v + "}";

        this.$norecur = true;

        
        if (v.indexOf("{") > -1 || v.indexOf("[") > -1)
            this.$setDynamicProperty(this.$childProperty, v);
        else
        
        if (this[this.$childProperty] != v)
            this.setProperty(this.$childProperty, v);
       
        this.$norecur = false;
    });
    
    //@todo Should be buffered
    this.addEventListener("DOMAttrModified", f);
    this.addEventListener("DOMNodeInserted", f);
    this.addEventListener("DOMNodeRemoved", f);
    
    this.addEventListener("$skinchange", function(e) {
       this.$propHandlers[this.$childProperty].call(this, this.caption || "");
    });
    
    this.$init(function(){
       this.addEventListener("prop." + this.$childProperty, function(e) {
           if (!this.$norecur && !e.value && !this.getAttributeNode(this.$childProperty))
               f.call(this);
       });
    });

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var hasNoProp = typeof this[this.$childProperty] == "undefined";
        
        //this.firstChild.nodeType != 7 && 
        if (hasNoProp
          && !this.getElementsByTagNameNS(this.namespaceURI, "*", true).length 
          && (this.childNodes.length > 1 || this.firstChild 
          && (this.firstChild.nodeType == 1 
          || this.firstChild.nodeValue.trim().length))) {
            //Get value from xml (could also serialize children, but that is slower
            var m = (this.$aml && this.$aml.xml || this.serialize()).match(re),
                v = m && m[2] || "";
            if (m && m[1])
                v = "{" + v + "}";

            
            if (v.indexOf("{") > -1 || v.indexOf("[") > -1)
                this.$setDynamicProperty(this.$childProperty, v);
            else
            
                this.setProperty(this.$childProperty, apf.html_entity_decode(v)); //@todo should be xml entity decode
        }
        else if (hasNoProp)
            this.$propHandlers[this.$childProperty].call(this, "");
    });
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__DATAACTION__ = 1 << 25;


/**
 * A [[term.baseclass baseclass]] that adds data action features to this element.
 * @class apf.DataAction
 */
apf.DataAction = function(){
    this.$regbase = this.$regbase | apf.__DATAACTION__;

    // *** Public Methods *** //

    /**
     * Gets the ActionTracker this element communicates with.
     *
     * @return {apf.actiontracker}
     * @see apf.smartbinding
     */
    this.getActionTracker = function(ignoreMe) {
        if (!apf.AmlNode)
            return apf.window.$at;

        var pNode = this, tracker = ignoreMe ? null : this.$at;
        if (!tracker && this.dataParent && this.dataParent.parent)
            tracker = this.dataParent.parent.$at; //@todo apf3.0 change this to be recursive??

        while (!tracker) {
            if (!pNode.parentNode && !pNode.$parentNode) {
                var model;
                return (model = this.getModel && this.getModel(true)) && model.$at || apf.window.$at;
            }

            tracker = (pNode = pNode.parentNode || pNode.$parentNode).$at;
        }
        return tracker;
    };

    

    this.$actionsLog = {};
    this.$actions = false;

    /**
     * @event locksuccess   Fires when a lock request succeeds
     * @bubbles 
     * @param {Object} e The standard event object, with the following properties:
     *                   - state ([[Number]]): The return code of the lock request
     *
     */
    /**
     * @event lockfailed    Fires when a lock request failes
     * @bubbles 
     * @param {Object} e The standard event object, with the following properties:
     *                   - state ([[Number]]): The return code of the lock request
     *
     */
    /**
     * @event unlocksuccess Fires when an unlock request succeeds
     * @bubbles 
     * @param {Object} e The standard event object, with the following properties:
     *                   - state ([[Number]]): The return code of the unlock request
     *
     */
    /**
     * @event unlockfailed  Fires when an unlock request fails
     * @bubbles 
     * @param {Object} e The standard event object, with the following properties:
     *                   - state ([[Number]]): The return code of the unlock request
     *
     */
    /*
     *  Starts the specified action, does optional locking and can be offline aware
     *  - or for optimistic locking it will record the timestamp (a setting
     *    <a:appsettings locking="optimistic"/>)
     *  - During offline work, optimistic locks will be handled by taking the
     *    timestamp of going offline
     *  - This method is always optional! The server should not expect locking to exist.
     *
     */
    this.$startAction = function(name, xmlContext, fRollback) {
        if (this.disabled || this.liveedit && name != "edit")
            return false;

        var actionRule = this.$actions && this.$actions.getRule(name, xmlContext);
        if (!actionRule && apf.config.autoDisableActions && this.$actions) {
            

            return false;
        }

        var bHasOffline = typeof apf.offline != "undefined";
        

        if (this.dispatchEvent(name + "start", {
            xmlContext: xmlContext
        }) === false)
            return false;

        

        this.$actionsLog[name] = xmlContext;

        return true;
    };

    
    // @todo think about if this is only for rdb
    this.addEventListener("xmlupdate", function(e) {
        if (apf.xmldb.disableRDB != 2)
            return;

        for (var name in this.$actionsLog) {
            if (apf.isChildOf(this.$actionsLog[name], e.xmlNode, true)) {
                //this.$stopAction(name, true);
                this.$actionsLog[name].rollback.call(this, this.$actionsLog[name].xmlContext);
            }
        }
    });
    

    this.$stopAction = function(name, isCancelled, curLock) {
        delete this.$actionsLog[name];

        
    };

    /*
     * Executes an action using action rules set in the {@link apf.actions actions element}.
     *
     * @param {String}      atAction      The name of the action to be performed by the [[ActionTracker]]. Possible values include:
     *                                 - `"setTextNode"`:   Sets the first text node of an XML element. For more information, see {@link core.xmldb.method.setTextNode}
     *                                 - `"setAttribute"`:  Sets the attribute of an XML element. For more information, see {@link core.xmldb.method.setAttribute}
     *                                 - `"removeAttribute"`:   Removes an attribute from an XML element. For more information, see {@link core.xmldb.method.removeAttribute}
     *                                 - `"setAttributes"`:   Sets multiple attribute on an XML element. The arguments are in the form of `xmlNode, Array`
     *                                 - `"replaceNode"`:   Replaces an XML child with another one. For more information, see {@link core.xmldb.method.replaceNode}
     *                                 - `"addChildNode"`:   Adds a new XML node to a parent node. For more information, see {@link core.xmldb.method.addChildNode}
     *                                 - `"appendChild"`:   Appends an XML node to a parent node. For more information, see {@link core.xmldb.method.appendChild}
     *                                 - `"moveNode"` :  Moves an XML node from one parent to another. For more information, see {@link core.xmldb.method.moveNode}
     *                                 - `"removeNode"`:   Removes a node from it's parent. For more information, see {@link core.xmldb.method.removeNode}
     *                                 - `" removeNodeList"`:    Removes multiple nodes from their parent. For more information, see {@link core.xmldb.method.removeNodeList}
     *                                 - `"setValueByXpath"`:   Sets the nodeValue of an XML node which is selected
     *                                                           by an xpath statement. The arguments are in the form of `xmlNode, xpath, value`
     *                                 - `"multicall"`:          Calls multiple of the above actions. The argument`s are an array
     *                                                           of argument arrays for these actions each with a func`
     *                                                           property, which is the name of the action.
     * @param {Array}       args          the arguments to the function specified
     *                                    in <code>atAction</code>.
     * @param {String}      action        the name of the action rule defined in
     *                                    actions for this element.
     * @param {XMLElement}  xmlNode       the context for the action rules.
     * @param {Boolean}     [noevent]     whether or not to call events.
     * @param {XMLElement}  [contextNode] the context node for action processing
     *                                    (such as RPC calls). Usually the same
     *                                    as <code>xmlNode</code>
     * @return {Boolean} specifies success or failure
     * @see apf.smartbinding
     */
    this.$executeAction = function(atAction, args, action, xmlNode, noevent, contextNode, multiple) {
        

        

        //Get Rules from Array
        var rule = this.$actions && this.$actions.getRule(action, xmlNode);
        if (!rule && this.$actions && apf.config.autoDisableActions
          && "action|change".indexOf(action) == -1) {
            apf.console.warn("Could not execute action '" + action + "'. \
              No valid action rule was found and auto-disable-actions is enabled");

            return false;
        }

        

        var newMultiple;
        if (multiple) {
            newMultiple = [];
            for (var k = multiple.length - 1; k >= 0; k--) {
                newMultiple.unshift({
                    xmlActionNode: rule, // && rule[4],
                    amlNode: this,
                    selNode: multiple[k],
                    xmlNode: multiple[k]
                })
            }
        }

        //@todo apf3.0 Shouldn't the contextNode be made by the match
        var ev = new apf.AmlEvent("before" + action.toLowerCase(), {
            action: atAction,
            args: args,
            xmlActionNode: rule,
            amlNode: this,
            selNode: contextNode,
            multiple: newMultiple || false
            
        });

        //Call Event and cancel if it returns false
        if (!noevent) {
            //Allow the action and arguments to be changed by the event
            if (this.dispatchEvent(ev.name, null, ev) === false)
                return false;

            delete ev.currentTarget;
        }

        //Call ActionTracker and return ID of Action in Tracker
        var at = this.getActionTracker();
        if (!at)// This only happens at destruction of apf
            return UndoObj;

        var UndoObj = at.execute(ev);
        ev.xmlNode = UndoObj.xmlNode;
        ev.undoObj = UndoObj;

        //Call After Event
        if (!noevent) { //@todo noevent is not implemented for before. ???
            ev.name = "after" + action.toLowerCase();
            ev.cancelBubble = false;
            delete ev.returnValue;
            delete ev.currentTarget;
            this.dispatchEvent(ev.name, null, ev);
        }

        return UndoObj;
    };

    /*
     * Executes an action based on the set name and the new value
     * @param {String}      atName   the name of the action rule defined in actions for this element.
     * @param {String}      setName  the name of the binding rule defined in bindings for this element.
     * @param {XMLElement}  xmlNode  the xml element to which the rules are applied
     * @param {String}      value    the new value of the node
     */
    this.$executeSingleValue = function(atName, setName, xmlNode, value, getArgList) {
        var xpath, args, rule = this.$getBindRule(setName, xmlNode);

        //recompile bindrule to create nodes
        if (!rule) {
            
                return false;
        }

        var compiled;
        ["valuematch", "match", "value"].each(function(type) {
            if (!rule[type] || compiled)
                return;

            compiled = rule["c" + type]; //cvaluematch || (rule.value ? rule.cvalue : rule.cmatch);
            if (!compiled)
                compiled = rule.compile(type);

            if (compiled.type != 3)
                compiled = null;
        });

        

        var atAction, model, node,
            sel = compiled.xpaths, //get first xpath
            shouldLoad = false;

        if (sel[0] == "#" || sel[1] == "#") {
            var m = (rule.cvalue3 || (rule.cvalue3 = apf.lm.compile(rule.value, {
                xpathmode: 5
            })))(xmlNode, apf.nameserver.lookup["all"]);

            model = m.model && m.model.$isModel && m.model;
            if (model) {
                node = model.queryNode(m.xpath);
                xmlNode = model.data;
            }
            else if (m.model) {
                model = apf.xmldb.findModel(m.model);
                node = m.model.selectSingleNode(m.xpath);
                xmlNode = m.model;
            }
            else {

            }

            sel[1] = m.xpath;
        }
        else {
            
            model = sel[0] && apf.nameserver.get("model", sel[0]) || this.$model,
            node = model
                ? model.queryNode(sel[1])
                : (xmlNode || this.xmlRoot).selectSingleNode(sel[1]);
            if (model && !xmlNode)
                xmlNode = model.data; //@experimental, after changing this, please run test/test_rename_edge.html
            
        }

        if (node) {
            if (apf.queryValue(node) == value) return; // Do nothing if value is unchanged

            atAction = (node.nodeType == 1 || node.nodeType == 3
                || node.nodeType == 4) ? "setTextNode" : "setAttribute";
            args = (node.nodeType == 1)
                ? [node, value]
                : (node.nodeType == 3 || node.nodeType == 4
                    ? [node.parentNode, value]
                    : [node.ownerElement || node.selectSingleNode(".."), node.nodeName, value]);
        }
        else {
            atAction = "setValueByXpath";
            xpath = sel[1];
            
            if (!this.$createModel || this.getModel() && !this.getModel().$createModel) {
                throw new Error("Model data does not exist, and I am not "
                    + "allowed to create the element for xpath '" 
                    + xpath + "' and element " + this.serialize(true));
            }

            if (!xmlNode) {
                //Assuming this component is connnected to a model
                if (!model)
                    model = this.getModel();
                if (model) {
                    if (!model.data)
                        model.load("<data />");

                    xpath = (model.getXpathByAmlNode(this) || ".")
                        + (xpath && xpath != "." ? "/" + xpath : "");
                    xmlNode = model.data;
                }
                else {
                    if (!this.dataParent)
                        return false;

                    xmlNode = this.dataParent.parent.selected || this.dataParent.parent.xmlRoot;
                    if (!xmlNode)
                        return false;

                    xpath = (this.dataParent.xpath || ".")
                        + (xpath && xpath != "." ? "/" + xpath : "");
                    shouldLoad = true;
                }
            }

            args = [xmlNode, value, xpath];
        }

        if (getArgList) {
            return {
                action: atAction,
                args: args
            };
        }

        //Use Action Tracker
        var result = this.$executeAction(atAction, args, atName, xmlNode);

        if (shouldLoad)
            this.load(xmlNode.selectSingleNode(xpath));

        return result;
    };

    /**
     * Changes the value of this element.
     * @action
     * @param {String}  [string] The new value of this element.
     * 
     */
    this.change = function(value, force){ // @todo apf3.0 maybe not for multiselect?? - why is clearError handling not in setProperty for value
        
        if (this.errBox && this.errBox.visible && this.isValid && this.isValid())
            this.clearError();
        

        
        //Not databound
        if (!this.xmlRoot && !this.$createModel || !(this.$mainBind == "value"
          && this.hasFeature(apf.__MULTISELECT__)
            ? this.$attrBindings["value"]
            : this.$hasBindRule(this.$mainBind))) {
        
            if (!force && value === this.value
              || this.dispatchEvent("beforechange", {value : value}) === false)
                return false;

            //@todo in theory one could support actions
            //@todo disabled below, because it gives unexpected behaviour when
            //form elements are used for layout and other UI alterations
            /*this.getActionTracker().execute({
                action: "setProperty",
                args: [this, "value", value, false, true],
                amlNode: this
            });*/
            this.setProperty("value", value);

            return this.dispatchEvent("afterchange", {value : value});
        
        }

        var valueRule = this.$attrBindings["eachvalue"] && "eachvalue"
            || this.$bindings["value"] && "value"
            || this.$hasBindRule("caption") && "caption";

        if (value === (valueRule != "value" && (this.xmlRoot
          && this.$applyBindRule("value", this.xmlRoot, null, true))
          || this.value))
            return false;

        this.$executeSingleValue("change", this.$mainBind, this.xmlRoot, value);
        
    };

    this.$booleanProperties["render-root"] = true;
    this.$supportedProperties.push("create-model", "actions");

    /**
     * @attribute {Boolean} create-model Sets or gets whether the model this element connects
     * to is extended when the data pointed to does not exist. Defaults to true.
     * 
     * #### Example
     *
     * In this example, a model is extended when the user enters information in
     * the form elements. Because no model is specified for the form elements,
     * the first available model is chosen. At the start, it doesn't have any
     * data; this changes when (for instance) the name is filled in. A root node
     * is created, and under that a 'name' element with a textnode containing
     * the entered text.
     * 
     * ```xml
     *  <a:bar>
     *      <a:label>Name</a:label>
     *      <a:textbox value="[name]" required="true" />
     *
     *      <a:label>Address</a:label>
     *      <a:textarea value="[address]" />
     *
     *      <a:label>Country</a:label>
     *      <a:dropdown
     *        value = "[mdlForm::country]"
     *        model = "countries.xml"
     *        each = "[country]"
     *        caption = "[@name]" />
     *      <a:button action="submit">Submit</a:button>
     *  </a:bar>
     * ```
     */
    this.$propHandlers["create-model"] = function(value) {
        this.$createModel = value;
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (typeof this["create-model"] == "undefined"
          && !this.$setInheritedAttribute("create-model")) {
            this.$createModel = true;
        }
    });
};

apf.config.$inheritProperties["create-model"] = 1;





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__CACHE__ = 1 << 2;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have caching features. It takes care of
 * storing, retrieving, and updating rendered data (in HTML form)
 * to overcome the waiting time while rendering the contents every time the
 * data is loaded.
 *
 * @class apf.Cache
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.Cache = function(){
    /* ********************************************************************
                                        PROPERTIES
    *********************************************************************/
    this.cache = {};
    this.$subTreeCacheContext = null;

    this.caching = true;
    this.$regbase = this.$regbase | apf.__CACHE__;

    /* ********************************************************************
                                        PUBLIC METHODS
    *********************************************************************/

    this.addEventListener("$load", function(e) {
        if (!this.caching || e.forceNoCache)
            return;

        // retrieve the cacheId
        if (!this.cacheId) {
            this.cacheId = this.$generateCacheId && this.$generateCacheId(e.xmlNode) 
                || e.xmlNode.getAttribute(apf.xmldb.xmlIdTag) 
                || apf.xmldb.nodeConnect(apf.xmldb.getXmlDocId(e.xmlNode), e.xmlNode);//e.xmlNode
        }

        // Retrieve cached version of document if available
        var fromCache = getCache.call(this, this.cacheId, e.xmlNode);
        if (fromCache) {
            if (fromCache == -1 || !this.getTraverseNodes)
                return (e.returnValue = false);

            var nodes = this.getTraverseNodes();

            //Information needs to be passed to the followers... even when cached...
            if (nodes.length) {
                if (this["default"])
                    this.select(this["default"]);
                else if (this.autoselect)
                    this.select(nodes[0], null, null, null, true);
            }
            else if (this.clearSelection)
                this.clearSelection(); //@todo apf3.0 was setProperty("selected", null

            if (!nodes.length) {
                // Remove message notifying user the control is without data
                this.$removeClearMessage();
                this.$setClearMessage(this["empty-message"], "empty");
            }
                
            
            //@todo move this to getCache??
            if (nodes.length != this.length)
                this.setProperty("length", nodes.length);
            

            return false;
        }
    });
    
    this.addEventListener("$clear", function(){
        if (!this.caching)
            return;

        /*
            Check if we borrowed an HTMLElement
            We should return it where it came from

            note: There is a potential that we can't find the exact location
            to put it back. We should then look at it's position in the xml.
            (but since I'm lazy it's not doing this right now)
            There might also be problems when removing the xmlroot
        */
        if (this.hasFeature(apf.__MULTISELECT__)
          && this.$subTreeCacheContext && this.$subTreeCacheContext.oHtml) {
            if (this.renderRoot) {
                this.$subTreeCacheContext.parentNode.insertBefore(
                    this.$subTreeCacheContext.oHtml, this.$subTreeCacheContext.beforeNode);
            }
            else {
                var container = this.$subTreeCacheContext.container || this.$container;
                while (container.childNodes.length)
                    this.$subTreeCacheContext.oHtml.appendChild(container.childNodes[0]);
            }

            this.documentId = this.xmlRoot = this.cacheId = this.$subTreeCacheContext = null;
        }
        else {
            /* If the current item was loaded whilst offline, we won't cache
             * anything
             */
            if (this.$loadedWhenOffline) {
                this.$loadedWhenOffline = false;
            }
            else {
                // Here we cache the current part
                var fragment = this.$getCurrentFragment();
                if (!fragment) return;//this.$setClearMessage(this["empty-message"]);

                fragment.documentId = this.documentId;
                fragment.xmlRoot = this.xmlRoot;
                
                if (this.cacheId || this.xmlRoot)
                    setCache.call(this, this.cacheId ||
                        this.xmlRoot.getAttribute(apf.xmldb.xmlIdTag) || "doc"
                        + this.xmlRoot.getAttribute(apf.xmldb.xmlDocTag), fragment);
            }
        }
    });

    /*
     * Checks the cache for a cached item by ID. If the ID is found, the
     * representation is loaded from cache and set active.
     *
     * @param {String}  id  The id of the cache element which is looked up.
     * @param {Object}  xmlNode
     * @return {Boolean} If `true`, the cache element was found and set active
     * @see    baseclass.databinding.method.load
     * @private
     */
    function getCache(id, xmlNode) {
        /*
            Let's check if the requested source is actually
            a sub tree of an already rendered part
        */
        
        if (xmlNode && this.hasFeature(apf.__MULTISELECT__) && this.$isTreeArch) {
            var cacheItem,
                htmlId = xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId,
                node = this.$pHtmlDoc.getElementById(htmlId);
            if (node) 
                cacheItem = id ? false : this.$container; //@todo what is the purpose of this statement?
            else {
                for (var prop in this.cache) {
                    if (this.cache[prop] && this.cache[prop].nodeType) {
                        node = this.cache[prop].getElementById(htmlId);
                        if (node) {
                            cacheItem = id ? prop : this.cache[prop]; //@todo what is the purpose of this statement?
                            break;
                        }
                    }
                }
            }
            
            if (cacheItem && !this.cache[id]) {
                /*
                    Ok so it is, let's borrow it for a while
                    We can't clone it, because the updates will
                    get ambiguous, so we have to put it back later
                */
                var oHtml = this.$findHtmlNode(
                    xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
                this.$subTreeCacheContext = {
                    oHtml: oHtml,
                    parentNode: oHtml.parentNode,
                    beforeNode: oHtml.nextSibling,
                    cacheItem: cacheItem
                };

                this.documentId = apf.xmldb.getXmlDocId(xmlNode);
                this.cacheId = id;
                this.xmlRoot = xmlNode;

                //Load html
                if (this.renderRoot)
                    this.$container.appendChild(oHtml);
                else {
                    while (oHtml.childNodes.length)
                        this.$container.appendChild(oHtml.childNodes[0]);
                }

                return true;
            }
        }
        

        //Checking Cache...
        if (!this.cache[id]) return false;

        //Get Fragment and clear Cache Item
        var fragment = this.cache[id];

        this.documentId = fragment.documentId;
        this.cacheId = id;
        this.xmlRoot = xmlNode;//fragment.xmlRoot;
        
        
        this.setProperty("root", this.xmlRoot);
        

        this.clearCacheItem(id);

        this.$setCurrentFragment(fragment);

        return true;
    };

    /*
     * Sets cache element and its ID.
     *
     * @param {String}           id        The id of the cache element to be stored.
     * @param {DocumentFragment} fragment  The data to be stored.
     * @private
     */
    function setCache(id, fragment) {
        if (!this.caching) return;

        this.cache[id] = fragment;
    };

    /*
     * Finds HTML presentation node in cache by ID.
     *
     * @param {String}  id  The id of the HTMLElement which is looked up.
     * @return {HTMLElement} The HTMLElement found. When no element is found, `null` is returned.
     */
    this.$findHtmlNode = function(id) {
        var node = this.$pHtmlDoc.getElementById(id);
        if (node) return node;

        for (var prop in this.cache) {
            if (this.cache[prop] && this.cache[prop].nodeType) {
                node = this.cache[prop].getElementById(id);
                if (node) return node;
            }
        }

        return null;
    };

    /**
     * Removes an item from the cache.
     *
     * @param {String}  id       The id of the HTMLElement which is looked up.
     * @param {Boolean} [remove] Specifies whether to destroy the Fragment.
     * @see baseclass.databinding.method.clear
     * @private
     */
    this.clearCacheItem = function(id, remove) {
        this.cache[id].documentId = 
        this.cache[id].cacheId = 
        this.cache[id].xmlRoot = null;

        if (remove)
            apf.destroyHtmlNode(this.cache[id]);

        this.cache[id] = null;
    };

    /*
     * Removes all items from the cache
     *
     * @see baseclass.databinding.method.clearCacheItem
     * @private
     */
    this.clearAllCache = function(){
        for (var prop in this.cache) {
            if (this.cache[prop])
                this.clearCacheItem(prop, true);
        }
    };

    /**
     * Gets the cache item by its id
     *
     * @param {String} id  The id of the HTMLElement which is looked up.
     * @see baseclass.databinding.method.clearCacheItem
     * @private
     */
    this.getCacheItem = function(id) {
        return this.cache[id];
    };

    /*
     * Checks whether a cache item exists by the specified id
     *
     * @param {String} id  the id of the cache item to check.
     * @see baseclass.databinding.method.clearCacheItem
     * @private
     */
    this.$isCached = function(id) {
        return this.cache[id] || this.cacheId == id ? true : false;
    };
    
    if (!this.$getCurrentFragment) {
        this.$getCurrentFragment = function(){
            var fragment = this.$container.ownerDocument.createDocumentFragment();
    
            while (this.$container.childNodes.length) {
                fragment.appendChild(this.$container.childNodes[0]);
            }
    
            return fragment;
        };
    
        this.$setCurrentFragment = function(fragment) {
            this.$container.appendChild(fragment);
    
            if (!apf.window.hasFocus(this) && this.blur)
                this.blur();
        };
    }
    
    /**
     * @attribute {Boolean} caching Sets or gets whether caching is enabled for this element.
     */
    this.$booleanProperties["caching"] = true;
    this.$supportedProperties.push("caching");

    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        //Remove all cached Items
        this.clearAllCache();
    });
};

apf.GuiElement.propHandlers["caching"] = function(value) {
    if (!apf.isTrue(value)) return;
    
    if (!this.hasFeature(apf.__CACHE__))
        this.implement(apf.Cache);
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__RENAME__ = 1 << 10;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have the rename features. Rename is triggered by
 * pressing F2 on an item or by clicking once on an already selected item. This
 * will show an input element in place where the user can change the name of the
 * item to a new one. When the caption is changed the {@link term.datanode data node} is
 * changed accordingly.
 * 
 * #### Example
 *
 * This example shows a list containing products. Only products that have the
 * editable attribute set to 1 can be renamed by the user.
 * 
 * ```xml
 *  <a:model id="mdlTest">
 *      <data>
 *          <product name="TV" />
 *          <product name="LCD" editable="1" />
 *      </data>
 *  </a:model>
 *  <a:list id="list" model="mdlTest" width="200">
 *      <a:each match="[product]">
 *          <a:caption match="[@name]" />
 *      </a:each>
 *      <a:actions>
 *          <a:rename
 *            match = "[product[@editable='1']]"
 *            set = "rename.php" />
 *      </a:actions>
 *  </a:list>
 *       
 *  <a:button
 *    caption = "Rename"
 *    onclick = "list.startRename()" />
 * ```
 *
 * @class apf.Rename
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.5
 */
/**
 * @event stoprename Fires when a rename action is cancelled.
 *
 */
apf.Rename = function(){
    this.$regbase = this.$regbase|apf.__RENAME__;

    this.canrename = false;
    this.$renameSubject =
    this.renameTimer = 
    this.lastCursor = null;
    
    /**
     * @attribute  {Boolean}  rename  Sets or gets whether the user can start renaming rendered nodes in this element.
     */
    this.$booleanProperties["canrename"] = true;
    this.$booleanProperties["autorename"] = true;
    this.$supportedProperties.push("canrename", "autorename");

    

    /**
     * Changes the data presented as the caption of a specified {@link term.datanode data node}.
     * 
     * If none are specified, the indicated node is used.
     *
     * @action
     * @param {XMLElement}  xmlNode The element to change the caption of.
     * @param {String}      value   The value to set as the caption of the {@link term.datanode data node}.
     */
    this.rename = function(xmlNode, value) {
        if (!xmlNode)
            xmlNode = this.caret || this.selected;

        if (!xmlNode) return;

        return this.$executeSingleValue("rename", "caption", xmlNode, value);
    };

    // @todo Doc params
    /**
     * Starts the rename process with a delay, allowing for cancellation when
     * necesary. Cancellation is necesary for instance, when double click was
     * intended or a dragdrop operation.
     *
     */
    this.startDelayedRename = function(e, time, userAction) {
        clearTimeout(this.renameTimer);
        
        if (e && (e.button == 2 || e.ctrlKey || e.shiftKey) 
          || userAction && this.disabled)
            return;

        this.renameTimer = $setTimeout('apf.lookup('
            + this.$uniqueId + ').startRename()', time || 400);
    };
    
    // @todo Doc params
    /**
     * Starts the rename process by displaying an input box at the position
     * of the item that can be renamed by the user.
     *
     */
    this.startRename = function(force, startEmpty, userAction) {
        if (!force && (this.renaming || !this.canrename 
          || !this.$startAction("rename", this.caret 
          || this.selected, this.stopRename))
          || userAction && this.disabled)
            return false;

        if (!this.hasFocus())
            this.focus(null, null, true);

        clearTimeout(this.renameTimer);

        var elCaption = this.$getCaptionElement
            ? this.$getCaptionElement()
            : this.$caret || this.$selected;

        if (!elCaption) 
            return this.stopRename();
        
        this.renaming = true;
        this.$renameSubject = this.caret || this.selected;

        var wdt = elCaption.offsetWidth;
        this.lastCursor = elCaption.style.cursor;
        elCaption.style.cursor = "text";
        elCaption.parentNode.replaceChild(this.$txt, elCaption);
        elCaption.host = this;

        if (apf.isTrue(this.$getOption("main", "scalerename"))) {
            var diff = apf.getWidthDiff(this.$txt);
            this.$txt.style.width = (wdt - diff - 3) + "px";
        }

        this.$replacedNode = elCaption;
        var xmlNode = this.$getCaptionXml
            ? this.$getCaptionXml(this.$renameSubject)
            : this.$getDataNode("caption", this.$renameSubject);

        //xmlNode.nodeType >= 2 && xmlNode.nodeType <= 4
        var value =  startEmpty || !xmlNode
            ? ""
            : (xmlNode.nodeType != 1
                ? unescape(xmlNode.nodeValue) //decodeURI( - throws an error when using % in a non expected way
                : (apf.isOnlyChild(xmlNode.firstChild, [3,4])
                    ? apf.queryValue(xmlNode)
                    : this.$applyBindRule("caption", this.$renameSubject))) || "";

        if (apf.hasContentEditable) {
            if (this.$multiLineRename)
                this.$txt.innerHTML = apf.htmlCleaner.prepare(value.trim()
                    .replace(/</g, "&lt;")
                    .replace(/>/g, "&gt;")
                    .replace(/\n/g, "<br />"));
            else
                this.$txt.innerHTML = value.replace(/</g, "&lt;")
                  || apf.hasContentEditableContainerBug && "<br>" || "";
        }
        else 
            this.$txt.value = value;

        this.$txt.unselectable = "Off";
        this.$txt.host = this;

        //this.$txt.focus();
        var txt = this.$txt;
        var f = function(){
            try {
                txt.focus();
                txt.select();
            }
            catch (e) {}
        };
        if (apf.isIE) f() 
        else $setTimeout(f);
    };

    // @todo Doc params
    /**
     * Stops the renaming process and changes the data according to the set value.
     * Cancel the renaming process without changing data.
     *
     */
    this.stopRename = function(contextXml, success) {
        clearTimeout(this.renameTimer);

        if (!this.renaming || contextXml && contextXml != this.$renameSubject
          || !this.$replacedNode)
            return false;
        
        this.renaming = false;

        if (this.$txt.parentNode && this.$txt.parentNode.nodeType == 1) {
            if (apf.isIE8 || apf.isIE7Emulate)
                this.$txt.blur();
            
            this.$txt.parentNode.replaceChild(this.$replacedNode, this.$txt);
        }

        if (this.$replacedNode) {
            this.$replacedNode.style.cursor = this.lastCursor || "";
            this.$replacedNode.host = null;
        }
        
        //apf.hasContentEditable ??
        if (this.$multiLineRename) {
            var value = apf.html_entity_decode(
                apf.htmlCleaner.parse(this.$txt.innerHTML, true)
                            .replace(/<br \/>/g, "")
                            .replace(/<\/?p>/g, ""));
        }
        else {
            var value = this.$txt[apf.hasContentEditable ? "innerText" : "value"]
                            .replace(/<.*?nobr>/gi, "").replace(/\n$/, ""); //last replace is for chrome
        }

        if (!success || (this.$validateRename && !this.$validateRename(value))) {
            this.dispatchEvent("stoprename");
            this.$stopAction("rename");
        }
        else {
            //this.$selected.innerHTML = this.$txt.innerHTML;
            if (this.rename(this.$renameSubject, value) !== false) {
                if (this.$replacedNode)
                    this.$replacedNode.innerHTML = value.replace(/</g, "&lt;").replace(/\r?\n/g, "<br />");
            }
        }

        if (!this.renaming) {
            this.$renameSubject = null;
            this.$replacedNode = null;
            this.$txt.style.width = "";
        }

        return true;
    };

    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode;

        if (this.renaming) {
            if (key == 27 || this.$multiLineRename && e.ctrlKey && key == 13 
              || !this.$multiLineRename && key == 13) {
                this.stopRename(null, key == 13 && !this.$autocomplete);
                e.cancelBubble = true;
                return false;
            }
            else if (apf.hasContentEditableContainerBug && key == 8
              && this.$txt.innerHTML == "<br>") {
                e.preventDefault();
            }

            return;
        }

        //F2
        if (key == 113) {
            if (this.$tempsel)
                this.$selectTemp();

            if (this.caret != this.selected) {
                if (this.multiselect || this.isSelected(this.caret)) {
                    this.selected = this.caret;
                    this.$selected = this.$caret;
                }
                else
                    this.select(this.caret, true);
            }

            this.startRename();

            return false;
        }
    }, true);
    

    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        this.$txt.refCount--;

        if (!this.$txt.refCount) {
            this.$txt.host = 
            this.$txt.onmouseover =
            this.$txt.onmousedown =
            this.$txt.select = 
            this.$txt.onfocus = 
            this.$txt.onblur = null;
        }
        this.$txt = null;
    });
    
    this.$init(apf.Rename.initEditableArea);
};

apf.Rename.initEditableArea = function(){
    if (!(this.$txt = document.getElementById("txt_rename"))) {
        if (apf.hasContentEditable) {
            this.$txt = document.createElement("DIV");
            this.$txt.contentEditable = true;
            if (apf.isIE6)
                this.$txt.style.width = "1px";
            //this.$txt.canHaveHTML = false;
        }
        else {
            this.$txt = document.createElement("input");
            this.$txt.id = "txt_rename";
            this.$txt.autocomplete = false;
        }
    
        
        this.$txt.spellcheck = false;
        this.$txt.refCount = 0;
        this.$txt.id = "txt_rename";
        //this.$txt.style.whiteSpace = "nowrap";
        apf.importCssString("#txt_rename{white-space:nowrap}");
        this.$txt.onselectstart = function(e) {
            (e || event).cancelBubble = true;
        };
    
        this.$txt.onmouseover = 
        this.$txt.onmouseout = 
        this.$txt.oncontextmenu =
        //this.$txt.onkeydown = 
        this.$txt.onmouseup = 
        this.$txt.ondblclick = 
        this.$txt.onmousedown = function(e) { 
            apf.stopPropagation(e || event)
        };
    
        this.$txt.onkeyup = function(e) {
            //(e || event).cancelBubble = true;
            
            if (!this.host.$autocomplete)
                return;
    
            this.host.$lookup(this[apf.hasContentEditable ? "innerHTML" : "value"]);
        }
    
        var sel;
        this.$txt.select = function(){
            if (!apf.hasMsRangeObject) {
                if (window.getSelection && document.createRange) {
                    var sel = window.getSelection();
                    sel.removeAllRanges();
                    var r = document.createRange();
                    r.setStart(this.firstChild, 0);
                    var lastIndex = this.firstChild.nodeValue.lastIndexOf(".");
                    r.setEnd(this.firstChild, lastIndex > -1 ? lastIndex : this.firstChild.nodeValue.length);
                    sel.addRange(r)
                }
                else {
                    (sel || (sel = new apf.selection())).selectNode(this);
                }
                return;
            }
    
            var r = document.selection.createRange();
            //r.moveEnd("character", this.$ext.innerText.length);
            try {
                r.moveToElementText(this);
    
                if (apf.isFalse(this.host.$getOption("main", "selectrename"))
                  || typeof this.host.$renameStartCollapse != "undefined") //@todo please deprecate renameStartCollapse
                    r.collapse(this.host.$renameStartCollapse);
            } catch (e) {} //BUG!!!!
    
            r.select();
        };
    
        
    
        this.$txt.onblur = function(){
            //if (apf.isGecko)
                //return; //bug in firefox calling onblur too much
            //if (apf.isChrome && !arguments.callee.caller)
                //return;

            
    
            if (this.host.$autocomplete)
                return;
    
            this.host.stopRename(null, true);
        };
    }
    
    this.$txt.refCount++;
}







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__ALIGNMENT__ = 1 << 29;





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Baseclass of an element that has one or two states and can be clicked on to
 * trigger an action (_i.e._ {@link apf.button} or {@link apf.checkbox}).
 *
 * @class apf.BaseButton
 * @baseclass
 * @author      Abe Ginner
 * @version     %I%, %G%
 * @since       0.8
 * @inherits apf.StandardBinding
 */
/**
 * @event click Fires when the user presses a mouse button while over this element...and then lets the mousebutton go. 
 */
apf.BaseButton = function(){
    this.$init(true);
};

(function() {
    
    this.implement(apf.ChildValue);
    
    
    this.$refKeyDown = // Number of keys pressed.
    this.$refMouseDown = 0;     // Mouse button down?
    this.$mouseOver = // Mouse hovering over the button?
    this.$mouseLeft = false; // Has the mouse left the control since pressing the button.

    // *** Properties and Attributes *** //

    /**
     * @attribute {String} background Sets or gets a multistate background. The arguments
     * are seperated by pipes (`'|'`) and are in the order of:'imagefilename|mapdirection|nrofstates|imagesize'
     *
     * - The `mapdirection` argument may have the value of `'vertical'` or `'horizontal'`.
     * - The `nrofstates` argument specifies the number of states the iconfile contains:
     *     - 1: normal
     *     - 2: normal, hover
     *     - 3: normal, hover, down
     *     - 4: normal, hover, down, disabled
     * - The `imagesize` argument specifies how high or wide each icon is inside the
     * map, depending on the `mapdirection` argument.
     * {: #multiStateDoc}
     * 
     * #### Example
     * 
     * Here's a three state picture where each state is 16px high, vertically spaced:
     * 
     * ```xml
     * background="threestates.gif|vertical|3|16"
     * ```
     */
    this.$propHandlers["background"] = function(value) {
        var oNode = this.$getLayoutNode("main", "background", this.$ext);
        
        if (!oNode) return;
        

        if (value) {
            var b = value.split("|");
            this.$background = b.concat(["vertical", 2, 16].slice(b.length - 1));

            oNode.style.backgroundImage = "url(" + this.mediaPath + b[0] + ")";
            oNode.style.backgroundRepeat = "no-repeat";
        }
        else {
            oNode.style.backgroundImage = "";
            oNode.style.backgroundRepeat = "";
            this.$background = null;
        }
    };

    // *** Keyboard Support *** //

    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode;
        //var ctrlKey = e.ctrlKey;  << UNUSED
        //var shiftKey = e.shiftKey; << UNUSED

        switch (key) {
            case 13:
                if (this.localName != "checkbox")
                    this.$ext.onmouseup(e.htmlEvent, true);
                break;
            case 32:
                if (!e.htmlEvent.repeat) { // Only when first pressed, not on autorepeat.
                    this.$refKeyDown++;
                    this.$updateState(e.htmlEvent);
                }
                return false;
        }
    }, true);

    this.addEventListener("keyup", function(e) {
        var key = e.keyCode;

        switch (key) {
            case 32:
                this.$refKeyDown--;

                if (this.$refKeyDown < 0) {
                    this.$refKeyDown = 0;
                    return false;
                }

                if (this.$refKeyDown + this.$refMouseDown == 0 && !this.disabled)
                    this.$ext.onmouseup(e, true);

                this.$updateState(e);
                return false;
        }
    }, true);
    

    // *** Private state handling methods *** //

    this.states = {
        "Out"   : 1,
        "Over"  : 2,
        "Down"  : 3
    };

    this.$updateState = function(e, strEvent) {
        if (e.reset) { //this.disabled || 
            this.$refKeyDown = 0;
            this.$refMouseDown = 0;
            this.$mouseOver = false;
            return false;
        }

        if (this.$refKeyDown > 0
          || (this.$refMouseDown > 0 && (this.$mouseOver || (!apf.isIE && this.$ext === e.currentTarget)))
          || (this.isBoolean && this.value)) {
            this.$setState("Down", e, strEvent);
        }
        else if (this.$mouseOver) {
            this.$setState("Over", e, strEvent);
        }
        else
            this.$setState("Out", e, strEvent);
    };

    this.$setupEvents = function() {
        if (this.editable)
            return;
        
        var _self = this;

        this.$ext.onmousedown = function(e) {
            e = e || window.event;

            if (_self.$notfromext && (e.srcElement || e.target) == this)
                return;

            _self.$refMouseDown = 1;
            _self.$mouseLeft = false;
            
            if (_self.disabled)
                return;

            if (!apf.isIE) { // && (apf.isGecko || !_self.submenu) Causes a focus problem for menus
                if (_self.value)
                    apf.stopEvent(e);
                else
                    apf.cancelBubble(e);
            }
            
            _self.$updateState(e, "mousedown");
        };
        
        this.$ext.onmouseup = function(e, force) {
            e = e || window.event;
            //if (e)  e.cancelBubble = true;
            if (_self.disabled || !force && ((!_self.$mouseOver && (!apf.isIE && this !== e.currentTarget)) || !_self.$refMouseDown))
                return;

            _self.$refMouseDown = 0;
            _self.$updateState(e, "mouseup");

            // If this is coming from a mouse click, we shouldn't have left the button.
            if (_self.disabled || (e && e.type == "click" && _self.$mouseLeft == true))
                return false;

            // If there are still buttons down, this is not a real click.
            if (_self.$refMouseDown + _self.$refKeyDown)
                return false;

            if (_self.$clickHandler && _self.$clickHandler())
                _self.$updateState (e || event, "click");
            else
                _self.dispatchEvent("click", {htmlEvent : e});

            return false;
        };

        this.$ext.onmousemove = function(e) {
            if ((!_self.$mouseOver || _self.$mouseOver == 2)) {
                e = e || window.event;

                if (_self.$notfromext && (e.srcElement || e.target) == this)
                    return;

                _self.$mouseOver = true;
                
                if (!_self.disabled)
                    _self.$updateState(e, "mouseover");
            }
        };

        this.$ext.onmouseout = function(e) {
            e = e || window.event;

            //Check if the mouse out is meant for us
            var tEl = e.explicitOriginalTarget || e.toElement;
            if (apf.isChildOf(this, tEl)) //this == tEl ||
                return;

            _self.$mouseOver = false;
            _self.$refMouseDown = 0;
            _self.$mouseLeft = true;
            
            if (!_self.disabled)
                _self.$updateState(e, "mouseout");
        };

        

        if (apf.hasClickFastBug)
            this.$ext.ondblclick = this.$ext.onmouseup;
    };

    this.$doBgSwitch = function(nr) {
        if (this.background && (this.$background[2] >= nr || nr == 4)) {
            if (nr == 4)
                nr = this.$background[2] + 1;

            var strBG = this.$background[1] == "vertical"
                ? "0 -" + (parseInt(this.$background[3]) * (nr - 1)) + "px"
                : "-"   + (parseInt(this.$background[3]) * (nr - 1)) + "px 0";

            this.$getLayoutNode("main", "background",
                this.$ext).style.backgroundPosition = strBG;
        }
    };

    // *** Focus Handling *** //

    this.$focus = function(){
        if (!this.$ext)
            return;

        this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
    };

    this.$blur = function(e) {
        if (!this.$ext)
            return; //FIREFOX BUG!

        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
        /*this.$refKeyDown = 0;
        this.$refMouseDown = 0;
        this.$mouseLeft = true;*/

        
        /*if (this.submenu) {
            if (this.value) {
                this.$setState("Down", {}, "mousedown");
                this.$hideMenu();
            }
        }*/
        

        if (e)
            this.$updateState({});//, "onblur"
    };
    
    this.addEventListener("prop.disabled", function(e) {
        this.$refKeyDown = 
        this.$refMouseDown = 0;
        //this.$mouseOver = 
        //this.$mouseLeft = false;
    });

    /*** Clearing potential memory leaks ****/

    this.$destroy = function(skinChange) {
        if (!skinChange && this.$ext) {
            this.$ext.onmousedown = this.$ext.onmouseup = this.$ext.onmouseover =
            this.$ext.onmouseout = this.$ext.onclick = this.$ext.ondblclick = null;
        }
    };

}).call(apf.BaseButton.prototype = new apf.StandardBinding());






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * The baseclass of elements that allows the user to select one or more items
 * out of a list.
 *
 * @class apf.BaseList
 * @baseclass
 *
 * @inherits apf.MultiSelect
 * @inherits apf.Cache
 * @inherits apf.DataAction
 * @inheritsElsewhere apf.XForms
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 * @default_private
 *
 */
/**
 * @binding caption  Determines the caption of a node.
 */
/**
 * @binding icon     Determines the icon of a node. 
*
 * This binding rule is used
 * to determine the icon displayed when using a list skin. The {@link baseclass.baselist.binding.image image binding}
 * is used to determine the image in the thumbnail skin.
 */
/**
 * @binding image    Determines the image of a node. 
 * 
 * This binding rule is used
 * to determine the image displayed when using a thumbnail skin. The {@link baseclass.baselist.binding.icon icon binding}
 * is used to determine the icon in the list skin.
 *
 * #### Example
 *
 * In this example, the image URL is read from the thumbnail attribute of the data node.
 *
 * ```xml
 *  <a:thumbnail>
 *      <a:model>
 *          <data>
 *              <image caption="Thumb 1" thumbnail="img1" />
 *              <image caption="Thumb 2" thumbnail="img2" />
 *              <image caption="Thumb 3" />
 *          </data>
 *      </a:model>
 *      <a:bindings>
 *          <a:caption match="[@caption]" />
 *          <a:image match="[@thumbnail]" value="images/slideshow_img/[@thumbnail]_small.jpg" />
 *          <a:image value="images/slideshow_img/img29_small.jpg" />
 *          <a:each match="[image]" />
 *      </a:bindings>
 *  </a:thumbnail>
 * ```
 *
 */
/**
 * @binding css      Determines a CSS class for a node.
 *
 * #### Example
 *
 * In this example a node is bold when the folder contains unread messages:
 *
 * ```xml
 *  <a:tree>
 *      <a:model>
 *          <data>
 *              <folder caption="Folder 1">
 *                  <message unread="true" caption="message 1" />
 *              </folder>
 *              <folder caption="Folder 2" icon="email.png">
 *                  <message caption="message 2" />
 *              </folder>
 *              <folder caption="Folder 3">
 *                  <message caption="message 3" />
 *                  <message caption="message 4" />
 *              </folder>
 *          </data>
 *      </a:model>
 *      <a:bindings>
 *          <a:caption match="[@caption]" />
 *          <a:css match="[message[@unread]]" value="highlighUnread" />
 *          <a:icon match="[@icon]" />
 *          <a:icon match="[folder]" value="Famfolder.gif" />
 *          <a:each match="[folder|message]" />
 *      </a:bindings>
 *  </a:tree>
 * ```
 *
 */
/**
 * @binding tooltip  Determines the tooltip of a node. 
 */
/**
 * @event notunique Fires when the `more` attribute is set and an item is added that has a caption that already exists in the list.
 * @param {Object} e The standard event object, with the following properties:
 *                    - value ([[String]]): The value that was entered
 */
apf.BaseList = function(){
    this.$init(true);
    
    
    this.$dynCssClasses = [];
    
    
    this.listNodes = [];
};

(function() {
    
    this.implement(
        
        apf.Cache,
        
        
        apf.DataAction,
        
        
        apf.K
    );
    

    // *** Properties and Attributes *** //

    this.$focussable = true; // This object can get the focus
    this.$isWindowContainer = -1;
    
    this.multiselect = true; // Initially Disable MultiSelect

    /**
     * @attribute {String} fill Sets or gets the set of items that should be loaded into this
     * element. Items are seperated by a comma (`,`). Ranges are specified by a start and end value seperated by a dash (`-`).
     *
     * #### Example
     *
     * This example loads a list with items starting at 1980 and ending at 2050. It also loads several other items and ranges.
     *
     * ```xml
     *  <a:dropdown fill="1980-2050" />
     *  <a:dropdown fill="red,green,blue,white" />
     *  <a:dropdown fill="None,100-110,1000-1100" /> <!-- 101, 102...110, 1000,1001, e.t.c. -->
     *  <a:dropdown fill="01-10" /> <!-- 01, 02, 03, 04, e.t.c. -->
     *  <a:dropdown fill="1-10" /> <!-- // 1 2 3 4 e.t.c. -->

     * ```
     */
    this.$propHandlers["fill"] = function(value) {
        if (value)
            this.loadFillData(this.getAttribute("fill"));
        else
            this.clear();
    };
    
    
    
    /**
     * @attribute {String} mode Sets or gets the way this element interacts with the user.
     *  
     * The following values are possible:
     *
     *   - `check`: the user can select a single item from this element. The selected item is indicated.
     *   - `radio`: the user can select multiple items from this element. Each selected item is indicated.
     */
    this.$mode = 0;
    this.$propHandlers["mode"] = function(value) {
        if ("check|radio".indexOf(value) > -1) {
            if (!this.hasFeature(apf.__MULTICHECK__))
                this.implement(apf.MultiCheck);
            
            this.addEventListener("afterrename", $afterRenameMode); //what does this do?
            
            this.multicheck = value == "check"; //radio is single
            this.$mode = this.multicheck ? 1 : 2;
        }
        else {
            //@todo undo actionRules setting
            this.removeEventListener("afterrename", $afterRenameMode);
            //@todo unimplement??
            this.$mode = 0;
        }
    };
    
    //@todo apf3.0 retest this completely
    function $afterRenameMode(){
    }
    
    

    // *** Keyboard support *** //

    

    //Handler for a plane list
    this.$keyHandler = function(e) {
        var key = e.keyCode,
            ctrlKey = e.ctrlKey,
            shiftKey = e.shiftKey,
            selHtml = this.$caret || this.$selected;

        if (e.returnValue == -1 || !selHtml || this.renaming) //@todo how about allowdeselect?
            return;

        var selXml = this.caret || this.selected,
            oExt = this.$ext,
            // variables used in the switch statement below:
            node, margin, items, lines, hasScroll, hasScrollX, hasScrollY;

        switch (key) {
            case 13:
                if (this.$tempsel)
                    this.$selectTemp();

                if (this.ctrlselect == "enter")
                    this.select(this.caret, true);

                this.choose(this.selected);
                break;
            case 32:
                if (ctrlKey || !this.isSelected(this.caret))
                    this.select(this.caret, ctrlKey);
                break;
            case 109:
            case 46:
                //DELETE
                if (this.disableremove)
                    return;

                if (this.$tempsel)
                    this.$selectTemp();

                this.remove();
                break;
            case 36:
                //HOME
                var node = this.getFirstTraverseNode();
                
                
                if (this.hasFeature(apf.__VIRTUALVIEWPORT__))
                    return this.$viewport.scrollIntoView(node);
                
                    
                this.select(node, false, shiftKey);
                this.$container.scrollTop = 0;
                break;
            case 35:
                //END
                var node = this.getLastTraverseNode();
                
                
                if (this.hasFeature(apf.__VIRTUALVIEWPORT__))
                    return this.$viewport.scrollIntoView(node, true);
                
                
                this.select(node, false, shiftKey);
                this.$container.scrollTop = this.$container.scrollHeight;
                break;
            case 107:
                //+
                if (this.more)
                    this.startMore();
                break;
            case 37:
                //LEFT
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;
                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                items = selHtml.offsetWidth
                    ? Math.floor((oExt.offsetWidth
                        - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth
                        + margin[1] + margin[3]))
                    : 1;

                //margin = apf.getBox(apf.getStyle(selHtml, "margin"));

                node = this.getNextTraverseSelected(node, false);
                if (node)
                    this.$setTempSelected(node, ctrlKey, shiftKey, true);
                else
                    return;

                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop < oExt.scrollTop) {
                    oExt.scrollTop = Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items
                        ? 0
                        : selHtml.offsetTop - margin[0];
                }
                break;
            case 38:
                //UP
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;

                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScroll = oExt.scrollHeight > oExt.offsetHeight;
                items = selHtml.offsetWidth
                    ? Math.floor((oExt.offsetWidth
                        - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth
                        + margin[1] + margin[3]))
                    : 1;

                node = this.getNextTraverseSelected(node, false, items);
                if (node)
                    this.$setTempSelected (node, ctrlKey, shiftKey, true);
                else
                    return;
                
                
                if (this.hasFeature(apf.__VIRTUALVIEWPORT__))
                    return this.$viewport.scrollIntoView(node);
                
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop < oExt.scrollTop) {
                    oExt.scrollTop = Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items
                        ? 0
                        : selHtml.offsetTop - margin[0];
                }
                break;
            case 39:
                //RIGHT
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;
                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                node = this.getNextTraverseSelected(node, true);
                if (node)
                    this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return;

                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop + selHtml.offsetHeight
                  > oExt.scrollTop + oExt.offsetHeight) {
                    oExt.scrollTop = selHtml.offsetTop
                        - oExt.offsetHeight + selHtml.offsetHeight
                        + margin[0];
                }
                break;
            case 40:
                //DOWN
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;

                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScroll = oExt.scrollHeight > oExt.offsetHeight;
                items = selHtml.offsetWidth
                    ? Math.floor((oExt.offsetWidth
                        - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth
                        + margin[1] + margin[3]))
                    : 1;

                node = this.getNextTraverseSelected(node, true, items);
                if (node)
                    this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return;

                
                if (this.hasFeature(apf.__VIRTUALVIEWPORT__))
                    return this.$viewport.scrollIntoView(node, true);
                
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop + selHtml.offsetHeight
                  > oExt.scrollTop + oExt.offsetHeight) { // - (hasScroll ? 10 : 0)
                    oExt.scrollTop = selHtml.offsetTop
                        - oExt.offsetHeight + selHtml.offsetHeight
                        + margin[0]; //+ (hasScroll ? 10 : 0)
                }
                break;
            case 33:
                //PGUP
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;

                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScrollY = oExt.scrollHeight > oExt.offsetHeight;
                hasScrollX = oExt.scrollWidth > oExt.offsetWidth;
                items = Math.floor((oExt.offsetWidth
                    - (hasScrollY ? 15 : 0)) / (selHtml.offsetWidth
                    + margin[1] + margin[3]));
                lines = Math.floor((oExt.offsetHeight
                    - (hasScrollX ? 15 : 0)) / (selHtml.offsetHeight
                    + margin[0] + margin[2]));

                node = this.getNextTraverseSelected(node, false, items * lines);
                if (!node)
                    node = this.getFirstTraverseNode();
                if (node)
                    this.$setTempSelected (node, ctrlKey, shiftKey, true);
                else
                    return;
                
                
                if (this.hasFeature(apf.__VIRTUALVIEWPORT__))
                    return this.$viewport.scrollIntoView(node);
                
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop < oExt.scrollTop) {
                    oExt.scrollTop = Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items
                        ? 0
                        : selHtml.offsetTop - margin[0];
                }
                break;
            case 34:
                //PGDN
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;

                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScrollY = oExt.scrollHeight > oExt.offsetHeight;
                hasScrollX = oExt.scrollWidth > oExt.offsetWidth;
                items = Math.floor((oExt.offsetWidth - (hasScrollY ? 15 : 0))
                    / (selHtml.offsetWidth + margin[1] + margin[3]));
                lines = Math.floor((oExt.offsetHeight - (hasScrollX ? 15 : 0))
                    / (selHtml.offsetHeight + margin[0] + margin[2]));

                node = this.getNextTraverseSelected(selXml, true, items * lines);
                if (!node)
                    node = this.getLastTraverseNode();
                if (node)
                    this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return;
                
                
                if (this.hasFeature(apf.__VIRTUALVIEWPORT__))
                    return this.$viewport.scrollIntoView(node, true);
                
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop + selHtml.offsetHeight
                  > oExt.scrollTop + oExt.offsetHeight) { // - (hasScrollY ? 10 : 0)
                    oExt.scrollTop = selHtml.offsetTop
                        - oExt.offsetHeight + selHtml.offsetHeight
                        + margin[0]; //+ 10 + (hasScrollY ? 10 : 0)
                }
                break;

            default:
                if (key == 65 && ctrlKey) {
                    this.selectAll();
                }
                else if (this.$hasBindRule("caption")) {
                    if (!this.xmlRoot || this.autorename) return;

                    //this should move to a onkeypress based function
                    if (!this.lookup || new Date().getTime()
                      - this.lookup.date.getTime() > 300) {
                        this.lookup = {
                            str: "",
                            date: new Date()
                        };
                    }

                    this.lookup.str += String.fromCharCode(key);

                    var nodes = this.getTraverseNodes(); //@todo start at current indicator
                    for (var v, i = 0; i < nodes.length; i++) {
                        v = this.$applyBindRule("caption", nodes[i]);
                        if (v && v.substr(0, this.lookup.str.length)
                          .toUpperCase() == this.lookup.str) {

                            if (!this.isSelected(nodes[i])) {
                                this.select(nodes[i]);
                            }

                            if (selHtml) {
                                this.$container.scrollTop = selHtml.offsetTop
                                    - (this.$container.offsetHeight
                                    - selHtml.offsetHeight) / 2;
                            }
                            return;
                        }
                    }
                    return;
                }
                break;
        }

        this.lookup = null;
        return false;
    };

    

    // *** Private databinding functions *** //

    this.$deInitNode = function(xmlNode, htmlNode) {
        if (!htmlNode) return;

        //Remove htmlNodes from tree
        htmlNode.parentNode.removeChild(htmlNode);
    };

    this.$updateNode = function(xmlNode, htmlNode, noModifier) {
        //Update Identity (Look)
        var elIcon = this.$getLayoutNode("item", "icon", htmlNode);

        if (elIcon) {
            if (elIcon.nodeType == 1) {
                elIcon.style.backgroundImage = "url(" + 
                  apf.getAbsolutePath(this.iconPath,
                      this.$applyBindRule("icon", xmlNode)) + ")";
            }
            else {
                elIcon.nodeValue = apf.getAbsolutePath(this.iconPath,
                    this.$applyBindRule("icon", xmlNode));
            }
        }
        else {
            //.style.backgroundImage = "url(" + this.$applyBindRule("image", xmlNode) + ")";
            var elImage = this.$getLayoutNode("item", "image", htmlNode);
            if (elImage) {
                if (elImage.nodeType == 1) {
                    elImage.style.backgroundImage = "url(" + 
                        apf.getAbsolutePath(apf.hostPath,
                            this.$applyBindRule("image", xmlNode)) + ")";
                }
                else {
                    elImage.nodeValue = apf.getAbsolutePath(apf.hostPath, 
                        this.$applyBindRule("image", xmlNode));
                }
            }
        }

        var elCaption = this.$getLayoutNode("item", "caption", htmlNode);
        if (elCaption) {
            if (elCaption.nodeType == 1) {
                
                    elCaption.innerHTML = this.$applyBindRule("caption", xmlNode);
            }
            else
                elCaption.nodeValue = this.$applyBindRule("caption", xmlNode);
        }
        
        
        //@todo
        

        htmlNode.title = this.$applyBindRule("title", xmlNode) || "";

        
        var cssClass = this.$applyBindRule("css", xmlNode);

        if (cssClass || this.$dynCssClasses.length) {
            this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses);
            if (cssClass && !this.$dynCssClasses.contains(cssClass)) {
                this.$dynCssClasses.push(cssClass);
            }
        }
        

        if (!noModifier && this.$updateModifier)
            this.$updateModifier(xmlNode, htmlNode);
    };

    this.$moveNode = function(xmlNode, htmlNode) {
        if (!htmlNode) return;

        var oPHtmlNode = htmlNode.parentNode;
        var nNode = this.getNextTraverse(xmlNode); //@todo could optimize because getTraverseNodes returns array indexOf
        var beforeNode = nNode
            ? apf.xmldb.findHtmlNode(nNode, this)
            : null;

        oPHtmlNode.insertBefore(htmlNode, beforeNode);
        //if(this.emptyMessage && !oPHtmlNode.childNodes.length) this.setEmpty(oPHtmlNode);
    };

    this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode) {
        //Build Row
        this.$getNewContext("item");
        var oItem = this.$getLayoutNode("item"),
            elSelect = this.$getLayoutNode("item", "select"),
            elIcon = this.$getLayoutNode("item", "icon"),
            elImage = this.$getLayoutNode("item", "image"),
            //elCheckbox = this.$getLayoutNode("item", "checkbox"), // NOT USED
            elCaption = this.$getLayoutNode("item", "caption");

        oItem.setAttribute("id", Lid);

        elSelect.setAttribute("onmouseover",   "var o = apf.lookup(" + this.$uniqueId 
            + "); o.$setStyleClass(this, 'hover', null, true);");
        elSelect.setAttribute("onselectstart", "return false;");
        elSelect.setAttribute("style",         (elSelect.getAttribute("style") || "") 
            + ";user-select:none;-moz-user-select:none;-webkit-user-select:none;");

        if (this.hasFeature(apf.__RENAME__) || this.hasFeature(apf.__DRAGDROP__)) {
            elSelect.setAttribute("ondblclick", "var o = apf.lookup(" + this.$uniqueId + "); " +
                
                "o.stopRename();" +
                
                " o.choose()");
            elSelect.setAttribute("onmouseout", "var o = apf.lookup(" + this.$uniqueId + ");\
                  o.$setStyleClass(this, '', ['hover'], true);\
                this.hasPassedDown = false;");
            elSelect.setAttribute(this.itemSelectEvent || "onmousedown",
                'var o = apf.lookup(' + this.$uniqueId + ');\
                 var xmlNode = apf.xmldb.findXmlNode(this);\
                 var isSelected = o.isSelected(xmlNode);\
                 this.hasPassedDown = true;\
                 if (event.button == 2) \
                    o.stopRename();\
                 else if (!o.renaming && o.hasFocus() && isSelected == 1) \
                    this.dorename = true;\
                 if (event.button == 2 && isSelected)\
                    return;\
                 if (!o.hasFeature(apf.__DRAGDROP__) || !isSelected && !event.ctrlKey)\
                     o.select(this, event.ctrlKey, event.shiftKey, -1)');
            elSelect.setAttribute("onmouseup", 'if (!this.hasPassedDown) return;\
                var o = apf.lookup(' + this.$uniqueId + ');' +
                
                'if (o.hasFeature(apf.__RENAME__) && this.dorename)\
                    o.startDelayedRename(event, null, true);' +
                
                'this.dorename = false;\
                 var xmlNode = apf.xmldb.findXmlNode(this);\
                 var isSelected = o.isSelected(xmlNode);\
                 if (o.hasFeature(apf.__DRAGDROP__))\
                     o.select(this, event.ctrlKey, event.shiftKey, -1)');
        } //@todo add DRAGDROP ifdefs
        else {
            elSelect.setAttribute("onmouseout",    "apf.setStyleClass(this, '', ['hover']);");
            elSelect.setAttribute("ondblclick", 'var o = apf.lookup('
                + this.$uniqueId + '); o.choose(null, true)');
            elSelect.setAttribute(this.itemSelectEvent
                || "onmousedown", 'var o = apf.lookup(' + this.$uniqueId
                + '); o.select(this, event.ctrlKey, event.shiftKey, -1)');
        }
        
        
        
        
        if (this.$mode) {
            var elCheck = this.$getLayoutNode("item", "check");
            if (elCheck) {
                elCheck.setAttribute("onmousedown",
                    "var o = apf.lookup(" + this.$uniqueId + ");\
                    o.checkToggle(this, true);\o.$skipSelect = true;");

                if (apf.isTrue(this.$applyBindRule("checked", xmlNode))) {
                    this.$checkedList.push(xmlNode);
                    this.$setStyleClass(oItem, "checked");
                }
                else if (this.isChecked(xmlNode))
                    this.$setStyleClass(oItem, "checked");
            }
            else {
                
                return false;
            }
        }
        

        //Setup Nodes Identity (Look)
        if (elIcon) {
            if (elIcon.nodeType == 1) {
                elIcon.setAttribute("style", "background-image:url("
                    + apf.getAbsolutePath(this.iconPath, this.$applyBindRule("icon", xmlNode))
                    + ")");
            }
            else {
                elIcon.nodeValue = apf.getAbsolutePath(this.iconPath,
                    this.$applyBindRule("icon", xmlNode));
            }
        }
        else if (elImage) {
            if (elImage.nodeType == 1) {
                if ((elImage.tagName || "").toLowerCase() == "img") {
                    elImage.setAttribute("src", apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode)));
                }
                else {
                    elImage.setAttribute("style", "background-image:url("
                        + apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode))
                        + ")");
                }
            }
            else {
                if (apf.isSafariOld) { //@todo this should be changed... blrgh..
                    var p = elImage.ownerElement.parentNode,
                        img = p.appendChild(p.ownerDocument.createElement("img"));
                    img.setAttribute("src", 
                        apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode)));
                }
                else {
                    elImage.nodeValue = 
                        apf.getAbsolutePath(apf.hostPath, this.$applyBindRule("image", xmlNode));
                }
            }
        }

        if (elCaption) {
            
            {
                apf.setNodeValue(elCaption,
                    this.$applyBindRule("caption", xmlNode));
            }
        }
        oItem.setAttribute("title", this.$applyBindRule("tooltip", xmlNode) || "");

        
        var cssClass = this.$applyBindRule("css", xmlNode);
        if (cssClass) {
            this.$setStyleClass(oItem, cssClass);
            if (cssClass)
                this.$dynCssClasses.push(cssClass);
        }
        

        if (this.$addModifier && 
          this.$addModifier(xmlNode, oItem, htmlParentNode, beforeNode) === false)
            return;

        if (htmlParentNode)
            apf.insertHtmlNode(oItem, htmlParentNode, beforeNode);
        else
            this.listNodes.push(oItem);
    };
    
    this.addEventListener("$skinchange", function(e) {
        if (this.more)
            delete this.moreItem;
    });

    this.$fill = function(){
        if (this.more && !this.moreItem) {
            this.$getNewContext("item");
            var Item = this.$getLayoutNode("item"),
                elCaption = this.$getLayoutNode("item", "caption"),
                elSelect = this.$getLayoutNode("item", "select");

            Item.setAttribute("class", this.$baseCSSname + "More");
            elSelect.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId
                + ');o.clearSelection();o.$setStyleClass(this, "more_down", null, true);');
            elSelect.setAttribute("onmouseout", 'apf.lookup(' + this.$uniqueId
                + ').$setStyleClass(this, "", ["more_down"], true);');
            elSelect.setAttribute("onmouseup", 'apf.lookup(' + this.$uniqueId
                + ').startMore(this, true)');

            if (elCaption)
                apf.setNodeValue(elCaption,
                    this.more.match(/caption:(.*)(;|$)/i)[1]);
            this.listNodes.push(Item);
        }

        apf.insertHtmlNodes(this.listNodes, this.$container);
        this.listNodes.length = 0;

        if (this.more && !this.moreItem) {
            this.moreItem = this.$container.lastChild;
        }
            
    };

    /**
     * Adds a new item to the list, and lets the users type in the new name.
     *
     * This functionality is especially useful in the interface when
     * the list mode is set to `check` or `radio`--for instance in a form.
     */
    this.startMore = function(o, userAction) {
        if (userAction && this.disabled)
            return;

        this.$setStyleClass(o, "", ["more_down"]);

        var xmlNode;
        if (!this.$actions["add"]) {
            if (this.each && !this.each.match(/[\/\[]/)) {
                xmlNode = "<" + this.each + (this.each.match(/^a:/) 
                    ? " xmlns:a='" + apf.ns.aml + "'" 
                    : "") + " custom='1' />";
            }
            else {
                
                //return false;
                xmlNode = "<item />";
            }
        }

        this.add(xmlNode, null, null, function(addedNode) {
            this.select(addedNode, null, null, null, null, true);
            if (this.morePos == "begin")
                this.$container.insertBefore(this.moreItem, this.$container.firstChild);
            else
                this.$container.appendChild(this.moreItem);
    
            var undoLastAction = function(){
                this.getActionTracker().undo(this.autoselect ? 2 : 1);
    
                this.removeEventListener("stoprename", undoLastAction);
                this.removeEventListener("beforerename", removeSetRenameEvent);
                this.removeEventListener("afterrename",  afterRename);
            }
            var afterRename = function(){
                //this.select(addedNode);
                this.removeEventListener("afterrename",  afterRename);
            };
            var removeSetRenameEvent = function(e) {
                this.removeEventListener("stoprename", undoLastAction);
                this.removeEventListener("beforerename", removeSetRenameEvent);
    
                //There is already a choice with the same value
                var xmlNode = this.findXmlNodeByValue(e.args[1]);
                if (xmlNode || !e.args[1]) {
                    if (e.args[1] && this.dispatchEvent("notunique", {
                        value: e.args[1]
                    }) === false) {
                        this.startRename();
                        
                        this.addEventListener("stoprename",   undoLastAction);
                        this.addEventListener("beforerename", removeSetRenameEvent);
                    }
                    else {
                        this.removeEventListener("afterrename", afterRename);
                        
                        this.getActionTracker().undo();//this.autoselect ? 2 : 1);
                        if (!this.isSelected(xmlNode))
                            this.select(xmlNode);
                    }
                    
                    return false;
                }
            };
    
            this.addEventListener("stoprename",   undoLastAction);
            this.addEventListener("beforerename", removeSetRenameEvent);
            this.addEventListener("afterrename",  afterRename);
    
            
            this.startDelayedRename({}, 1);
            
        });
    };

    // *** Selection *** //

    this.$calcSelectRange = function(xmlStartNode, xmlEndNode) {
        var r = [],
            nodes = this.hasFeature(apf.__VIRTUALVIEWPORT__)
                ? this.xmlRoot.selectNodes(this.each)
                : this.getTraverseNodes(),
            f, i;
        for (f = false, i = 0; i < nodes.length; i++) {
            if (nodes[i] == xmlStartNode)
                f = true;
            if (f)
                r.push(nodes[i]);
            if (nodes[i] == xmlEndNode)
                f = false;
        }

        if (!r.length || f) {
            r = [];
            for (f = false, i = nodes.length - 1; i >= 0; i--) {
                if (nodes[i] == xmlStartNode)
                    f = true;
                if (f)
                    r.push(nodes[i]);
                if (nodes[i] == xmlEndNode)
                    f = false;
            }
        }

        return r;
    };

    this.$selectDefault = function(XMLRoot) {
        this.select(this.getTraverseNodes()[0], null, null, null, true);
    };

    /**
     * Generates a list of items based on a string.
     * @param {String} str The description of the items. Items are seperated by a comma (`,`). Ranges are specified by a start and end value seperated by a dash (`-`).
     *
     * #### Example
     *
     * This example loads a list with items starting at 1980 and ending at 2050.
     *
     * #### ```xml
     *  lst.loadFillData("1980-2050");
     *  lst.loadFillData("red,green,blue,white");
     *  lst.loadFillData("None,100-110,1000-1100"); // 101, 102...110, 1000,1001, e.t.c.
     *  lst.loadFillData("1-10"); // 1 2 3 4 e.t.c.
     *  lst.loadFillData("01-10"); //01, 02, 03, 04, e.t.c.
     * ```
     */
    this.loadFillData = function(str) {
        var len, start, end, parts = str.splitSafe(","), data = [];
        
        for (var p, part, i = 0; i < parts.length; i++) {
            if ((part = parts[i]).match(/^\d+-\d+$/)) {
                p = part.split("-");
                start = parseInt(p[0]);
                end = parseInt(p[1]);
                
                if (p[0].length == p[1].length) {
                    len = Math.max(p[0].length, p[1].length);
                    for (var j = start; j < end + 1; j++) {
                        data.push("<item>" + (j + "").pad(len, "0") + "</item>");
                    }
                }
                else {
                    for (var j = start; j < end + 1; j++) {
                        data.push("<item>" + j + "</item>");
                    }
                }
            }
            else {
                data.push("<item>" + part + "</item>");
            }
        }
        
        //@todo this is all an ugly hack (copied from item.js line 486)
        //this.$preventDataLoad = true;//@todo apf3.0 add remove for this
        
        this.$initingModel = true;
        
        this.each = "item";
        this.$setDynamicProperty("caption", "[label/text()|@caption|text()]");
        this.$setDynamicProperty("eachvalue", "[value/text()|@value|text()]");
        this.$canLoadDataAttr = false;

        this.load("<data>" + data.join("") + "</data>");
    };

}).call(apf.BaseList.prototype = new apf.MultiSelect());







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Baseclass of a simple element. These are usually displaying elements 
 * (_i.e._ {@link apf.label}, {@link apf.img})
 *
 * @class apf.BaseSimple
 * @baseclass
 *
 * @inherits apf.StandardBinding
 * @inherits apf.DataAction
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.BaseSimple = function(){
    this.$init(true);
};

(function() {
    
    this.implement(apf.DataAction);
    
    
    this.getValue = function(){
        return this.value;
    };

}).call(apf.BaseSimple.prototype = new apf.StandardBinding());







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * The base class for state buttons.
 *
 * @class apf.BaseStateButtons
 * @baseclass
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.BaseStateButtons = function(){
    this.state = "normal";
    this.edit = false;
    
    var actions = {
        "min"   : ["minimized", "minimize", "restore"],
        "max"   : ["maximized", "maximize", "restore"],
        "edit"  : ["edit", "edit", "closeedit"],
        "close" : ["closed", "close", "show"]
    };
    this.$lastheight = null;
    this.$lastpos = null;

    this.$lastState = {"normal":true};    
    this.$booleanProperties["animate"] = true;
    this.$supportedProperties.push("buttons", "animate", "state");
    
    /**
     * Close the window. It can be reopened by using {@link apf.GuiElement.show}
     * @chainable
     */
    this.close = function(){ // @todo show should unset closed
        this.setProperty("state", this.state.split("|")
            .pushUnique("closed").join("|"), false, true);
        return this;
    };

    /**
     * Minimize the window. The window will become the height of the title of
     * the parent window.
     * @chainable
     */
    this.minimize = function(){
        this.setProperty("state", this.state.split("|")
            .remove("maximized")
            .remove("normal")
            .pushUnique("minimized").join("|"), false, true);
        return this;
    };

    /**
     * Maximize the window. The window will become the width and height of the
     * browser window.
     * @chainable
     */
    this.maximize = function(){
        this.setProperty("state", this.state.split("|")
            .remove("minimized")
            .remove("normal")
            .pushUnique("maximized").join("|"), false, true);
        return this;
    };

    /**
     * Restore the size of the window. The window will become the width and
     * height it had before it was minimized or maximized.
     * @chainable
     */
    this.restore = function(){
        this.setProperty("state", this.state.split("|")
            .remove("minimized")
            .remove("maximized")
            .pushUnique("normal").join("|"), false, true);
        return this;
    };
    
     /**
     * Set the window into edit state. The configuration panel is shown.
     * @chainable
     */
    this.edit = function(value) {
        this.setProperty("state", this.state.split("|")
            .pushUnique("edit").join("|"), false, true);
        return this;
    };

    /**
     * Removes the edit state of this window. The configuration panel is hidden.
     * @chainable
     */
    this.closeedit = function(value) {
        this.setProperty("state", this.state.split("|")
            .remove("edit").join("|"), false, true);
        return this;
    };
    
    this.$toggle = function(type) {
        var c = actions[type][0];
        this[actions[type][this.state.indexOf(c) > -1 ? 2 : 1]]();
    };
    
    this.$propHandlers["refparent"] = function(value) {
        if (typeof value == "string")
            this.$refParent = self[value] && self[value].$ext || document.getElementById(value);
        else this.$refParent = value;
    }
    
    this.$propHandlers["maxconf"] = function(value) {
        this.$maxconf = value.splitSafe(",");
    }
    
    /**
     * @attribute {String} state Sets or gets the state of the window. The state can be a
     * combination of multiple states, seperated by a pipe (`'|'`) character.
     *   
     * The possible values include:
     *
     *   `"normal"`:     The window has its normal size and position. This is the default value.
     *   `"minimized"`:  The window is minimized.
     *   `"maximized"`:  The window is maximized.
     *   `"edit"`:       The window is in the edit state.
     *   `"closed"`:     The window is closed.
     */
    this.$propHandlers["state"] = function(value, prop, force, reenter, noanim) {
        var _self = this;
        if (!this.$amlLoaded) { //@todo I still think this is weird and should not be needed
            apf.queue.add("state" + this.$uniqueId, function(){
                _self.$propHandlers["state"].call(_self, value, prop, force, reenter, noanim);
            });
            return;
        }

        if (value == 0)
            value = "normal";

        var i, pNode, position, l, t,
            o = {},
            s = value.split("|"),
            lastState = this.$lastState,
            styleClass = [];

        for (i = 0; i < s.length; i++)
            o[s[i]] = true;
        o.value = value;

        if (!o.maximized && !o.minimized)
            o.normal = true;

        if (!reenter && this.dispatchEvent("beforestatechange", {
          from: lastState, 
          to: o}) === false) {
            this.state = lastState.value;
            return false;
        }

        //Closed state
        if (o.closed == this.visible) {//change detected
            this.setProperty("visible", !o["closed"], false, true);
            //@todo difference is, we're not clearing the other states, check the docking example
        }

        //Restore state
        if (o.normal != lastState.normal
          || !o.normal && (o.minimized != lastState.minimized
            || o.maximized != lastState.maximized)) {

            if (this.$lastheight != null) // this.aData && this.aData.hidden == 3 ??
                this.$ext.style.height = this.$lastheight;//(this.$lastheight - apf.getHeightDiff(this.$ext)) + "px";

            if (this.$lastpos) {
                apf.plane.hide(this.$uniqueId);
                
                if (this.animate && !noanim) {
                    //Pre remove paused event because of not having onresize
                    //if (apf.hasSingleRszEvent)
                        //delete apf.layout.onresize[apf.layout.getHtmlId(this.$pHtmlNode)];

                    var htmlNode = this.$ext;
                    position = apf.getStyle(htmlNode, "position");
                    if (position != "absolute") {
                        l = parseInt(apf.getStyle(htmlNode, "left")) || 0;
                        t = parseInt(apf.getStyle(htmlNode, "top")) || 0;
                    }
                    else {
                        l = htmlNode.offsetLeft;
                        t = htmlNode.offsetTop;
                    }

                    this.animstate = 1;
                    apf.tween.multi(htmlNode, {
                        steps: 15,
                        anim: apf.tween.easeInOutCubic,
                        interval: 10,
                        tweens: [
                            {type: "left",   from: l,    to: this.$lastpos.px[0]},
                            {type: "top",    from: t,    to: this.$lastpos.px[1]},
                            {type: "width",  from: this.$ext.offsetWidth,
                                to: this.$lastpos.px[2]},
                            {type: "height", from: this.$ext.offsetHeight,
                                to: this.$lastpos.px[3]}
                        ],
                        oneach: function(){
                            
                            if (apf.hasSingleRszEvent)
                                apf.layout.forceResize(_self.$int);
                            
                        },
                        onfinish: function(){
                            _self.$lastpos.parentNode.insertBefore(_self.$ext, _self.$lastpos.beforeNode);
                            
                            if (_self.$placeHolder)
                                _self.$placeHolder.parentNode.removeChild(_self.$placeHolder);
                            
                            _self.$propHandlers["state"].call(_self, value, null,
                                null, true, true);
                        }
                    });

                    return;
                }
                else if (!this.animate) {
                    apf.plane.hide(this.$uniqueId, true);
                    
                    _self.$lastpos.parentNode.insertBefore(_self.$ext, _self.$lastpos.beforeNode);
                            
                    if (_self.$placeHolder)
                        _self.$placeHolder.parentNode.removeChild(_self.$placeHolder);
                }

                this.$ext.style.position = this.$lastpos.pos;
                this.$ext.style.left = this.$lastpos.css[0];
                this.$ext.style.top = this.$lastpos.css[1];
                this.$ext.style.width = this.$lastpos.css[2];
                this.$ext.style.height = this.$lastpos.css[3];
                
                pNode = this.$lastpos.parentNode;
                pNode.style.width = this.$lastpos.parent[0];
                pNode.style.height = this.$lastpos.parent[1];
                pNode.style.overflow = this.$lastpos.parent[2];
            }

            
            if (this.aData && this.aData.restore)
                this.aData.restore();
            

            
            if (apf.layout)
                apf.layout.play(this.$pHtmlNode);
            

            this.$lastheight = this.$lastpos = null;

            if (o.normal)
                styleClass.push("",
                    this.$baseCSSname + "Max",
                    this.$baseCSSname + "Min");
        }

        if (o.minimized != lastState.minimized) {
            if (o.minimized) {
                styleClass.unshift(
                    this.$baseCSSname + "Min",
                    this.$baseCSSname + "Max",
                    this.$baseCSSname + "Edit");

                
                if (this.aData && this.aData.minimize)
                    this.aData.minimize(this.collapsedHeight);
                

                if (!this.aData || !this.aData.minimize) {
                    this.$lastheight = this.$ext.style.height; //apf.getStyle(this.$ext, "height");//this.$ext.offsetHeight;

                    this.$ext.style.height = Math.max(0, this.collapsedHeight
                        - apf.getHeightDiff(this.$ext)) + "px";
                }

                if (this.hasFocus())
                    apf.window.moveNext(null, this, true);
                //else if(apf.document.activeElement)
                    //apf.document.activeElement.$focus({mouse: true});
            }
            else {
                styleClass.push(this.$baseCSSname + "Min");

                $setTimeout(function(){
                    apf.window.$focusLast(_self);
                });
            }
        }

        if (o.maximized != lastState.maximized) {
            if (o.maximized) {
                styleClass.unshift(
                    this.$baseCSSname + "Max",
                    this.$baseCSSname + "Min",
                    this.$baseCSSname + "Edit");

                pNode = this.$refParent;
                if (!pNode)
                    pNode = (this.$ext.offsetParent == document.body
                      ? document.documentElement
                      : this.$ext.parentNode);

                this.animstate = 0;
                var hasAnimated = false, htmlNode = this.$ext;
                
                var position = apf.getStyle(htmlNode, "position");
                if (position == "absolute") {
                    pNode.style.overflow = "hidden";
                    l = htmlNode.offsetLeft;
                    t = htmlNode.offsetTop;
                }
                else {
                    var pos = apf.getAbsolutePosition(htmlNode); //pNode
                    l = pos[0];//parseInt(apf.getStyle(htmlNode, "left")) || 0;
                    t = pos[1];//parseInt(apf.getStyle(htmlNode, "top")) || 0;
                }
                
                this.$lastpos = {
                    css: [this.$ext.style.left, this.$ext.style.top,
                              this.$ext.style.width, this.$ext.style.height,
                              this.$ext.style.margin, this.$ext.style.zIndex],
                    px: [l, t, this.$ext.offsetWidth, 
                              this.$ext.offsetHeight],
                    parent: [pNode.style.width, pNode.style.height, 
                              pNode.style.overflow],
                    pos: htmlNode.style.position,
                    parentNode: pNode,
                    beforeNode: this.$ext.nextSibling
                };
                
                if (this.parentNode.$layout) {
                    if (!this.$placeHolder)
                        this.$placeHolder = document.createElement("div");
                    this.$placeHolder.style.position = this.$lastpos.pos;
                    this.$placeHolder.style.left = this.$lastpos.css[0];
                    this.$placeHolder.style.top = this.$lastpos.css[1];
                    this.$placeHolder.style.width = this.$lastpos.px[2] + "px";
                    this.$placeHolder.style.height = this.$lastpos.px[3] + "px";
                    this.$placeHolder.style.margin = this.$lastpos.css[4];
                    this.$placeHolder.style.zIndex = this.$lastpos.css[5];
                    this.$pHtmlNode.insertBefore(this.$placeHolder, this.$ext);
                    
                    htmlNode.style.position = "absolute";
                }
                
                document.body.appendChild(htmlNode);
                htmlNode.style.left = l + "px";
                htmlNode.style.top = t + "px";
                
                function setMax(){
                    //While animating dont execute this function
                    if (_self.animstate)
                        return;
                    
                    var w, h, pos, box, pDiff;
                    if (_self.maxconf) {
                        w = _self.$maxconf[0];
                        h = _self.$maxconf[1];
                        
                        pos = [_self.$maxconf[2] == "center" 
                            ? (apf.getWindowWidth() - w)/2
                            : _self.$maxconf[2], 
                               _self.$maxconf[3] == "center" 
                            ? (apf.getWindowHeight() - h)/3
                            : _self.$maxconf[3]];
                    }
                    else {
                        w = !apf.isIE && pNode == document.documentElement
                            ? window.innerWidth
                            : pNode.offsetWidth,
                        h = !apf.isIE && pNode == document.documentElement
                            ? window.innerHeight
                            : pNode.offsetHeight;
                    }
                    
                    if (!pos) {
                        pos = pNode != htmlNode.offsetParent
                            ? apf.getAbsolutePosition(pNode, htmlNode.offsetParent)
                            : [0, 0];
                    }

                    if (position != "absolute") {
                        var diff = apf.getDiff(pNode);
                        w -= diff[0] + (!_self.$refParent && apf.isIE8 ? 4 : 0);//@todo dirty hack!
                        h -= diff[0] + (!_self.$refParent && apf.isIE8 ? 4 : 0);//@todo dirty hack!
                    }
                    //@todo dirty hack!
                    else if (!_self.$refParent && apf.isIE8) {
                        w -= 4;
                        h -= 4;
                    }
                    
                    box = _self.$refParent ? [0,0,0,0] : marginBox;
                    pDiff = apf.getDiff(pNode);

                    pNode.style.width = (pNode.offsetWidth - pDiff[0]) + "px";
                    pNode.style.height = (pNode.offsetHeight - pDiff[1]) + "px";
                    
                    if (!hasAnimated && _self.$maxconf && _self.$maxconf[4])
                        apf.plane.show(htmlNode, false, null, null, {
                            color: _self.$maxconf[4], 
                            opacity: _self.$maxconf[5],
                            animate: _self.animate,
                            protect: _self.$uniqueId
                        });
                    
                    if (_self.animate && !hasAnimated) {
                        _self.animstate = 1;
                        hasAnimated = true;
                        apf.tween.multi(htmlNode, {
                            steps: 15,
                            anim: apf.tween.easeInOutCubic,
                            interval: 10,
                            tweens: [
                                {type: "left",   from: l, to: pos[0] - box[3]},
                                {type: "top",    from: t, to: pos[1] - box[0]},
                                {type: "width",  from: _self.$lastpos.px[2],
                                    to: (w + box[1] + box[3] - apf.getWidthDiff(_self.$ext))},
                                {type: "height", from: _self.$lastpos.px[3],
                                    to: (h + box[0] + box[2] - apf.getHeightDiff(_self.$ext))}
                            ],
                            oneach: function(){
                                
                                if (apf.hasSingleRszEvent)
                                    apf.layout.forceResize(_self.$int);
                                
                            },
                            onfinish: function(){
                                _self.animstate = 0;
                                
                                _self.dispatchEvent("afterstatechange", {
                                  from: lastState, 
                                  to: o
                                });
                                
                                
                                if (apf.hasSingleRszEvent)
                                    apf.layout.forceResize(_self.$int);
                                
                            }
                        });
                    }
                    else if (!_self.animstate) {
                        htmlNode.style.left = (pos[0] - box[3]) + "px";
                        htmlNode.style.top = (pos[1] - box[0]) + "px";

                        var diff = apf.getDiff(_self.$ext);
                        htmlNode.style.width = (w
                            - diff[0] + box[1] + box[3]) + "px";
                        htmlNode.style.height = (h
                            - diff[1] + box[0] + box[2]) + "px";
                    }
                }

                
                if (apf.layout)
                    apf.layout.pause(this.$pHtmlNode, setMax);
                
            }
            else {
                styleClass.push(this.$baseCSSname + "Max");
            }
        }

        if (o.edit != lastState.edit) {
            if (o.edit) {
                styleClass.unshift(
                    this.$baseCSSname + "Edit",
                    this.$baseCSSname + "Max",
                    this.$baseCSSname + "Min");

                if (this.btnedit)
                    oButtons.edit.innerHTML = "close"; //hack

                this.dispatchEvent('editstart');
            }
            else {
                if (this.dispatchEvent('editstop') === false)
                    return false;

                styleClass.push(this.$baseCSSname + "Edit");
                if (styleClass.length == 1)
                    styleClass.unshift("");

                if (this.btnedit)
                    oButtons.edit.innerHTML = "edit"; //hack
            }
        }

        if (styleClass.length || o.closed != lastState.closed) {
            if (styleClass.length)
                this.$setStyleClass(this.$ext, styleClass.shift(), styleClass);
                
            if (o.edit) { //@todo apf3.0
                this.dispatchEvent("prop.visible", {value:true});
                
                if (_self.oSettings)
                    apf.layout.forceResize(_self.oSettings);
                
            }

            //@todo research why this is not symmetrical
            if (!o.maximized || !this.animate || lastState.maximized && _self.animate) {
                _self.dispatchEvent("afterstatechange", {
                  from: lastState, 
                  to: o});
            }
            
            this.$lastState = o;

            
            if (this.aData && !o.maximized) { //@todo is this the most optimal position?
                this.$purgeAlignment();
            }
            

            
            if (!this.animate && apf.hasSingleRszEvent && apf.layout)
                apf.layout.forceResize(_self.$int);
            
        }
    };

    var marginBox, hordiff, verdiff, oButtons = {}
    /**
     * @attribute {String} buttons Sets or gets the buttons that the window displays. This
     * can be multiple values seperated by a pipe (`'|'`) character.
     *   
     * The possible values include:
     *
     *   `"min"`:    The button that minimizes the window.
     *   `"max"`:    The button that maximizes the window.
     *   `"close"`:  The button that closes the window.
     *   `"edit"`:   The button that puts the window into the edit state.
     */
    this.$propHandlers["buttons"] = function(value) {
        
        if (!this.$hasLayoutNode("button"))
            return;

        var buttons = value && (value = value.replace(/(\|)\||\|$/, "$1")).split("|") || [],
            nodes = this.$buttons.childNodes,
            re = value && new RegExp("(" + value + ")"),
            found = {},
            idleNodes = [];

        //Check if we can 'remove' buttons
        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 1 || nodes[i].tagName != "DIV") //@todo temp hack
                continue;

            if (nodes[i].getAttribute("button") && (!value 
              || !nodes[i].className || !nodes[i].className.match(re))) {
                nodes[i].style.display = "none";
                this.$setStyleClass(nodes[i], "", ["min", "max", "close", "edit"]);
                idleNodes.push(nodes[i]);
            }
            else {
                found[RegExp.$1] = nodes[i];
            }
        }

        //Create new buttons if needed
        for (i = 0; i < buttons.length; i++) {
            if (!buttons[i])
                continue;
            
            if (found[buttons[i]]) {
                this.$buttons.insertBefore(found[buttons[i]], this.$buttons.firstChild);
                continue;
            }

            var btn = idleNodes.pop();
            if (!btn) {
                this.$getNewContext("button");
                btn = this.$getLayoutNode("button");
                btn.setAttribute("button", "button");
                setButtonEvents.call(this, btn);
                btn = apf.insertHtmlNode(btn, this.$buttons);
            }

            this.$setStyleClass(btn, buttons[i], ["min", "max", "close", "edit"]);
            btn.onclick = new Function("apfLookup(" + this.$uniqueId + ").$toggle('"
                                       + buttons[i] + "')");
            btn.style.display = "block";
            oButtons[buttons[i]] = btn;
            this.$buttons.insertBefore(btn, this.$buttons.firstChild);
        }
        
        marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth"));
    };
    
    function setButtonEvents(btn) {
        //@todo can this cancelBubble just go?
        //event.cancelBubble = true; \
        btn.setAttribute("onmousedown",
            "var o = apf.all[" + this.$uniqueId + "];\
             o.$setStyleClass(this, 'down', null, true);\
             apf.cancelBubble(event, o); \
             var o = apf.findHost(this).$ext;\
             if (o.onmousedown) o.onmousedown(event);\
             apf.cancelBubble(event, o);\
             apf.window.$mousedown(event);");
        btn.setAttribute("onmouseup",
            "var o = apf.all[" + this.$uniqueId + "];\
             o.$setStyleClass(this, '', ['down'], true);");
        btn.setAttribute("onmouseover",
            "var o = apf.all[" + this.$uniqueId + "];\
             o.$setStyleClass(this, 'hover', null, true);");
        btn.setAttribute("onmouseout",
            "var o = apf.all[" + this.$uniqueId + "];\
             o.$setStyleClass(this, '', ['hover', 'down'], true);");
        btn.setAttribute("ondblclick", "apf.stopPropagation(event);");
    }
    
    this.$initButtons = function(oExt) {
        this.animate = apf.enableAnim;
        
        this.collapsedHeight = this.$getOption("Main", "collapsed-height");

        var oButtons = this.$getLayoutNode("main", "buttons", oExt);
        if (!oButtons || apf.isIphone || !this.getAttribute("buttons") 
          || !this.$hasLayoutNode("button"))
            return;

        var len = (this.getAttribute("buttons") || "").split("|").length;
        for (var btn, i = 0; i < len; i++) {
            this.$getNewContext("button");
            btn = oButtons.appendChild(this.$getLayoutNode("button"));
            btn.setAttribute("button", "button");
            setButtonEvents.call(this, btn);
        }
    };
    
    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        for (var name in oButtons) {
            oButtons[name].onclick = null;
        }
    });
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Baseclass of a paged element. 
 *
 * @class apf.BaseTab
 * @baseclass
 * @allowchild tab
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 * @inherits apf.Presentation
 *
 */
/**
 * @event beforeswitch  Fires before this element switches to another tab.
 * @cancelable Prevents the tab to become active.
 * @param {Object} e The standard event object. It contains the following properties:
 *   - previous ([[String]] or [[Number]]): The name or number of the current tab.
 *   - previousId ([[Number]]): The number of the current tab.
 *   - previousPage ([[apf.tab]]): The current tab.
 *   - next ([[String]] or [[Number]]): The name or number of the tab the will become active.
 *   - nextId ([[Number]]): The number of the tab the will become active.
 *   - nextPage ([[apf.tab]]): The tab the will become active.
 */
/**
 *  @event afterswitch   Fires after this element has switched to another tab.
 *  @param {Object} e The standard event object. It contains the following properties:
 *   - previous ([[String]] or [[Number]]): The name or number of the previous tab.
 *   - previousId ([[Number]]): The number of the previous tab.
 *   - previousPage ([[apf.tab]]): The previous tab.
 *   - next ([[String]] or [[Number]]): The name or number of the current tab.
 *   - nextId ([[Number]]): The number of the the current tab.
 *   - nextPage ([[apf.tab]]): The the current tab.   
 */
apf.BaseTab = function(){
    this.$init(true);
};

(function() {
    this.isPaged = true;
    this.$focussable = apf.KEYBOARD;
    this.length = 0;
    this.isLoading = {};
    this.inited = 
    this.ready = false;
    this.$scroll = true;

    /**
     * Sets the current tab of this element.
     * @param {String | Number}    tab     The name or number of the tab which is made active.
     * @param {Function} callback The function called after setting the tab. Especially handy when using the `src` attribute.
     */
    this.set = function(tab, callback, noEvent) {
        if (noEvent || this.src && !this.$findPage(tab, {})) {
            return this.$propHandlers["activepage"].call(
                this, tab, null, null, callback, noEvent);
        }
        
        if (this.activepage == (tab.name || tab))
            return callback && callback(this.getPage(tab));

        this.$lastCallback = callback;
        this.setProperty("activepage", tab);
    };

    // *** Properties and Attributes *** //

    this.$supportedProperties.push("activepage", "activepagenr", "length",
        "src", "loading", "trans-in", "trans-out");

    /**
     * @property {Number} [SCROLL_LEFT=1] The constant representing the "scroll left" button
     * @readonly
     */
    /**
     * @property {Number} [SCROLL_RIGHT=2] The constant representing the "scroll right" button
     * @readonly
     */
    /**
     * @property {Number} [SCROLL_BOTH=4] The constant representing the "scroll left" and "scroll right" buttons
     * @readonly
     */
    /**
     * @attribute {Number} activepagenr Sets or gets the child number of the active tab.
     * 
     * #### Example
     *
     * This example uses property binding to maintain consistency between a
     * dropdown which is used as a menu, and a pages element
     * 
     * ```xml
     *  <a:dropdown id="ddMenu" value="0">
     *      <a:item value="0">Home</a:item>
     *      <a:item value="1">General</a:item>
     *      <a:item value="2">Advanced</a:item>
     *  </a:dropdown>
     * 
     *  <a:pages activepagenr="{ddMenu.value}">
     *      <a:tab>
     *          <h1>Home Page</h1>
     *      </a:tab>
     *      <a:tab>
     *          <h1>General Page</h1>
     *      </a:tab>
     *      <a:tab>
     *          <h1>Advanced Page</h1>
     *      </a:tab>
     *  </a:pages>
     * ```
     */
    this.$propHandlers["activepagenr"] =

    /**
     * @attribute {String} activepage Sets or gets the name of the active tab.
     *  
     * #### Example
     *
     * ```xml
     *  <a:pane activepage="general" width="250" height="100">
     *      <a:tab id="home" caption="Home">
     *      ...
     *      </a:tab>
     *      <a:tab id="advanced" caption="Advanced">
     *          ...
     *      </a:tab>
     *      <a:tab id="general" caption="General">
     *          ...
     *      </a:tab>
     *   </a:pane>
     * ```
     */
    this.$propHandlers["activepage"] = function(next, prop, force, callback, noEvent) {
        if (!this.inited || apf.isNot(next) || next == -1) return;

        if (!callback) {
            callback = this.$lastCallback;
            delete this.$lastCallback;
        }

        var tab, info = {};
        tab = this.$findPage(next, info);

        if (!tab) {
            if (this.src) {
                if (this.isLoading[next])
                    return;
                
                if (this.$findPage("loading", {}))
                    this.$propHandlers["activepage"].call(this, "loading");
                
                this.setProperty("loading", true);
                this.isLoading[next] = true;

                tab = this.ownerDocument.createElementNS(apf.ns.apf, "tab");
                tab.setAttribute("id", next);
                this.appendChild(tab);

                var _self = this;
                tab.insertMarkup(this.src, {
                    tab: next,
                    //@todo apf3.0 change callback arguments in xinclude
                    callback: function(options) {
                        delete _self.isLoading[next];
                    
                        if (!options.xmlNode) {
                            var oError = new Error(apf.formatErrorString(0, null,
                                "Loading new tab", "Could not load new tab: "
                                + _self.src));
                                
                            _self.setProperty("loading", false);
                            
                            if (this.dispatchEvent("error", apf.extend({
                                error: oError,
                                bubbles: true
                            }, options)) === false)
                                return true;
                            
                            throw oError;
                        }
                        else {
                            //for success
                            _self.setProperty("activepage", next);
                            
                            //Needs to be after set
                            if (callback)
                                callback(options.amlNode);
    
                            _self.setProperty("loading", false);
                        }
                    }
                });
                return;
            }
            
            

            return false;
        }

        if (tab.parentNode != this) {
            

            return false;
        }

        if (!tab.visible || tab.disabled) {
            

            return false;
        }

        //If tab is given as first argument, let's use its position
        if (next.tagName) {
            next = info.position;
            this.activepage = tab.name || next;//tab.type || 
        }

        //Call the onbeforeswitch event;
        if (!noEvent) {
            var oEvent = {
                previous: this.activepage,
                previousId: this.activepagenr,
                previousPage: this.$activepage,
                next: next,
                nextId: info.position,
                nextPage: tab
            };

            if (this.dispatchEvent("beforeswitch", oEvent) === false) {
                //Loader support
                if (this.hideLoader)
                    this.hideLoader();

                return false;
            }
        }

        //Maintain an activepagenr property (not reentrant)
        this.activepagenr = info.position;
        this.setProperty("activepagenr", info.position);

        //Deactivate the current tab, if any,  and activate the new one
        if (this.$activepage)
            this.$activepage.$deactivate();

        tab.$activate();

        

        this.$activepage = tab;
        
        //this.scrollIntoView(tab);
        

        //Loader support
        if (this.hideLoader) {
            if (tab.$rendered !== false) {
                this.hideLoader();
            }
            else {
                //Delayed rendering support
                tab.addEventListener("afterrender", function(){
                    this.parentNode.hideLoader();
                 });
            }
        }

        if (!noEvent) {
            if (tab.$rendered !== false)
                this.dispatchEvent("afterswitch", oEvent);
            else {
                //Delayed rendering support
                tab.addEventListener("afterrender", function(){ 
                    this.parentNode.dispatchEvent("afterswitch", oEvent);
                });
             }
        }
        
        if (typeof callback == "function") 
            callback(tab);

        return true;
    };
    
    /**
     * @attribute {String} buttons Sets or gets the modifier for pane tab buttons, seperated by a `|` character
     *   
     * Possible values include:
     *   - `close`:   The button has a close button inside it
     *   - `scale`:  The buttons are scaled to make room for more buttons
     *   - `scroll`:  When the buttons take too much space, scroll buttons are displayed
     */
    this.$propHandlers["buttons"] = function(value) {
        //this.buttons = value;
        this.$scale = value.indexOf("scale") > -1;
        this.$scroll = !this.$scale;
        this.$order = value.indexOf("order") > -1;
        
        
        //@todo skin change
        //@todo buttons on the side
        if (this.$scale) {
            this.$maxBtnWidth = parseInt(this.$getOption("button", "maxwidth")) || 150;
            this.$minBtnWidth = parseInt(this.$getOption("button", "minwidth")) || 10;
            this.$setStyleClass(this.$buttons, "scale");
            this.addEventListener("resize", scalersz);
            
            // this.minwidth = this.$minBtnWidth * this.getPages().length + 10;
            // this.$ext.style.minWidth = Math.max(0, this.minwidth - apf.getWidthDiff(this.$ext)) + "px";
        }
        else {
            this.$setStyleClass(this.$buttons, "", ["scale"]);
            this.removeEventListener("resize", scalersz);
        }
        
    };
    
    
    function visCheck(){
        scalersz.call(this)
    }
    
    this.anims = "add|remove|sync";

    //Add an element
    function animAddTab(pane, callback) {
        var t = pane.$button;

        var animateWidth = (t.offsetWidth 
            - apf.getWidthDiff(t)) < parseInt(apf.getStyle(t, "maxWidth"));
        
        if (animateWidth) {
            var p = pane.parentNode.getPages()[0] == pane 
                ? null 
                : pane.previousSibling.$button;
            var tb = p 
                ? (p.offsetWidth - apf.getWidthDiff(p)) 
                : parseInt(apf.getStyle(t, "maxWidth"));
            t.style.maxWidth = "0px";
        }

        t.style.marginTop = (t.offsetHeight + 2) + "px";
        
        function animateToTop(){
            t.style.marginTop = "0px";
            
            setTimeout(function(){
                t.style[apf.CSSPREFIX + "TransitionProperty"] = "";
                t.style[apf.CSSPREFIX + "TransitionDuration"] = "";
                t.style[apf.CSSPREFIX + "TimingFunction"] = "";
                
                t.style.marginTop = "";
                
                if (animateWidth)
                    t.style.maxWidth = "";
                
                callback(pane);
            }, 150);
        }
        
        setTimeout(function(){
            t.style[apf.CSSPREFIX + "TransitionProperty"] = "margin-top, max-width";
            t.style[apf.CSSPREFIX + "TransitionDuration"] = "100ms, 50ms";
            t.style[apf.CSSPREFIX + "TimingFunction"] = "cubic-bezier(.10, .10, .25, .90), cubic-bezier(.10, .10, .25, .90)";
            
            if (animateWidth) {
                t.style.maxWidth = tb + "px";
                setTimeout(animateToTop, 50);
            }
            else animateToTop();
        });
    }
    
    //Remove an element
    function animRemoveTab(pane, isLast, isContracted, callback, isOnly) {
        var t = pane.$button;
        var tb = t.offsetHeight;
        
        var diff = t.offsetWidth;
        
        t.style[apf.CSSPREFIX + "TransitionProperty"] = "margin-top, max-width, padding";
        t.style[apf.CSSPREFIX + "TransitionDuration"] = (isOnly ? ".2" : ".15") + "s, .1s, .1s";
        t.style[apf.CSSPREFIX + "TimingFunction"] = "linear, ease-out, ease-out";
        
        t.style.marginTop = (tb + 2) + "px";

        var p = t.parentNode;
        if (apf.isGecko) p = p.parentNode;
        
        p.style[apf.CSSPREFIX + "TransitionProperty"] = "padding-right";
        p.style[apf.CSSPREFIX + "TransitionDuration"] = ".2s";
        p.style[apf.CSSPREFIX + "TimingFunction"] = "ease-out";
        
        if (isLast)
            p.style.paddingRight = "";
        else {
            var cur = parseInt(apf.getStyle(p, "paddingRight"));
            p.style.paddingRight = (cur + diff - 21) + "px";
        }
        
        function end(){
            setTimeout(function(){
                p.style[apf.CSSPREFIX + "TransitionProperty"] = "";
                p.style[apf.CSSPREFIX + "TransitionDuration"] = "";
                p.style[apf.CSSPREFIX + "TimingFunction"] = "";
                
                t.style[apf.CSSPREFIX + "TransitionProperty"] = "";
                t.style[apf.CSSPREFIX + "TransitionDuration"] = "";
                t.style[apf.CSSPREFIX + "TimingFunction"] = "";
                
                t.style.display = "none";
                
                callback(pane);
            }, 150);
        }
        
        if (!isLast && isContracted) {
            t.style.minWidth = "20px"
            t.style.maxWidth = "0px";
            t.style.padding = 0;
            
            end();
        }
        else {
            setTimeout(function(){
                t.style.minWidth = "20px"
                t.style.maxWidth = "0px";
                t.style.padding = 0;
                
                end();
            }, isOnly ? 150 : 100);
        }
    }
    
    this.$scaleinit = function(node, type, callback, force) {
        var _self = this;
        
        var pg = this.getPages();
        var l = pg.length;
        if (!l) return;
        
        // this.minwidth = this.$minBtnWidth * l + 10; //@todo padding + margin of button container
        // this.$ext.style.minWidth = Math.max(0, this.minwidth - apf.getWidthDiff(this.$ext)) + "px";
        
        if (force && !this.$ext.offsetWidth && !this.$ext.offsetHeight
          || this.anims.indexOf(type) == -1 || this.skipAnimOnce) {
            scalersz.call(this);
            
            this.skipAnimOnce = false;
              
            if (type == "add")
                node.dispatchEvent("afteropen");
            else if (type == "remove") {
                if (node.dispatchEvent("afterclose") !== false)
                    callback();
            }
            
            return;
        }
        
        if (!apf.window.vManager.check(this, "tabscale", visCheck))
            return;
        
        if (!type)
            return scalersz.call(this);
        
        function btnMoHandler(e) {
            var pos = apf.getAbsolutePosition(this);
            if (e.clientX <= pos[0] || e.clientY <= pos[1] 
              || e.clientX >= pos[0] + this.offsetWidth 
              || e.clientY >= pos[1] + this.offsetHeight) {
                apf.removeListener(_self.$buttons, "mouseout", btnMoHandler);
                    delete _self.$waitForMouseOut;
                    _self.$scaleinit(null, "sync");
//                }
//                else if (_self.$waitForMouseOut)
//                    _self.$waitForMouseOut = 2;
            }
        }
        
        if (type == "add") {
            animAddTab(node, function(){
                node.dispatchEvent("afteropen");
            });
        }
        else if (type == "sync") {
            scalersz.call(this);
        }
        else if (type == "remove") {
            var onfinish = function(){
                if (node.dispatchEvent("afterclose") === false)
                    return;
                    
                callback();

                if (!isLast && isContracted) {
                    var pages = _self.getPages();
                    for (var i = 0, l = pages.length; i < l; i++) {
                        var tab = pages[i];
                        tab.$button.style.minWidth = "";
                        tab.$button.style.maxWidth = "";
                    }
                }
                
                if (_self.$waitForMouseOut == 2) {
                    apf.removeListener(_self.$buttons, "mouseout", btnMoHandler);
                    delete _self.$waitForMouseOut;
                }
                else if (isLast)
                    delete _self.$waitForMouseOut;
            };
            
            var pages = this.getPages();
            
            var lNode = pages[pages.length - 1];
            while (lNode && (!lNode.$button || lNode.$button.style.top)) {
                lNode = lNode.previousSibling;
            }
            if (!lNode) return;
            
            var isLast = lNode == node;
            var isContracted = (node.$button.offsetWidth - apf.getWidthDiff(node.$button) 
                != parseInt(apf.getStyle(node.$button, "maxWidth")));
            
            if (!isLast && isContracted) {
                for (var i = 0, l = pages.length; i < l; i++) {
                    var tab = pages[i];
                    tab.$button.style.minWidth = 
                    tab.$button.style.maxWidth = (tab.$button.offsetWidth 
                        - (apf.isGecko ? 0 : apf.getWidthDiff(tab.$button))) 
                        + "px";
                }
            }
            
            var isCur = this.$activepage == node;
                
            //Set activetab if the current one is lost
            if (_self.nextTabInLine) {
                _self.set(_self.nextTabInLine);
                delete _self.nextTabInLine;
            }
            else if (_self.$activepage == node) {
                var ln = node.nextSibling;
                while (ln && (!ln.$first || !ln.visible))
                    ln = ln.nextSibling;
                var rn = node.previousSibling;
                while (rn && (!rn.$last || !rn.visible))
                    rn = rn.previousSibling;
                if (ln || rn)
                    _self.set(ln || rn);
            }
            
            if (isCur) {
                apf.setStyleClass(node.$button, "curbtn");
                node.$button.style.zIndex = 0;
            }
            
            if (pages.length == 1)
                (node.relPage || node).$ext.style.display = "none";
            
            animRemoveTab(node, isLast, isContracted, onfinish, pages.length == 1);
            
            this.$waitForMouseOut = true;
            if (!isLast)
                apf.addListener(_self.$buttons, "mouseout", btnMoHandler);
        }
    }
    
    /*
     * Update the size of the pane container
     */
    function scalersz(e, excl) {
        if (!this.length && !this.getPages().length || this.$waitForMouseOut)
            return;
        
        var p = apf.isGecko ? this.$buttons.parentNode : this.$buttons;
        
        p.style[apf.CSSPREFIX + "TransitionProperty"] = "padding-right";
        p.style[apf.CSSPREFIX + "TransitionDuration"] = ".2s";
        p.style[apf.CSSPREFIX + "TimingFunction"] = "ease-out";
        
        if (apf.isGecko) {
            p.style.paddingRight = apf.getWidthDiff(this.$buttons) + "px";
        }
        else {
            p.style.paddingRight = "";
        }
        
        setTimeout(function(){
            p.style[apf.CSSPREFIX + "TransitionProperty"] = "";
            p.style[apf.CSSPREFIX + "TransitionDuration"] = "";
            p.style[apf.CSSPREFIX + "TimingFunction"] = "";
        }, 250);
    }
    

    // *** Public methods *** //

    

    /**
     * Retrieves an array of all the tab elements of this element.
     * @returns {Array} An array of all the {apf.tab} elements
     */
    this.getPages = function(){
        var r = [], nodes = this.childNodes;
        if (!nodes) return r;
        for (var i = 0, l = nodes.length; i < l; i++) {
            if ("tab|case".indexOf(nodes[i].localName) > -1 && nodes[i].visible !== false)
                r.push(nodes[i]);
        }
        return r;
    };

    /**
     * Retrieves a tab element by its name or child number.
     * @param {String | Number} nameOrId The name or child number of the tab element to retrieve.
     * @return {apf.tab} The found tab element.
     */
    this.getPage = function(nameOrId) {
        if (apf.isNot(nameOrId))
            return this.$activepage;
        else
            return this.$findPage(nameOrId);
    };

    /**
     * Adds a new tab element
     * @param {String} [caption] The text displayed on the button of the tab
     * @param {String} [name]    The name of the tab which is can be referenced by
     * @param {String} [type]    The type of the tab
     * @param {apf.tab} [insertBefore]   The tab to insert ahead of; `null` means to put it at the end
     * @param {Function} [callback]   A callback to call and pass the new tab to
     * @return {apf.tab} The created tab element.
     */
    this.add = function(caption, name, type, before, callback) {
        var tab = this.ownerDocument.createElementNS(apf.ns.aml, "tab");
        if (name)
            tab.setAttribute("id", name);
        if (type)
            tab.setAttribute("type", type);
        if (caption)
            tab.setAttribute("caption", caption);
        
        if (callback)
            callback(tab);
            
        this.insertBefore(tab, before);
        
        
        //this.scrollIntoView(tab);
        
        return tab;
    };

    /**
     * Removes a tab element from this element. This function destroys ALL children
     * of this tab. To simple remove the tab from the DOM tree, use the
     * [[apf.AmlNode.removeNode]] method.
     *
     * @param {Mixed} nameOrId The name or child number of the tab element to remove
     * @return {apf.tab} The removed tab element
     */
    this.remove = function(nameOrId, force, noAnimation) {
        var tab = typeof nameOrId == "object" 
            ? nameOrId 
            : this.$findPage(nameOrId);
        if (!tab)
            return false;

        var e = {tab: tab};
        if (typeof force == "object") {
            e.htmlEvent = force;
            force = false;
        }

        if (!force && this.dispatchEvent("close", e) === false)
            return;

        if (this.$scale && !noAnimation) {
            this.$scaleinit(tab, "remove", function(){
                //tab.removeNode();
                tab.destroy(true, true);
            }, true);
        }
        else 
        
        {
            //tab.removeNode();
            if (tab.dispatchEvent("afterclose") !== false)
                tab.destroy(true, true);
            
            
            //@todo this is wrong, we can also use removeChild
            //this.setScrollerState();
            
        }
        
        return tab;
    };

    
    /*
    var SCROLLANIM = {
            scrollOn: false,
            steps: 15,
            interval: 10,
            size: 0,
            left: 0,
            control: {
                stop: false
            },
            stopHandle: function() {
                bAnimating = false;
            }
        },
        SCROLL_OFF = 0x0001,
        SCROLL_HOVER = 0x0002,
        SCROLL_DOWN = 0x0004,
        SCROLL_DIS = 0x0008,
        SCROLL_L_STATE = SCROLL_OFF,
        SCROLL_R_STATE = SCROLL_OFF,
        SCROLL_LEFT = 0x0001,
        SCROLL_RIGHT = 0x0002,
        SCROLL_BOTH = 0x0004,
        bAnimating = false,
        scrollTimer = null,
        keepScrolling = false,
        globalDir = SCROLL_LEFT;
*/
    function getButtonsWidth() {
        var cId = "cache_" + this.$buttons.childNodes.length;
        if (SCROLLANIM[cId])
            return SCROLLANIM[cId];

        var iWidth = 0;
        for (var i = 0, l = this.$buttons.childNodes.length; i < l; i++) {
            if (typeof this.$buttons.childNodes[i].offsetWidth != "undefined")
                iWidth += this.$buttons.childNodes[i].offsetWidth;
        }

        return SCROLLANIM[cId] = iWidth;
    }

    function setButtonState(dir, state) {
        var bBoth = dir & SCROLL_BOTH;
        if (bBoth)
            dir = SCROLL_LEFT;
        var oBtn = this[dir & SCROLL_LEFT ? "oLeftScroll" : "oRightScroll"];
        if (!(state & SCROLL_DIS)) {
            if (dir & SCROLL_LEFT)
                SCROLL_L_STATE = state;
            else
                SCROLL_R_STATE = state;
        }
        
        if (state & SCROLL_OFF)
            apf.setStyleClass(oBtn,  "", ["disabled", "hover", "down"]);
        else if (state & SCROLL_HOVER)
            apf.setStyleClass(oBtn,  "hover", ["disabled", "down"]);
        else if (state & SCROLL_DOWN)
            apf.setStyleClass(oBtn,  "down", ["disabled", "hover"]);
        else if (state & SCROLL_DIS)
            apf.setStyleClass(oBtn,  "disabled", ["hover", "down"]);

        if (bBoth)
            setButtonState(SCROLL_RIGHT, state);
    }

    /**
     * Sets the state scroller buttons: `enabled`, `disabled` or completely `hidden`,
     * depending on the state of the pane buttons
     *
     * @param {Boolean} [bOn]   Indicates whether to turn the scroll buttons on or off
     * @param {Number}  [iBtns] Specifies the buttons to set the state of. Can be [[apf.BaseTab.SCROLL_LEFT]], [[apf.BaseTab.SCROLL_RIGHT]] or [[apf.BaseTab.SCROLL_BOTH]]
     * 
     */
    this.setScrollerState = function(bOn, iBtns) {
        if (!this.ready || !this.$hasButtons || !this.oScroller) return;

        if (typeof bOn == "undefined") {
            var scrollerWidth = this.oScroller.offsetWidth
                || parseInt(apf.getStyle(this.oScroller, "width").replace(/(px|em|%)/, ""));
            bOn = ((getButtonsWidth.call(this) + scrollerWidth) > this.$ext.offsetWidth);
            iBtns = SCROLL_BOTH;
        }

        if (iBtns & SCROLL_BOTH && bOn !== SCROLLANIM.scrollOn) {
            // in case of HIDING the scroller: check if the anim stuff has reverted
            SCROLLANIM.scrollOn = bOn;
            if (!bOn) {
                this.$buttons.style.left = SCROLLANIM.left + "px";
                this.oScroller.style.display = "none";
            }
            //else
            //    TODO: scroll active pane into view if it becomes hidden beneath scroller node(s)
        }
        else {
            this.oScroller.style.display = "";
        }

        this.oScroller.style.display = (iBtns & SCROLL_BOTH && !bOn)
            ? "none"
            : "";
        if (typeof iBtns == "undefined")
            iBtns = SCROLL_BOTH;
        if (!bOn) {
            if ((iBtns & SCROLL_LEFT) || (iBtns & SCROLL_BOTH))
                setButtonState.call(this, SCROLL_LEFT, SCROLL_DIS);
            if ((iBtns & SCROLL_RIGHT) || (iBtns & SCROLL_BOTH))
                setButtonState.call(this, SCROLL_RIGHT, SCROLL_DIS);
        }
    };

    /**
     * Corrects the state of the scroller buttons when the state of external
     * components change, like on a resize event of a window.
     *
     */
    this.correctScrollState = function() {
        if (!this.ready || !this.$hasButtons || !this.oScroller) return;
        this.setScrollerState();
    };

    /**
     * Retrieves the utmost left or right boundaries of the pane buttons strip that
     * can be scrolled to. The tabs cannot scroll any further than these boundaries
     *
     * @param {Number} dir        Determines which boundary side to look at, either [[apf.BaseTab.SCROLL_LEFT]] or [[apf.BaseTab.SCROLL_RIGHT]]
     * @param {Boolean} [useCache] Used only when tabs are draggable. Not implemented.
     * @returns  {Number}
     */
    function getAnimationBoundary(dir, useCache) {
        if (SCROLLANIM.size <= 0) {
            SCROLLANIM.left = this.$buttons.offsetLeft;
            SCROLLANIM.size = Math.round(this.firstChild.$button.offsetWidth);
        }
        if (dir & SCROLL_LEFT) {
            return SCROLLANIM.left;
        }
        else if (dir & SCROLL_RIGHT) {
            // TODO: support Drag n Drop of tabs...
            //if (typeof useCache == "undefined") useCache = false;
            //if (!tabcontrol.drag) tabcontrol.drag = {};
            //if (useCache && tabcontrol.drag.boundCache)
            //    return tabcontrol.drag.boundCache;
            var oNode = this.$buttons.childNodes[this.$buttons.childNodes.length - 1];

            return this.$ext.offsetWidth - (oNode.offsetLeft + oNode.offsetWidth
                + (this.oScroller.offsetWidth + 4));// used to be tabcontrol.drag.boundCache;
        }
    }

    /**
     * @event scroll Executed when the user presses one of the two scroll buttons
     * (left or right). 
     * 
     * If the pane-buttons strip can be scrolled, the
     * respective behavior is called.
     *
     * @param {Event}  e   An event object, usually a mousedown event from a scroller-button
     * @param {Number} dir The direction of the scroll, either [[apf.BaseTab.SCROLL_LEFT]] or [[apf.BaseTab.SCROLL_RIGHT]]
     *
     */
    this.scroll = function(e, dir) {
        if (!this.ready || !this.$hasButtons || !this.oScroller) return;
        if (!e)
            e = window.event;
        if (typeof e["type"] == "unknown") //scope expired (prolly GC'ed)
            e = {type: "click"};
        if (bAnimating && e.type != "dblclick") return;
        var bAnimating = true;

        if (typeof dir == "undefined")
            dir = SCROLL_LEFT;

        //apf.tween.clearQueue(this.$buttons, true);
        var iCurrentLeft = this.$buttons.offsetLeft,
            size = e["delta"] ? Math.round(e.delta * 36) : SCROLLANIM.size,
            //get maximum left offset for either direction
            iBoundary = getAnimationBoundary.call(this, dir),
            _self = this;
        if (dir & SCROLL_LEFT) {
            setButtonState(SCROLL_LEFT,  SCROLL_DOWN);
            setButtonState(SCROLL_RIGHT, SCROLL_OFF);
            if (iCurrentLeft === iBoundary) {
                this.setScrollerState(false, SCROLL_LEFT);
                return apf.tween.single(this.$buttons, {
                    steps: SCROLLANIM.steps,
                    interval: 20,
                    from: iCurrentLeft,
                    to: iCurrentLeft + 12,
                    type: "left",
                    anim: apf.tween.EASEOUT,
                    onstop: SCROLLANIM.stopHandle,
                    onfinish: function(oNode) {
                        apf.tween.single(oNode, {
                            steps: SCROLLANIM.steps,
                            interval: SCROLLANIM.interval,
                            from: iCurrentLeft + 12,
                            to: iCurrentLeft,
                            type: "left",
                            anim: apf.tween.EASEIN,
                            onstop: SCROLLANIM.stopHandle,
                            onfinish: function() {
                                bAnimating = false;
                                if (e.name == "mousescroll")
                                    setButtonState(SCROLL_LEFT, SCROLL_OFF);
                            }
                        });
                    }
                });
            }
            //one scroll animation scrolls by a SCROLLANIM.size px.
            var iTargetLeft = iCurrentLeft + (e.type == "dblclick" ? size * 3 : size);
            if (iTargetLeft > iBoundary)
                iTargetLeft = iBoundary;

            if (iTargetLeft === iBoundary)
                this.setScrollerState(false, SCROLL_LEFT);
            this.setScrollerState(true, SCROLL_RIGHT);

            //start animated scroll to the left
            apf.tween.single(this.$buttons, {
                steps: SCROLLANIM.steps,
                interval: SCROLLANIM.interval,
                control: SCROLLANIM.control,
                from: iCurrentLeft,
                to: iTargetLeft,
                type: "left",
                anim: apf.tween.NORMAL,
                onstop: SCROLLANIM.stopHandle,
                onfinish: function() {
                    bAnimating = false;
                    if (e.name == "mousescroll")
                        setButtonState(SCROLL_LEFT, SCROLL_OFF);
                    if (keepScrolling)
                        _self.scroll(e, globalDir);
                }
            });
        }
        else if (dir & SCROLL_RIGHT) {
            this.setScrollerState(true);
            setButtonState(SCROLL_RIGHT, SCROLL_DOWN);
            setButtonState(SCROLL_LEFT,  SCROLL_OFF);
            if (iCurrentLeft === iBoundary) {
                this.setScrollerState(false, SCROLL_RIGHT);
                return apf.tween.single(this.$buttons, {
                    steps: SCROLLANIM.steps,
                    interval: 20,
                    from: iCurrentLeft,
                    to: iCurrentLeft - 24,
                    type: "left",
                    anim: apf.tween.EASEOUT,
                    onstop: SCROLLANIM.stopHandle,
                    onfinish: function(oNode, options) {
                        apf.tween.single(oNode, {
                            steps: SCROLLANIM.steps,
                            interval: SCROLLANIM.interval,
                            from: iCurrentLeft - 24,
                            to: iCurrentLeft,
                            type: "left",
                            anim: apf.tween.EASEIN,
                            onstop: SCROLLANIM.stopHandle,
                            onfinish: function() {
                                bAnimating = false;
                                if (e.name == "mousescroll")
                                    setButtonState(SCROLL_RIGHT, SCROLL_OFF);
                            }
                        });
                    }
                });
            }
            //one scroll animation scrolls by a SCROLLANIM.size px.
            var iTargetLeft = iCurrentLeft - (e.type == "dblclick" ? size * 3 : size);
            //make sure we don't scroll more to the right than the
            //maximum left:
            if (iTargetLeft < iBoundary)
                iTargetLeft = iBoundary;
            //start animated scroll to the right
            apf.tween.single(this.$buttons, {
                steps: SCROLLANIM.steps,
                interval: SCROLLANIM.interval,
                control: SCROLLANIM.control,
                from: iCurrentLeft,
                to: iTargetLeft,
                type: "left",
                anim: apf.tween.NORMAL,
                onstop: SCROLLANIM.stopHandle,
                onfinish: function() {
                    bAnimating = false;
                    if (e.name == "mousescroll")
                        setButtonState(SCROLL_RIGHT, SCROLL_OFF);
                    if (keepScrolling)
                        _self.scroll(e, globalDir);
                }
            });
        }
    };

    /**
     * If a pane tab is outside of the user's view, this function scrolls that
     * tabpage into view smoothly.
     *
     * @param {apf.tab} oPage The tab to scroll into view
     * 
     */
    this.scrollIntoView = function(oPage) {
        bAnimating = false;
        if (!this.ready || !this.$hasButtons || !this.oScroller || !oPage.$drawn)
            return;
        bAnimating = true;
        if (this.$buttons.offsetWidth < this.$ext.offsetWidth)
            return this.setScrollerState(false);

        var iTabLeft = oPage.$button.offsetLeft,
            iTabWidth = oPage.$button.offsetWidth,
            iCurrentLeft = this.$buttons.offsetLeft;

        if (SCROLLANIM.size <= 0) {
            SCROLLANIM.left = this.$buttons.offsetLeft;
            var p = this.firstChild;
            while (!p.$button)
                p = p.nextSibling;
            SCROLLANIM.size = Math.round(p.$button.offsetWidth);
        }
        this.$buttons.style.left = iCurrentLeft;

        var iRealWidth = this.$ext.offsetWidth,
            iScrollCorr = this.oScroller.offsetWidth + 4,
            iTargetLeft = null,
            dir;

        if ((iTabLeft + iTabWidth) > ((iRealWidth - iScrollCorr) - iCurrentLeft)) { //scroll to the right
            iTargetLeft = (-(iTabLeft - SCROLLANIM.left)
                + (iRealWidth - iTabWidth - iScrollCorr));
            dir = SCROLL_RIGHT;
        }
        else if ((iCurrentLeft + iTabLeft) < SCROLLANIM.left) { //sroll to the left
            iTargetLeft = SCROLLANIM.left - iTabLeft;
            dir = SCROLL_LEFT;
        }

        if (iTargetLeft !== null) {
            this.setScrollerState(true);
            setButtonState(SCROLL_RIGHT, dir & SCROLL_RIGHT ? SCROLL_DOWN : SCROLL_OFF);
            setButtonState(SCROLL_LEFT,  dir & SCROLL_LEFT  ? SCROLL_DOWN : SCROLL_OFF);
            apf.tween.clearQueue(this.$buttons, true);

            apf.tween.single(this.$buttons, {
                steps: SCROLLANIM.steps,
                interval: SCROLLANIM.interval,
                from: iCurrentLeft,
                to: iTargetLeft,
                type: "left",
                anim: apf.tween.NORMAL,
                onstop: SCROLLANIM.stopHandle,
                onfinish: function() {
                    bAnimating = false;
                    setButtonState(SCROLL_RIGHT, SCROLL_OFF);
                    setButtonState(SCROLL_LEFT,  SCROLL_OFF);
                }
            });
        }
        else
            bAnimating = false;
    };

    

    // *** DOM Hooks *** //

    this.addEventListener("DOMNodeRemoved", function(e) {
        var amlNode = e.currentTarget;
        if (e.$doOnlyAdmin || e.relatedNode != this 
          || amlNode.localName != "tab")
            return;
        
        if ((this.activepage || this.activepage == 0) && this.activepage != -1) {
            if (!this.getPage(this.nextTabInLine))
                this.nextTabInLine = null;
            
            if (this.nextTabInLine)
                this.set(this.nextTabInLine);
            
            if (!this.nextTabInLine && this.$activepage == amlNode) {
                var ln = amlNode.nextSibling;
                while (ln && (!ln.$first || !ln.visible))
                    ln = ln.nextSibling;
                var rn = amlNode.previousSibling;
                while (rn && (!rn.$last || !rn.visible))
                    rn = rn.previousSibling;
        
                if (this.firstChild == amlNode && ln)
                    ln && ln.$first();
                if (this.lastChild == amlNode && rn)
                    rn && rn.$last();
                
                if (ln || rn)
                    this.set(ln || rn);
                else {
                    amlNode.$deactivate();
                    
                    
                    //this.setScrollerState();
                    
                    this.$activepage = 
                    this.activepage = 
                    this.activepagenr = null;
                    this.setProperty("activepage", null);
                }
            }
            else {
                
                //if (this.$scroll) 
                    //this.setScrollerState();
                
                
                if (this.$scale) 
                    this.$scaleinit();
                
            }
            
            delete this.nextTabInLine;
        }

        
        this.setProperty("length", this.getPages().length - 1);
        
    });

    this.addEventListener("DOMNodeInserted",function(e) {
        var amlNode = e.currentTarget;

        if (amlNode.localName != "tab" || e.relatedNode != this || amlNode.nodeType != 1)
            return;

        var pages = this.getPages();

        if (!e.$beforeNode) {
            var lastChild, pg = pages;
            if (lastChild = pg[pg.length - 2])
                lastChild.$last(true);
            amlNode.$last();
        }
    
        var p2, p = pages[0]; //@todo $beforeNode doesnt have to be a tab
        if (amlNode == p) {
            if (p2 = this.getPage(1))
                p2.$first(true);
            amlNode.$first();
        }

        if (this.$activepage) {
            var info = {};
            this.$findPage(this.$activepage, info);

            if (this.activepagenr != info.position) {
                if (parseInt(this.activepage) == this.activepage) {
                    this.activepage = info.position;
                    this.setProperty("activepage", info.position);
                }
                this.activepagenr = info.position;
                this.setProperty("activepagenr", info.position);
            }
        }
        else if (!this.activepage && !this.$activepage 
          && !amlNode.render || amlNode.$rendered) {
            this.set(amlNode);
        }
        
        
        if (this.$scale && amlNode.visible && !e.$isMoveWithinParent) 
            this.$scaleinit(amlNode, "add");
        else 
        
        {
            amlNode.dispatchEvent("afteropen");
        }
        
        
        this.setProperty("length", this.getPages().length);
        
    });

    // *** Private state handling functions *** //

    this.$findPage = function(nameOrId, info) {
        var node, nodes = this.childNodes;
        
        if (nameOrId.localName) {
            for (var t = 0, i = 0, l = nodes.length; i < l; i++) {
                node = nodes[i];
                if ("tab|case".indexOf(node.localName) > -1 && (++t) && node == nameOrId) {
                    if (info)
                        info.position = t - 1;
                    return node;
                }
            }
        }
        else {
            for (var t = 0, i = 0, l = nodes.length; i < l; i++) {
                node = nodes[i];
                if ("tab|case".indexOf(node.localName) > -1 && (t++ == nameOrId
                  || node.name == nameOrId)) {
                    if (info)
                        info.position = t - 1;
                    return node;
                }
            }
        }
        
        return null;
    };

    this.$enable = function(){
        var nodes = this.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++) {
            if (nodes[i].enable)
                nodes[i].enable();
        }
    };

    this.$disable = function(){
        var nodes = this.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++) {
            if (nodes[i].disable)
                nodes[i].disable();
        }
    };

    // *** Keyboard support *** //

    

    

    

    // *** Init *** //

    this.$loadChildren = function(callback) {
        var tab = false,
            _self = this,
            i, j, l, node, nodes;

        this.inited = true;

        if (this.$hasButtons) {
            this.$buttons = this.$getLayoutNode("main", "buttons", this.$ext);
            this.$buttons.setAttribute("id", this.$uniqueId + "_buttons");
            
            if (apf.isGecko && !this.$gotContainer) {
                var div = this.$ext.appendChild(document.createElement("div"));
                div.style.backgroundImage = apf.getStyle(this.$buttons, "backgroundImage");
                div.style.backgroundColor = apf.getStyle(this.$buttons, "backgroundColor");
                div.style.position = "absolute";
                div.style.left = 0;
                div.style.top = 0;
                div.style.right = 0;
                div.style.overflow = "hidden";
                div.style.height = this.$buttons.offsetHeight + "px";
                div.appendChild(this.$buttons);
                this.$buttons.style.width = "100%";
                div.style.paddingRight = apf.getWidthDiff(this.$buttons) + "px";
                
                this.$gotContainer = true;
            }
        }

        this.oPages = this.$getLayoutNode("main", "pages", this.$ext);
        
        
        // add scroller node(s)
        /*this.oScroller = this.$getLayoutNode("main", "scroller", this.oPages);
        if (this.oScroller) {
            function startTimer(e, dir) {
                clearTimeout(scrollTimer);
                globalDir = dir;
                scrollTimer = $setTimeout(function() {
                    keepScrolling = true;
                    _self.scroll(e, dir);
                }, 500);
            }
            function stopTimer() {
                clearTimeout(scrollTimer);
                keepScrolling = false;
            }

            this.oScroller.onmouseout = function(e) {
                SCROLLANIM.control.stop = true;
                setButtonState(SCROLL_BOTH, SCROLL_OFF);
            };

            
            /*apf.addEventListener("mousescroll", function(e) {
                var found = (e.target == _self.$buttons);
                while (!found && e.target != document.body) {
                    e.target = e.target.offsetParent;
                    found = (e.target == _self.$buttons);
                }
                if (!found) return;
                var dir = e.delta > 0 ? SCROLL_LEFT : SCROLL_RIGHT;
                e.delta = Math.abs(e.delta);
                _self.scroll(e, dir);
            });* /
            

            this.oLeftScroll = apf.getNode(this.oScroller, [0]);
            this.oRightScroll = apf.getNode(this.oScroller, [1]);
            
            ["oLeftScroll", "oRightScroll"].forEach(function(sBtn) {
                var dir = sBtn == "oLeftScroll" ? SCROLL_LEFT  : SCROLL_RIGHT,
                    revDir = sBtn == "oLeftScroll" ? SCROLL_RIGHT : SCROLL_LEFT;

                _self[sBtn].ondbclick = 
                _self[sBtn].onmousedown = function(e) {
                    SCROLLANIM.control.stop = false;
                    var state = dir & SCROLL_LEFT ? SCROLL_L_STATE : SCROLL_R_STATE;
                    if (this.className.indexOf("disabled") != -1
                      || state & SCROLL_DOWN) return;
                    e = e || event;
                    _self.scroll(e, dir);
                    startTimer(e, dir);
                    if (!apf.isSafariOld)
                        this.onmouseout();
                };
                _self[sBtn].onmouseover = function() {
                    SCROLLANIM.control.stop = false;
                    var state = dir & SCROLL_LEFT ? SCROLL_L_STATE : SCROLL_R_STATE;
                    if (this.className.indexOf("disabled") != -1
                      || state & SCROLL_DOWN) return;
                    setButtonState(dir, SCROLL_HOVER);
                    setButtonState(revDir, SCROLL_OFF);
                    globalDir = dir;
                };
                _self[sBtn].onmouseout = function() {
                    var state = dir & SCROLL_LEFT ? SCROLL_L_STATE : SCROLL_R_STATE;
                    if (this.className.indexOf("disabled") != -1
                      || state & SCROLL_DOWN) return;
                    setButtonState(dir, SCROLL_OFF);
                };
                _self[sBtn].onmouseup = function() {
                    if (this.className.indexOf("disabled") == -1) {
                        setButtonState(dir, SCROLL_OFF);
                    }
                    stopTimer();
                    SCROLLANIM.control.stop = true;
                };
            });
        }

        
        apf.layout.setRules(this.$ext, this.$uniqueId + "_tabscroller",
            "var o = apf.all[" + this.$uniqueId + "]; o && o.correctScrollState()");
        apf.layout.queue(this.$ext);*/
        
        

        //Skin changing support
        if (this.$int) {
            //apf.AmlParser.replaceNode(this.oPages, oPages);
            this.$int = this.oPages;
            tab = true;

            //@todo apf3.0 skin change?
            nodes = this.childNodes;
            for (i = 0; i < nodes.length; i++) {
                node = nodes[i];
                if (node.nodeType != 1)
                    continue;
                node.$draw(true);
                if (node.$skinchange)
                    node.$skinchange();
                node.$loadAml();
            }
        }
        else {
            this.$int = this.oPages;

            //Build children
            nodes = this.getPages();
            if (nodes.length) {
                nodes[0].$first();
                (node = nodes[nodes.length - 1]).$last();
            }
        }

        //Set active tab
        if (node) {
            this.activepage = (typeof this.activepage != "undefined"
                ? this.activepage
                : this.activepagenr) || 0;
            tab = this.getPage(this.activepage);
            if (!tab.render || tab.$rendered)
                this.$propHandlers.activepage.call(this, this.activepage);
        }
        else {
            this.isPages = false;
        }

        
        this.setProperty("length", this.getPages().length);
        

        this.ready = true;
        
        /*window.setTimeout(function() {
            _self.setScrollerState();
        }, 0);*/
        

        if (!this.activepage && this.getAttribute("src")) {
            this.src = this.getAttribute("src");
            this.$propHandlers["activepage"].call(this);
        }
    };
    
    this.$destroy = function(bSkinChange) {
        if (bSkinChange || !this.oScroller)
            return;
        
        
        /*apf.layout.removeRule(this.$ext, this.$uniqueId + "_tabscroller");
        
        [this.oLeftScroll, this.oRightScroll].forEach(function(oBtn) {
            oBtn.onmousedown = oBtn.ondblclick = oBtn.onmouseover = 
            oBtn.onmouseout = oBtn.onmouseup = null;
        });*/
        
    };
}).call(apf.BaseTab.prototype = new apf.Presentation());






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Baseclass of elements that allows the user to select one or more items
 * from a tree based element.
 *
 * @class apf.BaseTree
 * @baseclass
 *
 * @inherits apf.MultiSelect
 * @inheritsElsewhere apf.XForms
 * @inherits apf.Cache
 * @inherits apf.DataAction
 * @inherits apf.Rename
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 * @default_private
 *
 */
apf.BaseTree = function(){
    this.$init(true);

    
    this.$dynCssClasses = [];
    

    this.$nodes = [];
};

(function() {
    
    this.implement(
        
        
        apf.Rename,
        
        
        apf.DataAction,
        
        
        apf.Cache,
        
        apf.K
    );
    

    // *** Properties and Attributes *** //

    //Options
    this.$isTreeArch = true; // This element has a tree architecture.
    this.$focussable = true; // This object can get the focus.
    this.multiselect = false; // Initially multiselect is disabled.
    this.bufferselect = true;

    this.startcollapsed = true;
    this.$animType = apf.tween.NORMAL;
    this.$animOpenStep = 3;
    this.$animCloseStep = 1;
    this.$animSpeed = 10;

    var HAS_CHILD = 1 << 1,
        IS_CLOSED = 1 << 2,
        IS_LAST = 1 << 3,
        IS_ROOT = 1 << 4;

    var treeState = this.$treeState = {};
    this.$treeState[0] = "";
    this.$treeState[HAS_CHILD] = "min";
    this.$treeState[HAS_CHILD | IS_CLOSED] = "plus";
    this.$treeState[IS_LAST] = "last";
    this.$treeState[IS_LAST | HAS_CHILD] = "minlast";
    this.$treeState[IS_LAST | HAS_CHILD | IS_CLOSED] = "pluslast";
    this.$treeState[IS_ROOT] = "root";

    // *** Properties and Attributes *** //

    /**
     * @attribute {Boolean} openadd         Sets or gets whether the tree expands the parent to which a node is added. Defaults to `true`.
     */
    /**
     * @attribute {Boolean} startcollapsed  Sets or gets whether the tree collapses all nodes that contain children on load. Defaults to `true`.
    */
    /**
     *  @attribute {Boolean} nocollapse      Sets or gets whether the user cannot collapse a node. Defaults to `false`.
     */
    /**
     *  @attribute {Boolean} singleopen      Sets or gets whether the tree will expand a node by a single click. Defaults to `false`.
     */
    /**
     *  @attribute {Boolean} prerender       Sets or gets whether the tree will render all the nodes at load. Defaults to `true`.
     */
    /**
     *  @attribute {Boolean} disableremove   Sets or gets whether the tree disallows removing nodes with the keyboard, by pressing [[keys: Del]]. Defaults to `false`.
     */
    this.$booleanProperties["openadd"] = true;
    this.$booleanProperties["startcollapsed"] = true;
    this.$booleanProperties["nocollapse"] = true;
    this.$booleanProperties["singleopen"] = true;
    this.$booleanProperties["animation"] = true;
    this.$booleanProperties["prerender"] = true;
    this.$booleanProperties["removecontainer"] = true;
    this.$booleanProperties["dragroot"] = true;
    this.$booleanProperties["disableremove"] = true;

    this.$supportedProperties.push("openadd", "startcollapsed", "nocollapse",
        "singleopen", "prerender", "removecontainer", "animation", "dragroot",
        "disableremove");

    this.openadd = true;
    this.startcollapsed = 1;
    this.prerender = true;
    this.disableremove = false;

    // *** Public Methods *** //

    /**
     * Expands all items in the tree.
     */
    this.expandAll = function(){
        if (!this.xmlRoot)
            return;

        var xpath = this.each.split('|')
                        .join('[' + this.each.replace(/\|/g, " or ") + ']|.//'),
            _self = this;
        (function(node) {
            var nodes = node.selectNodes(xpath);
            //for (var i = nodes.length - 1; i >= 0; i--) {
            for (var o, i = 0; i < nodes.length; i++) {
                if (o = apf.xmldb.getHtmlNode(nodes[i], _self))
                    _self.slideToggle(o, 1, true);
                arguments.callee(nodes[i]);
            }
        })(this.xmlRoot);
    };

    /**
     * Collapses all items in the tree.
     */
    this.collapseAll = function(){
        if (!this.xmlRoot)
            return;

        var pNodes = this.xmlRoot.selectNodes(".//" + this.each
          .split('|').join('[' + this.each.replace(/\|/g, " or ") + ']|.//'));

        for (var o, i = pNodes.length - 1; i >=0; i--) {
            if (o = apf.xmldb.getHtmlNode(pNodes[i], this))
                this.slideToggle(o, 2, true);
        }
    };

    /**
     * Collapse a single node in the tree.
     * - xmlNode ([[XMLNode]]): The node to collapse
     */
    this.collapse = function(xmlNode) {
        if (!this.xmlRoot)
            return;

        if (this.isCollapsed(xmlNode))
            return;
        var htmlNode = apf.xmldb.getHtmlNode(xmlNode, this);
        if (!htmlNode)
            return;
        this.slideToggle(htmlNode, 2, true);
    };

    /**
     * Selects a node and expands each parent of it.
     * - xmlNode ([[XMLNode]]): The node to expand
     */
    this.expandAndSelect = function(xmlNode) {
        if (!this.xmlRoot)
            return;

        var _self = this;
        if ((function _recur(loopNode) {
            var pNode = _self.getTraverseParent(loopNode);
            if (pNode == _self.xmlRoot)
                return true;

            if (!pNode || _recur(pNode) === false)
                return false;

            _self.slideToggle(apf.xmldb.getHtmlNode(pNode, _self), 1, true);
        })(xmlNode) !== false)
            this.select(xmlNode);
    };

    /**
     * Loads a list of folders.
     * paths {[String]} Array of strings in the form of `'folder[1]/folder[2]'` representing a list of folders
     * onFinished {Function} A callback to be called when finished
     */
    this.expandList = function (paths, onFinished) {
        var _self = this;
        var root = this.xmlRoot;

        // recursive load function
        function expand(currentSelector, allSelectors) {
            var selectorNode = root.selectSingleNode(currentSelector);

            // if the node could not be found,
            // just tell the consumer that it has expanded
            if (!selectorNode) {
                return hasExpanded(currentSelector);
            }

            // first expand the item passed in
            _self.slideToggle(apf.xmldb.getHtmlNode(selectorNode, _self), 1, true, null, function () {
                // the number of times the callback has fired, prevent it from executing forever
                var timesRan = 0;

                // the callback fires, but we might be waiting for data from the server
                var callback = function () {
                    // check whether the node is loaded
                    if (!_self.$hasLoadStatus(root.selectSingleNode(currentSelector), "loaded")) {
                        // if not, retry after a second (max 3 times)
                        if (++timesRan < 3) {
                            return setTimeout(function () {
                                _self.slideToggle(apf.xmldb.getHtmlNode(selectorNode, _self), 1, true, null, callback);
                            }, 1000);
                        }
                        else {
                            // still failing? call 'hasExpanded' for this one and all its children
                            // cause we're ignoring everything from now;
                            // and we need the callback to be fired anyway
                            var allAncestors = allSelectors.filter(function (s) {
                                return s.indexOf(currentSelector + "/") === 0;
                            });

                            hasExpanded(currentSelector);
                            allAncestors.forEach(function (sel) {
                                hasExpanded(sel);
                            });

                            return null;
                        }
                    }

                    // notify
                    hasExpanded();

                    // when finished, find all the other selectors that start with the current selector
                    // plus a slash to make it really really sure
                    // plus we check whether it's a child and not a grand child
                    var childSelectors = allSelectors.filter(function (s) {
                        return s.indexOf(currentSelector + "/") === 0
                                && currentSelector.split("/").length + 1 === s.split("/").length;
                    });

                    // then expand each of the child items
                    childSelectors.forEach(function (selector) {
                        expand(selector, allSelectors);
                    });
                };
                callback();
            });
        }

        // function to be called when an item has expanded, used to determine whether we finished
        var expandCount = 0;
        function hasExpanded() {
            // if we have expanded all items, invoke the callback
            if (++expandCount === paths.length) {
                onFinished();
            }
        }

        // find all rootNodes (nodes without a slash in them)
        var rootNodes = paths.filter(function (p) { return p.split("/").length === 1; });

        // expand all root nodes, expand will recursively expand all child items
        rootNodes.forEach(function (node) {
            expand(node, paths);
        });
    };

    /*
     * @notimplemented
     * @todo who's volunteering?
     * @private
     */
    this.selectPath = function(path) {};

    // *** Sliding functions *** //

    /*
     * @private
     */
    this.slideToggle = function(htmlNode, force, immediate, userAction, callback) {
        callback = typeof callback === "function" ? callback : function () {};

        if (this.nocollapse || userAction && this.disabled)
            return callback();

        if (!htmlNode)
            htmlNode = this.$selected;

        if (!htmlNode)
            return callback();

        var id = htmlNode.getAttribute(apf.xmldb.htmlIdTag);
        while (!id && htmlNode.parentNode)
            id = (htmlNode = htmlNode.parentNode)
                .getAttribute(apf.xmldb.htmlIdTag);

        var container = this.$getLayoutNode("item", "container", htmlNode);
        if (!container) return callback();

        if (apf.getStyle(container, "display") == "block") {
            if (force == 1) {
                if (callback) callback();
                return;
            }
            htmlNode.className = htmlNode.className.replace(/min/, "plus");
            this.slideClose(container, apf.xmldb.getNode(htmlNode), immediate, callback);
        }
        else {
            if (force == 2) {
                if (callback) callback();
                return;
            }
            htmlNode.className = htmlNode.className.replace(/plus/, "min");
            this.slideOpen(container, apf.xmldb.getNode(htmlNode), immediate, callback);
        }
    };

    this.isCollapsed = function(xmlNode) {
        return (apf.getStyle(this.$getLayoutNode("item", "container",
            apf.xmldb.getHtmlNode(xmlNode, this)), "display") == "none");
    }

    var lastOpened = {};
    /*
     * @event expand Fires when a tree leaf is expanded from collapsed view to
     *               reveal its children leaves.
     * @private
     */
    this.slideOpen = function(container, xmlNode, immediate, callback) {
        if (!xmlNode)
            xmlNode = this.selected;

        var htmlNode = apf.xmldb.getHtmlNode(xmlNode, this);
        if (!container)
            container = this.$findContainer(htmlNode);

        //We don't slide open elements without children.
        if ((!container.childNodes.length || container.firstChild.nodeType == 3
          && container.firstChild.nodeValue.trim() == "")
          && !this.$getBindRule("insert", xmlNode)
          && !this.getTraverseNodes(xmlNode).length)
            return callback && callback();

        if (this.singleopen) {
            var pNode = this.getTraverseParent(xmlNode),
                p = (pNode || this.xmlRoot).getAttribute(apf.xmldb.xmlIdTag);
            if (lastOpened[p] && lastOpened[p][1] != xmlNode
              && this.getTraverseParent(lastOpened[p][1]) == pNode)
                this.slideToggle(lastOpened[p][0], 2);//lastOpened[p][1]);
            lastOpened[p] = [htmlNode, xmlNode];
        }

        if (!this.nocollapse)
            container.style.display = "block";

        if (!this.prerender && this.$hasLoadStatus(xmlNode, "potential")
          && !container.childNodes.length) {
            this.$extend(xmlNode, container, immediate, callback);
            return;
        }

        if (immediate || container.scrollHeight > 1000) {
            var _scrollTop = container.scrollHeight
                                - container.offsetHeight - diff - (apf.isGecko ? 16 : 0);
            container.scrollTop = _scrollTop == 0 ? 100 : 0;
            if (!this.nocollapse && container != this.$container) {
                container.style.height = "auto";
                container.style.overflow = "visible";
            }

            if (this.$hasLoadStatus(xmlNode, "potential"))
                return this.$extend(xmlNode, container, immediate, callback);

            this.dispatchEvent("expand", {xmlNode: xmlNode});
            return callback && callback();
        }

        var _self = this;
        var prevHeight = container.style.height;
        container.style.overflow = "visible";
        if (!apf.isIE7) {
            container.style.height = apf.hasHeightAutoDrawBug ? "100%" : "auto";
        }
        var height = container.scrollHeight;
        container.style.overflow = "hidden";
        container.style.height = prevHeight;

        function finishSlide() {
            if (xmlNode && _self.$hasLoadStatus(xmlNode, "potential")) {
                $setTimeout(function(){
                    if (container != this.$container) {
                        container.style.height = container.scrollHeight + "px";
                        container.style.overflow = "hidden";
                    }
                    _self.$extend(xmlNode, container, null, callback);
                });
                if (container != this.$container) {
                    if (!apf.isIE7) {
                        container.style.height = apf.hasHeightAutoDrawBug ? "100%" : "auto";
                    }
                    container.style.overflow = "visible";
                }
            }
            else if (container != this.$container) {
                container.style.overflow = "visible";
                if (!apf.isIE7) {
                    container.style.height = apf.hasHeightAutoDrawBug ? "100%" : "auto";
                }
                _self.dispatchEvent("expand", {xmlNode: xmlNode});
            }
        }

        if (!this.animation) {
                var diff = apf.getHeightDiff(container),
                    oInt = container;
    
                // This fixes a bug that was introduced in Chrome 20
                container.style.height = "10px";
                oInt.scrollTop = 0;
                // End fix
                container.style.height = Math.max((height), 0) + "px";
                _scrollTop = oInt.scrollHeight
                    - oInt.offsetHeight - diff - (apf.isGecko ? 16 : 0);
                oInt.scrollTop = _scrollTop == 0 ? 100 : 0;
                finishSlide();
        }
        else {
            apf.tween.single(container, {
                type: 'scrollheight',
                from: container.offsetHeight,
                to: height,
                anim: this.$animType,
                steps: this.$animOpenStep,
                interval: this.$animSpeed,
                onfinish: function(container) {
                    finishSlide();
                }
            });
        }
    };

    /*
     * @event collapse Fires when a tree leaf is collapsed from expanded view to
     *                 conceal its children leaves.
     * @private
     */
    this.slideClose = function(container, xmlNode, immediate) {
        if (this.nocollapse)
            return;

        if (!xmlNode)
            xmlNode = this.selected;

        if (this.singleopen) {
            var p = (this.getTraverseParent(xmlNode) || this.xmlRoot)
                .getAttribute(apf.xmldb.xmlIdTag);
            lastOpened[p] = null;
        }

        if (!container) {
            var htmlNode = apf.xmldb.getHtmlNode(xmlNode, this);
            container = this.$findContainer(htmlNode);
        }

        if (container != this.$container) {
            container.style.height = container.offsetHeight;
            container.style.overflow = "hidden";
        }

        if (immediate) {
            if (container != this.$container)
                container.style.height = 0;
            container.style.display = "none";
            this.dispatchEvent("collapse", {xmlNode: xmlNode});
            return;
        }

        var _self = this;
        apf.tween.single(container, {
            type: 'scrollheight',
            from: container.scrollHeight,
            to: 0,
            anim: this.$animType,
            steps: this.$animCloseStep,
            interval: this.$animSpeed,
            onfinish: function(container, data) {
               container.style.display = "none";
               _self.dispatchEvent("collapse", {xmlNode: xmlNode});
            }
        });
    };

    // *** Databinding Support *** //

    this.$isStartCollapsed = function(xmlNode) {
        return this.$hasBindRule("collapsed")
            ? (this.$getDataNode("collapsed", xmlNode) ? true : false)
            : (this.$hasBindRule("expanded")
                ? (this.$getDataNode("expanded", xmlNode) ? false : true)
                : this.startcollapsed);
    }

    //@todo apf3.x refactor
    this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode, isLast, depth, nextNode, action) {
        if (this.$isTreeArch && this.$needsDepth && typeof depth == "undefined") {
            var loopNode = xmlParentNode; depth = 0;
            while (loopNode != this.xmlRoot) {
                depth++;
                loopNode = loopNode.parentNode;
            }
        }

        var loadChildren = this.$getBindRule("insert", xmlNode) ? true : false,
            traverseNodes = this.getTraverseNodes(xmlNode),
            hasTraverseNodes = traverseNodes.length ? true : false,
            hasChildren = loadChildren || hasTraverseNodes,
            startcollapsed = this.$isStartCollapsed(xmlNode),
            state = (hasChildren ? HAS_CHILD : 0) | (startcollapsed && hasChildren
                || loadChildren ? IS_CLOSED : 0) | (isLast ? IS_LAST : 0),

            htmlNode = this.$initNode(xmlNode, state, Lid, depth),
            container = this.$getLayoutNode("item", "container", htmlNode),
            eachLength;

        if (!startcollapsed && !this.nocollapse)
            container.setAttribute("style", "overflow:visible;height:auto;display:block;");

        var msg, removeContainer = (!this.removecontainer || hasChildren);

        //TEMP on for dynamic subloading
        if (!hasChildren || loadChildren)
            container.setAttribute("style", "display:none;");

        //Dynamic SubLoading (Insertion) of SubTree
        if (!this.prerender)
            eachLength = traverseNodes.length;

        if (hasChildren && !this.prerender && eachLength > 2 && startcollapsed
          || loadChildren && (!this.$hasLoadStatus(xmlNode)
          || this.$hasLoadStatus(xmlNode, "potential")))
            this.$setLoading(xmlNode, container);
        else if (!hasTraverseNodes && (msg = this.$applyBindRule("empty", xmlNode))) {
            this.$setEmptyMessage(container, msg);
        }

        if ((!htmlParentNode || htmlParentNode == this.$container)
          && xmlParentNode == this.xmlRoot && !beforeNode
          || action == "insert") {
            this.$nodes.push(htmlNode);
            if (!apf.isChildOf(htmlNode, container, true) && removeContainer)
                this.$nodes.push(container);

            if (action != "insert") {
                this.$setStyleClass(htmlNode,  "root");
                this.$setStyleClass(container, "root");
            }
        }
        else {
            if (!htmlParentNode) {
                htmlParentNode = apf.xmldb.getHtmlNode(xmlNode.parentNode, this);
                htmlParentNode = htmlParentNode
                    ? this.$getLayoutNode("item", "container", htmlParentNode)
                    : this.$container;
            }

            if (htmlParentNode == this.$container) {
                this.$setStyleClass(htmlNode,  "root");
                this.$setStyleClass(container, "root");
            }

            var next;
            if (action != "load" && action != "extend") {
                if (!beforeNode && (next = this.getNextTraverse(xmlNode)))
                    beforeNode = apf.xmldb.getHtmlNode(next, this);
            }
            if (beforeNode && beforeNode.parentNode != htmlParentNode)
                beforeNode = null;

            if (htmlParentNode.style
              && this.getTraverseNodes(xmlNode.parentNode).length == 1)
                this.$removeEmptyMessage(htmlParentNode);

            //alert("|" + htmlNode.nodeType + "-" + htmlParentNode.nodeType + "-" + beforeNode + ":" + container.nodeType);
            //Insert Node into Tree
            if (htmlParentNode.style) {
                var isChildOfHtmlNode = !apf.isChildOf(htmlNode, container, true)
                htmlNode = apf.insertHtmlNode(htmlNode, htmlParentNode, beforeNode);
                if (isChildOfHtmlNode && removeContainer)
                    var container = apf.insertHtmlNode(container,
                        htmlParentNode, beforeNode);
                else
                    var container = this.$getLayoutNode("item", "container", htmlNode);
            }
            else {
                htmlParentNode.insertBefore(htmlNode, beforeNode);
                if (!apf.isChildOf(htmlNode, container, true) && removeContainer)
                    htmlParentNode.insertBefore(container, beforeNode);
            }

            //Fix parent if child is added to drawn parentNode
            if (htmlParentNode.style) {
                if (this.openadd && htmlParentNode != this.$container
                  && htmlParentNode.style.display != "block") {
                    if (!this.$isStartCollapsed(xmlParentNode))
                        this.slideOpen(htmlParentNode, xmlParentNode, true);
                }

                if (!this.$fillParent)
                    this.$fillParent = xmlParentNode;

                var next = nextNode == undefined ? this.getNextTraverse(xmlNode, true) : nextNode;

                var html;
                if (next && (html = apf.xmldb.getHtmlNode(next, this))) //should use each here
                    this.$fixItem(next, html);
            }
        }

        if ((this.prerender || eachLength < 3 || !startcollapsed) && (xmlNode.namespaceURI != apf.ns.apf || xmlNode.localName != "item")) {
            this.$addNodes(xmlNode, container, false, null, null, (depth || 0) + 1); //checkChildren ???
        }
        /*else {
            this.$setLoadStatus(xmlNode, "potential");
        }*/

        return container;
    };

    this.$fill = function(){
        if (this.$useiframe)
            this.$pHtmlDoc = this.oDoc;

        if (this.$nodes.length) {
            apf.insertHtmlNodes(this.$nodes, this.$fillParentHtml || this.$container);
            this.$nodes.length = 0;
            delete this.$fillParentHtml;
        }

        if (this.$fillParent) {
            this.$fixItem(this.$fillParent, apf.xmldb.getHtmlNode(this.$fillParent, this));
            delete this.$fillParent;
        }
    };

    this.$getParentNode = function(htmlNode) {
        return htmlNode
            ? this.$getLayoutNode("item", "container", htmlNode)
            : this.$container;
    };

    this.$fixItem = function(xmlNode, htmlNode, isDeleting, oneLeft, noChildren) {
        if (!htmlNode) return;

        if (isDeleting) {
            //if isLast fix previousSibling
            var prevSib;
            if (prevSib = this.getNextTraverse(xmlNode, true))
                this.$fixItem(prevSib, this.$findHtmlNode(prevSib
                    .getAttribute(apf.xmldb.xmlIdTag) + "|"
                    + this.$uniqueId), null, true);

            //if no sibling fix parent
            if (!this.emptyMessage && xmlNode.parentNode && xmlNode.parentNode.selectNodes(this.each).length == 1)
                this.$fixItem(xmlNode.parentNode, this.$findHtmlNode(
                    xmlNode.parentNode.getAttribute(apf.xmldb.xmlIdTag)
                    + "|" + this.$uniqueId), null, false, true);
        }
        else {
            var container = this.$getLayoutNode("item", "container", htmlNode),
                hasChildren = false;
            if (noChildren)
                hasChildren = false;
            else if (this.getTraverseNodes(xmlNode).length > 0)
                hasChildren = true;
            else if (this.$hasLoadStatus(xmlNode, "potential"))
                hasChildren = true;
            else
                hasChildren = false;

            var isClosed = hasChildren && apf.getStyle(container, "display") == "none"; //htmlNode.className.indexOf("min") == -1;//container.style.display != "block",
                isLast = this.getNextTraverse(xmlNode, null, oneLeft ? 2 : 1)
                    ? false
                    : true,
                state = (hasChildren ? HAS_CHILD : 0)
                    | (isClosed ? IS_CLOSED : 0) | (isLast ? IS_LAST : 0);
            this.$setStyleClass(this.$getLayoutNode("item", "class", htmlNode),
                treeState[state], ["min", "plus", "last", "minlast", "pluslast"]);
            this.$setStyleClass(this.$getLayoutNode("item", "container", htmlNode),
                treeState[state], ["min", "plus", "last", "minlast", "pluslast"]);

            if (this.$getLayoutNode("item", "openclose", htmlNode))
                this.$getLayoutNode("item", "openclose", htmlNode)
                    .setAttribute("children", hasChildren);

            if (container) {
                if (!hasChildren)
                    container.style.display = "none";
                else if (!isClosed)
                    container.style.display = "block";
            }
        }
    };

    this.$deInitNode = function(xmlNode, htmlNode) {
        //Lookup container
        var containerNode = this.$getLayoutNode("item", "container", htmlNode),
            pContainer = htmlNode.parentNode;

        //Remove htmlNodes from tree
        if (containerNode)
            containerNode.parentNode.removeChild(containerNode);
        pContainer.removeChild(htmlNode);

        //Datagrid??
        if (this.$withContainer)
            htmlNode.parentNode.removeChild(htmlNode.nextSibling);

        //Fix Images (+, - and lines)
        if (xmlNode.parentNode != this.xmlRoot)
            this.$fixItem(xmlNode, htmlNode, true);

        var msg;
        if (!pContainer.childNodes.length && (msg = this.$applyBindRule("empty", xmlNode)))
            this.$setEmptyMessage(pContainer, msg);

        //Fix look (tree thing)
        this.$fixItem(xmlNode, htmlNode, true);
        //this.$fixItem(xmlNode.parentNode, apf.xmldb.findHtmlNode(xmlNode.parentNode, this));
        /*throw new Error();
        if (xmlNode.previousSibling) //should use each here
            this.$fixItem(xmlNode.previousSibling, apf.xmldb.findHtmlNode(xmlNode.previousSibling, this));*/
    };

    this.$moveNode = function(xmlNode, htmlNode, oldXmlParent) {
        if (!apf.debug && !htmlNode)
            return;

        var container;
        if (this.$hasLoadStatus(xmlNode.parentNode, "potential")) {
            container = this.$getLayoutNode("item", "container", htmlNode);
            htmlNode.parentNode.removeChild(htmlNode);
            container.parentNode.removeChild(container);
            this.$extend(xmlNode.parentNode);
            return;
        }

        var nSibling = this.getNextTraverse(xmlNode),
            beforeNode = nSibling
                ? apf.xmldb.getHtmlNode(nSibling, this)
                : null;

        var next = htmlNode.nextSibling;
        if (next && next.tagName != htmlNode.tagName)
            next = next.nextSibling;
        if (next && beforeNode == next)
            return;

        var oPHtmlNode = htmlNode.parentNode,
            tParent = this.getTraverseParent(xmlNode),
            pHtmlNode = apf.xmldb.getHtmlNode(tParent, this),
        //if(!pHtmlNode) return;

            pContainer = pHtmlNode
                ? this.$getLayoutNode("item", "container", pHtmlNode)
                : this.$container;

        container = this.$getLayoutNode("item", "container", htmlNode);

        if (pContainer != oPHtmlNode && this.getTraverseNodes(xmlNode.parentNode).length == 1)
            this.$removeEmptyMessage(pContainer);

        pContainer.insertBefore(htmlNode, beforeNode);
        if (container)
            pContainer.insertBefore(container, beforeNode);

        /*if (!this.startcollapsed) {
            pContainer.style.display = "block";
            pContainer.style.height = "auto";
        }*/

        var msg;
        if (!this.getTraverseNodes(oldXmlParent).length && (msg = this.$applyBindRule("empty", oldXmlParent)))
            this.$setEmptyMessage(oPHtmlNode, msg);

//        if (this.openadd && pHtmlNode != this.$container && pContainer.style.display != "block")
//            this.slideOpen(pContainer, pHtmlNode, true);

        //Fix look (tree thing)
        this.$fixItem(xmlNode, htmlNode);

        this.$fixItem(tParent, apf.xmldb.getHtmlNode(tParent, this));
        this.$updateNode(oldXmlParent, apf.xmldb.getHtmlNode(oldXmlParent, this));
        var next;
        if (next = this.getNextTraverse(xmlNode, true)) { //should use each here
            this.$fixItem(next, apf.xmldb.getHtmlNode(next, this));
        }
    };

    //???
    this.$setLoading = function(xmlNode, container) {
        this.$setLoadStatus(xmlNode, "potential");

        var len = this.getTraverseNodes(xmlNode).length;
        if (!len || len > 20) {
            this.$getNewContext("loading");
            apf.insertHtmlNode(this.$getLayoutNode("loading"), container);
        }
    };

    //???
    this.$removeLoading = function(xmlNode) {
        if (!xmlNode) return;

        if (this.$timers)
            clearTimeout(this.$timers[xmlNode.getAttribute(apf.xmldb.xmlIdTag)]);

        var htmlNode = apf.xmldb.getHtmlNode(xmlNode, this);
        if (htmlNode) {
            this.$getLayoutNode("item", "container", htmlNode).innerHTML = "";
            this.$setStyleClass(htmlNode, "", ["loading"]);
        }
    };

    //check databinding for how this is normally implemented
    this.$extend = function(xmlNode, container, immediate, callback) {
        if (!this.$hasLoadStatus(xmlNode, "potential"))
            return;

        var rule = this.$getBindRule("insert", xmlNode),
            _self = this,
            xmlContext = rule && rule.match
                ? (rule.cmatch || rule.compile("match"))(xmlNode)
                : xmlNode;

        if (rule && xmlContext) {
            this.$setLoadStatus(xmlNode, "loading");

            var _self = this;
            //(this.$timers || (this.$timers = {}))[xmlNode.getAttribute(apf.xmldb.xmlIdTag)] = $setTimeout(function(){;
                _self.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, _self), "loading");
            //}, 100);

            if (this.dispatchEvent("beforeinsert", {
                xmlNode: xmlNode
            }) === false)
                return;

            if (rule.get) {
                

                this.getModel().$insertFrom(rule.getAttribute("get"), {
                    xmlNode: xmlContext,
                    insertPoint: xmlContext,
                    amlNode: this,
                    callback: function(data, state, extra) {
                        if (state != apf.SUCCESS) {
                            _self.$setLoadStatus(xmlNode, "potential");
                            _self.$removeLoading(xmlNode);
                            _self.slideToggle(apf.xmldb.getHtmlNode(xmlNode, _self), 2, true);
                        }
                        else {
                            _self.slideOpen(null, xmlNode);
                        }

                        if (callback)
                            callback(data, state, extra);
                    }
                });
            }
            else {
                if (this.$applyBindRule("insert", xmlNode))
                    this.insert(data, {insertPoint: xmlContext});
            }
        }
        else if (!this.prerender) {
            this.$setLoadStatus(xmlNode, "loaded");
            this.$removeLoading(xmlNode);
            xmlUpdateHandler.call(this, {
                action: "insert",
                xmlNode: xmlNode,
                result: this.$addNodes(xmlNode, container, true, null, null, null, "extend"), //checkChildren ???
                anim: !immediate
            });
        }
    };

    function xmlUpdateHandler(e) {
        /*
            Display the animation if the item added is
            * Not in the cache
            - Being insterted using xmlUpdate
            - there is at least 1 child inserted
        */

        if (e.action == "move-away")
            this.$fixItem(e.xmlNode, apf.xmldb.findHtmlNode(e.xmlNode, this), true);

        if (e.action != "insert") return;

        var htmlNode = this.$findHtmlNode(e.xmlNode.getAttribute(
            apf.xmldb.xmlIdTag) + "|" + this.$uniqueId);
        if (!htmlNode) return;

        //this.$hasLoadStatus(e.xmlNode, "loading")
        if (e.action == "insert" && e.result.length > 0) {
            if (this.$hasLoadStatus(e.xmlNode, "loaded", true)) {
                var container = this.$getLayoutNode("item", "container", htmlNode);
                this.slideOpen(container, e.xmlNode);//, e.$anim ? false : true
            }
            else if (this.$hasLoadStatus(e.xmlNode, "potential", true)) {
                this.$setLoadStatus(e.xmlNode, "loaded");
            }
        }
        else
            this.$fixItem(e.xmlNode, htmlNode);

        //Can this be removed?? (because it was added in the insert function)
        //if (this.$hasLoadStatus(e.xmlNode, "loading"))
            //this.$setLoadStatus(e.xmlNode, "loaded");
    }

    this.addEventListener("xmlupdate", xmlUpdateHandler);

    // *** Keyboard Support *** //

    
    this.addEventListener("beforerename", function(){
        if (this.$tempsel) {
            clearTimeout(this.timer);
            this.select(this.$tempsel);

            this.$tempsel = null;
            this.timer = null;
        }
    });
    

    this.scrollIntoView = function(sNode, onTop) {
        var selHtml = apf.xmldb.getHtmlNode(sNode, this), top;
        if (!selHtml)
            return;

        top = apf.getAbsolutePosition(selHtml, this.$container)[1];

        if (onTop) {
            if (top <= this.$container.scrollTop)
                this.$container.scrollTop = top;
        }
        else {
            if (top > this.$container.scrollTop + this.$container.offsetHeight)
                this.$container.scrollTop = top - this.$container.offsetHeight + selHtml.offsetHeight;
        }
    }

    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode,
            ctrlKey = e.ctrlKey,
            shiftKey = e.shiftKey,
            selHtml = this.$caret || this.$selected,
            pos, top, el, node, nodes, sNode, pNode, container;

        if (e.returnValue == -1 || !selHtml || this.renaming) //@todo how about allowdeselect?
            return;

        var selXml = this.caret || this.selected,
            oExt = this.$container;

        switch (key) {
            case 13:
                if (this.$tempsel)
                    this.$selectTemp();

                if (this.ctrlselect == "enter")
                    this.select(this.caret, true);

                this.choose(selHtml);
                break;
            case 32:
                if (this.$tempsel)
                    this.$selectTemp();

                
                if (this.$mode && !ctrlKey) {
                    var sel = this.getSelection();
                    if (!sel.length || !this.multiselect)
                        this.checkToggle(this.caret, true);
                    else
                        this.checkList(sel, this.isChecked(this.selected), true, false, true);
                }
                else
                
                if (ctrlKey || !this.isSelected(this.caret))
                    this.select(this.caret, true);
                return false;
            case 46:
                if (this.disableremove)
                    return;

                if (this.$tempsel)
                    this.$selectTemp();

                //DELETE
                //this.remove();
                this.remove(); //this.mode != "check"
                break;
            case 36:
                //HOME
                this.$setTempSelected(this.getFirstTraverseNode(), false, shiftKey);
                oExt.scrollTop = 0;
                return false;
            case 35:
                //END
                var lastNode = this.getLastTraverseNode();
                while (!this.isCollapsed(lastNode))
                    lastNode = this.getLastTraverseNode(lastNode);

                this.$setTempSelected(lastNode, false, shiftKey, true);
                oExt.scrollTop = oExt.scrollHeight;
                return false;
            case 37:
                //LEFT
                if (this.$tempsel)
                    this.$selectTemp();
                if (this.caret.selectSingleNode(this.each)
                  && !this.isCollapsed(this.caret))
                    this.slideToggle(this.$caret || this.$selected, 2)
                else if ((pNode = this.getTraverseParent(this.caret))
                  && pNode != this.xmlRoot)
                    this.select(pNode)
                return false;
            case 107: //+
            case 187: //+
            case 39:
                //RIGHT
                if (this.$tempsel)
                    this.$selectTemp();

                if (this.$hasLoadStatus(this.caret, "potential")
                  || this.getFirstTraverseNode(this.caret))
                    this.slideToggle(this.$caret || this.$selected, 1)
                break;
            case 109:
            case 189:
                //-
                if (this.getFirstTraverseNode(this.caret))
                    this.slideToggle(this.$caret || this.$selected, 2)
                break;
            case 38:
                //UP
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;

                sNode = this.getNextTraverse(node, true);
                if (sNode && sNode != node) {
                    nodes = this.getTraverseNodes(sNode);

                    do {
                        container = this.$getLayoutNode("item", "container",
                            this.$findHtmlNode(apf.xmldb.getID(sNode, this)));
                        if (container && apf.getStyle(container, "display") == "block"
                          && nodes.length) {
                                sNode = nodes[nodes.length-1];
                        }
                        else {
                            break;
                        }
                    }
                    while (sNode && (nodes = this.getTraverseNodes(sNode)).length);
                }
                else if (this.getTraverseParent(node) == this.xmlRoot) {
                    this.dispatchEvent("selecttop");
                    return;
                }
                else
                    sNode = this.getTraverseParent(node);

                if (sNode && sNode.nodeType == 1)
                   this.$setTempSelected(sNode, ctrlKey, shiftKey, true);
                else
                    return false;

                selHtml = apf.xmldb.getHtmlNode(sNode, this);
                top = apf.getAbsolutePosition(selHtml, this.$container)[1]
                     //- (selHtml.offsetHeight);
                if (top <= oExt.scrollTop)
                    oExt.scrollTop = top;

                return false;
            case 40:
                //DOWN
                if (!selXml && !this.$tempsel)
                    return;

                node = this.$tempsel
                    ? apf.xmldb.getNode(this.$tempsel)
                    : selXml;

                sNode = this.getFirstTraverseNode(node);
                if (sNode) {
                    container = this.$getLayoutNode("item", "container",
                        this.$findHtmlNode(apf.xmldb.getID(node, this)));
                    if (container && apf.getStyle(container, "display") != "block")
                        sNode = null;
                }

                while (!sNode) {
                    pNode = this.getTraverseParent(node);
                    if (!pNode) break;

                    var i = 0;
                    nodes = this.getTraverseNodes(pNode);
                    while (nodes[i] && nodes[i] != node)
                        i++;
                    sNode = nodes[i+1];
                    node = pNode;
                }

                if (sNode && sNode.nodeType == 1)
                   this.$setTempSelected(sNode, ctrlKey, shiftKey);
                else
                    return false;

                selHtml = apf.xmldb.getHtmlNode(sNode, this);
                top = apf.getAbsolutePosition(selHtml, this.$container)[1]
                    + (selHtml.offsetHeight);
                if (top > oExt.scrollTop + oExt.offsetHeight)
                    oExt.scrollTop = top - oExt.offsetHeight;
                return false;
            case 33: //@todo
                //PGUP
                pos = apf.getAbsolutePosition(this.$container);
                el = document.elementFromPoint(pos[0] + this.$container.offsetWidth
                      - 2, pos[1] + 2);
                sNode = apf.xmldb.findXmlNode(el);
                if (sNode == this.selected) {
                    oExt.scrollTop -= oExt.offsetHeight - apf.getHeightDiff(oExt);
                    el = document.elementFromPoint(pos[0] + this.$container.offsetWidth
                          - 2, pos[1] + 2);
                    sNode = apf.xmldb.findXmlNode(el);
                }
                this.select(sNode);

                selHtml = apf.xmldb.getHtmlNode(sNode, this);
                top = apf.getAbsolutePosition(selHtml, this.$container)[1]
                     - (selHtml.offsetHeight);
                if (top <= oExt.scrollTop)
                    oExt.scrollTop = top;
                break;
            case 34: //@todo
                //PGDN
                pos = apf.getAbsolutePosition(this.$container);
                el = document.elementFromPoint(pos[0] + this.$container.offsetWidth
                      - 2, pos[1] + this.$ext.offsetHeight - 4);
                sNode = apf.xmldb.findXmlNode(el);
                if (sNode == this.selected) {
                    oExt.scrollTop += oExt.offsetHeight - apf.getHeightDiff(oExt);
                    el = document.elementFromPoint(pos[0] + this.$container.offsetWidth
                          - 2, pos[1] + this.$ext.offsetHeight - 4);
                    sNode = apf.xmldb.findXmlNode(el);
                }
                this.select(sNode);

                selHtml = apf.xmldb.getHtmlNode(sNode, this);
                top = apf.getAbsolutePosition(selHtml, this.$container)[1]
                    + (selHtml.offsetHeight);
                if (top > oExt.scrollTop + oExt.offsetHeight)
                    oExt.scrollTop = top - oExt.offsetHeight;
                break;
            default:
                if (this.celledit) {
                    if (!ctrlKey && !e.altKey && (key > 46 && key < 112 || key > 123))
                        this.startRename(null, true);
                    return;
                }
                else if (key == 65 && ctrlKey) {
                    this.selectAll();
                    return false;
                }
                //@todo make this work with the sorted column
                else if (this.caption || (this.bindingRules || {})["caption"]) {
                    if (!this.xmlRoot) return;

                    //this should move to a onkeypress based function
                    if (!this.lookup || new Date().getTime()
                      - this.lookup.date.getTime() > 300)
                        this.lookup = {
                            str: "",
                            date: new Date()
                        };

                    this.lookup.str += String.fromCharCode(key);

                    var nodes = this.getTraverseNodes(); //@todo start at current indicator
                    for (var v, i = 0; i < nodes.length; i++) {
                        v = this.$applyBindRule("caption", nodes[i]);
                        if (v && v.substr(0, this.lookup.str.length)
                          .toUpperCase() == this.lookup.str) {

                            if (!this.isSelected(nodes[i]))
                                this.select(nodes[i]);

                            if (selHtml)
                                this.$container.scrollTop = selHtml.offsetTop
                                    - (this.$container.offsetHeight
                                    - selHtml.offsetHeight) / 2;
                            return;
                        }
                    }
                    return;
                }
                break;
        }
    }, true);
    

    // *** Rename Support *** //

    
    this.$getCaptionElement = function(){
        if (!this.$selected) return false;
        var x = this.$getLayoutNode("item", "caption", this.$selected);
        return x.nodeType == 1 ? x : x.parentNode;
    };
    

    // *** Selection Support *** //
    /*
        nodes = this.hasFeature(apf.__VIRTUALVIEWPORT__)
                ? this.xmlRoot.selectNodes(this.$isTreeArch
                    ? this.each
                    : ".//" + this.each.split('|').join('|.//'))
                :
    */
    this.$calcSelectRange = function(xmlStartNode, xmlEndNode) {
        var r = [],
            f = false,
            i = 0,
            pNodeStart = this.getTraverseParent(xmlStartNode),
            pNodeEnd = this.getTraverseParent(xmlEndNode),
            nodes = this.getTraverseNodes(pNodeStart);

        for (; i < nodes.length; i++) {
            if (nodes[i] == xmlStartNode)
                f = true;
            if (f)
                r.push(nodes[i]);
            if (nodes[i] == xmlEndNode)
                f = false;
        }

        if (!r.length || f) {
            r = [];
            for (f = false, i = nodes.length - 1; i >= 0; i--) {
                if (nodes[i] == xmlStartNode)
                    f = true;
                if (f)
                    r.push(nodes[i]);
                if (nodes[i] == xmlEndNode)
                    f = false;
            }
        }

        return r;
    };

    this.$findContainer = function(htmlNode) {
        return this.$getLayoutNode("item", "container", htmlNode);
    };

    this.$selectDefault = function(xmlNode) {
        var firstNode = this.getFirstTraverseNode(xmlNode);
        if (!firstNode)
            return;

        if (this.select(firstNode, null, null, null, true)) {
            return true;
        }
        else {
            var nodes = this.getTraverseNodes(xmlNode);
            for (var i = 0; i < nodes.length; i++) {
                if (this.$selectDefault(nodes[i]))
                    return true;
            }
        }
    };

    this.$setEmptyMessage = function(htmlNode, msg) {
        this.$getNewContext("empty");
        var xmlEmpty = this.$getLayoutNode("empty");
        if (!xmlEmpty) return;

        var empty = apf.insertHtmlNode(xmlEmpty, htmlNode);
        empty.setAttribute("empty", "true");
        var caption = this.$getLayoutNode("empty", "caption", empty);

        if (caption)
            apf.setNodeValue(caption, msg || "");

        if (htmlNode.style)
            this.slideOpen(htmlNode, null, true);
        else if (this.nocollapse)
            htmlNode.setAttribute("style", "display:inline-block;");
        else
            htmlNode.setAttribute("style", "overflow:visible;height:auto;display:block;");

    }

    this.$removeEmptyMessage = function(htmlNode) {
        var cNode = htmlNode.firstElementChild || htmlNode.firstChild;
        if (!cNode)
            return;

        do {
            if (cNode.getAttribute && cNode.getAttribute("empty")) { //@todo hack
                htmlNode.removeChild(cNode);
                return;
            }
            cNode = cNode.nextSibling;
        }
        while (cNode);
    }

    // *** Init *** //

    /**
     * @event click Fires when the user presses a mousebutton while over this
     *              element, and then lets the mousebutton go
     *
     */
    this.$drawBase = function(){
        //@todo apf3.0 checkmode, radiomode
        /*if (!this.getAttribute("skin")) {
            var mode = this.getAttribute("mode");
            this.skinName = null;
            this.skin = mode + "tree";
            this.$loadSkin();
        }*/

        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$container = this.$getLayoutNode("main", "container", this.$ext);
        this.opencloseaction = this.$getOption("main", "openclose");

        //Need fix...
        //this.$ext.style.MozUserSelect = "none";

        if (apf.hasCssUpdateScrollbarBug && !this.mode)
            this.$fixScrollBug();

        var _self = this;
        this.$ext.onclick = function(e) {
            _self.dispatchEvent("click", {htmlEvent : e || event});
        };
        this.$ext.onmousedown = function(e) {
            _self.dispatchEvent("mousedown", {htmlEvent : e || event});
        };
        this.$ext.onmouseover = function(e) {
            _self.dispatchEvent("mouseover", {htmlEvent : e || event});
        };
        this.$ext.onmouseout = function(e) {
            _self.dispatchEvent("mouseout", {htmlEvent : e || event});
        };
        this.$ext.onmousemove = function(e) {
            _self.dispatchEvent("mousemove", {htmlEvent : e || event});
        };
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function(){
        if (this.nocollapse)
            this.startcollapsed = false;
        else if (this.startcollapsed === 1)
            this.startcollapsed = !apf.isFalse(this.$getOption("main", "startcollapsed"));
    });

    this.addEventListener("DOMNodeRemovedFromDocument", function(){
        this.$ext.onclick = null;

        apf.destroyHtmlNode(this.oDrag);
        this.oDrag = null;
    });

}).call(apf.BaseTree.prototype = new apf.MultiSelect());







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__DELAYEDRENDER__ = 1 << 11



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have delayed
 * rendering features. 
 *
 * Any element that is (partially) hidden at startup has the
 * possibility to delay rendering its childNodes by setting `render="runtime"` on
 * the element. These elements include `window`, `pane`, `pages`, `form` and c`ontainer`.
 * For instance, a pane tab in a container is initally hidden and does not
 * need to be rendered. When the pane button is pressed to activate the tab,
 * the tab is rendered and then displayed. This can dramatically decrease
 * the startup time of the application.
 * 
 * #### Example
 *
 * In this example the button isn't rendered until the advanced pane becomes active.
 * 
 * ```xml
 *  <a:pane width="200" height="150">
 *      <a:tab caption="General">
 *      ...
 *      </a:tab>
 *      <a:tab caption="Advanced" render="runtime">
 *         <a:button>OK</a:button>
 *      </a:tab>
 *  </a:pane>
 * ```
 * @class apf.DelayedRender
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8.9
 */
/**
 * @event beforerender  Fires before elements are rendered. Use this event to display a loader.
 * @cancelable Prevents rendering of the childNodes
 */
/**
 *  @event afterrender   Fires after elements are rendered. Use this event to hide a loader.
 *
 */
/**
 *  @attribute {String}  render   Sets or gets when the contents of this element is rendered.
 *   
 * Possible values include:
 *   
 *   - init:     elements are rendered during the initialization of the application.
 *   - runtime:  elements are rendered when the user requests them.
 */
/**
 *  @attribute {Boolean} use-render-delay Sets or gets whether there's a short delay between showing this element and rendering its contents.
 *  
 * If `true`, the elements are rendered immediately. Otherwise, there is a delay between showing this element and the actual rendering,
 * allowing the browsers' render engine to draw (for instance, a loader).
 *
 */
apf.DelayedRender = function(){
    this.$regbase = this.$regbase | apf.__DELAYEDRENDER__;
    this.$rendered = false;
    
    /*
     * Renders the children of this element.
     *
     * @param {Boolean} [usedelay] Specifies whether a delay is added between calling 
     * this function and the actual rendering. This allows the browsers' 
     * render engine to draw (for instance a loader).
     */
    this.$render = function(usedelay) {
        if (this.$rendered)
            return;

        if (this.dispatchEvent("beforerender") === false)
            return;

        if (this["render-delay"] || usedelay)
            $setTimeout("apf.lookup(" + this.$uniqueId + ").$renderparse()", 10);
        else
            this.$renderparse();
    };

    this.$renderparse = function(){
        if (this.$rendered)
            return;

        // Hide render pass from sight for inner callstack 
        // redrawing browsers like firefox
        this.$ext.style.visibility = "hidden";

        var domParser = this.ownerDocument.$domParser;
        domParser.parseFromXml(this.$aml, {
            amlNode: this,
            doc: this.ownerDocument,
            //nodelay       : true,
            delayedRender: true
        });
        domParser.$continueParsing(this);

        this.$rendered = true;

        this.dispatchEvent("afterrender");
        this.addEventListener("$event.afterrender", function(cb) {
            cb.call(this);
        });

        this.$ext.style.visibility = "";
    };
    
    /*var _self = this;
    if (apf.window.vManager.check(this, "delayedrender", function(){
        _self.$render();
    })) this.$render();*/
    
    var f;
    this.addEventListener("prop.visible", f = function(){
        if (arguments[0].value) {
            
            this.$render();
            
            
            this.removeEventListener("prop.visible", f);
        }
    });
};

apf.GuiElement.propHandlers["render"] = function(value) {
    if (!this.hasFeature(apf.__DELAYEDRENDER__) && value == "runtime") {
        this.implement(apf.DelayedRender);
    
        if (this.localName != "tab") {
            this.visible = false;
            this.$ext.style.display = "none";
        }
        
        if (typeof this["render-delay"] == "undefined")
            this.$setInheritedAttribute("render-delay");
    }
};

apf.config.$inheritProperties["render-delay"] = 1;





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__DRAGDROP__ = 1 << 5;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have drag & drop 
 * features. 
 *
 * This baseclass operates on the bound data of this element. 
 * When a rendered item is dragged and dropped, the bound data is moved or 
 * copied from one element to another, or to the same element but at a different 
 * position. This is possible because the rendered item has a 
 * {@link term.smartbinding bidirectional connection} to the data. Drag & drop can 
 * be turned on with a simple boolean, or by specifying detailed rules to set 
 * which data can be dragged and dropped and where.
 *
 *
 * #### Example
 *
 * This is a simple example, enabling drag & drop for a list:
 *
 * ```xml
 *  <a:list
 *    drag = "true"
 *    drop = "true"
 *    dragcopy = "true" />
 * ```
 *
 *
 * #### Example
 *
 * This example shows a smartbinding that represents files and folders. It uses a
 * {@link term.datainstruction data instruction} to communicate to the webdav
 * server when an item is copied or moved.
 *
 * ```xml
 *  <a:smartbinding>
 *      <a:bindings>
 *          <a:caption match="[@filename]" />
 *          <a:each match="[file|folder]" />
 *
 *          <a:drag 
 *            match = "[person]" 
 *            copy = "event.ctrlKey" />
 *          <a:drop
 *            match = "[file]"
 *            target = "[folder]"
 *            action = "tree-append"
 *            copy = "event.ctrlKey" />
 *          <a:drop
 *            match = "[folder]"
 *            target = "[folder]"
 *            action = "insert-before"
 *            copy = "event.ctrlKey" />
 *      </a:bindings>
 *      <a:actions>
 *          <a:move
 *              match = "[folder]"
 *              set = "{myWebdav.move([@path], [../@path])}" />
 *          <a:copy
 *              match = "[file]"
 *              set = "{myWebdav.copy([@path], [../@path])}" />
 *      </a:actions>
 *  </a:smartbinding>
 * ```
 *
 * #### Example 
 *
 * This example shows a small mail application. The tree element displays a root
 * node, accounts and folders in a tree. The datagrid contains the mails. This
 * rule specifies which data nodes can be dropped where. Folders can be dropped 
 * in folders and accounts. Mails can be dropped in folders.
 *
 * ```xml
 *
 *  <a:tree align="left" width="200">
 *      <a:each match="[root|account|folder|mail]">
 *          <a:caption match="[@name]" />
 *          <a:drag match = "[folder]" />
 *          <a:drop match = "[folder]" 
 *                  target = "[folder|account]"
 *                  action = "tree-append" />
 *          <a:drop match = "[mail]" 
 *                  target = "[folder]"
 *                  action = "tree-append" />
 *      </a:each>
 *      <a:model>
 *          <data>
 *              <root name="Root">
 *                  <account name="Account 1">
 *                      <folder name="Folder 1"></folder>
 *                  </account>
 *              </root>
 *           </data>
 *      </a:model>
 *  </a:tree>
 *  <a:datagrid align="right">
 *      <a:each match="[mail]">
 *          <a:column 
 *            caption = "Name" 
 *            value = "[@name]"
 *            width = "100%" /> 
 *          <a:drag match="[mail]" />
 *      </a:each>
 *      <a:model>
 *          <data>
 *              <mail name="Mail 1"></mail>
 *          </data>
 *      </a:model>
 *  </a:datagrid>
 *```
 * 
 * @class apf.DragDrop
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.5
 * @define dragdrop
 * @allowchild drop, drag
 * @define drag 
 */
/**
 * @event  dragdata  Fires before a drag & drop operation is started to determine the data that is dragged.
 * @param {Object} e The standard event object. It contains the following property:
 *   - `data` ([[XMLElement]]): The default data for the drag & drop operation
 */
/**
 * @event  dragstart Fires before a drag operation is started.
 * @cancelable Prevents the drag operation to start.
 * @param {Object} e The standard event object. It contains the following properties:
 *   - `data` ([[XMLElement]]): The data for the drag & drop operation
 *   - `selection` ([[XMLElement]]): The selection at the start of the drag operation
 *   - `indicator` ([[HTMLElement]]): The HTML element that is shown while dragging the data
 *   - `host` ([[apf.AmlElement]]): The AML source element.
 */
/**
 * @event  dragover Fires when the users drags over this AML element.
 * @cancelable Prevents the possibility to drop.
 * @param {Object} e The standard event object. It contains the following properties:
 *   {XMLElement}  data      The data for the drag & drop operation
 *   {XMLElement}  selection The selection at the start of the drag operation
 *   {HTMLElement} indicator The HTML element that is shown while dragging the data
 *   {apf.AmlElement}  host      the AML source element.
 */
/**
 * @event  dragout  Fires when the user moves away from this AML element.
 * @param {Object} e The standard event object. It contains the following properties:
 *   {XMLElement}  data      the data for the drag & drop operation
 *   {XMLElement}  selection the selection at the start of the drag operation
 *   {HTMLElement} indicator the HTML element that is shown while dragging the data
 *   {apf.AmlElement}  host      the aml source element.
 */
/**
 * @event  dragdrop  Fires when the user drops an item on this aml element.
 * @cancelable Prevents the possibility to drop.
 * @param {Object} e The standard event object. It contains the following properties:
 *   {XMLElement}  data      The data for the drag & drop operation
 *   {XMLElement}  selection The selection at the start of the drag operation
 *   {HTMLElement} indicator The html element that is shown while dragging the data
 *   {apf.AmlElement}  host      The AML source element.
 *   {Boolean}     candrop   Specifies whether the data can be inserted at the point hovered over by the user
 *
 *
 */
/**
 * @attribute {String} match           An XPath statement querying the
 *                                     {@link term.datanode data node} that is
 *                                     dragged. If the query matches a node it
 *                                     is allowed to be dropped. The XPath is
 *                                     automatically prefixed by `'self::'`.
 */
/**
 * @attribute {String} copy            A JavaScript expression that determines
 *                                     whether the dragged element is a copy or
 *                                     a move. Use event.ctrlKey to use the Ctrl
 *                                     key to determine whether the element is copied.
 *
 */
/**
 * @attribute {String} match           An XPath statement querying the
 *                                     {@link term.datanode data node} that is
 *                                     dragged. If the query matches a node it
 *                                     is allowed to be dropped. The XPath is
 *                                     automatically prefixed by `'self::'`.
 */
/**
 * @attribute {String} target          An XPath statement determining the new
 *                                     parent of the dropped {@link term.datanode data node}.
 *                                     The XPath is automatically prefixed by `'self::'`.
 */
/**
 * @attribute {String} action          The action to perform when the
 *                                     {@link term.datanode data node} is inserted.
 *   The possible values include:
 *
 *   - `tree-append`:    Appends the {@link term.datanode data node} to the element its dropped on.
 *   - `list-append`:    Appends the {@link term.datanode data node} to the root element of this element.
 *   - `insert-before`:  Inserts the {@link term.datanode data node} before the elements its dropped on.
 */
/**
 * @attribute {String} copy            A JavaScript expression that determines
 *                                     whether the drop is a copy or a move.
 *                                     Use event.ctrlKey to use the [[keys: Ctrl]] key to
 *                                     determine whether the element is copied.
 */
apf.DragDrop = function(){
    this.$regbase = this.$regbase | apf.__DRAGDROP__;

    this.$dragInited = false;

    /* **********************
            Actions
    ***********************/

    /**
     * Copies a {@link term.datanode data node} to the bound data of this element.
     *
     * @action
     * @param {XMLElement}  xmlNode      The {@link term.datanode data node} which is copied.
     * @param {XMLElement}  [pNode]        The new parent element of the copied
     *                                   {@link term.datanode data node}. If none is
     *                                   specified the root element of the data
     *                                   loaded in this element is used.
     * @param {XMLElement}  [beforeNode] The position where the {@link term.datanode data node}
     *                                   is inserted.
     */
    this.copy = function(nodeList, pNode, beforeNode, isMove) {
        if (nodeList.nodeType)
            nodeList = [nodeList];
        
        var exec,
            changes = [],
            i = 0,
            l = nodeList.length;
        for (; i < l; i++) {
            changes.push({
                action: isMove ? "moveNode" : "appendChild",
                args: [pNode, isMove
                    ? nodeList[i]
                    : nodeList[i] = nodeList[i].cloneNode(true), beforeNode]
            });
        }
        
        if (this.$actions[(isMove ? "movegroup" : "copygroup")]) {
            exec = this.$executeAction("multicall", changes, 
                (isMove ? "movegroup" : "copygroup"), nodeList[0]);
        }
        else {
            exec = this.$executeAction("multicall", changes, 
                (isMove ? "move" : "copy"), nodeList[0], null, null, 
                nodeList.length > 1 ? nodeList : null);
        }

        if (exec !== false)
            return nodeList;

        return false;
    };

    /**
     * Moves a {@link term.datanode data node} to the bound data of this element.
     *
     * @action
     * @param {XMLElement}   xmlNode      The {@link term.datanode data node} which is copied.
     * @param {XMLElement}   [pNode]      The new parent element of the moved
     *                                    {@link term.datanode data node}. If none
     *                                    specified the root element of the data
     *                                    loaded in this element is used.
     * @param {XMLElement}   [beforeNode] The position where the
     *                                    {@link term.datanode data node} is inserted.
     */
    this.move = function(nodeList, pNode, beforeNode) {
        return this.copy(nodeList, pNode, beforeNode, true);
    };
    
    /**
     * Determines whether the user is allowed to drag the passed 
     * {@link term.datanode data node}. 
     *
     * For instance, imagine a mail application with a root
     * node, accounts and folders in a tree, and mails in a datagrid. The rules
     * would specify you can drag & drop folders within an account, and emails between
     * folders, but not on accounts or the root.
     *
     * @param {XMLElement}  dataNode The {@link term.datanode data node} subject to the test.
     * @return {Boolean} The result of the test
     * @see apf.DragDrop.isDragAllowed
     */
    this.isDragAllowed = function(x, data) {
        
        
        if (!this.dragroot && this.xmlRoot.firstChild == x[0])
            return false;
        
        if (this.disabled || !x || !x.length || !x[0])
            return false;

        if (this.drag || this.dragcopy) {
            if (data)
                data.merge(x);
            return true;
        }

        /*var rules = this.$bindings["drag"]
          || this.$attrBindings && this.$attrBindings["drag"];
        if (!rules || !rules.length)
            return false;*/

        var d, 
            ruleList = [],
            j = 0,
            l = x.length;
        for (; j < l; j++) {
            d = this.$getDataNode("drag", x[j], null, ruleList);
            if (!d) return false; //It's all or nothing
            if (data)
                data.push(d);
        }

        return ruleList.length ? ruleList : false;
    };

    /**
     * Determines whether the user is allowed to drop the passed 
     * {@link term.datanode data node}. 
     * 
     * For instance, imagine a mail application with a root
     * node, accounts and folders in a tree, and mails in a datagrid. The rules
     * would specify you can drag & drop folders within an account, and emails between
     * folders, but not on accounts or the root.
     *
     * @param {XMLElement}  dataNode The {@link term.datanode data node} subject
     *                               to the test.
     * @param {XMLElement}  target   The {@link term.datanode data node} on which
     *                               the dragged data node is dropped.
     * @return {Boolean} The result of the test
     * @see apf.DragDrop.isDragAllowed
     */
    this.isDropAllowed = function(x, target) {
        

        if (this.disabled || !x || !x.length || !target) //!x[0] ???
            return false;
            
        if (!this.dragroot == false && this.xmlRoot.firstChild == x[0])
            return false;
        
        for (var i = x.length - 1; i >= 0; i--)
            if (apf.isChildOf(x[i], target, true))
                return false;
        
        var data, tgt, hasDropRule = this.$attrBindings && this.$attrBindings["drop"];
        if (this.drop && (!hasDropRule || hasDropRule.value == "true")) {
            this.$setDynamicProperty("drop", this.hasFeature(apf.__MULTISELECT__)
              ? "[" + this.each + "]"
              : "[node()]"); //@todo apf3.0 make sure each is without {}
            hasDropRule = true;
        }

        if (hasDropRule) {
            for (var j = 0, l = x.length; j < l; j++) {
                data = this.$getDataNode("drop", x[j]);
                if (!data)
                    break;
            }
            if (j == l && target && !apf.isChildOf(data, target, true))
                return [target, null];
        }

        var rules = this.$bindings["drop"];
        if (!rules || !rules.length)
            return false;

        //@todo this can be optimized when needed
        var rule, strTgt,
            i = 0,
            rl = rules.length;
        for (; i < rl; i++) {
            rule = this.$bindings.getRuleIndex("drop", i);

            for (var j = 0, l = x.length; j < l; j++) {
                data = rule.cvalue ? rule.cvalue(x[j]) : rule.cmatch(x[j]);
                if (!data)
                    break;
            }
            if (j != l)
                continue;
            
            strTgt = rule.target;//node.getAttribute("target");
            if (!strTgt || strTgt == ".") {
                //op = node.getAttribute("action") 
                  //|| (this.$isTreeArch ? "tree-append" : "list-append");
                tgt = target;/*(op == "list-append" || target == this.xmlRoot
                  ? this.xmlRoot
                  : null);*/
            }
            else {
                tgt = (rule.ctarget || rule.compile("target"))(target);
            }
            
            if (tgt && !apf.isChildOf(data, tgt, true))
                return [tgt, rule];
        }

        return false;
    };

    this.$dragDrop = function(xmlReceiver, xmlNodeList, rule, defaction, isParent, srcRule, event, forceCopy) {
        /*
            Possibilities:

            tree-append [default]: xmlNode.appendChild(movedNode);
            list-append          : xmlNode.parentNode.appendChild(movedNode);
            insert-before        : xmlNode.parentNode.insertBefore(movedNode, xmlNode);
        */
        var action = rule && rule.action;//node && node.getAttribute("action");

        if (action)
            action = (rule.caction || rule.compile("action"))(xmlNodeList[0]);
        else
            action = defaction;
            
        // @todo apf3.0 action not known here yet... should be moved down?
        if (action == "tree-append" && isParent) 
            return false;

        if (!event)
            event = {};

        //copy convenience variables
        var context = {
              internal: apf.DragServer.dragdata && apf.DragServer.dragdata.host == this,
              ctrlKey: event.ctrlKey,
              keyCode: event.keyCode
          },
          //@todo apf3.0 below should actually be compileNode with with_options
          ifcopy = rule && rule.copy;//.getAttribute("copy");

        if (typeof forceCopy == "boolean")
            ifcopy = forceCopy;
        else if (ifcopy) {
            context.event = event || {};
            ifcopy = !apf.isFalse((rule.ccopy || rule.compile("copy"))(xmlNodeList[0], context));
        }
        else if (typeof this.dragcopy == "boolean" || typeof this.dropcopy == "boolean") { //@todo apf3.0 boolean here?
            if (this.dropcopy) {
                ifcopy = this.dropcopy;
            }
            else if (this.dragcopy) {
                ifcopy = event.ctrlKey;
            }
            else {
                //@todo read this from src
                var copyRule = this.$attrBindings && this.$attrBindings["dragcopy"];
                if (copyRule) {
                    ifcopy = !apf.isFalse((copyRule.cvalue2
                      || copyRule.compile("value", {
                        withopt: true
                      }))(xmlNodeList[0], context));
                }
            }
        }

        if (!ifcopy && srcRule) { //Implemented one copy is all copy
            for (var i = 0, l = srcRule.length; i < l; i++) {
                ifcopy = typeof srcRule[i] == "object" && srcRule[i].copy
                    ? !apf.isFalse((srcRule[i].ccopy || srcRule[i].compile("copy"))(xmlNodeList[0], context))
                    : event.ctrlKey;
                if (ifcopy) break;
            }
        }

        var sNode,
            actRule = ifcopy ? "copy" : "move",
            parentXpath = rule ? rule.getAttribute("parent") : null; //@todo apf3.0 Should be lm syntax
        switch (action) {
            case "list-append":
                xmlReceiver = (isParent 
                  ? xmlReceiver
                  : this.getTraverseParent(xmlReceiver));
                if (parentXpath) {
                    if (xmlReceiver.selectSingleNode(parentXpath))
                        xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
                    else {
                        xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath));
                        xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
                    }
                }
                sNode = this[actRule](xmlNodeList, xmlReceiver);
                break;
            case "insert-before":
                sNode = isParent
                    ? this[actRule](xmlNodeList, xmlReceiver)
                    : this[actRule](xmlNodeList, xmlReceiver.parentNode, xmlReceiver);
                break;
            case "tree-append":
                if (parentXpath) {
                    if (xmlReceiver.selectSingleNode(parentXpath))
                        xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
                    else {
                        xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath));
                        xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
                    }
                }
                sNode = this[actRule](xmlNodeList, xmlReceiver);
                break;
        }

        if (this.selectable && sNode) {
            this.selectList(sNode);//, null, null, null, true);
            this.setCaret(sNode[0]);
            this.focus();
        }

        return sNode;
    };

    /* **********************
            Init
    ***********************/

    /*
     * Loads the dragdrop rules from the dragdrop element
     *
     * @param {Array}       rules     The rules array created using {@link core.apf.method.getrules}
     * @param {XMLElement}  [node] The reference to the drag & drop element
     * @see  SmartBinding
     * @private
     */
    this.enableDragDrop = function(){
        

        //Set cursors
        //SHOULD come from skin
        this.icoAllowed = "";//this.xmlDragDrop.getAttribute("allowed");
        this.icoDenied = "";//this.xmlDragDrop.getAttribute("denied");

        //Setup External Object
        this.$ext.dragdrop = false;

        var _self = this;

        this.$ext[apf.isIphone ? "ontouchstart" : "onmousedown"] = function(e) {
            if (_self.disabled)
                return;

            e = e || window.event;
            

            var fEl,
                srcEl = e.originalTarget || e.srcElement || e.target,
                multiselect = _self.hasFeature(apf.__MULTISELECT__);
            if (multiselect && srcEl == _self.$container)
                return;
            _self.dragging = 0;

            try{ //Firefox can crash here because of some chrome permission issue 
                if (!apf.isIphone && _self.allowdeselect
                  && (srcEl == this || srcEl.getAttribute(apf.xmldb.htmlIdTag) 
                  && _self.$getLayoutNode("item", "select", this) != this))
                    return; //This broke making a selection with the mouse in rename:  _self.clearSelection(); //@todo hacky - should detect what element has the select from the skin
            }catch(e) {return;}

            //MultiSelect must have carret behaviour AND deselect at clicking white
            if (_self.$findValueNode)
                fEl = _self.$findValueNode(srcEl);
            var el = (fEl
                ? apf.xmldb.getNode(fEl)
                : apf.xmldb.findXmlNode(srcEl));
            if (multiselect && (!_self.selected || !el || el == _self.xmlRoot))
                return;

            if (_self.isDragAllowed(multiselect ? _self.$getSelection() : el)) {
                

                apf.DragServer.start(_self, srcEl, e);
            }

            //e.cancelBubble = true;
        };

        this.$ext[apf.isIphone ? "ontouchmove" : "onmousemove"] = function(e) {
            if (this.host.dragging != 1 || _self.disabled) return;
        };

        
        {
            this.$ext.onmouseup = function(){
                if (_self.disabled)
                    return;
                    
                this.host.dragging = 0;
            };

            this.$ext.ondragcopy = 
            this.$ext.ondragstart = function(){return false;};
        }

        if (document.elementFromPointAdd)
            document.elementFromPointAdd(this.$ext);

        if (this.$initDragDrop && !this.$dragInited) {
            this.$initDragDrop();
            this.$dragInited = 2;
        }
        else {
            this.$dragInited = true;
        }
    };
    
    function disableDragDrop(){
        this.$dragInited = false; //@todo solve oExt event conflicts
        
        
        {
            this.$ext.onmousedown = this.$ext.onmousemove
                = this.$ext.onmouseup = null;
        }

        if (document.elementFromPointRemove)
            document.elementFromPointRemove(this.$ext);
    }
    
    this.implement(
      
      this.hasFeature(apf.__MULTISELECT__)
        ? apf.MultiselectDragDrop : 
      
        apf.StandardDragDrop);
    
    //this.$booleanProperties["drag"] = true;
    //this.$booleanProperties["dragcopy"] = true;
    this.$supportedProperties.push("drop", "drag", "dragcopy");

    /**
     * @attribute  {Boolean}  drag   Sets or gets whether the element allows dragging of its items.
     *
     * #### Example
     *
     * ```xml
     *
     *  <a:list drag="true">
     *      <a:item>item 1</a:item>
     *      <a:item>item 2</a:item>
     *      <a:item>item 3</a:item>
     *  </a:list>
     *```
     *
     */
    /**
     * @attribute  {Boolean}  dragcopy   whether dragged items are copied.
     *
     * #### Example
     *
     * ```xml
     *  <a:list 
     *    drag = "true" 
     *    align = "right" 
     *    height = "300" 
     *    caption = "[@name]" 
     *    each = "[mail]">
     *      <a:model>
     *          <data>
     *              <mail name="Mail 1"></mail>
     *              <mail name="Mail 2"></mail>
     *              <mail name="Mail 3"></mail>
     *          </data>
     *      </a:model>
     *  </a:list>
     * ```
     *
     * #### Example
     *
     * Items are only copied when the user holds the [[keys: Ctrl]] key
     *
     * ```xml
     *  <a:list dragcopy="[ctrlKey]">
     *      <a:item>item 1</a:item>
     *      <a:item>item 2</a:item>
     *      <a:item>item 3</a:item>
     *  </a:list>
     * ```
     */
    /**
     * @attribute  {Boolean}  drop       Sets or gets whether the element allows items to be dropped.
     *
     * #### Example
     *
     *
     * ```xml
     *  <a:list drop="true">
     *      <a:item>item 1</a:item>
     *      <a:item>item 2</a:item>
     *      <a:item>item 3</a:item>
     *  </a:list>
     * ```
     * @attribute  {String}   dragdrop    Sets or gets the name of the dragdrop element for this element.
     *
     * ```xml
     *  <a:tree align="left" width="200" height="300">
     *      <a:each match="[root|account|folder|mail]">
     *          <a:caption match = "[@name]" />
     *          <a:drag    match = "[folder|mail]" />
     *          <a:drop    match = "[folder]" 
     *                     target = "[folder|account]"
     *                     action = "tree-append" />
     *           <a:drop   match = "[mail]" 
     *                     target = "[folder]"
     *                     action = "tree-append" />
     *      </a:each>
     *      <a:model>
     *          <data>
     *              <root name="Root">
     *                  <account name="Account 1">
     *                      <folder name="Folder 1">
     *                          <mail name="Mail drag drop"></mail>
     *                      </folder>
     *                  </account>
     *              </root>
     *          </data>
     *      </a:model>
     *  </a:tree>
     * 
     *  <a:list bindings="bndDragdrop" align="right">
     *      <a:model>
     *          <data>
     *              <mail name="Mail 1"></mail>
     *              <mail name="Mail 2"></mail>
     *              <mail name="Mail 3"></mail>
     *          </data>
     *      </a:model>
     *  </a:list>
     * 
     *  <a:bindings id="bndDragdrop">
     *      <a:caption match="[@name]" />
     *      <a:each match="[mail]" />
     *      <a:drag match = "[mail]" />
     *      <a:drop
     *        match = "[mail]"
     *        action = "list-append" />
     *   </a:bindings>
     * ```
     */
    this.$propHandlers["dragcopy"] =
    this.$propHandlers["dropcopy"] =
    this.$propHandlers["drag"] = 
    this.$propHandlers["drop"] = function(value, prop) {
        this[prop] = apf.isTrue(value);

        if (this.$dragInited && prop == "drag" && value && this.$dragInited != 2) {
            this.$initDragDrop();
            this.$dragInited = 2;
            return;
        }

        if (prop == "dragcopy" || prop == "dropcopy")
            return;
        
        if (!value && !this.drag && !this.drop && !this.$bindings 
          && (this.$attrBindings && (!this.$attrBindings["drag"] || !this.$attrBindings["drop"])))
            disableDragDrop.call(this);
        else if (value && !this.$dragInited)
            this.enableDragDrop();
    };

    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        disableDragDrop.call(this);
        
        if (this.oDrag) {
            apf.destroyHtmlNode(this.oDrag);
            this.oDrag = null;
        }
    });
};

apf.GuiElement.propHandlers["dragcopy"] =
apf.GuiElement.propHandlers["dropcopy"] =
apf.GuiElement.propHandlers["drop"] = 
apf.GuiElement.propHandlers["drag"] = function(value, prop) {
    if (!apf.isFalse(value)) {
        if (!this.hasFeature(apf.__DRAGDROP__)) {
            this.implement(apf.DragDrop);
            this.enableDragDrop();
        }
        
        this[prop] = apf.isTrue(value);
    }
};

/*
 * Central object for dragdrop handling.
 * @private
 */
apf.DragServer = {
    Init: function(){
        

        apf.addEventListener("hotkey", function(e) {
            if (apf.window.dragging && e.keyCode == 27) {
                if (document.body.lastHost && document.body.lastHost.dragOut)
                    document.body.lastHost.dragOut(apf.dragHost);

                return apf.DragServer.stopdrag();
            }
        });
    },

    start: function(amlNode, srcEl, e, customNode) {
        if (document.elementFromPointReset)
            document.elementFromPointReset();
        
        amlNode.dragging = 1;

        var d = window.document;
        d = (!d.compatMode || d.compatMode == "CSS1Compat")
            ? d.html || d.documentElement
            : d.body

        var scrollX = (apf.isIE ? d.scrollLeft : window.pageXOffset),
            scrollY = (apf.isIE ? d.scrollTop  : window.pageYOffset),
            oParent = amlNode.$ext.offsetParent,
            pos
        while (oParent && oParent != d && oParent.tagName != "BODY") {
            scrollX -= oParent.scrollLeft;
            scrollY -= oParent.scrollTop;
            oParent = oParent.offsetParent;
        }

        //The coordinates need to be relative to the html element that 
        //represents the xml data node.
        if (!srcEl && customNode) {
            pos = [0, 0];
        }
        else {
            var loopEl = srcEl, lastId;
            while (loopEl && loopEl.nodeType == 1 
              && !(lastId = loopEl.getAttribute(apf.xmldb.htmlIdTag))) {
                loopEl = loopEl.parentNode;
            }
            if (!lastId)
                return;
            pos = apf.getAbsolutePosition(loopEl);
        }

        //Set coordinates object
        apf.DragServer.coordinates = {
            srcElement: srcEl,
            doc: d,
            scrollX: scrollX,
            scrollY: scrollY,
            offsetX: e.clientX - pos[0],
            offsetY: e.clientY - pos[1],
            clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
            clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
        };
        
        //Create Drag Data Object
        var selection = customNode || amlNode.hasFeature(apf.__MULTISELECT__) 
                ? amlNode.getSelection()
                : [amlNode.xmlRoot],
            data = [],
            srcRules = amlNode.isDragAllowed(selection, data);
        if (!srcRules) return;

        if (amlNode.hasEventListener("dragdata"))
            data = amlNode.dispatchEvent("dragdata", {data : data});
        
        /*for(var i = 0, l = data.length; i < l; i++) {
            data[i] = apf.getCleanCopy(data[i]);
        }*/

        this.dragdata = {
            rules: srcRules,
            selection: selection,
            data: data,
            indicator: amlNode.$showDragIndicator(selection, this.coordinates),
            host: amlNode
        };

        //EVENT - cancelable: ondragstart
        if (amlNode.dispatchEvent("dragstart", this.dragdata) === false)
            return false;//(this.amlNode.$tempsel ? select(this.amlNode.$tempsel) : false);
        
        amlNode.dragging = 2;

        apf.dragMode = true;
        document.onmousemove = this.onmousemove;
        document.onmouseup = this.onmouseup;
    },

    stop: function(runEvent, success, e) {
        if (this.last) this.dragout();
        
        this.dragdata.host.dispatchEvent("dragstop", apf.extend(this.dragdata, {
            success: success
        }));
        
        //Reset Objects
        this.dragdata.host.dragging = 0;
        this.dragdata.host.$hideDragIndicator(success);
        
        /*if (runEvent && this.dragdata.host.$dragstop) 
            this.dragdata.host.$dragstop();*/
        
        apf.dragMode = false;
        document.onmousemove = 
        document.onmouseup = null;
        
        this.dragdata = null;
    },

    dragover: function(o, el, e) {
        var _self = this,
            originalEl = el;
        
        function checkPermission(targetEl) {
            return o.isDropAllowed && o.xmlRoot
                ? o.isDropAllowed(_self.dragdata.data, targetEl)
                : apf.isTrue(apf.getInheritedAttribute(o, "", function(p) {
                      if (p.drop) {
                          o = p;
                          if (o == apf.DragServer.last)
                            return false;
                          return true;
                      }
                   }));
        }
        
        e = e || window.event;

        //@todo optimize by not checking the same node dragged over twice in a row
        var fEl;
        if (o.$findValueNode)
            fEl = o.$findValueNode(el);

        if (this.lastFel && this.lastFel == fEl 
          || !this.lastFel && this.last == o) //optimization
            return;

        //Check Permission
        var elSel = (fEl
                ? apf.xmldb.getNode(fEl)
                : apf.xmldb.findXmlNode(el)),
            candrop = checkPermission(elSel || o.xmlRoot);

        if (this.last && this.last != o)
            this.dragout(this.last, e);

        this.last = o;
        this.lastFel = fEl;

        if (!candrop) {
            if (o && o.$dragover) {
                var parentNode = (elSel || o.xmlRoot).parentNode;
                if (parentNode && (el = apf.xmldb.findHtmlNode(parentNode, o))) {                   
                    if (o.$findValueNode)
                        fEl = o.$findValueNode(el);
                    
                    elSel = (fEl
                        ? apf.xmldb.getNode(fEl)
                        : apf.xmldb.findXmlNode(el));
                            
                    candrop = checkPermission(parentNode);
                    this.lastFel = el;
                    
                    
                    if (!candrop)
                        return;
                }
                else
                    return;
            }
            else
                return;
        }
        
        //EVENT - cancelable: ondragover
        if (o.dispatchEvent("dragover", this.dragdata, {
            target: (elSel || o.xmlRoot), 
            lastEl: o.lastel,
            originalEl: originalEl
        }) === false)
            candrop = false;

        //Set Cursor
        var srcEl = e.originalTarget || e.srcElement || e.target;
        /*srcEl.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);
        if (srcEl.onmouseout != this.m_out) {
            srcEl.$onmouseout = srcEl.onmouseout;
            srcEl.onmouseout = this.m_out;
        }
        o.$ext.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);*/

        //REQUIRED INTERFACE: __dragover()
        if (o && o.$dragover)
            o.$dragover(el, this.dragdata, candrop);
    },

    dragout: function(o, e) {
        //if (this.last == o) 
            //return false;

        this.lastFel = null;

        //EVENT: ondragout
        if (o) {
            this.dragdata.htmlEvent = e;
            o.dispatchEvent("dragout", this.dragdata);
        }

        //REQUIRED INTERFACE: __dragout()
        if (this.last && this.last.$dragout)
            this.last.$dragout(null, this.dragdata);

        //Reset Cursor
        //o.$ext.style.cursor = "default";
        this.last = null;
    },

    dragdrop: function(o, el, srcO, e) {
        var _self = this;
        
        function checkPermission(targetEl) {
            return o.isDropAllowed && o.xmlRoot
            ? o.isDropAllowed(_self.dragdata.data, targetEl)
            : apf.isTrue(apf.getInheritedAttribute(o, "", function(p) {
                if (p.drop) {
                    o = p;
                    return true;
                }
            }));
        }
        
        //Check Permission
        var isParent, lastTop,
            elSel = (o.$findValueNode
              ? apf.xmldb.getNode(o.$findValueNode(el))
              : apf.xmldb.findXmlNode(el)),
            candrop = checkPermission(elSel || o.xmlRoot);
         
        if (this.dragdata.indicator) {
            lastTop = this.dragdata.indicator.style.top;
            this.dragdata.indicator.style.top = "10000px";
        }
         
        if (!candrop) {
            if (o && o.$dragover) {
                var parentNode = (elSel || o.xmlRoot).parentNode,
                    htmlParentNode;
                if (parentNode && (htmlParentNode = apf.xmldb.findHtmlNode(parentNode, o))) {
                    isParent = true;
                    candrop = checkPermission(parentNode);
                    el = htmlParentNode;
                }
            }
        }

        //EVENT - cancelable: ondragdrop
        if (candrop) {
            if (o.dispatchEvent("dragdrop", apf.extend({candrop : candrop, htmlEvent : e, top: lastTop},
              this.dragdata)) === false) {
                candrop = false;
            }
            else {
                if (!o.xmlRoot) {
                    var m = o.getModel 
                      ? o.getModel(true) 
                      :
                      
                      apf.nameserver.get("model", o.model)
                      
                    if (m)
                        m.load(this.dragdata.data[0])
                    //else warn??
                    return true;
                }
                else {
                    var action = candrop[1]
                        && candrop[1].action
                        || (o.$isTreeArch ? "tree-append" : "list-append");
                    if (action == "list-append" && (!o.$isTreeArch && o == this.dragdata.host))
                        candrop = false;
                }
            }
        }
        
        if (this.dragdata.indicator)
            this.dragdata.indicator.style.top = lastTop;

        //Exit if not allowed
        if (!candrop) {
            this.dragout(o, e);
            return false;
        }

        if (o.$dragDrop) {
            //Move XML
            var rNode = o.$dragDrop(candrop[0], this.dragdata.data, candrop[1],
                action, isParent || candrop[0] == o.xmlRoot, this.dragdata.rules, e);
            this.dragdata.resultNode = rNode;
        }
        
        if (o.$dragdrop) {
            o.$dragdrop(el, apf.extend({
                htmlEvent: e,
                xmlNode: rNode
            }, this.dragdata), candrop);
        }

        //Reset Cursor
        //o.$ext.style.cursor = "default";
        this.last = null;
        this.lastFel = null;
        
        return true;
    },

    /* **********************
        Mouse Movements
    ***********************/

    onmousemove: function(e) {
        if (!apf.DragServer.dragdata) return;
        e = e || window.event;
        
        
        var dragdata = apf.DragServer.dragdata,
            c = {
                clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
                clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
            };

        if (!dragdata.started
          && Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6
          && Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6)
            return;

        if (!dragdata.started) {
            if (dragdata.host.$dragstart)
                dragdata.host.$dragstart(null, dragdata);
            dragdata.started = true;
        }
        
        //dragdata.indicator.style.top = e.clientY+"px";
        //dragdata.indicator.style.left = e.clientX+"px";

        if (dragdata.indicator) {
            var storeIndicatorTopPos = dragdata.indicator.style.top;
            //console.log("INDICATOR BEFORE: "+dragdata.indicator.style.top+" "+dragdata.indicator.style.left);
            //get Element at x, y
            dragdata.indicator.style.display = "block";
                dragdata.indicator.style.top = "10000px";
        }
        apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE
            ? window.pageXOffset
            : 0) : c.clientX;
        apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE
            ? window.pageYOffset
            : 0) : c.clientY;
        var el = document.elementFromPoint(apf.DragServer.dragdata.x,
            apf.DragServer.dragdata.y);
            if (!el) {
                el = document.elementFromPoint(apf.DragServer.dragdata.x,
                apf.DragServer.dragdata.y);
            }
        
        if (dragdata.indicator)
            dragdata.indicator.style.top = storeIndicatorTopPos;
        //console.log("INDICATOR AFTER: "+dragdata.indicator.style.top+" "
        //+dragdata.indicator.style.left+" "+apf.DragServer.dragdata.x+" "+apf.DragServer.dragdata.y);
        //Set Indicator
        dragdata.host.$moveDragIndicator(c);

        //get element and call events
        var receiver = apf.findHost(el);

        //Run Events
        if (receiver)
            apf.DragServer.dragover(receiver, el, e);
        else if (apf.DragServer.last)
            apf.DragServer.dragout(apf.DragServer.last, e);

        apf.DragServer.lastTime = new Date().getTime();
    },

    onmouseup: function(e) {
        e = e || window.event;
        

        var c = {
            clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
            clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
        };

        if (!apf.DragServer.dragdata.started
          && Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6
          && Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6) {
            apf.DragServer.stop(true, null, e)
            return;
        }

        //get Element at x, y
        var indicator = apf.DragServer.dragdata.indicator,
            storeIndicatorTopPos = indicator.style.top;
        //apf.console.info("INDICATOR UP BEFORE: "+indicator.style.top+" "+indicator.style.left);
        if (indicator)
            indicator.style.top = "10000px";

        apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE
            ? window.pageXOffset
            : 0) : c.clientX;
        apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE
            ? window.pageYOffset
            : 0) : c.clientY;

        var el = document.elementFromPoint(apf.DragServer.dragdata.x,
            apf.DragServer.dragdata.y);
        if (!el) {
            el = document.elementFromPoint(apf.DragServer.dragdata.x,
            apf.DragServer.dragdata.y);
        }

        indicator.style.top = storeIndicatorTopPos;
        //apf.console.info("INDICATOR UP AFTER: "+indicator.style.top+" "+indicator.style.left);

        //get element and call events
        var host = apf.findHost(el);

        //Run Events
        if (apf.DragServer.host && host != apf.DragServer.host)
            apf.DragServer.dragout(apf.DragServer.host, e);
        var success = apf.DragServer.dragdrop(host, el, apf.DragServer.dragdata.host, e);
        apf.DragServer.stop(true, success, e);
    }
};

/*
 * @private
 */
apf.MultiselectDragDrop = function() {
    // *** Drag & Drop *** //
    
    this.diffX = 
    this.diffY = 0;
    this.multiple = false;
    this.lastDragNode = null;
    this.lastel = null;

    this.$showDragIndicator = function(sel, e) {
        var srcEl = e.originalTarget || e.srcElement || e.target;
        
        this.multiple = sel.length > 1;
        
        if (this.multiple) {
            this.diffX = e.scrollX;
            this.diffY = e.scrollY;
        }
        else {
            var itemNode = apf.xmldb.findHtmlNode(sel[0], this);
            this.diffX = -1 * (e.offsetX - parseInt(apf.getStyleRecur(itemNode, "padding-left").replace(/px$/, "") - 10));
            this.diffY = -1 * e.offsetY;
        }
        
        var prefix = this.oDrag.className.split(" ")[0]
        //@todo the class should be removed here
        this.$setStyleClass(this.oDrag, (this.multiple
            ? prefix + "_multiple" : "") + (this["class"] ? " " + this["class"] : ""), [prefix + "_multiple"]);

        if (this.multiple) {
            document.body.appendChild(this.oDrag);
            return this.oDrag;
        }
        else if (this.localName == "datagrid") {
            if (this.lastDragNode)
                apf.destroyHtmlNode(this.lastDragNode);

            sel = this.$selected || this.$caret;
            var oDrag = sel.cloneNode(true);
            oDrag.removeAttribute("onmousedown");oDrag.onmousedown = null;
            oDrag.removeAttribute("onmouseup");oDrag.onmouseup = null;
            oDrag.removeAttribute("onmouseout");oDrag.onmouseout = null;
            oDrag.removeAttribute("ondblclick");oDrag.ondblclick = null;
            document.body.appendChild(oDrag);
            
            oDrag.style.position = "absolute";
            oDrag.style.width = sel.offsetWidth + "px";
            oDrag.style.display = "none";
            oDrag.removeAttribute("id");
            
            this.$setStyleClass(oDrag, "draggrid");
            var nodes = sel.childNodes;
            var dragnodes = oDrag.childNodes;
            for (var i = nodes.length - 1; i >= 0; i--) {
                if (dragnodes[i].nodeType == 1)
                    dragnodes[i].style.width = apf.getStyle(nodes[i], "width");
            }
            //@todo apf3.0 remove all the event handlers of the children.
            return (this.lastDragNode = oDrag);
        }
        else {
            var sel = this.$selected || this.$caret,
                width = apf.getStyle(this.oDrag, "width");
            
            if (!sel)
                return;
            
            // if (!width || width == "auto")
            //     this.oDrag.style.width = (sel.offsetWidth - apf.getWidthDiff(this.oDrag)) + "px";
            this.$updateNode(this.selected, this.oDrag);
        }
        
        apf.window.zManager.set("drag", this.oDrag);
        
        return this.oDrag;
    };
    
    this.$hideDragIndicator = function(success) {
        var oDrag = this.lastDragNode || this.oDrag, _self = this;
        if (!this.multiple && !success && oDrag.style.display == "block") {
            if (!this.$selected && !this.$caret)
                return;
            
            var pos = apf.getAbsolutePosition(this.$selected || this.$caret);
            apf.tween.multi(oDrag, {
                anim: apf.tween.easeInOutCubic,
                steps: apf.isIE ? 15 : 20,
                interval: 15,
                tweens: [
                    {type: "left", from: oDrag.offsetLeft, to: (pos[0] + parseInt(apf.getStyleRecur(this.$selected, "padding-left").replace(/px$/, "")))},
                    {type: "top",  from: oDrag.offsetTop,  to: pos[1]}
                ],
                onfinish: function(){
                    if (_self.lastDragNode) {
                        apf.destroyHtmlNode(_self.lastDragNode);
                        _self.lastDragNode = null;
                    }
                    else {
                        _self.oDrag.style.display = "none";
                    }
                }
            });
        }
        else if (this.lastDragNode) {
            apf.destroyHtmlNode(this.lastDragNode);
            this.lastDragNode = null;
        }
        else {
            this.oDrag.style.display = "none";
        }
    };
    
    this.$moveDragIndicator = function(e) {
        var oDrag = this.lastDragNode || this.oDrag;
        oDrag.style.left = (e.clientX + this.diffX) + "px";// - this.oDrag.startX
        oDrag.style.top = (e.clientY + this.diffY + (this.multiple ? 15 : 0)) + "px";// - this.oDrag.startY
    };
    
    this.addEventListener("$skinchange", function(){
        this.$initDragDrop();
    });
    
    this.$initDragDrop = function(){
        if (!this.$hasLayoutNode("dragindicator")) 
            return;

        this.oDrag = apf.insertHtmlNode(
            this.$getLayoutNode("dragindicator"), document.body);

        apf.window.zManager.set("drag", this.oDrag);
        
        this.oDrag.style.position = "absolute";
        this.oDrag.style.cursor = "default";
        this.oDrag.style.display = "none";
    };

    this.$findValueNode = function(el) {
        if (!el) return null;

        while (el && el.nodeType == 1 
          && !el.getAttribute(apf.xmldb.htmlIdTag)) {
            if (this.$isTreeArch && el.previousSibling 
              && el.previousSibling.nodeType == 1) //@todo hack!! apf3.0 fix this.
                el = el.previousSibling;
            else
                el = el.parentNode;
        }

        return (el && el.nodeType == 1 && el.getAttribute(apf.xmldb.htmlIdTag)) 
            ? el 
            : null;
    };
    

    this.$dragout = function(el, dragdata, extra) {
        if (this.lastel)
            this.$setStyleClass(this.lastel, "", ["dragDenied", "dragInsert",
                "dragAppend", "selected", "indicate"]);
        
        var sel = this.$getSelection(true);
        for (var i = 0, l = sel.length; i < l; i++) 
            this.$setStyleClass(sel[i], "selected", ["dragDenied",
                "dragInsert", "dragAppend", "indicate"]);
        
        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Drop"]);
        
        this.lastel = null;
    };
    
    if (!this.$dragdrop)
        this.$dragdrop = this.$dragout;

    this.$dragover = function(el, dragdata, extra) {
        this.$setStyleClass(this.$ext, this.$baseCSSname + "Drop");
        
        var sel = this.$getSelection(true);
        for (var i = 0, l = sel.length; i < l; i++) 
            this.$setStyleClass(sel[i], "", ["dragDenied",
                "dragInsert", "dragAppend", "selected", "indicate"]);
        
        if (this.lastel)
            this.$setStyleClass(this.lastel, "", ["dragDenied",
                "dragInsert", "dragAppend", "selected", "indicate"]);

        var action = extra[1] && extra[1].action;
        this.lastel = this.$findValueNode(el);
        if (this.$isTreeArch && action == "list-append") {
            var htmlNode = apf.xmldb.findHtmlNode(this.getTraverseParent(apf.xmldb.getNode(this.lastel)), this);
            
            this.lastel = htmlNode
                ? this.$getLayoutNode("item", "container", htmlNode)
                : this.$container;
            
            this.$setStyleClass(this.lastel, "dragInsert");
        }
        else {
            this.$setStyleClass(this.lastel, extra
                ? (action == "insert-before" 
                    ? "dragInsert" 
                    : "dragAppend") 
                : "dragDenied");
        }
    };
    
};

/*
 * @private
 */
apf.StandardDragDrop = function() {
    this.$showDragIndicator = function(sel, e) {
        var x = e.offsetX + 22,
            y = e.offsetY;
        
        this.oDrag.startX = x;
        this.oDrag.startY = y;
        
        
        document.body.appendChild(this.oDrag);
        //this.oDrag.getElementsByTagName("DIV")[0].innerHTML = this.selected.innerHTML;
        //this.oDrag.getElementsByTagName("IMG")[0].src = this.selected.parentNode.parentNode.childNodes[1].firstChild.src;
        var oInt = this.$getLayoutNode("main", "caption", this.oDrag);
        if (oInt.nodeType != 1) 
            oInt = oInt.parentNode;
        
        oInt.innerHTML = this.$applyBindRule("caption", this.xmlRoot) || "";
        
        return this.oDrag;
    };
    
    this.$hideDragIndicator = function(){
        this.oDrag.style.display = "none";
    };
    
    this.$moveDragIndicator = function(e) {
        this.oDrag.style.left = (e.clientX - this.oDrag.startX
            + document.documentElement.scrollLeft) + "px";
        this.oDrag.style.top = (e.clientY - this.oDrag.startY
            + document.documentElement.scrollTop) + "px";
    };
    
    //@todo falsely assuming only attributes are used for non multiselect widgets
    this.$initDragDrop = function(){
        if (!this.getAttribute("drag"))
            return;
        
        this.oDrag = document.body.appendChild(this.$ext.cloneNode(true));
        
        apf.window.zManager.set("drag", this.oDrag);
        
        this.oDrag.style.position = "absolute";
        this.oDrag.style.cursor = "default";
        this.oDrag.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=50)";
        this.oDrag.style.MozOpacity = 0.5;
        this.oDrag.style.opacity = 0.5;
        this.oDrag.style.display = "none";
    };
};

apf.DragServer.Init();






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__FOCUSSABLE__ = 1 << 26;

/**
 * All elements inheriting from this {@link term.baseclass baseclass} have focussable
 * features
 * 
 * @class apf.Focussable
 * @baseclass
 * 
 */

apf.Focussable = function(){
    this.$regbase = this.$regbase | apf.__FOCUSSABLE__;
    if (this.disabled == undefined)
        this.disabled = false;
    
    /**
     * Sets the position in the list that determines the sequence
     * of elements when using the pane key to move between them.
     * @chainable
     * @param {Number} tabindex The position in the list
     */
    this.setTabIndex = function(tabindex) {
        apf.window.$removeFocus(this);
        apf.window.$addFocus(this, tabindex);
        return this;
    };

    /**
     * Gives this element the focus. This means that keyboard events
     * are sent to this element.
     * @chainable
     */
    this.focus = function(noset, e, nofix) {
        if (!noset) {
            if (this.$isWindowContainer > -1) {
                apf.window.$focusLast(this, e, true);
            }
            else {
                apf.window.$focus(this, e);

                
            }

            return this;
        }

        if (this.$focus && !this.editable && (!e || !e.mouse || this.$focussable == apf.KEYBOARD_MOUSE))
            this.$focus(e);

        this.dispatchEvent("focus", apf.extend({
            bubbles: true
        }, e));
        return this;
    };

    /**
     * Removes the focus from this element.
     * @chainable
     */
    this.blur = function(noset, e) {
        
        if ((e && !apf.isChildOf(e.fromElement, e.toElement)) && apf.popup.isShowing(this.$uniqueId) && e.toElement.localName != "menu")
            apf.popup.forceHide(); //This should be put in a more general position
        
        
        if (this.$blur)
            this.$blur(e);

        if (!noset)
            apf.window.$blur(this);

        this.dispatchEvent("blur", apf.extend({
            bubbles: !e || !e.cancelBubble
        }, e));
        return this;
    };

    /**
     * Determines whether this element has the focus
     * @returns {Boolean} Indicates whether this element has the focus
     */
    this.hasFocus = function(){
        return apf.document.activeElement == this || this.$isWindowContainer
            && (apf.document.activeElement || {}).$focusParent == this;
    };
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__INTERACTIVE__ = 1 << 21;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have interactive features, making an
 * element draggable and resizable.
 * 
 * #### Example
 *
 * ```xml
 *  <a:textarea draggable="true" resizable="true" />
 * ```
 * 
 * @class apf.Interactive
 * @baseclass
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       1.0
 *
 * @see apf.appsettings.outline
 * @see apf.appsettings.resize-outline
 * @see apf.appsettings.drag-outline
 */
/**
 * @attribute {Boolean} draggable Sets or gets whether an element is draggable. The user will
 * able to move the element around while holding the mouse button down on the 
 * element.
 * 
 * #### Example
 *
 * ```xml
 *  <a:bar 
 *    draggable = "true" 
 *    width = "200" 
 *    height = "200" 
 *    left = "10" 
 *    top = "10" />
 * ```
 */
/**
 * @attribute {Boolean} resizable Sets or gets whether an element is resizable. 
 * 
 * The user will able
 * to resize the element by grabbing one of the four edges of the element and 
 * pulling it in either direction. Grabbing the corners allows users to 
 * resize horizontally and vertically at the same time. The right bottom corner 
 * is special, because it offers an especially big grab area. The size of this
 * area can be configured in the skin of the element.
 * 
 * #### Example
 *
 * ```xml
 *  <a:window 
 *    resizable = "true"
 *    visible = "true" 
 *    width = "400" 
 *    height = "200" />
 * ```
 */
/**
 * @attribute {Number} minwidth  Sets or gets the minimum horizontal size the element can get when resizing.
 */
/**
 * @attribute {Number} minheight Sets or gets the minimum vertical size the element can get when resizing.
 */
/**
 * @attribute {Number} maxwidth  Sets or gets the maximum horizontal size the element can get when resizing.
 */
/**
 * @attribute {Number} maxheight Sets or gets the maximum vertical size the element can get when resizing.
 *
 */
/**
 * @event drag          Fires when the widget has been dragged.
 */
/**
 * @event resizestart   Fires before the widget is resized.
 * @cancelable Prevents this resize action to start.
 * @param {Object} e The standard event object. It contains the following property:
 *   - type ([[String]]): the type of resize. This is a combination of the four directions--`"n"`, `"s"`, `"e"`, `"w"`.
 */
/**
 * @event resize        Fires when the widget has been resized.
 *
 */
apf.Interactive = function(){
    var nX, nY, rX, rY, startPos, lastCursor = null, l, t, r, b, lMax, tMax, lMin,
        tMin, w, h, we, no, ea, so, rszborder, rszcorner, marginBox,
        verdiff, hordiff, _self = this, posAbs, oX, oY, overThreshold,
        dragOutline, resizeOutline, myPos, startGeo;

    this.$regbase = this.$regbase | apf.__INTERACTIVE__;

    this.$dragStart = function(e, reparent) {
        var nativeEvent = e || event;
        if (!reparent && nativeEvent.button == 2)
            return;

        
        {
            dragStart.apply(nativeEvent.srcElement || this, arguments);
        }
    }

    this.$propHandlers["draggable"] = function(value) {
        if (apf.isFalse(value))
            this.draggable = value = false;
        else if (apf.isTrue(value))
            this.draggable = value = true;

        var o = this.editable ? this.$ext : this.oDrag || this.$ext;
        if (value)
            apf.addListener(o, "mousedown", this.$dragStart);
        else
            apf.removeListener(o, "mousedown", this.$dragStart);
        
        //deprecated??
        if (o.interactive & 1) 
            return;
        o.interactive = (o.interactive||0)+1;
        
        //this.$ext.style.position = "absolute";
    };

    this.$propHandlers["resizable"] = function(value) {
        if (apf.isFalse(value))
            this.resizable = false;
        else if (apf.isTrue(value))
            this.resizable = "true";
        
        this.$ext.style.cursor = "";
        
        var o = this.oResize || this.$ext;
        if (o.interactive & 2) 
            return;

        if (!_self.editable) {        
            apf.addListener(o, "mousedown", function(){
                resizeStart.apply(o, arguments);
            });
    
            apf.addListener(o, "mousemove", function(){
                resizeIndicate.apply(o, arguments);
            });
        }
        
        o.interactive = (o.interactive||0)+2;
        
        //this.$ext.style.position = "absolute";
        
        rszborder = this.$getOption && parseInt(this.$getOption("Main", "resize-border")) || 3;
        rszcorner = this.$getOption && parseInt(this.$getOption("Main", "resize-corner")) || 12;
        marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth"));
    };
    
    /*
    this.$propHandlers["minwidth"] = 
    this.$propHandlers["maxwidth"] = 
    this.$propHandlers["minheight"] = 
    this.$propHandlers["maxheight"] = function(value, prop) {
        if (this.aData)
            this.aData[prop] = parseInt(value);
    }
    if (this.aData) {
        this.aData.minwidth = this.minwidth;
        this.aData.minheight = this.minheight;
    }*/
    
    this.$cancelInteractive = function(){
        document.onmouseup(null, true);
    }
    
    function dragStart(e, reparent) {
        if (!e) e = event;

        if (!reparent && (!_self.draggable || apf.dragMode))//_self.editable || 
            return;
        
        
        dragOutline = false;        
        
        
        if (_self.dispatchEvent("beforedragstart", {htmlEvent: e}) === false)
            return;
        
        apf.dragMode = true;
        if (reparent) {
            _self.dispatchEvent("beforedrag")
            overThreshold = true;
        }
        else
            overThreshold = false;
        
        
        apf.popup.forceHide();
        
        
        posAbs = "absolute|fixed".indexOf(apf.getStyle(_self.$ext, "position")) > -1;
        if (!posAbs) {
            _self.$ext.style.position = posAbs //(posAbs = _self.dragSelection) 
                ? "absolute" : "relative";
        }
        if (_self.editable)
            posAbs = true;

        //@todo not for docking
        
        if (posAbs && !_self.aData) {
            apf.plane.show(dragOutline
                ? oOutline
                : _self.$ext, e.reappend);//, true
        }
        

        var ext = (reparent || (oOutline && oOutline.self)) && dragOutline //little dirty hack to detect outline set by visualselect
            ? oOutline 
            : _self.$ext;
        var pos = posAbs
            ? apf.getAbsolutePosition(ext, ext.offsetParent, true) 
            : [parseInt(apf.getStyle(ext, "left")) || 0, 
               parseInt(apf.getStyle(ext, "top")) || 0];

        
        startGeo = [ext.style.left, ext.style.top, ext.style.right, 
                    ext.style.bottom, ext.style.width, ext.style.height];
        

        nX = pos[0] - (oX = e.clientX);
        nY = pos[1] - (oY = e.clientY);
        
        //if (_self.hasFeature && _self.hasFeature(apf.__ANCHORING__))
            //_self.$disableAnchoring();

        if (!(reparent || (oOutline && oOutline.self))) {
            
            {
                if (_self.$ext.style.right) {
                    _self.$ext.style.left = pos[0] + "px";
                    _self.$ext.style.right = "";
                }
                if (_self.$ext.style.bottom) {
                    _self.$ext.style.top = pos[1] + "px";
                    _self.$ext.style.bottom = "";
                }
            }
        }

        document.onmousemove = dragMove;
        document.onmouseup = function(e, cancel) {
            document.onmousemove = document.onmouseup = null;

            
            if (posAbs && !_self.aData)
                apf.plane.hide();
            
            
            var htmlNode = dragOutline
                ? oOutline
                : _self.$ext;

            if (overThreshold && !_self.$multidrag) {
                
                if (cancel) {
                    var ext = _self.$ext;
                    ext.style.left = startGeo[0];
                    ext.style.top = startGeo[1];
                    ext.style.right = startGeo[2];
                    ext.style.bottom = startGeo[3];
                    ext.style.width = startGeo[4];
                    ext.style.height = startGeo[5];
                    
                    if (_self.dispatchEvent)
                        _self.dispatchEvent("dragcancel", {
                            htmlNode: htmlNode,
                            htmlEvent: e
                        });
                }
                else
                
                
                if (_self.setProperty) {
                    updateProperties();
                }
                else if (dragOutline) {
                    _self.$ext.style.left = l + "px";
                    _self.$ext.style.top = t + "px";
                }
            }
            
            l = t = w = h = null;
            
            if (!posAbs)
                _self.$ext.style.position = "relative";
            
            if (_self.showdragging)
                apf.setStyleClass(_self.$ext, "", ["dragging"]);
            
            if (posAbs && dragOutline && !oOutline.self) //little dirty hack to detect outline set by visualselect
                oOutline.style.display = "none";
            
            apf.dragMode = false;

            if (!cancel && _self.dispatchEvent && overThreshold)
                _self.dispatchEvent("afterdrag", {
                    htmlNode: htmlNode,
                    htmlEvent: e
                });
        };
        
        if (reparent)
            document.onmousemove(e);
        //else if (apf.isIE)
            //apf.window.$mousedown(e);

        return false;
    };
    
    function dragMove(e) {
        if (!e) e = event;
        
        //if (_self.dragSelection)
            //overThreshold = true;
        
        if (!overThreshold && _self.showdragging)
            apf.setStyleClass(_self.$ext, "dragging");
        
        // usability rule: start dragging ONLY when mouse pointer has moved delta x pixels
        var dx = e.clientX - oX,
            dy = e.clientY - oY,
            distance; 

        if (!overThreshold 
          && (distance = dx*dx > dy*dy ? dx : dy) * distance < 2)
            return;

        //Drag outline support
        else if (!overThreshold) {
            if (dragOutline 
              && oOutline.style.display != "block")
                oOutline.style.display = "block";

            if (_self.dispatchEvent && _self.dispatchEvent("beforedrag", {htmlEvent: e}) === false) {
                document.onmouseup();
                return;
            }
        }

        var oHtml = dragOutline
            ? oOutline
            : _self.$ext;

        oHtml.style.left = (l = e.clientX + nX) + "px";
        oHtml.style.top = (t = e.clientY + nY) + "px";

        if (_self.realtime) {
            var change = _self.$stick = {};
            _self.$showDrag(l, t, oHtml, e, change);
            
            if (typeof change.l != "undefined") 
                l = change.l, oHtml.style.left = l + "px";
            if (typeof change.t != "undefined") 
                t = change.t, oHtml.style.top = t + "px";
        }

        overThreshold = true;
    };
    
    this.$resizeStart = resizeStart;
    function resizeStart(e, options) {
        if (!e) e = event;

        //|| _self.editable 
        if (!_self.resizable 
          || String(_self.height).indexOf("%") > -1 && _self.parentNode.localName == "vbox" //can't resize percentage based for now
          || String(_self.width).indexOf("%") > -1 && _self.parentNode.localName == "hbox") //can't resize percentage based for now
            return;

        
        resizeOutline = false;        
        
        
        var ext = _self.$ext;
        if (!resizeOutline) {
            var diff = apf.getDiff(ext);
            hordiff = diff[0];
            verdiff = diff[1];
        }
        
        //@todo This is probably not gen purpose
        startPos = apf.getAbsolutePosition(ext);//, ext.offsetParent);
        startPos.push(ext.offsetWidth);
        startPos.push(ext.offsetHeight);
        myPos = apf.getAbsolutePosition(ext, ext.offsetParent, true);

        
        startGeo = [ext.style.left, ext.style.top, ext.style.right, 
                    ext.style.bottom, ext.style.width, ext.style.height];
        

        var sLeft = 0,
            sTop = 0,
            x = (oX = e.clientX) - startPos[0] + sLeft + document.documentElement.scrollLeft,
            y = (oY = e.clientY) - startPos[1] + sTop + document.documentElement.scrollTop,
            resizeType;

        if (options && options.resizeType) {
            posAbs = "absolute|fixed".indexOf(apf.getStyle(ext, "position")) > -1;
            resizeType = options.resizeType;
        }
        else {
            resizeType = getResizeType.call(ext, x, y);
        }
        rX = x;
        rY = y;

        if (!resizeType)
            return;

        if (_self.dispatchEvent && _self.dispatchEvent("beforeresize", {
            type: resizeType,
            setType: function(type) {
                resizeType = type;
            }
          }) === false) {
            //if (apf.isIE)
                //apf.window.$mousedown(e); //@todo is this necessary?
            return;
        }
        
        
        apf.popup.forceHide();
        

        //if (_self.hasFeature && _self.hasFeature(apf.__ANCHORING__))
            //_self.$disableAnchoring();
        
        apf.dragMode = true;
        overThreshold = false;

        we = resizeType.indexOf("w") > -1;
        no = resizeType.indexOf("n") > -1;
        ea = resizeType.indexOf("e") > -1;
        so = resizeType.indexOf("s") > -1;
        
        if (!_self.minwidth)  _self.minwidth = 0;
        if (!_self.minheight) _self.minheight = 0;
        if (!_self.maxwidth)  _self.maxwidth = 10000;
        if (!_self.maxheight) _self.maxheight = 10000;

        if (posAbs) {
            lMax = myPos[0] + startPos[2];
            tMax = myPos[1] + startPos[3];
            lMin = myPos[0] + startPos[2];
            tMin = myPos[1] + startPos[3];
        }

        
        if (posAbs) {
            apf.plane.show(resizeOutline
                ? oOutline
                : ext);//, true
        }
        
        
        var iMarginLeft;
        
        
        {
            if (ext.style.right) {
                ext.style.left = myPos[0] + "px";

                //console.log(myPos[0]);
                //ext.style.right = "";
            }
            if (ext.style.bottom) {
                ext.style.top = myPos[1] + "px";
                //ext.style.bottom = "";
            }
        }
        
        if (!options || !options.nocursor) {
            if (lastCursor === null)
                lastCursor = document.body.style.cursor;//apf.getStyle(document.body, "cursor");
            document.body.style.cursor = getCssCursor(resizeType) + "-resize";
        }
        
        document.onmousemove = resizeMove;
        document.onmouseup = function(e, cancel) {
            document.onmousemove = document.onmouseup = null;
            
            
            if (posAbs)
                apf.plane.hide();
            
            
            clearTimeout(timer);
            
            if (resizeOutline) {
                var diff = apf.getDiff(_self.$ext);
                hordiff = diff[0];
                verdiff = diff[1];
            }

            
            if (cancel) {
                var ext = _self.$ext;
                ext.style.left = startGeo[0];
                ext.style.top = startGeo[1];
                ext.style.right = startGeo[2];
                ext.style.bottom = startGeo[3];
                ext.style.width = startGeo[4];
                ext.style.height = startGeo[5];
                
                if (_self.dispatchEvent)
                    _self.dispatchEvent("resizecancel");
            }
            else
            
                doResize(e || event, true);

            if (_self.setProperty)
                updateProperties();
                
            document.body.style.cursor = lastCursor || "";
            lastCursor = null;
            
            if (resizeOutline)
                oOutline.style.display = "none";
            
            apf.dragMode = false;

            if (!cancel && _self.dispatchEvent)
                _self.dispatchEvent("afterresize", {
                    l: l, t: t, w: w+hordiff, h: h+verdiff
                });
            
            l = t = w = h = null;
        };
        
        //if (apf.isIE)
            //apf.window.$mousedown(e);

        return false;
    }
    
    function updateProperties(left, top, width, height, hdiff, vdiff, right, bottom) {
        if (typeof left == "undefined") {
            left = l, top = t, width = w, height = h, 
                vdiff = verdiff, hdiff = hordiff;
        }
        else posAbs = true;

        var hasLeft = _self.left || _self.left === 0;
        var hasRight = _self.right || _self.right === 0;
        var hasBottom = _self.bottom || _self.bottom === 0;
        var hasTop = _self.top || _self.top === 0;

        if (posAbs) {
            var htmlNode = (oOutline && oOutline.style.display == "block")
                ? oOutline
                : _self.$ext;

            if (hasRight && !(right || right === 0))
                right = apf.getHtmlRight(htmlNode);

            if (hasBottom && !(bottom || bottom === 0))
                bottom = apf.getHtmlBottom(htmlNode);

            if (hasRight) {
                _self.setProperty("right", right, 0, _self.editable);
                if (!_self.left)
                    htmlNode.style.left = "";
            }
            
            if (hasBottom) {
                _self.setProperty("bottom", bottom, 0, _self.editable);
                if (!_self.top)
                    htmlNode.style.top = "";
            }

            if ((left || left === 0) && (!hasRight || hasLeft)) 
                _self.setProperty("left", left, 0, _self.editable);
            if ((top || top === 0) && (!hasBottom || hasTop)) 
                _self.setProperty("top", top, 0, _self.editable);
        }

        if (hdiff != undefined && width && (!hasLeft || !hasRight)) 
            _self.setProperty("width", width + hdiff, 0, _self.editable) 
        if (vdiff != undefined && height && (!hasTop || !hasBottom)) 
            _self.setProperty("height", height + vdiff, 0, _self.editable); 
    }
    this.$updateProperties = updateProperties;
    
    var min = Math.min, max = Math.max, lastTime, timer;
    function resizeMove(e) {
        if (!e) e = event;
        
        //if (!e.button)
            //return this.onmouseup();
        
        // usability rule: start dragging ONLY when mouse pointer has moved delta x pixels
        /*var dx = e.clientX - oX,
            dy = e.clientY - oY,
            distance; 
        
        if (!overThreshold 
          && (distance = dx*dx > dy*dy ? dx : dy) * distance < 4)
            return;*/
        
        clearTimeout(timer);
        if (lastTime && new Date().getTime() 
          - lastTime < (resizeOutline ? 6 : apf.mouseEventBuffer)) {
            var z = {
                clientX: e.clientX,
                clientY: e.clientY
            }
            timer = $setTimeout(function(){
                doResize(z);
            }, 10);
            return;
        }
        lastTime = new Date().getTime();
        
        doResize(e);
        
        //overThreshold = true;
    }
    
    function doResize(e, force) {
        var oHtml = resizeOutline && !force
            ? oOutline
            : _self.$ext;

        var sLeft = document.documentElement.scrollLeft,
            sTop = document.documentElement.scrollTop;
        
        if (we) {
            if (posAbs)
                oHtml.style.left = (l = max((lMin - _self.maxwidth), 
                    min((lMax - _self.minwidth), 
                    myPos[0] + e.clientX - oX + sLeft))) + "px";
            oHtml.style.width = (w = min(_self.maxwidth - hordiff, 
                max(hordiff, _self.minwidth, 
                    startPos[2] - (e.clientX - oX) + sLeft
                    ) - hordiff)) + "px"; //@todo
        }
        
        if (no) {
            if (posAbs)
                oHtml.style.top = (t = max((tMin - _self.maxheight), 
                    min((tMax - _self.minheight), 
                    myPos[1] + e.clientY - oY + sTop))) + "px";
            oHtml.style.height = (h = min(_self.maxheight - verdiff, 
                max(verdiff, _self.minheight, 
                    startPos[3] - (e.clientY - oY) + sTop
                    ) - verdiff)) + "px"; //@todo
        }

        if (ea)
            oHtml.style.width = (w = min(_self.maxwidth - hordiff, 
                max(hordiff, _self.minwidth, 
                    e.clientX - startPos[0] + (startPos[2] - rX) + sLeft)
                    - hordiff)) + "px";

        if (so)
            oHtml.style.height = (h = min(_self.maxheight - verdiff, 
                max(verdiff, _self.minheight, 
                    e.clientY - startPos[1] + (startPos[3] - rY) + sTop)
                    - verdiff)) + "px";

        //@todo apf3.0 this is execution wise inefficient
        if (_self.parentNode && _self.parentNode.localName == "table") {
            updateProperties();
            apf.layout.processQueue();
        }
        
        if (_self.realtime) {
            var change = _self.$stick = {};
            
            //@todo calc l and t once at start of resize (subtract borders)
            _self.$showResize(l || apf.getHtmlLeft(oHtml), t || apf.getHtmlTop(oHtml), 
                w && w + hordiff || oHtml.offsetWidth, 
                h && h + verdiff || oHtml.offsetHeight, e, change, we, no, ea, so);

            if (posAbs && we && typeof change.l != "undefined")
                oHtml.style.left = (l = max((lMin - _self.maxwidth), min((lMax - _self.minwidth), change.l))) + "px";
            
            if (posAbs && no && typeof change.t != "undefined")
                oHtml.style.top = (t = max((tMin - _self.maxheight), min((tMax - _self.minheight), change.t))) + "px";
            
            if (typeof change.w != "undefined") 
                oHtml.style.width = (w = min(_self.maxwidth - hordiff, 
                    max(hordiff, _self.minwidth, 
                        change.w) - hordiff)) + "px";
            if (typeof change.h != "undefined") 
                oHtml.style.height = (h = min(_self.maxheight - verdiff, 
                    max(verdiff, _self.minheight, 
                        change.h) - verdiff)) + "px";
        }

        
        if (apf.hasSingleRszEvent)
            apf.layout.forceResize(_self.$int);
        
    }
    
    function getResizeType(x, y) {
        var cursor = "", 
            tcursor = "";
        posAbs = "absolute|fixed".indexOf(apf.getStyle(_self.$ext, "position")) > -1;
        if (_self.resizable == "true" || _self.resizable == "vertical" || _self.resizable.indexOf('top') > -1 || _self.resizable.indexOf('bottom') > -1) {
            if (y < rszborder + marginBox[0] && _self.resizable.indexOf('bottom') == -1)
                cursor = posAbs ? "n" : "";
            else if (y > this.offsetHeight - (rszcorner || rszborder) && _self.resizable.indexOf('top') == -1) //marginBox[0] - marginBox[2] - 
                cursor = "s";
        }
        
        if (_self.resizable == "true" || _self.resizable == "horizontal" || _self.resizable.indexOf('left') > -1 || _self.resizable.indexOf('right') > -1) {
            if (x < (cursor ? rszcorner : rszborder) + marginBox[0] && _self.resizable.indexOf('right') == -1)
                cursor += tcursor + (posAbs ? "w" : "");
            else if (x > this.offsetWidth - (cursor || tcursor ? rszcorner : rszborder) && _self.resizable.indexOf('left') == -1) //marginBox[1] - marginBox[3] - 
                cursor += tcursor + "e";
        }

        return cursor;
    }
    
    var originalCursor;
    function resizeIndicate(e) {
        if (!e) e = event;
        
        if (!_self.resizable || _self.editable || document.onmousemove)
            return;

        //@todo This is probably not gen purpose
        var pos = apf.getAbsolutePosition(_self.$ext),//, _self.$ext.offsetParent
            sLeft = 0,
            sTop = 0,
            x = e.clientX - pos[0] + sLeft + document.documentElement.scrollLeft,
            y = e.clientY - pos[1] + sTop + document.documentElement.scrollTop;
        
        if (!originalCursor)
            originalCursor = apf.getStyle(this, "cursor");

        var cursor = getResizeType.call(_self.$ext, x, y);
        
        this.style.cursor = cursor 
            ? getCssCursor(cursor) + "-resize" 
            : originalCursor || "default";
    };
    
    function getCssCursor(cursor) {
        var cssCursor = cursor;
        if (apf.isWebkit) {
            if (cursor == "se" || cursor == "nw")
                cssCursor = "nwse";
            else if (cursor == "sw" || cursor == "ne")
                cssCursor = "nesw";
            else if (cursor == "s" || cursor == "n")
                cssCursor = "ns";
            else if (cursor == "e" || cursor == "w")
                cssCursor = "ew";
        }
        
        return cssCursor;
    }

    var oOutline;
    
    
    /*this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        oOutline.refCount--;
        
        if (!oOutline.refCount) {
            //destroy
        }
    });*/
};

apf.GuiElement.propHandlers["resizable"] = function(value) {
    this.implement(apf.Interactive);
    this.$propHandlers["resizable"].apply(this, arguments);
}

apf.GuiElement.propHandlers["draggable"] = function(value) {
    this.implement(apf.Interactive);
    this.$propHandlers["draggable"].apply(this, arguments);
};

apf.Init.run("interactive");





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__MEDIA__ = 1 << 20;






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__MULTICHECK__ = 1 << 22;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} have checkable items.
 *
 * @class apf.MultiCheck
 * @baseclass
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       3.0
 *
 *
 */
// @todo type detection, errors (see functions in multiselect)
apf.MultiCheck = function(){
    this.$regbase = this.$regbase | apf.__MULTICHECK__;

    // *** Properties *** //

    this.multicheck = true;
    this.checklength = 0;
    this.$checkedList = [];

    // *** Public Methods *** //

    /**
     * @event  beforecheck  Fires before a check is made
     * @param {Object} e The standard event object, with the following properties:
     *   - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that will be checked.
     */
    /**
     * @event  aftercheck  Fires after a check is made
     * @param {Object} e The standard event object, with the following properties:
     *   - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that was checked.
     *
     */    
    /**
     * Checks a single, or a set of, elements.
     *
     * The checking can be visually represented in this element.
     * The element can be checked, partialy checked, or unchecked
     *
     * @param {Mixed}   xmlNode      The identifier to determine the selection.
     * @return  {Boolean}  Indicates whether the selection could not be made (`false`)
     */
    this.check = function(xmlNode, userAction) {
        if (userAction && this.disabled 
          || this.$checkedList.indexOf(xmlNode) > -1)
            return;

        if (userAction
          && this.$executeSingleValue("check", "checked", xmlNode, "true") !== false)
            return;
        
        if (this.dispatchEvent("beforecheck", {xmlNode : xmlNode}) === false)
            return false;
        
        if (!this.multicheck && this.$checkedList.length)
            this.clearChecked(true);

        this.$checkedList.push(xmlNode);
        
        

        this.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, this),
            "checked", ["partial"]);
        
//        this.dispatchEvent("aftercheck", {
//            list        : this.$checkedList,
//            xmlNode     : xmlNode
//        });
    };

    /**
     * @event  beforeuncheck  Fires before a uncheck is made
     * @param {Object} e The standard event object, with the following properties:
     *   - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that will be unchecked.
     *
     */
    /**
     * @event  afteruncheck  Fires after a uncheck is made
     * @param {Object} e The standard event object, with the following properties:
     *   - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that was unchecked.
     *
     */     
    /**
     * Unchecks a single, or set of, elements.
     *
     * @param {Mixed}   xmlNode      The identifier to determine the selection.
     * @return  {Boolean}  Indicates if the selection could be made (`false`)
     */
    this.uncheck = function(xmlNode, userAction) {
        if (userAction && this.disabled 
          || this.$checkedList.indexOf(xmlNode) == -1)
            return;
        
        if (userAction
          && this.$executeSingleValue("check", "checked", xmlNode, "false") !== false)
            return;
        
        
        
        if (this.dispatchEvent("beforeuncheck", {
            xmlNode: xmlNode
        }) === false)
            return false;

        this.$checkedList.remove(xmlNode);
        this.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, this), 
            "", ["checked", "partial"]);
        
        this.dispatchEvent("afteruncheck", {
            list: this.$checkedList,
            xmlNode: xmlNode
        });
    };

    /**
     * Toggles between a check and uncheck of a single, or set of, elements.
     *
     * @param {Mixed}   xmlNode    The identifier to determine the selection.
     *
     */
    this.checkToggle = function(xmlNode, userAction) {
        if (userAction && this.disabled)
            return;
        
        if (xmlNode.style) {
            var htmlNode = xmlNode,
                id = htmlNode.getAttribute(apf.xmldb.htmlIdTag);
            while (!id && htmlNode.parentNode)
                id = (htmlNode = htmlNode.parentNode)
                    .getAttribute(apf.xmldb.htmlIdTag);
            xmlNode = apf.xmldb.getNode(htmlNode)
        }

        if (this.$checkedList.indexOf(xmlNode) > -1)
            this.uncheck(xmlNode, userAction);
        else
            this.check(xmlNode, userAction);
    };
    
    /**
     * Checks a set of items.
     *
     * @param {Array} xmlNodeList The {@link term.datanode data nodes} that will be selected.
     * @param {Boolean} uncheck If `true`, checks the items
     * @param {Boolean} noClear If `true`, does not also clears the selection
     * @param {Boolean} noEvent Indicates whether to call any events
     * @event  beforecheck  Fires before a check is made
     * @param {Object} e
     *   
     * @event  aftercheck   Fires after a check is made
     * @param {Object} e
     *   {XMLElement} xmlNode   the {@link term.datanode data node} that is deselected.
     */
    this.checkList = function(xmlNodeList, uncheck, noClear, noEvent, userAction) {
        //if (apf.isIE < 8)
        if (!xmlNodeList.indexOf)
            xmlNodeList = apf.getArrayFromNodelist(xmlNodeList);
            //@todo is this need for ie8 and/or other browsers

        if (userAction) {
            if (this.disabled) 
                return;
            
            var changes = [];
            for (var c, i = 0; i < xmlNodeList.length; i++) {
                c = this.$executeSingleValue("check", "checked", xmlNodeList[i], uncheck ? "false" : "true", true)
                if (c === false) break;
                changes.push(c);
            }
    
            if (changes.length) {
                return this.$executeAction("multicall", changes, "checked", 
                  xmlNodeList[0], null, null, 
                  xmlNodeList.length > 1 ? xmlNodeList : null);
            }
        }
        
        if (userAction && this.disabled) return;
        
        if (!noEvent && this.dispatchEvent("beforecheck", {
            list: xmlNodeList
        }) === false)
            return false;
        
        if (!uncheck && !noClear) 
            this.clearChecked(true);
        
        if (!this.multicheck)
            xmlNodeList = [xmlNodeList[0]];

        var i;
        if (uncheck) {
            for (i = xmlNodeList.length - 1; i >= 0; i--) {
                this.$checkedList.remove(xmlNodeList[i]);
                this.$setStyleClass(
                    apf.xmldb.getHtmlNode(xmlNodeList[i], this), "", ["checked"]);
            }
        }
        else {
            for (i = xmlNodeList.length - 1; i >= 0; i--) {
                this.$checkedList.push(xmlNodeList[i]);
                this.$setStyleClass(
                    apf.xmldb.getHtmlNode(xmlNodeList[i], this), "checked");
            }
        }

        
        
        if (!noEvent)
            this.dispatchEvent("aftercheck", {
                list: xmlNodeList
            });
    };

    /**
     * Removes the selection of one or more checked nodes.
     *
     * @param {Boolean} [noEvent]    Indicates whether to call any events
     */
    this.clearChecked = function(noEvent) {
        if (!noEvent && this.dispatchEvent("beforeuncheck", {
            xmlNode: this.$checkedList
        }) === false)
            return false;
        
        for (var i = this.$checkedList.length - 1; i >= 0; i--) {
            this.$setStyleClass(
                apf.xmldb.getHtmlNode(this.$checkedList[i], this), "", ["checked"]);
        }
        
        this.$checkedList.length = 0;
        
        if (!noEvent) {
            this.dispatchEvent("afteruncheck", {
                list: this.$checkedList
            });
        }
    };
    
    /**
     * Determines whether a node is checked.
     *
     * @param {XMLElement}  xmlNode  The {@link term.datanode data node} to be checked.
     * @return  {Boolean} Whether the element is selected.
     */
    this.isChecked = function(xmlNode) {
        return this.$checkedList.indexOf(xmlNode) > -1;
    };

    /**
     * Retrieves an array or a document fragment containing all the checked
     * {@link term.datanode data nodes} from this element.
     *
     * @param {Boolean} [xmldoc] Specifies whether the method should return a document fragment.
     * @return {Mixed} The selection of this element.
     */
    this.getChecked = function(xmldoc) {
        var i, r;
        if (xmldoc) {
            r = this.xmlRoot
                ? this.xmlRoot.ownerDocument.createDocumentFragment()
                : apf.getXmlDom().createDocumentFragment();
            for (i = 0; i < this.$checkedList.length; i++)
                apf.xmldb.cleanNode(r.appendChild(
                    this.$checkedList[i].cloneNode(true)));
        }
        else {
            for (r = [], i = 0; i < this.$checkedList.length; i++)
                r.push(this.$checkedList[i]);
        }

        return r;
    };
    
    /**
     * Checks all the {@link term.eachnode each nodes} of this element
     *
     */
    this.checkAll = function(userAction) {
        if (!this.multicheck || userAction && this.disabled || !this.xmlRoot)
            return;

        var nodes = this.$isTreeArch
            ? this.xmlRoot.selectNodes(".//" 
              + this.each.split("|").join("|.//"))
            : this.getTraverseNodes();
        
        this.checkList(nodes);
    };
    
    this.addEventListener("beforeload", function(){
        if (!this.$hasBindRule("checked")) //only reset state when check state isnt recorded
            this.clearChecked(true);
    });
    
    this.addEventListener("afterload", function(){
        if (!this.$hasBindRule("checked") && this.$checkedList.length) //only reset state when check state isnt recorded
            this.checkList(this.$checkedList, false, true, false); //@todo could be optimized (no event calling)
    });
    
    this.addEventListener("xmlupdate", function(e) {
        if (e.action == "attribute" || e.action == "text"
          || e.action == "synchronize" || e.action == "update") {
            //@todo list support!
            var c1 = apf.isTrue(this.$applyBindRule("checked", e.xmlNode));
            var c2 = this.isChecked(e.xmlNode);
            if (c1 != c2) {
                if (c1) {
                    this.check(e.xmlNode);
                }
                else {
                    this.uncheck(e.xmlNode);
                }
            }
        }
    });
    
    
    this.addEventListener("aftercheck", function(){
        //@todo inconsistent because setting this is in event callback
        if (this.checklength != this.$checkedList.length)
            this.setProperty("checklength", this.$checkedList.length);
    });
    
    this.addEventListener("afteruncheck", function(){
        //@todo inconsistent because setting this is in event callback
        if (this.checklength != this.$checkedList.length)
            this.setProperty("checklength", this.$checkedList.length);
    });
    
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Element which specifies the ways the application can communicate to remote
 * data sources.
 * 
 *
 * #### Example
 *
 * Here's an example of the RPC module with the CGI protocol.
 * 
 * ```xml
 *  <a:rpc id="comm" protocol="cgi">
 *      <a:method
 *        name = "searchProduct"
 *        url = "http://example.com/search.php"
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method
 *        name = "loadProduct"
 *        url = "http://example.com/show-product.php">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * ```
 * 
 * #### Example
 *
 * Example of the RPC module with the SOAP protocol.
 * 
 * ```xml
 *  <a:rpc id="comm" 
 *    protocol = "soap" 
 *    url = "http://example.com/show-product.php" 
 *    soap-prefix = "m" 
 *    soap-xmlns = "http://example.com">
 *      <a:method 
 *        name = "searchProduct" 
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method 
 *        name = "loadProduct">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * ```
 * 
 * #### Example
 *
 * Writing to a file with a WebDAV connector
 * 
 * ```xml
 *   <a:webdav id="myWebDAV"
 *    url = "http://my-webdav-server.com/dav_files/" />
 *     
 *  <a:script>
 *      // write the text 'bar' to a file on the server called 'foo.txt'
 *      myWebDAV.write('http://my-webdav-server.com/dav_files/foo.txt', 'bar');
 *  </a:script>
 * ```
 * 
 * #### Example
 *
 * XMPP connector with new message notification
 * 
 * ```xml
 *  <a:xmpp id="myXMPP"
 *    url = "http://my-jabber-server.com:5280/http-bind"
 *    model = "mdlRoster"
 *    connection = "bosh"
 *    onreceivechat = "messageReceived(arguments[0].from)" />
 *
 *  <a:script>
 *      // This function is called when a message has arrived
 *      function messageReceived(from) {
 *          alert('Received message from ' + from);
 *      }
 *
 *      // Send a message to John
 *      myXMPP.sendMessage('john@my-jabber-server.com', 'A test message', '',
 *          apf.xmpp.MSG_CHAT);
 *  </a:script>
 * ```
 *
 * @class apf.Teleport
 * @define teleport
 * @inherits apf.AmlElement
 * @allowchild {teleport}
 *
 * @default_private
 */
/**
 * @attribute {String}  url              The location of the server that is
 *                                       recipient of the RPC messages.
 */
/**
 *  @attribute {String}  [route-server]   String specifying the URL to the route script.
 *                                       The route script will receive the route information in three extra headers:
 *                                           - `X-Route-Request`: Containing the destination URL
 *                                           - `X-Proxy-Request`: Containing the destination URL
 *                                           - `X-Compress-Response`: Set to `'gzip'`
 */
/**
 *  @attribute {Boolean} [autoroute]     Indicates whether the call should be routed
 *                                       through a proxy when a permission
 *                                       error occurs due to the same domain policy.
 */
/**
 *  @attribute {Number}  [timeout]       The number of milliseconds after
 *                                       which the call is considered timed out.
 *
 */
apf.Teleport = function(){
    this.$init(true);
};

apf.__TELEPORT__ = 1 << 28;

(function() {
    this.$parsePrio = "002";
    
    this.$regbase = this.$regbase | apf.__TELEPORT__;

    this.$booleanProperties["autoroute"] = true;
    
    this.$supportedProperties.push("url", "timeout", "protocol", "route-server",
        "autoroute");

    this.$propHandlers["url"] = function(value) {
        var url = new apf.url(value);

        // do some extra startup/ syntax error checking
        if (!url.protocol) {
            return apf.console.error(apf.formatErrorString(0, this,
                "Communication (Teleport) initialization error",
                "Invalid server url provided: '" + value + "'"));
        }

        this.$host = url.host;
        this.$rootPath = url.path;
        this.$server = value.replace(new RegExp(this.$rootPath + "$"), "");
    };

    this.$propHandlers["timeout"] = function(value) {
        this.timeout = parseInt(value) || 10000;
    };

    this.$propHandlers["protocol"] = function(value) {
        var proto = value.toLowerCase();
        if (!apf[proto]) {
            throw new Error(apf.formatErrorString(1025, null, "Teleport baseclass",
                "Could not find Ajax.org Teleport RPC Component '" + proto + "'", this));
        }
        this.implement(apf[proto]);
    };
    
    /**
     * Returns a string representation of this object.
     */
    this.toString = function(){
        return "[Ajax.org Teleport Component : " + (this.name || "")
            + " (" + this.type + ")]";
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function() {
        var error;
        if (this.type && this.type == "socket") {
            // Implement Socket Module
            if (!apf.socket)
                error = "Socket";
            else
                this.implement(apf.socket);
        }
        else {
            // Implement HTTP Module
            if (!apf.http)
                error = "HTTP";
            else
                this.implement(apf.http);
        }
        if (error) {
            throw new Error(apf.formatErrorString(1024, null, "Teleport baseclass", 
                    "Could not find Ajax.org Teleport " + error + " Component", this.$aml));
        }

        if (this.id)
            apf.$asyncObjects[this.id] = 1;
    });
}).call(apf.Teleport.prototype = new apf.AmlElement());





apf.Init.run("teleport");




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__TRANSACTION__ = 1 << 3;





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__VIRTUALVIEWPORT__ = 1 << 19;



/**
 * All elements inheriting from this {@link term.baseclass baseclass} can have a virtual viewport.
 *
 * @experimental This code has never been run. 
 * @constructor
 * @baseclass
 * @private
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org) & Mike de Boer
 * @version     %I%, %G%
 * @since       1.0
 */
apf.VirtualViewport = function(){
    this.$init(true);

    this.$regbase = this.$regbase | apf.__VIRTUALVIEWPORT__;

    this.virtualVTimer = null;
    this._xmlUpdate = this.$xmlUpdate;
    
    apf.setStyleClass(this.$ext, "virtual");
    
    this.$deInitNode = function(xmlNode, htmlNode) {
        /*  
            Not the htmlNode is deleted, but the viewport is rerendered from this node on. 
            If viewport is too high either the render starting point is adjusted and
            a complete rerender is requested, or the last empty elements are hidden
        */
        this.$viewport.redraw();//very unoptimized
    };
    
    this.$moveNode = function(xmlNode, htmlNode) {
        /*
            Do a remove when removed from current viewport
            Do an add when moved to current viewport
            Do a redraw from the first of either when both in viewport
        */
        this.$viewport.redraw();//very unoptimized
    };
    
    this.emptyNode = apf.xmldb.getXml("<empty />");
    this.$addEmpty = this.$add;
    this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode) {
        //find new slot
        var htmlNode = this.$pHtmlDoc.getElementById(Lid);
        
        if (!htmlNode)
            return;
        
        //execute update
        this.$updateNode(xmlNode, htmlNode);//, noModifier);
    };

    this.$fill = function(){
        
    };
    
    this.clear = function(nomsg, do_event) {
        if (this.clearSelection)
            this.clearSelection(!do_event);

        //this.documentId = this.xmlRoot = this.cacheId = null;

        if (typeof nomsg == "string") {
            var msgType = nomsg;
            nomsg = false;
            
            //@todo apf3.0 please use attr. inheritance
            if (!this[msgType + "-message"])
                this.$setInheritedAttribute(msgType + "-message");
        }

        if (!nomsg) {
            this.$viewport.offset = 0;
            this.$viewport.length = 0;
            this.$viewport.resize(0);
            if (this.$viewport.sb) 
                this.$viewport.sb.$update();
    
            if (this.$setClearMessage) {
                this.$setClearMessage(msgType 
                  ? this[msgType + "-message"] 
                  : this["empty-message"], msgType || "empty");
            }
        }
        else if (this.$removeClearMessage)
            this.$removeClearMessage();
        
        this.$viewport.cache = null;
    };

    var _self = this;
    this.$viewport = new apf.$viewportVirtual(this);
    
    this.getViewport = function(){
        return this.$viewport;
    }
    
    this.$isInViewport = function(xmlNode, struct) {
        /*var marker = xmlNode.selectSingleNode("preceding-sibling::a_marker");
        var start = marker ? marker.getAttribute("end") : 0;
        
        if (!struct && this.$viewport.offset + this.$viewport.limit < start + 1)
            return false;
        
        var position = start;
        var nodes = (marker || xmlNode).selectNodes("following-sibling::"
              + this.each.split("|").join("following-sibling::"));
        
        for (var i = 0; i < nodes.length; i++) {
            ++position;
            if (nodes[i] == xmlNode)
                break;
        }
        
        if (struct) struct.position = position;
        
        if (this.$viewport.offset > position 
          || this.$viewport.offset + this.$viewport.limit < position)
            return false;
        
        return true;*/
        var nodes = this.getTraverseNodes();
        for (var i = 0, l = nodes.length; i < l; i++){
            if (nodes[i] == xmlNode)
                return true;
        }
        
        return false;
    };
    
    this.scrollTo = function(xmlNode, last) {
        var sPos = {};
        this.$isInViewport(xmlNode, sPos);
        this.$viewport.change(sPos.position + (last ? this.$viewport.limit - 1 : 0));
    };
    
    /**
     * @todo this one should be optimized
     */
    this.getFirstTraverseNode = function(xmlNode) {
        return this.getTraverseNodes(xmlNode)[0];
    };
    
    
    
    this.$xmlUpdate = function(){
        this.$viewport.cache = null;
        this.$viewport.length = this.xmlRoot.selectNodes(this.each).length; //@todo fix this for virtual length
        this.$viewport.sb.$update(this.$container);
        this._xmlUpdate.apply(this, arguments);
    };
    
    this.$load = function(XMLRoot) {
        //Add listener to XMLRoot Node
        apf.xmldb.addNodeListener(XMLRoot, this);

        //Reserve here a set of nodeConnect id's and add them to our initial marker
        //Init virtual dataset here
        
        this.$updateTraverseCache(XMLRoot, true);
        
        if (!this.renderRoot && !this.$cachedTraverseList.length)
            return this.clear();
        
        //this.$removeClearMessage();
        
        
        
        //Prepare viewport
        this.$viewport.cache = null;
        this.$viewport.offset = 0;
        this.$viewport.length = this.$cachedTraverseList.length; //@todo fix this for virtual length
        if (this.$viewport.length < this.$viewport.limit)
            this.$viewport.resize(this.$viewport.length);
        this.$viewport.prepare();
        //this.$viewport.change(0);
        
        //Traverse through XMLTree
        var nodes = this.$addNodes(XMLRoot, null, null, this.renderRoot);
        
        //Build HTML
        //this.$fill(nodes);

        //Select First Child
        if (this.selectable) {
            if (this.autoselect) {
                if (nodes.length)
                    this.$selectDefault(XMLRoot);
                
                else
                    this.setProperty("selected", null); //@todo review this
                
            }
            else {
                this.clearSelection(true);
                var xmlNode = this.getFirstTraverseNode(); //should this be moved to the clearSelection function?
                if (xmlNode)
                    this.setCaret(xmlNode);
                
                this.setProperty("selected", null); //@todo review this
                this.setProperty("chosen", null);
                
            }
        }

        if (this.$focussable)
            apf.window.hasFocus(this) ? this.$focus() : this.$blur();
        
        this.$viewport.setScrollTop(0, true);
    };
    
    this.$loadSubData = function(){}; //We use the same process for subloading, it shouldn't be done twice
    
    
    
    this.$updateTraverseCache = function(xmlNode, force) {
        if (force || !this.$cachedTraverseList || this.$cachedTraverseList.name != this.each) {
            this.$cachedTraverseList = (xmlNode || this.xmlRoot).selectNodes(this.each);
            this.$cachedTraverseList.name = this.each;
        }
    };
    
    /**
     * Retrieves a nodelist containing the {@link term.datanode data nodes} which are rendered by
     * this element.
     *
     * @param {XMLElement} [xmlNode] the parent element on which the each query is applied.
     */
    var getTraverseNodes = this.getTraverseNodes = function(xmlNode) {
        if (!this.xmlRoot)
            return;
        
        if (this.$viewport.cache)
            return this.$viewport.cache;

        //caching statement here
        this.$updateTraverseCache(xmlNode);

        var start = this.$viewport.offset,
            end = Math.min(this.$cachedTraverseList.length, start + this.$viewport.limit);
        
        
            
            //Special case for fully loaded virtual dataset
            var list = [];
            for (var i = start; i < end; i++) {
                list.push(this.$cachedTraverseList[i]);
            }

            
            return this.$sort ? this.$sort.apply(list) : list;
            
        
        
    };
    
    var baseNTS = this.getNextTraverseSelected;
    this.getNextTraverseSelected = function(xmlNode, up, count) {
        if (!xmlNode)
            xmlNode = this.selected;
        if (!count)
            count = 1;

        this.getTraverseNodes = apf.MultiselectBinding.prototype.getTraverseNodes;
        var node = baseNTS.call(this, xmlNode, up, count);
        this.getTraverseNodes = getTraverseNodes;
        
        return node;
        
//        if (node && node != xmlNode)
//            return node;

        //@todo treeArch support
        //this.getTraverseNodes()
//        var nodes = apf.MultiselectBinding.prototype.getTraverseNodes.call(_self), i = 0;
//        while (nodes[i] && nodes[i] != xmlNode)
//            i++;

//        if (up)
//            i = -1 * (nodes.length - i - 1);

//        this.$viewport.change(Math.max(0, this.$viewport.offset + i
//            + (up ? count : -1 * count)), null, true, true);
            
        //nodes = this.getTraverseNodes();
//        return nodes[up ? nodes.length - 1 : 0];
    };
    
    //@todo keyboard handlers for pgup/pgdown should measure items instead of assuming fixed height
    
    //Init
    this.caching = false; //for now, because the implications are unknown
};

apf.$viewportVirtual = function(amlNode) {
    this.amlNode = amlNode;
    
    var _self = this;
    this.amlNode.addEventListener("mousescroll", function(e) {
        if (!_self.scrollbar || _self.scrollbar.horizontal)
            return;
        
        if (e.returnValue === false)
            return;

        var div = _self.getScrollHeight() - _self.getHeight();
        if (div) {
            _self.setScrollTop(_self.getScrollTop() 
                + (-1 * e.delta * Math.min(45, _self.getHeight()/10)), false, true)
            
            e.preventDefault();
        }
    });
    
    apf.addListener(this.$getHtmlHost(), "scroll", function(){
        _self.setScrollTop(_self.getScrollTop());
    });
    
    amlNode.addEventListener("resize", function(){
        _self.$findNewLimit();
    });
    
    amlNode.addEventListener("afterload", function(){
        var itemHeight = _self.$getItemHeight();
        _self.$findNewLimit();
        
        if (itemHeight == 1000)
            _self.$findNewLimit();
    });
    
    if (!amlNode.scrollbar) {
        this.sb = new apf.scrollbar();
        
        this.sb.parentNode = new apf.Class().$init();
        this.sb.parentNode.$container = amlNode.$pHtmlNode;
        this.sb.parentNode.$int = amlNode.$pHtmlNode;
        this.sb.dispatchEvent("DOMNodeInsertedIntoDocument");
        
        this.sb.attach(this);
    }
    
    /* @todo
     * - Fix bug in optimization
     * - Fix flickering with larger viewport
     * - Get templates to work
     * - Firefox has problems with the scrollbar
     * / Fix scrolling of items bigger than viewport (limit is too tight sometimes)
     * - Improve pgup/pgdown
     * - Fix multigrid lists (thumbnail)
     * - Fix FF html conversion (insertHtmlNodes)
     * - Optimize grow function to use fill
     */
    
//    apf.layout.setRules(amlNode.$container, "scrollbar", "\
//        var s = apf.all[" + _self.sb.$uniqueId + "];\
//        s.$update();\
//    ", true);
//    apf.layout.queue(amlNode.$container);
    
};

(function(){
    this.offset = 0;
    this.limit = 2;
    this.length = 0;
    this.host = this;
    this.cache = null;
    this.inited = false;
    this.virtual = true;
    
    this.setScrollbar = function(scrollbar, onscroll) {
       this.scrollbar = scrollbar;
       
       this.amlNode.addEventListener("scroll", onscroll);
    }
    
    this.isVisible = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.offsetHeight || htmlNode.offsetWidth ? true : false;
    }
    
    this.focus = function(){
        if (this.amlNode.focus && this.amlNode.$isWindowContainer !== true)
            this.amlNode.focus();
    }
    
    this.getScrollTop = function(){
        var htmlNode = this.$getHtmlHost();
        return (this.offset * this.$getItemHeight()) + htmlNode.scrollTop;
    }
    
    this.getScrollLeft = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.scrollLeft;
    }
    
    this.getScrollHeight = function(){
        return (this.length) * this.$getItemHeight();
    }
    
    this.getScrollWidth = function(){
        var htmlNode = this.$getHtmlHost();
        return (apf.isIE && htmlNode.lastChild 
            ? htmlNode.lastChild.offsetLeft 
                + htmlNode.lastChild.offsetWidth
                + apf.getBox(apf.getStyle(htmlNode, "padding"))[1]
                + (parseInt(apf.getStyle(htmlNode, "marginRight")) || 0)
            : htmlNode.scrollWidth);
    }
    
    this.getHeight = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.tagName == "HTML" || htmlNode.tagName == "BODY" 
            ? apf.getWindowHeight() 
            : apf.getHtmlInnerHeight(htmlNode);
    }
    
    this.getWidth = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.tagName == "HTML" || htmlNode.tagName == "BODY" 
            ? apf.getWindowHeight() 
            : apf.getHtmlInnerWidth(htmlNode);
    }
    
    this.setScrollTop = function(value, preventEvent, byUser) {
        var htmlNode = this.$getHtmlHost();
        var itemHeight = this.$getItemHeight();

        this.change(Math.floor(value / itemHeight));
        
        htmlNode.scrollTop = value - (this.offset * itemHeight);
        
        if (!preventEvent) {
            this.amlNode.dispatchEvent("scroll", {
                direction: "vertical",
                byUser: byUser,
                viewport: this,
                scrollbar: this.scrollbar
            });
        }
    }
    
    this.setScrollLeft = function(value, preventEvent, byUser) {
        var htmlNode = this.$getHtmlHost();
        
        htmlNode.scrollLeft = value;
        
        if (!preventEvent) {
            this.amlNode.dispatchEvent("scroll", {
                direction: "horizontal",
                byUser: byUser,
                viewport: this,
                scrollbar: this.scrollbar
            });
        }
    }
    
    this.scrollIntoView = function(xmlNode, toBottom) {
        var _self = this.amlNode;
        var htmlNode = apf.xmldb.findHtmlNode(xmlNode, _self);
        if (htmlNode && htmlNode.offsetTop - _self.$container.scrollTop > 0 
          && htmlNode.offsetTop + htmlNode.offsetHeight 
            - _self.$container.scrollTop < _self.$container.offsetHeight)
            return;
        
        if (this.amlNode.$sharedScrollbarMove)
            this.amlNode.$sharedScrollbarMove();
        
        var nr = apf.getChildNumber(xmlNode, 
            apf.MultiselectBinding.prototype.getTraverseNodes.call(_self));
        var itemHeight = this.$getItemHeight();
        
        this.setScrollTop(nr * itemHeight + (toBottom ? itemHeight - this.getHeight() : 0));
    }
    
    // *** Private *** //
    
    //Assume all items have the same height;
    //@todo this can be optimized by caching
    this.$getItemHeight = function(){
        if (this.amlNode.each && this.amlNode.xmlRoot) {
            var nodes = this.amlNode.getTraverseNodes();
            if (nodes.length) {
                var htmlNode = apf.xmldb.findHtmlNode(nodes[0], this.amlNode);
                if (htmlNode)
                    return this.$lastItemHeight = htmlNode.offsetHeight;
            }
        }
        
        return this.$lastItemHeight || 1000;
    }
    
    this.$getHtmlHost = function(){
        var htmlNode = this.amlNode.$int || this.amlNode.$container;
        return (htmlNode.tagName == "BODY" || htmlNode.tagName == "HTML" 
            ? (apf.isSafari || apf.isChrome ? document.body : htmlNode.parentNode) 
            : htmlNode);
    }

    // *** Implementation *** //

    this.draw = function(){
        this.inited = true;
        var limit = this.limit; this.limit = 0;
        this.resize(limit, true);
    }
    
    this.redraw = function(){
        this.change(this.offset, this.limit, true);
    }
    
    // set id's of xml to the viewport
    this.prepare = function(){
        if (!this.inited)
            this.draw();
        
        var _self = this.amlNode;
        var nodes = _self.getTraverseNodes();
        if (!nodes)
            return;
        
        var len = nodes.length;
        var docId = apf.xmldb.getXmlDocId(_self.xmlRoot),
            hNodes = _self.$container.childNodes, hNode;
        for (var j = 0, i = 0, l = hNodes.length; i < l; i++) {
            if (hNodes[i].nodeType != 1) continue;
            
            if (j >= len)
                hNodes[i].style.display = "none"; //Will ruin tables & lists
            else {
                hNode = hNodes[i];
                _self.$deselect(hNode);
                hNode.style.display = "block"; //Will ruin tables & lists
                apf.xmldb.nodeConnect(docId, nodes[j], hNode, _self);
            }
            
            j++;
        }
    };
    
    /**
     * Note: this function only supports single dimension items (also no grid, like thumbnails)
     */
    this.resize = function(limit, updateScrollbar) {
        this.cache = null;
        
        var _self = this.amlNode;
        var i;
        //Viewport shrinks
        if (limit < this.limit) {
            var nodes = _self.$container.childNodes;
            for (i = nodes.length - 1; i >= 0; i--) {
                if (nodes[i].nodeType != 1) continue;
                _self.$container.removeChild(nodes[i]);
                if (--this.limit == limit) break;
            }
        }
        //Viewport grows
        else if (limit > this.limit) {
            for (i = this.limit; i < limit; i++) {
                _self.$addEmpty(_self.emptyNode, "", _self.xmlRoot, _self.$container);
            }
        }
        else
            return;
        
        this.limit = limit;
        
        if (updateScrollbar && this.sb)
            this.sb.$update(this.$container);
    };
    
    this.$findNewLimit = function(){
        if (!this.amlNode.xmlRoot || !this.amlNode.$ext.offsetHeight)
            return;
        
        var limit = Math.ceil(this.getHeight() / this.$getItemHeight() + 2);
        if (this.amlNode.$cachedTraverseList 
          && this.amlNode.$cachedTraverseList.length < limit)
            limit = this.amlNode.$cachedTraverseList.length;
        
        if (limit != this.limit) {
            this.resize(limit);
            this.redraw();
        }
    };
    
    /**
     *  @todo   This method should be optimized by checking if there is
     *          overlap between the new offset and the old one
     */
    this.change = function(offset, limit, force) {
        var offsetN;
        var _self = this.amlNode;
        
        if (offset > this.length - this.limit) 
            offsetN = Math.floor(this.length - this.limit);
        else 
            offsetN = Math.floor(offset);
        
        if (offsetN < 0) 
            offsetN = 0;
        
        if (!limit)
            limit = this.limit;
        
        if (!force)
            force = apf.isGecko;
        
        var scrollTop = this.getScrollTop();
        
        this.cache = null;
        var diff = offsetN - this.offset,
            oldLimit = this.limit;
        if (diff * diff >= this.limit*this.limit) //there is no overlap
            diff = false;
        this.offset = offsetN;
        
        if (diff > 0) { //get last node before resize
            var lastNode = _self.$container.lastChild;
            if (lastNode.nodeType != 1)
                lastNode = lastNode.previousSibling;
        }
        
        //Traverse through XMLTree
        var nodes = _self.getTraverseNodes();
        if (!nodes || !nodes.length)
            return;
            
        if (nodes.length < this.limit) {
            if (offset > 0)
                alert("shouldnt get here");
            else
                this.resize(nodes.length);
        }

        var docId = apf.xmldb.getXmlDocId(_self.xmlRoot),
            hNodes = _self.$container.childNodes,
            xmlNode, htmlNode, xmlPos, sel, len, j, i;

        //remove nodes from the beginning
        if (diff > 0) {
            xmlPos = oldLimit - diff;
            len = hNodes.length,
            sel = _self.$getSelection();
            for (j = 0, i = 0; j < diff && i < len; i++) {
                htmlNode = _self.$container.firstChild;
                if (htmlNode.nodeType == 1) {
                    j++;
                    xmlNode = nodes[xmlPos++];
                    if (xmlNode) {
                        apf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self);
                        _self.$updateNode(xmlNode, htmlNode);//, noModifier);
                        if (sel.indexOf(xmlNode) > -1)
                            _self.$select(htmlNode);
                        else
                            _self.$deselect(htmlNode);
                        htmlNode.style.display = "block";
                    }
                    else {
                        htmlNode.style.display = "none";
                    }
                }
                
                _self.$container.appendChild(htmlNode);
            }
        }
        //remove nodes from the end
        else if (diff < 0) {
            diff = diff * -1;
            xmlPos = 0; //should be adjusted for changing limit
            sel = _self.$getSelection();
            for (j = 0, i = hNodes.length-1; j < diff && i >= 0; i++) {
                htmlNode = _self.$container.lastChild;
                if (htmlNode.nodeType == 1) {
                    j++;
                    xmlNode = nodes[xmlPos++];
                    apf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self);
                    _self.$updateNode(xmlNode, htmlNode);//, noModifier);
                    if (sel.indexOf(xmlNode) > -1)
                        _self.$select(htmlNode);
                    else
                        _self.$deselect(htmlNode);
                    htmlNode.style.display = "block";
                }

                _self.$container.insertBefore(htmlNode, _self.$container.firstChild);
            }
        }
        //Recalc all nodes
        else if (force || diff === false) {
            len = hNodes.length; 
            sel = _self.$getSelection();
            for (j = 0, i = 0; i < len; i++) {
                htmlNode = hNodes[i];
                if (htmlNode.nodeType == 1) {
                    xmlNode = nodes[j++];
                    apf.xmldb.nodeConnect(docId, xmlNode, htmlNode, _self);
                    _self.$updateNode(xmlNode, htmlNode);//, noModifier);
                    
                    if (sel.indexOf(xmlNode) > -1)
                        _self.$select(htmlNode);
                    else
                        _self.$deselect(htmlNode);
                }
            }
        }
        
        if (!_self.$selected && sel && sel.length)
            _self.$selected = apf.xmldb.findHtmlNode(sel[0], _self);

        var htmlNode = this.$getHtmlHost();
        var itemHeight = this.$getItemHeight();
        htmlNode.scrollTop = scrollTop - (this.offset * itemHeight);
    };
}).call(apf.$viewportVirtual.prototype);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

apf.__XFORMS__ = 1 << 17;






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.clipboard = new apf.Class().$init();
apf.clipboard.store = null;
apf.clipboard.empty = true;
apf.clipboard.copied = null;
apf.clipboard.put = function(item) {
    this.store = item;
    this.setProperty("data", item);
    this.setProperty("empty", item ? false : true);
};
apf.clipboard.clear = function(){
    this.setProperty("data", null);
    this.setProperty("empty", true);
}
apf.clipboard.get = function(){
    return this.store;
};


apf.clipboard.$highlightSelection = function(amlNode, nodes, unset) {
    for (var i = 0, l = nodes.length; i < l; i++) {
        apf.setStyleClass(apf.xmldb.getHtmlNode(nodes[i], amlNode), (unset ? '' : 'cut'), ['cut']);
    }
}
apf.clipboard.copySelection = function(amlNode) {
    var nodes = this.get() || [];
    this.$highlightSelection(amlNode, nodes, true);
    this.put(amlNode.getSelection().map(function (node) {
        return apf.xmldb.getCleanCopy(node);
    }));
    this.copied = true;
};
apf.clipboard.cutSelection = function(amlNode) {
    var nodes = this.get() || [];
    this.$highlightSelection(amlNode, nodes, true);
    this.put(nodes = amlNode.getSelection());
    this.$highlightSelection(amlNode, nodes);
    this.copied = false;
};
apf.clipboard.pasteSelection = function(amlNode, selected) {
    var copied, nodes = this.get();
    if (!nodes) return;

    if (!selected)
        selected = amlNode.selected || amlNode.getFirstTraverseNode();

    if (amlNode.hasFeature(apf.__DRAGDROP__)) {
        var candrop = amlNode.isDropAllowed(apf.clipboard.data, selected);
        if (!candrop)
            return false;
        var action = candrop[1] && candrop[1].action 
          || (amlNode.$isTreeArch ? "tree-append" : "list-append");
         
        copied = nodes.slice(0);
        amlNode.$dragDrop(selected, copied, candrop && candrop[1], action, 
            null, null, null, this.copied);
        
        //amlNode.copy(nodes, selected, undefined, !this.copied);
    }
    else {
        if (nodes[0].parentNode) {
            for (var i = 0, l = nodes.length; i < l; i++) {
                apf.xmldb.moveNode(selected, nodes[i]);
            }
        }
        else {
            copied = [];
            for (var i = 0, l = nodes.length; i < l; i++) {
                copied[i] = apf.xmldb.appendChild(selected, nodes[i]);
            }
        }
    }
    
    if (!this.copied) {
        this.$highlightSelection(amlNode, nodes, true);
        apf.clipboard.clear();
    }
    
    // amlNode.selectList(copied || nodes);
};







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Object representing the window of the AML application. The semantic is
 * similar to that of a window in the browser, except that this window is not
 * the same as the JavaScript global object. It handles the focussing within
 * the document and several other events such as exit and the keyboard events.
 *
 * @class apf.window
 * @inherits apf.Class
 * @default_private
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
/**
 * @event blur              Fires when the browser window loses focus.
 */
/**
 * @event focus             Fires when the browser window receives focus.
 *
 *
 */
apf.window = function(){
    this.$uniqueId = apf.all.push(this);
    this.apf = apf;

    /**
     * Returns a string representation of this object.
     */
    this.toString = function(){
        return "[apf.window]";
    };
    
    /**
     * Retrieves the primary {@link apf.actiontracker action tracker} of the application.
     */
    this.getActionTracker = function(){
        return this.$at
    };

    /*
     * @private
     */
    this.loadCodeFile = function(url) {
        //if(apf.isWebkit) return;
        if (self[url])
            apf.importClass(self[url], true, this.win);
        else
            apf.include(url);//, this.document);
    };

    

    /**
     * Show the browser window.
     */
    this.show = function(){
        if (apf.isDeskrun)
            jdwin.Show();
    };

    /**
     * Hide the browser window.
     */
    this.hide = function(){
        if (apf.isDeskrun) {
            jdwin.Hide();
        }
        else {
            this.loaded = false;
            if (this.win)
                this.win.close();
        }
    };

    /**
     * Focus the browser window.
     */
    this.focus = function(){
        if (apf.isDeskrun)
            jdwin.SetFocus();
        else
            window.focus();
    };

    /**
     * Set the icon of the browser window.
     * @param {String} url The location of the _.ico_ file.
     */
    this.setIcon = function(url) {
        if (apf.isDeskrun)
            jdwin.icon = parseInt(url) == url ? parseInt(url) : url;
    };

    /**
     * Set the title of the browser window.
     * @param {String} value The new title of the window.
     */
    this.setTitle = function(value) {
        this.title = value || "";

        if (apf.isDeskrun)
            jdwin.caption = value;
        else
            document.title = (value || "");
    };

    /*
     * @private
     */
    this.loadAml = function(x) {
        if (x[apf.TAGNAME] == "deskrun")
            this.loadDeskRun(x);
        /*else {

        }*/
    };

    

    // *** Focus Internals *** //

    
    this.vManager = new apf.visibilitymanager();
    

    
    this.zManager = new apf.zmanager();
    

    

    this.$tabList = [];

    this.$addFocus = function(amlNode, tabindex, isAdmin) {
        if (!isAdmin) {
            amlNode.addEventListener("DOMNodeInserted", moveFocus);
            amlNode.addEventListener("DOMNodeRemoved", removeFocus);

            if (amlNode.$isWindowContainer > -2) {
                amlNode.addEventListener("focus", trackChildFocus);
                amlNode.addEventListener("blur", trackChildFocus);

                amlNode.$focusParent = amlNode;

                if (amlNode.$isWindowContainer > -1) {
                    if (!amlNode.$tabList)
                        amlNode.$tabList = [amlNode];
                    
                    this.$tabList.push(amlNode);
                    return;
                }
                else {
                    amlNode.$tabList = [amlNode];
                }
            }
        }

        var fParent = findFocusParent(amlNode),
            list = fParent.$tabList;

        

        if (!amlNode.$isWindowContainer)
            amlNode.$focusParent = fParent;
        else
            amlNode.$focusParent2 = fParent;

        if (list[tabindex])
            list.insertIndex(amlNode, tabindex);
        else if (tabindex || parseInt(tabindex) === 0)
            list[tabindex] = amlNode;
        else
            list.push(amlNode);
    };

    this.$removeFocus = function(amlNode) {
        if (!amlNode.$focusParent)
            return;

        amlNode.$focusParent.$tabList.remove(amlNode);

        if (!amlNode.$isWindowContainer) {
            amlNode.removeEventListener("DOMNodeInserted", moveFocus);
            amlNode.removeEventListener("DOMNodeRemoved", removeFocus);
        }

        if (amlNode.$isWindowContainer > -2) {
            amlNode.removeEventListener("focus", trackChildFocus); 
            amlNode.removeEventListener("blur", trackChildFocus);
        }
    };

    var focusLoopDetect;
    this.$focus = function(amlNode, e, force) {
        var aEl = this.activeElement;
        if (aEl == amlNode && !force)
            return; //or maybe when force do $focus

        

        this.$settingFocus = amlNode;

        if (!e)
            e = {};

        e.toElement = amlNode;
        e.fromElement = aEl;

        if (aEl && aEl != amlNode && focusLoopDetect != aEl) {
            focusLoopDetect = aEl;

            aEl.blur(true, e);

            
            
            if (focusLoopDetect != aEl)
                return false;
        }

        if (amlNode.$focussable != apf.MENU || !apf.activeElement) {
            apf.activeElement = 
            this.document.activeElement = 
            this.document.documentElement.$lastFocussed = amlNode;
        }

        (apf.window.activeElement = amlNode).focus(true, e);

        this.$settingFocus = null;

        apf.dispatchEvent("movefocus", {
            toElement: amlNode
        });

        

        

        
    };

    this.$blur = function(amlNode) {
        var aEl = this.activeElement;
        if (aEl != amlNode)
            return false;

        

        aEl.$focusParent.$lastFocussed = null;
        
        if (aEl.$focussable != apf.MENU) {
            apf.activeElement = 
            this.document.activeElement = null;
        }

        apf.window.activeElement = null;
        
        apf.dispatchEvent("movefocus", {
            fromElement: amlNode
        });

        
    };
    
    var lastFocusParent;

    this.$focusDefault = function(amlNode, e) {
        var fParent = findFocusParent(amlNode);
        this.$focusLast(fParent, e);
    };

    this.$focusRoot = function(e) {
        var docEl = apf.document.documentElement;
        if (this.$focusLast(docEl, e) === false) {
            //docEl.$lastFocussed = null;
            //this.moveNext(null, apf.document.documentElement, true, e);
        }
    };

    this.$focusLast = function(amlNode, e, ignoreVisible) {
        var lf = amlNode.$lastFocussed;

        if (lf && lf.parentNode && lf.$focussable === true
          && (ignoreVisible || lf.$ext.offsetHeight)) {
            this.$focus(lf, e, true);
        }
        else { //Let's find the object to focus first
            var next, node = amlNode, skip;
            while (node) {
                if (!skip && node.focussable !== false && node.$focussable === true && !node.$tabList
                  && (ignoreVisible || node.$ext && node.$ext.offsetHeight) && node.disabled < 1) {
                    this.$focus(node, e, true);
                    break;
                }
                
                //Walk sub tree
                if ((next = !skip && node.firstChild || !(skip = false) && node.nextSibling)) {
                    node = next;
                    if (node.$isWindowContainer > 0)
                        skip = true;
                }
                else if (node == amlNode) {
                    if (node.$isWindowContainer)
                        this.$focus(node, e, true);
                    return;
                }
                else {
                    do {
                        node = node.parentNode;
                    } while (node && !node.nextSibling && node != amlNode 
                      && !node.$isWindowContainer)
                    
                    if (node == amlNode) {
                        if (node.$isWindowContainer)
                            this.$focus(node, e, true);
                        return; //do nothing
                    }
                    
                    if (node) {
                        if (node.$isWindowContainer) {
                            this.$focus(node, e, true);
                            break;
                        }
                        
                        node = node.nextSibling;
                    }
                }
            }

            if (!node)
                this.$focus(apf.document.documentElement);//return false;//

            /*@todo get this back from SVN
            var node, list = amlNode.$tabList;
            for (var i = 0; i < list.length; i++) {
                node = list[i];
                if (node.focussable !== false && node.$focussable === true
                  && (ignoreVisible || node.$ext.offsetHeight)) {
                    this.$focus(node, e, true);
                    return;
                }
            }

            this.$focus(apf.document.documentElement);*/
        }
    };

    function trackChildFocus(e) {
        if (e.name == "blur") {
            if (e.srcElement != this && this.$blur)
                this.$blur();
            return;
        }
        
        if (e.srcElement != this && this.$focus && (!e || !e.mouse || this.$focussable == apf.KEYBOARD_MOUSE))
            this.$focus();
        
        if (e.srcElement == this || e.trackedChild) {
            e.trackedChild = true;
            return;
        }

        this.$lastFocussed = e.srcElement;

        if (this.localName && this.localName.indexOf("window") > -1)
            e.trackedChild = true;
    }

    function findFocusParent(amlNode) {
        var node = amlNode;
        do {
            node = node.parentNode;
        } while (node && !node.$isWindowContainer);
        //(!node.$focussable || node.focussable === false)

        return node || apf.document.documentElement;
    }

    //Dom handler
    //@todo make this look at the dom tree insertion point to determine tabindex
    function moveFocus(e) {
        if (e && e.currentTarget != this)
            return;
        
        if (this.$isWindowContainer)
            apf.window.$tabList.pushUnique(this);
        else
            apf.window.$addFocus(this, this.tabindex, true)
    }

    //Dom handler
    function removeFocus(e) {
        if (e && (e.currentTarget != this || e.$doOnlyAdmin))
            return;

        //@todo apf3.0 this should be fixed by adding domremovenode events to all children
        var list = this.$focusParent.$tabList;
        var nodes = this.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++) {
            list.remove(nodes[i]); //@todo assuming no windows here
        }

        if (apf.window.activeElement == this)
            apf.window.moveNext();
        
        if (this.$isWindowContainer) {
            apf.window.$tabList.remove(this); //@todo this can't be right
            return;
        }

        if (!this.$focusParent)
            return;

        list.remove(this);
        //this.$focusParent = null; //@experimental to not execute this
    }

    // *** Focus API *** //

    /**
     * Determines whether a given AML element has the focus.
     * @param {apf.AmlElement} The element to check
     * @returns {Boolean} Indicates whether the element has focus.
     */
    this.hasFocus = function(amlNode) {
        return this.activeElement == amlNode;
    };

    /*
     * @private
     */
    this.moveNext = function(shiftKey, relObject, switchWindows, e) {
        if (switchWindows && apf.window.activeElement) {
            var p = apf.window.activeElement.$focusParent;
            if (p.visible && p.modal)
                return false;
        }

        var dir, start, next,
            amlNode = relObject || apf.window.activeElement,
            fParent = amlNode
                ? (switchWindows && amlNode.$isWindowContainer 
                  && amlNode.$isWindowContainer != -1
                    ? apf.window
                    : e && e.innerList ? amlNode.$focusParent : amlNode.$focusParent2 || amlNode.$focusParent)
                : apf.document.documentElement,
            list = fParent.$tabList;

        if (amlNode && (switchWindows || amlNode != apf.document.documentElement)) {
            start = (list || []).indexOf(amlNode);
            if (start == -1) {
                

                return;
            }
        }
        else {
            start = -1;
        }

        if (this.activeElement == amlNode
          && list.length == 1 || list.length == 0)
            return false;

        dir = (shiftKey ? -1 : 1);
        next = start;
        if (start < 0)
            start = 0;
        do {
            next += dir;

            if (next >= list.length)
                next = 0;
            else if (next < 0)
                next = list.length - 1;

            if (start == next && amlNode) {
                if (list[0].$isWindowContainer)
                    this.$focus(list[0], e);
                
                return false; //No visible enabled element was found
            }

            amlNode = list[next];
        }
        while (amlNode && (
               amlNode.disabled > 0
            || amlNode == apf.window.activeElement
            || (switchWindows ? !amlNode.visible : amlNode.$ext && !amlNode.$ext.offsetHeight)
            || amlNode.focussable === false
            || switchWindows && !amlNode.$tabList.length
        ));
        
        if (!amlNode)
            return;

        if (fParent == apf.window && amlNode.$isWindowContainer != -2) {
            this.$focusLast(amlNode, {mouse:true}, switchWindows);
        }
        else {
            (e || (e = {})).shiftKey = shiftKey;
            this.$focus(amlNode, e);
        }

        
    };

    /*
     * @private
     */
    this.focusDefault = function(){
        

        if (this.moveNext() === false)
            this.moveNext(null, apf.document.documentElement, true)
    };

    

    // *** Set Window Events *** //

    apf.addListener(window, "beforeunload", function(){
        return apf.dispatchEvent("exit");
    });

    //@todo apf3.x why is this loaded twice
    apf.addListener(window, "unload", function(){
        if (!apf)
            return;
        
        apf.window.isExiting = true;
    });

    

    // *** Keyboard and Focus Handling *** //

    apf.addListener(document, "contextmenu", function(e) {
        if (!e)
            e = event;

        
        var pos, ev,
            amlNode = apf.findHost(e.srcElement || e.target)
              || apf.window.activeElement
              || apf.document && apf.document.documentElement;

        if (amlNode && amlNode.localName == "menu") //The menu is already visible
            return false;


        //if (amlNode && amlNode.localName == "menu")
            //amlNode = amlNode.parentNode;

        if (apf.contextMenuKeyboard) {
            if (amlNode) {
                pos = amlNode.selected
                    ? apf.getAbsolutePosition(amlNode.$selected)
                    : apf.getAbsolutePosition(amlNode.$ext || amlNode.$pHtmlNode);
            }
            else {
                pos = [0, 0];
            }

            ev = {
                x: pos[0] + 10 + document.documentElement.scrollLeft,
                y: pos[1] + 10 + document.documentElement.scrollTop,
                amlNode: amlNode,
                htmlEvent: e
            }
        }
        else {
            if (e.htmlEvent) {
                ev = e;
            }
            else {
                ev = { //@todo probably have to deduct the border of the window
                    x: e.clientX + document.documentElement.scrollLeft,
                    y: e.clientY + document.documentElement.scrollTop,
                    htmlEvent: e
                }
            }
        }

        ev.bubbles = true; //@todo discuss this, are we ok with bubbling?

        apf.contextMenuKeyboard = null;

        if ((amlNode || apf).dispatchEvent("contextmenu", ev) === false
          || ev.returnValue === false) {
            if (e.preventDefault)
                e.preventDefault();
            return false;
        }
        

        if (apf.config.disableRightClick) {
            if (e.preventDefault)
                e.preventDefault();
            return false;
        }
    });
    
    apf.addListener(document, "mouseup", function(e) {
        if (!e) e = event;
        
        apf.dispatchEvent("mouseup", {
            htmlEvent: e
        });
    });

    var ta = {"INPUT":1, "TEXTAREA":1, "SELECT":1, "EMBED":1, "OBJECT":1};
    apf.addListener(document, "mousedown", this.$mousedown = function(e) {

        if (!e) e = event;
        var p,
            amlNode = apf.findHost(e.srcElement || e.target);
            /*cEditable = amlNode && amlNode.liveedit
              
            ;*/
        
        apf.popup.$mousedownHandler(amlNode, e);
        
        if (amlNode === false) 
            amlNode = apf.window.activeElement;
        
        var eventTarget = amlNode;
        
        while (amlNode && !amlNode.focussable) 
            amlNode = amlNode.parentNode;
        
        //Make sure the user cannot leave a modal window
        if ((!amlNode || ((!amlNode.$focussable || amlNode.focussable === false)
          && amlNode.canHaveChildren != 2 && !amlNode.$focusParent))
          && apf.config.allowBlur) {
            lastFocusParent = null;
            if (apf.window.activeElement)
                apf.window.activeElement.blur();
        }
        else if (amlNode) { //@todo check this for documentElement apf3.0
            if ((p = apf.window.activeElement
              && apf.window.activeElement.$focusParent || lastFocusParent)
              && p.visible && p.modal && amlNode.$focusParent != p
              && amlNode.$isWindowContainer != -1) {
                apf.window.$focusLast(p, {mouse: true, ctrlKey: e.ctrlKey});
            }
            else if (!amlNode && apf.window.activeElement) {
                apf.window.$focusRoot();
            }
            else if (amlNode.$isWindowContainer == -1) {
                if (amlNode.$tabList.length)
                    apf.window.moveNext(null, amlNode.$tabList[0], null, {mouse: true, innerList: true});
                else
                    apf.window.$focus(amlNode);
            }
            else if ((amlNode.disabled == undefined || amlNode.disabled < 1) 
              && amlNode.focussable !== false) {
                if (amlNode.$focussable) { // === apf.KEYBOARD_MOUSE
                    apf.window.$focus(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
                }
                else if (amlNode.canHaveChildren == 2) {
                    if (!apf.config.allowBlur || !apf.window.activeElement 
                      || apf.window.activeElement.$focusParent != amlNode)
                        apf.window.$focusLast(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
                }
//                else {
//                    if (!apf.config.allowBlur || amlNode != apf.document.documentElement)
//                        apf.window.$focusDefault(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
//                }
            }
            else {
                // Disabled this to prevent menus from becoming unclickable
                // apf.window.$focusDefault(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
            }
    
            
        }
        
        amlNode = eventTarget;
        
        
        apf.dispatchEvent("mousedown", {
            htmlEvent: e,
            amlNode: amlNode || apf.document.documentElement
        });

        //Non IE/ iPhone selection handling
        if (apf.isIE || apf.isIphone)
            return;

        var canSelect = !((!apf.document
          && (!apf.isParsingPartial || amlNode)
          || apf.dragMode) && !ta[e.target && e.target.tagName]);

        if (canSelect && amlNode) {
            if (!e.target && e.srcElement)
                e.target = {};
            var isTextInput = (ta[e.target.tagName]
                || e.target.contentEditable == "true") && !e.target.disabled  //@todo apf3.0 need to loop here?
                || amlNode.$isTextInput
                && amlNode.$isTextInput(e) && amlNode.disabled < 1;

            //(!amlNode.canHaveChildren || !apf.isChildOf(amlNode.$int, e.srcElement))
            if (!apf.config.allowSelect && !isTextInput
              && amlNode.nodeType != amlNode.NODE_PROCESSING_INSTRUCTION 
              && !amlNode.textselect) //&& (!amlNode.$int || amlNode.$focussable) //getElementsByTagNameNS(apf.ns.xhtml, "*").length
                canSelect = false;
        }
        
        if (!canSelect && e.button != 2) { // && !cEditable
            if (e.preventDefault)
                e.preventDefault();
           
            try{  
                if (document.activeElement && document.activeElement.contentEditable == "true") //@todo apf3.0 need to loop here?
                    document.activeElement.blur();
            }catch(e) {}
        }
    });

    //IE selection handling
    apf.addListener(document, "selectstart", function(e) {
        if (!apf.isIE)
            return;
        
        if (!e) e = event;

        var amlNode = apf.findHost(e.srcElement);
        var canSelect = !(!apf.document
          && (!apf.isParsingPartial || amlNode)
          || apf.dragMode);
        
        if (canSelect) {
            //(!amlNode.canHaveChildren || !apf.isChildOf(amlNode.$int, e.srcElement))
            if (!apf.config.allowSelect 
              && (amlNode && amlNode.nodeType != amlNode.NODE_PROCESSING_INSTRUCTION 
              && !amlNode.textselect)) //&& !amlNode.$int // getElementsByTagNameNS(apf.ns.xhtml, "*").length
                canSelect = false;
        }

        if (!canSelect) {
            e.returnValue = false;
            return false;
        }
    });

    // Keyboard forwarding to focussed object
    apf.addListener(document, "keyup", this.$keyup = function(e) {
        if (!e) e = event;

        
        var ev = {
            keyCode: e.keyCode,
            ctrlKey: e.ctrlKey,
            shiftKey: e.shiftKey,
            metaKey: e.metaKey,
            altKey: e.altkey,
            htmlEvent: e,
            bubbles: true //@todo is this much slower?
        };
        
        var aEl = apf.document && apf.window.activeElement;
        if ((aEl && !aEl.disableKeyboard
          ? aEl.dispatchEvent("keyup", ev)
          : apf.dispatchEvent("keyup", ev)) === false) {
            apf.preventDefault(e);
            return false;
        }
        
    });

    
    var wheel = this.$mousewheel = function wheel(e) {
        if (!e)
            e = event;

        var delta = null;
        if (e.wheelDelta) {
            delta = e.wheelDelta / 120;
            if (apf.isOpera)
                delta *= -1;
        }
        else if (e.detail) {
            delta = -e.detail / 3;
        }

        if (delta !== null) {
            //Fix for scrolling too much
            if (apf.isIE) {
                var el = e.srcElement || e.target;
                while (el && el.scrollHeight <= el.offsetHeight)
                    el = el.parentNode || el.$parentNode;
                
                if (el && el.nodeType == 9)
                    el = el.documentElement;
                
                if (!el || el.nodeType != 1) return;

                if (el && el.tagName == "BODY" && "auto|scroll".indexOf(apf.getStyle(el, "overflowY")) == -1)
                    el = document.documentElement;

                if (el && "auto|scroll".indexOf(apf.getStyle(el, "overflowY")) > -1) {
                    var max, dist = 0.35 * el.offsetHeight * delta;
                    if (delta < 0) {
                        if (el && el.scrollTop >= (max = el.scrollHeight - el.offsetHeight + apf.getVerBorders(el)) + dist) {
                            el.scrollTop = max;
                            e.returnValue = false;
                        }
                    }
                    else {
                        if (el && el.scrollTop <= dist) {
                            el.scrollTop = 0;
                            e.returnValue = false;
                        }
                    }
                }
            }
            
            var ev = {
                delta: delta, 
                target: e.target || e.srcElement, 
                button: e.button, 
                ctrlKey: e.ctrlKey, 
                shiftKey: e.shiftKey, 
                metaKey: e.metaKey,
                altKey: e.altKey,
                bubbles: true,
                htmlEvent: e
            };
            
            var amlNode = apf.findHost(e.srcElement || e.target);
            var res = (amlNode || apf).dispatchEvent("mousescroll", ev);
            if (res === false || ev.returnValue === false) {
                if (e.preventDefault)
                    e.preventDefault();

                e.returnValue = false;
            }
        }
    }

    if (document.addEventListener)
        document.addEventListener('DOMMouseScroll', wheel, false);

    window.onmousewheel = 
    document.onmousewheel = wheel; //@todo 2 keer events??
    

    //var browserNavKeys = {32:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1}
    
    apf.addListener(document, "keyup", function(e) {
        e = e || event;

        if (e.ctrlKey && e.keyCode == 9 && apf.window.activeElement) {
            var w = apf.window.activeElement.$focusParent;
            if (w.modal) {
                if (e.preventDefault)
                    e.preventDefault();
                return false;
            }

            apf.window.moveNext(e.shiftKey,
                apf.window.activeElement.$focusParent, true);

            w = apf.window.activeElement.$focusParent;
            if (w && w.bringToFront)
                w.bringToFront();
            
            if (e.preventDefault)
                e.preventDefault();
            return false;    
        }
    });
    
    //@todo optimize this function
    apf.addListener(document, "keydown", this.$keydown = function(e) {
        e = e || event;

        

        
        if (e.keyCode == 93)
            apf.contextMenuKeyboard = true;
        

        var amlNode = apf.window.activeElement, //apf.findHost(e.srcElement || e.target),
            htmlNode = (e.explicitOriginalTarget || e.srcElement || e.target),
            isTextInput = (ta[htmlNode.tagName]
              || htmlNode.contentEditable || htmlNode.contentEditable == "true")  //@todo apf3.0 need to loop here?
              && !htmlNode.disabled
              || amlNode && amlNode.$isTextInput
              && amlNode.$isTextInput(e) && amlNode.disabled < 1;

        

        var eInfo = {
            ctrlKey: e.ctrlKey,
            metaKey: e.metaKey,
            shiftKey: e.shiftKey,
            altKey: e.altKey,
            keyCode: e.keyCode,
            htmlEvent: e,
            isTextInput: isTextInput,
            bubbles: true
        };
        
        delete eInfo.currentTarget;
        
        //Keyboard forwarding to focussed object
        var aEl = amlNode; //isTextInput ? amlNode :
        if ((aEl && !aEl.disableKeyboard && !aEl.editable
          ? aEl.dispatchEvent("keydown", eInfo) 
          : apf.dispatchEvent("keydown", eInfo)) === false) {
            apf.stopEvent(e);
            if (apf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch (e) {}
            }
            return false;
        }
        
        //Focus handling
        else if ((!apf.config.disableTabbing || apf.window.activeElement) && e.keyCode == 9) {
            //Window focus handling
            if (e.ctrlKey && apf.window.activeElement) {
                var w = apf.window.activeElement.$focusParent;
                if (w.modal) {
                    if (e.preventDefault)
                        e.preventDefault();
                    return false;
                }

                apf.window.moveNext(e.shiftKey,
                    apf.window.activeElement.$focusParent, true);

                w = apf.window.activeElement.$focusParent;
                if (w && w.bringToFront)
                    w.bringToFront();
            }
            //Element focus handling
            else if (!apf.window.activeElement || apf.window.activeElement.tagName != "menu") {
                apf.window.moveNext(e.shiftKey);
            }

            if (e.preventDefault)
                e.preventDefault();
            return false;
        }
        

        //Disable backspace behaviour triggering the backbutton behaviour
        var altKey = apf.isMac ? e.metaKey : e.altKey;
        if (apf.config.disableBackspace
          && e.keyCode == 8// || (altKey && (e.keyCode == 37 || e.keyCode == 39)))
          && !isTextInput) {
            if (apf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch (e) {}
            }
            e.returnValue = false;
        }

        //Disable space behaviour of scrolling down the tab
        /*if(Application.disableSpace && e.keyCode == 32 && e.srcElement.tagName.toLowerCase() != "input"){
            e.keyCode = 0;
            e.returnValue = false;
        }*/

        //Disable F5 refresh behaviour
        if (apf.config.disableF5 && (e.keyCode == 116 || e.keyCode == 117)) {
            if (apf.canDisableKeyCodes) {
                try {
                    e.keyCode = 0;
                }
                catch (e) {}
            }
            else {
                e.preventDefault();
                e.stopPropagation();
            }
            //return false;
        }
        
        
        /*if (browserNavKeys[e.keyCode] && apf.window.activeElement 
          && apf.config.autoDisableNavKeys)
            e.returnValue = false;*/

        if (e.keyCode == 27)
            e.returnValue = false;

        if (!apf.config.allowSelect
          && e.shiftKey && (e.keyCode > 32 && e.keyCode < 41)
          && !isTextInput) {
            e.returnValue = false;
        }

        //apf.dispatchEvent("keydown", null, eInfo);

        if (e.returnValue === false && e.preventDefault)
            e.preventDefault();

        return e.returnValue;
        
    });
    
    apf.document = {};
    this.init = function(strAml) {
        
        if (apf.actiontracker) {
            this.$at = new apf.actiontracker();
            this.$at.name = "default";
            
            apf.nameserver.register("actiontracker", "default", this.$at);
            
        }
        
        
        

         
        
        
        //Put this in callback in between the two phases
        

        this.$domParser = new apf.DOMParser();
        this.document = apf.document = this.$domParser.parseFromString(strAml, 
          "text/xml", {
            
            timeout: apf.config.initdelay,
            
            callback: function(doc) {
                //@todo apf3.0

                //Call the onload event (prevent recursion)
                if (apf.parsed != 2) {
                    //@todo apf3.0 onload is being called too often
                    var inital = apf.parsed;
                    apf.parsed = 2;
                    apf.dispatchEvent("parse", { //@todo apf3.0 document
                        initial: inital
                    });
                    apf.parsed = true;
                }
        
                if (!apf.loaded) {
                    
        
                    
                    //Set the default selected element
                    if (!apf.window.activeElement && (!apf.config.allowBlur 
                      || apf.document.documentElement 
                      && apf.document.documentElement.editable))
                        apf.window.focusDefault();
                    

                    apf.loaded = true;
                    $setTimeout(function() {
                        apf.dispatchEvent("load");
                        apf.addEventListener("$event.load", function(cb) {
                            cb();
                        });
                    });
                }
        
                //END OF ENTIRE APPLICATION STARTUP
        
                
                
                
          }
        }); //async
    };
    
    

    /*
     * @private
     */
    this.destroy = function(){
        
    };
};
apf.window.prototype = new apf.Class().$init();
apf.window = new apf.window();











/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Compatibility layer for Gecko based browsers.
 * @private
 */
apf.runGecko = function(){
    if (apf.runNonIe)
        apf.runNonIe();

    /* ***************************************************************************
     XSLT
     ****************************************************************************/
    
    
    //XMLDocument.selectNodes
    HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) {
        try {
            var oResult = this.evaluate(sExpr, (contextNode || this),
                this.createNSResolver(this.documentElement),
                7, null); //XpathResult.ORDERED_NODE_ITERATOR_TYPE
        }
        catch (ex) {
            var msg = ex.message;
            if (ex.code == ex.INVALID_EXPRESSION_ERR)
                msg = msg.replace(/the expression/i, "'" + sExpr + "'");
            throw new Error(ex.lineNumber, "XPath error: " + msg);
        }

        var nodeList = new Array(oResult.snapshotLength);
        nodeList.expr = sExpr;
        for (var i = nodeList.length - 1; i >= 0; i--) 
            nodeList[i] = oResult.snapshotItem(i);
        return nodeList;
    };
    
    //Element.selectNodes
    Text.prototype.selectNodes =
    Attr.prototype.selectNodes =
    Element.prototype.selectNodes = function(sExpr) {
       return this.ownerDocument.selectNodes(sExpr, this);
    };
    
    //XMLDocument.selectSingleNode
    HTMLDocument.prototype.selectSingleNode = 
    XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) {
        try {
            var oResult = this.evaluate(sExpr, (contextNode || this),
                this.createNSResolver(this.documentElement),
                9, null); //XpathResult.FIRST_ORDERED_NODE_TYPE
        }
        catch (ex) {
            var msg = ex.message;
            if (ex.code == ex.INVALID_EXPRESSION_ERR)
                msg = msg.replace(/the expression/i, "'" + sExpr + "'");
            throw new Error(ex.lineNumber, "XPath error: " + msg);
        }
        
        return oResult.singleNodeValue;
    };
    
    //Element.selectSingleNode
    Text.prototype.selectSingleNode =
    Attr.prototype.selectSingleNode =
    Element.prototype.selectSingleNode = function(sExpr) {
        return this.ownerDocument.selectSingleNode(sExpr, this);
    };
    
    
    
    var serializer = new XMLSerializer();
    var o = document.createElement("div");
    apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
        var frag, l, node, i;
        if (nodeList) {
            frag = document.createDocumentFragment();
            for (i = nodeList.length - 1; i >= 0; i--) {
                node = nodeList[i];
                frag.insertBefore(node, frag.firstChild);
            }
        }
        
        o.innerHTML = typeof s == "string" ? s : apf.html_entity_decode(serializer.serializeToString(frag))
            .replace(/<([^>]+)\/>/g, "<$1></$1>");

        frag = document.createDocumentFragment();
        for (i = 0, l = o.childNodes.length; i < l; i++) {
            node = o.childNodes[0];
            frag.appendChild(node);
        }

        if (beforeNode)
            htmlNode.insertBefore(frag, beforeNode);
        htmlNode.appendChild(frag);
    };

    apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) {
        if (htmlNode.nodeType != 11 && !htmlNode.style)
            return htmlNode.appendChild(xmlNode);
        
        if (!s) {
            s = apf.html_entity_decode(xmlNode.serialize
                ? xmlNode.serialize(true)
                : ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2)
                    ? xmlNode.nodeValue
                    : serializer.serializeToString(xmlNode)));
        }

        o.innerHTML = s.replace(/<([^>]+)\/>/g, "<$1></$1>");

        if (beforeNode)
            htmlNode.insertBefore(o.firstChild, beforeNode);
        else
            htmlNode.appendChild(o.firstChild);

        return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild;
    };
    
    /* ******** Error Compatibility **********************************************
     Error Object like IE
     ****************************************************************************/
    function Error(nr, msg) {
        
        
        this.message = msg;
        this.nr = nr;
    }
    
    apf.getHtmlLeft = function(oHtml) {
        return (oHtml.offsetLeft
            + (parseInt(apf.getStyle(oHtml.parentNode, "borderLeftWidth")) || 0));
    };

    apf.getHtmlRight = function(oHtml) {
        var p;
        return (((p = oHtml.offsetParent).tagName == "BODY" 
          ? apf.getWindowWidth()
          : p.offsetWidth)
            - oHtml.offsetLeft - oHtml.offsetWidth
            - (2 * (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0))
            - (parseInt(apf.getStyle(p, "borderRightWidth")) || 0));
    };

    apf.getHtmlTop = function(oHtml) {
        return (oHtml.offsetTop
            + (parseInt(apf.getStyle(oHtml.parentNode, "borderTopWidth")) || 0));
    };
    
    apf.getHtmlBottom = function(oHtml) {
        var p;
        return (((p = oHtml.offsetParent).tagName == "BODY" 
          ? apf.getWindowHeight()
          : p.offsetHeight)
            - oHtml.offsetTop - oHtml.offsetHeight
            - (2 * (parseInt(apf.getStyle(p, "borderTopWidth")) || 0))
            - (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0));
    };

    apf.getBorderOffset = function(oHtml) {
        return [-1 * (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0),
            -1 * (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)];
    };
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Compatibility layer for Internet Explorer browsers.
 * @private
 */
apf.runIE = function(){
    /* ******** XML Compatibility ************************************************
     Extensions to the xmldb
     ****************************************************************************/
    var hasIE7Security = false,
        hasIESecurity = false;
    

    
    

    apf.getHttpReq = hasIESecurity
        ? function(){
            if (apf.availHTTP.length)
                return apf.availHTTP.pop();

            

            return new XMLHttpRequest();
        }
        : function(){
            if (apf.availHTTP.length)
                return apf.availHTTP.pop();

            

            return new ActiveXObject("microsoft.XMLHTTP");
        };

    apf.getXmlDom = hasIESecurity
        ? function(message, noError) {
            var xmlParser = getDOMParser(message, noError);
            return xmlParser;
        }
        : function(message, noError, preserveWhiteSpaces) {
            var xmlParser = new ActiveXObject("microsoft.XMLDOM");
            xmlParser.setProperty("SelectionLanguage", "XPath");
            if (preserveWhiteSpaces)
                xmlParser.preserveWhiteSpace = true;

            if (message) {
                if (apf.cantParseXmlDefinition)
                    message = message.replace(/\] \]/g, "] ]")
                                     .replace(/^<\?[^>]*\?>/, "");//replace xml definition <?xml .* ?> for IE5.0

                xmlParser.loadXML(message);

                
                if (!noError)
                    this.xmlParseError(xmlParser);
            }

            return xmlParser;
        };

    apf.xmlParseError = function(xml) {
        var xmlParseError = xml.parseError;
        if (xmlParseError != 0) {
            /*
             http: //msdn.microsoft.com/library/en-us/xmlsdk30/htm/xmobjpmexmldomparseerror.asp?frame=true

             errorCode     Contains the error code of the last parse error. Read-only.
             filepos         Contains the absolute file position where the error occurred. Read-only.
             line             Specifies the line number that contains the error. Read-only.
             linepos         Contains the character position within the line where the error occurred. Read-only.
             reason         Explains the reason for the error. Read-only.
             srcText         Returns the full text of the line containing the error. Read-only.
             url             Contains the URL of the XML document containing the last error. Read-only.
             */
            throw new Error(apf.formatErrorString(1050, null,
                "XML Parse error on line " + xmlParseError.line,
                xmlParseError.reason + "Source Text:\n"
                    + xmlParseError.srcText.replace(/\t/gi, " ")
            ));
        }

        return xml;
    };
    
    /**
     * This method retrieves the current value of a property on a HTML element
     * @param {HTMLElement} el    the element to read the property from
     * @param {String}      prop  the property to read
     * @returns {String}
     */
    apf.getStyle = function(el, prop) {
        return el.currentStyle[prop];
    };
  
    apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
        var str;
        if (nodeList) {
            for (str = [], i = 0, l = nodeList.length; i < l; i++)
                str[i] = nodeList[i].xml;
        }
        str = s || apf.html_entity_decode(str.join(""));
        
        if (apf.isIE < 7)
            str = str.replace(/style="background-image:([^"]*)"/g, 
              "find='$1' style='background-image:$1'");

        try {
            (beforeNode || htmlNode).insertAdjacentHTML(beforeNode
                ? "beforebegin"
                : "beforeend", str);
        }
        catch (e) {
            //IE table hack
            document.body.insertAdjacentHTML("beforeend", "<table><tr>"
                + str + "</tr></table>");

            var x = document.body.lastChild.firstChild.firstChild;
            for (i = x.childNodes.length - 1; i >= 0; i--)
                htmlNode.appendChild(x.childNodes[apf.hasDynamicItemList ? 0 : i]);
        }

        //Fix IE image loading bug
        if (apf.isIE < 7) {
            $setTimeout(function(){
                var nodes = htmlNode.getElementsByTagName("*");
                for (var s, i = 0, l = nodes.length; i < l; i++) {
                    if (s = nodes[i].getAttribute("find"))
                        nodes[i].style.backgroundImage = s.trim(); //@todo apf3.0 why is this needed?
                }
            });
        }
    };
    
    /* I have no idea what below code should do
    
    if (pNode.nodeType == 11) {
        id = xmlNode.getAttribute("id");
        if (!id)
            throw new Error(apf.formatErrorString(1049, null, "xmldb", "Inserting Cache Item in Document Fragment without an ID"));

        document.body.insertAdjacentHTML(beforeNode ? "beforebegin" : "beforeend", strHTML);
        pNode.appendChild(document.getElementById(id));
    }*/
    apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, str) {
        if (htmlNode.nodeType != 11 && !htmlNode.style)
            return htmlNode.appendChild(xmlNode);
        
        var pNode = beforeNode || htmlNode;
        
        if (!str)
            str = apf.html_entity_decode(xmlNode.serialize
                ? xmlNode.serialize(true)
                : xmlNode.xml || xmlNode.outerHTML || xmlNode.nodeValue);
        try {
            pNode.insertAdjacentHTML(beforeNode 
                ? "beforeBegin" 
                : "beforeEnd", str);
        }
        catch (e) {
            
            
            pNode.insertAdjacentHTML("afterEnd", str);
            return pNode.nextSibling;
        }

        if (beforeNode)
            return beforeNode.previousSibling;
        else 
            return htmlNode.lastChild.nodeType == 1 
                ? htmlNode.lastChild 
                : htmlNode.lastChild.previousSibling;
            
    };

    apf.getHtmlLeft = function(oHtml) {
        return (oHtml.offsetLeft
            - (apf.isIE > 7 && parseInt(oHtml.parentNode.currentStyle["borderLeftWidth"]) || 0));
    };

    apf.getHtmlRight = function(oHtml) {
        var p;
        return (((p = oHtml.offsetParent).tagName == "BODY"
          ? apf.getWindowWidth()
          : p.offsetWidth)
            - oHtml.offsetLeft - oHtml.offsetWidth
            - (apf.isIE < 8 && parseInt(p.currentStyle["borderLeftWidth"]) || 0)
            - (parseInt(p.currentStyle["borderRightWidth"]) || 0));
    };

    apf.getHtmlTop = function(oHtml) {
        return (oHtml.offsetTop
            - (apf.isIE > 7 && parseInt(oHtml.offsetParent.currentStyle["borderTopWidth"]) || 0));
    };

    apf.getHtmlBottom = function(oHtml) {
        var p;
        return (((p = oHtml.offsetParent).tagName == "BODY"
          ? apf.getWindowHeight()
          : p.offsetHeight)
            - oHtml.offsetTop - oHtml.offsetHeight
            - (apf.isIE < 8 && parseInt(p.currentStyle["borderTopWidth"]) || 0)
            - (parseInt(p.currentStyle["borderBottomidth"]) || 0));
    };

    apf.getBorderOffset = function(oHtml) {
        return apf.isIE < 8 && [0,0] || [parseInt(oHtml.currentStyle["borderLeftWidth"]) || 0,
                parseInt(oHtml.currentStyle["borderTopWidth"]) || 0]
    };
    
    apf.getOpacity = function(oHtml) {
        return parseInt(((oHtml.currentStyle["filter"] || "").match(/alpha\(opacity=(\d*)\)/) || [0,0])[1]) / 100;
    };
    
    apf.setOpacity = function(oHtml, value) {
        oHtml.style.filter = value == 1
            ? ""
            : "alpha(opacity=" + Math.round(value * 100) + ")";
    };
    
    

    
    apf.importClass(apf.runXpath, true, self);
    
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * @private
 */
apf.runNonIe = function (){
    

    DocumentFragment.prototype.getElementById = function(id) {
        return this.childNodes.length ? this.childNodes[0].ownerDocument.getElementById(id) : null;
    };

    
    
    // *** XML Serialization *** //
    if (XMLDocument.prototype.__defineGetter__) {
        //XMLDocument.xml
        XMLDocument.prototype.__defineGetter__("xml", function(){
            return (new XMLSerializer()).serializeToString(this);
        });
        XMLDocument.prototype.__defineSetter__("xml", function(){
            throw new Error(apf.formatErrorString(1042, null, "XML serializer", "Invalid assignment on read-only property 'xml'."));
        });
        
        //Node.xml
        Node.prototype.__defineGetter__("xml", function(){
            if (this.nodeType == 3 || this.nodeType == 4 || this.nodeType == 2) 
                return this.nodeValue;
            return (new XMLSerializer()).serializeToString(this);
        });
        
        //Node.xml
        Element.prototype.__defineGetter__("xml", function(){
            return (new XMLSerializer()).serializeToString(this);
        });
    }
    
    /* ******** HTML Interfaces **************************************************
        insertAdjacentHTML(), insertAdjacentText() and insertAdjacentElement()
    ****************************************************************************/
    if (typeof HTMLElement!="undefined") {
        if (!HTMLElement.prototype.insertAdjacentElement) {
            Text.prototype.insertAdjacentElement =
            HTMLElement.prototype.insertAdjacentElement = function(where,parsedNode) {
                switch (where.toLowerCase()) {
                    case "beforebegin":
                        this.parentNode.insertBefore(parsedNode,this);
                        break;
                    case "afterbegin":
                        this.insertBefore(parsedNode,this.firstChild);
                        break;
                    case "beforeend":
                        this.appendChild(parsedNode);
                        break;
                    case "afterend":
                        if (this.nextSibling)
                            this.parentNode.insertBefore(parsedNode,this.nextSibling);
                        else
                            this.parentNode.appendChild(parsedNode);
                        break;
                }
            };
        }

        if (!HTMLElement.prototype.insertAdjacentHTML) {
            Text.prototype.insertAdjacentHTML =
            HTMLElement.prototype.insertAdjacentHTML = function(where,htmlStr) {
                var r = this.ownerDocument.createRange();
                r.setStartBefore(apf.isWebkit
                    ? document.body
                    : (self.document ? document.body : this));
                var parsedHTML = r.createContextualFragment(htmlStr);
                this.insertAdjacentElement(where, parsedHTML);
            };
        }

        if (!HTMLBodyElement.prototype.insertAdjacentHTML) //apf.isWebkit)
            HTMLBodyElement.prototype.insertAdjacentHTML = HTMLElement.prototype.insertAdjacentHTML;
    
        if (!HTMLElement.prototype.insertAdjacentText) {
            Text.prototype.insertAdjacentText =
            HTMLElement.prototype.insertAdjacentText = function(where,txtStr) {
                var parsedText = document.createTextNode(txtStr);
                this.insertAdjacentElement(where,parsedText);
            };
        }
        
        //HTMLElement.removeNode
        HTMLElement.prototype.removeNode = function(){
            if (!this.parentNode) return;

            this.parentNode.removeChild(this);
        };
        
        //Currently only supported by Gecko
        if (HTMLElement.prototype.__defineSetter__) {
            //HTMLElement.innerText
            HTMLElement.prototype.__defineSetter__("innerText", function(sText) {
                var s = "" + sText;
                this.innerHTML = s.replace(/\&/g, "&amp;")
                    .replace(/</g, "&lt;").replace(/>/g, "&gt;");
            });
        
            HTMLElement.prototype.__defineGetter__("innerText", function(){
                return this.innerHTML.replace(/<[^>]+>/g,"")
                    .replace(/\s\s+/g, " ").replace(/^\s+|\s+$/g, " ");
            });
            
            HTMLElement.prototype.__defineGetter__("outerHTML", function(){
                return (new XMLSerializer()).serializeToString(this);
            });
        }
    }
    
    /* ******** XML Compatibility ************************************************
        Giving the Mozilla XML Parser the same interface as IE's Parser
    ****************************************************************************/
    var ASYNCNOTSUPPORTED = false;
    
    //Test if Async is supported
    try {
        XMLDocument.prototype.async = true;
        ASYNCNOTSUPPORTED = true;
    } catch (e) {/*trap*/} 
    
    Document.prototype.onreadystatechange = null;
    Document.prototype.parseError = 0;
    
    Array.prototype.item = function(i){return this[i];};
    Array.prototype.expr = "";
    
    /*try{
        XMLDocument.prototype.readyState = 0;
    }catch(e){}*/
    
    XMLDocument.prototype.$clearDOM = function(){
        while (this.hasChildNodes())
            this.removeChild(this.firstChild);
    };
    
    XMLDocument.prototype.$copyDOM = function(oDoc) {
        this.$clearDOM();
        
        if (oDoc.nodeType == 9 || oDoc.nodeType == 11) {
           var oNodes = oDoc.childNodes;
    
           for (var i = 0; i < oNodes.length; i++)
                this.appendChild(this.importNode(oNodes[i], true));
        }
        else if (oDoc.nodeType == 1)
            this.appendChild(this.importNode(oDoc, true));
    };
    
    //XMLDocument.loadXML();
    XMLDocument.prototype.loadXML = function(strXML) {
        apf.xmldb.setReadyState(this, 1);
        var sOldXML = this.xml || this.serialize();
        var oDoc = (new DOMParser()).parseFromString(strXML, "text/xml");
        apf.xmldb.setReadyState(this, 2);
        this.$copyDOM(oDoc);
        apf.xmldb.setReadyState(this, 3);
        apf.xmldb.loadHandler(this);
        return sOldXML;
    };
    
    Node.prototype.getElementById = function(id) {};
    
    HTMLElement.prototype.replaceNode = 
    Element.prototype.replaceNode = function(xmlNode) {
        if (!this.parentNode) return;

        this.parentNode.insertBefore(xmlNode, this);
        this.parentNode.removeChild(this);
    };
    
    //XMLDocument.load
    XMLDocument.prototype.$load = XMLDocument.prototype.load;
    XMLDocument.prototype.load = function(sURI) {
        var oDoc = document.implementation.createDocument("", "", null);
        oDoc.$copyDOM(this);
        this.parseError = 0;
        apf.xmldb.setReadyState(this, 1);
    
        try {
            if (this.async == false && ASYNCNOTSUPPORTED) {
                var tmp = new XMLHttpRequest();
                tmp.open("GET", sURI, false);
                tmp.overrideMimeType("text/xml");
                tmp.send(null);
                apf.xmldb.setReadyState(this, 2);
                this.$copyDOM(tmp.responseXML);
                apf.xmldb.setReadyState(this, 3);
            } else
                this.$load(sURI);
        }
        catch (objException) {
            this.parseError = -1;
        }
        finally {
            apf.xmldb.loadHandler(this);
        }
    
        return oDoc;
    };
    
    
    
    
    
    /**
     * This method retrieves the current value of a property on a HTML element
     * @param {HTMLElement} el    the element to read the property from
     * @param {String}      prop  the property to read
     * @returns {String}
     */
    var getStyle = apf.getStyle = function(el, prop) {
        try{
            return (window.getComputedStyle(el, "") || {})[prop] || "";
        }catch(e) {}
    };
    
    //XMLDocument.setProperty
    HTMLDocument.prototype.setProperty = 
    XMLDocument.prototype.setProperty = function(x,y) {};
    
    /* ******** XML Compatibility ************************************************
        Extensions to the xmldb
    ****************************************************************************/
    apf.getHttpReq = function(){
        if (apf.availHTTP.length)
            return apf.availHTTP.pop();
        return new XMLHttpRequest();
    };

    apf.getXmlDom = function(message, noError, preserveWhiteSpaces) {
        var xmlParser;
        if (message) {
            if (preserveWhiteSpaces === false)
                message = message.replace(/>[\s\n\r]*</g, "><");
            
            xmlParser = new DOMParser();
            xmlParser = xmlParser.parseFromString(message, "text/xml");

            
            if (!noError)
                this.xmlParseError(xmlParser);
        }
        else {
            xmlParser = document.implementation.createDocument("", "", null);
        }
        
        return xmlParser;
    };
    
    apf.xmlParseError = function(xml) {
        //if (xml.documentElement.tagName == "parsererror") {
        if (xml.getElementsByTagName("parsererror").length) { 
            var nodeValue = xml.documentElement.firstChild.nodeValue;

            if (nodeValue != null) {
                var str = nodeValue.split("\n"),
                    linenr = str[2].match(/\w+ (\d+)/)[1],
                    message = str[0].replace(/\w+ \w+ \w+: (.*)/, "$1");
            } else {
                if (nodeValue = xml.documentElement.firstChild.getElementsByTagName('div')[0].firstChild.nodeValue) {
                    var linenr = nodeValue.match(/line\s(\d*)/)[1] || "N/A",
                        message = nodeValue.match(/column\s\d*:(.*)/)[1] || "N/A";
                }
                else {
                    var linenr = "N/A",
                        message = "N/A";
                }
            }

            var srcText = xml.documentElement.lastChild.firstChild,//.split("\n")[0];
                srcMsg = "";
            if (srcText && srcText.nodeValue) {
                srcMsg = "\nSource Text : " + srcText.nodeValue.replace(/\t/gi, " ")
            }
            throw new Error(apf.formatErrorString(1050, null, 
                "XML Parse Error on line " +  linenr, message + srcMsg));
        }
        
        return xml;
    };

    
    apf.xmldb.setReadyState = function(oDoc, iReadyState) {
        oDoc.readyState = iReadyState;
        if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function")
            oDoc.onreadystatechange();
    };
    
    apf.xmldb.loadHandler = function(oDoc) {
        if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror")
            oDoc.parseError = -1;
        
        apf.xmldb.setReadyState(oDoc, 4);
    };
    
    //
    //Fix XML Data-Island Support Problem with Form Tag
    apf.Init.add(function(){
        var i, nodes = document.getElementsByTagName("form");
        for (i = 0; i < nodes.length; i++)
            nodes[i].removeNode();
        nodes = document.getElementsByTagName("xml");
        for (i = 0; i < nodes.length; i++)
            nodes[i].removeNode();
        nodes = null;
    });
    
    /*window.onerror = function(message, filename, linenr) {
        if (++ERROR_COUNT > MAXMSG) return;
        filename = filename ? filename.match(/\/([^\/]*)$/)[1] : "[Mozilla Library]";
        new Error("---- APF Error ----\nProcess : Javascript code in '" + filename +  "'\nLine : " + linenr + "\nMessage : " + message);
        return false;
    }*/
    
    if (document.body)
        document.body.focus = function(){};
    
    

    apf.getOpacity = function(oHtml) {
        return apf.getStyle(oHtml, "opacity");
    };
    
    apf.setOpacity = function(oHtml, value) {
        oHtml.style.opacity = value;
    };
};










/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Compatibility layer for Webkit based browsers.
 * @private
 */
apf.runWebkit = function(){
    
    
    
    if (XMLHttpRequest.prototype.sendAsBinary === undefined) {
        if (window.ArrayBuffer) {
            /**
             * Binary support for Chrome 7+ which implements [ECMA-262] typed arrays
             * 
             * For more information, see <http://www.khronos.org/registry/typedarray/specs/latest/>.
             */
            XMLHttpRequest.prototype.sendAsBinary = function(string) {
                var bytes = Array.prototype.map.call(string, function(c) {
                    return c.charCodeAt(0) & 0xff;
                });
                this.send(new Uint8Array(bytes).buffer);
            };
        }
    }
    
    
    
    
    
    
    HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) {
        if (sExpr.substr(0,2) == "//")
            sExpr = "." + sExpr;
        
        try {
            var oResult = this.evaluate(sExpr, (contextNode || this),
                this.createNSResolver(this.documentElement),
                7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
        }
        catch (ex) {
            try {
                var oResult = this.evaluate("child::" + sExpr, (contextNode || this),
                    this.createNSResolver(this.documentElement),
                    7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
            }
            catch (ex) {
                throw new Error("XPath error: " + ex.message + "\nLine: " + ex.lineNumber  + "\nExpression: '" + sExpr + "'");
            }
        }
        
        var nodeList = new Array(oResult.snapshotLength);
        nodeList.expr = sExpr;
        for (var i = nodeList.length - 1; i >= 0; i--) 
            nodeList[i] = oResult.snapshotItem(i);
        return nodeList;
    };
    
    //Element.selectNodes
    Text.prototype.selectNodes =
    Attr.prototype.selectNodes =
    Element.prototype.selectNodes = function(sExpr) {
       return this.ownerDocument.selectNodes(sExpr, this);
    };
    
    //XMLDocument.selectSingleNode
    HTMLDocument.prototype.selectSingleNode = XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) {
        var nodeList = this.selectNodes("(" + sExpr + ")[1]", contextNode ? contextNode : null);
        return nodeList.length > 0 ? nodeList[0] : null;
    };
    
    //Element.selectSingleNode
    Text.prototype.selectSingleNode =
    Attr.prototype.selectSingleNode =
    Element.prototype.selectSingleNode = function(sExpr) {
        return this.ownerDocument.selectSingleNode(sExpr, this);
    };
    
    
    
    var serializer = new XMLSerializer();
    apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
        var node, frag, a, i, l;
        if (nodeList) {
            frag = document.createDocumentFragment();
            a = [], i = 0, l = nodeList.length;
            for (; i < l; i++) {
                if (!(node = nodeList[i])) continue;
                frag.appendChild(node);
            }
        }
        
        (beforeNode || htmlNode).insertAdjacentHTML(beforeNode
            ? "beforebegin"
            : "beforeend", s || apf.html_entity_decode(serializer.serializeToString(frag))
                .replace(/<([^>]+)\/>/g, "<$1></$1>"));
    };

    apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) {
        if (htmlNode.nodeType != 11 && !htmlNode.style)
            return htmlNode.appendChild(xmlNode);
        
        if (!s) {
            s = apf.html_entity_decode(xmlNode.serialize 
                ? xmlNode.serialize(true)
                : ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2)
                    ? xmlNode.nodeValue
                    : serializer.serializeToString(xmlNode)));
        }
        
        (beforeNode || htmlNode).insertAdjacentHTML(beforeNode
            ? "beforebegin"
            : "beforeend", s.match(/<(IMG|LINK|META|BR|HR|BASEFONT)[^\/>]*/i) ? s.replace(/<([^>]+)\/>/g, "<$1 />") : s.replace(/<([^>]+)\/>/g, "<$1></$1>"));

        return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild;
    };

    apf.getHtmlLeft = function(oHtml) {
        return oHtml.offsetLeft;
    };

    apf.getHtmlRight = function(oHtml) {
        var p;
        return (((p = oHtml.offsetParent).tagName == "BODY" 
          ? apf.getWindowWidth()
          : p.offsetWidth)
            - oHtml.offsetLeft - oHtml.offsetWidth
            - (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0)
            - (parseInt(apf.getStyle(p, "borderRightWidth")) || 0));
    };

    apf.getHtmlTop = function(oHtml) {
        return oHtml.offsetTop
    };

    apf.getHtmlBottom = function(oHtml) {
        var p;
        return (((p = oHtml.offsetParent).tagName == "BODY" 
          ? apf.getWindowHeight()
          : p.offsetHeight)
            - oHtml.offsetTop - oHtml.offsetHeight
            - (parseInt(apf.getStyle(p, "borderTopWidth")) || 0)
            - (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0));
    };

    apf.getBorderOffset = function(oHtml) {
        return [0,0];
    };
    
    if (apf.runNonIe)
        apf.runNonIe();
};










/*
 * Crypt.Barrett, a class for performing Barrett modular reduction computations in
 * JavaScript.
 *
 * Requires BigInt.js.
 *
 * Copyright 2004-2005 David Shapiro.
 *
 * You may use, re-use, abuse, copy, and modify this code to your liking, but
 * please keep this header.
 *
 * Thanks!
 * 
 * @author Dave Shapiro <dave AT ohdave DOT com>
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.crypto.Base64 = (function() {
    
    var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    
    // public method for encoding
    function encode(data) {
        var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "",
            tmp_arr = [];

        if (!data)
            return data;

        data = apf.crypto.UTF8.encode(data + "");

        do { // pack three octets into four hexets
            o1 = data.charCodeAt(i++);
            o2 = data.charCodeAt(i++);
            o3 = data.charCodeAt(i++);

            bits = o1 << 16 | o2 << 8 | o3;

            h1 = bits >> 18 & 0x3f;
            h2 = bits >> 12 & 0x3f;
            h3 = bits >> 6  & 0x3f;
            h4 = bits & 0x3f;

            // use hexets to index into b64, and append result to encoded string
            tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3)
                + b64.charAt(h4);
        }
        while (i < data.length);

        enc = tmp_arr.join("");

        switch (data.length % 3) {
            case 1:
                enc = enc.slice(0, -2) + '==';
                break;
            case 2:
                enc = enc.slice(0, -1) + '=';
                break;
        }

        return enc;
    }
    
    // public method for decoding
    function decode(data) {
        var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = [];

        if (!data) {
            return data;
        }

        data += "";

        do {  // unpack four hexets into three octets using index points in b64
            h1 = b64.indexOf(data.charAt(i++));
            h2 = b64.indexOf(data.charAt(i++));
            h3 = b64.indexOf(data.charAt(i++));
            h4 = b64.indexOf(data.charAt(i++));

            bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;

            o1 = bits>>16 & 0xff;
            o2 = bits>>8 & 0xff;
            o3 = bits & 0xff;

            if (h3 == 64)
                tmp_arr[ac++] = String.fromCharCode(o1);
            else if (h4 == 64)
                tmp_arr[ac++] = String.fromCharCode(o1, o2);
            else
                tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
        }
        while (i < data.length);

        return apf.crypto.UTF8.decode(tmp_arr.join(""));
    }
    
    return {
        decode: decode,
        encode: encode
    };
    
})();

apf.crypto.UTF8 = {
    // private method for UTF-8 encoding
    encode: function (string) {
        // Encodes an ISO-8859-1 string to UTF-8
        //
        // version: 905.1217
        // discuss at: http://phpjs.org/functions/utf8_encode
        // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: sowberry
        // +    tweaked by: Jack
        // +   bugfixed by: Onno Marsman
        // +   improved by: Yves Sucaet
        // +   bugfixed by: Onno Marsman
        // *     example 1: utf8_encode('Kevin van Zonneveld');
        // *     returns 1: 'Kevin van Zonneveld'
        string = (string + "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");

        var tmp_arr = [],
            start = 0,
            end = 0,
            c1, enc;

        for (var n = 0, l = string.length; n < l; n++) {
            c1 = string.charCodeAt(n);
            enc = null;

            if (c1 < 128) {
                end++;
            }
            else if ((c1 > 127) && (c1 < 2048)) {
                enc = String.fromCharCode((c1 >> 6) | 192)
                    + String.fromCharCode((c1 & 63) | 128);
            }
            else {
                enc = String.fromCharCode((c1 >> 12) | 224) 
                    + String.fromCharCode(((c1 >> 6) & 63) | 128)
                    + String.fromCharCode((c1 & 63) | 128);
            }
            if (enc !== null) {
                if (end > start)
                    tmp_arr.push(string.substring(start, end));
                tmp_arr.push(enc);
                start = end = n + 1;
            }
        }

        if (end > start)
            tmp_arr.push(string.substring(start, string.length));

        return tmp_arr.join("");
    },

    // private method for UTF-8 decoding
    decode: function (str_data) {
        // Converts a UTF-8 encoded string to ISO-8859-1
        //
        // version: 905.3122
        // discuss at: http://phpjs.org/functions/utf8_decode
        // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
        // +      input by: Aman Gupta
        // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // +   improved by: Norman "zEh" Fuchs
        // +   bugfixed by: hitwork
        // +   bugfixed by: Onno Marsman
        // +      input by: Brett Zamir (http://brett-zamir.me)
        // +   bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
        // *     example 1: utf8_decode('Kevin van Zonneveld');
        // *     returns 1: 'Kevin van Zonneveld'
        var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0;

        str_data += "";

        while (i < str_data.length) {
            c1 = str_data.charCodeAt(i);
            if (c1 < 128) {
                tmp_arr[ac++] = String.fromCharCode(c1);
                i++;
            }
            else if ((c1 > 191) && (c1 < 224)) {
                c2 = str_data.charCodeAt(i+1);
                tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = str_data.charCodeAt(i+1);
                c3 = str_data.charCodeAt(i+2);
                tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12)
                    | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }

        return tmp_arr.join('');
    }

};






/*
 * BigInt, a suite of routines for performing multiple-precision arithmetic in
 * JavaScript.
 *
 * Copyright 1998-2005 David Shapiro.
 *
 * You may use, re-use, abuse,
 * copy, and modify this code to your liking, but please keep this header.
 * Thanks!
 *
 * @author Dave Shapiro <dave AT ohdave DOT com>
 * @author Ian Bunning
 *
 * IMPORTANT THING: Be sure to set maxDigits according to your precision
 * needs. Use the setMaxDigits() function to do this. See comments below.
 *
 * Tweaked by Ian Bunning
 * Alterations:
 * Fix bug in function biFromHex(s) to allow
 * parsing of strings of length != 0 (mod 4)
 *
 * Changes made by Dave Shapiro as of 12/30/2004:
 *
 * The BigInt() constructor doesn't take a string anymore. If you want to
 * create a BigInt from a string, use biFromDecimal() for base-10
 * representations, biFromHex() for base-16 representations, or
 * biFromString() for base-2-to-36 representations.
 *
 * biFromArray() has been removed. Use biCopy() instead, passing a BigInt
 * instead of an array.
 *
 * The BigInt() constructor now only constructs a zeroed-out array.
 * Alternatively, if you pass <true>, it won't construct any array. See the
 * biCopy() method for an example of this.
 *
 * Be sure to set maxDigits depending on your precision needs. The default
 * zeroed-out array ZERO_ARRAY is constructed inside the setMaxDigits()
 * function. So use this function to set the variable. DON'T JUST SET THE
 * VALUE. USE THE FUNCTION.
 *
 * ZERO_ARRAY exists to hopefully speed up construction of BigInts(). By
 * precalculating the zero array, we can just use slice(0) to make copies of
 * it. Presumably this calls faster native code, as opposed to setting the
 * elements one at a time. I have not done any timing tests to verify this
 * claim.
 * Max number = 10^16 - 2 = 9999999999999998;
 *               2^53 = 9007199254740992;
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.crypto.MD5 = {
    /*
     * Configurable variables. You may need to tweak these to be compatible with
     * the server-side, but the defaults work in most cases.
     */
    hexcase: 0,  /* hex output format. 0 - lowercase; 1 - uppercase        */
    b64pad : "", /* base-64 pad character. "=" for strict RFC compliance   */
    chrsz: 8,  /* bits per input character. 8 - ASCII; 16 - Unicode      */

    /**
     * These are the functions you'll usually want to call
     * They take string arguments and return either hex or base-64 encoded strings
     * 
     * Example:
     * var hash = apf.crypto.MD5.hex_md5("uzza"); //fddb7463a72e6b000abf631f558cf034
     */
    
    hex_md5: function(s) {
        return this.binl2hex(this.core_md5(this.str2binl(s), s.length * this.chrsz));
    },
    b64_md5: function(s) {
        return this.binl2b64(this.core_md5(this.str2binl(s), s.length * this.chrsz));
    },
    str_md5: function(s) {
        return this.binl2str(this.core_md5(this.str2binl(s), s.length * this.chrsz));
    },
    hex_hmac_md5: function(key, data) {
        return this.binl2hex(this.core_hmac_md5(key, data));
    },
    b64_hmac_md5: function(key, data) {
        return this.binl2b64(this.core_hmac_md5(key, data));
    },
    str_hmac_md5: function(key, data) {
        return this.binl2str(this.core_hmac_md5(key, data));
    },
    /**
     * Calculate the MD5 of an array of little-endian words, and a bit length
     */
    core_md5: function(x, len) {
      /* append padding */
      x[len >> 5] |= 0x80 << ((len) % 32);
      x[(((len + 64) >>> 9) << 4) + 14] = len;

      var a =  1732584193, b = -271733879, c = -1732584194, d =  271733878;

      for (var i = 0; i < x.length; i += 16) {
        var olda = a, oldb = b, oldc = c, oldd = d;

        a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
        d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
        c = this.md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
        b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
        a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
        d = this.md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
        c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
        b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
        a = this.md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
        d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
        c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063);
        b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
        a = this.md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
        d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101);
        c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
        b = this.md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

        a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
        d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
        c = this.md5_gg(c, d, a, b, x[i+11], 14,  643717713);
        b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
        a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
        d = this.md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
        c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335);
        b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
        a = this.md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
        d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
        c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
        b = this.md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
        a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
        d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
        c = this.md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
        b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

        a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
        d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
        c = this.md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
        b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556);
        a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
        d = this.md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
        c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
        b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
        a = this.md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
        d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
        c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
        b = this.md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
        a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
        d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835);
        c = this.md5_hh(c, d, a, b, x[i+15], 16,  530742520);
        b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

        a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
        d = this.md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
        c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
        b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
        a = this.md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
        d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
        c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523);
        b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
        a = this.md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
        d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744);
        c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
        b = this.md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
        a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
        d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
        c = this.md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
        b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

        a = this.safe_add(a, olda);
        b = this.safe_add(b, oldb);
        c = this.safe_add(c, oldc);
        d = this.safe_add(d, oldd);
      }
      return [a, b, c, d];
    },
    /*
     * These functions implement the four basic operations the algorithm uses.
     */
    md5_cmn: function(q, a, b, x, s, t) {
        return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q),
            this.safe_add(x, t)), s),b);
    },
    md5_ff: function(a, b, c, d, x, s, t) {
        return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
    },
    md5_gg: function(a, b, c, d, x, s, t) {
        return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
    },
    md5_hh: function(a, b, c, d, x, s, t) {
        return this.md5_cmn(b ^ c ^ d, a, b, x, s, t);
    },
    md5_ii: function(a, b, c, d, x, s, t) {
        return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
    },
    /**
     * Calculate the HMAC-MD5, of a key and some data
     */
    core_hmac_md5: function(key, data) {
        var bkey = this.str2binl(key),
            ipad = Array(16),
            opad = Array(16);
        if (bkey.length > 16)
            bkey = this.core_md5(bkey, key.length * this.chrsz);

        for (var i = 0; i < 16; i++) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }

        return this.core_md5(opad.concat(
            this.core_md5(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz)
        ), 512 + 128);
    },
    /**
     * Add integers, wrapping at 2^32. This uses 16-bit operations internally
     * to work around bugs in some JS interpreters.
     */
    safe_add: function(x, y) {
        var lsw = (x & 0xFFFF) + (y & 0xFFFF),
            msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    },
    /**
     * Bitwise rotate a 32-bit number to the left.
     */
    bit_rol: function(num, cnt) {
        return (num << cnt) | (num >>> (32 - cnt));
    },
    /**
     * Convert a string to an array of little-endian words
     * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
     */
    str2binl: function(str) {
        var bin = [], i,
            mask = (1 << this.chrsz) - 1;
        for (i = 0; i < str.length * this.chrsz; i += this.chrsz)
            bin[i >> 5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i%32);
        return bin;
    },
    /**
     * Convert an array of little-endian words to a string
     */
    binl2str: function(bin) {
        var str = [], i,
            mask = (1 << this.chrsz) - 1;
        for (i = 0; i < bin.length * 32; i += this.chrsz)
            str.push(String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask));
        return str.join("");
    },
    /**
     * Convert an array of little-endian words to a hex string.
     */
    binl2hex: function(binarray) {
        var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef",
            str = [], i;
        for (i = 0; i < binarray.length * 4; i++) {
            str.push(hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
                     hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF));
        }
        return str.join("");
    },
    /**
     * Convert an array of little-endian words to a base-64 string
     */
    binl2b64: function(binarray) {
        var pane = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
            str = [], i;
        for (i = 0; i < binarray.length * 4; i += 3) {
            var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                    | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                    |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
            for (var j = 0; j < 4; j++) {
                if (i * 8 + j * 6 > binarray.length * 32)
                    str.push(this.b64pad);
                else
                    str.push(pane.charAt((triplet >> 6*(3-j)) & 0x3F));
            }
        }
        return str.join("");
    }
};






/*
 * RSA, a suite of routines for performing RSA public-key computations in
 * JavaScript.
 *
 * Requires BigInt.js and Barrett.js.
 *
 * Copyright 1998-2005 David Shapiro.
 *
 * You may use, re-use, abuse, copy, and modify this code to your liking, but
 * please keep this header.
 *
 * Thanks!
 * 
 * @author Dave Shapiro <dave AT ohdave DOT com>
 */







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



(function(global) {

function rotate_left(n,s) {
    var t4 = ( n<<s ) | (n>>>(32-s));
    return t4;
};

/*
function lsb_hex(val) { // Not in use; needed?
    var str="";
    var i;
    var vh;
    var vl;

    for ( i=0; i<=6; i+=2 ) {
        vh = (val>>>(i*4+4))&0x0f;
        vl = (val>>>(i*4))&0x0f;
        str += vh.toString(16) + vl.toString(16);
    }
    return str;
};
*/

function cvt_hex(val) {
    var str="";
    var i;
    var v;

    for ( i=7; i>=0; i-- ) {
        v = (val>>>(i*4))&0x0f;
        str += v.toString(16);
    }
    return str;
};

global.SHA1 = function(str) {
    // Calculate the sha1 hash of a string
    //
    // version: 905.3122
    // discuss at: http://phpjs.org/functions/sha1
    // +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
    // + namespaced by: Michael White (http://getsprink.com)
    // +      input by: Brett Zamir (http://brett-zamir.me)
    // +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
    // -    depends on: utf8_encode
    // *     example 1: sha1('Kevin van Zonneveld');
    // *     returns 1: '54916d2e62f65b3afa6e192e6a601cdbe5cb5897'
    var blockstart, i, j, W = new Array(80),
        H0 = 0x67452301,
        H1 = 0xEFCDAB89,
        H2 = 0x98BADCFE,
        H3 = 0x10325476,
        H4 = 0xC3D2E1F0,
        A, B, C, D, E, temp;

    str = apf.crypto.UTF8.encode(str);
    var str_len = str.length,
        word_array = [];

    for (i = 0; i < str_len - 3; i += 4) {
        j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 |
            str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3);
        word_array.push(j);
    }

    switch (str_len % 4) {
        case 0:
            i = 0x080000000;
            break;
        case 1:
            i = str.charCodeAt(str_len - 1) << 24 | 0x0800000;
            break;
        case 2:
            i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1)
                << 16 | 0x08000;
            break;
        case 3:
            i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2)
                << 16 | str.charCodeAt(str_len - 1) << 8 | 0x80;
            break;
    }

    word_array.push( i );

    while ((word_array.length % 16) != 14)
        word_array.push( 0 );

    word_array.push(str_len >>> 29);
    word_array.push((str_len << 3) & 0x0ffffffff);

    for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
        for (i = 0; i < 16; i++)
            W[i] = word_array[blockstart + i];
        for (i = 16; i <= 79; i++)
            W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);

        A = H0;
        B = H1;
        C = H2;
        D = H3;
        E = H4;

        for (i = 0; i <= 19; i++) {
            temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i]
                + 0x5A827999) & 0x0ffffffff;
            E = D;
            D = C;
            C = rotate_left(B, 30);
            B = A;
            A = temp;
        }

        for (i = 20; i <= 39; i++) {
            temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1)
                & 0x0ffffffff;
            E = D;
            D = C;
            C = rotate_left(B, 30);
            B = A;
            A = temp;
        }

        for (i = 40; i <= 59; i++) {
            temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i]
                + 0x8F1BBCDC) & 0x0ffffffff;
            E = D;
            D = C;
            C = rotate_left(B, 30);
            B = A;
            A = temp;
        }

        for (i = 60; i <= 79; i++) {
            temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6)
                & 0x0ffffffff;
            E = D;
            D = C;
            C = rotate_left(B, 30);
            B = A;
            A = temp;
        }

        H0 = (H0 + A) & 0x0ffffffff;
        H1 = (H1 + B) & 0x0ffffffff;
        H2 = (H2 + C) & 0x0ffffffff;
        H3 = (H3 + D) & 0x0ffffffff;
        H4 = (H4 + E) & 0x0ffffffff;
    }

    temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
    return temp.toLowerCase();
};

})(apf.crypto);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */











/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/**
 * @constructor
 * @parser
 *
 * @author      Rik Arends
 * @version     %I%, %G%
 * @since       3.0
 */
apf.lm = new (function(){

    var statement_lut = { // all js statements to see its NOT an expression
            "var": 1, "for": 1, "while": 1, "do": 1, "if": 1, "else": 1,
            "switch": 1, "case": 1, "break": 1, "continue": 1, "default": 1,
            "function":2, "return": 1, "try": 1, "catch": 1, "throw":1,
            "debugger": 1, "alert": 1, "confirm": 1,"setTimeout": 1,"setInterval": 1,"delete": 1, "export": 1, "import": 1,
            "label": 1, "foreach":1, "each": 1, "eachrev":1, "foreachrev":1, "node": 1, "local": 1, "yield": 1,
            "let":1, "finally":1, "delete":1
        },
        type_lut = { // used for optimizing the parse regexp
            "\n": 1, "\r\n": 1, "==":2, "++":2, "--":2, '"': 5, "'": 5,
            "<!--": 6, "-->": 6, "/*": 6, "//": 6, "*/": 6, "{": 7, "}": 8,
            "[": 9, "]": 10, "(": 11, ")": 12, "<": 13, ">": 14, "+=":2,
            "-=":2, "/=":2, "*=":2, "!=":2
        },
        type_close = { // handy
            "{": "}", "[": "]", "(": ")", "{{":"}"
        },
        xpath_axes = { // used to detect xpath axes or model
            "ancestor": 1, "ancestor-or-self": 1, "attribute": 1, "child": 1,
            "descendant": 1, "descendant-or-self": 1, "following": 1,
            "following-sibling": 1, "namespace": 1, "parent": 1, "preceding": 1,
            "self": 1
        },
        misc_tok = { // misc token lookup
            ";":1, ",":2, "^":3, "=":4, "+=":4, "-=":4, "/=":4, "*=":4, "/":5, ":":6
        },
        xpath_lut_code = { // which autoxpath to use when doing macro({xpath})
            "~": "_val(_n,", "%": "_nod(_n,", "*": "_nods(_n,", "#": "_cnt(_n,", "$": "_lng("
        },
        xpath_lut_text = { // which autoxpath to use when doing xpath macros in textmode
            "~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lng("
        },
        xpath_lut_attr = { // xpath lut for node attributes
            "~": "_val(_n,", "%": "_val(_n,", "*": "_val(_n,", "#": "_cnt(_n,", "$": "_lng("
        },
        xpath_lut_node,
        xpath_lut_node_normal = { // normal xpath lookup
            "~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lng("
        },
        xpath_lut_node_langedit = { // language edit xpath lookup
            "~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lnged("
        },
        pre_regexp = {
            "[":1, "(":1, ",":1, "=":1, "return":1, "throw":1
        },
        pre_xpath = {
            "else":1, "return":1, "delete":1
        },
        pre_plain = {
            "do":1, "else":1, "try":1
        },
        op_lut = { // obj.prop += operator lut
            "=" : "_asn(", "+=": "_add(", "-=": "_sub(", "/=": "_div(", "*=": "_mul("
        },
        new_block = {
            "+":1, "%":1, "-":1, "/":1, "=":1, "(":1, "?":1, "|":1, "^":1, "[":1,
            "&":1, "*":1, "!":1, ":":1, "<":1, ",":1
        },
        out_context_word = { // token preceeding a word signalling a new output
            "{":1, "} ":1, ")":1, ")   ":1, ";":1, "\n":1, "else":1
        },
        out_context_paren = { // token preceeding a paren signalling a new output
            "{":1, ";":1, "\n":1, "else":1
        }, // special markers: ') ' tail of xpath macro. ')  ' func def, tok=')    ' its not an if while etc.
        markup_in_code_lut = {
            "} ":1, ")   ":1,// the } used when it wasnt a code-expression
            "(":1, /*")":1,*/ ";":1, "&":1, "^":1, "|":1, ",":1, '"':1, "'":1, "=":1,
            "!=":2,"+=":2, "-=":2, "/=":2, "*=":2, "?":1, "{":1, "}":1, ">":1, "[":1,
            /*"]":1,*/ "+":1, ":":1, "else":1, "return":1
        },
        block_autoappend = { // token preceeding block signalling auto append
            '"':1, "'":1, ">":1, "]":1, "}":1
        },
        unesc_lut = { // unescape in code and xpath mode
            "\\\"": "\"", "\\\'": "\'", "\\{": "{", "\\}": "}", "\\[": "[",
            "\\]": "]", "\\(":"(", "\\)":")", "\\\\":"\\"
        },
        call_exclusion = {
            "alert": 1, "confirm" :1, "setTimeout":1, "setInterval":1, "switch":1,
            "call":1, "return":1, "throw":1, "case":1, "catch":1,
            "abs":1,"acos":1,"asin":1,"atan":1,"atan2":1,"ceil":1,
            "cos":1,"exp":1,"floor":1,"log":1,"max":1,"min":1,
            "pow":1,"random":1,"round":1,"sin":1,"sqrt":1,"tan":1,"lin":1,"linear":1,
            "idx":1,"sort":1,"typeof":1
        },
        is_out_space = {
            " ":1, "\n":1
        },
        newline_notallowed = {
            "{":1, ";":1, "(":1, "\n":1
        },//@todo !verify and document! character escaping system
        unesc_str = { // unescape in string mode
            "\\{": "{", "\\}": "}", "\\[": "[", "\\]": "]", "\\(": "(", "\\)": ")"
        },
        unesc_txt = { // unescape in text mode
            "\\{" : "{", "\\}" : "}", "\\[" : "[", "\\]" : "]", "\\(" : "(",
            "\\)" : ")", "\\\\": "\\\\\\\\", "\\"  :"\\\\", "\\<" : "<", "\\>" : ">"
        },
        xml_code_operators = { // word to operand lookup table for easy use in xml
            "lte": "<=", "gte": ">=", "lt": "<", "gt": ">", "and": "&&", "or": "||",
            "andbin": "&", "orbin": "|", "LTE": "<=", "GTE": ">=", "LT": "<",
            "GT": ">", "AND": "&&", "OR": "||", "ANDBIN": "&", "ORBIN": "|"
        },
        xpath_macro = { // which autoxpath to use when doing macro({xpath})
            0 : "_val(_n,",
            1 : "_valcr(_n,_cr,",
            2 : "_nod(_n,",
            3 : "_nodcr(_n,_cr,",
            4 : "_nods(_n,",
            5 : "_xpt(_n,",
            6 : "_valst(_n,",
            7 : "_valed(_n,",
            8 : "_valattr(_n,",
            "foreach"   : "_nods(_n,",
            "each"      : "_nods(_n,",
            "foreachrev": "_nods(_n,",
            "eachrev"   : "_nods(_n,",
            "xabs"      : "_valst(_n,",
           // "edit"      : "_argwrap(_n,",
           // "edit"      : "_val(_n,", // toggled by liveedit
            "local"     : "_nod(_n,",
            "tagName"   : "_nod(_n,",
            "localName" : "_nod(_n,",
            "xml"       : "_xmlq(",
            "_call"     : "_val(_n,"
        },
        xpath_model = { // which autoxpath to use when doing macro({xpath})
            "_val(_n,"      : "_valm(",
            "_valcr(_n,_cr,": "_valcr(0,_cr,",
            "_nod(_n,"      : "_nodm(",
            "_nodcr(_n,_cr,": "_nodcr(0,_cr,",
            "_nods(_n,"     : "_nodsm(",
            "_argwrap(_n,"  : "_argwrapm(",
            "_xml(_n,"      : "_xml(0,",
            "_xmls(_n,"     : "_xmls(0,",
            "_cnt(_n,"      : "_cntm(",
            "_xpt(_n,"      : "_xptm(",
            "_valst(_n,"    : "_valm(",
            "_valed(_n,"    : "_valed(0,",
            "_lng("         : "_lng(",
            "_lnged("       : "_lnged("
        },
        parserx = /(\r?[\n]|\/\*|\*\/|\/\/|\<\!\-\-|\-\-\>|[=\!+\/\*-]=|\+\+|\-\-|["'{(\[\])}\]\<\>]|$)|([ \t]+)|([a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF.$_][\w.$_]*)|(\d[x\d.]*)|(\\?[\w._?,:;!=+-\\\/^&|*"'[\]{}()%$#@~`<>]?)/g,
        selfrx = /(^|\|)(?!\@|text\(\)|\.\.|[\w\-\:]+?\:\:)/g, // inject self regexp
        macro_o = {},
        macro_c = {},
        macro_m = {},
        // config vars
        c_async_lut = apf.$asyncObjects || { // used to figure out if the thing before the. is an async obj
            "comm" :1,
            "rpc"  :1,
            "http" :1,
            "apf.ajax" :1
        },
        c_process_async,
        c_xpathmode,    // guess 'node' as the type for {} o_xpathpairs, 1 = node, 2 = nodes
        c_elemxpath,    // which xpath macro to use inside an element
        c_statexpath,   // which xpath to use for the stateful value
        c_injectself,   // add self:: in some o_xpathpairs
        c_propassign,   // support property assigns
        c_export,       // export function to some object
        // outputs
        o, ol,          // output and output len
        o_asyncs,       // number of async calls
        o_xpathpairs,   // all xpaths and their models in pairs
        o_props,        // the js properties found
        o_segs,         // segments at groundlevel
        o_xpaths,       // xpaths at groundlevel
        o_models,       // number of xpaths with models
        // temp and state vars
        s = [], sl,     // scopestack and scopestack len
        bt = [],        // backtrack lut
        bts = [],       // backtrack string stack
        parse_mode,     // the parse parse_mode
        scope,          // ol of a scope begni
        segment,        // the ol of a segment begin
        start_tok,      // the token a string or comment was started with
        str,str_len,           // length of the input string
        line_no,        // line number we are at
        nesting,        // nesting count
        // last state vars
        last_tok,       // last token
        last_type,      // last type
        last_dot,       // . pos when last token was a word
        last_model,     // last model found
        last_prop,      // last property found
        last_cmt_mode,  // the parse mode outside the comment
        last_cmt_tok,   // last token before comment
        last_cmt_type,  // last type before comment
        last_line,      // offset of last newline
        last_ns,        // last namespace found
        last_word;      // last word in code mode
    // macros used in code()
    macro_o["if"] = "if(",
    macro_c["if"] = ")",
    macro_o["while"] = "while(",
    macro_c["while"] = ")",
    macro_o["for"] = "for(",
    macro_c["for"] = ")",
    macro_o["switch"] = "switch(",
    macro_c["switch"] = ")",
    macro_o["catch"] = "catch(",
    macro_c["catch"] = ")",
    macro_c["function"] = ")  ";

    macro_o.foreach = 
    macro_o.each = "\nfor(var _t=_t||[],_t=(_t.push(_n,0,(",
    macro_c.foreach = 
    macro_c.each = ")||[]),_t);(_n=_t[_t.length-1][_t[_t.length-2]++])||(_t.length-=2,_n=_t.pop(),0);)",
    macro_o.foreachrev = 
    macro_o.eachrev = "\nfor(var _t=_t||[],_t=(_t.push(_n,0,(",
    macro_c.foreachrev = 
    macro_c.eachrev = ")||[]),_t);(_n=_t[_t.length-1][_t[_t.length-1].length-(_t[_t.length-2]++)-1])||(_t.length-=2,_n=_t.pop(),0);)",

    macro_o.local = "\nfor(var _t=_t||[],_t=(_t.push(_n,((_n=_local(",
    macro_c.local = ")),1)),_t);(_t[_t.length-1]--&&_n)||(_t.length--,_n=_t.pop(),0);)",
    macro_o._editlm = "_valedx(true, ", // only serves to switch default xpath in edit([xpath])
    macro_o._editnormal = "_valedx(false, ", // only serves to switch default xpath in edit([xpath])
    macro_c.edit = ")",
    macro_o.xabs = "  (  ",
    macro_c.xabs = "  )  ",

    macro_o.localName = "_localName(_n",
    macro_c.localName = ")",
    macro_o.output = "_o.join(''",
    macro_c.output = ")",
    macro_o.reset = "(_o=[],l=0",
    macro_c.reset = ")",
    macro_o.index = "apf.getChildNumber.call(apf",
    macro_c.index = ")",
    macro_o.item = "(_t[_t.length-1][_t[_t.length-2]-1]",
    macro_c.item = ")",
    macro_o.first = "(_t[_t.length-2]==1",
    macro_c.first = ")",
    macro_o.last = "(_t[_t.length-2]==_t[_t.length-1].length",
    macro_c.last = ")",
    macro_o.total = "(_t[_t.length-1].length",
    macro_c.total = ")",
    macro_o.pos = "(_t[_t.length-2]-1",
    macro_c.pos = ")",

    macro_o.tagName = "_tagName(_n",
    macro_c.tagName = ")",
    macro_o._nodeValue = "_nodeValue(_n",
    macro_c._nodeValue = ")",
    macro_c.async = "])",
    macro_c.precall = "])",
    macro_c._call = ")";

    var call_args_lut = {
            _call: ".call(_n",
            localName: macro_o.localName,
            tagName: macro_o.tagName,
            nodeValue: macro_o.nodeValue,
            index: macro_o.index
        },

        // centralized code fragments used in parser/generator
        cf_block_o = "(function(){var _o=[],_l=0;\n",
        cf_block_c = ";return _l==1?_o[0]:_o.join('');}).call(this)",
        cf_async_o = "_async(_n,_c,_a,_w,_f,this,",
        cf_async_m = "',_a[++_a.i]||[",
        cf_obj_output = "_r=",
        cf_mode_output,
        cf_str_output = "_o[_l++]=",
        cf_def_output = "",
        cf_func_o = "{var _o=[],_l=0,_n=this;\n",
        cf_func_c = ";\nreturn _l==1?_o[0]:_o.join('');}",

        // compile chunks used in compile/match
        cc_async_o = "(_a=_a||{}).i=0;try{\n",
        cc_async_c = "}catch(_e){if(_e.x)return;throw(_e);}\n",
        //cc_async_o = "(_a=_a||{}).i=0;",
        //cc_async_c = "",
        cc_pc_o = "(_a=_a||{}).i=0;try{_precall(_w);",
        cc_pc_c = "}catch(_e){if(_e.x)return;throw(_e);}",
        cc_opt_o = "with(_w){",
        cc_opt_c = "}",
        cc_v_blk_o = "var _o=[],_l=0;_o[_l++]=",
        cc_v_blk_ob = "var _o=[],_l=0;\n",
        cc_v_blk_c = ";\nreturn _ret(_l==1?_o[0]:_o.join(''));",
        cc_v_blk_cb = ";\n_c(_ret(_l==1?_o[0]:_o.join('')),apf.SUCCESS,apf.$lmx);apf.$lmx=null;",
        cc_v_ret_o = "return _ret(",
        cc_v_ret_c = ");",
        cc_v_cb_o = "_c(_ret(",
        cc_v_cb_c = "),apf.SUCCESS,apf.$lmx);apf.$lmx=null;\n",

        cc_o_blk_o = "var _r=",
        cc_o_blk_ob = "var _r;",

        cc_o_blk_c = ";\nreturn _r;",
        cc_o_blk_cb = ";\n_c(_r,apf.SUCCESS,apf.$lmx);apf.$lmx=null;",
        cc_o_blk_ce = ";\n_c(0,apf.SUCCESS,apf.$lmx);apf.$lmx=null;;",
        cc_o_ret_o = "return ",
        cc_o_ret_c = "",
        cc_o_cb_o = "_c(",
        cc_o_cb_c = ",apf.SUCCESS);",
        cc_f_async_o = "var _f=function(_n,_c,_w,_a){",
        cc_f_opt_o = "var _f=function(_n,_w){",
        cc_f_o = "var _f=function(_n){",
        cc_fc_async_o = "var _f=function(_n,_c,_w,_cr,_a){",
        cc_fc_opt_o = "var _f=function(_n,_w,_cr,){",
        cc_fc_o = "var _f=function(_n,_cr){",
        cc_fe_async_o = "var _f=function(event,_c,_w,_a,_n){",
        cc_fe_opt_o = "var _f=function(event,_w,_n){",
        cc_fe_o = "var _f=function(event,_n){",
        cc_f_c = "}",
        cc_f_match_o = "var _f=function(_m){",

        cc_m_m_blk = ";\nif(_n=_r){if(!_n.nodeType)_n=_m;",
        cc_m_m_value_o = ";\nif(_n=",
        cc_m_m_value_c = "){if(!_n.nodeType)_n=_m;",
        cc_m_v_string = "\nreturn ",
        cc_m_v_o = "\nreturn _ret(",
        cc_m_v_c = ");",
        cc_m_n_string = "\nreturn _n;",
        cc_m_n_o = "\nreturn (_r = (",
        // decision point for compileMatch node-mode for the return type
        cc_m_n_c = "))?(_r.nodeType?_r:_n):(_r===null?null:_n);",

        cc_m_o = "var _r, _n = _m;",
        cc_m_brk = ";\n_n = _m;",
        cc_m_v_ret = "\nreturn _ret(_nodeValue(_n));" ,
        cc_m_n_ret = "\nreturn _n;" ,
        cc_m_c = "\n}";

    function switchToBlock(no_output){ // used to switch expression mode to block mode
        var u, v;
        if (o[scope-1] == "{{")
            u = scope-1; // scan for our root expression block to switch to block
        else
            for (v = sl - 2, u = 0; v >= 0 && o[u=(s[v] & 0xfffffff) - 1] != "{{"; v -=2 ){};

        if (!no_output && ol > u + 1)  // inject auto output unless no output or nothing to output in buffer
            o[u] = cf_block_o + cf_str_output
        else
            o[u] = cf_block_o;
        parse_mode = 1;
    }

    function parser(tok, rx_lut, rx_white, rx_word, rx_num, rx_misc, pos) {
        var u, v, w,
            type = rx_lut ? type_lut[rx_lut] : (rx_white ? 0 : (rx_word ? 3 : (rx_num ? 4 : (tok ? 2 : 15))));
        switch (parse_mode) {
            case 0: // =====================  expression parse_mode =========================
            case 1: // ==========================  block parse_mode =========================
                switch (type) {
                    case 0: // -------- whitespace --------
                        if ((last_type == 3 && last_tok!='$') || last_type == 4)
                            o[ol++] = " ";
                        else if (xpath_lut_code[last_tok])
                            last_type = 0;// make last_type visible to xpathmode select
                        break;
                    case 1: // -------- newline --------
                        line_no++,
                        last_line = pos;
                        if (o[ol-1] != "\n" && !newline_notallowed[last_tok])
                            o[ol++] = "\n";
                        if (xpath_lut_code[last_tok])
                            last_type = 0;// make last_type visible to xpathmode select
                        break;
                    case 2: // -------- misc --------
                        if (v = misc_tok[tok]) {
                            switch (v) {
                                case 1: // ';'
                                    if (!s[sl-1]) {// close = macro
                                        o[ol++] = ")",
                                        sl     -= 2;
                                    }

                                    if (!parse_mode) { // dont do ; inject newline instead
                                        if (o[ol-1] != "\n" && last_tok != "{" && last_tok != ";")
                                            o[ol++] = "\n";
                                    }
                                    else if (!sl || s[sl - 1]) // dont inject ; if we are in nested assignment macros
                                        o[ol++] = ";";
                                    break;
                                case 2: // ','
                                    if (!s[sl - 1]) { // close = macro
                                        o[ol++] = ")",
                                        sl     -= 2;
                                    }
                                    o[ol++] = ",";
                                    break;
                                case 3: //'^' // dont output
                                    if (o[ol-1] == "\n" || o[ol - 1] == ";" || last_tok=="{"
                                      || last_tok == "} " || ol == scope) { // dont output-shortcut requirements
                                        if (!parse_mode)
                                            switchToBlock();
                                        o[ol++] = "  "; // two spaces make it modify the output-ifs outcome
                                    }
                                    else
                                        o[ol++] = "^";
                                    break;
                                case 4: //'= += -= assignment macro mode
                                    if (last_tok!='<' && last_tok!='>'){
                                        // we should only switch to block when we are not in a ( ) scope
                                        if (!parse_mode && o[scope-1]!='(')
                                            switchToBlock(true);
                                        o[ol++] = tok;
                                        // lets scan in reverse to see if we have an output or a non-output

                                        for (v = ol; v >= scope && !statement_lut[o[v]] && !((o[v] == "  "
                                            || o[v] == (nesting ? cf_str_output : cf_mode_output)) && (o[v]="",1)); v--){};

                                        if (last_type == 3 && last_dot>0 && last_tok.charAt(0)!="."){ // prop = macro
                                            if (c_propassign) {
                                                ol -= 2;
                                                while (is_out_space[o[ol]])
                                                    ol--;
                                                w = last_tok;
                                                o[ol++] = op_lut[tok], o[ol++] = w.slice(0,last_dot),
                                                o[ol++] = ",'", o[ol++] = w.slice(last_dot+1),
                                                o[ol++] = "',", s[sl++] = scope | (parse_mode << 28),
                                                s[sl++] = ""; // notabene, this stored item is checked everywhere
                                            }
                                        }
                                     }else{
                                        o[ol++] = tok;
                                    }break;
                                case 5: // '/' // regexp mode
                                    if (pre_regexp[last_tok]) {
                                        s[sl++] = scope | (parse_mode << 28);
                                        s[sl++] = o[ol++] = tok;
                                        scope = segment = ol - 1;
                                        nesting++, parse_mode = 5, start_tok = tok;
                                    }
                                    else
                                        o[ol++] = "/";
                                    break;
                                case 6: // ':' // switch to {x:1} object mode
                                    if (sl > 2 && s[sl - 1] == "{{" && (ol < scope + 4 && last_type == 5)
                                      || (ol < scope + 3 && (last_type == 3 || last_type == 4))) {
                                        o[scope-1] = s[sl-1] = "{"
                                        parse_mode = (v = s[sl - 2]) >> 28;
                                        s[sl-2] = v & 0xfffffff,
                                        nesting--;
                                    }
                                    else if (o[ol - 3] == "case" || (last_type == 5 && last_word == "case"))
                                        tok = ";"; //fixes auto output problem
                                    o[ol++] = ":";
                                    break;
                                default:
                                     o[ol++] = tok;
                                    break;
                            }
                        }
                        else
                            o[ol++] = unesc_lut[tok] || tok;
                        break;
                    case 3: // -------- word --------
                    case 4: // ------- number -------
                        if ( v = xml_code_operators[tok] ){
                            o[ol++] = tok = v, type = 2;
                        } else {
                            v = u = w = 0;// last_word used for case 'bla bla':
                            last_dot = (last_word = tok).lastIndexOf(".");
                            if (tok.charAt(0) != '.' // .obj shouldnt trigger block
                              && ((v = (u = ((out_context_word[last_tok]  // check if we need to switch
                                    || o[ol - 1] == "\n")  && !new_block[last_tok]))
                                    && !s[sl - 1].indexOf("{") && ol > scope)
                                    || (w = statement_lut[tok])) && !parse_mode){ // check statement
                                if (w == 2 && s[sl - 1].indexOf("{")) w = 0; // (function() shouldnt trigger blockmode
                                switchToBlock(w);  // pass in statement_lut[tok] as outputflag
                            }
                            if (u && !s[sl - 1]) { // assign macro close
                                o[ol-1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
                                o[ol++] = "\n", v = 1, sl -= 2;
                            }
                            if (v && parse_mode && !statement_lut[tok] && !call_exclusion[tok]) // inject output
                                o[ol++] = (nesting ? cf_str_output : cf_mode_output);

                            if (last_dot > 0 && tok.charAt(0) != ".") // store property
                                o_props[o[ol++] = last_prop = tok] = 1;
                            else o[ol++] =  tok;
                        }
                        break;
                    case 5: // -------- stringquotes --------
                        if ((v = (u = ((out_context_word[last_tok] || o[ol - 1]== "\n" )
                          && !new_block[last_tok])) && !s[sl - 1].indexOf("{")
                          && ol > scope) && !parse_mode) // check if we need to switch to block mode
                            switchToBlock();

                        if (u && !s[sl - 1]) { // close = macro
                            o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
                            o[ol++] = "\n", v = 1, sl -= 2;
                        }
                        if (v) { // generate output
                            o[ol++] = (o[ol-2] != "\n" && block_autoappend[last_tok])
                                ? "+"
                                : (nesting ? cf_str_output : cf_mode_output);
                        }
                        else if (block_autoappend[last_tok])
                            o[ol++] = "+";

                        s[sl++] = scope | (parse_mode << 28), s[sl++] = o[ol++] = tok;
                        scope = segment = ol - 1, nesting++, parse_mode = 5, start_tok = tok;
                        break;
                    case 6: // -------- comment --------
                        if (tok == "*/" || tok== "-->")
                            throw {
                                t: "Unmatched comment "+tok,
                                p: pos
                            };
                        last_cmt_mode = parse_mode, last_cmt_tok = last_tok,
                        last_cmt_type = last_type, parse_mode = 6, start_tok = tok;
                        break;
                    case 7: // -------- { --------
                        if (o[ol - 1] == ")  " || (o[ol - 2] == ")  " && ol--)) { // ')  ' is function def
                            if (s[sl - 1] != "(" && s[sl - 1] != "[") {
                                s[sl++] = scope | (parse_mode << 28),
                                s[sl++] = "{{", o[ol++] = cf_func_o,
                                scope = ol, parse_mode = 1, nesting++, o[ol++] = ""; // make the scope check pass
                            }
                            else {
                                s[sl++] = scope, s[sl++] = o[ol++] = tok, scope = ol;
                                parse_mode = 1;
                            }// for do else..etc below
                        }
                        else if ((macro_o[s[sl + 1]] && last_tok == ")   ") || pre_plain[last_tok]) {
                            s[sl++] = scope, s[sl++] = o[ol++] = tok, scope = ol;
                            o[ol++] = "";
                        }
                        else {
                            if ((v = (u = ((out_context_word[last_tok]||o[ol - 1] == "\n")
                              && !new_block[last_tok]))
                              && !s[sl - 1].indexOf("{") && ol > scope) && !parse_mode)
                                switchToBlock(); // block mode detection

                            if (u && !s[sl - 1]) { // close = macro
                                o[ol - 1] == "\n" && (o[ol - 1] = ""),
                                o[ol++] = ")", o[ol++] = "\n", v = 1, sl -= 2;
                            }
                            if (v) { // inject output, +''+ is when between two { } { } (supposedly)
                                o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
                                    ? "+''+"
                                    : (nesting ? cf_str_output : cf_mode_output);
                            }
                            else if (block_autoappend[last_tok]) // inject append
                                o[ol++] = (last_tok == "}") ? "+''+" : "+";

                            s[sl++] = scope | (parse_mode << 28), s[sl++] = o[ol++] = "{{";

                            if (!nesting && scope != ol) // count output segments on nesting 0
                                o_segs++;

                            nesting++, segment = scope = ol, parse_mode = 0;
                        }
                        break;
                    case 8: // -------- } --------
                        if (!s[sl - 1]) // close = macro
                            o[ol++] = ")", o[ol++] = "\n",sl -= 2;

                        if (type_close[v = s[--sl]] != (o[ol++] = tok))
                            throw {
                                t: "Cannot close " + v + " with " + tok,
                                p: pos
                            };

                        if (v == "{{") { // closing code block
                            if (scope == ol - 1) {
                                if ( (s[sl - 1] >> 28) <= 1) // empty code in code
                                    o[scope-1] = "{", o[ol - 1] = "}";
                                else // empty code elsewhere
                                    o[scope - 1] = o[ol - 1] = "'";
                            }
                            else {
                                if (!parse_mode) { // expression wraps in ()
                                    o[scope - 1] = "(",
                                    o[ol - 1] = ")";
                                }
                                else { // codeblock wraps in (function(){})()
                                    if (o[scope - 1] == cf_func_o) {
                                        if (scope == ol - 2)
                                            o[scope - 1] = "{", o[ol - 1] = "}";
                                        else
                                            o[ol - 1] = cf_func_c;
                                    }
                                    else
                                        o[ol - 1] = cf_block_c;
                                }
                            }
                            parse_mode = (v=s[--sl])>>28, scope = v&0x0fffffff;
                            segment = ol, nesting--;

                            if (!nesting) // count segs on nesting level 0
                                o_segs++;

                            if (parse_mode == 7) // attribute hack
                                o[ol++] = "+\"\\\"", parse_mode = 4;
                        } else scope = s[--sl]; // was object def or if (){}

                        break;
                    case 9: // -------- [ --------
                        if (((last_type == 3 && !pre_xpath[last_tok] && last_tok!='$') || last_tok == ")" || last_tok == "]") && o[ol - 1] != "\n") {
                            o[ol++] = "[", s[sl++] = scope | (parse_mode << 28), //was array index
                            s[sl++] = tok, segment = scope = ol;
                        }
                        else {
                            last_model = null;

                            if ((w = xpath_lut_code[last_tok])) {
                                ol--, last_tok = o[ol-1]; // xpath with *%$#
                            }
                            else {
                                w = xpath_macro[s[sl - 1]] || xpath_macro[nesting ? 0 : c_xpathmode];
                            }
                            if ((v = (u = ((out_context_word[last_tok] || o[ol - 1] == "\n")
                              && !new_block[last_tok])) && !s[sl - 1].indexOf("{")
                              && (ol > scope || s[sl - 1].length == 1)) && !parse_mode)
                                switchToBlock(); // check if we need to switch to block mode

                            if (u && !s[sl - 1]) { // close = macro
                                o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
                                o[ol++] = "\n", v = 1, sl -= 2;
                            }

                            if (v) { // inject output
                                o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
                                    ? "+"
                                    : (nesting ? cf_str_output : cf_mode_output);
                            }
                            else if (block_autoappend[last_tok]) // inject append
                                o[ol++] = "+";

                            if (!nesting && ol!=scope)
                                o_segs++;
                            // store scope in bt for reparse of array
                            nesting++, s[sl++] = scope|(parse_mode<<28), s[sl++] = o[ol++] = w,
                            segment = scope = ol, bt[scope] = pos, parse_mode = 3;
                        }
                        break;
                    case 10: // -------- ] --------
                        if (!s[sl-1]) // close = macro
                            o[ol++]=")",sl -=2;

                        if ( type_close[v = s[--sl]] != (o[ol++] = tok))
                            throw {
                                t: "Cannot close " + v + " with " + tok,
                                p: pos
                            };

                        scope = s[--sl]&0xfffffff; // clean scope of possible parse_mode 1
                        break;
                    case 11: // -------- ( --------
                        if ( ((v = (u=((out_context_paren[last_tok]||o[ol-1]=="\n") &&
                            !new_block[last_tok])) && !s[sl-1].indexOf("{") &&
                            ol>scope)) && !parse_mode)
                            switchToBlock();

                        if (u && !s[sl-1]) // close = macro
                            o[ol-1]=="\n"&&(o[ol-1]=""),o[ol++]=")", o[ol++]="\n",v = 1,sl -=2;

                        if (v && parse_mode) // inject output
                            o[ol++] = (nesting?cf_str_output:cf_mode_output), last_type = 0;

                        if (w = macro_o[last_tok]) {
                            if (o[ol-1]==" ") ol--; // support func ()
                            o[ol-1] = w, s[sl++] = scope, s[sl++] = last_tok, scope = segment = ol;
                        }
                        else {
                            if (last_type == 3) { // word(
                                if (last_dot < 0) { // no dot
                                    v = 0;
                                    if (last_tok == "function" || o[ol - 3] == "function" || o[ol - 4] == "function") {
                                        s[sl++] = scope, s[sl++] = "function", //func def
                                        o[ol++] = "(", scope = segment = ol;
                                        //TODO! check the depth with which functions are made global
                                        if (last_tok!="function" && c_export && sl==4) {
                                            o[v=(o[ol - 4] == "function")?(ol-4):(ol-5)] =
                                                "var "+last_tok+" = "+c_export+"."+last_tok+" = function";
                                            o[v+2] = "";
                                        }
                                    }
                                    else { // its a call and not a new
                                        if (!call_exclusion[last_tok] && o[ol-3]!="new") {
                                            o[ol++] = ".call(_n", s[sl++] = scope,
                                            s[sl++] = "_call", scope = segment = ol;
                                        }
                                        else { // its an excluded call
                                            s[sl++] = scope, s[sl++] = o[ol++] = tok,
                                            scope = segment = ol;
                                        }
                                    }
                                }
                                else {
                                    if (last_dot > 1 && c_process_async && (c_async_lut[v = last_tok.substring(0,last_dot)] || c_async_lut[v = last_tok])) {// its an async call
                                    if (o[--ol] == " ")
                                        ol--;
                                    o[ol++] = cf_async_o, o[ol++] = v, o[ol++] = ",'";
                                    o[ol++] = last_tok.slice(last_dot + 1);
                                    o[ol++] = cf_async_m, s[sl++] = scope, s[sl++] = "async",
                                    scope = segment = ol, o_asyncs++;
                                    }
                                    else { // its a obj.prop() type call
                                        if (last_tok.indexOf('.')!=last_dot) // obj.prop.call();
                                            o_props[last_tok.slice(0,last_dot)] = 1;

                                        s[sl++] = scope, s[sl++] = o[ol++] = tok,
                                        scope = segment = ol;
                                    }
                                }
                            }
                            else { // function object call
                                s[sl++] = scope, s[sl++] = o[ol++] = tok,
                                scope = segment = ol;
                            } // dont store calls as props
                            if (last_tok == last_prop)
                                delete o_props[last_tok];
                        }
                        break;
                    case 12: // -------- ) --------
                        if (!s[sl - 1]) { // close = macro
                            o[ol-1] == "\n" && (o[ol-1] = ""), o[ol++] = ")",
                            o[ol++]="\n", v = 1, sl -= 2;
                        }

                        if (w = macro_c[v = s[--sl]]) { // closing a macro
                            if (v != "_call")
                                tok = ")   "; // make sure any [ ] doesnt get interpreted as array index
                            if ((u = call_args_lut[v]) && u != o[ol - 1])
                                o[scope - 1] = u + ",";// do , insertion for argless macros
                            o[ol++] = w; // write close-end of macro
                        }
                        else if (type_close[v] != (o[ol++] = tok)) {
                            throw {
                                t: "Cannot close " + v + " with " + tok,
                                p: pos
                            };
                        }
                        scope = s[--sl] & 0xfffffff; // scope should be unimpacted
                        break;
                    case 13: // -------- < --------
                        // check if < is markup or not
                        if (ol == scope || markup_in_code_lut[last_tok] || o[ol - 1] == "\n"){
                            if ((v = (u = ((out_context_word[last_tok] || o[ol - 1] == "\n")
                              && !new_block[last_tok])) && !s[sl - 1].indexOf("{")
                              && ol > scope) && !parse_mode)
                                switchToBlock(); // switch to block mode

                            if (u && !s[sl - 1]) { // close = macro
                                o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
                                o[ol++] = "\n", v = 1, sl -= 2;
                            }
                            if (v) {
                                o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
                                    ? "+''+"
                                    : (nesting ? cf_str_output : cf_mode_output);
                            }
                            else if (block_autoappend[last_tok])
                                o[ol++] = "+";
                            // start markup mode with the markup-stack counting
                            last_ns = null, o[ol++] = '"', o[ol++] = "<", nesting++,
                            s[sl++] = scope | (parse_mode << 28), sl += 3,
                            s[sl - 2] = s[sl - 1] = 0;

                            segment = scope = ol - 1, parse_mode = 4;
                        }
                        else
                            o[ol++] = "<";
                        break;
                    case 14: // -------- < --------
                        o[ol++] = ">";
                        break;
                    case 15: // end
                        if (sl && !s[sl - 1]) { // close = macro
                            o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
                            o[ol++] = "\n", v = 1, sl -= 2;
                        }
                        break;
                }
                break;
            case 2: // ==========================  text parse_mode ==========================
                switch (type) {
                    case 1: // -------- newline --------
                        line_no++, last_line = pos;
                        if (ol != scope && ol != segment) // only output when not first
                            o[ol++] = "\\n";
                        break;
                    case 2: // -------- misc --------
                        if (ol == segment) // segment connectors
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";

                        o[ol++] = unesc_txt[tok] || tok;
                        break;
                    case 3: // word
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        if (tok.charAt(tok.length-1)=='$'){
                            o[ol++] = tok.slice(0,-1);
                            o[ol++] = tok = '$';// fix word$[xpath]
                        }else o[ol++] = tok;
                        break;
                    case 5: // -------- stringquotes --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";

                        o[ol++] = (tok == '"') ? "\\\"" : "'";
                        break;
                    case 7: // -------- { -------- code mode
                        if (ol == segment) {
                            if (ol != scope )
                                o[ol++] =  "+";
                        }
                        else
                            o[ol++] = "\"+", nesting || o_segs++;
                        s[sl++] = scope | 0x20000000, s[sl++] = o[ol++] = "{{",
                        nesting++, segment = scope = ol, parse_mode = 0;
                        break;
                    case 9:  // -------- [ --------  xpath mode
                        last_model = null; // get xpath macro
                        if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok) {
                            if (--ol - 1 == scope)
                                ol --; // remove first ""
                        }
                        else // only select c_xpathmode when nesting == 0
                            w = xpath_macro[(nesting || scope != ol) ? 0 : c_xpathmode];

                        if (ol != scope) {
                            o[ol] = (ol++ == segment) ? "+" : (nesting || o_segs++, "\"+");

                            if (!nesting)
                                o_segs++;
                        }

                        s[sl++] = scope | 0x20000000, s[sl++] = o[ol++] = w,
                        segment = scope = ol, nesting++, parse_mode = 3;
                        break;
                    case 15: // -------- end --------
                        if (sl)
                            throw {
                                t: "Unclosed " + s[sl-1] + " found at end in textmode",
                                p: pos
                            };
                        if (ol != scope && ol != segment)
                            o[ol++] = "\"", nesting || o_segs++;
                        break;
                    default: // -------- default --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        o[ol++] = tok;
                }
                break;
            case 3: // ==========================  xpath parse_mode =========================
                switch(type) {
                    case 0: // -------- whitespace --------
                        if (ol != scope){ // strip initial spaces\l
                            if (ol == segment)
                                o[ol++] = "+\"";
                            o[ol++] = tok;
                        }
                        break;
                    case 1: // -------- newline --------
                        line_no++, last_line = pos;
                        break;
                    case 2: // -------- misc --------
                        if (tok == ":" && last_tok == ":" && !xpath_axes[w = o[ol - 2]]
                          && ((v = s[sl - 2]) >> 28) != 6) { // found model::xpath split
                            if (o[ol - 2] == '+"') // model is calculated
                                o[ol - 2] = o[ol - 1] = "", last_model = "#";
                            else {
                                o[ol - 1] = '"';
                                if (segment == scope) // model is normal name
                                    last_model = o.slice(segment + 1, ol - 1).join("");
                                else // model is calculated
                                    last_model = "#";
                            }
                            if (!(w = xpath_model[o[scope - 1]]))
                                throw {
                                    t: "Invalid model found for: "+o[scope-1],
                                    p: pos
                                };

                            o[scope - 1] = w, o[ol++] = ",", segment = scope = ol;
                        }
                        else {
                            if (tok == "," && (v = (s[sl - 2] >> 28)) <= 1) { // xpath is an array in code
                                ol = scope-1, u = str.slice(bt[scope] + 1, pos + 1);
                                // fix up stack to not be an xpath but an array
                                last_type = 9, parse_mode = v, o[ol++] = last_tok = "[";
                                s[sl - 2] = (s[sl - 2] & 0xfffffff) | (parse_mode << 28),
                                s[sl - 1] = last_tok, segment = scope = ol, nesting--;

                                if (!nesting)
                                    o_xpaths--;
                                if (u.length > 1) { // ignore [, optimized escaping
                                    bts.push(str); // push str so str always is the string in replace
                                    (str = u).replace(parserx, parser); // reparse it
                                    str = bts.pop(); // pop it again
                                }
                            }
                            else {
                                if (ol == segment)
                                    o[ol] = (ol++ == scope) ? "\"" : "+\"";
                                o[ol++] = unesc_lut[tok] || tok;
                            }
                        }
                        break;
                    case 3: // word
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        if (tok.charAt(tok.length-1)=='$'){
                            o[ol++] = tok.slice(0,-1);
                            o[ol++] = tok = '$';// fix word$[xpath]
                        }else o[ol++] = tok;
                        break
                    case 5: // -------- stringquotes --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        if (s[sl - 1] == "[") // strings only are used in [ ]
                            s[sl - 1] = tok;
                        else if (s[sl - 1] == tok) // close string
                            s[sl - 1] = "[";

                        if (tok == '"')
                            o[ol++] = "\\";
                        o[ol++] = tok;
                        break;
                    case 7: // -------- { --------
                        if (ol == segment) {
                            if (ol != scope)
                                o[ol++] = "+''+";
                        }
                        else
                            o[ol++] = "\"+";

                        s[sl++] = scope | 0x30000000, s[sl++] = o[ol++] = "{{",
                        nesting++, segment = scope = ol, parse_mode = 0;

                        if (last_model && s[sl - 3] != xpath_lut_text["$"]) {
                            o_xpathpairs.push(last_model, "#");
                            last_model = null, o_models++;
                        }
                        break;
                    case 9: // -------- [ --------
                        // lets see if we are an xpath
                        if (s[sl - 1] == "'" || s[sl - 1] == '"' ||
                            ((last_type != 3 || last_tok=='$') && last_tok != ")" && last_tok != "]") ) {
                            if (last_model)
                                o_xpathpairs.push(last_model, "#"), o_models++;
                            last_model = null;

                            if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok)
                                ol--;
                            else
                                w = xpath_macro[0];

                            if (ol == segment) {
                                if (ol != scope)
                                    o[ol++] = "+";
                            }
                            else o[ol++] = "\"+";

                            s[sl++] = scope | 0x30000000, s[sl++] = o[ol++] = w, nesting++,
                            segment = scope = ol, parse_mode = 3;
                        }
                        else {
                            if (ol == segment)
                                o[ol] = (ol++ == scope) ? "\"" : "+\"";

                            s[sl++] = scope|0x60000000, s[sl++] = o[ol++] = "["; // keep track of [, abuse mode 6
                        }
                        break;
                    case 10: // -------- ] --------
                        sl--, parse_mode = (w = s[--sl]) >> 28, w = w & 0x0fffffff;

                        if (parse_mode == 6){ // was part of [] internally to xpath, see above
                            if (s[sl + 1] != "[")
                                throw {
                                    t: "In xpath, cannot close " + s[sl + 1] + " with " + tok,
                                    p: pos
                                };
                            if (ol == segment)
                                o[ol] = (ol++ == scope) ? "\"" : "+\"";

                            o[ol++] = "]";
                            parse_mode = 3;
                        }
                        else {
                            if (ol == scope ) {
                                if ((s[sl] >> 28) <= 1) // empty array in code
                                    o[scope - 1] = "[", o[ol++] = "]";
                                else // empty xpath elsewhere
                                    o[scope - 1] = o[ol++] = "\"" ;
                                segment = ol;
                            }
                            else {
                                //if( s[sl+1] != '[' )
                                //    throw {t:"Unclosed string in xpath"+s[sl+1], p: pos};
                                if (ol != segment)
                                    o[ol++] = "\"";
                                if (segment == scope){ // we might have an xpath name
                                    v = o.slice(segment + 1, ol - 1).join("");
                                    if (c_injectself && o[scope - 1] != "," // inject self
                                      && v != (u = v.replace(selfrx, "$1self::"))
                                      && s[sl + 1] != xpath_lut_text["$"]) {
                                        o[scope+1] = v = u;
                                        for (u = scope + 2; u < ol - 1; u++)
                                            o[u] = "";
                                    }
                                }
                                else {
                                    if ((u = o[scope - 1]) != ",") {
                                        v = "#";
                                        if (c_injectself)// inject dyn self if dyn xpath
                                            o[scope - 1] = u + "_injself(", o[ol++] = ")";
                                    }
                                    else
                                        v = "";
                                }
                                if (s[sl + 1] != xpath_lut_text["$"] && v) {
                                    o_xpathpairs.push(last_model, v);  // only store if not _lng
                                    if (last_model)
                                        o_models++;
                                }
                                o[ol++] = ") ", segment = ol; // close xpath with ') ' marker
                                //logw("CLOSING XPATH"+o.join('#')+nesting);
                                if (parse_mode == 7) // attribute assign in xml mode
                                    o[ol++] = "+\"\\\"", parse_mode = 4;
                            }
                            // lets output an xpath if we werent a language symbol
                            nesting--, last_model = null;
                            if (!nesting)
                                o_segs++, o_xpaths++;
                        }
                        scope = w;
                        break;
                    case 11: // -------- ( --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        s[sl++] = scope | 0x30000000, // keep track of () in xpath
                        s[sl++] = o[ol++] =  "(";//, last_model = null;
                        break;
                    case 12: // -------- ) --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";

                        if (type_close[v = s[--sl]] != (o[ol++] = tok))
                            throw {
                                t: "Cannot close " + v + " with " + tok,
                                p: pos
                            };

                        scope = s[--sl] & 0xfffffff;
                        break;
                    case 15: // -------- end --------
                        throw {
                            t: "Unexpected end whilst parsing xpath",
                            p: pos
                        };
                        break;
                    default: // -------- default --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        o[ol++] = tok;
                        break;
                }
                break;
            case 4: // ===========================  xml parse_mode ==========================
                switch (type) {// stack: '<'sl+4,outside=0, '</'sl-4  '>'sl-2,outside=1 '/>'sl-4,outside=1
                    case 0: // -------- whitespace --------
                        if (ol == segment)
                            o[ol++] = "+\"";

                        o[ol++] = " ", last_type = 0;
                        break;
                    case 1: // -------- newline --------
                        if (ol == segment)
                            o[ol++] = "+\"";

                        line_no++, last_line = pos, o[ol++] = "\\n", last_type = 1;
                        break;
                    case 2: // -------- misc --------
                        if (ol == segment)
                            o[ol++] = "+\"";
                        if (tok == "/" && last_tok == "<") {
                            sl -= 4; // </ closing tag, drop stacklevel by 4
                            if (s[sl] || s[sl + 2])
                                throw {
                                    t: "Unexpected closing tag whilst parsing xml",
                                    p: pos
                                };
                        }
                        else if (tok == ":" && last_type == 3 && o[ol - 2] == "<")
                            last_ns = last_tok; // found a namespace item in a tag
                        o[ol++] = unesc_txt[tok] || tok;
                        break;
                    case 3: // word
                        if (ol == segment)
                            o[ol++] = "+\"";
                        if (tok.charAt(tok.length-1)=='$'){
                            o[ol++] = tok.slice(0,-1);
                            o[ol++] = tok = '$';// fix word$[xpath]
                        }else o[ol++] = tok;
                        break
                    case 5: // -------- stringquotes --------
                        if (ol == segment)
                            o[ol++] = "+\"";

                        if (tok == '"')
                            o[ol++] = "\\";
                        o[ol++] = tok;
                        break;
                    case 6: // -------- comment --------
                        if (tok == "//" && !s[sl - 1]) {
                            if (ol == segment)
                                o[ol++] = "+\"";  // < char ups stack by 4, outside= 0
                            o[ol++] = tok;
                        }
                        else {
                            if (tok == "*/")
                                throw {
                                    t: "Unmatched comment "+tok,
                                    p: pos
                                };
                            last_cmt_mode = parse_mode, last_cmt_tok = last_tok,
                            last_cmt_type = last_type, parse_mode = 6, start_tok = tok;
                        }
                        break;
                    case 13: // -------- < --------
                        last_ns = null;
                        if (ol == segment)
                            o[ol++] = "+\"";  // < char ups stack by 4, outside= 0
                        o[ol++] = tok, s[sl] = s[sl + 2] = 0, sl += 4, s[sl - 1]=0;
                        break;
                    case 14: // -------- > --------
                        if (ol == segment)
                            o[ol++] = "+\"";

                        o[ol++] = tok;
                        if (last_tok != "<") {
                            if (last_tok == "/") {
                                sl -= 4; // self close tag /> drops stack -4
                                if (s[sl + 2])
                                    throw {
                                        t: "Unexpected / whilst parsing xml",
                                        p: pos
                                    }
                                if (o[ol - 3] == "<") // remove empty </> from output
                                    ol -= 2, o[ol - 1] = "";
                            }
                            else
                                sl -= 2; // <tag> nets stackdepth of 2
                            if (s[sl]) { // end of xml mode
                                nesting--, o[ol++] = "\"", scope = s[sl], segment = ol,
                                parse_mode = scope >> 28, scope = scope & 0x0fffffff;
                            }
                            else
                                s[sl - 1] = 1; // we are outside a tag, flag it on the stack
                        }
                        else // remove empty <> from output
                            ol--, o[ol - 1] = "";
                        break;
                    case 9:  // -------- [ --------  xpath mode
                        last_model = null;

                        if (last_tok == "!" && o[ol - 2] == "<" && !s[sl - 1]) { // CDATA mode
                            o[ol++] = tok, s[sl++] = scope | (parse_mode << 28);
                            s[sl++] = "]]>", scope = segment = ol - 1;
                            nesting++, parse_mode = 5;
                        }
                        else {
                            if (s[sl - 1]) { // we are outside a tag
                                if ((v = xpath_lut_node[last_tok]))
                                    ol --;
                                else
                                    v = xpath_macro[c_elemxpath];

                                s[sl++] = scope | 0x40000000
                            }
                            else {
                                s[sl++] = scope | 0x40000000
                                if ((v = xpath_lut_attr[last_tok])) {
                                    ol--;
                                    if (o[ol - 1] == "=")
                                        last_tok = "=";
                                }
                                else
                                    v = xpath_macro[last_ns ? c_statexpath : 8];

                                if (last_tok == "=")//0x7 flags xpath-in-missing-quotes <a i=[xp]/>
                                    o[ol++] = "\\\"", s[sl - 1] = scope | 0x70000000;
                            }
                            o[ol] = (ol++ == segment) ? "+''+" : "\"+";
                            nesting++, s[sl++] = o[ol++] = v,
                            segment = scope = ol, parse_mode = 3;
                        }
                        break;
                    case 7: // -------- { -------- code mode
                        if ( !s[sl - 1] && last_tok == "=") // 0x7 flags code-in-missing-quotes <a i={x}/>
                            o[ol++] = "\\\"", s[sl++] = scope | 0x70000000;
                        else
                            s[sl++] = scope | 0x40000000

                        o[ol] = (ol++ == segment) ? "+''+" : "\"+";
                        s[sl++] = o[ol++] = "{{", nesting++;
                        segment = scope = ol, parse_mode = 0;
                        break;
                    default:
                        if (ol == segment)
                            o[ol++] = "+\"";
                        o[ol++] = tok;
                        break;
                    case 15: // -------- end --------
                        throw {
                            t: "Unexpected end whilst parsing xml",
                            p: pos
                        };
                        break;
                }break
            case 5: // ==========================  string parse_mode ========================
                switch (type) {
                    case 1: // -------- newline --------
                        line_no++, last_line = pos;
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "\"" : "+\"";
                        o[ol++] = "\\n";
                        break;
                    case 2: // -------- misc --------
                        if (tok == "/" && s[sl - 1] == "/") { // regexp closing character
                            o[ol++] = "/", scope = s[sl -= 2], segment = ol,
                            parse_mode = scope >> 28,
                            scope = scope & 0x0fffffff, nesting--;
                        }
                        else {
                            if (ol == segment)
                                o[ol] = (ol++ == scope) ? "\"" : "+\"";
                            o[ol++] = (s[sl - 1] != "/" && unesc_str[tok]) || tok;
                        }
                        break;
                    case 3: // word
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "" : "+\"";
                        if (tok.charAt(tok.length-1)=='$'){
                            o[ol++] = tok.slice(0,-1);
                            o[ol++] = tok = '$';// fix word$[xpath]
                        }else o[ol++] = tok;
                        break
                    case 5: // -------- stringquotes --------
                        if (s[sl - 1] == tok) { // closed by matching quote
                            if (scope != segment) // string is segmented, output )
                                o[ol] = (ol++ != segment) ? (tok + ")") : ")";
                            else
                                o[ol++] = tok; // else just close
                            scope = s[sl -= 2], segment = ol, parse_mode = scope >> 28;
                            scope = scope & 0x0fffffff, nesting--;
                        }
                        else {
                            if (ol == segment)
                                o[ol] = (ol++ == scope) ? "\"" : "+\"";
                            o[ol++] = tok == '"' ? "\\\"" : tok;
                        }
                        break;
                    case 6: // -------- default --------
                        if (s[sl - 1] == "/" && tok == "*/") { // caught faux comment in regexp /a*/, is close
                            o[ol++] = "*/", scope = s[sl -= 2], segment = ol,
                            parse_mode = scope >> 28, scope = scope & 0x0fffffff, nesting--;
                        }
                        else {
                            if (ol == segment)
                                o[ol] = (ol++ == scope) ? "" : "+\"";
                            o[ol++] = tok;
                        }
                        break;
                    case 7: // -------- { -------- code mode
                        if (s[sl - 1] != "'" && s[sl - 1] != "/") {
                            if (s[sl - 1] == '"')
                                o[scope] = '("';
                            if (ol == segment) {
                                if (ol != scope)
                                    o[ol++] = "+";
                            }
                            else
                                o[ol++] = "\"+";
                            s[sl++] = scope | 0x50000000, o[ol++] =  s[sl++] = "{{",
                            nesting++, segment = scope = ol, parse_mode = 0;
                        }
                        else
                            o[ol++] = tok;
                        break;
                    case 9:  // -------- [ --------  xpath mode
                        if (s[sl - 1] != "'" && s[sl - 1] != "/" // ignore in '' and CDATA[, else xpath
                          && (s[sl - 1] == '"' && (o[scope] = '("') || ol != scope + 2
                          || last_tok != "CDATA") ) {
                            last_model = null;
                            if ((w = xpath_lut_text[last_tok])  && o[ol - 1] == last_tok)
                                ol--;
                            else
                                w = xpath_macro[0]

                            if (ol != scope)
                                o[ol] = (ol++ == segment) ? "+" : "\"+";

                            s[sl++] = scope | 0x50000000, s[sl++] = o[ol++] = w,
                            segment = scope = ol, nesting++, parse_mode = 3;
                        }
                        else
                            o[ol++] = tok;
                        break;
                    case 14: // -------- > --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "" : "+\"";
                        o[ol++] = tok;

                        if (s[sl - 1] == "]]>" && last_tok == "]" && o[ol - 3]=="]") { // check if CDATA close
                            scope = s[sl -= 2], parse_mode = scope >> 28;
                            scope = scope & 0x0fffffff, nesting--;
                            sl -= 4; // close the tag since we came from XML mode
                            if (s[sl]) // was last tag, jump up the stack one more.
                                nesting--, o[ol++] = "\"", scope = s[sl], segment = ol,
                                parse_mode = scope >> 28, scope = scope & 0x0fffffff;
                            else
                                s[sl - 1] = 1;
                        }
                        break;
                    case 15: // -------- end --------
                        throw {
                            t: "Unexpected end whilst parsing string",
                            p: pos
                        };
                        break;
                    default: // -------- default --------
                        if (ol == segment)
                            o[ol] = (ol++ == scope) ? "" : "+\"";
                        o[ol++] = tok;
                        break;
                }
                break;
            case 6: // =========================  comment parse_mode ========================
                switch (type) {
                    case 1: // -------- newline --------
                        line_no++, last_line = pos;
                        if (start_tok == "//")
                            parse_mode = last_cmt_mode,
                            tok = last_tok = last_cmt_tok,
                            type = last_type = last_cmt_type;
                        break;
                    case 6: // -------- comment --------
                        if ((start_tok == "/*" && tok == "*/")
                          || (start_tok == "<!--" && tok == "-->")) {
                            parse_mode = last_cmt_mode,
                            tok = last_tok = last_cmt_tok,
                            type = last_type = last_cmt_type;
                        }
                        break;
                    case 15: // -------- end --------
                        if (start_tok != "//"){
                            throw {
                                t: "Unexpected end whilst parsing comment",
                                p: pos
                            }
                        } else {
                            parse_mode = last_cmt_mode,
                            tok = last_tok = last_cmt_tok,
                            type = last_type = last_cmt_type;
                            if (sl && !s[sl - 1]) { // close = macro
                                o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
                                o[ol++] = "\n", v = 1, sl -= 2;
                            }
                        };
                        break;
                }
                break;
        }
        if (type > 1)
            last_tok = tok, last_type = type;
    }

    this.lastCode = function(){
        if (typeof(o) == "object")
            return o.join("");
        return o;
    };

    function handleError(e, last_line, part, linenr) {
        // TODO: make a proper APF exception with this information:
        if (e.t) {
            throw new Error(apf.formatErrorString(0, null,
                "Parsing live markup source",
                "Error whilst parsing: " + e.t + " on line:"+ line_no
                + " col:" + (e.p - last_line - 2)
                + (part ? (" part: " + part) : "") + "\n" + str));
        }
        else {
            throw new Error(apf.formatErrorString(0, null,
                "Compiling live markup function on line " + linenr,
                "Error whilst compiling: " + e.message
                //+ "\nStack Trace:\n" + e.stack
                + "\nInput:\n" + str
                + "\nGenerated:\n" + apf.lm.lastCode()));
        }
    }

    /**
     * description of the method.
     * Remarks:
     *   function{type:1,xpaths:[ model,name], props: ['obj.propname','obj2.otherpropname'], asyncs=1}
     *   this is a normal compiled function with extra properties
     *   if the xpath model and/or name is '#' it means it is a runtime calculated modelname or xpath.
     *   obj{type:2, str:str} it was single string by cfg option !alwayscode
     *   obj{type:3, xpaths:[ model, name ] } it was a single xpath  by cfg simplexpath
     *
     * @param {String}   str      the code to compile
     * @param {Object}   options
     *   Properties:
     *   {Boolean} withopt     creates with(_w){  code using an options block. (reqd for precall)
     *   {Boolean} precall     wraps 1 async call into precallstore. call with _w._pc = 1 to precall, second time to execute.
     *   {Boolean} alwayscb    always call callback function, even if not async
     *   {Boolean} nostring    even generate code for a simple string
     *   {Number} xpathmode    default type of root level xpath in code mode
     *      Possible values:
     *      0: value
     *      1: value with createnode
     *      2: node
     *      3: node with createnode
     *      4: nodes
     *      5: xpathobj        returns a {model:model,xpath:xpath} object from xpaths
     *   {Boolean} parsecode   start in codemode. if 0 its textmode.
      *  {Boolean} nostate       dont' use _valst macro on [xpath] in namespaced xml.
     *   {Boolean} liveedit    use the _valed macro for <xml>[xpath]</xml> in namespaced xml.
     *   {Boolean} langedit    use of language items in namespaced xml text.
     *   {Boolean} injectself  injects self:: to suitable xpaths
     *   {Boolean} event       its an event thats being compiled, results in no returnvalue for this function.
     *                         and the first argument is now an 'e' for the event object.
     *   {Boolean} funcglobal  all functions defined in LM are made global

     *
     * @return  {Function} returns a function with extra properties
     *   Properties:
     *   {Number}  type         description
     *      Possible values:
     *      1  Function return type
     *      2  Parsed data is a pure string
     *      3  Function return type, but its a single xpath
     *      4  Function return type, but single propxs
     *   {Array}   xpaths       array of [model,xpath, model,xpath] pairs if model
     *                          or xpath is '#', its dynamic if model is null its a local xpath
     *   {Number}  models        number of models
     *   {Array}   props        description
     *   {Number}  asyncs       description
     *   {String]  str            optional, returned with type 2
     */
    var cache = {},
        emptyCfg = {};
    this.resetCache = function(){
        cache = {};
    };
    var lmcache_rx = /^\s*~~(c\d+)~~/;
    this.compile = function(istr, cfg) {
        if (!cfg)
            cfg = emptyCfg;
        if (istr == null || !istr.length) {
            return (cfg.nostring || cfg.event)?function(){return istr}:{
                type: 2,
                str: istr
            };
        }
        // lets see if we need to fetch precompiled cachemarker
        var c, f, is_single_prop;
        if (istr.charAt(0)=="~" && (c=istr.match(lmcache_rx))){
            if (c=apf.lm_exec[c[1]]) return c;
            alert("ERROR, undefined live markup cache marker found:"+istr);
            return {type:2,str:istr};
        }

        var key = (cfg.xpathmode | (cfg.withopt && 0x10) | (cfg.precall && 0x20)
                | (cfg.alwayscb && 0x40) | (cfg.nostring && 0x80)  | (cfg.parsecode && 0x100)
                | (cfg.nostate && 0x200) | (cfg.liveedit && 0x400)| (cfg.langedit && 0x800)
                | (cfg.injectself && 0x1000) | (cfg.event && 0x2000) | (cfg.funcglobal && 0x4000)) + istr;

        if (c = cache[key])
            return c;


        c_injectself = cfg.injectself,  c_xpathmode = cfg.xpathmode||0,
        c_statexpath = cfg.nostate ? 0 : 6, c_elemxpath = 0;
        c_export = cfg.funcglobal?"self":(cfg.withopt?"_w":null);
        c_process_async = !cfg.event;

        xpath_macro.edit = cfg.liveedit ? "_argwrap(_n," : "_argwrap(_n,";//"_val(_n,";
        macro_o.edit = cfg.liveedit ? macro_o._editlm : macro_o._editnormal;

        xpath_lut_node = cfg.langedit ? xpath_lut_node_langedit : xpath_lut_node_normal;

        o_props = {}, o_xpathpairs = [], s = [], o = ["","","",""], str = istr,
        str_len = str.length;
        ol = scope = segment = o.length,
        o_segs = o_xpaths = o_asyncs = o_models = nesting = line_no = last_type = last_line = 0;

        if (cfg.parsecode) {
            parse_mode = 0, sl = 2, s[0] = ol, s[1] = "{{", last_tok = "{",
            cf_mode_output = cfg.event ? "" : (c_xpathmode <= 1 ? cf_str_output : cf_obj_output);
        }
        else
            parse_mode = 2, sl = last_tok = 0, cf_mode_output = cf_str_output;

        if (cfg.nothrow) {
            str.replace(parserx, parser);
        }
        else {
            try {
                str.replace(parserx, parser);
            }
            catch (e) {
                handleError(e, last_line);
                return null;
            }
        }

        if (cfg.parsecode) {
            if (nesting || s[sl - 1].length == 1)
                handleError({
                    t: "Unclosed " + s[sl-1] + " found at end in codemode",
                    p: str_len
                },last_line);
            if (segment!=ol)
                o_segs++
        }else if( (ol==7 || ol==8) && o_segs == 1) {
            is_single_prop = 0;
            for (c in o_props)is_single_prop++;
            if (is_single_prop!=1)is_single_prop = 0;
        }
        if ((!cfg.nostring && !cfg.event)&& (parse_mode == 2 && segment == 4 || ol == 4)) {
            return {
                type: 2,
                str: o.slice(5, -1).join("").replace(/\\n/g, "\n").replace(/\\"/g, '"')
            }; // string only
        }
        if (o_asyncs || cfg.alwayscb) {

            if (cfg.event) { // event
                if (parse_mode == 1)
                    o[3] = "";
                o[ol++] = cc_o_blk_ce;
            }
            else if (c_xpathmode) { // object return
                if (parse_mode == 1) {
                    o[3] = (o[3] != cf_block_o) ? cc_o_blk_o : cc_o_blk_ob,
                    o[ol++] = cc_o_blk_cb;
                }
                else
                    o[3] = cc_o_cb_o, o[ol++] = cc_o_cb_c;
            }
            else { // value return
                if (parse_mode == 1)
                    o[3] = (o[3] != cf_block_o) ? cc_v_blk_o : cc_v_blk_ob,
                    o[ol++] = cc_v_blk_cb;
                else
                    o[3] = cc_v_cb_o, o[ol++] = cc_v_cb_c;
            }

            if (o_asyncs) {
                // for parse_mode == 1 we can squeeze in before [3] and cb close
                // else we put var _r= in 3 and put our ending last and put
                // the cb at the end
                if (parse_mode==1) {
                    if (cfg.precall)
                        o[2] = cc_pc_o, o[ol-1] = cc_pc_c + o[ol-1];
                    else
                        o[2] = cc_async_o, o[ol-1] = cc_async_c + o[ol-1];
                }else{
                    o[ol++] = o[3] + '_r' + o[ol-2];
                    if (cfg.precall)
                        o[2] = cc_pc_o, o[3] = cc_o_blk_o, o[ol-2] = cc_pc_c;
                    else
                        o[2] = cc_async_o, o[3] = cc_o_blk_o, o[ol-2] = cc_async_c;
                }
             }

            if (cfg.withopt)
                o[1] = cc_opt_o, o[ol++] = cc_opt_c;

            o[0] = cfg.event
                ? cc_fe_async_o
                : ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_async_o : cc_f_async_o);
            o[ol++] = cc_f_c;
        }
        else {
            if (cfg.event) { // event
                if (parse_mode == 1)
                    o[3] = "";
            }
            else if (c_xpathmode) { // object return
                if (parse_mode == 1) {
                    o[3] = (o[3] != cf_block_o) ? cc_o_blk_o : cc_o_blk_ob,
                    o[ol++] = cc_o_blk_c;
                }
                else
                    o[3] = cc_o_ret_o, o[ol++] = cc_o_ret_c;
            }
            else { // value return
                if (parse_mode == 1) {
                    o[3] = (o[3] != cf_block_o) ? cc_v_blk_o : cc_v_blk_ob,
                    o[ol++] = cc_v_blk_c;
                }
                else
                    o[3] = cc_v_ret_o, o[ol++] = cc_v_ret_c;
            }
            if (cfg.withopt)
                o[2] = cc_opt_o, o[ol++] = cc_opt_c;

            o[0] = cfg.event
                ? (cfg.withopt ? cc_fe_opt_o : cc_fe_o)
                : (cfg.withopt
                    ? ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_opt_o : cc_f_opt_o)
                    : ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_o : cc_f_o));
            o[ol++] = cc_f_c;
        }
        
        var code = "with(apf.nameserver.lookup.all){\n" + o.join("") + "\n}";

        if (cfg.nothrow) {
            f = apf.lm_exec.compile(code);
        }
        else {
            try {
                f = apf.lm_exec.compile(code);
            }
            catch (e) {
                if (!apf.isIE) {
                    var oErr = window.onerror;
                    window.onerror = function(x,y,line) {
                        window.onerror = oErr;
                        handleError(e, last_line, null, line);
                        return true;
                    }
                    apf.include("", "", null, o.join(""));
                    window.onerror = oErr;
                }
                else {
                    handleError(e,last_line);
                }
                return null;
            }
        }
        f.type = (o_segs == 1 && o_xpaths == 1) ? 3 : (is_single_prop?4:1);
        f.xpaths = o_xpathpairs, f.models = o_models,
        f.props = o_props, f.asyncs = o_asyncs;

        cache[key] = f;
        return f;
    };

    /**
     * description of the method.
     * Remarks:
     * @param {String}   str      the code to compile
     * @param {Object}   options
     *   Properties:
     *   {Boolean} node      tries to return a node, used as a dual-compile with 'normal mode'
     *
     * @return  {Function} returns a function with extra properties
     *   Properties:
     *   {Number}  type         description
     *      Possible values:
     *      1  Function return type
     *      2  Parsed data is a pure string
     *      3  Function return type, but its a single xpath
     *   {Array}   xpaths       array of [model,xpath, model,xpath] pairs if model
     *                          or xpath is '#', its dynamic if model is null its a local xpath
     *   {Number}  models        number of models
     *   {Array}   props        description
     *   {Number}  asyncs       description
     *   {String]  str          optional, returned with type 2
     */

    this.compileMatch = function(strarray, cfg) {
        if (!cfg)
            cfg = emptyCfg;

        o_props = {}, o_xpathpairs = [], o = [cc_f_match_o, cc_m_o], s = [],
        nesting = 0, ol = o.length, xpath_lut_node = xpath_lut_node_normal;

        for (var st, ob, i = 0, j = strarray.length; i < j; i += 2) {
            if (str = strarray[i]) {
                str_len = s.length, c_xpathmode = 2;
                if (i)
                    o[ol++] = cc_m_brk;
                o[ol++] = "";
                s[0] = ob = ol = scope = segment = o.length, cf_mode_output = cf_obj_output;
                line_no = last_type = o_segs = o_xpaths = o_asyncs = parse_mode = last_line = 0;
                sl = 2, s[1] = "{{", last_tok = "{";
                c_injectself = 1;

                if (cfg.nothrow) {
                    str.replace(parserx, parser);
                }
                else {
                    try {
                        str.replace(parserx, parser);
                    }
                    catch (e) {
                        handleError(e,last_line);
                        return null;
                    }
                }

                if (nesting || s[sl - 1].length == 1)
                    handleError({
                        t: "Unclosed " + s[sl - 1] + " found at end in codemode",
                        p: str_len
                    });

                if (o_asyncs)
                    handleError({t:"Asynchronous calls not supported in match/value"});

                if (parse_mode == 1) { // block mode
                    o[ob - 1] = (o[ob - 1] != cf_block_o) ? cf_mode_output : "",
                    o[ol++] = cc_m_m_blk;
                }
                else // value mode
                    o[ob-1] = cc_m_m_value_o, o[ol++] = cc_m_m_value_c;
            }
            if (str = strarray[i + 1]) {
                str_len = s.length;
                if (!strarray[i] && i)
                    o[ol++] = cc_m_brk;
                o[ol++] = "";
                ob = ol = scope = segment = o.length, cf_mode_output = cf_str_output;
                c_xpathmode = c_injectself = last_tok = sl = line_no = o_segs = o_xpaths =
                last_type = o_asyncs = last_line = 0;
                if (cfg.node)
                    c_xpathmode = 2;
                parse_mode = 2, c_injectself = 0;

                if (cfg.nothrow) {
                    str.replace(parserx, parser);
                }
                else {
                    try {
                        str.replace(parserx, parser);
                    }
                    catch (e) {
                        handleError(e,last_line);
                        return null;
                    }
                }

                if (o_asyncs)
                    handleError({t:"Asynchronous calls not supported in match/value"});

                if (cfg.node) {
                    if (parse_mode == 2 && segment == ob || ol == ob)
                        o[ob-1] = cc_m_n_string;
                    else
                        o[ob-1] = cc_m_n_o, o[ol++] = cc_m_n_c;
                }else{
                    if (parse_mode == 2 && segment == ob || ol == ob)
                        o[ob-1] = cc_m_v_string;
                    else
                        o[ob-1] = cc_m_v_o, o[ol++] = cc_m_v_c;
                }

                if (strarray[i])
                    o[ol++] = cc_m_c;
                else
                    break;
            }
            else {
                if (!strarray[i])
                    handleError({t:"Both match and value are empty"});

                if (cfg.node)
                    o[ol++] = cc_m_n_ret;
                else
                    o[ol++] = cc_m_v_ret;

                c_xpathmode = 2;

                o[ol++] = cc_m_c;
            }
        }
        o[ol++] = cc_f_c;

        var f;
        if (cfg.nothrow) {
            f = apf.lm_exec.compile(o.join(""));
        }
        else {
            try{
                f = apf.lm_exec.compile(o.join(""));
            }
            catch (e) {
                handleError(e,last_line);
                return null;
            }
        }

        f.type = 1, f.xpaths = o_xpathpairs,
        f.props = o_props, f.asyncs = o_asyncs;
        return f;
    };

    this.setWarnLevel = function(lvl) {
        apf.lm_exec.setWarnLevel(lvl);
    };

    this.parseExpression = function(istr, cfg) {
        if (!cfg)
            cfg = emptyCfg;

        o_props = {}, o_xpathpairs = [], o = [], s = [],
        nesting = 0, xpath_lut_node = xpath_lut_node_normal;
        str = istr, str_len = str.length;
        ob = ol = scope = segment = o.length, cf_mode_output = cf_str_output;
        c_xpathmode = c_injectself = last_tok = sl = line_no = o_segs = o_xpaths =
        last_type = o_asyncs = last_line = 0;
        parse_mode = 2;

        if (cfg.nothrow) {
            str.replace(parserx, parser);
        }
        else {
            try {
                str.replace(parserx, parser);
            }
            catch (e) {
                handleError(e,last_line);
                return null;
            }
        }
        return o.join('');
    }


})();

// apf lm_exec makes sure there is no scope pollution for eval'ed live markup.
apf.lm_exec = new (function(){
    
    var wlvl = 1; // 0: no warnings 1: language/models missing, 2:nodes missing, 3:all failed xpaths

    //warning functions
    this.setWarnLevel = function(lvl) {
        wlvl = lvl;
    };

    function wxpath(x, t) {
        apf.console.warn("Live Markup warning in " + t + ", no results for xpath: '" + x + "'");
    }

    function wnode(x, t) {
        apf.console.warn("Live Markup warning in " + t + ", xpath on null node: '" + x + "'");
    }

    function wmodel(m, x, t) {
        apf.console.log("Live Markup warning in " + t + ", xpath on empty model: '" + m + "' xpath: '" + x + "'");
    }

    function wlang(x, t) {
        apf.console.log("Live Markup warning in " + t + ", language symbol not found: '" + x + "'");
    }

    // xml parse function used by all livemarkup objects
    function xmlParse(str) {
        var n = apf.getXmlDom("<_apflmlist_>" + str + "</_apflmlist_>");
        if (!n || !(n = n.documentElement))
            return null;
        return (n.firstChild == n.lastChild) ? n.firstChild : n;
    }

    // value of node by xpath
    function __val(n, x) {
        if (!n)
            return ("")
        return apf.escapeXML((n = (!n.nodeType && n || (n = n.selectSingleNode(x)) //!= 1
          && (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
          && n.nodeValue || (""));
    }

    var __valattrrx = /(["'])/g;
    function __valattrrp(m,a) {
        return m=='"'?"&quot;":"&apos;";
    }
    function __valattr(n, x) {
        if (!n)
            return ("")
        return apf.escapeXML((n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x))
          && (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
          &&  n.nodeValue.replace(__valattrrx,__valattrrp) || (""));
    }


    // value of model node by xpath
    function __valm(m, x) {
        var n;
        if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
          || ((n = apf.nameserver.lookup.model[m]) && n.data)))
          || (m.$isModel ? m.data : (m.charAt ? 0 : m))))
            return ("");
        return (n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x))
          && (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
          && n.nodeValue || ("");
    }

    function __nod(n, x){           // node by xpath
        return n ? n.selectSingleNode(x) : (null);
    }

    function _nods(n, x){           // array of nodes by xpath
        return n ? n.selectNodes(x) : ([]);
    }

    function __nodm(m, x){          // node of model by xpath
        var n;
        if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
          || ((n = apf.nameserver.lookup.model[m]) && n.data)))
          || (m.$isModel ? m.data : (m.charAt ? 0 : m))))
            return (null);

        return n.selectSingleNode(x);
    }

    function _nodsm(m, x){          // array of nodes from model by xpath
        var n;
        if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
          || ((n = apf.nameserver.lookup.model[m]) && n.data)))
          || (m.$isModel ? m.data : (m.charAt ? 0 : m))))
            return ([]);

        return n.selectNodes(x);
    }

    function __cnt(n, x){        // count nodes by xpath
        return n ? n.selectNodes(x).length:(0);
    }

    function __cntm(m, x){      // count nodes from model by xpath
        var n;
        if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
          || ((n = apf.nameserver.lookup.model[m]) && n.data)))
          || (m.$isModel ? m.data : (m.charAt ? 0 : m))))
            return (0);

        return n.selectNodes(x).length;
    }

    function _xpt(n, x){        // return the query wrapped in an object
        return {
            xpath: x,
            toString: function(){
                return "LM Xpath object: " + this.x
            }
        };
    }

    function _xptm(m, x){       // return the query with model wrapped in an object
        if (m && !m.$isModel) {
            var node = m;
            m = apf.xmldb.findModel(m);
            x = apf.xmlToXpath(node, m.data) + "/" + x;
        }

        return {
            model: m,
            xpath: x,
            toString: function(){
                return "LM Xpath object with model: " + this.x
            }
        };
    }

    //----- the following functions are combined model and normal mode ------

    function _xml(n, m, x){     // serialize node by xpath via .xml
        if (n) x = m;
        else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
            ((n = apf.nameserver.lookup.model[m]) && n.data))) ||
        (m.$isModel?m.data:(m.charAt?0:m))))
            return ("");

        return (n && (n = n.selectSingleNode(x))) && n.xml ||
        ("");
    }

    function _xmls(n, m, x){    // serialize nodes by xpath with .xml concatenated
        if (n) x = m;
        else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
            ((n = apf.nameserver.lookup.model[m]) && n.data))) ||
        (m.$isModel?m.data:(m.charAt?0:m))))
            return ("");
        for (var i = 0,j = ((n=n.selectNodes(x))).length,o = [];i<j;i++)
            o[i] = n[i].xml;
        return o.join("");
    }

    function _valcr(n, cr, m, x){ // value with a create flag
        if (n) x = m;
        else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
            ((n = apf.nameserver.lookup.model[m]) && n.data))) ||
        (m.$isModel?m.data:(m.charAt?0:m))))
            return ("");

        if (cr) {
            apf.createNodeFromXpath( ni, x );
        }else
        if ( n = ni.selectSingleNode(x) ){
            return (n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x)) &&
                (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n))) && n.nodeValue || ""
        }
        return ("");
    }

    function _nodcr(n, cr, m, x){ // node with create flag
        if (n) x = m;
        else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
            ((n = apf.nameserver.lookup.model[m]) && n.data))) ||
        (m.$isModel?m.data:(m.charAt?0:m))))
            return (null);
        return n.selectSingleNode(x) || (cr && apf.createNodeFromXpath( n, x ));
    }

    function _valst(n, x){      // a value with state holding
        var m = apf.xmldb.findModel(n);
        if (!m)
            return ("");
        return "[" + m.id + "::" + apf.xmlToXpath(n, m.data, true) + (!x || x == "." ? "" : "/" + x) + "]";
    }

    function _asn(o, p, v){     // assign propert
        if (!o || typeof(o)!="object")
            throw new Error(apf.formatErrorString(0,0,"LM Property Assign",
                "Cannot assign property on non object, property:"+p));

        if (o.setAttribute)
            o.setAttribute(p,v);
        else
            o[p] = v;
        return v;
    }

    function _add(o, p, v){     // += property
        return _asn(o,p,o && o[p]+v);
    }

    function _sub(o, p, v){     // -= propery
        return _asn(o,p,o && o[p]-v);
    }

    function _div(o, p, v){     // /= property
        return _asn(o,p,o && o[p]/v);
    }

    function _mul(o, p, v){     // *= property
        return _asn(o,p,o && o[p]*v);
    }

    // macro implementations
    function _local(n){         // local(x) for local n
        // check what n is.. if string parse
        if (n && n.charAt && n.charAt(0)=="<")
            return apf.getXmlDom(n).documentElement;
        
        return n;
    }

    function _tagName(n1, n2){  // tagname macro
        return (n2 && n2.tagName) || (n1 && n1.tagName);
    }

    function _localName(n1, n2){    // localname macro
        return (n2 && n2[apf.TAGNAME]) || (n1 && n1[apf.TAGNAME]);
    }

    function _nodeValue(n,n2){      // value of a node, or localnode.
        if (n2) n = n2;
        return (n = (n.nodeType != 1 && n ||
            (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n))) && n.nodeValue || ""
    }

    // Language processing


    function __ret(r){          // return function, translates $[lang] things in data

        return r;
    }

    function __lng(x,x2){           // the language macro

        return "$["+x+"]";

    }

    function _lnged(x,x2){          // editable language macro

        return "$["+x+"]";

    }

    function _(n, m, x){   // wrap a value with editable div
        return '<span class="liveEdit" xpath="' + (n
            ? (m.substr(0,1) != "/"
                ? apf.xmlToXpath(n, null, false)
                : "") + "/" + m
            : "") + '">' + ((n?__val(n,m):__valm(m,x)) || "&#32;") + '</span>';
    }

//    function _edit(n, opts) {
//        return '<span class="liveEdit" xpath="' + (apf.xmlToXpath(n, null, false)  '">' + ((n?__val(n,m):__valm(m,x)) || "&nbsp;") + '</span>';
//    }

    function _argwrap(n,x) {
        return [n,x];
    }

    function _argwrapm(m,x) {
        return [0,m,x];
    }

    function _valedx(editMode, args, opt){   // wrap a value with editable div
        args[3] = opt;
        args[4] = editMode;
        return _valed.apply(this, args);
    }

    function _valed(n, m, x, options, editMode){   // wrap a value with editable div
        var res = (n?__val(n,m):__valm(m,x));

        if (options && options.multiline && options.editor != "richtext")
            res = res.replace(/\n/g, "<br />");

        if (editMode !== false) {
            var value = res || options && options.initial || "&#32;";
            if (!options || !options.richtext)
                value = apf.htmlentities(value);
            if (options && options.multiline)
                value = value
                    .replace(/&lt;br ?\/?&gt;/g, "<br />")
                    .replace(/&lt;(\/?div)&gt;/g, "<$1>");

            return '<div'
              + ' onmousedown="apf.LiveEdit.mousedown(this, event)" class="liveEdit' + (options && options.multiline ? ' liveeditMultiline' : '') + (!res && options && options.initial ? ' liveEditInitial' : '') + '" xpath="' + (n
                ? ((m.substr(0,1) != "/"
                    ? apf.xmlToXpath(n, null, false)
                    : "") + "/" + m).replace(/([\[\{\}\]])/g, "\\$1")
                : (self[m]
                    ? (m + ".queryNode('" + x.replace(/'/g, "\\'") + "')").replace(/([\[\{\}\]])/g, "\\$1")
                    : "")) + '"'
              + (options
                ? ' options="' + JSON.stringify(options).escapeHTML()
                                  .replace(/"/g, "&quot;")
                                  .replace(/([\[\{\}\]])/g, "\\$1") + '"'
                    + (options.editor ? ' editor="' + options.editor + '"' : "")
                : "") + '>' + value
              + '</div>';
        }
        else {
            return res;
        }
    }

    var selfrx = /(^|\|)(?!\@|text\(\)|\.\.|[\w\-\:]+?\:\:)/g; // inject self regexp

    function _injself(s){           // self inject helper func
        return s.charAt?s.replace(selfrx, "$1self::"):s;
    }

    apf.$lmx = null;

    function _async(_n,_c,_a,_w,_f,_this,obj,func,args){ // Async handling
        var i = _a.i, v;

        if (!_a.ret)_a.ret = [];

        if (_a[i])
            return _a.ret[i];

        _a[i] = true;   // flag this ID so args dont get computed again

        if (!obj.exec)
            return  _a.ret[i]=(func)?obj[func].apply(obj,args):obj.apply(obj,args);

        var cb = function(data, state, extra) {
            if (_w)
                delete _w._pc;

            if (state != apf.SUCCESS) {
                _c(null, state, extra);
            }
            else{
                apf.$lmx = extra;
                _a.ret[i] = data;

                if (_w)
                    _f.call(_this,_n,_c,_w,_a);
                else
                    _f.call(_this,_n,_c,_a);
            }
        };

        if (_w && _w._pc) {
            _w._pc = {
                obj: obj,
                func: func,
                args: args,
                message: obj.createMessage && obj.createMessage(func, args),
                _c: _c,
                cb: cb
            };
        }else{
            obj.exec(func, args, cb);
        }
        throw({
            x: 1
        });
    }

    function _precall(_w){ // precall
        var o;
        if (typeof(o = _w._pc) != "object" || !o)
            return;

        o.obj.exec(o.func, o.args, o.cb, {message: o.message});

        throw({x:1});
    }

    var _clut = apf.color?apf.color.colorshex:{}, _cparse = /^(rgb|hsv|hsb)\(\s+(\d+)\s+,\s+(\d+)\s+,\s+(\d+)\)/

    function sort(set, xpath, options) {
        var s = new apf.Sort();
        options = options || {};
        if (!xpath.charAt)xpath = "";
        if (xpath.charAt(0)=='@'){
            xpath = xpath.slice(1);
            options.getValue = function(n) {
                return n.getAttribute(xpath);
            }
        }else{
            options.getValue = function(n) {
                return apf.queryValue(n,xpath);
            }
        }
        s.set(options);
        return s.apply(apf.getArrayFromNodelist(set));
    }

    function _cthex(c) {
        var t;
        if ((t=typeof(c))=='string'){
            if (c.indexOf('#')==0) {
                if (c.length==7) return parseInt(c.slice(-1),16);
                return parseInt(c.slice(-1),16); // compute repeat
            }
            if (t = _clut[a])return t;
            if (c=c.match(_cparse)){
                if ((t=c[1]) == 'rgb'){
                    return (((t=c[2])<0?0:(t>255?255:parseInt(t)))<<16)+
                            (((t=c[3])<0?0:(t>255?255:parseInt(t)))<<8)+
                            (((t=c[4])<0?0:(t>255?255:parseInt(t))));
                } else { // hsv
                    var h=parseFloat(c[2]),s=parseFloat(c[3]),v=parseFloat(c[4]),
                        i,m=v*(1-s),n=v*(1-s*((i=floor(((h<0?-h:h)%1)*6))?h-i:1-(h-i)));
                    switch(i) {
                      case 6:case 0: return ((v&0xff)<<16)+((n&0xff)<<8)+(m&0xff);
                      case 1: return ((n&0xff)<<16)+((v&0xff)<<8)+(m&0xff);
                      case 2: return ((m&0xff)<<16)+((v&0xff)<<8)+(n&0xff);
                      case 3: return ((m&0xff)<<16)+((n&0xff)<<8)+(v&0xff);
                      case 4: return ((n&0xff)<<16)+((m&0xff)<<8)+(v&0xff);
                      default: case 5: return ((v&0xff)<<16)+((m&0xff)<<8)+(n&0xff);
                    }
                }
            }
        } else if (t=='number')return t;
        return null;
    }

    function lin(f, a, b) {
        var fa = parseFloat(a), fb = parseFloat(b), fm = 1-(f = f<0?0:(f>1?1:f));
        if (fa!=a || fb!=b)
            return (((fa=_cthex(a))&0xff0000)*f+((fb=_cthex(b))&0xff0000)*fm)&0xff0000|
                   ((fa&0xff00)*f+(fb&0xff00)*fm)&0xff00 |
                   ((fa&0xff)*f+(fb&0xff)*fm)&0xff;
        return f*fa+fm*fb;
    }

    var abs = Math.abs, acos = Math.acos, asin = Math.asin,
       atan = Math.atan, atan2 = Math.atan2, ceil = Math.ceil,
       cos = Math.cos, exp = Math.exp, floor = Math.floor,
       log = Math.log, max = Math.max, min = Math.min,
       pow = Math.pow, random = Math.random, round = Math.round,
       sin = Math.sin, sqrt = Math.sqrt, tan = Math.tan, linear = lin;

    function tsin(x){ return 0.5*sin(x)+0.5;}
    function tcos(x){ return 0.5*cos(x)+0.5;}
    function usin(x){ return 0.5-0.5*sin(x);}
    function ucos(x){ return 0.5-0.5*cos(x);}
    function snap(a,b){ return round(a/b)*b; }
    function clamp(a,b,c){ return a<b?b:(a>c?c:a); }

    this.compile = function(code) {
        // up-scope much used functions
        var _ret = __ret, _val = __val,_valm = __valm, _nod = __nod,
        _nodm = __nodm, _cnt = __cnt, _cntm = __cntm, _lng = __lng, _valattr = __valattr;

        eval(code);

        return _f;
    }

    this.compileWith = function(code, withs) {
        // up-scope much used functions
        var _ret = __ret, _val = __val,_valm = __valm, _nod = __nod,
        _nodm = __nodm, _cnt = __cnt, _cntm = __cntm, _lng = __lng, _valattr = __valattr;

        eval(code);

        return _f;
    }

    var LMBEGINCACHE;
    /*LIVEMARKUP BEGIN CACHE
    var _ret = __ret, _val = __val,_valm = __valm, _nod = __nod,
    _nodm = __nodm, _cnt = __cnt, _cntm = __cntm, _lng = __lng, _valattr = __valattr;
    this.c342 = function(_n,_a,_w) {
        ..cached LM function..
    }
    this.c342.type = 2;
    this.c342.xpaths = {...};
    this.c342.props = {...};
    this.c723 = function(....){

    }
    // replace
    d.replace(/var_LMBEGINCACHE;[\s\S]*var_LMBEGINCACHE;/,"code");
    _async(_n,_c,_a,_w,_f,this,
    _async(_n,_c,_a,_w,apf.lm_exec.c342,this,
    LIVEMARKUP END CACHE*/
    var LMENDCACHE;
    
})();





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An object that represents a URI, broken down to its parts, according to [RFC3986](http://tools.ietf.org/html/rfc3986).
 *
 * All parts are publicly accessible after parsing, like 'url.port' or 'url.host'.
 * 
 * #### Example
 *
 * ```javascript
 *   var url = new apf.url('http://usr:pwd@www.test.com:81/dir/dir.2/index.htm?q1=0&&test1&test2=value#top');
 *   alert(url.port); //will show '81'
 *   alert(url.host); //will show 'www.test.com'
 *   alert(url.isSameLocation()) // will show 'true' when the browser is surfing on the www.test.com domain
 * ```
 *
 * @class apf.url
 * @parser
 * @default_private
 *
 * @author      Mike de Boer
 * @version     %I%, %G%
 * @since       1.0
 */
apf.url = function(str) {
    var base;
    var location = (window.location && window.location.toString()) || "";
    if (str.indexOf(":") == -1 && (base = location).indexOf(":") != -1) {
        base = new apf.url(base);
        str = apf.getAbsolutePath(base.protocol + "://" + base.host 
            + (base.directory.charAt(0) == "/" ? "" : "/")
            + (base.directory.charAt(base.directory.length - 1) == "/"
                 ? base.directory
                 : base.directory + '/'), str).replace(/\/\/\/\//, "///");
    }
    var o = apf.url.options,
    m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
    i = 14;
    this.uri = str.toString(); //copy string

    while (i--)
        this[o.key[i]] = m[i] || "";

    this[o.q.name] = {};
    var _self = this;
    this[o.key[12]].replace(o.q.parser, function($0, $1, $2) {
        if ($1)
            _self[o.q.name][$1] = $2;
    });

    /**
     * Checks if the [same origin policy](http://developer.mozilla.org/index.php?title=En/Same_origin_policy_for_JavaScript) is in effect for this URI.
     *
     * @returns {Boolean} `true` if it's the same.
     */
    this.isSameLocation = function(){
        // filter out anchors
        if (this.uri.length && this.uri.charAt(0) == "#")
            return false;
        // totally relative -- ../../someFile.html
        if (!this.protocol && !this.port && !this.host)
            return true;

        // scheme relative with port specified -- foo.com:8080
        if (!this.protocol && this.host && this.port
          && window.location.hostname == this.host
          && window.location.port     == this.port) {
            return true;
        }
        // scheme relative with no-port specified -- foo.com
        if (!this.protocol && this.host && !this.port
          && window.location.hostname == this.host
          && window.location.port     == 80) {
            return true;
        }
        return window.location.protocol == (this.protocol + ":")
            && window.location.hostname == this.host
            && (window.location.port    == this.port || !window.location.port && !this.port);
    }
};

apf.url.options = {
    strictMode: false,
    key: ["source", "protocol", "authority", "userInfo", "user", "password",
          "host", "port", "relative", "path", "directory", "file", "query",
          "anchor"],
    q: {
        name: "queryKey",
        parser: /(?:^|&)([^&=]*)=?([^&]*)/g
    },
    parser: {
        strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
        loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
    }
};







/**
 * @private
 */
apf.runXpath = function(){

/**
 *    Workaround for the lack of having an XPath parser on safari.
 *    It works on Safari's document and XMLDocument object.
 *
 *    It doesn't support the full XPath spec, but just enought for
 *    the skinning engine which needs XPath on the HTML document.
 *
 *    Supports:
 *    - Compilation of xpath statements
 *    - Caching of XPath statements
 *
 * @parser
 * @private
 */
apf.XPath = {
    cache: {},

    getSelf: function(htmlNode, tagName, info, count, num, sResult) {
        var numfound = 0, result = null, data = info[count];

        if (data)
            data[0](htmlNode, data[1], info, count + 1, numfound++ , sResult);
        else
            sResult.push(htmlNode);
    },

    getChildNode: function(htmlNode, tagName, info, count, num, sResult) {
        var numfound = 0, result = null, data = info[count];

        var nodes = htmlNode.childNodes;
        if (!nodes) return; //Weird bug in Safari
        for (var i = 0; i < nodes.length; i++) {
            //if (nodes[i].nodeType != 1)
                //continue;

            if (tagName && (tagName != nodes[i].tagName) && (nodes[i].style
              ? nodes[i].tagName.toLowerCase()
              : nodes[i].tagName) != tagName)
                continue;// || numsearch && ++numfound != numsearch
            
            htmlNode = nodes[i];

            if (data)
                data[0](nodes[i], data[1], info, count + 1, numfound++ , sResult);
            else
                sResult.push(nodes[i]);
        }

        //commented out :  && (!numsearch || numsearch == numfound)
    },

    doQuery: function(htmlNode, qData, info, count, num, sResult) {
        var result = null, data = info[count];
        var query = qData[0];
        var returnResult = qData[1];
        try {
            var qResult = eval(query);
        }catch(e) {
            apf.console.error(e.name + " " + e.type + ":" + apf.XPath.lastExpr + "\n\n" + query);
            //throw new Error(e.name + " " + e.type + ":" + apf.XPath.lastExpr + "\n\n" + query);
            return;
        }

        if (returnResult)
            return sResult.push(qResult);
        if (!qResult || qResult.dataType == apf.ARRAY && !qResult.length) 
            return;

        if (data)
            data[0](htmlNode, data[1], info, count + 1, 0, sResult);
        else
            sResult.push(htmlNode);
    },

    getTextNode: function(htmlNode, empty, info, count, num, sResult) {
        var data = info[count],
            nodes = htmlNode.childNodes;

        for (var i = 0; i < nodes.length; i++) {
            if (nodes[i].nodeType != 3 && nodes[i].nodeType != 4)
                continue;

            if (data)
                data[0](nodes[i], data[1], info, count + 1, i, sResult);
            else
                sResult.push(nodes[i]);
        }
    },

    getAnyNode: function(htmlNode, empty, info, count, num, sResult) {
        var data = info[count],
            nodes = htmlNode.getElementsByTagName("*");//childNodes;

        for (var i = 0; i < nodes.length; i++) {
            if (data)
                data[0](nodes[i], data[1], info, count + 1, i, sResult);
            else
                sResult.push(nodes[i]);
        }
    },

    getAttributeNode: function(htmlNode, attrName, info, count, num, sResult) {
        if (!htmlNode || htmlNode.nodeType != 1) return;

        if (attrName == "*") {
            var nodes = htmlNode.attributes;
            for (var i = 0; i < nodes.length; i++) {
                arguments.callee.call(this, htmlNode, nodes[i].nodeName, info,
                    count, i, sResult);
            }
            return;
        }

        var data = info[count],
            value = htmlNode.getAttributeNode(attrName);//htmlNode.attributes[attrName];//

        if (data)
            data[0](value, data[1], info, count + 1, 0, sResult);
        else if (value)
            sResult.push(value);
    },

    getAllNodes: function(htmlNode, x, info, count, num, sResult) {
        var data = info[count],
            tagName = x[0],
            inclSelf = x[1],
            prefix = x[2],
            nodes, i, l;

        if (inclSelf && (htmlNode.tagName == tagName || tagName == "*" || tagName == "node()")) {
            if (data)
                data[0](htmlNode, data[1], info, count + 1, 0, sResult);
            else
                sResult.push(htmlNode);
        }

        if (tagName == "node()") {
            tagName = "*";
            prefix = "";
            if (apf.isIE) {
                nodes = htmlNode.getElementsByTagName("*");
            }
            else {
                nodes = [];
                (function recur(x) {
                    for (var n, i = 0; i < x.childNodes.length; i++) {
                        n = x.childNodes[i];
                        if (n.nodeType != 1)
                            continue;
                        nodes.push(n);
                        
                        recur(n);
                    }
                })(htmlNode);
            }
        }
        else {
            nodes = htmlNode.getElementsByTagName((prefix
              && (apf.isGecko || apf.isOpera || htmlNode.nodeFunc) ? prefix + ":" : "") + tagName);
        }

        for (i = 0, l = nodes.length; i < l; i++) {
            if (data)
                data[0](nodes[i], data[1], info, count + 1, i, sResult);
            else
                sResult.push(nodes[i]);
        }
    },

    getAllAncestorNodes: function(htmlNode, x, info, count, num, sResult) {
        var data = info[count],
            tagName = x[0],
            inclSelf = x[1],
            i = 0,
            s = inclSelf ? htmlNode : htmlNode.parentNode;
        while (s && s.nodeType == 1) {
            if (s.tagName == tagName || tagName == "*" || tagName == "node()") {
                if (data)
                    data[0](s, data[1], info, count + 1, ++i, sResult);
                else
                    sResult.push(s);
            }
            s = s.parentNode
        }
    },

    getParentNode: function(htmlNode, empty, info, count, num, sResult) {
        var data = info[count],
            node = htmlNode.parentNode;

        if (data)
            data[0](node, data[1], info, count + 1, 0, sResult);
        else if (node)
            sResult.push(node);
    },

    //precsiblg[3] might not be conform spec
    getPrecedingSibling: function(htmlNode, tagName, info, count, num, sResult) {
        var data = info[count],
            node = htmlNode.previousSibling;

        while (node) {
            if (tagName != "node()" && (node.style
              ? node.tagName.toLowerCase()
              : node.tagName) != tagName) {
                node = node.previousSibling;
                continue;
            }

            if (data)
                data[0](node, data[1], info, count+1, 0, sResult);
            else if (node) {
                sResult.push(node);
                break;
            }
        }
    },

    //flwsiblg[3] might not be conform spec
    getFollowingSibling: function(htmlNode, tagName, info, count, num, sResult) {
        var result = null, data = info[count];

        var node = htmlNode.nextSibling;
        while (node) {
            if (tagName != "node()" && (node.style
              ? node.tagName.toLowerCase()
              : node.tagName) != tagName) {
                node = node.nextSibling;
                continue;
            }

            if (data)
                data[0](node, data[1], info, count+1, 0, sResult);
            else if (node) {
                sResult.push(node);
                break;
            }
        }
    },

    multiXpaths: function(contextNode, list, info, count, num, sResult) {
        for (var i = 0; i < list.length; i++) {
            info = list[i][0];
            var rootNode = (info[3]
                ? contextNode.ownerDocument.documentElement
                : contextNode);//document.body
            info[0](rootNode, info[1], list[i], 1, 0, sResult);
        }

        sResult.makeUnique();
    },

    compile: function(sExpr) {
        var isAbsolute = sExpr.match(/^\//);//[^\/]/

        sExpr = sExpr.replace(/\[(\d+)\]/g, "/##$1")
            .replace(/\|\|(\d+)\|\|\d+/g, "##$1")
            .replace(/\.\|\|\d+/g, ".")
            .replace(/\[([^\]]*)\]/g, function(match, m1) {
                return "/##" + m1.replace(/\|/g, "_@_");
            }); //wrong assumption think of |

        if (sExpr == "/" || sExpr == ".")
            return sExpr;

        //Mark // elements
        //sExpr = sExpr.replace(/\/\//g, "/[]/self::");

        //Check if this is an absolute query
        return this.processXpath(sExpr.replace(/\/\//g, "descendant::"), isAbsolute);
    },

    processXpath: function(sExpr, isAbsolute) {
        var results = [],
            i, l, m, query;
        sExpr = sExpr.replace(/'[^']*'/g, function(m) {
            return m.replace("|", "_@_");
        });

        sExpr = sExpr.split("\|");
        for (i = 0, l = sExpr.length; i < l; i++)
            sExpr[i] = sExpr[i].replace(/_\@\_/g, "|");//replace(/('[^']*)\_\@\_([^']*')/g, "$1|$2");

        if (sExpr.length == 1) {
            sExpr = sExpr[0];
        }
        else {
            for (i = 0, l = sExpr.length; i < l; i++)
                sExpr[i] = this.processXpath(sExpr[i]);
            results.push([this.multiXpaths, sExpr]);
            return results;
        }

        var sections = sExpr.split("/");
        for (i = 0, l = sections.length; i < l; i++) {
            if (sections[i] == "." || sections[i] == "")
                continue;
            else if (sections[i] == "..")
                results.push([this.getParentNode, null]);
            else if (sections[i].match(/^[\w\-_\.]+(?:\:[\w\-_\.]+){0,1}$/))
                results.push([this.getChildNode, sections[i]]);//.toUpperCase()
            else if (sections[i].match(/^\#\#(\d+)$/))
                results.push([this.doQuery, ["num+1 == " + parseInt(RegExp.$1)]]);
            else if (sections[i].match(/^\#\#(.*)$/)) {
                //FIX THIS CODE
                query = RegExp.$1;
                m = [query.match(/\(/g), query.match(/\)/g)];
                if (m[0] || m[1]) {
                    while (!m[0] && m[1] || m[0] && !m[1]
                      || m[0].length != m[1].length) {
                        if (!sections[++i]) break;
                        query += "/" + sections[i];
                        m = [query.match(/\(/g), query.match(/\)/g)];
                    }
                }

                results.push([this.doQuery, [this.compileQuery(query)]]);
            }
            else if (sections[i] == "*")
                results.push([this.getChildNode, null]); //FIX - put in def function
            else if (sections[i].substr(0,2) == "[]")
                results.push([this.getAllNodes, ["*", false]]);//sections[i].substr(2) ||
            else if (sections[i].match(/descendant-or-self::node\(\)$/))
                results.push([this.getAllNodes, ["*", true]]);
            else if (sections[i].match(/descendant-or-self::([^\:]*)(?:\:(.*)){0,1}$/))
                results.push([this.getAllNodes, [RegExp.$2 || RegExp.$1, true, RegExp.$1]]);
            else if (sections[i].match(/descendant::([^\:]*)(?:\:(.*)){0,1}$/))
                results.push([this.getAllNodes, [RegExp.$2 || RegExp.$1, false, RegExp.$1]]);
            else if (sections[i].match(/ancestor-or-self::([^\:]*)(?:\:(.*)){0,1}$/))
                results.push([this.getAllAncestorNodes, [RegExp.$2 || RegExp.$1, true, RegExp.$1]]);
            else if (sections[i].match(/ancestor::([^\:]*)(?:\:(.*)){0,1}$/))
                results.push([this.getAllAncestorNodes, [RegExp.$2 || RegExp.$1, false, RegExp.$1]]);
            else if (sections[i].match(/^\@(.*)$/))
                results.push([this.getAttributeNode, RegExp.$1]);
            else if (sections[i] == "text()")
                results.push([this.getTextNode, null]);
            else if (sections[i] == "node()")
                results.push([this.getChildNode, null]);//FIX - put in def function
            else if (sections[i].match(/following-sibling::(.*)$/))
                results.push([this.getFollowingSibling, RegExp.$1.toLowerCase()]);
            else if (sections[i].match(/preceding-sibling::(.*)$/))
                results.push([this.getPrecedingSibling, RegExp.$1.toLowerCase()]);
            else if (sections[i] == "self::node()")
                results.push([this.getSelf, null]);
            else if (sections[i].match(/self::(.*)$/))
                results.push([this.doQuery, ["apf.XPath.doXpathFunc(htmlNode, 'local-name') == '" + RegExp.$1 + "'"]]);
            else {
                //@todo FIX THIS CODE
                //add some checking here
                query = sections[i];
                m = [query.match(/\(/g), query.match(/\)/g)];
                if (m[0] || m[1]) {
                    while (!m[0] && m[1] || m[0] && !m[1] || m[0].length != m[1].length) {
                        if (!sections[++i]) break;
                        query += "/" + sections[i];
                        m = [query.match(/\(/g), query.match(/\)/g)];
                    }
                }

                results.push([this.doQuery, [this.compileQuery(query), true]])

                //throw new Error("---- APF Error ----\nMessage : Could not match XPath statement: '" + sections[i] + "' in '" + sExpr + "'");
            }
        }

        results[0][3] = isAbsolute;
        return results;
    },

    compileQuery: function(code) {
        return new apf.CodeCompilation(code).compile();
    },

    doXpathFunc: function(contextNode, type, nodelist, arg2, arg3, xmlNode, force) {
        if (!nodelist || nodelist.length == 0)
            nodelist = "";

        if (type == "not")
            return !nodelist;

        if (!force) {
            var arg1, i, l;
            if (typeof nodelist == "object" || nodelist.dataType == apf.ARRAY) {
                if (nodelist && !nodelist.length)
                    nodelist = [nodelist];
                
                var res = false, value;
                for (i = 0, l = nodelist.length; i < l; i++) {
                    xmlNode = nodelist[i];
                    if (!xmlNode || typeof xmlNode == "string"
                      || "position|last|count|local-name|name".indexOf(type) > -1) {
                        value = xmlNode;
                    }
                    else {
                        if (xmlNode.nodeType == 1 && xmlNode.firstChild && xmlNode.firstChild.nodeType != 1)
                            xmlNode = xmlNode.firstChild;
                        value = xmlNode.nodeValue;
                    }
    
                    if (res = arguments.callee.call(this, contextNode, type, value, arg2, arg3, xmlNode, true))
                        return res;
                }
                return res;
            }
            else {
                arg1 = nodelist;
            }
        }
        else {
            arg1 = nodelist;
        }
        
        switch(type) {
            case "position":
                return apf.getChildNumber(contextNode) + 1;
            case "format-number":
                return apf.formatNumber(arg1); //@todo this should actually do something
            case "floor":
                return Math.floor(arg1);
            case "ceiling":
                return Math.ceil(arg1);
            case "starts-with":
                return arg1 ? arg1.substr(0, arg2.length) == arg2 : false;
            case "string-length":
                return arg1 ? arg1.length : 0;
            case "count":
                return arg1 ? arg1.length : 0;
            case "last":
                return arg1 ? arg1[arg1.length-1] : null;
            case "name":
                var c = xmlNode || contextNode;
                return c.nodeName || c.tagName;
            case "local-name":
                var c = xmlNode || contextNode;
                if (c.nodeType != 1) return false;
                return c.localName || (c.tagName || "").split(":").pop();//[apf.TAGNAME]
            case "substring":
                return arg1 && arg2 ? arg1.substring(arg2, arg3 || 0) : "";
            case "contains":
                return arg1 && arg2 ? arg1.indexOf(arg2) > -1 : false;
            case "concat":
                var str = ""
                for (i = 1, l = arguments.length; i < l; i++) {
                    if (typeof arguments[i] == "object") {
                        str += getNodeValue(arguments[i][0]);
                        continue;
                    }
                    str += arguments[i];
                }
                return str;
            case "translate":
                for (i = 0, l = arg2.length; i < l; i++)
                    arg1 = arg1.replace(arg2.substr(i,1), arg3.substr(i,1));
                return arg1;
        }
    },

    selectNodeExtended: function(sExpr, contextNode, match) {
        var sResult = this.selectNodes(sExpr, contextNode);

        if (sResult.length == 0)
            return null;
        if (!match)
            return sResult;

        for (var i = 0, l = sResult.length; i < l; i++) {
            if (String(getNodeValue(sResult[i])) == match)
                return [sResult[i]];
        }

        return null;
    },
    
    getRoot: function(xmlNode) {
        while (xmlNode.parentNode && xmlNode.parentNode.nodeType == 1)
            xmlNode = xmlNode.parentNode;
        
        return xmlNode.parentNode;
    },

    selectNodes: function(sExpr, contextNode) {
        if (!this.cache[sExpr])
            this.cache[sExpr] = this.compile(sExpr);

        
        
        if (typeof this.cache[sExpr] == "string"){
            if (this.cache[sExpr] == ".")
                return [contextNode];
            if (this.cache[sExpr] == "/") {
                return [(contextNode.nodeType == 9
                    ? contextNode.documentElement
                    : this.getRoot(contextNode))];
            }
        }

        if (typeof this.cache[sExpr] == "string" && this.cache[sExpr] == ".")
            return [contextNode];

        var info = this.cache[sExpr][0],
            rootNode = (info[3]
                ? (contextNode.nodeType == 9
                    ? contextNode.documentElement
                    : this.getRoot(contextNode))
                : contextNode),//document.body*/
            sResult = [];

        if (rootNode)
            info[0](rootNode, info[1], this.cache[sExpr], 1, 0, sResult);

        return sResult;
    }
};

function getNodeValue(sResult) {
    if (sResult.nodeType == 1)
        return sResult.firstChild ? sResult.firstChild.nodeValue : "";
    if (sResult.nodeType > 1 || sResult.nodeType < 5)
        return sResult.nodeValue;
    return sResult;
}

/**
 * @constructor
 * @private
 */
apf.CodeCompilation = function(code) {
    this.data = {
        F: [],
        S: [],
        I: [],
        X: []
    };

    this.compile = function(){
        code = code.replace(/ or /g, " || ")
            .replace(/ and /g, " && ")
            .replace(/!=/g, "{}")
            .replace(/=/g, "==")
            .replace(/\{\}/g, "!=");

        // Tokenize
        this.tokenize();

        // Insert
        this.insert();
        
        code = code.replace(/, \)/g, ", htmlNode)");

        return code;
    };

    this.tokenize = function(){
        //Functions
        var data = this.data.F;
        code = code.replace(/(translate|format-number|contains|substring|local-name|last|position|round|starts-with|string|string-length|sum|floor|ceiling|concat|count|not)\s*\(/g,
            function(d, match) {
                return (data.push(match) - 1) + "F_";
            }
        );

        //Strings
        data = this.data.S;
        code = code.replace(/'([^']*)'/g, function(d, match) {
                return (data.push(match) - 1) + "S_";
            })
            .replace(/"([^"]*)"/g, function(d, match) {
                return (data.push(match) - 1) + "S_";
            });

        //Xpath
        data = this.data.X;
        code = code.replace(/(^|\W|\_)([\@\.\/A-Za-z\*][\*\.\@\/\w\:\-]*(?:\(\)){0,1})/g,
            function(d, m1, m2) {
                return m1 + (data.push(m2) - 1) + "X_";
            })
            .replace(/(\.[\.\@\/\w]*)/g, function(d, m1, m2) {
                return (data.push(m1) - 1) + "X_";
            });

        //Ints
        data = this.data.I;
        code = code.replace(/(\d+)(\W)/g, function(d, m1, m2) {
            return (data.push(m1) - 1) + "I_" + m2;
        });
    };

    this.insert = function(){
        var data = this.data;
        code = code.replace(/(\d+)X_\s*==\s*(\d+S_)/g, function(d, nr, str) {
                return "apf.XPath.selectNodeExtended('"
                    +  data.X[nr].replace(/'/g, "\\'") + "', htmlNode, " + str + ")";
            })
            .replace(/(\d+)([FISX])_/g, function(d, nr, type) {
                var value = data[type][nr];

                if (type == "F") {
                    return "apf.XPath.doXpathFunc(htmlNode, '" + value + "', ";
                }
                else if (type == "S") {
                    return "'" + value + "'";
                }
                else if (type == "I") {
                    return value;
                }
                else if (type == "X") {
                    return "apf.XPath.selectNodeExtended('"
                        + value.replace(/'/g, "\\'") + "', htmlNode)";
                }
            })
            .replace(/, \)/g, ")");
    };
};

};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An element containing all the binding rules for the data 
 * bound elements referencing this element.
 * 
 * #### Example
 * 
 * ```xml
 *  <a:model id="mdlList">
 *      <data>
 *          <item date="2009-11-12" deleted="0"></item>
 *          <item date="2009-11-11" deleted="0"></item>
 *      </data>
 *  </a:model>
 *  <a:bindings id="bndFolders" >
 *      <a:caption match="[@date]" />
 *      <a:icon match="[@icon]" />
 *      <a:each match="[item]" sort="[@date]" />
 *  </a:bindings>
 *  <a:list 
 *    id = "list" 
 *    width = "200" 
 *    height = "200" 
 *    model = "mdlList" 
 *    bindings = "bndFolders" />
 * ```
 * 
 * @see apf.smartbinding
 * @baseclass
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 *
 * @default_private
 */
apf.BindingRule = function(struct, tagName) {
    this.$init(tagName || true, apf.NODE_HIDDEN, struct);
};

(function(){
    this.$bindingRule = true;
    
    this.compile = function(prop) {
        return (this["c" + prop] = apf.lm.compile(this[prop], {
            xpathmode: 3,
            injectself: true
        }));
    };
    
    this.$compile = function(prop, options) {
        return (this["c" + prop + "2"] = apf.lm.compile(this[prop], options));
    };

    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        value: 1,
        match: 1
    }, this.$attrExcludePropBind);

    this.$booleanProperties["hasaml"] = true;
    
    this.$propHandlers["value"] = 
    this.$propHandlers["match"] = function(value, prop) {
        delete this["c" + prop];
        
        if (this.$amlLoaded) {
            //Find parent that this rule works on
            var node = this;
            while (node && node.$bindingRule) 
                node = node.parentNode;
            
            if (!node) return;
            
            //Reload parent to propagate change
            apf.queue.add("reload" + node.$uniqueId, function(){
                node.reload();
            });
            
            //Recompile ruleset
            if (node.$bindings.$isCompiled)
                node.$bindings.$compiled = node.$bindings.compile(
                    this.localName != "each" && this.localName);
        }
    };
    
    // *** DOM Handlers *** //
    
    /*this.addEventListener("DOMAttrModified", function(e) {
        
    });*/
    
    this.addEventListener("DOMNodeInserted", function(e) {
        //Find parent that this rule works on
        var node = this;
        while (node.$bindingRule) 
            node = node.parentNode;
        
        //Reload parent to propagate change
        //@todo trigger should be maintained on node itself to prevent dual reload
        if ("expanded|collapsed".indexOf(this.localName) == -1)
            apf.queue.add("reload" + node.$uniqueId, function(){
                node.reload();
            });

        //If this node is added, add to set
        if (e.currentTarget == this) {
            (node.$bindings[this.localName] 
                || (node.$bindings[this.localName] = [])).pushUnique(this);
        }
        //@todo apf3.0 test if proc instr and cdata needs to be serialized
        //Else just update the binding value
        else  if (!this.attributes.getNamedItem("value"))
            this.value = apf.serializeChildren(this);
        //Or do nothing
        else return;

        //Recompile ruleset
        if (node.$bindings.$isCompiled)
            node.$bindings.$compiled = node.$bindings.compile(
                this.localName != "each" && this.localName);
    });
    
    this.addEventListener("DOMNodeRemoved", function(e) {
        if (this.$amlDestroyed)
            return;
        
        //Find parent that this rule works on
        var first, node = this;
        while (node && node.$bindingRule) 
            node = node.parentNode;
       
        if (!node)
            return;
       
        //If this node is removed, remove to set
        if (e.currentTarget == this) {
            if (node.$bindings && node.$bindings[this.localName])
                node.$bindings[this.localName].remove(this);
            else
                return;
        }
        //@todo apf3.0 test if proc instr and cdata needs to be serialized
        //Else just update the binding value
        else  if (!this.attributes.getNamedItem("value") && (first = this.firstChild)) {
            if (first.nodeType == this.NODE_PROCESSING_INSTRUCTION) {
                if (first.target == "lm")
                    this.value = "{" + first.nodeValue + "}";
                else
                    this.value = first.nodeValue;
            }
            else
                this.value = apf.serializeChildren(this).trim();
        }
        //Or do nothing
        else return;

        //Reload parent to propagate change
        if ("expanded|collapsed".indexOf(this.localName) == -1)
            apf.queue.add("reload" + node.$uniqueId, function(){
                if (!node.$amlDestroyed)
                    node.reload();
            });

        //Recompile ruleset
        if (node.$bindings.$isCompiled)
            node.$bindings.$compiled = node.$bindings.compile(
                this.localName != "each" && this.localName);
    });

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        

        var first;
        if (!this.value && this.localName != "each" && (first = this.$aml 
          && this.$aml.firstChild || this.firstChild)) {
            if (first.nodeType == this.NODE_PROCESSING_INSTRUCTION) {
                if (first.target == "lm")
                    this.value = "{" + first.nodeValue + "}";
                else
                    this.value = first.nodeValue;
            }
            else
                this.value = apf.serializeChildren(this.$aml).trim();
        }
        
        //Find the parent this rule works on
        var pNode = this.parentNode;
        while (pNode.$bindingRule)
            pNode = pNode.parentNode;

        //Add the rule to the set
        var bindings = pNode.$bindings || (pNode.$bindings = new apf.ruleList());
        (bindings[this.localName] || (bindings[this.localName] = [])).push(this);
        
        //Compile if necessary
        if (pNode.localName != "bindings" && (this.localName != "each" || !this.childNodes.length)) {
            var ns = this;
            while ((ns = ns.nextSibling) && ns.nodeType != 1);
            
            if (!ns || !ns.$bindingRule) {
                pNode.$cbindings = pNode.$bindings.compile(
                  pNode.$bindings.$isCompiled ? this.localName : null);
                
                pNode.dispatchEvent("bindingsload", {
                    bindings: pNode.$bindings, 
                    compiled: pNode.$cbindings
                });
                pNode.$checkLoadQueue();
            }
        }
    });
}).call(apf.BindingRule.prototype = new apf.AmlElement());

apf.aml.setElement("icon",       apf.BindingRule);
apf.aml.setElement("image",      apf.BindingRule);
apf.aml.setElement("caption",    apf.BindingRule);
apf.aml.setElement("tooltip",    apf.BindingRule);
apf.aml.setElement("css",        apf.BindingRule);
apf.aml.setElement("selectable", apf.BindingRule);
apf.aml.setElement("value",      apf.BindingRule);
apf.aml.setElement("src",        apf.BindingRule);
apf.aml.setElement("collapsed",  apf.BindingRule);
apf.aml.setElement("expanded",  apf.BindingRule);
apf.aml.setElement("empty",      apf.BindingRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */
 


/** 
 * Define some action rules.
 *
 * @class apf.ActionRule
 * @inherits apf.AmlElement
 */
// @todo Doc do all of these.
/*
 * @attribute {String} match
 */
/* 
 * @attribute {String} set
 */
/* 
 * @attribute {String} undo
 */
/* 
 * @attribute {String} lock
 */
// @todo Doc ALL of these
/*
 * @define update
 */
/* 
 * @attribute {String} get 
 */
/* 
 * @attribute {String} parent
 */
/* 
 * @define add
 */
/* 
 * @attribute {Boolean} get 
 */
/*
 * @attribute {Boolean} parent
 */
apf.ActionRule = function(struct, tagName) {
    this.$init(tagName || true, apf.NODE_HIDDEN, struct);
};

(function(){
    this.$actionRule = true;
    
    this.compile = function(prop, options) {
        return (this["c" + prop] = apf.lm.compile(this[prop], 
            options || {xpathmode: 2}));
    }
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        set: 1,
        get: 1,
        undo: 1,
        lock: 1,
        match: 1,
        parent: 1
    }, this.$attrExcludePropBind);

    this.$propHandlers["set"] = 
    this.$propHandlers["get"] = 
    this.$propHandlers["parent"] = 
    this.$propHandlers["match"] = function(value, prop) {
        delete this["c" + prop];
    }

    // *** DOM Handlers *** //

    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget == this) {
            var pNode = this.parentNode;
            if (!pNode.$actions)
                pNode.$actions = new apf.ruleList();
            
            (pNode.$actions[this.localName] 
              || (pNode.$actions[this.localName] = [])).push(this);
        }
        else {
            if (this.attributes.getNamedItem("value"))
                return;
            
             //@todo apf3.0 test if proc instr and cdata needs to be serialized
            this.value = apf.serializeChildren(this);
       }
    });

    this.addEventListener("DOMNodeRemoved", function(e) {
        if (this.$amlDestroyed)
            return;
        
        if (e.currentTarget == this) {
            this.parentNode.$actions[this.localName].remove(this);
        }
        else {
            if (this.attributes.getNamedItem("value"))
                return;
            
             //@todo apf3.0 test if proc instr and cdata needs to be serialized
            this.value = apf.serializeChildren(this);
       }
    });

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (!this.get)
            this.get = apf.serializeChildren(this.$aml).trim();

        var actions = this.parentNode.$actions 
          || (this.parentNode.$actions = new apf.ruleList());
        
        (actions[this.localName] || (actions[this.localName] = [])).push(this);
    });
}).call(apf.ActionRule.prototype = new apf.AmlElement());

apf.aml.setElement("rename", apf.ActionRule);   
apf.aml.setElement("remove", apf.ActionRule);
apf.aml.setElement("add",    apf.ActionRule);
apf.aml.setElement("update", apf.ActionRule);
apf.aml.setElement("copy",   apf.ActionRule);
apf.aml.setElement("move",   apf.ActionRule);
apf.aml.setElement("check",  apf.ActionRule);
apf.aml.setElement("change", apf.ActionRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




/**
 * This element allows you to define some rules containing all the action rules for the data 
 * bound elements referencing this element.
 * 
 * @allowchild {actions}
 *
 * @class apf.actions
 * @inherits apf.AmlElement
 * @define actions
 * @logic
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 *
 * @default_private
 */
apf.actions = function(struct, tagName) {
    this.$init(tagName || "actions", apf.NODE_HIDDEN, struct);
    
    this.$actions = new apf.ruleList();
    this.$amlNodes = {};
};

(function(){
    this.$smartbinding = null;

    this.register = function(amlNode) {
        if (amlNode.localName == "smartbinding") {
            this.$smartbinding = amlNode;
            this.$smartbinding.add(this); //Assuming only at init
            return;
        }
        
        this.$amlNodes[amlNode.$uniqueId] = amlNode;
        amlNode.$actions = this.$actions;
        amlNode.$actionsElement = this;
        amlNode.dispatchEvent("actionsload", {bindings: this});
    }

    this.unregister = function(amlNode) {
        //unregister element
        this.$amlNodes[amlNode.$uniqueId] = null;
        delete this.$amlNodes[amlNode.$uniqueId];

        delete amlNode.$actionsElement;
        delete amlNode.$actions;
        amlNode.dispatchEvent("actionsunload", {bindings: this});
    };
    
    // *** DOM Handlers *** //
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var nodes = this.childNodes;
        for (var node, i = 0, l = nodes.length; i < l; i++) {
            if ((node = nodes[i]).nodeType != 1)
                continue;
                
            node.dispatchEvent("DOMNodeInsertedIntoDocument");
        }
        
        this.register(this.parentNode);
    });
}).call(apf.actions.prototype = new apf.AmlElement());

apf.aml.setElement("actions", apf.actions);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element the keeps track of all user actions that are triggered in GUI
 * elements. 
 * 
 * This element maintains a stack of actions, and knows how to
 * undo and redo them. It is also aware of how to synchronize the changes to the
 * backend data store.
 * 
 * 
 * #### Remarks
 *
 * With offline support enabled, the actiontracker can
 * serialize both its undo stack and its execution stack such that these can
 * be kept in-between application sessions. This means that a user will be able
 * to close the application and start it at a later date whilst keeping his or
 * her entire undo/redo stack. Furthermore, all changes done whilst being offline
 * will be synchronized to the data store when the application comes online.
 *
 * @class apf.actiontracker
 * @define actiontracker
 * @logic
 *
 * @inherits apf.Class
 * @inherits apf.AmlElement
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 *
 */
/**
 * @event afterchange   Fires after a change to the action stack occurs
 * @param {Object} e The standard event object. It contains the following property:
 *    - action ([[String]]): the name of the action that was executed
 */
/**
 * @event beforechange  Fires before a change to the action stack will occur
 * @cancelable    Prevents the execution of the action.
 * @param {Object} e The standard event object. It contains the following property:
 *   - `action` ([[String]]): The action to be executed
 *   - `args` ([[Array]]): The arguments for the action
 *   - `[xmlActionNode]` ([[XMLNode]]): The rules to synchronize the changes to the server
 *                              for both execution and undo.
 *   - `[amlNode]` ([[apf.AmlNode]]): The GUI element that triggered the action
 *   - `[selNode]` ([[XMLNode]]):        The relevant {@link term.datanode data node} to
 *                              which the action node works on
 *   - `[timestamp]` ([[Number]]): The start of the action that is now executed.
 */
/**
 * @event actionfail Fires when an action fails to be sent to the server.
 * @bubbles
 * @param {Object} e The standard event object. It contains the following property:
 *     - `error` ([[Error]]): The error object that is thrown when the event
 *                                callback doesn't return false.
 *     - `state` ([[Number]]): The state of the call. It can be one of the following values:
 *       - `apf.SUCCESS`:  The request was successfull
 *       - `apf.TIMEOUT`:  The request has timed out.
 *       - `apf.ERROR`:    An error has occurred while making the request.
 *       - `apf.OFFLINE`:  The request was made while the application was offline.
 *     - `userdata` (`Mixed`): Data that the caller wanted to be available in
 *                                the callback of the HTTP request.
 *     - `http` ([[XMLHttpRequest]]): the object that executed the actual HTTP request.
 *     - `url` ([[String]]): The URL that was requested.
 *     - `tpModule` ([[apf.http]]): The teleport module that is making the request.
 *     - `id` ([[Number]]): The id of the request.
 *     - `message` ([[String]]): The error message.
 * @see term.locking
 */
/**
 * @event actionsuccess Fires when an action is successfully sent to the server.
 * @bubbles
 * @param {Object} e The standard event object. It contains the following property:
 *     - `state` ([[Number]]): The state of the call. It can be one of the following values:
 *       - `apf.SUCCESS`:  The request was successfull
 *       - `apf.TIMEOUT`:  The request has timed out.
 *       - `apf.ERROR`:    An error has occurred while making the request.
 *       - `apf.OFFLINE`:  The request was made while the application was offline.
 *     - `userdata` (`Mixed`): Data that the caller wanted to be available in
 *                                the callback of the HTTP request.
 *     - `http` ([[XMLHttpRequest]]): The object that executed the actual HTTP request.
 *     - `url` ([[String]]): The URL that was requested.
 *     - `tpModule` ([[apf.http]]): The teleport module that is making the request.
 *     - `id` ([[Number]]): The id of the request.
 *
 */
apf.actiontracker = function(struct, tagName) {
    this.$init(tagName || "actiontracker", apf.NODE_HIDDEN, struct);

    this.$undostack = [];
    this.$redostack = [];
    this.$execstack = [];
    
};

(function(){
    this.$lastExecStackItem = null;
    this.$paused = false;

    this.realtime = true;
    this.undolength = 0;
    this.redolength = 0;
    

    /**
     * @attribute {Number}  undolength Sets or gets the length of the undo stack.
     *
     */
    /** 
     * @attribute {Number}  redolength Sets or gets the length of the redo stack.
     */
    /** 
     *  @attribute {Number}  length     Sets or gets the length of the undo/redo stack combined.
     *                                  Use this attribute to bind a slider's max
     *                                  attribute to.
     */
    /** 
     *  @attribute {Number}  position   Sets or gets the position within the total length (the same
     *                                  value as [[apf.actiontracker.undolength]]). Use this attribute
     *                                  to bind a slider's value attribute to.
     */
    /** 
     *  @attribute {Boolean} realtime   Sets or gets whether changes are immediately sent to
     * the datastore, or held back until [[apf.actiontracker.purge]] is called.
     */
    this.$booleanProperties = {};
    this.$booleanProperties["realtime"] = true;
    this.$supportedProperties = ["realtime", "undolength", "redolength", "alias", "length", "position"];
    this.$handlePropSet = function(prop, value, force) {
        if (this.$booleanProperties[prop])
            value = apf.isTrue(value);

        //Read only properties
        switch (prop) {
            case "undolength":
                this.undolength = this.$undostack.length;
                
                break;
            case "redolength":
                this.redolength = this.$redostack.length;
                break;
            
            
            default:
                this[prop] = value;
        }
    };

    /*this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (this.parentNode)
            this.parentNode.$at = this;
    });*/

    

    /**
     * Adds a new action handler which can be used by any actiontracker.
     * @param {String} action Specifies the name of the action
     * @param {Function} func Specifies the function that is executed when
     *                        executing or undoing the action.
     */
    this.define = function(action, func) {
        apf.actiontracker.actions[action] = func;
    };

    /**
     * Searches for the action tracker that functions as a parent for this one.
     * @return {apf.actiontracker} Returns the parent action tracker
     */
    this.getParent = function(){
        return this.parentNode && this.parentNode.getActionTracker
            ? this.parentNode.getActionTracker(true)
            : (apf.window.$at != this ? apf.window.$at : null);
    };

    this.getDone = function(time) {
        if (typeof time != "number")
            return [];
        for (var o, i = this.$undostack.length; i >= 0; --i) {
            if (!(o = this.$undostack[i]) || !o.timestamp) continue;
            if (o.timestamp >= time)
                return this.$undostack.slice(i);
        }
        return [];
    };

    this.getUndone = function(time) {
        if (typeof time != "number")
            return [];
        for (var o, i = 0, l = this.$redostack.length; i < l; ++i) {
            if (!(o = this.$redostack[i]) || !o.timestamp) continue;
            if (o.timestamp <= time)
                return this.$redostack.slice(0, i + 1);
        }
        return [];
    };

    /**
     * Executes an action, which later can be undone and which the execution
     * can be synchronized to the data store.
     * @param {Object} options The details of the execution. It contains the following properties:
     *   Properties:
     *   - `action` ([[String]]): The action to be executed
     *   - `args` ([[Array]]): The arguments for the action
     *   - `[xmlActionNode]` ([[XMLNode]]): The rules to synchronize the changes to the
     *                              server for both execution and undo.
     *   - `[amlNode]` ([[apf.AmlNode]]): The GUI element that triggered the action
     *   - `[selNode]` ([[XMLNode]]):      The relevant {@link term.datanode data node}
     *                              to which the action node works on
     *   - `[timestamp]` ([[Number]]): The start of the action that is now executed.
     *   - `[annotator]` ([[String]]): The name or identifier of the entity that is
     *                              responsible for the action
     */
    this.execute = function(options) {
        if (this.dispatchEvent("beforechange", options) === false)
            return false;

        //Execute action
        var UndoObj = new apf.UndoData(options, this);
        if (options.action && !options.transaction)
            apf.actiontracker.actions[options.action](UndoObj, false, this);

        if (!this.$paused) {
            //Add action to stack
            UndoObj.id = this.$undostack.push(UndoObj) - 1;
        }

        this.setProperty("undolength", this.$undostack.length);

        

        //Respond
        if (UndoObj.multiple)
            this.$addToQueue(UndoObj.multiple, false, true);
        else
            this.$addToQueue(UndoObj, false);

        //Reset Redo Stack
        // @todo for rdb refactor we have to deal with collision handling within the at
        if (!options.rdb) {
            this.$redostack.length = 0;
            this.setProperty("redolength", this.$redostack.length);

            
        }

        this.dispatchEvent("afterchange", {
            action: "do"
        })

        //return stack id of action
        return UndoObj;
    };
    
    this.pauseTracking = function() {
        this.$paused = true;
    };
    
    this.resumeTracking = function() {
        this.$paused = false;
    };
    
    this.isTracking = function() {
        return !this.$paused;
    };

    //deprecated??
    /*this.$addActionGroup = function(done, rpc) {
        var UndoObj = new apf.UndoData("group", null, [
            //@todo apf.copyArray is deprecated and no longer exists
            apf.copyArray(done, UndoData), apf.copyArray(rpc, UndoData)
        ]);
        this.$undostack.push(UndoObj);
        this.setProperty("undolength", this.$undostack.length);

        //@todo reset redo here?

        

        this.dispatchEvent("afterchange", {action: "group", done: done});
    };*/

    // @todo I don't really know if this stacking into the parent is still used, for instance for apf.Transaction. please think about it.
    /**
     * Synchronizes all held back changes to the data store.
     * 
     */
    this.purge = function(nogrouping, forcegrouping){//@todo, maybe add noReset argument
        //var parent = this.getParent();

        //@todo Check if this still works together with transactions
        if (true) {//nogrouping && parent
            if (this.$execstack.length) {
                this.$execstack[0].undoObj.saveChange(this.$execstack[0].undo, this);
                this.$lastExecStackItem = this.$execstack[this.$execstack.length - 1];
            }
        }
        else if (parent) {
            /*
                Copy Stacked Actions as a single
                grouped action to parent ActionTracker
            */
            //parent.$addActionGroup(this.$undostack, stackRPC);

            //Reset Stacks
            this.reset();
        }
    };

    

    /**
     * Empties the action stack. After this method is run, running undo
     * or redo will not do anything.
     */
    this.reset = function(){
        this.$undostack.length = this.$redostack.length = 0;
        this.$paused = false;

        this.setProperty("undolength", 0);
        this.setProperty("redolength", 0);
        

        this.dispatchEvent("afterchange", {action: "reset"});
    };

    /**
     * Revert the most recent action on the action stack.
     */
    this.undo = function(id, single, rollback) {
        change.call(this, id, single, true, rollback);
    };

    /**
     * Re-executes the last undone action.
     */
    this.redo = function(id, single, rollback) {
        change.call(this, id, single, false, rollback);
    };

    function change(id, single, undo, rollback) {
        var undoStack = undo ? this.$undostack : this.$redostack, //local vars switch
            redoStack = undo ? this.$redostack : this.$undostack; //local vars switch

        if (!undoStack.length) return;

        if (single) {
            var UndoObj = undoStack[id];
            if (!UndoObj) return;

            
            undoStack.length--;
            redoStack.push(UndoObj); //@todo check: moved from outside if(single)

            

            //Undo Client Side Action
            if (UndoObj.action)
                apf.actiontracker.actions[UndoObj.action](UndoObj, undo, this);

            if (!rollback) {
                if (UndoObj.multiple)
                    this.$addToQueue(UndoObj.multiple, undo, true);
                else
                    this.$addToQueue(UndoObj, undo);
            }

            //Set Changed Value
            this.setProperty("undolength", this.$undostack.length);
            this.setProperty("redolength", this.$redostack.length);
            return UndoObj;
        }

        if (this.dispatchEvent("beforechange") === false)
            return;

        

        //Undo the last X places - where X = id;
        if (id == -1)
            id = undoStack.length;

        if (!id)
            id = 1;

        var i = 0;
        while (i < id && undoStack.length > 0) {
            if (!undoStack[undoStack.length - 1]) {
                undoStack.length--;

                

                this.$undostack = [];
                this.$redostack = [];

                

                return false;
            }
            else {
                change.call(this, undoStack.length - 1, true, undo, rollback);
                i++;
            }
        }

        this.dispatchEvent("afterchange", {
            action: undo ? "undo" : "redo",
            rollback: rollback
        })
    }

    this.$receive = function(data, state, extra, UndoObj, callback) {
        if (state == apf.TIMEOUT
          && extra.tpModule.retryTimeout(extra, state, this) === true)
            return true;

        if (state != apf.SUCCESS) {
            //Tell anyone that wants to hear about our failure :(
            if (this.dispatchEvent("actionfail", apf.extend(extra, {
                  state: state,
                  message: "Could not sent Action RPC request for control "
                          + this.name
                          + "[" + this.localName + "] \n\n"
                          + extra.message,
                  bubbles: true
              })) === false) {

                

                return true; //don't delete the call from the queue
            }

            /*
                Undo the failed action. We're only undoing one item of the stack
                if the developer has told us using the @ignore-fail attribute
                that it's ok, the data will be safe if we undo only this one.

                @todo: Shouldn't the stackUndone be cleared after this... or
                       is it intuitive enough for the user that redo will
                       let the user retry the action??
            */
            if (typeof apf.offline != "undefined" && !apf.offline.reloading)
                this.undo(UndoObj.id, extra.userdata, true);

            if (callback)
                callback(!extra.userdata);

            if (!extra.userdata) {
                /*
                    Clearing the execStack, none of the changes will be send to
                    the server. This seems the best way right now and is related
                    to the todo item above.

                    @todo: Think about adding ignore-fail to settings and
                           actiontracker.
                */
                this.$execstack = [];

                var oError = new Error(apf.formatErrorString(0, this,
                    "Executing action",
                    "Error sending action to the server:\n"
                    + (extra.url ? "Url:" + extra.url + "\n\n" : "")
                    + extra.message));

                //(UndoObj && UndoObj.xmlActionNode || extra.amlNode || apf)
                if (this.dispatchEvent("error", apf.extend({
                    error: oError,
                    state: state,
                    bubbles: true
                }, extra)) === false)
                    return;

                throw oError;
            }
        }
        else {
            //Tell anyone that wants to hear about our success
            this.dispatchEvent("actionsuccess", apf.extend(extra, {
                state: state,
                bubbles: true
            }, extra));

            
            //Send out the RDB message, letting friends know of our change
            UndoObj.processRsbQueue();
            

            if (callback)
                callback();
        }

        this.$queueNext(UndoObj, callback);
    };

    this.$addToQueue = function(UndoObj, undo, isGroup) {
        /*
            Remove item from the execution stack if it's not yet executed
            to keep the stack clean
        */
        //@todo Implement this for isGroup if deemed useful
        if (!isGroup && this.$execstack.length && !UndoObj.state
          && this.$execstack[this.$execstack.length - 1].undoObj == UndoObj) {
            this.$execstack.length--;

            

            
            UndoObj.clearRsbQueue();
            

            return;
        }

        var idx, undoObj, qItem;
        // Add the item to the queue
        if (isGroup) { 
            //@todo currently no offline support for grouped actions
            //@todo Why not just pop()?
            var undoObj, qItem = this.$execstack.shift();
            for (var i = 0; i < UndoObj.length; i++) {
                undoObj = UndoObj[i];
                this.$execstack.unshift({
                    undoObj: (undoObj.tagName
                        ? undoObj
                        : new apf.UndoData(undoObj, this)).preparse(undo, this),
                    undo: undo
                });
            }
            if (qItem)
                this.$execstack.unshift(qItem);

            if (!this.$execstack[0].undoObj.state)
                this.$execstack[0].undoObj.saveChange(this.$execstack[0].undo, this);

            return;
        }

        qItem = {
            undoObj: UndoObj.preparse(undo, this),
            undo: undo

        };
        this.$execstack.push(qItem) - 1;

        

        //The queue was empty, yay! we're gonna exec immediately
        if (this.$execstack.length == 1 && this.realtime)
            UndoObj.saveChange(undo, this);
    };

    this.$queueNext = function(UndoObj, callback) {
        /*
            These thow checks are so important, that they are also executed
            in release mode.
        */
        if (!this.$execstack[0] || this.$execstack[0].undoObj != UndoObj) {
            throw new Error(apf.formatErrorString(0, this, "Executing Next \
                action in queue", "The execution stack was corrupted. This is \
                a fatal error. The application should be restarted. You will \
                lose all your changes. Please contact the administrator."));
        }

        //Reset the state of the undo item
        UndoObj.state = null;

        //Remove the action item from the stack
        var lastItem = this.$execstack.shift();

        

        //Check if there is a new action to execute;
        if (!this.$execstack[0] || lastItem == this.$lastExecStackItem)
            return;

        // @todo you could optimize this process by using multicall, but too much for now

        //Execute action next in queue
        this.$execstack[0].undoObj.saveChange(this.$execstack[0].undo, this, callback);
    };

    
}).call(apf.actiontracker.prototype = new apf.AmlElement());

apf.aml.setElement("actiontracker", apf.actiontracker);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @todo description
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.application = function(){
    this.$init("application", apf.NODE_HIDDEN);
    
    if (!apf.isO3) {    
        this.$int = document.body;
        this.$tabList = []; //Prevents documentElement from being focussed
        this.$focussable = apf.KEYBOARD;
        this.focussable = true;
        this.visible = true;
        this.$isWindowContainer = true;
        this.focus = function(){ this.dispatchEvent("focus"); };
        this.blur = function(){ this.dispatchEvent("blur"); };
    
        
        apf.window.$addFocus(this);
        
    }
};
apf.application.prototype = new apf.AmlElement();
apf.aml.setElement("application", apf.application);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element specifies the settings of the APF application.
 * 
 * @class apf.appsettings
 * @define appsettings
 * @logic
 * @inherits apf.AmlElement
 * @allowchild auth, authentication, offline, printer, defaults
 * 
 */
// @todo describe defaults
/**
 * @attribute {Boolean} debug                   Sets or gets whether the debug screen is shown at startup.
 * 
 */
/**
 * @attribute {String}  name                    Sets or gets the name of the application; used by many different services to uniquely identify the application.
 */
/**
 * @attribute {Boolean} disable-right-click     Sets or gets whether a user can get the browser's contextmenu when the right mouse button is clicked.
 * @see apf.contextmenu
 */
/**
 * @attribute {Boolean} allow-select            Sets or gets whether general text in the application can be selected.
 */
/**
 * @attribute {Boolean} allow-blur              Sets or gets whether its possible to blur an element while not giving the focus to another element. Defaults to `true`.
 */
/**
 * @attribute {Boolean} auto-disable-actions    Sets or gets whether smartbinding actions are by default disabled.
 * @see term.action
 */
/**
 * @attribute {Boolean} auto-disable            Sets or gets whether elements that don't have content loaded are automatically disabled.
 */
/**
 * @attribute {Boolean} disable-f5              Sets or gets whether the F5 key for refreshing is disabled.
 */
/**
 * @attribute {Boolean} auto-hide-loading       Sets or gets whether the load screen defined by the loader element is automatically hidden. Setting this to `false` enables you to control when the loading screen is hidden. 
 * 
 * The following code shows how this can be done:
 *
 * ```javascript
 *  apf.document.getElementsByTagName("a:loader")[0].hide()
 *  //or
 *  loaderId.hide()
 * ```
 */
/**
 * @attribute {Boolean} disable-space           Sets or gets whether the space button default behavior of scrolling the tab is disabled.
 */
/**
 * @attribute {Boolean} disable-backspace       Sets or gets whether the backspace button default behavior of going to the previous history state is disabled.
 */
/**
 * @attribute {String}  default-tab            Sets or gets the name of the default tab if none is specified using the `#`. Defaults to `"home"`.
 * @see apf.history
 */
/**
 * @attribute {Boolean} undokeys                Sets or gets whether the undo and redo keyboard bindings are enabled.
 * @see apf.actiontracker
 */
/**
 * @attribute {String | Boolean} outline         Sets or gets whether an outline of an element is shown while dragging or resizing.
 * @see apf.Interactive
 */
/**
 * @attribute {String | Boolean} drag-outline    Sets or gets whether an outline of an element is shown while dragging.
 * @see apf.Interactive
 */
/**
 * @attribute {String | Boolean} resize-outline  Sets or gets whether an outline of an element is shown while resizing.
 * @see apf.Interactive
 */
/**
 * @attribute {String}  baseurl                 Sets or gets the basepath for any relative url used throughout your application. This included teleport definitions and {@link term.datainstruction data instructions}.
 * 
 */
/**
 * @attribute {String}  loading-message         Sets or gets the global value for the loading message of elements during a loading state.
 * @see apf.DataBinding.loading-message
 */
/**
 * @attribute {String}  offline-message         Sets or gets the global value for the offline message of elements not able to display content while offline.
 * @see apf.DataBinding.offline-message
 */
/**
 * @attribute {String}  empty-message           Sets or gets the global value for the empty message of elements containing no contents.
 * @see apf.DataBinding.empty-message
 */
/**
 * @attribute {String}  model                   Sets or gets the default model for this application.
 * @see apf.model
 */
/**
 * @attribute {String}  realtime                Sets or gets the global value whether bound values are updated realtime. When set to `false`, elements do not update until they lose focus.
 * 
 */
/**
 * @attribute {String}  skinset                 Sets or gets the skin set used by the application.
 * @see apf.Presentation.skinset
 */
/**
 * @attribute {String}  storage                 Sets or gets the storage provider to be used for key/value storage.
 * 
 */
/**
 * @attribute {String}  offline                 Sets or gets the storage provider to be used for offline support.
 * 
 */
/**
 * @attribute {String}  login                   Sets or gets the {@link term.datainstruction data instruction} which logs a user into the application.
 * 
 */
/**
 * @attribute {String}  logout                  Sets or gets the {@link term.datainstruction data instruction} which logs a user out of the application.
 * 
 */
/**
 * @attribute {String}  iepngfix                Sets or gets whether the fix for PNG images with transparency should be applied. Default is `false`.
 */
/**
 * @attribute {String}  iepngfix-elements       Sets or gets a comma-seperated list of CSS identifiers (classes) to which the transparent-PNG fix will be applied.
 */
/**
 * @attribute {Boolean} iphone-fullscreen       Sets or gets whether the application should cover the entire screen of the iPhone. Default is ztruez.
 */
/**
 * @attribute {String}  iphone-statusbar        Sets or gets the style of the statusbar of the iPhone webbrowser. Posssible values: `'default'`, `'black-translucent'` or `'black'`.
 */
/**
 * @attribute {String}  iphone-icon             Sets or gets path pointing to the icon that should be used when this application is put on the iPhone Dashboard.
 */
/**
 * @attribute {Boolean} iphone-icon-is-glossy   Sets or gets whether the icon specified with 'iphone-icon' already is glossy or if the iPhone OS should apply that effect. Default is `false`.
 */
/**
 * @attribute {Boolean} iphone-fixed-viewport   Sets or gets whether the viewport of the application is fixed and whether the zoom should be enabled. Default is `true`.
 */
apf.appsettings = function(struct, tagName) {
    this.$init(tagName || "appsettings", apf.NODE_HIDDEN, struct);
};

(function(){
    this.$parsePrio = "001";
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = {
        language: 1,
        login: 1,
        logout: 1
    };
    
    this.$supportedProperties = ["debug", "name", "baseurl", "resource-path", 
        "disable-right-click", "allow-select", "allow-blur", 
        "auto-disable-actions", "auto-disable", "disable-f5", 
        "auto-hide-loading", "disable-space", "disable-backspace", "undokeys", 
        "initdelay", "default-tab", "query-append", "outline", "drag-outline", 
        "resize-outline", "resize-outline", "iepngfix", "iepngfix-elements", 
        "iphone-fullscreen", "iphone-statusbar", "iphone-icon", 
        "iphone-icon-is-glossy", "iphone-fixed-viewport", "skinset", 
        "language", "storage", "offline", "login"];
    this.$booleanProperties = {
        
        "debug":1,
        "disable-right-click":1,
        "allow-select":1,
        "allow-blur":1,
        "auto-disable-actions":1,
        "auto-disable":1,
        "disable-f5":1,
        "auto-hide-loading":1,
        "disable-space":1,
        "disable-backspace":1,
        "undokeys":1,
        "initdelay":1,
        "outline":1,
        "iepngfix":1,
        "iphone-fullscreen":1,
        "iphone-icon-is-glossy":1, 
        "iphone-fixed-viewport":1
    };
    
    var $setProperty = this.setProperty;
    this.setProperty = function(prop, value, forceOnMe, setAttr, inherited) {
        if (inherited != 2)
            $setProperty.apply(this, arguments);
    }
    
    this.$handlePropSet = function(prop, value, force) {
        if (this.$booleanProperties[prop])
            value = apf.isTrue(value);

        this[prop] = value;

        apf.config.setProperty(prop, value);
    };
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        
    });
}).call(apf.appsettings.prototype = new apf.AmlElement());

apf.aml.setElement("appsettings", apf.appsettings);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @define auth Centralized authentication handling. Not being logged in, after being
 * offline for a while can put the application
 * in a complex undefined state. The auth element makes sure the state is always
 * properly managed. When it gets signalled 'authentication required' it dispatches the
 * appropriate events to display a login box. It can automatically retry logging
 * in to one or more services using in memory stored username/password
 * combinations. It will queue all requests that require authentication until
 * the application is logged in again and will then empty the queue.
 * Example:
 * This example sets up apf.auth with two services that it can log into. 
 * <code>
 *  <a:appsettings>
 *      <a:auth>
 *           <a:service name = "my-backend"
 *                      login = "{comm.login(username, password)}"
 *                      logout = "{comm.logout()}" />
 *           <a:service name = "my-jabber-server"
 *                      login = "{myXmpp.login(username, password, domain)}"
 *                      logout = "{myXmpp.logout()}" />
 *      </a:auth>
 *  </a:appsettings>
 * </code>
 * Example:
 * A login window with different states managed by apf.auth
 * <code>
 *   <a:appsettings>
 *       <a:auth 
 *         login = "{comm.login(username, password)}" 
 *         logout = "{comm.logout()}"
 *         autostart = "false"
 *         window = "winLogin"
 *         fail-state = "stFail"
 *         error-state = "stError"
 *         login-state = "stIdle"
 *         logout-state = "stLoggedOut"
 *         waiting-state = "stLoggingIn" />
 *   </a:appsettings>
 *   <a:teleport>
 *       <a:rpc 
 *         id = "comm" 
 *         protocol = "cgi">
 *           <a:method name="login" url="login.php">
 *               <a:param name="username" />
 *               <a:param name="password" />
 *           </a:method>
 *           <a:method name="logout" url="logout.php" />
 *       </a:rpc>
 *   </a:teleport>
 *  
 *   <a:state-group
 *     loginMsg.visible = "false"
 *     winLogin.disabled = "false">
 *       <a:state id="stFail"
 *         loginMsg.value = "Username or password incorrect"
 *         loginMsg.visible = "true"
 *         winLogin.disabled = "false" />
 *       <a:state id="stError"
 *         loginMsg.value = "An error has occurred. Please check your network."
 *         loginMsg.visible = "true"
 *         winLogin.disabled = "false" />
 *       <a:state id="stLoggingIn"
 *         loginMsg.value = "Please wait whilst logging in..."
 *         loginMsg.visible = "true"
 *         winLogin.disabled = "true"
 *         btnLogout.visible = "false" />
 *       <a:state id="stIdle"
 *         btnLogout.visible = "true" />
 *       <a:state id="stLoggedOut"
 *         btnLogout.visible = "false"
 *         loginMsg.visible = "false"
 *         winLogin.disabled = "false" />
 *  </a:state-group>
 * 
 *  <a:window id="winLogin" visible="true" width="400" height="400">
 *      <a:label>Username</a:label>
 *      <a:textbox type="username" value="TestUser" />
 *  
 *      <a:label>Password</a:label>
 *      <a:textbox type="password" value="open" />
 * 
 *      <a:label id="loginMsg" />
 *      <a:button action="login">Log in</a:button>
 *  </a:window>
 *  <a:button id="btnLogout" visible="false" action="logout">Log out</a:button>
 * </code>
 *
 * @event beforelogin   Fires before the log in request is sent to the service
 *   cancelable:    Prevents the log in from happening
 * @event beforelogout  Fires before the log out request is sent to the service
 *   cancelable:    Prevents the log out from happening
 * @event logincheck    Fires when log in data is received. Login is sometimes very complex, this event is dispatched to allow a custom check if a log in succeeded.
 *   bubbles: yes
 * @param {Object} e
 *     {Object} data     the data received from the log in request
 *     {Number} state    the return code of the log in request
 * @event loginfail     Fires when a log in attempt has failed
 * @event loginsuccess  Fires when a log in attempt succeeded
 * @event logoutcheck   Fires when log out data is received. Login is sometimes very complex, this event is dispatched to allow a custom check if a log out succeeded.
 *   bubbles: yes
 * @param {Object} e
 *     {Object} data     the data received from the log out request
 *     {Number} state    the return code of the log out request
 * @event logoutfail    Fires when a log out attempt has failed
 * @event logoutsuccess Fires when a log out attempt succeeded
 * @event authrequired  Fires when log in credentials are required, either because they are incorrect, or because they are unavailable.
 *   bubbles: yes
 *
 * @inherits apf.Class
 *
 * @attribute {String}  login           the {@link term.datainstruction data instruction} on how to log in to a service.
 * @attribute {String}  logout          the {@link term.datainstruction data instruction} on how to log out of a service.
 * @attribute {Boolean} autostart       whether to fire authrequired at startup. Defaults to true.
 * @attribute {String}  window          the id of the window element that offers a log in form to the user. DEPRECATED.
 * @attribute {String}  authreq-state   the id of the state element which is activated when logging in failed because the credentials where incorrect.
 * @attribute {String}  login-state     the id of the state element which is activated when logging in succeeded.
 * @attribute {String}  waiting-state   the id of the state element which is activated when the user is waiting while the application is logging in.
 * @attribute {String}  fail-state      the id of the state element which is activated when logging in failed because the credentials where incorrect.
 * @attribute {String}  error-state     the id of the state element which is activated when logging in failed because of an error (i.e. network disconnected).
 * @attribute {String}  logout-state    the id of the state element which is activated when the user is logged out.
 * @attribute {String}  model           the id of the model element which gets the data loaded given at login success.
 * @attribute {String}  remember        whether to remember the login credentials after the first successful login attempt. Will only be used i.c.w. RPC
 * @allowchild service
 * @define service  Element specifying a server to log into.
 * @attribute {String} name     the unique identifier of the service
 * @attribute {String} login    the {@link term.datainstruction data instruction} on how to log in to a service
 * @attribute {String} logout   the {@link term.datainstruction data instruction} on how to log out of a service
 * @see apf.appsettings
 *
 * @default_private
 */

apf.auth = function(struct, tagName) {
    this.$init(tagName || "auth", apf.NODE_HIDDEN, struct);

    this.$services = {};
    this.$cache = {};
    this.$queue = [];
    this.$credentials = null;
};

apf.aml.setElement("auth", apf.auth);

(function(){
    this.autostart = true;
    this.authenticated = false;
    this.enablequeue = false;
    
    this.$retry = true;
    this.loggedIn = false;
    this.$needsLogin = false;
    this.$hasHost = false;

    /**
     * Indicates the state of the log in process.
     * Possible values:
     * 0 idle
     * 1 logging in
     * 2 logging out
     */
    this.inProcess = 0;
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        login: 1,
        logout: 1
    }, this.$attrExcludePropBind);
    
    this.$booleanProperties["autostart"] = true;
    this.$booleanProperties["remember"] = true;

    this.$supportedProperties.push("login", "logout", "fail-state", "error-state",
        "login-state", "logout-state", "waiting-state", "window", "autostart",
        "remember", "authenticated", "enablequeue");

    this.$propHandlers["login"] = 
    this.$propHandlers["login-state"] = function(value) {
        this.$services["default"] = value ? this : null;
        this.$needsLogin = value ? true : false;
    };
    
    this.register = function(node) {
        this.$services[node.name] = node;
        this.$needsLogin = true;
    };
    
    this.unregister = function(node) {
        var prop;
        delete this.$services[node.name];
        if (!(prop in this.$services))
            this.$needsLogin = false;
    };
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.inited = true;

        if (this.parentNode && this.parentNode.$setAuth) {
            this.parentNode.$setAuth(this);
            this.$hasHost = true;
        }

        if (this.autostart && !this.$hasHost) {
            var _self = this;
            apf.addEventListener("load", function(){
                apf.addEventListener("login", function(){
                    _self.authRequired();
                    apf.removeEventListener("load", arguments.callee);
                });
            });
        }
    });
    
    this.addEventListener("authrequired", function(){
        if (self[this.window]) {
            this.win = self[this.window];
            if (this.win) {
                this.win.show();
                return false;
            }
        }
        
        if (self[this["authreq-state"]]) {
            this.state = self[this["authreq-state"]];
            if (this.state) {
                this.state.activate();
                return false;
            }
        }
    });

    this.addEventListener("beforelogin", function(){
        if (self[this["waiting-state"]]) {
            this.state = self[this["waiting-state"]];
            if (this.state)
                this.state.activate();
        }
    });

    var knownHttpAuthErrors = {401:1, 403:1}
    function failFunction(e) {
        var st = (e.state == apf.TIMEOUT || !knownHttpAuthErrors[e.status]
            ? self[this["error-state"]]
            : self[this["fail-state"]]) || self[this["fail-state"]]

        if (st) {
            this.state = st;
            if (this.state) {
                this.state.activate();
                return false;
            }
        }
    }
    
    this.addEventListener("loginfail",  failFunction);
    this.addEventListener("logoutfail", failFunction);

    this.addEventListener("logoutsuccess", function(){
        if (self[this["logout-state"]]) {
            this.state = self[this["logout-state"]];
            if (this.state)
                this.state.activate();
        }
    });

    this.addEventListener("loginsuccess", function(e) {
        if (self[this.window]) {
            this.win = self[this.window];
            if (this.win)
                this.win.hide();
        }

        if (self[this["login-state"]]) {
            this.state = self[this["login-state"]];
            if (this.state)
                this.state.activate();
        }

        
        if (e.data && this.model) {
            this.model = apf.nameserver.get("model", this.model);
            if (this.model)
                this.model.load(e.data);
        }
        
    });

    /**
     * Log in to one or more services
     * @param {String}   username   the username portion of the credentials used to log in with
     * @param {String}   password   the password portion of the credentials used to log in with
     * @param {Function} [callback] code to be called when the application succeeds or fails logging in
     * @param {Object}   [options]  extra settings and variables for the login. These variables will be available in the {@link term.datainstruction data instruction} which is called to execute the actual log in.
     *   Properties:
     *   {Array} services   a list of names of services to be logged in to
     *   {String} service   the name of a single service to log in to
     */
    this.logIn = function(username, password, callback, options) {
        if (!options) options = {};

        options.username = username;
        options.password = password;

        if (this.dispatchEvent("beforelogin", options) === false)
            return false;

        this.inProcess = 1; //Logging in

        var pos = 0,
            len = 0,
            _self = this,
            doneCallback = function() {
                if (len != ++pos)
                    return;

                _self.inProcess = 0; //Idle
                _self.loggedIn = true;
                _self.clearQueue();

                if (callback)
                    callback();
            };

        if (this.$hasHost) { // child of Teleport element
            this.$credentials = options;
            callback = this.$hostCallback;
            this.$hostCallback = null;
            len = 1;
            doneCallback();
            this.dispatchEvent("loginsuccess", {
                state: 1,
                data: null,
                bubbles: true,
                username: username,
                password: password
            });
            if (!this.remember)
                this.$credentials = null;
        }
        else {
            if (!options.service) {
                var s = options.$services || this.$services;
                for (var name in s) {
                    len++;
                    this.$do(name, options, "in", null, doneCallback);
                }
            }
            else if (options.service) {
                len = 1;
                this.$do(options.service, options, "in", null, doneCallback);
            }
        }
    };

    this.relogin = function(){
        if (this.dispatchEvent("beforerelogin") === false)
            return false;

        

        //@todo shouldn't I be using inProces here?
        var name, pos = 0, len = 0, _self = this,
            doneCallback = function(){
                if (len != ++pos)
                    return;

                _self.inProcess = 0; //Idle
                _self.loggedIn = true;
                _self.clearQueue();
            };

        for (name in this.$services) {
            if (!this.$cache[name])
                return false;
            len++;
            this.$do(name, this.$cache[name], "in", true, doneCallback);
        }

        return true;
    };

    this.$do = function(service, options, type, isRelogin, callback) {
        var xmlNode = this.$services[service],
            _self = options.userdata = this;

        

        

        //Execute login call
        options.callback = function(data, state, extra) {
            if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries)
                return extra.tpModule.retry(extra.id);

            /*
                Login is sometimes very complex, so this check is
                here to test the data for login information
            */
            var result = _self.dispatchEvent("log" + type + "check",
                apf.extend({
                    state: state,
                    data: data,
                    service: service,
                    bubbles: true
                }, extra)),

                loginFailed = typeof result == "boolean"
                    ? !result
                    : !(state == apf.SUCCESS || type == "out" && extra.status == 401);

            if (loginFailed) {
                _self.inProcess = 0; //Idle

                if (isRelogin) //If we're retrying then we'll step out here
                    return _self.authRequired();

                

                var commError = new Error(apf.formatErrorString(0, null,
                    "Logging " + type, "Error logging in: " + extra.message));

                if (_self.dispatchEvent("log" + type + "fail", apf.extend({
                    error: commError,
                    service: service,
                    state: state,
                    data: data,
                    bubbles: true,
                    username: options.username,
                    password: options.password
                }, extra)) !== false)
                    throw commError; //@todo ouch, too harsh?

                //@todo Call auth required again??
                
                _self.setProperty("authenticated", false);

                return;
            }

            if (type == "in") {
                //If we use retry, cache the login information
                if (!isRelogin && _self.$retry) {
                    var cacheItem = {};
                    for (var prop in options) {
                        if ("object|array".indexOf(typeof options[prop]) == -1)
                            cacheItem[prop] = options[prop];
                    }
                    _self.$cache[service || "default"] = cacheItem;
                }
            }
            else {
                //Remove cached credentials
                if (_self.$cache[service || "default"])
                     _self.$cache[service || "default"] = null;

                //_self.authRequired();
            }

            if (callback)
                callback();

            _self.dispatchEvent("log" + type + "success", apf.extend({}, extra, {
                state: state,
                service: service,
                data: data,
                bubbles: true,
                username: options.username,
                password: options.password
            }));

            
            
            _self.setProperty("authenticated", true);
        };
        apf.saveData(xmlNode.getAttribute("log" + type), options);
    };

    this.clearQueue = function(){
        if (!this.loggedIn) //Queue should only be cleared when we're logged in
            return;

        var queue = this.$queue.slice();
        this.$queue.length = 0;

        for (var i = 0; i < queue.length; i++) {
            var qItem = queue[i];

            //We might be logged out somewhere in this process (think sync)
            if (!this.loggedIn) {
                this.$queue.push(qItem);
                continue;
            }

             //Specialty retry (protocol specific)
            if (qItem.retry)
                qItem.$retry.call(qItem.object);

            //Standard TelePort Module retry
            else if (qItem.id)
                qItem.tpModule.retry(qItem.id);

            
        }

        //The queue might be filled somehow
        if (this.$queue.length)
            this.clearQueue();
    };

    /**
     * Log out of one or more services
     * @param {Function} [callback] code to be called when the application succeeds or fails logging out
     * @param {Object}   [options]  extra settings and variables for the login. These variables will be available out the {@link term.datainstruction data instruction} which is called to execute the actual log out.
     *   Properties:
     *   {Array} services   a list of names of services to be logged out of
     *   {String} service   the name of a single service to log out of
     */
    this.logOut = function(callback, options) {
        if (!options) options = {};

        if (this.dispatchEvent("beforelogout", options) === false)
            return;

        this.loggedIn = false;

        if (!options.service) {
            for (var name in this.$services)
                this.$do(name, options, "out", null, callback);
        }
        else if (options.service)
            this.$do(options.service, options, "out", null, callback);

    };
    
    this.getCredentials = function(service) {
        var cache = this.$cache[service || "default"];
        return !cache ? ["", ""] : [cache.username, cache.password];
    };

    /**
     * Signals services that a log in is required and fires authrequired event
     * @param {Object}   [options]      information on how to reconstruct a failed action, that detected a log in was required. (i.e. When an HTTP call fails with a 401 Auth Required the options object contains information on how to retry the http request)
     * @param {Object}   [forceNoRetry] don't try to log in with stored credentials. 
     */
    this.authRequired = function(options, forceNoRetry) {
        // If we're already logging in return
        if (options && options.userdata == this)
            return;

        // If we're supposed to be logged in we'll try to log in automatically
        if (this.loggedIn && !forceNoRetry && this.$retry && this.relogin()) {
            var result = false;
        }
        else if (this.inProcess != 1) { //If we're not already logging in
            if (this.$hasHost && typeof options == "function") { //inside Teleport element
                if (this.$credentials) 
                    return options();
                this.$hostCallback = options;
            }
            /*
                Apparently our credentials aren't valid anymore,
                or retry is turned off. If this event returns false
                the developer will call apf.auth.login() at a later date.
            */
            var result = this.dispatchEvent("authrequired", apf.extend({
                bubbles: true,
                data: options && options.data
            }, options));
        }

        this.loggedIn = false;

        if (result === false) {
            if (this.enablequeue && options) //Add communication to queue for later processing
                this.$queue.push(options);

            return true; //cancels error state in protocol
        }
    };

}).call(apf.auth.prototype = new apf.AmlElement());




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element displays a skinnable rectangle which can contain other 
 * AML elements. Often, it's also used in place of a regular HTML `<div>`.
 *
 *
 * #### Example
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *     <!-- startcontent -->
 *     <a:bar id="winGoToFile"
 *       width = "500" 
 *       skin = "winGoToFile"
 *       minheight = "35"
 *       maxheight = "400"
 *       >
 *         <a:vbox id="vboxGoToFile" edge="5 5 5 5" padding="5" anchors2="0 0 0 0">
 *             <a:textbox id="txtGoToFile" realtime="true" skin="searchbox_textbox" focusselect="true" />
 *             <a:list id="dgGoToFile"
 *               class = "searchresults noscrollbar"
 *               skin = "lineselect"
 *               maxheight = "350"
 *               scrollbar = "sbShared 32 7 7"
 *               viewport = "virtual"
 *               multiselect = "true"
 *               empty-message = "A filelist would go here.">
 *             </a:list>
 *         </a:vbox>
 *     </a:bar>
 *     <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Remarks
 *
 * This component is used in the accordion element to create its sections. In
 * the `apf.statusbar`, the panel element is an alias of [[apf.bar]].
 *
 * @class apf.bar
 * @inherits apf.Presentation
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @define bar
 * @container
 * @allowchild button
 * @allowchild {elements}, {anyaml}
 *
 */
/**
 * @attribute {String} icon Sets or gets the URL pointing to the icon image.
 */
/**
 *  @attribute {Boolean} [collapsed=false]  Sets or gets the collapse panel on load
 * 
 */
/**
 * @attribute {String} title  Sets or gets the title string
 */
apf.section = function(struct, tagName) {
    this.$init(tagName || "section", apf.NODE_VISIBLE, struct);
};

apf.menubar = function(struct, tagName) {
    this.$init(tagName || "menubar", apf.NODE_VISIBLE, struct);
};

apf.bar = function(struct, tagName) {
    this.$init(tagName || "bar", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$focussable = false;
    this.$canLeechSkin = true;
    this.$isLeechingSkin = false;
    
    this.$propHandlers["caption"] = function(value) {
        this.$int.innerHTML = value;
    }
    
    //@todo apf3.0 refactor
    this.addEventListener("AMLReparent", 
        function(beforeNode, pNode, withinParent) {
            if (!this.$amlLoaded)
                return;

            if (this.$isLeechingSkin && !withinParent
              && this.skinName != pNode.skinName
              || !this.$isLeechingSkin
              && this.parentNode.$hasLayoutNode 
              && this.parentNode.$hasLayoutNode(this.localName)) {
                this.$isLeechingSkin = true;
                this.$forceSkinChange(this.parentNode.skinName.split(":")[0] + ":" + skinName);
            }
        });

    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal(this.$isLeechingSkin
            ? this.localName 
            : "main");

        //Draggable area support, mostly for a:toolbar
        if (this.oDrag) //Remove if already exist (skin change)
            this.oDrag.parentNode.removeChild(this.oDrag);
        
        this.oDrag = this.$getLayoutNode(this.$isLeechingSkin
            ? this.localName 
            : "main", "dragger", this.$ext);
            
        this.$int = this.$getLayoutNode(this.$isLeechingSkin
            ? this.localName 
            : "main", "container", this.$ext);
    };

    this.$loadAml = function(x) {
        
    };
    
    
    this.$skinchange = function(){
        
    }
    
}).call(apf.bar.prototype = new apf.Presentation());

apf.menubar.prototype = 
apf.section.prototype = apf.bar.prototype;

apf.aml.setElement("bar", apf.bar);
apf.aml.setElement("menubar", apf.menubar);
apf.aml.setElement("section", apf.section);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/*
 * @todo docs
 */
apf.BindingColorRule = function(struct, tagName) {
    this.$init(tagName, apf.NODE_HIDDEN, struct);
};

(function(){
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //@todo This should support multiple color rules, by inserting the rules at the right place.
        if (this.$bindings && this.$bindings.color) {
            var clr = this.$bindings.color[0];
            apf.setStyleRule("." + this.$baseCSSname + (apf.isIE
                ? " .records .highlight SPAN"
                : " .records .highlight span"), "color", clr.getAttribute("text"), null, this.oWin);
            apf.setStyleRule("." + this.$baseCSSname + (apf.isIE
                ? " .records .highlight SPAN"
                : " .records .highlight span"), "backgroundColor", clr.getAttribute("row"), null, this.oWin);
            apf.setStyleRule("." + this.$baseCSSname + (apf.isIE
                ? " .records .highlight"
                : " .records .highlight"), "backgroundColor", clr.getAttribute("row"), null, this.oWin);
            /*apf.importCssString("." + this.$baseCSSname + " .records div.highlight{background-color:" 
                + clr.getAttribute("row") + ";} ." 
                + this.$baseCSSname + " .records div.highlight span{color:" 
                + clr.getAttribute("text") + ";}");*/
        }
        
        //"." + this.$baseCSSname + " .headings 
        apf.importStylesheet([
          ["." + this.className,
            "width:" + this.$width + (this.$isPercentage ? "%;" : "px;")
            + "text-align:" + h.align],
          ["." + this.className,
            "width:" + this.$width + (this.$isPercentage ? "%;" : "px;")
            + "text-align:" + h.align]
        ]);
        
        this.$draw();
    });
}).call(apf.BindingColorRule.prototype = new apf.BindingRule());

apf.aml.setElement("color", apf.BindingColorRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @attribute {String}  icon
 * @attribute {String}  caption
 * @attribute {String}  width
 * @attribute {String}  options
 * @attribute {String}  editor
 * @attribute {String}  colspan
 * @attribute {String}  align
 * @attribute {String}  css
 * @attribute {Boolean} tree
 */
apf.BindingColumnRule = function(struct, tagName) {
    this.$init(tagName || "column", apf.NODE_VISIBLE, struct);
    
    this.$className = "col" + this.$uniqueId;
};

(function(){
    this.$defaultwidth = "100";
    this.$width = 0;
    
    this.$sortable = true; //@todo set defaults based on localName of element to which its applied
    this.$resizable = true;
    this.$movable = true;
    this.$cssInit = false;
    
    this.visible = true;
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        css: 1,
        icon: 1,
        caption: 1,
        eachcaption: 1,
        eachvalue: 1,
        each: 1,
        icon: 1
    }, this.$attrExcludePropBind);
    
    this.$supportedProperties.push("tree", "icon", "caption", "width", "options", 
        "check", "Editor", "colspan", "align", "css", "sorted", "each", 
        "eachvalue", "eachcaption", "model");
    
    this.$booleanProperties["tree"] = true;
    this.$booleanProperties["check"] = true;
    this.$booleanProperties["sorted"] = true;
    this.$booleanProperties["visible"] = true;
    
    this.$setParentFixedWidth = function(){
        var pNode = this.parentNode;
        var vLeft = (pNode.$fixed) + "px";
        if (!this.$isFixedGrid) {
            //apf.setStyleRule("." + this.$baseCSSname + " .headings ." + hFirst.$className, "marginLeft", "-" + vLeft); //Set
            //apf.setStyleRule("." + this.$baseCSSname + " .records ." + hFirst.$className, "marginLeft", "-" + vLeft); //Set
            apf.setStyleRule("." + pNode.$baseCSSname + " .row" + pNode.$uniqueId,
                "paddingRight", vLeft, null, this.oWin); //Set
//            apf.setStyleRule("." + pNode.$baseCSSname + " .row" + pNode.$uniqueId,
//                "marginRight", "-" + vLeft, null, pNode.oWin); //Set
        
            //headings and records have same padding-right
            if (pNode.$container)
                pNode.$container.style.paddingRight = vLeft;
            if (pNode.$head)
                pNode.$head.style.paddingRight = vLeft;
        }
    }
    
    this.$propHandlers["width"] = function(value, prop) {
        if (!value)
            return;

        var diff = value - this.$width;

        this.$isPercentage = value && String(value).indexOf("%") > -1;
        this.$width = parseFloat(value);

        var pNode = this.parentNode;
        
        if (this.$isPercentage) {
            apf.setStyleRule("." + this.$className, "width", this.$width + "%");
            
            //if (apf.z && !this.resizing)
                //this.resize(this.$width, pNode, true);
        }
        else {
            apf.setStyleRule("." + this.$className, "width", this.$width + "px", null, pNode.oWin); //Set
            
            if (pNode.$amlLoaded) {
                if (this.visible)
                    pNode.$fixed += diff;
                
                this.$setParentFixedWidth();
            }
        }
    }
    
    this.$propHandlers["options"] = function(value, prop) {
        this.$sortable = value.indexOf("sort") > -1;
        this.$resizable = value.indexOf("size") > -1;
        this.$movable = value.indexOf("move") > -1;
    }
    
    this.$propHandlers["visible"] = function(value, prop, el, force) {
        var pNode = this.parentNode;
        
        if (!force && !this.$amlLoaded)
            return;

        if (value) {
            apf.setStyleRule("." + this.$className,
                "display", "inline-block", null, this.oWin);
            
            var size = this.$isPercentage
                ? (this.$ext.offsetWidth - (pNode.$widthdiff - 3))
                : this.$width;

            this.resize(size, pNode, true);
        }
        else {
            apf.setStyleRule("." + this.$className,
                "display", "none", null, this.oWin);

            this.resize(0, pNode, true);
        }
    }
    
    this.resize = function(newsize, pNode, toggleShowHide) {
        var hN;
        
        if (this.$isPercentage) {
            var oldsize = (this.visible && this.$ext.offsetWidth
              ? this.$ext.offsetWidth - (pNode.$widthdiff - 3)
              : 0),
                ratio = oldsize ? newsize / oldsize : 1, //div 0 ??
                next = [],
                fixed = [],
                total = 0,
                node = toggleShowHide 
                    ? this.$ext.parentNode.firstChild 
                    : this.$ext.nextSibling;
            
            while (node && node.getAttribute("hid")) {
                hN = apf.all[node.getAttribute("hid")];
                if (hN.visible !== false) {
                    if (hN.$isPercentage) {
                        next.push(hN);
                        total += hN.$width;
                    }
                    else fixed.push(hN);
                }
                node = node.nextSibling;
            }
            
            if (fixed.length && !next.length)
                return fixed[0].resize(fixed[0].$width + (oldsize - newsize), pNode);

            var diffPerc, diffRatio;
            if (ratio == 1 && total < 101) {
                ratio = total/101;
                diffRatio = 1/ratio;
            }
            else if (total > 101) {
                ratio = ratio * 101/total
                diffRatio = ratio;
            }
            else {
                diffPerc = (ratio - 1) * this.$width;
                diffRatio = (total - diffPerc) / total;
                if (diffRatio < 0.01 && diffRatio > 0) {
                    if (newsize < 20) return;
                    return this.resize(newsize - 10, pNode);//pNode.resizeColumn(nr, newsize - 10);
                }
            }
            
            for (var n, i = 0; i < next.length; i++) {
                n = next[i];
                if (n == this)
                    continue;
                
                n.setProperty("width", String(n.$width * diffRatio) + "%", false, true);
            }
            
            if (this.visible !== false) {
                this.setProperty("width", String(ratio * this.$width) + "%", false, true);
            }
        }
        else if (toggleShowHide) {
            var diff = newsize;
            pNode.$fixed += diff;
            this.$setParentFixedWidth();
        }
        else {
            if (apf.isIE && pNode.oIframe)
                this.$ext.style.width = newsize + "px";

            this.setProperty("width", newsize, false, true);
        }
    };
    
    this.hide = function(){
        this.setProperty("visible", false, false, true);
        return this;
    };
    
    this.show = function(){
        this.setProperty("visible", true, false, true);
        return this;
    };
    
    /**
     * Sorts a column.
     * @param {Number} hid the heading number; this number is based on the sequence of the column elements.
     */
    this.sort = function(pNode, initial) {
        if (pNode.$lastSorted == this) {
            apf.setStyleClass(this.$int,
                pNode.toggleSortOrder()
                    ? "ascending"
                    : "descending", ["descending", "ascending"]);
            return;
        }

        var h;
        if (h = pNode.$lastSorted) {
            apf.setStyleRule("." + h.$className, "background", "white"); //This breaks row coloring
            apf.setStyleClass(h.$int, "", ["descending", "ascending"]);
        }
        
        apf.setStyleRule("." + this.$className, "background", "#f3f3f3");
        apf.setStyleClass(this.$int, "ascending", ["descending", "ascending"]);

        pNode.resort({
            order: "ascending",
            xpath: (this.cvalue || this.compile("value")).xpaths[1],
            type: this["data-type"]
        }, false, initial || !pNode.length);
        
        
        //@todo needs more thought
        /*if (pNode.$lastSorted)
            pNode.$lastSorted.setProperty("sorted", false);
        this.setProperty("sorted", true);*/
        
        pNode.$lastSorted = this;
    };
    
    /**
     * Moves a column to another position.
     * @param {Number} fromHid the heading number of the column to move; this number is based on the sequence of the column elements.
     * @param {Number} toHid   the position the column is moved to;
     */
    this.move = function(hTo, pNode) {
        if (hTo && this == hTo) 
            return;
        
        var hFrom = this,
            childNrFrom = apf.getChildNumber(hFrom.$int),
            childNrTo = hTo && apf.getChildNumber(hTo.$int);

        pNode.$head.insertBefore(hFrom.$int, hTo && hTo.$int || null);

        if (!pNode.length)
            return;

        (function _recur(nodes) {
            for (var node, i = 0; i < nodes.length; i++) {
                if (nodes[i].nodeType != 1)
                    continue;
                //if (pNode.$withContainer && ((i+1) % 2) == 0)
                    //continue;
    
                node = nodes[i];
                if (pNode.$isTreeArch && node.tagName == "BLOCKQUOTE") { //@todo small hack
                    _recur(node.childNodes);
                }
                else {
                    node.insertBefore(node.childNodes[childNrFrom], 
                        childNrTo != undefined && node.childNodes[childNrTo] || null);
                }
            }
        })(pNode.$container.childNodes);
        
        /*if (this.$first == from || this.$first == to) {
            var hReset = this.$first == from ? hFrom : hTo;
            
            apf.setStyleRule("." + this.$baseCSSname + " .headings ."
                + hReset.className, "marginLeft", "-5px"); //Reset
            apf.setStyleRule("." + this.$baseCSSname + " .records ."
                + hReset.className, "marginLeft", "-5px"); //Reset
            
            this.$first = pNode.$head.firstChild.getAttribute("hid");
            var h = headings[this.$first];
            var vLeft = "-" + (this.$fixed + 5) + "px";

            apf.setStyleRule("." + this.$baseCSSname + " .headings ."
                + h.className, "marginLeft", vLeft); //Set
            apf.setStyleRule("." + this.$baseCSSname + " .records ."
                + h.className, "marginLeft", vLeft); //Set
        }*/
    }
    
    this.$draw = function(pNode, caption, width, className) {
        //Find the parent this rule works on
        var pNode = pNode || this.parentNode;
        while (pNode.$bindingRule)
            pNode = pNode.parentNode;
        
        if (!pNode.hasFeature(apf.__PRESENTATION__))
            return;
            
        if (width) 
            this.$propHandlers["width"].call(this, width);

        //"." + this.$baseCSSname + " .headings 
        //if initial
        //only needs once if this works

        apf.importStylesheet([
            ["." + this.$className,
                "width:" + this.$width + (this.$isPercentage ? "%;" : "px;")
                + "text-align:" + this.align + ";display: inline-block"],
        ]);
        
        //Add to htmlRoot
        pNode.$getNewContext("headitem");
        var $head = pNode.$getLayoutNode("headitem");
        $head.setAttribute("class", this.$className + (className ? " " + className : ""));
        $head.setAttribute("hid", this.$uniqueId);
        
        var hCaption = pNode.$getLayoutNode("headitem", "caption");
        /*if (this.icon) {
            this.$sortable = false;
            $head.setAttribute("style", "background-image:url("
                + apf.getAbsolutePath(pNode.iconPath, this.icon) 
                + ")");
            hCaption.nodeValue = "&nbsp;";
        }
        else*/
            hCaption.nodeValue = this.caption || caption || '&nbsp';
        
        this.$ext = this.$int = apf.insertHtmlNode($head, pNode.$head || pNode.$container);
        
        var dragging = false;
        var _self = this;
        
        if (this.sorted)
            this.sort(pNode, true);
        
        /*this.$int.onmouseover = function(e) {
            if (!e) e = event;
            
            if (pNode.disabled) return;
            
            clearTimeout(this.$timer);

            apf.setStyleClass(this, "hover", ["down"]);
        };*/
        
        this.$int.onmouseup = function(e) {
            if (!e) e = event;
            
            if (pNode.disabled || !apf.isChildOf(dragging, this, true)) 
                return;
            
            apf.setStyleClass(this, "hover", ["down"]);

            if (_self.$sortable)
                _self.sort(pNode);
            
            //@todo pNode or Self?
            pNode.dispatchEvent("sortcolumn", _self);
        };
        
        this.$int.onmousedown = function(e) {
            if (!e) e = event;
            dragging = target = this;
            
            if (pNode.disabled) return;

            //Resizing
            var pos = apf.getAbsolutePosition(target),
                sLeft = pNode.$head.scrollLeft;
            var d = e.clientX - pos[0] + sLeft;
            if (d < 4 || target.offsetWidth - d - 8 < 3
              && apf.getChildNumber(_self.$int) < pNode.$headings.length - 1) {
                var t = d < 4 && target.previousSibling || target;
                
                if (_self.$resizable) {
                    pos = apf.getAbsolutePosition(t);
                    apf.setStyleClass(pNode.$pointer, "size_pointer", ["move_pointer"]);
                    pNode.$pointer.style.display = "block";
                    pNode.$pointer.style.left = (t.offsetLeft - sLeft - 1) + "px";
                    pNode.$pointer.style.width = (t.offsetWidth - pNode.$widthdiff + 1) + "px";
                    
                    
                    apf.plane.show(pNode.$pointer, null, true);
                    

                    dragging = true;
                    document.onmouseup = function(){
                        if (!e) e = event;
    
                        document.onmouseup = 
                        document.onmousemove = null;
                        
                        apf.all[t.getAttribute("hid")].resize(pNode.$pointer.offsetWidth, pNode);
                        
                        dragging = false;
                        pNode.$pointer.style.display = "none";
                        
                        
                        apf.plane.hide();
                        

                    };
                    
                    document.onmousemove = function(e) {
                        if (!e) e = event;

                        pNode.$pointer.style.width = Math.max(10, 
                            Math.min(pNode.$container.offsetWidth - pNode.$pointer.offsetLeft - 20, 
                                e.clientX - pos[0] - 1 + sLeft)) + "px";
                    };
                    
                    return;
                }
            }
            
            apf.setStyleClass(target, "down", ["hover"]);
            
            //Moving
            if (!_self.$movable) {
                document.onmouseup = function(e) {
                    document.onmouseup = null;
                    dragging = false;
                };
                
                return;
            }
            
            apf.setStyleClass(pNode.$pointer, "move_pointer", ["size_pointer"]);
            
            var x = e.clientX - target.offsetLeft, sX = e.clientX,
                y = e.clientY - target.offsetTop,  sY = e.clientY,
                copy;
            
            document.onmouseup = function(e) {
                if (!e) e = event;
                
                document.onmouseup = 
                document.onmousemove = null;
                
                dragging = false;
                pNode.$pointer.style.display = "none";
                
                if (!copy)
                    return;
                    
                copy.style.top = "-100px";
                
                var el = document.elementFromPoint(e.clientX, e.clientY);
                if (el.parentNode == copy.parentNode) {
                    var pos = apf.getAbsolutePosition(el);
                    var beforeNode = (e.clientX - pos[0] > el.offsetWidth / 2
                        ? el.nextSibling
                        : el);

                    _self.move(beforeNode ? apf.all[beforeNode.getAttribute("hid")] : null, pNode);
                }
                
                apf.destroyHtmlNode(copy);
            };

            document.onmousemove = function(e) {
                if (!e) e = event;
                
                if (!copy) {
                    if (Math.abs(e.clientX - sX) < 3 && Math.abs(e.clientY - sY) < 3)
                        return;
                    
                    copy = target.cloneNode(true);
                    copy.style.position = "absolute";
                    var diff = apf.getWidthDiff(target);
                    copy.style.width = (target.offsetWidth - diff
                        - pNode.$widthdiff + 2) + "px";
                    copy.style.left = target.offsetLeft;
                    copy.style.top = target.offsetTop;
                    copy.style.margin = 0;
                    copy.removeAttribute("hid")
                    
                    apf.setStyleClass(copy, "drag", ["ascending", "descending"]);
                    target.parentNode.appendChild(copy);
                }
                
                copy.style.top = "-100px";
                pNode.$pointer.style.display = "none";
                
                var el = document.elementFromPoint(e.clientX, e.clientY);
                if (el.parentNode == copy.parentNode) {
                    var pos = apf.getAbsolutePosition(el);
                    pNode.$pointer.style.left = (el.offsetLeft 
                        + ((e.clientX - pos[0] > el.offsetWidth / 2)
                            ? el.offsetWidth - 8
                            : 0)) + "px";
                    pNode.$pointer.style.display = "block";
                }
                
                copy.style.left = (e.clientX - x) + 'px';
                copy.style.top = (e.clientY - y) + 'px';
            };
        };
        
        this.$int.onmouseout = function(e) {
            if (!e) e = event;

            if (pNode.disabled) 
                return;
            
            var _self = this;
            this.$timer = setTimeout(function(){
                pNode.$ext.style.cursor = "";
                apf.setStyleClass(_self, "", ["hover", "down"]);
            }, 10);
        };
        
        this.$int.onmousemove = function(e) {
            if (dragging || pNode.disabled)
                return;
                
            if (!e) e = event;

            var pos = apf.getAbsolutePosition(this),
                sLeft = pNode.$head.scrollLeft;
            var d = e.clientX - pos[0] + sLeft;

            if (d < 4 || this.offsetWidth - d - pNode.$widthdiff < 3 
              && apf.getChildNumber(_self.$int) < pNode.$headings.length - 1) {
                var t = d < 4 ? this.previousSibling : this;
                pNode.$ext.style.cursor = t && _self.$resizable
                    ? (apf.isWebkit ? "ew-resize" : "w-resize")
                    : "default";
                
                apf.setStyleClass(this, "", ["hover", "down"]);
            }
            else {
                pNode.$ext.style.cursor = "default";
                apf.setStyleClass(this, "hover", ["down"]);
            }
        };

        if (!this.options && pNode.options)
            this.$propHandlers["options"].call(this, 
                this.options = pNode.options);

        if (this.visible === false)
            this.$propHandlers["visible"].call(this, false, null, null, true);
        
        return this;
    }
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.$draw();
    });
    
    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        if (!this.$int)
            return;
        
        this.$int.onmouseover = 
        this.$int.onmouseup = 
        this.$int.onmousedown = 
        this.$int.onmousemove = 
        this.$int.onmouseout = null;
    });
    
}).call(apf.BindingColumnRule.prototype = new apf.BindingRule());

apf.aml.setElement("column", apf.BindingColumnRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @attribute {String} target 
 * @attribute {String} action
 * @attribute {Boolean} copy
 */
apf.BindingDndRule = function(struct, tagName) {
    this.$init(tagName, apf.NODE_HIDDEN, struct);
};

(function(){
    this.compile = function(prop) {
        if (!this[prop])
            return;
        
        var compileData;
        if (prop == "value")
            compileData = apf.lm.compile(this[prop], {
                xpathmode: 3
            });
        else if (prop == "match")
            compileData = apf.lm.compile(this[prop], {
                xpathmode: 3,
                injectself: true
            });
        else if (prop == "target")
            compileData = apf.lm.compile(this[prop], {
                xpathmode: 2,
                injectself: true
            });
        else if (prop == "action")
            compileData = apf.lm.compile(this[prop], {
                nostring: true
            });
        else if (prop == "copy")
            compileData = apf.lm.compile(this[prop], {
                withopt: true,
                nostring: true
            });
        else
            throw new Error("Missing property handler for compile");
        
        return (this["c" + prop] = compileData);
    }
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        target: 1,
        parent: 1,
        action: 1,
        copy: 1
    }, this.$attrExcludePropBind);

    this.$propHandlers["target"] = 
    this.$propHandlers["parent"] = 
    this.$propHandlers["action"] = 
    this.$propHandlers["copy"] = function(value, prop) {
        delete this["c" + prop];
    }
    
    this.$noderegister = function(e) {
         apf.GuiElement.propHandlers["drop"].call(e.amlNode, true);
    }
    
    //@todo removal
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //Find parent that this rule works on
        var pNode = this;
        while (pNode && pNode.$bindingRule) 
            pNode = pNode.parentNode;
       
        if (!pNode)
            return;
        
        if (pNode.localName == "bindings") {
            pNode.addEventListener("noderegister", this.$noderegister);
            
            var nodes = pNode.$amlNodes;
            for (var i = 0; i < nodes.length; i++)
                apf.GuiElement.propHandlers["drop"].call(nodes[i], true);
        }
        else {
            apf.GuiElement.propHandlers["drop"].call(pNode, true);
        }
    });
}).call(apf.BindingDndRule.prototype = new apf.BindingRule());

apf.aml.setElement("drag", apf.BindingDndRule);
apf.aml.setElement("drop", apf.BindingDndRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @binding each Determines the list of elements for which each
 * gets a visual representation within the element. It also can determine
 * the sequence of how the elements are visualized by offering a way to
 * specify the sort order. The sorting mechanism is very similar to
 * that of XSLT.
 *
 * #### Example
 *
 * This example contains a list that displays elements with the tagName
 * `'mail'` that do not have a deleted attribute set to 1.
 * 
 * ```xml
 *  <a:model id="mdlList">
 *      <data>
 *          <item date="2009-11-12" deleted="0"></item>
 *          <item date="2009-11-11" deleted="0"></item>
 *          <item date="2009-11-10" deleted="0"></item>
 *          <item date="2009-11-09" deleted="1"></item>
 *          <item date="2009-11-08" deleted="1"></item>
 *      </data>
 *  </a:model>
 *  <a:list id="list" width="200" height="200" model="mdlList">
 *      <a:bindings>
 *          <a:caption match="[@date]" />
 *          <a:each match="[item[not(@deleted='1')]]" />
 *      </a:bindings>
 *  </a:list>
 * ```
 * 
 * #### Example
 *
 * This example shows how to use the each rule to order files based
 * on their modified data.
 * 
 * ```xml
 *  <a:model id="mdlList">
 *      <data>
 *          <item date="2009-11-12"></item>
 *          <item date="2009-11-11"></item>
 *      </data>
 *  </a:model>
 *  <a:list width="200" height="200" model="mdlList">
 *      <a:each match="[item]" sort="[@date]" order="ascending">
 *          <a:caption match="[@date]" />
 *      </a:each>
 *  </a:list>
 * ```
 * 
 * #### Example
 * 
 * This example shows how to do complex sorting using a JavaScript callback function.
 * 
 * ```xml
 *  <a:model id="mdlList">
 *      <data>
 *          <item date="2009-11-12" deleted="0"></item>
 *          <item date="2009-11-11" deleted="0"></item>
 *          <item date="2009-11-10" deleted="1"></item>
 *          <item date="2009-11-09" deleted="1"></item>
 *          <item2 date="2009-11-08" deleted="1"></item2>
 *      </data>
 *  </a:model>
 *  <a:list id="list" width="200" height="200" model="mdlList">
 *      <a:script>
 *          function sth_compare(value, args, xmlNode) {
 *          
 *          }
 *      </a:script>
 *      <a:each match="[item]" sort="[@date]" sort-method="sth_compare">
 *          <a:caption match="[@date]" />
 *      </a:each>
 *  </a:list>
 * ```
 *
 */
/**
 * @attribute {String} match        Sets or gets an XPath statement which selects the nodes
 *                                  which will be rendered.
 */
/**
 * @attribute {String} sort         Sets or gets an XPath statement which selects the value
 *                                  which is subject to the sorting algorithm.
 */
/**
 * @attribute {String} data-type    Sets or gets the way sorting is executed. 
 *   The possible values include:
 *
 *   - `string`:  Sorts alphabetically
 *   - `number`:  Sorts based on numerical value (_i.e._, 9 is lower than 10).
 *   - `date`:    Sorts based on the date sequence (`21-6-1980` is lower than `1-1-2000`).
 */
/**
 * @attribute {String} date-format  Sets or gets the format of the date on which is sorted.
 *   
 *  The possible values include:
 *
 *   - `YYYY`:   Full year
 *   - `YY`:     Short year
 *   - `DD`:     Day of month
 *   - `MM`:     Month
 *   - `hh`:     Hours
 *   - `mm`:     Minutes
 *   - `ss`:     Seconds
 * 
 * 
 * #### Example
 *
 * ```
 *  date-format="DD-MM-YYYY"
 * ```
 */
/**
 * @attribute {String} sort-method  Sets or gets the name of a JavaScript function to execute
 *                                  to determine the value to sort on.
 */
/**
 * @attribute {String} order        Sets or gets the order of the sorted list.
 *   
 *  The possible values include:
 *
 *   - `ascending`:  Default sorting order
 *   - `descending`: Reverses the default sorting order
 */
/**
 * @attribute {String} case-order   Sets or gets whether upper case characters have preference
 *                                  above lower case characters.
 *   
 *  The possible values include:
 *
 *   - `upper-first`:    Upper case characters are higher.
 *   - `lower-first`:    Lower case characters are higher.
 */
apf.BindingEachRule = function(struct, tagName) {
    this.$init(tagName, apf.NODE_HIDDEN, struct);
    
    var _self = this;
    this.$noderegister = function(e) {
        e.amlNode.$handleBindingRule(_self.match, "each");
    
        
        e.amlNode.$sort = _self.sort ? new apf.Sort(_self) : null;
        
    }
};

(function(){
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        "sort"         : 1,
        "data-type"    : 1,
        "date-format"  : 1,
        "sort-method"  : 1,
        "order"        : 1,
        "case-order"   : 1
    }, this.$attrExcludePropBind);

    this.$booleanProperties["animate"] = true;

    this.$propHandlers["sort"] = 
    this.$propHandlers["data-type"] = 
    this.$propHandlers["date-format"] = 
    this.$propHandlers["order"] = 
    this.$propHandlers["case-order"] = function(value, prop) {
        delete this["c" + prop];
        
        //@todo apf3.0 change sort
    }
    
    this.$updateEach = function(value) {
        var pNode = this.parentNode;//@todo apf3.0 get a list via $bindings
        if (pNode.localName == "bindings") {
            var nodes = pNode.$amlNodes,
                i = 0,
                l = nodes.length;
            for (; i < l; ++i)
                if (nodes[i].each != value)
                    nodes[i].$handleBindingRule(value, "each");
        }
        else
            if (pNode.each != value)
                pNode.$handleBindingRule(value, "each");
    }
    
    this.$getFilteredMatch = function(value) {
        if (!value)
            return this.match;

        var keywords = value.trim().toLowerCase().split(" ");
        
        var each = this.match.charAt(0) == "[" && this.match.charAt(this.match.length - 1) == "]"
            ? this.match.replace(/^\[|\]$/g, "")
            : this.match;
        
        var model;
        if (each.indexOf("::") > -1) {
            var parsed = each.split("::"); //@todo could be optimized
            if (!apf.xPathAxis[parsed[0]]) {
                model = parsed[0];
                each = parsed[1];
            }
        }
        
        // show search results
        var search = [], word, words, fields = this.$fields || ["text()"];
        for (var i = 0, l = keywords.length; i < l; i++) {
            words = [], word = keywords[i];
            for (var j = 0, jl = fields.length; j < jl; j++) {
                words.push("contains(translate(" + fields[j] + ", 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '" + word + "')");
            }
            search.push(words.join(" or "));
        }
        
        var filter = "(" + search.join(") and (") + ")";
        var groups = this["filter-groups"] ? this["filter-groups"].split("|") : [];
        
        each = each.split("|");
        var newEach = [];
        for (i = 0, l = each.length; i < l; i++) {
            if (!groups.contains(each[i]))
                newEach.push(each[i] + "[" + filter + "]");
        }
        
        var subEach = newEach.join("|");
        
        for (i = 0; i < groups.length; i++) {
            newEach.push(groups[i] + "[" + subEach + "]");
        }

        return newEach.join("|");
    }
    
    /*
     *      <a:each 
     *         match="[group|item]"
     *         filter="{tb.value}"
     *         filter-fields="@caption"
     *         filter-groups="group"
     *      /> 
     */
    
    this.$propHandlers["filter"] = function(value, prop) {
        if (!this.$amlLoaded)
            return;

        this.$updateEach(this.$getFilteredMatch(value));
    }
    this.$propHandlers["filter-fields"] = function(value, prop) {
        this.$fields = value.splitSafe(",");
    }
    
    this.addEventListener("prop.match", function(e) {
        if (!this.$amlLoaded)
            return;
        
        if (this.filter)
            this.$propHandlers["filter"].call(this, this.filter);
        else
            this.$updateEach(this.match);
    });
    
    //@todo apf3.0 optimize
    var f;
    this.addEventListener("DOMNodeInserted", f = function(e) {
        if (e.currentTarget != this)
            return;

        var match = this.$getFilteredMatch(this.filter);
        var pNode = this.parentNode;//@todo apf3.0 get a list via $bindings
        if (pNode.localName == "bindings") {
            pNode.addEventListener("noderegister", this.$noderegister);
            
            var nodes = pNode.$amlNodes,
                i = 0,
                l = nodes.length;
            for (; i < l; ++i) {
                nodes[i].$handleBindingRule(match, "each");
    
                
                nodes[i].$sort = this.sort ? new apf.Sort(this) : null;
                
            }
        }
        else {
            pNode.$handleBindingRule(match, "each");
    
            
            pNode.$sort = this.sort ? new apf.Sort(this) : null;
            
        }
    });
    
    this.addEventListener("DOMNodeRemoved", function(e) {
        if (e.currentTarget != this)
            return;
        
        //@todo apf3.0 how does this conflict with setting it through an attribute.
        //this.$clearDynamicProperty("each");
        //pNode.setProperty("each", null);//@todo double?
        //@todo remove model?
        
        //@todo this should be near $handleBindingRule...
        var pNode = this.parentNode;//@todo apf3.0 get a list via $bindings
        if (pNode.localName == "bindings") {
            pNode.removeEventListener("noderegister", this.$noderegister);
            
            var nodes = pNode.$amlNodes,
                i = 0,
                l = nodes.length;
            for (; i < l; ++i) {
                //delete nodes[i].each; //@todo apf3.x is already set by new one
    
                
                delete nodes[i].$sort;
                
            }
        }
        else {
            //delete pNode.each; //@todo apf3.x is already set by new one
            
            
            delete pNode.$sort;
            
        }
    });
    
    this.addEventListener("DOMNodeInsertedIntoDocument", f);
    
}).call(apf.BindingEachRule.prototype = new apf.BindingRule());

apf.aml.setElement("each", apf.BindingEachRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/*
 * @todo docs
 */
apf.BindingLoadRule = function(struct, tagName) {
    this.$init(tagName, apf.NODE_HIDDEN, struct);
};

(function(){
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        get: 1
    }, this.$attrExcludePropBind);

    this.$propHandlers["get"] = function(value, prop) {
        delete this["c" + prop];
    }
}).call(apf.BindingLoadRule.prototype = new apf.BindingRule());

apf.aml.setElement("load", apf.BindingLoadRule);
apf.aml.setElement("insert", apf.BindingLoadRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/*
 * @todo docs
 */
apf.BindingQuicksandRule = function(struct, tagName) {
    this.$init(tagName, apf.NODE_HIDDEN, struct);
};

(function(){
    function getFilteredNodes(pNode) {
        pNode = pNode || this.$parent;
        var attr, tmp,
            res = [],
            dataIDs = [],
            xmlNodes = pNode.$getDataNode("quicksand", pNode.xmlRoot, false, null, true),
            domNodes = pNode.$ext.childNodes,
            i = 0,
            l = xmlNodes.length;
        if (!domNodes.length || !l)
            return res;

        for (; i < l; ++i)
            dataIDs.push(xmlNodes[i].getAttribute("a_id"));

        for (i = 0, l = domNodes.length; i < l; ++i) {
            if (!domNodes[i] || !(attr = domNodes[i].getAttribute("id")))
                continue;
            tmp = attr.split("|");
            tmp.pop();
            if (dataIDs.indexOf(tmp.join("|")) > -1)
                res.push(domNodes[i]);
        }

        return res;
    }

    function nodeInFilter(node, filtered) {
        var i = 0,
            l = filtered.length,
            s = node.getAttribute("id");
        for (; i < l; ++i) {
            if (filtered[i].getAttribute("id") == s)
                return filtered[i];
        }
        return null;
    }

    function quicksand(parent, customOptions) {
        parent = this.$parent.$ext;

        var i, l, dest, src, offset,
            _self = this,
            coll = getFilteredNodes.call(this), // destination (target) collection
            //sourceHeight = apf.getStyle(parent, "height"), // used to keep height and document flow during the animation
            parentOffset = apf.getAbsolutePosition(parent), // offset of visible container, used in animation calculations
            offsets = [], // coordinates of every source collection item
            callbackFunction = typeof(arguments[1]) == "function"
                ? arguments[1]
                : typeof arguments[2] == "function" ? arguments[2] : apf.K,
            // Gets called when any animation is finished
            //var postCallbackPerformed = 0; // prevents the function from being called more than one time
            postCallback = function() {
                _self.dispatchEvent("afterquicksand");
                callbackFunction.call(_self);
                /*if (!postCallbackPerformed) {
                    parent.innerHTML = $dest.innerHTML; // put target HTML into visible source container
                    $("[data-quicksand-owner=" + cssPath(parent) + "]").remove(); // remove all temporary containers
                    if (typeof callbackFunction == "function")
                        callbackFunction.call(this);
                    postCallbackPerformed = 1;
                }*/
            },
            options = {
                steps: 60,
                anim: apf.tween.easeInOutQuad,
                onfinish: postCallback,
                // put false if you don't want the plugin to adjust height of container to fit all the items
                adjustHeight: true
            };
        apf.extend(options, customOptions);;

        // Replace the collection and quit if IE6
        if (apf.isIE && apf.isIE < 7) {
            parent.innerHTML = "";
            for (i = 0, l = coll.length; i < l; i++)
                parent.appendChild(coll[i]);
            return;
        }

        var $source = parent.childNodes; // source collection items

        // Position: relative situations
        var correctionOffset = [0, 0];
        /*var $correctionParent = parent.offsetParent();
        var correctionOffset = apf.getAbsolutePosition($correctionParent);
        if (apf.getStyle($correctionParent, "position") == "relative") {
            if ($correctionParent.get(0).nodeName.toLowerCase() == "body") {

            }
            else {
                correctionOffset[0] += parseFloat($correctionParent.css("border-top-width"));
                correctionOffset[1] += parseFloat($correctionParent.css("border-left-width"));
            }
        } else {
            correctionOffset[0] -= parseFloat($correctionParent.css("border-top-width"));
            correctionOffset[1] -= parseFloat($correctionParent.css("border-left-width"));
            correctionOffset[0] -= parseFloat($correctionParent.css("margin-top"));
            correctionOffset[1] -= parseFloat($correctionParent.css("margin-left"));
        }*/


        // keeps nodes after source container, holding their position
        parent.style.height = parent.offsetHeight + "px";

        // stops previous animations on source container
        apf.tween.clearQueue(parent, true);//$(this).stop();

        // get positions of source collections
        for (i = 0, l = $source.length; i < l; ++i) {
            src = $source[i];
            offsets[i] = apf.getAbsolutePosition(src, parent, false);
        }
        for (i = 0, l = $source.length; i < l; ++i) {
            src = $source[i];
            // This doesn"t move any element at all, just sets position to absolute
            // and adjusts top & left to make them compatible with position: absolute
            with (src.style) {
                position = "absolute";
                margin = "0";
                top = (offsets[i][1] - parentOffset[1]) + "px";// - parseFloat(apf.getStyle(src, "margin-top"))  + "px";
                left = (offsets[i][0] - parentOffset[0]) + "px";// - parseFloat(apf.getStyle(src, "margin-left")) + "px";
            }
        }

        // create temporary container with destination collection
        var $dest = parent.cloneNode(false);
        $dest.setAttribute("id", "");
        //$dest.setAttribute("data-quicksand-owner", parent.selector);
        //$dest.style.height = "auto";
        //$dest.style.width = parent.offsetWidth + "px";

        for (i = 0, l = coll.length; i < l; ++i) {
            $dest.appendChild(coll[i].cloneNode(true));
            coll[i].$offset = apf.getAbsolutePosition(coll[i]);
        }
        // insert node into HTML
        // Note that the node is under visible source container in the exactly same position
        // The browser render all the items without showing them (opacity: 0.0)
        // No offset calculations are needed, the browser just extracts position from underlayered destination items
        // and sets animation to destination positions.
        parent.parentNode.insertBefore($dest, parent);
        //$dest.setAttribute("data-quicksand-owner", cssPath(parent));
        with ($dest.style) {
            zIndex = 1;
            opacity = 0;
            margin = 0;
            position = "absolute";
            top = parentOffset[1] - correctionOffset[1] + "px";
            left = parentOffset[0] - correctionOffset[0] + "px";
        }

        // If destination container has different height than source container
        // the height can be animated, adjusting it to destination height
        if (false) {//options.adjustHeight) {
            apf.tween.single(parent, {
                steps: options.steps,
                anim: options.anim,
                type: "height",
                from: parent.offsetHeight,
                to: $dest.offsetHeight
            });//parent.animate({height: $dest.height()}, options.duration, options.easing);
        }

        // Now it's time to do shuffling animation
        // First of all, we need to identify same elements within source and destination collections
        var insets = [];
        for (i = 0, l = $source.length; i < l; ++i) {
            src = $source[i];
            //var destElement = coll.filter("[" + options.attribute + "=" + src.getAttribute(options.attribute) + "]");
            if (dest = nodeInFilter(src, coll)) {//destElement.length) {
                // The item is both in source and destination collections
                // If it's under different position, let's move it
                offset = insets.shift() || offsets[i];
                options.tweens = [{
                    type: "top",
                    from: dest.$offset[1] - parentOffset[1],
                    to: offset[1] - parentOffset[1]
                }, {
                    type: "left",
                    from: dest.$offset[0] - parentOffset[0],
                    to: offset[0] - parentOffset[0]
                }, {
                    type: "opacity",
                    from: apf.getStyle(src, "opacity"),
                    to: 1
                }];
                if (apf.supportCSSAnim) {
                    options.tweens.push({
                        type: "transform",
                        subType: "scale",
                        from: 0,
                        to: 1.0
                    });
                }
                apf.tween.multi(src, options);
            }
            else {
                // The item from source collection is not present in destination collections
                // Let's remove it
                options.tweens = [{
                    type: "opacity",
                    from: apf.getStyle(src, "opacity"),
                    to: 0
                }];
                if (apf.supportCSSAnim) {
                    options.tweens.push({
                        type: "transform",
                        subType: "scale",
                        from: 1.0,
                        to: 0
                    });
                }
                insets.push(offsets[i]);
                apf.tween.multi(src, options);
            }
        }

        for (i = 0, l = coll.length; i < l; ++i) {
            var item = coll[i];
            // Grab all items from target collection not present in visible source collection
            if (!nodeInFilter(item, $source)) {
                // No such element in source collection...
                dest = nodeInFilter(item, coll);
                
                options.tweens = [{
                    type: "opacity",
                    from: 0,
                    to: 1
                }];
                if (!apf.isIE) {
                    // @todo add scaleTo animation (CSS3) to 0.0
                }
                
                // Let's create it
                offset = apf.getAbsolutePosition(dest);
                dest = dest.cloneNode(true);

                with (dest.style) {
                    position = "absolute";
                    margin = "0";
                    top = (offset[1] - parentOffset[1]) + "px";
                    left = (offset[0] - parentOffset[0]) + "px";
                    opacity = "0";
                    transform = "scale(.0)";
                }
                parent.appendChild(dest);
                apf.tween.multi(dest, options);
            }
        }
    }

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //Find parent that this rule works on
        var pNode = this;
        while (pNode && pNode.$bindingRule)
            pNode = pNode.parentNode;
        this.$parent = pNode;

        if (!pNode)
            return;

        var _self = this;
        setTimeout(function() {
            quicksand.call(_self);
        });
    });
}).call(apf.BindingQuicksandRule.prototype = new apf.BindingRule());

apf.aml.setElement("quicksand", apf.BindingQuicksandRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An element containing all the binding rules for the data 
 * bound elements referencing this element.
 *
 * 
 * #### Example:
 *
 * ```xml
 *  <a:model id="mdlList">
 *      <data>
 *          <item date="2009-11-12" deleted="0"></item>
 *          <item date="2009-11-11" deleted="0"></item>
 *      </data>
 *  </a:model>
 *  <a:bindings id="bndFolders" >
 *      <a:caption match="[@date]" />
 *      <a:icon match="[@icon]" />
 *      <a:each match="[item]" sort="[@date]" />
 *  </a:bindings>
 *  <a:list 
 *    id = "list" 
 *    width = "200" 
 *    height = "200" 
 *    model = "mdlList" 
 *    bindings = "bndFolders" />
 * ```
 *
 * @see apf.smartbinding
 *
 * @class apf.bindings
 * @define bindings 
 * @inherits apf.AmlElement
 * @apfclass
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 *
 * @default_private
 */
apf.bindings = function(struct, tagName) {
    this.$init(tagName || "bindings", apf.NODE_HIDDEN, struct);
    
    this.$bindings = new apf.ruleList();
    this.$amlNodes = {};
};

(function(){
    this.$smartbinding = null;

    this.register = function(amlNode) {
        if (amlNode.localName == "smartbinding") {
            this.$smartbinding = amlNode;
            this.$smartbinding.add(this); //Assuming only at init
            return;
        }
        
        if (!amlNode.hasFeature(apf.__DATABINDING__))
            return;

        this.$amlNodes[amlNode.$uniqueId] = amlNode;
        
        if (!this.$amlLoaded)
            return;

        if (!this.$bindings.$isCompiled)
            this.$cbindings = this.$bindings.compile();
        
        amlNode.$bindings = this.$bindings;
        amlNode.$cbindings = this.$cbindings;
        amlNode.$bindingsElement = this;
        
        //@todo apf3.0 should be deprecated
        amlNode.dispatchEvent("bindingsload", {
            bindings: this.$bindings, 
            compiled: this.$cbindings
        });
        this.dispatchEvent("noderegister", {
            amlNode: amlNode
        });
        amlNode.$checkLoadQueue();
    };

    this.unregister = function(amlNode) {
        //unregister element
        this.$amlNodes[amlNode.$uniqueId] = null;
        delete this.$amlNodes[amlNode.$uniqueId];

        amlNode.$bindingsElement = 
        amlNode.$bindings = 
        amlNode.$cbindings = false;
        
        amlNode.dispatchEvent("bindingsunload", {
            bindings: this.$bindings, 
            compiled: this.$cbindings
        });
    };
    
    this.reload = function(){
        for (var id in this.$amlNodes) {
            this.$amlNodes[id].reload();
        }
    }
    
    // *** DOM Handlers *** //
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var nodes = this.childNodes;
        for (var node, i = 0, l = nodes.length; i < l; i++) {
            if (!(node = nodes[i]).$amlLoaded && node.nodeType == 1)
                node.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
        }
        
        this.register(this.parentNode);
        
        for (var id in this.$amlNodes)
            this.register(this.$amlNodes[id]);
    });
}).call(apf.bindings.prototype = new apf.AmlElement());

apf.ruleList = function(){
    this.$compiled = {};
}
apf.ruleList.prototype = {
    $isCompiled: false,
    
    getRule: function(name, xmlNode) {
        var rules = this[name];
        if (!rules) return false;
        
        //@todo Shouldn't allow async calls..., should always give a function
        for (var func, rule, i = 0, l = rules.length; i < l; i++) {
            rule = rules[i];
            if (!rule.match) 
                return rule;

            func = rule.cmatch || rule.compile("match", {injectself: true, xpathmode: 2});
            if (func && func(xmlNode))
                return rule;
        }
    },
    
    compile: function(name) {
        var rules, s, c = this.$compiled, hasAml = false;

        if (name) {
            s = [];
            rules = this[name];
            for (var rule, i = 0, l = rules.length; i < l; i++) {
                if (!(rule = rules[i]).match && !rule.value)
                    continue;

                s.push(rule.match, rule.value);
                
            }
            
            //always give a function, no async calls (could also just error on execution)
            c[name] = apf.lm.compileMatch(s); 
            
            
            
            return c;
        }
        
        for (name in this) {
            if (name == "each")
                continue;
            
            rules = this[name];
            if (rules.dataType != apf.ARRAY)
                continue;
            
            s = [], hasAml = false;
            for (var rule, i = 0, l = rules.length; i < l; i++) {
                if (!(rule = rules[i]).match && !rule.value)
                    continue;

                s.push(rule.match, rule.value);
                
            }
            
            //always give a function, no async calls (could also just error on execution)
            c[name] = apf.lm.compileMatch(s); 
            
            
        }

        this.$isCompiled = true;
        
        return c;
    },
    
    getRuleIndex: function(name, index) {
        var rule = this[name][index];
        if (rule.value) {
            if (!rule.cvalue)
                rule.compile("value");
        }
        else if (rule.match) {
            if (!rule.cmatch)
                rule.compile("match");
        }
        return rule;
    },
    
    getDataNode: function(name, xmlNode, createNode, ruleList, multiple) {
        var i, l, func, node, rule, rules = this[name];
        if (!rules)
            return;
        
        //@todo Shouldn't allow async calls..., should always give a function
        for (rule, i = 0, l = rules.length; i < l; i++) {
            rule = rules[i];
            
            func = rule.cvaluematch;
            if (!func) { //@todo apf3.0 cleanup
                if (rule.match && rule.value && rule.value.match(/^\[.*\]$/))
                    rule.valuematch = "{_n = " + rule.match + "; %[child::" 
                        + rule.value.substr(1, rule.value.length - 2)
                            .split("|").join("|child::") + "]}";
                else
                    rule.valuematch = rule.match || rule.value;
                
                func = rule.$compile("valuematch", {
                    xpathmode: multiple ? 4 : 3, 
                    injectself: rule.match ? true : false
                });
            }
            
            if (func && (node = func(xmlNode, createNode))) {
                if (ruleList)
                    ruleList.push(rule);

                return node;
            }
        }
        
        return false;
    }
}

apf.aml.setElement("bindings", apf.bindings);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



// @todo doc
/*
 * @attribute {String} formula 
 * @attribute {Number} length
 * @attribute {String} delimiter
 * @attribute {String} split
 * @attribute {String} css
 */
apf.BindingSeriesRule = function(struct, tagName) {
    this.$init(tagName || "series", apf.NODE_HIDDEN, struct);
};

(function(){
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        formula: 1
    }, this.$attrExcludePropBind);

    this.$propHandlers["formula"] = function(value, prop) {
        delete this["c" + prop];
    }
    
    /*this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //Find parent that this rule works on
        var pNode = this;
        while (pNode && pNode.$bindingRule) 
            pNode = pNode.parentNode;
       
        if (!pNode)
            return;
    });*/
}).call(apf.BindingSeriesRule.prototype = new apf.BindingRule());

apf.aml.setElement("series", apf.BindingSeriesRule);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @todo description
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */



apf.AmlConfig = function(){
    this.$init("config", apf.NODE_VISIBLE);
};

(function(){
    this.focussable = false;
    this.$canLeechSkin = true;
    
    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$int = this.$getExternal(this.$isLeechingSkin
            ? this.localName 
            : "main");
    };

}).call(apf.AmlConfig.prototype = new apf.Presentation());

apf.aml.setElement("config", apf.AmlConfig);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Element displaying the rendered contents of an URL.
 *
 * @constructor
 *
 * @define browser
 *
 * @inheritsElsewhere apf.XForms
 * @inherits apf.StandardBinding
 * @inherits apf.DataAction
 * 
 * @event load
 * @event error
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * Example (BROKEN):
 * Sets the url based on data loaded into this component.
 * <code>
 *  <a:model id="mdlBrowser">
 *      <data url="http://www.w3c.org"></data>
 *  </a:model>
 *  <a:browser 
 *    model = "mdlBrowser" 
 *    width = "800" 
 *    height = "600" 
 *    value = "[@url]" />
 * </code>
 * Example:
 * A shorter way to write this is:
 * <code>
 *  <a:model id="mdlBrowser">
 *      <data url="http://www.w3c.org"></data>
 *  </a:model>
 *  <a:browser 
 *    width = "800" 
 *    height = "600" 
 *    value = "[mdlBrowser::@url]" />
 * </code>
 */
apf.browser = function(struct, tagName) {
    this.$init(tagName || "browser", apf.NODE_VISIBLE, struct);
};
(function(){
    this.implement(
        
        
        apf.DataAction
        
        
        ,apf.StandardBinding
        
    );

    /**
     * @attribute {String} src   the url to be displayed in this element
     * @attribute {String} value alias for the 'url' attribute
     */
    this.$supportedProperties.push("value", "src");
    this.$propHandlers["src"] = 
    this.$propHandlers["value"] = function(value, force) {
        try {
            this.$browser.src = value || "about:blank";
        }
        catch (e) {
            this.$browser.src = "about:blank";
        }
    };
    this.$propHandlers["border"] = apf.Presentation.prototype.$propHandlers["border"];

    this.getValue = function() {
        return this.value || this.src;
    };

    /**
     * Retrieves the current url that is displayed.
     */
    this.getURL = function(){
        return this.$browser.src;
    };

    /**
     * Browses to the previous tab
     */
    this.back = function(){
        this.$browser.contentWindow.history.back();
    };

    /**
     * Browses to the next tab
     */
    this.forward = function(){
        this.$browser.contentWindow.history.forward();
    };

    /**
     * Reload the current tab
     */
    this.reload = function(){
        this.$browser.src = this.$browser.src;
    };

    /**
     * Print the currently displayed tab
     */
    this.print = function(){
        this.$browser.contentWindow.print();
    };

    /**
     * Execute a string of javascript on the tab. This is subject to browser
     * security and will most likely only work when the browsed tab is loaded
     * from the same domain.
     * @param {String}  str     javascript string to be executed.
     * @param {Boolean} noError whether the execution can throw an exception. Defaults to false.
     */
    this.runCode = function(str, noError) {
        if (noError) {
            try {
                this.$browser.contentWindow.eval(str);
            } catch (e) {}
        }
        else {
            this.$browser.contentWindow.eval(str);
        }
    };

    this.$draw = function(parentNode) {
        if (!parentNode)
            parentNode = this.$pHtmlNode;

        //Build Main Skin
        if (apf.cannotSizeIframe) {
            //parentNode.appendChild(document.createElement("iframe"));//
            var _iframe = document.createElement("iframe");
                _iframe.setAttribute("frameborder","0");
            this.$ext = parentNode.appendChild(document.createElement("DIV"))
                .appendChild(_iframe).parentNode;
            this.$ext.style.width = "100px";
            this.$ext.style.height = "100px";
            this.$browser = this.$ext.firstChild;
            //this.$browser = this.$ext;
            this.$browser.style.width = "100%";
            this.$browser.style.height = "100%";
            this.$browser.frameBorder = 0;
        }
        else {
            this.$ext = parentNode.appendChild(document.createElement("iframe"));
            this.$ext.setAttribute("frameborder","0");
            //this.$ext.style.width = "100px";
            //this.$ext.style.height = "100px";
            this.$browser = this.$ext;
            this.$ext.style.border = "1px solid #999";
            this.$ext.style.background = "white";
        }
        this.$ext.className = "apfbrowser"
        
        if (this.getAttribute("style"))
            this.$ext.setAttribute("style", this.getAttribute("style"));
        
        var _self = this;
        apf.addListener(this.$browser, "load", function(){
            var loc = this.contentWindow.location.href;
            _self.dispatchEvent("load", {href: loc});
            if (loc)
                _self.setProperty("src", loc);
        });

        apf.addListener(this.$browser, "error", function(){
            _self.dispatchEvent("error");
            if (this.contentWindow.location.href)
                 _self.setProperty("src", this.contentWindow.location.href);
        });

        //this.$browser = this.$ext.contentWindow.document.body;
        this.$ext.host = this;
        //this.$browser.host = this;
    };
}).call(apf.browser.prototype = new apf.GuiElement());

apf.aml.setElement("browser", apf.browser);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Element displaying a clickable rectangle that visually confirms to the
 * user when the area is clicked and then executes a command.
 *
 *
 * #### Example: Working with Events
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <a:table columns="100, 100" cellheight="24">
 *      <a:label>Onclick event</a:label>
 *      <a:button
 *        skin = "btn-default-css3"
 *        class = "btn-green"
 *        width = "120"
 *        onclick = "alert('Button has been clicked')">
 *          Example button</a:button>
 *      <a:label>Onmouseover event</a:label>
 *      <a:button 
 *        skin = "btn-default-css3"
 *        class = "btn-red"
 *        width = "120"
 *        onmouseover = "alert('Button has been hovered')">
 *          Example button</a:button>
 *      <a:label>Onmouseout event</a:label>
 *      <a:button 
 *        width = "120"
 *        onmouseout = "alert('Mouse hover out button')">
 *          Example button</a:button>
 *  </a:table>
 * </a:application>
 * ```
 * 
 * #### Example: Interactions and Colors
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <a:table columns="250" cellheight="24">
 *  <!-- startcontent -->
 *  <a:button
 *    onclick = "b1.setAttribute('width', '200')" 
 *    width = "250">
 *      Click me to resize Test button to 200px</a:button>
 *  <a:button 
 *    onclick = "b1.setAttribute('width', '50')" 
 *    width = "250">
 *      Click me to resize Test button to 50px</a:button>
 *  <a:button id="b1" color="#FF8203">Test</a:button>
 *  <!-- endcontent -->
 *  </a:table>
 * </a:application>
 * ```
 * 
 * @class apf.button
 * @inherits apf.BaseButton
 * @define button
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 * @form
 * @inherits apf.BaseButton
 */
apf.submit = function(struct, tagName) {
    this.$init(tagName || "submit", apf.NODE_VISIBLE, struct);
};


apf.trigger = function(struct, tagName) {
    this.$init(tagName || "trigger", apf.NODE_VISIBLE, struct);
};

apf.reset = function(struct, tagName) {
    this.$init(tagName || "reset", apf.NODE_VISIBLE, struct);
};

apf.button = function(struct, tagName) {
    this.$init(tagName || "button", apf.NODE_VISIBLE, struct);
};

(function() {
    this.$useExtraDiv;
    this.$childProperty = "caption";
    this.$inited = false;
    this.$isLeechingSkin = false;
    this.$canLeechSkin = true;

    // *** Properties and Attributes *** //

    this.$focussable = apf.KEYBOARD; // This object can get the focus
    this.value = null;
    
    this.$init(function(){
        //@todo reparenting
        var forceFocus, _self = this, lastDefaultParent;
        this.$propHandlers["default"] = function(value) {
            if (parseInt(value) != value)
                value = apf.isTrue(value) ? 1 : 0;

            this["default"] = parseInt(value);
            
            if (!this.focussable && value || forceFocus)
                this.setAttribute("focussable", forceFocus = value);

            if (lastDefaultParent) {
                lastDefaultParent.removeEventListener("focus", setDefault);
                lastDefaultParent.removeEventListener("blur", removeDefault);
            }
            
            if (!value)
                return;

            var pNode = this.parentNode;
            while (pNode && !pNode.focussable && --value)
                pNode = pNode.parentNode;
                
            //Currrently only support for parentNode, this might need to be expanded
            if (pNode) {
                pNode.addEventListener("focus", setDefault);
                pNode.addEventListener("blur", removeDefault);
            }
        };
    
        function setDefault(e) {
            if (e.defaultButtonSet || e.returnValue === false)
                return;
    
            e.defaultButtonSet = true;
    
            if (this.$useExtraDiv)
                _self.$ext.appendChild(apf.button.$extradiv);
    
            _self.$setStyleClass(_self.$ext, _self.$baseCSSname + "Default");
    
            if (e.toElement != _self && e.toElement) {
                // _self.$focusParent
                e.toElement.addEventListener("keydown", btnKeyDown);
            }
        }
    
        function removeDefault(e) {
            if (this.$useExtraDiv && apf.button.$extradiv.parentNode == _self.$ext)
                _self.$ext.removeChild(apf.button.$extradiv);
    
            _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Default"]);
    
            if (e.fromElement != _self && e.fromElement) {
                //_self.$focusParent
                e.fromElement.removeEventListener("keydown", btnKeyDown);
            }
        }
    
        function btnKeyDown(e) {
            var ml;
    
            var f = apf.document.activeElement;
            if (f) {
                if (f.hasFeature(apf.__MULTISELECT__))
                    return;
    
                ml = f.multiline;
            }
    
            if (!_self.$ext.onmouseup)
                return;
    
            if (ml && ml != "optional" && e.keyCode == 13
              && e.ctrlKey || (!ml || ml == "optional")
              && e.keyCode == 13 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
                apf.preventDefault(e.htmlEvent);
                _self.$ext.onmouseup(e.htmlEvent, true);
            }
        }
    
        this.addEventListener("focus", setDefault);
        this.addEventListener("blur", removeDefault);
        
        this.$enable = function(){
            if (this["default"]) {
                setDefault({});
                if (apf.document.activeElement)
                    apf.document.activeElement.focus(true);
            }
            
            if (this.state && this.value)
                this.$setState("Down", {});
            else if (this.$mouseOver)
                this.$updateState({}, "mouseover");
            else
                this.$doBgSwitch(1);
        };
    
        this.$disable = function(){
            if (this["default"])
                removeDefault({});
    
            this.$doBgSwitch(4);
            this.$setStyleClass(this.$ext, "",
                [this.$baseCSSname + "Over", this.$baseCSSname + "Down"]);
        };
    });

    /**
     * @attribute {String}  icon     Sets or gets the url from which the icon image is loaded.
     */
    /**
     * @attribute {Boolean} state    Sets or gets whether this boolean is a multi state button.
     */
    /**
     * @attribute {String}  value    Sets or gets the initial value of a state button.
     */
    /**
     * @attribute {String}  color    Sets or gets the text color of the caption of this element.
     */
    /**
     * @attribute {String}  caption  Sets or gets the text displayed on this element indicating the action when the button is pressed.
     */
    /**
     *  @attribute {String}  action   Sets or gets one of the default actions this button can perform when pressed.
     *   
     * The possible values include:
     *
     *   - `undo`:     Executes undo on the action tracker of the target element.
     *   - `redo`:     Executes redo on the action tracker of the target element.
     *   - `remove`:   Removes the selected node(s) of the target element.
     *   - `add`:      Adds a node to the target element.
     *   - `rename`:   Starts the rename function on the target element.
     *   - `login`:    Calls log in on the auth element with the values of the textboxes of type username and password.
     *   - `logout`:   Calls lot out on the auth element.
     *   - `submit`:   Submits the data of a model specified as the target.
     *   - `ok`:       Executes a `commitTransaction()` on the target element, and closes or hides that element.
     *   - `cancel`:   Executes a `rollbackTransaction()` on the target element, and closes or hides that element.
     *   - `apply`:    Executes a `commitTransaction()` on the target element.
     *   - `close`:    Closes the target element.
     */
    /**
     * @attribute {String}  target   Sets or gets the id of the element to apply the action to. Defaults to the parent container.
     */
    /**
     * @attribute {Number}  default  Sets or gets the search depth for which this button is the default action. `1` specifies the direct parent, `2` specifies the parent of this parent, _.e.t.c._
     */
    /**
     * @attribute {String}  submenu  Sets or gets the name of the contextmenu to display when the button is pressed.
     */
    //this.$booleanProperties["default"] = true;
    this.$booleanProperties["state"] = true;
    this.$supportedProperties.push("icon", "value", "tooltip", "state", 
        "color", "caption", "action", "target", "default", "submenu", "hotkey");

    this.$propHandlers["icon"] = function(value) {
        
        if (!this.oIcon) return;
        

        if (value)
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Icon");
        else
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Icon"]);

        apf.skins.setIcon(this.oIcon, value, this.iconPath);
    };

    this.$propHandlers["value"] = function(value) {
        if (!this.state && !this.submenu)
            return;
        
        if (value === undefined)
            value = !this.value;
        this.value = value;

        if (this.value) {
            this.$setState("Down", {});
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Checked")
        }
        else {
            this.$setState("Out", {});
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Checked"])
        }
    };

    this.$propHandlers["state"] = function(value) {
        if (value)
            this.$setStateBehaviour(this.value);
        else 
            this.$setNormalBehaviour();
    };

    this.$propHandlers["color"] = function(value) {
        if (this.oCaption)
            this.oCaption.parentNode.style.color = value;
    };

    this.$propHandlers["caption"] = function(value) {
        if (!this.oCaption)
            return;

        if (value)
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Empty"]);
        else
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Empty");

        if (this.oCaption.nodeType == 1)
            this.oCaption.innerHTML = String(value || "").trim();
        else
            this.oCaption.nodeValue = String(value || "").trim();
    };

    
    /**
     * @attribute {String} hotkey Sets or gets the key combination a user can press
     * to active the function of this element. Use any combination of
     * [[keys: Ctrl]], [[keys: Shift]], [[keys: Alt]], [[keys: F1]]-[[keys: F12]], and alphanumerical characters. Use a
     * space, a minus or plus sign as a seperator.
     *
     * #### Example
     *
     * ```xml
     *  <a:button hotkey="Ctrl-Z">Undo</a:button>
     * ```
     */
    this.$propHandlers["hotkey"] = function(value) {
        if (this.$hotkey)
            apf.setNodeValue(this.$hotkey, value);

        if (this.$lastHotkey) {
            apf.hotkeys.remove(this.$lastHotkey[0], this.$lastHotkey[1]);
            delete this.$lastHotkey[0];
        }

        if (value) {
            this.$lastHotkey = [value];
            var _self = this;
            apf.hotkeys.register(value, this.$lastHotkey[1] = function(){
                //hmm not very scalable...
                _self.$setState("Over", {});

                $setTimeout(function(){
                    _self.$setState("Out", {});
                }, 200);

                if (_self.$clickHandler && _self.$clickHandler())
                    _self.$updateState(e || event, "click");
                else
                    _self.dispatchEvent("click");
            });
        }

        if (this.tooltip)
            apf.GuiElement.propHandlers.tooltip.call(this, this.tooltip);
    }
    

    

    //@todo move this to menu.js
    function menuKeyHandler(e) {
        return;
        var key = e.keyCode;

        var next, nr = apf.getChildNumber(this);
        if (key == 37) { //left
            next = nr == 0
                ? this.parentNode.childNodes.length - 1
                : nr - 1;
            this.parentNode.childNodes[next].dispatchEvent("mouseover");
        }
        else if (key == 39) { //right
            next = (nr >= this.parentNode.childNodes.length - 1)
                ? 0
                : nr + 1;
            this.parentNode.childNodes[next].dispatchEvent("mouseover");
        }
    }

    function menuDown(e) {
        var menu = self[this.submenu] || this.submenu,
            $button1;

        this.value = !this.value;

        if (this.value)
            this.$setState("Down", {});

        

        var menuPressed = this.parentNode.menuIsPressed;
        if (menuPressed && menuPressed != this) {
            menuPressed.setValue(false);
            var oldMenu = self[menuPressed.submenu] || menuPressed.submenu;
            if (oldMenu != (self[this.submenu] || this.submenu))
                oldMenu.$propHandlers["visible"].call(oldMenu, false, true);
        }
        
        if (!this.value) {
            menu.hide();
            this.$setState("Over", {}, "toolbarover");

            if ($button1 = this.parentNode.$button1)
                $button1.$setState("Over", {}, "toolbarover");

            this.parentNode.menuIsPressed = false;
            if (this.parentNode.hasMoved)
                this.value = false;

            if (apf.hasFocusBug)
                apf.window.$focusfix();

            return false;
        }

        this.parentNode.menuIsPressed = this;

        menu.display(null, null, false, this,
            null, null, this.$ext.offsetWidth - 2);

        this.parentNode.hasMoved = false;

        if (e && e.htmlEvent)
            apf.stopPropagation(e.htmlEvent);

        return false;
    }

    function menuOver(){
        var menuPressed = this.parentNode.menuIsPressed;

        if (!menuPressed || menuPressed == this)
            return;

        var menu = self[this.submenu] || this.submenu;
        if (menu.pinned)
            return;

        menuPressed.setValue(false);
        var oldMenu = self[menuPressed.submenu] || menuPressed.submenu;
        oldMenu.$propHandlers["visible"].call(oldMenu, false, true);//.hide();

        this.setValue(true);
        this.parentNode.menuIsPressed = this;

        

        //var pos = apf.getAbsolutePosition(this.$ext, menu.$ext.offsetParent);

//        menu.display(pos[0],
//            pos[1] + this.$ext.offsetHeight, true, this,
//            null, null, this.$ext.offsetWidth - 2);
            
        menu.display(null, null, true, this,
            null, null, this.$ext.offsetWidth - 2);

        //apf.window.$focus(this);
        this.$focus();

        this.parentNode.hasMoved = true;

        return false;
    }

    this.$propHandlers["submenu"] = function(value) {
        if (!value) {
            if (this.value && this.parentNode) {
                
                menuDown.call(this);
                
            }

            this.$focussable = true;
            this.$setNormalBehaviour();
            this.removeEventListener("mousedown", menuDown);
            this.removeEventListener("mouseover", menuOver);
            this.removeEventListener("keydown", menuKeyHandler, true);
            return;
        }

        this.$focussable = false;
        this.$setStateBehaviour();

        this.addEventListener("mouseover", menuOver);
        this.addEventListener("mousedown", menuDown);
        this.addEventListener("keydown", menuKeyHandler, true);
    };
    

    // *** Public Methods *** //

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value the new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };

    /**
     * If this button is a submenu, this method shows it.
     */    
    this.showMenu = function(){
        if (this.submenu && !this.value)
            menuDown.call(this);
    };
    /**
     * If this button is a submenu, this method hides it.
     */     
    this.hideMenu = function(){
        if (this.submenu && this.value)
            menuDown.call(this);
    };

    /**
     * Sets the text displayed as a caption of this element.
     *
     * @param {String}   value   The string to display.
     * @see    apf.Validation
     */
    this.setCaption = function(value) {
        this.setProperty("caption", value, false, true);
    };

    /**
     * Sets the URL of the icon displayed on this element.
     *
     * @param {String}   value   The URL to the location of the icon.
     */
    this.setIcon = function(url) {
        this.setProperty("icon", url, false, true);
    };
    
    

    // *** Private state methods *** //

    this.$setStateBehaviour = function(value) {
        this.value = value || false;
        this.isBoolean = true;
        this.$setStyleClass(this.$ext, this.$baseCSSname + "Bool");

        if (this.value) {
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Down");
            this.$doBgSwitch(this.states["Down"]);
        }
    };

    this.$setNormalBehaviour = function(){
        this.value = null;
        this.isBoolean = false;
        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Bool"]);
    };

    this.$setState = function(state, e, strEvent) {
        var parentNode = this.parentNode;
        //if (this.disabled)
            //return;

        if (strEvent && this.dispatchEvent(strEvent, {htmlEvent: e}) === false)
            return;
        
        if (parentNode && parentNode.$button2 && parentNode.$button2.value && !this.submenu)
            return;

        this.$doBgSwitch(this.states[state]);
        var bs = this.$baseCSSname;
        this.$setStyleClass(this.$ext, (state != "Out" ? bs + state : ""),
            [(this.value ? "" : bs + "Down"), bs + "Over"]);

        if (this.submenu) {
            bs = this.$baseCSSname + "menu";
            this.$setStyleClass(this.$ext, (state != "Out" ? bs + state : ""),
            [(this.value ? "" : bs + "Down"), bs + "Over"]);
        }

        //if (state != "Down")
            //e.cancelBubble = true;
    };

    this.$clickHandler = function(){
        // This handles the actual OnClick action. Return true to redraw the button.
        if (this.isBoolean && !this.submenu && this.auto !== false) {
            this.setProperty("value", !this.value);
            return true;
        }
    };

    
    this.$submenu = function(hide, force) {
        if (hide && this.submenu) {
            this.setValue(false);
            this.$setState("Out", {}, "mouseout");
            if (this.parentNode)
                this.parentNode.menuIsPressed = false;
        }
    };
    

    // *** Init *** //

    this.addEventListener("$skinchange", function(e) {
        if (this.tooltip)
            apf.GuiElement.propHandlers.tooltip.call(this, this.tooltip);
    });

    this.$draw = function(){
        var pNode, isToolbarButton = (pNode = this.parentNode) 
            && pNode.parentNode && pNode.parentNode.localName == "toolbar";
        
        if (isToolbarButton) {
            if (typeof this.focussable == "undefined")
                this.focussable = false;
            
            this.$focussable = apf.KEYBOARD;
        }

        //Build Main Skin
        this.$ext = this.$getExternal();
        this.oIcon = this.$getLayoutNode("main", "icon", this.$ext);
        this.oCaption = this.$getLayoutNode("main", "caption", this.$ext);

        this.$useExtraDiv = apf.isTrue(this.$getOption("main", "extradiv"));
        if (!apf.button.$extradiv && this.$useExtraDiv) {
            (apf.button.$extradiv = document.createElement("div"))
                .className = "extradiv"
        }

        if (this.localName == "submit")
            this.action = "submit";
        else if (this.localName == "reset")
            this.action = "reset";

        this.$setupEvents();
    };

    
    this.addEventListener("$skinchange", function(){
        if (this.caption)
            this.$propHandlers["caption"].call(this, this.caption);

        if (this.icon)
            this.$propHandlers["icon"].call(this, this.icon);

        this.$updateState({reset:1});
        //this.$blur();

        //if (this.$focussable !== true && this.hasFocus())
            //apf.window.$focusLast(this.$focusParent);
    });
    

    

    
}).call(apf.button.prototype = new apf.BaseButton());

// submit, trigger, reset, button
apf.submit.prototype = 
apf.trigger.prototype =
apf.reset.prototype = apf.button.prototype;

apf.aml.setElement("submit",  apf.submit);
apf.aml.setElement("trigger", apf.trigger);
apf.aml.setElement("reset",   apf.reset);
apf.aml.setElement("button",  apf.button);








/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element displays a clickable rectangle with two states that
 * can be toggled by user interaction.
 * 
 * #### Example: Setting and Retrieving Values
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:checkbox 
 *   id = "ch1" 
 *   values = "full|empty" 
 *   checked = "true">Full</a:checkbox>
 *   <a:textbox value="the glass is {ch1.value}"></a:textbox>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * #### Example: Disabled Values
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:checkbox checked="true">Option 1</a:checkbox>
 *   <a:checkbox>Option 2</a:checkbox>
 *   <a:checkbox checked="true" disabled="true">Option 3</a:checkbox>
 *   <a:checkbox disabled="true">Option 4</a:checkbox>
 *   <a:checkbox label="Option 5" />
 *   <a:checkbox label="Option 6" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.checkbox
 *
 * @define checkbox
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 * @form
 * @inherits apf.BaseButton
 * @inheritsElsewhere apf.XForms
 *
 */
/**
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * 
 * #### Example
 *
 * Sets the value of the checkbox based on data loaded into this component.
 * ```xml
 *  <a:model id="mdlCheckbox">
 *      <data answer="Something"></data>
 *  </a:model>
 *  <a:checkbox 
 *    model = "mdlCheckbox" 
 *    value = "[@answer]">Caption</a:checkbox>
 * ```
 *
 * A shorter way to write this is:
 * 
 * ```xml
 *  <a:model id="mdlCheckbox">
 *      <data answer="Something"></data>
 *  </a:model>
 *  <a:checkbox value="[mdlCheckbox::@answer]">Caption</a:checkbox>
 * ```
 */
apf.checkbox = function(struct, tagName) {
    this.$init(tagName || "checkbox", apf.NODE_VISIBLE, struct);
};

(function() {
    this.implement(
        
        
        apf.DataAction
        
    );

    //Options
    this.$focussable = apf.KEYBOARD; // This object can get the focus
    this.checked = false;

    // *** Properties and Attributes *** //

    this.$booleanProperties["checked"] = true;
    this.$supportedProperties.push("value", "checked", "label", "values");

    /**
     * @attribute {String}  value   Sets or gets the value of this element.
     */
    this.$propHandlers["value"] = function(value) {
        value = (typeof value == "string" ? value.trim() : value);

        if (value == "" && this["default"])
            value = this.value = apf.isTrue(this["default"]);

        if (this.$values) {
            this.checked = (typeof value != "undefined" && value !== null
                && value.toString() == this.$values[0].toString());
        }
        else {
            this.checked = apf.isTrue(value);
        }
        
        if (this.checked)
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Checked");
        else
            apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Checked"]);
    };

    /**
     * @attribute {Boolean} checked  Sets or gets whether the element is in the checked state.
     */
    this.$propHandlers["checked"] = function(value) {
        if (!this.$values) {
            if (this.getAttribute("values"))
                this.$propHandler["values"].call(this, this.getAttribute("values"));
            //else
                //this.$values = [true, false];
        }
        this.setProperty("value", this.$values ? this.$values[value ? 0 : 1] : true);
    };

    /**
     * @attribute {String}  label Sets or gets the caption of the label explaining what
     * the meaning of the checked state of this element is.
     */
    this.$propHandlers["label"] = function(value) {
        if (!this.$ext)
            return;

        var lbl = this.$getLayoutNode("main", "label", this.$ext);
        if (!lbl)
            return;
        
        if (lbl.nodeType == 1)
            lbl.innerHTML = value;
        else
            lbl.nodeValue = value;
    };

    /**
     * @attribute {String}  values Sets or gets a pipe seperated list of two values which
     * correspond to the two states of the checkbox. The first for the checked
     * state, the second for the unchecked state.
     */
    this.$propHandlers["values"] = function(value) {
        this.$values = typeof value == "string"
            ? value.split("\|")
            : (value || [1, 0]);

        this.$propHandlers["value"].call(this, this.value);
    };

    // *** Public Methods *** //

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the [[apf.checkbox.values]] attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        if (!this.$values) return;
        this.setProperty("value", value, false, true);
    };

    /**
     * Returns the current value.
     */
    this.getValue = function(){
        return this.xmlRoot ? (this.$values 
            ? this.$values[this.checked ? 0 : 1]
            : this.checked) : this.value;
    };

    /**
     * Sets the checked state (and related value).
     */
    this.check = function(){
        this.setProperty("value", this.$values
            ? this.$values[0]
            : true, false, true);
    };

    /**
     * Sets the unchecked state (and related value).
     */
    this.uncheck = function(){
        this.setProperty("value", this.$values
            ? this.$values[1]
            : false, false, true);
    };
    
    

    // *** Private state handling methods *** //

    this.addEventListener("$clear", function(){
        this.setProperty("value", this.$values ? this.$values[1] : false);
    });

    this.$enable = function(){
        if (this.$input) this.$input.disabled = false;
        this.$doBgSwitch(1);
    };

    this.$disable = function(){
        if (this.$input) this.$input.disabled = true;
        this.$doBgSwitch(4);
    };

    this.$setState = function(state, e, strEvent) {
        //if (this.disabled) return;

        this.$doBgSwitch(this.states[state]);
        this.$setStyleClass(this.$ext, (state != "Out" ? this.$baseCSSname + state : ""),
            [this.$baseCSSname + "Down", this.$baseCSSname + "Over"]);
        this.state = state; // Store the current state so we can check on it coming here again.

        if (strEvent)
            this.dispatchEvent(strEvent, {htmlEvent: e});

        /*if (state == "Down")
            apf.cancelBubble(e, this);
        else
            e.cancelBubble = true;*/
    };

    this.$clickHandler = function(){
        //this.checked = !this.checked;
        this.change(this.$values
            ? this.$values[(!this.checked) ? 0 : 1]
            : !this.checked);

        
        if (this.validate) //@todo rewrite button
            this.validate(true);
        

        return true;
    };

    // *** Init *** //

    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$input = this.$getLayoutNode("main", "input", this.$ext);
        this.$notfromext = this.$input && this.$input != this.$ext;

        this.$setupEvents();
    };

    this.$childProperty = "label";

    
    this.addEventListener("$skinchange", function(){
        if (this.label)
            this.$propHandlers["label"].call(this, this.label);
    })
    
    
    
}).call(apf.checkbox.prototype = new apf.BaseButton());

apf.aml.setElement("checkbox", apf.checkbox);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Element giving the user a visual choice to pick a color just like Photoshop
 * does it!
 *
 * @constructor
 * @define colorpicker
 *
 *
 * @author      Mike de Boer (mike AT javeline DOT com)
 * @version     %I%, %G%
 * @since       3.0
 *
 * @inherits apf.StandardBinding
 * @inherits apf.DataAction
 *
 * @attribute {String} value the color that is selected in the color picker.
 *
 * @binding value  Determines the way the value for the element is retrieved
 * from the bound data.
 * Example:
 * Sets the color based on data loaded into this component.
 * <code>
 *  <a:model id="mdlColor">
 *      <data color="#000099"></data>
 *  </a:model>
 *  <a:colorpicker 
 *    model = "mdlColor" 
 *    value = "[@color]" />
 * </code>
 */
apf.colorpicker = function(struct, tagName) {
    this.$init(tagName || "colorpicker", apf.NODE_VISIBLE, struct);
};

(function(){
    this.value = "ff0000";
    this.changeTimer = null;

    var c = apf.color;

    this.$supportedProperties.push("color", "red", "green", "blue", "hue",
        "saturation", "brightness", "hex", "skin-textbox", "skin-spinner");

    this.$propHandlers["red"] = 
    this.$propHandlers["green"] = 
    this.$propHandlers["blue"] = 
    this.$propHandlers["hue"] = 
    this.$propHandlers["saturation"] = 
    this.$propHandlers["brightness"] = 
    this.$propHandlers["hex"] = function(val, prop) {
        if (this.$ignoreSetProperty)
            return;
        clearTimeout(this.changeTimer);
        var _self = this;
        this.changeTimer = $setTimeout(function() {
            _self.$change(prop);
        });
    };

    this.$propHandlers["value"] = function(val) {
        this.$restoreOriginal();
    };

    this.$restoreOriginal = function() {
        var hsb = c.hexToHSB(this.value);
        this.hue = hsb.h;
        this.saturation = hsb.s;
        this.brightness = hsb.b;
        this.$change();
        this.oCustomColor.style.backgroundColor = 
            (this.value.substr(0, 1) != "#" ? "#" : "") + this.value;
    };

    this.$change = function(prop) {
        if (!prop || /hsb|hue|saturation|brightness/.test(prop)) {
            prop = "hsb";
            var hsb = c.fixHSB({h: this.hue, s: this.saturation, b: this.brightness});
            var hex = c.HSBToHex(hsb);
            var rgb = c.HSBToRGB(hsb);
        } else if (/red|green|blue|rgb/.test(prop)) {
            prop = "rgb";
            var rgb = c.fixRGB({r: this.red, g: this.green, b: this.blue});
            var hex = c.RGBToHex(rgb);
            var hsb = c.RGBToHSB(rgb);
        } else {
            prop = "hex";
            var hex = c.fixHex(this.hex);
            var hsb = c.hexToHSB(hex);
            var rgb = c.hexToRGB(hex);
        }

        this.oNewColor.style.backgroundColor = "#" + hex;

        this.$ignoreSetProperty = true;
        if (prop != "rgb") {
            this.setProperty("red", rgb.r);
            this.setProperty("green", rgb.g);
            this.setProperty("blue", rgb.b);
        }
        if (prop != "hsb") {
            this.setProperty("hue", hsb.h);
            this.setProperty("saturation", hsb.s);
            this.setProperty("brightness", hsb.b);
        }
        if (prop != "hex")
            this.setProperty("hex", hex);
        this.$ignoreSetProperty = false;
        
        this.oSelector.style.background = "#" + c.HSBToHex({h: hsb.h, s: 100, b: 100});
        this.oHue.style.top = parseInt(150 - 150 * hsb.h / 360, 10) + "px";
        this.oSelectorInd.style.left = parseInt(150 * hsb.s / 100, 10) + "px";
        this.oSelectorInd.style.top = parseInt(150 * (100 - hsb.b) / 100, 10) + "px";
    };

    this.$draw = function() {
        if (!this.id)
            this.setProperty("id", "colorpicker" + this.$uniqueId);

        //Build Main Skin
        this.$ext = this.$getExternal();
        this.oSelector = this.$getLayoutNode("main", "selector", this.$ext);
        this.oSelectorInd = this.$getLayoutNode("main", "selector_indic", this.$ext);
        this.oHue = this.$getLayoutNode("main", "hue", this.$ext);
        this.oNewColor = this.$getLayoutNode("main", "newcolor", this.$ext);
        this.oCustomColor = this.$getLayoutNode("main", "customcolor", this.$ext);
        this.oInputs = this.$getLayoutNode("main", "inputs", this.$ext);

        this.$restoreOriginal();

        //attach behaviours
        var _self = this,
            doc = (!document.compatMode || document.compatMode == 'CSS1Compat')
                ? document.html : document.body;
        function stopMoving() {
            document.onmousemove = document.onmouseup = null;
            _self.$change();
            return false;
        }
        function selectorDown() {
            var el = this,
                pos = apf.getAbsolutePosition(el);
            
            function selectorMove(e) {
                e = e || event;
                var pageX = e.pageX || e.clientX + (doc ? doc.scrollLeft : 0),
                    pageY = e.pageY || e.clientY + (doc ? doc.scrollTop  : 0);
                // only the saturation and brightness change...
                _self.brightness = parseInt(100 * (150 - Math.max(0, Math.min(150,
                    (pageY - pos[1])))) / 150, 10);
                _self.saturation = parseInt(100 * (Math.max(0, Math.min(150,
                    (pageX - pos[0])))) / 150, 10);
                _self.$change();
                pos = apf.getAbsolutePosition(el);
                return false;
            }
            document.onmousemove = selectorMove;
            document.onmouseup = function(e) {
                selectorMove(e);
                return stopMoving(e);
            };
        }
        
        function hueDown(e) {
            var el = this,
                pos = apf.getAbsolutePosition(el);

            function hueMove(e) {
                e = e || event;
                var pageY = e.pageY || e.clientY + (doc ? doc.scrollTop : 0);
                _self.hue = parseInt(360 * (150 - Math.max(0,
                    Math.min(150, (pageY - pos[1])))) / 150, 10);
                _self.$change();
                pos = apf.getAbsolutePosition(el);
            }
            document.onmousemove = hueMove;
            document.onmouseup = function(e) {
                hueMove(e);
                return stopMoving(e);
            };
        }
        this.oSelector.onmousedown = selectorDown;
        this.oHue.parentNode.onmousedown = hueDown;
        this.oCustomColor.onmousedown = function() {
            _self.$restoreOriginal();
        };

        function spinnerChange(e) {
            var o = e.currentTarget;
            var isRGB = false;
            if (o.id.indexOf("hue") > -1) {
                _self.hue = e.value;
            }
            else if (o.id.indexOf("saturation") > -1) {
                _self.saturation = e.value;
            }
            else if (o.id.indexOf("brightness") > -1) {
                _self.brightness = e.value;
            }
            else if (o.id.indexOf("red") > -1) {
                _self.red = e.value;
                isRGB = true;
            }
            else if (o.id.indexOf("green") > -1) {
                _self.green = e.value;
                isRGB = true;
            }
            else if (o.id.indexOf("blue") > -1) {
                _self.blue = e.value;
                isRGB = true;
            }

            _self.$change(isRGB ? "red" : "hue");
        }

        //append APF widgets for additional controls
        var skin = apf.getInheritedAttribute(this.parentNode, "skinset");
        new apf.hbox({
            htmlNode: this.oInputs,
            skinset: skin,
            left: 212,
            top: 52,
            width: 150,
            padding: 3,
            childNodes: [
                new apf.vbox({
                    padding: 3,
                    edge: "0 10 0 0",
                    childNodes: [
                        new apf.hbox({
                            childNodes: [
                                new apf.label({
                                    width: 14,
                                    caption: "R:",
                                    "for": this.id + "_red"
                                }),
                                new apf.spinner({
                                    id: this.id + "_red",
                                    skin: this["skin-spinner"],
                                    realtime: true,
                                    width: 45,
                                    min: 0,
                                    max: 255,
                                    value: "{" + this.id + ".red}",
                                    onafterchange: spinnerChange
                                })
                            ]
                        }),
                        new apf.hbox({
                            edge: "0 0 3 0",
                            childNodes: [
                                new apf.label({
                                    width: 14,
                                    caption: "G:",
                                    "for": this.id + "_green"
                                }),
                                new apf.spinner({
                                    id: this.id + "_green",
                                    skin: this["skin-spinner"],
                                    realtime: true,
                                    width: 45,
                                    min: 0,
                                    max: 255,
                                    value: "{" + this.id + ".green}",
                                    onafterchange: spinnerChange
                                })
                            ]
                        }),
                        new apf.hbox({
                            edge: "0 0 3 0",
                            childNodes: [
                                new apf.label({
                                    width: 14,
                                    caption: "B:",
                                    "for": this.id + "_blue"
                                }),
                                new apf.spinner({
                                    id: this.id + "_blue",
                                    skin: this["skin-spinner"],
                                    realtime: true,
                                    width: 45,
                                    min: 0,
                                    max: 255,
                                    value: "{" + this.id + ".blue}",
                                    onafterchange: spinnerChange
                                })
                            ]
                        })
                    ]
                }),
                new apf.vbox({
                    padding: 3,
                    childNodes: [
                        new apf.hbox({
                            childNodes: [
                                new apf.label({
                                    width: 14,
                                    caption: "H:",
                                    "for": this.id + "_hue"
                                }),
                                new apf.spinner({
                                    id: this.id + "_hue",
                                    skin: this["skin-spinner"],
                                    realtime: true,
                                    width: 45,
                                    min: 0,
                                    max: 360,
                                    value: "{" + this.id + ".hue}",
                                    onafterchange: spinnerChange
                                })
                            ]
                        }),
                        new apf.hbox({
                            edge: "0 0 3 0",
                            childNodes: [
                                new apf.label({
                                    width: 14,
                                    caption: "S:",
                                    "for": this.id + "_saturation"
                                }),
                                new apf.spinner({
                                    id: this.id + "_saturation",
                                    skin: this["skin-spinner"],
                                    realtime: true,
                                    width: 45,
                                    min: 0,
                                    max: 100,
                                    value: "{" + this.id + ".saturation}",
                                    onafterchange: spinnerChange
                                })
                            ]
                        }),
                        new apf.hbox({
                            edge: "0 0 3 0",
                            childNodes: [
                                new apf.label({
                                    width: 14,
                                    caption: "B:",
                                    "for": this.id + "_brightness"
                                }),
                                new apf.spinner({
                                    id: this.id + "_brightness",
                                    skin: this["skin-spinner"],
                                    realtime: true,
                                    width: 45,
                                    min: 0,
                                    max: 100,
                                    value: "{" + this.id + ".brightness}",
                                    onafterchange: spinnerChange
                                })
                            ]
                        })
                    ]
                })
            ]
        });

        new apf.label({
            htmlNode: this.oInputs,
            skinset: skin,
            left: 212,
            top: 144,
            width: 14,
            caption: "#",
            "for": this.id + "_hex"
        });

        this.$input = new apf.textbox({
            htmlNode: this.oInputs,
            skinset: skin,
            skin: this["skin-textbox"],
            mask: "<XXXXXX;;_",
            left: 222,
            top: 140,
            width: 60,
            value: "{" + this.id + ".hex}",
            onafterchange: function(e) {
                _self.hex = c.fixHex(e.value);
                var hsb = c.hexToHSB(_self.hex);
                _self.hue = hsb.h;
                _self.saturation = hsb.s;
                _self.brightness = hsb.b;
                _self.$change();
            }
        });
    };

    this.$destroy = function() {
        this.$ext = this.oSelector = this.oSelectorInd = this.oHue =
            this.oNewColor = this.oCustomColor = this.oInputs = null;
    };

}).call(apf.colorpicker.prototype = new apf.StandardBinding());


apf.aml.setElement("colorpicker", apf.colorpicker);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * All elements within the comment tag are ignored by the parser.
 *
 * @class apf.comment
 * @define comment
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.comment = function(){
    this.$init("comment", apf.NODE_HIDDEN);
};

apf.comment.prototype = new apf.AmlComment();
apf.aml.setElement("comment", apf.comment);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element specifies which menu is shown when a
 * contextmenu is requested by a user for a AML node.
 * 
 * #### Example
 *
 * This example shows a list that shows the mnuRoot menu when the user
 * right clicks on the root {@link term.datanode data node}. Otherwise the `mnuItem` menu is
 * shown.
 *
 * ```xml, demo
 *  <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:menu id="ctxMenu">
 *       <a:item>Choice 1!</a:item>
 *       <a:item>Choice 2!</a:item>
 *   </a:menu>
 *   <a:list width="300" id="list1">
 *       <a:contextmenu menu="ctxMenu" />
 *       <a:item>The Netherlands</a:item>
 *       <a:item>United States of America</a:item>
 *       <a:item>Poland</a:item>
 *   </a:list>
 *   <!-- endcontent -->
 *   Right-click on the list to reveal the context menu!
 *  </a:application>
 * ```
 *
 * @class apf.contextmenu
 * @define contextmenu
 * @inherits apf.AmlElement
 * @selection
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
/**
 * @attribute {String} menu  Sets or gets the id of the menu element.
 */
/**
 * @attribute {String} select Sets or gets the XPath executed on the selected element of the databound element which determines whether this context menu is shown.
 *
 * 
 */
apf.contextmenu = function(){
    this.$init("contextmenu", apf.NODE_HIDDEN);
};

(function(){
    this.$amlNodes = [];
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        "match" : 1
    }, this.$attrExcludePropBind);
    
    this.register = function(amlParent) {
        if (!amlParent.contextmenus)
            amlParent.contextmenus = [];
        amlParent.contextmenus.push(this);
    };
    
    this.unregister = function(amlParent) {
        amlParent.contextmenus.remove(this);
    };
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.register(this.parentNode);
    });
}).call(apf.contextmenu.prototype = new apf.AmlElement());

apf.aml.setElement("contextmenu", apf.contextmenu);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element  provides a sortable, selectable grid containing scrollable 
 * information. The grid columns can be reordered and resized.
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="mdl">
 *         <data>
 *             <news name="Ajax" icon="note.png" count="54"></news>
 *             <news name="Ajax.org Platform" icon="note_b.png" count="3237">
 *             </news>
 *             <news name="Android" icon="note.png" count="2"></news>
 *             <news name="Apple" icon="note.png" count="11"></news>
 *             <news name="Aptana" icon="note.png" count="30"></news>
 *             <news name="Canvas" icon="note.png" count="115"></news>
 *             <news name="Chrome" icon="note.png" count="9"></news>
 *             <news name="ColdFusion" icon="note.png" count="10"></news>
 *             <news name="CSS" icon="note.png" count="176"></news>
 *             <news name="Dojo" icon="note.png" count="224"></news>
 *             <news name="JavaScript" icon="note.png" count="1218"></news>
 *             <news name="JSON" icon="note.png" count="59"></news>
 *             <news name="Mac" icon="note.png" count="1"></news>
 *             <news name="Mozilla" icon="note.png" count="11"></news>
 *         </data>
 *   </a:model>
 *   <a:datagrid 
 *     width = "500" 
 *     id = "dg1"
 *     model = "mdl"
 *     height = "150"> 
 *       <a:each match="[news]"> 
 *           <a:column 
 *             type = "icon" 
 *             width = "16" 
 *             value = "[@icon]" />
 *           <a:column 
 *             caption = "Name" 
 *             value = "[@name]"
 *             width = "100%" /> 
 *           <a:column 
 *             caption = "Article counter" 
 *             width = "180" 
 *             value = "[@count]" />
 *       </a:each> 
 *   </a:datagrid>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.datagrid
 * @define datagrid
 * @selection
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @inherits apf.Cache
 * @inherits apf.StandardBinding
 * @inherits apf.Rename
 * @inherits apf.MultiselectBinding
 *
 */
/**
 * @binding invalidmsg  Determines the error message that is shown when a cell is not valid.
 */
/**
 *  @binding description Determines the text that is displayed under the expanded row.
 */
apf.datagrid = function(struct, tagName) {
    this.$init(tagName || "datagrid", apf.NODE_VISIBLE, struct);
    
    this.$headings = [],
    this.$cssRules = []; //@todo Needs to be reset;
    this.$lastOpened = {};
    
    this.$editors = {};
    
    
    this.$dynCssClasses = [];
    
};

(function(){
    var treeState = this.$treeState;

    
    this.implement(
        apf.DataAction
    );
    

    /*this.$init(function() {
        this.addEventListener("keydown", keyHandler, true);
    });*/

    this.bufferselect = false;
    this.$useTable = false;
    this.$focussable = true;
    this.$isWindowContainer = -1;

    this.$widthdiff = 0;
    this.$defaultwidth = 0;
    this.$useiframe = 0;
    this.$needsDepth = true;
    this.$fixed = 0;

    
    this.canrename = false; //@todo remove rename from basetree and move to tree.js
    

    /**
     * @attribute {Boolean} iframe  Sets or gets whether this element is rendered inside an iframe. This is only supported for IE. Default is `false` for datagrid and `true` for spreadsheet and propedit.
     */
    this.$booleanProperties["iframe"] = true;

     
    
    /**
     * @attribute {String} mode Sets or gets the way this element interacts with the user.
     *   
     * Possible values include:
     *
     *   - `check`:  The user can select a single item from this element. The selected item is indicated.
     *   - `radio`: The user can select multiple items from this element. Each selected item is indicated.
     */
    this.$mode = 0;
    this.$propHandlers["mode"] = function(value) {
        if ("check|radio".indexOf(value) > -1) {
            if (!this.hasFeature(apf.__MULTICHECK__))
                this.implement(apf.MultiCheck);
            
            this.addEventListener("afterrename", $afterRenameMode); //what does this do?
            
            this.multicheck = value == "check"; //radio is single
            this.$mode = this.multicheck ? 1 : 2;
        }
        else {
            //@todo undo actionRules setting
            this.removeEventListener("afterrename", $afterRenameMode);
            //@todo unimplement??
            this.$mode = 0;
        }
    };
    
    //@todo apf3.0 retest this completely
    function $afterRenameMode(){
    }
    
    
    
    this.$propHandlers["options"] = function(value) {
        for (var i = 0, l = this.$headings.length; i < l; i++) {
            this.$headings[i].setAttribute("options", value);
        }
    };
    
    function scrollIntoView(){
        var Q = (this.current || this.$selected),
            o = this.$container;
        o.scrollTop = (Q.offsetTop) - 21;
    }

    // *** Keyboard Support *** //
    
    
    /*function keyHandler(e) {
        var key = e.keyCode,
            ctrlKey = e.ctrlKey,
            shiftKey = e.shiftKey,
            selHtml = this.$selected || this.$caret;
        
        if (!e.force && (!selHtml || this.renaming)) //@todo how about allowdeselect?
            return;

        var selXml = this.caret || this.selected,
            oInt = this.$useiframe ? this.oDoc.documentElement : this.$container,
            margin, node, hasScroll, hasScrollX, hasScrollY, items, lines;

        switch (key) {
            case 13:
                if (this.$tempsel)
                    this.$selectTemp();
            
                this.choose(selHtml);
                break;
            case 32:
                if (ctrlKey || !this.isSelected(this.caret))
                    this.select(this.caret, true);
                return false;
            case 109:
            case 46:
                //DELETE
                if (this.disableremove) 
                    return;
                    
                if (this.celledit) {
                    this.rename(this.caret || this.selected, "");
                    return;
                }
            
                if (this.$tempsel)
                    this.$selectTemp();
            
                this.remove(this.mode ? this.caret : null); //this.mode != "check"
                break;
            case 36:
                //HOME
                this.$setTempSelected (this.getFirstTraverseNode(), false, shiftKey);
                this.$container.scrollTop = 0;
                return false;
            case 35:
                //END
                this.$setTempSelected (this.getLastTraverseNode(), false, shiftKey);
                this.$container.scrollTop = this.$container.scrollHeight;
                return false;
            case 107:
                //+
                if (this.more)
                    this.startMore();
                break;
            case 37:
                //LEFT
                if (this.$tempsel)
                    this.$selectTemp();
                    
                if (this.cellselect) {
                    if (this.$lastcell) {
                        if (this.$lastcell.previousSibling) {
                            this.selectCell({target:this.$lastcell.previousSibling},
                                this.$selected);
                        }
                    }
                    else {
                        this.selectCell({target:this.$selected.firstChild}, 
                            this.$selected);
                    }
                }
                else if (this.$withContainer)
                    this.slideToggle(this.$caret || this.$selected, 2)
                return false;
            case 107:
            case 39:
                //RIGHT
                if (this.$tempsel)
                    this.$selectTemp();
                    
                if (this.cellselect) {
                    if (this.$lastcell) {
                        if (this.$lastcell.nextSibling) {
                            this.selectCell({target:this.$lastcell.nextSibling},
                                this.$selected);
                        }
                    }
                    else {
                        this.selectCell({target:this.$selected.firstChild}, 
                            this.$selected);
                    }
                }
                else if (this.$withContainer)
                    this.slideToggle(this.$caret || this.$selected, 1)
                    
                return false;
            case 38:
                //UP
                if (!selXml && !this.$tempsel) 
                    return false;
                    
                node = this.$tempsel 
                    ? apf.xmldb.getNode(this.$tempsel) 
                    : selXml;

                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScroll = oInt.scrollHeight > oInt.offsetHeight;
                items = Math.floor((oInt.offsetWidth
                    - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth
                    + margin[1] + margin[3]));
                
                node = this.getNextTraverseSelected(node, false, items);
                if (node)
                    this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return false;

                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop <= oInt.scrollTop) {
                    oInt.scrollTop = (Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items
                      ? 0
                      : selHtml.offsetTop - margin[0])
                        - parseInt(apf.getStyle(oInt, "paddingTop"));
                }
                return false;
            case 40:
                //DOWN
                if (!selXml && !this.$tempsel) 
                    return false;
                    
                node = this.$tempsel 
                    ? apf.xmldb.getNode(this.$tempsel) 
                    : selXml;
                
                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScroll = oInt.scrollHeight > oInt.offsetHeight;
                items = Math.floor((oInt.offsetWidth
                    - (hasScroll ? 15 : 0)) / (selHtml.offsetWidth
                    + margin[1] + margin[3]));
                
                node = this.getNextTraverseSelected(node, true, items);
                if (node)
                   this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return false;
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop + selHtml.offsetHeight
                  > oInt.scrollTop + oInt.offsetHeight) // - (hasScroll ? 10 : 0)
                    oInt.scrollTop = selHtml.offsetTop
                        - oInt.offsetHeight + selHtml.offsetHeight
                        + margin[0]; //+ (hasScroll ? 10 : 0)
                
                return false;
            case 33:
                //PGUP
                if (!selXml && !this.$tempsel) 
                    return false;
                    
                node = this.$tempsel 
                    ? apf.xmldb.getNode(this.$tempsel) 
                    : selXml;
                
                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScrollY = oInt.scrollHeight > oInt.offsetHeight;
                hasScrollX = oInt.scrollWidth > oInt.offsetWidth;
                items = Math.floor((oInt.offsetWidth
                    - (hasScrollY ? 15 : 0)) / (selHtml.offsetWidth
                    + margin[1] + margin[3])) || 1;
                lines = Math.floor((oInt.offsetHeight
                    - (hasScrollX ? 15 : 0)) / (selHtml.offsetHeight
                    + margin[0] + margin[2]));
                
                node = this.getNextTraverseSelected(node, false, items * lines);
                if (!node)
                    node = this.getFirstTraverseNode();
                if (node)
                   this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return false;
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop < oInt.scrollTop) {
                    oInt.scrollTop = (Array.prototype.indexOf.call(this.getTraverseNodes(), node) < items
                      ? 0
                      : selHtml.offsetTop - margin[0]) 
                        - parseInt(apf.getStyle(oInt, "paddingTop"));
                }
                return false;
            case 34:
                //PGDN
                if (!selXml && !this.$tempsel) 
                    return false;

                node = this.$tempsel 
                    ? apf.xmldb.getNode(this.$tempsel) 
                    : selXml;
                
                margin = apf.getBox(apf.getStyle(selHtml, "margin"));
                hasScrollY = oInt.scrollHeight > oInt.offsetHeight;
                hasScrollX = oInt.scrollWidth > oInt.offsetWidth;
                items = Math.floor((oInt.offsetWidth - (hasScrollY ? 15 : 0))
                    / (selHtml.offsetWidth + margin[1] + margin[3])) || 1;
                lines = Math.floor((oInt.offsetHeight - (hasScrollX ? 15 : 0))
                    / (selHtml.offsetHeight + margin[0] + margin[2]));
                
                node = this.getNextTraverseSelected(selXml, true, items * lines);
                if (!node)
                    node = this.getLastTraverseNode();
                if (node)
                   this.$setTempSelected (node, ctrlKey, shiftKey);
                else
                    return false;
                
                selHtml = apf.xmldb.findHtmlNode(node, this);
                if (selHtml.offsetTop + selHtml.offsetHeight
                  > oInt.scrollTop + oInt.offsetHeight) // - (hasScrollY ? 10 : 0)
                    oInt.scrollTop = selHtml.offsetTop
                        - oInt.offsetHeight + selHtml.offsetHeight
                        + margin[0]; //+ 10 + (hasScrollY ? 10 : 0)
                return false;
            default:
                if (this.celledit) {
                    if (!ctrlKey && !e.altKey && (key > 46 && key < 112 || key > 123))
                        this.startRename(null, true);
                    return;
                }
                else if (key == 65 && ctrlKey) {
                    this.selectAll();
                    return false;
                } 
                //@todo make this work with the sorted column
                else if (this.caption || (this.bindingRules || {})["caption"]) {
                    if (!this.xmlRoot) return;
                    
                    //this should move to a onkeypress based function
                    if (!this.lookup || new Date().getTime()
                      - this.lookup.date.getTime() > 300)
                        this.lookup = {
                            str: "",
                            date: new Date()
                        };
                    
                    this.lookup.str += String.fromCharCode(key);
    
                    var nodes = this.getTraverseNodes(); //@todo start at current indicator
                    for (var v, i = 0; i < nodes.length; i++) {
                        v = this.$applyBindRule("caption", nodes[i]);
                        if (v && v.substr(0, this.lookup.str.length)
                          .toUpperCase() == this.lookup.str) {
                            
                            if (!this.isSelected(nodes[i])) {
                                if (this.mode == "check")
                                    this.setCaret(nodes[i]);
                                else
                                    this.select(nodes[i]);
                            }
                            
                            if (selHtml)
                                this.$container.scrollTop = selHtml.offsetTop
                                    - (this.$container.offsetHeight
                                    - selHtml.offsetHeight) / 2;
                            return;
                        }
                    }
                    return;
                }
                break;
        };
        
        this.lookup = null;
        //return false;
    }*/
    
    
    
    // *** Focus *** //
    // Too slow for IE
    
    
    this.$getCaptionElement = function(){
        if (!this.$selected) 
            return false;

        var nodes = this.$head.childNodes,
            htmlNodes = this.$selected.childNodes, i = 0;
            
        nodeIter = htmlNodes[i];
        while (nodeIter) {
            if (nodeIter.nodeType != 1) {
                nodeIter = nodeIter.nextSibling;
                continue;
            }
            
            h = apf.all[nodes[i].getAttribute("hid")];
            if (h.tree || h.rename)
                return this.$getLayoutNode(h.tree ? "treecell" : "cell", "caption", nodeIter) || nodeIter;
            
            i++;
            nodeIter = nodeIter.nextSibling;
        }
        
        throw new Error("Datagrid without rename column specified");
    };
    
    
    this.$focus = function(){
        if (!this.$ext || (apf.isIE && this.$useiframe && this.cssfix)) //@todo fix this by fixing focussing for this component
            return;

        this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
        
        if (this.oDoc)
            this.$setStyleClass(this.oDoc.documentElement, this.$baseCSSname + "Focus");
    };

    this.$blur = function(){
        //@todo fix this by fixing focussing for this component
        if (!this.$ext || (apf.isIE && this.$useiframe && this.cssfix))
            return;

        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
        
        if (this.oDoc)
            this.$setStyleClass(this.oDoc.documentElement, "", [this.$baseCSSname + "Focus"]);
        
        hideEditor.call(this);
    };
    
    // *** Databinding *** //
    
    this.addEventListener("bindingsload", this.$loaddatabinding = function(e) {
        var rules = e.bindings["column"];
        if (!rules || !rules.length)
            return;
        
        this.$cssRules = [];
        this.$headings = rules;
        
        this.clearAllCache();

        var fixed = 0, found = false;
        for (var h, i = 0, l = rules.length; i < l; i++) {
            h = rules[i];
            
            
            
            if (h.visible !== false) {
                if (!h.$isPercentage)
                    fixed += parseFloat(h.$width) || 0;
                else 
                    found = true;
            }
        }
        
        if (!found) { //@todo removal???
            this.$isFixedGrid = true;
            this.$setStyleClass(this.$ext, "fixed");
            
            if (this.$useiframe)
                this.$setStyleClass(this.oDoc.documentElement, "fixed");
        }
        else {
            //@todo remove
        }

        if (fixed > 0 && !this.$isFixedGrid) {
            var vLeft = fixed;
            
            //first column has total -1 * fixed margin-left. - 5
            //cssRules[0][1] += ";margin-left:-" + vLeft + "px;";
            //cssRules[1][1] += ";margin-left:-" + vLeft + "px;";
            this.$cssRules.push(["." + this.$baseCSSname + " .row" + this.$uniqueId,
                "padding-right:" + vLeft + "px;"]); //margin-right:-" + vLeft + "px
        
            //headings and records have same padding-right
            this.$container.style.paddingRight = (vLeft - 1) + "px";
            this.$head.style.paddingRight = (vLeft - 2) + "px";
        }
        
        this.$fixed = fixed;
        this.$first = 0;

        this.$withContainer = e.bindings.description ? true : false;

        //Activate CSS Rules
        apf.importStylesheet(this.$cssRules, window);
        
        if (this.$useiframe)
            apf.importStylesheet(this.$cssRules, this.oWin);
    });
    
    this.$initNode = function(xmlNode, state, Lid, depth) {
        //Build Row
        this.$getNewContext("item");
        var oRow = this.$getLayoutNode("item");
        oRow.setAttribute("id", Lid);

        //@todo if treearch
        oRow.setAttribute("class", oRow.getAttribute("class") + " "  
            + treeState[state] + " item" + this.$uniqueId);//"width:" + (totalWidth+40) + "px");
        this.$setStyleClass(this.$getLayoutNode("item", "container"), treeState[state])
        
        oRow.setAttribute("ondblclick", 'var o = apf.lookup(' + this.$uniqueId + ');o.choose(null, true);'
            + (this.$withContainer ? 'o.slideToggle(this, null, true);' : '')
            + (this.celledit && !this.namevalue ? 'o.startRename(null, null, true);' : ''));
        
        if (this.hasFeature(apf.__DRAGDROP__)) {
            oRow.setAttribute("onmouseout", 'this.hasPassedDown = false;');
            oRow.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + ');\
                var xmlNode = apf.xmldb.findXmlNode(this);\
                 var isSelected = o.isSelected(xmlNode);\
                 this.hasPassedDown = true;\
                 if (!o.hasFeature(apf.__DRAGDROP__) || !isSelected && !apf.getCtrlKey(event))\
                     o.select(this, apf.getCtrlKey(event), event.shiftKey, -1);'
                + (this.cellselect || this.namevalue ? 'o.selectCell(event, this, isSelected);' : ''));
            
            oRow.setAttribute("onmouseup", 'if (!this.hasPassedDown) return;\
                var o = apf.lookup(' + this.$uniqueId + ');\
                 var xmlNode = apf.xmldb.findXmlNode(this);\
                 var isSelected = o.isSelected(xmlNode);\
                 if (o.hasFeature(apf.__DRAGDROP__))\
                     o.select(this, apf.getCtrlKey(event), event.shiftKey, -1);');
        } //@todo add DRAGDROP ifdefs
        else {
            oRow.setAttribute("onmousedown", 'var o = apf.lookup(' + this.$uniqueId + ');\
                var wasSelected = o.$selected == this;\
                o.select(this, apf.getCtrlKey(event), event.shiftKey, -1);'
                + (this.cellselect || this.namevalue ? 'o.selectCell(event, this, wasSelected);' : ''));
        }
        
        //Build the Cells
        for (var cellType, cell, h, i = 0; i < this.$headings.length; i++) {
            h = this.$headings[i];
            
            if (h.tree && h.check) {
                cellType = "treecheckcell";
                
                this.$getNewContext("treecheckcell");
                cell = this.$getLayoutNode("treecheckcell");
                var oc = this.$getLayoutNode("treecheckcell", "openclose");
                oc.setAttribute("style", "margin-left:" + (((depth||0)) * 15 + 4) + "px;");
                oc.setAttribute("onmousedown",
                    "var o = apf.lookup(" + this.$uniqueId + ");\
                    o.slideToggle(this, null, null, true);\
                    event.cancelBubble = true;\
                    apf.window.$mousedown(event);");
            
                oc.setAttribute("ondblclick", "event.cancelBubble = true");
            }
            else if (h.tree) {
                cellType = "treecell";
                
                this.$getNewContext("treecell");
                cell = this.$getLayoutNode("treecell");
                var oc = this.$getLayoutNode("treecell", "openclose");
                oc.setAttribute("style", "margin-left:" + (((depth||0)) * 15 + 4) + "px;");
                oc.setAttribute("onmousedown",
                    "var o = apf.lookup(" + this.$uniqueId + ");\
                    o.slideToggle(this, null, null, true);\
                    event.cancelBubble = true;\
                    apf.window.$mousedown(event);");
            
                oc.setAttribute("ondblclick", "event.cancelBubble = true");
                
                /*cell.setAttribute("style", "background-position: " 
                    + ((((depth||0)+1) * 15) - 10) + "px 50%");*/
            }
            else {
                
                cellType = h.check ? "checkcell" : "cell";
                
                
                this.$getNewContext(cellType);
                cell = this.$getLayoutNode(cellType);
            }
            
            
            if (this.$mode && h.check) {
                var elCheck = this.$getLayoutNode(cellType, "check");
                if (elCheck) {
                    elCheck.setAttribute("onmousedown",
                        "var o = apf.lookup(" + this.$uniqueId + ");\
                        o.checkToggle(this, true);\o.$skipSelect = true;");
    
                    if (apf.isTrue(this.$applyBindRule("checked", xmlNode))) {
                        this.$checkedList.push(xmlNode);
                        this.$setStyleClass(oRow, "checked");
                    }
                    else if (this.isChecked(xmlNode))
                        this.$setStyleClass(oRow, "checked");
                }
                else {
                    
                    return false;
                }
            }
            
            
            apf.setStyleClass(cell, h.$className);
            
            if (h.css)
                apf.setStyleClass(cell, (apf.lm.compile(h.css, {nostring: true}))(xmlNode)); //@todo cashing of compiled function?
            
            if (h.icon) {
                var node = this.$getLayoutNode(cellType, "caption", oRow.appendChild(cell));
                    node = (node.nodeType == 1 && node || node.parentNode);
                    node.setAttribute("style", "background-image:url(" 
                        + apf.getAbsolutePath(this.iconPath, 
                            ((h.cicon || h.$compile("icon", {nostring: true}))(xmlNode) || ""))
                        + ");");
                    this.$setStyleClass(node, "iconCell", []);
            }
            
            if (h.value) {
                if (!h.cvalue2) {
                    h.$compile("value", {nostring: true});
                    
                    
                }
                
                
                {
                    apf.setNodeValue(this.$getLayoutNode(cellType, 
                        "caption", oRow.appendChild(cell)),
                        h.cvalue2(xmlNode) || '&nbsp');
                }
            }
        }
        
        if (this.$bindings && this.$bindings.color) {
            var colorRule = this.$getDataNode("color", xmlNode);
            this.$setStyleClass(oRow, colorRule ? "highlight" : null, colorRule ? ["highlight"] : null);
        }
        
        
        var cssClass = this.$applyBindRule("css", xmlNode);
        if (cssClass) {
            this.$setStyleClass(oRow, cssClass);
            if (cssClass)
                this.$dynCssClasses.push(cssClass);
        }
        

        /*if (this.$withContainer) {
            var desc = this.$applyBindRule("description", xmlNode);
            this.$getNewContext("container");
            var oDesc = this.$getLayoutNode("container");
            apf.setNodeValue(this.$getLayoutNode("container", "container",
                oDesc), desc);
            oDesc.setAttribute("class", (oDesc.getAttribute("class") || "")
                + " row" + this.$uniqueId);
            
            if (htmlParentNode)
                apf.insertHtmlNode(oDesc, htmlParentNode, beforeNode);
            else 
                this.$nodes.push(oDesc);
        }*/
        
        return oRow;
    };
    
    this.$updateNode = function(xmlNode, htmlNode) {
        if (!htmlNode) return;

        var nodes = this.$head.childNodes,
            htmlNodes = htmlNode.childNodes,
            cell, p;
        
        if (!this.namevalue && this.$curBtn)
            p = this.$curBtn.parentNode;

        var nodeIter, h, i = 0;
        nodeIter = htmlNodes[0];
        while (nodeIter) {
            if (nodeIter.nodeType != 1) {
                nodeIter = nodeIter.nextSibling;
                continue;
            }
            
            h = apf.all[nodes[i].getAttribute("hid")];
            
            //@todo fake optimization
            cell = this.$getLayoutNode(h.tree ? "treecell" : "cell", "caption", nodeIter) || nodeIter;//htmlNodes[i].firstChild || 

            if (h.css)
                apf.setStyleClass(cell, (apf.lm.compile(h.css, {nostring: true}))(xmlNode)); //@todo cashing of compiled function?

            if (h.tree) {
                
                //@todo
                
                
                /*var oc = this.$getLayoutNode("treecell", "openclose", cell);
                oc.setAttribute("style", "margin-left:" + (((depth||0)) * 15 + 4) + "px;");
                oc.setAttribute("onmousedown",
                    "var o = apf.lookup(" + this.$uniqueId + ");\
                    o.slideToggle(this, null, null, true);");*/
            }
            
            if (h.value) {
                if (!h.cvalue2) {
                    h.$compile("value", {nostring: true});
                    
                    
                }

                
                    cell.innerHTML = h.cvalue2(xmlNode) || "&nbsp";
            }
            
            if (h.icon) {
                (cell.nodeType == 1 && cell || cell.parentNode).style.backgroundImage = 
                    "url(" + apf.getAbsolutePath(this.iconPath, 
                        ((h.cicon || h.$compile("icon", {nostring: true}))(xmlNode) || ""))
                    + ")";
            }
            
            i++;
            nodeIter = nodeIter.nextSibling;
        }
        
        //return; //@todo fake optimization
        
        if (this.$bindings && this.$bindings.color) {
            var colorRule = this.$getDataNode("color", xmlNode);
            this.$setStyleClass(htmlNode, colorRule ? "highlight" : null,
                colorRule ? ["highlight"] : null);
        }
        
        
        var cssClass = this.$applyBindRule("css", xmlNode);
        if (cssClass || this.$dynCssClasses.length) {
            this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses);
            if (cssClass && !this.$dynCssClasses.contains(cssClass))
                this.$dynCssClasses.push(cssClass);
        }
        
        
        /*if (this.$withContainer) {
            htmlNode.nextSibling.innerHTML 
                = this.$applyBindRule("description", xmlNode) || "";
        }*/
    };
    
    this.$dblclick = function(htmlNode) {
        var _self = this, id, cell;
        while (!(id = htmlNode.getAttribute(apf.xmldb.htmlIdTag)) || id.indexOf("|") == -1) {
            htmlNode = (cell = htmlNode).parentNode;
            if (!htmlNode || htmlNode.nodeType != 1)
                return;
        }
        
        if (this.$lastEditor && this.$lastEditor[3] == htmlNode)
            return;
        
        var h, colId = cell.className.match(/(col\d+)/)[1];
        for (var i = 0; i < this.$headings.length; i++) {
            if (this.$headings[i].$className == colId) {
                h = this.$headings[i];
                break;
            }
        }
        
        if (!h.editor) //No editor specified
            return;

        if (this.dispatchEvent("beforeEdit", {heading: h}) === false)
            return;

        /*if (this.$lastEditor) {
            //this.$lastEditor[0].$blur();
            this.$lastEditor[0].setProperty("visible", false);
            
            var nodes = this.$lastEditor[1].childNodes;
            for (var i = 0, l = nodes.length; i < l; i++) {
                if (!nodes[i].host)
                    nodes[i].style.display = "";
            }
        }*/
        
        var xmlNode = apf.xmldb.getNode(htmlNode);
        /*
            - editor (name of widget, lm function returning amlNode or lm template ref)
            - children being aml nodes
        */
        var editParent = h.tree 
          ? this.$getLayoutNode("cell", "caption", cell)
          : cell;

        var oEditor, editor = h.editor; 
        var ceditor = apf.lm.compile(editor, {xpathmode: 2}); //@todo can this be more efficient?
    
        var nodes = editParent.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++) {
            if (!nodes[i].host) {
                if (nodes[i].nodeType == 1)
                    nodes[i].style.display = "none";
                else {
                    this.$lastTextValue = nodes[i].nodeValue;
                    nodes[i].nodeValue = ""; //@todo
                }
            }
        }

        if (ceditor.type == 2) {
            if (!this.$editors[h.$uniqueId + ":" + editor]) {
                var constr = apf.namespaces[apf.ns.aml].elements[editor];
                var info = {
                    htmlNode: editParent,
                    style: "position:relative;z-index:10000",
                    value: "[{" + this.id + ".selected}::" 
                        + (v = (h.match || h.value)).substr(1, v.length - 2)  //only xpath value's supported for now
                        + "]",
                    focussable: false
                };

                if (h.tree)
                    info.width = "100% - " + (editParent.offsetLeft + parseInt(this.$getOption("treecell", "editoroffset")));
                else
                    info.width = "100%";
                
                //@todo copy all non-known properties of the prop element

                if (constr.prototype.hasFeature(apf.__MULTISELECT__)) {
                    info.caption = h.eachcaption || "[text()]";
                    info.eachvalue = h.eachvalue; // || "[@value]";
                    
                    var model;
                    if (model = h.getAttribute("model")) {
                        info.model = model;
                        info.each = h.each;
                    }
                    else {
                        /*var each = h.each;
                        if (each.charAt(0) == "[")
                            each = each.substr(1, each.length - 2);
                        info.each = "[{" + this.id + ".selected}::" + each + "]";*/
                        info.model = "{" + this.id + ".selected}"
                        info.each = h.each;
                    }
                }
                
                if (h.skin)
                    info.skin = h.skin;
                if (h["class"])
                    info["class"] = h["class"];
                if (h.fill)
                    info.fill = h.fill;

                oEditor = this.$editors[h.$uniqueId + ":" + editor] = new constr(info);

                var box = apf.getBox(apf.getStyle(oEditor.$ext, "margin"));
                if (box[1] || box[3]) {
                    oEditor.setAttribute("width", "100%+2-" + (box[1] + box[3]));
                }
                //else if (!box[3])
                    //oEditor.$ext.style.marginLeft = "-1px";
                
                //oEditor.$focussable = false;
                oEditor.addEventListener("blur", function(){
                    hideEditor.call(_self);
                });
                oEditor.parentNode = this;
                oEditor.realtime = false;
                oEditor.$focusParent = this;
                oEditor.setAttribute("focussable", "true");
                //delete oEditor.parentNode;
                
                oEditor.addEventListener("keyup", function(e) {
                    if (e.keyCode == 13) {
                        hideEditor.call(_self);
                        _self.$focus();
                    }
                    else if (e.keyCode == 27) {
                        oEditor.removeAttribute("value"); //@todo this bugs in slider
                        hideEditor.call(_self);
                        //_self.getActionTracker().undo();
                    }
                });
                
                //@todo set actiontracker
                
                //Patch oEditor to forward change
                oEditor.$executeAction = function(){
                    this.parentNode.$executeAction.apply(this.parentNode, arguments);
                }
                
                this.dispatchEvent("editor.create", {
                    editor: oEditor
                });
            }
            else {
                oEditor = this.$editors[h.$uniqueId + ":" + editor];
                
                if (oEditor.hasFeature(apf.__MULTISELECT__) && !h.model) {
                    //oEditor.setAttribute("model", "{" + this.id + ".selected}");
                    /*var each = h.each;
                    if (each.charAt(0) == "[")
                        each = each.substr(1, each.length - 2);
                    oEditor.setAttribute("each", "[{" + this.id + ".selected}::" + each + "]");*/
                    /*apf.queue.empty();*/
                    oEditor.setAttribute("value", "[{" + this.id + ".selected}::" 
                        + (v = h.value).substr(1, v.length - 2) 
                        + "]");
                }
                else {
                    oEditor.setAttribute("value", "[{" + this.id + ".selected}::" 
                        + (v = (h.match || h.value)).substr(1, v.length - 2) 
                        + "]");
                }

                oEditor.setProperty("visible", true);
                editParent.appendChild(oEditor.$ext);
                
                oEditor.setAttribute("width", h.tree 
                    ? "100% - " + (editParent.offsetLeft + parseInt(this.$getOption("treecell", "editoroffset"))) 
                    : "100%");
            }
            
            /*setTimeout(function(){
                oEditor.focus();
            });*/
        }
        else {
            //Create dropdown 
            
            var obj = ceditor.call(this, this.xmlRoot);
            if (obj.localName == "template") {
                //add template contents to dropped area
            }
            else {
                //add xml into dropped area
            }
        }
        
        if (oEditor.localName == "textbox")
            oEditor.select();
        
        oEditor.focus();
        oEditor.$focus();
        
        this.$setStyleClass(htmlNode, "editing");
        
        this.$lastEditor = [oEditor, editParent, xmlNode, htmlNode, this.getActionTracker().undolength];
    }
    
    this.addEventListener("mousedown", function(e) {
        if (this.$lastEditor 
          && !apf.isChildOf(this.$lastEditor[1], 
            e.htmlEvent.srcElement || e.htmlEvent.target, true))
                hideEditor.call(this);
    });
    
    var hideEditor = function(e) {
        if (this.$lastEditor) {
            var ed = this.$lastEditor;
            this.$lastEditor = null;

            if (ed[0].hasFeature(apf.__MULTISELECT__)) // && !ed[0].model
                ed[0].$clearDynamicProperty("value");

            //this.$lastEditor[0].$blur();
            ed[0].setProperty("visible", false);
            
            var nodes = ed[1].childNodes;
            for (var i = 0, l = nodes.length; i < l; i++) {
                if (!nodes[i].host) {
                    if (nodes[i].nodeType == 1)
                        nodes[i].style.display = "";
                    else if (!ed[0].value || ed[0].value == this.$lastTextValue) {
                        nodes[i].nodeValue = this.$lastTextValue; //@todo
                    }
                }
            }
            
            this.$setStyleClass(ed[3], "", ["editing"]);
            
            this.focus();
        }
    };
    this.addEventListener("beforeselect", hideEditor);
    
    // *** Column management *** //

    /**
     * Returns a column definition object based on the column number.
     * @param {Number} nr The heading number; this number is based on the sequence of the column elements.
     */
    this.getColumn = function(nr) {
        return this.$headings[nr || this.$lastcol || 0];
    };
    
    /** 
     * Resizes a column.
     * @param {Number} nr      The heading number; this number is based on the sequence of the column elements. 
     * @param {Number} newsize  The new size of the column.
     *
     */
    this.resizeColumn = function(nr, newsize){ //  @todo optimize but bringing down the string concats
        var h = this.$headings[nr];
        h.resize(newsize);
    };

    /**
     * Hides a column.
     * @param {Number} nr      The heading number; this number is based on the sequence of the column elements. 
     */
    this.hideColumn = function(nr) {
        var h = this.$headings[nr];
        h.hide();
    };
    
    /**
     * Shows a hidden column.
     * @param {Number} nr      The heading number; this number is based on the sequence of the column elements. 
     */
    this.showColumn = function(nr) {
        var h = this.$headings[nr];
        h.show();
    };
    
    /**
     * Sorts a column.
     * @param {Number} hid The heading number; this number is based on the sequence of the column elements.
     */
    this.sortColumn = function(hid) {
        var h = this.$headings[nr];
        h.sort();
    };
    
    /**
     * Moves a column to another position.
     * @param {Number} from The heading number of the column to move; this number is based on the sequence of the column elements.
     * @param {Number} to   The position to move the column to
     */
    this.moveColumn = function(from, to) {
        var h = this.$headings[nr];
        h.move(to);
    }
    
    // *** Init *** //

    this.$draw = function(){
        this.$drawBase();
        
        var _self = this;
        this.$ext.onmousedown = function(e) {
            _self.dispatchEvent("mousedown", {htmlEvent: e || event}); 
        }
        
        //@todo rename 'body' to 'container'
        
        //Build Main Skin
        this.$head = this.$getLayoutNode("main", "head", this.$ext);
        this.$pointer = this.$getLayoutNode("main", "pointer", this.$ext);

        if (this.$head.firstChild)
            this.$head.removeChild(this.$head.firstChild);
        if (this.$container.firstChild)
            this.$container.removeChild(this.$container.firstChild);

        var widthdiff = this.$widthdiff = this.$getOption("main", "widthdiff") || 0;
        this.$defaultwidth = this.$getOption("main", "defaultwidth") || "100";
        this.$useiframe = apf.isIE && (apf.isTrue(this.$getOption("main", "iframe")) || this.iframe);

        //Initialize Iframe 
        if (this.$useiframe && !this.oIframe) {
            //this.$container.style.overflow = "hidden";
            //var sInt = this.$container.outerHTML 
            var sClass = this.$container.className;
            //this.$container.parentNode.removeChild(this.$container);
            this.oIframe = this.$container.appendChild(document.createElement(apf.isIE 
                ? "<iframe frameborder='0'></iframe>"
                : "iframe"));
            this.oIframe.frameBorder = 0;
            this.oWin = this.oIframe.contentWindow;
            this.oDoc = this.oWin.document;
            this.oDoc.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\
                <html xmlns="http://www.w3.org/1999/xhtml">\
                    <head><script>\
                        apf = {\
                            lookup: function(uid){\
                                return window.parent.apf.lookup(uid);\
                            },\
                            Init: {add:function(){},run:function(){}}\
                        };</script>\
                    </head>\
                    <body></body>\
                </html>');
            //Import CSS
            //this.oDoc.body.innerHTML = sInt;
            this.$container = this.oDoc.body;//.firstChild;
            this.$container.className = sClass;//this.oIframe.parentNode.className;
            this.oDoc.documentElement.className = this.$ext.className;
            //this.oDoc.body.className = this.$ext.className;

            apf.skins.loadCssInWindow(this.skinName, this.oWin, this.mediaPath, this.iconPath);
            
            if (apf.isIE) //@todo this can be removed when focussing is fixed for this component
                this.$setStyleClass(this.oDoc.documentElement, this.$baseCSSname + "Focus");
            
            apf.convertIframe(this.oIframe, true);

            if (apf.getStyle(this.oDoc.documentElement, "overflowY") == "auto") {
                //@todo ie only
                this.oIframe.onresize = function(){
                    _self.$head.style.marginRight = 
                      _self.oDoc.documentElement.scrollHeight > _self.oDoc.documentElement.offsetHeight 
                        ? "16px" : "0";
                }
                
                this.addEventListener("afterload", this.oIframe.onresize);
                this.addEventListener("xmlupdate", this.oIframe.onresize);
            }
            
            this.oDoc.documentElement.onscroll = 
                function(){
                    if (_self.$isFixedGrid)
                        _self.$head.scrollLeft = _self.oDoc.documentElement.scrollLeft;
                };
        }
        else {
//            if (apf.getStyle(this.$container, "overflowY") == "auto") {
//                this.$resize = function(){
//                    _self.$head.style.marginRight = 
//                      _self.$container.scrollHeight > _self.$container.offsetHeight 
//                        ? "16px" : "0";
//                }
//                
//                
//                apf.layout.setRules(this.$ext, this.$uniqueId + "_datagrid",
//                    "var o = apf.all[" + this.$uniqueId + "];\
//                     if (o) o.$resize()");
//                apf.layout.queue(this.$ext);
//                
//                
//                this.addEventListener("afterload", this.$resize);
//                this.addEventListener("xmlupdate", this.$resize);
//            }
            
            this.$container.onscroll = 
                function(){
                    if (_self.$isFixedGrid)
                        _self.$head.scrollLeft = _self.$container.scrollLeft;
                };
        }
        
        this.$container[this.clickedit ? "onmousedown" : "ondblclick"] = function(e) {
            if (!e) e = event;
            _self.$dblclick(e.srcElement || e.target);
        }
    };
    
    this.$destroy = function(){
        //@todo destroy this.$txt here

        this.$ext.onclick = this.$container.onresize = null;
        
        
        apf.layout.removeRule(this.$container, "dg" + this.$uniqueId);
        apf.layout.activateRules(this.$container);
        
    };
}).call(apf.datagrid.prototype = new apf.BaseTree());

apf.aml.setElement("datagrid",    apf.datagrid);
//apf.aml.setElement("column",      apf.BindingRule);
apf.aml.setElement("description", apf.BindingRule);
apf.aml.setElement("color",       apf.BindingRule);
apf.aml.setElement("contents",    apf.BindingRule);







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Element displaying a divider. For use in toolbars, menus, and such.
 * @class apf.divider
 * @define divider
 * @inherits apf.Presentation
 */
apf.divider = function(struct, tagName) {
    this.$init(tagName || "divider", apf.NODE_VISIBLE, struct);
};

(function() {
    this.$focussable = false;

    this.minwidth = 0;
    this.minheight = 0;

    this.implement(apf.ChildValue);
    this.$childProperty = "caption";
    
    //@todo apf3.0 fix this
    this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent) {
        if (!this.$amlLoaded)
            return;
        
        if (!withinParent && this.skinName != pNode.skinName) {
            //@todo for now, assuming dom garbage collection doesn't leak
            this.loadAml();
        }
    });
    
    /** 
     * @attribute {String} caption the text displayed in the area defined by this 
     * element. 
     */
    this.$supportedProperties.push("caption", "value", "for", "textalign");
    this.$propHandlers["caption"] = function(value) {
        if (this.$caption) {
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Caption");
            this.$caption.innerHTML = value;
        }
        else {
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Caption"]);
        }
    };
    
    this.$canLeechSkin = true;
    
    /**
     * @private
     */
    this.$draw = function() {
        if (this.parentNode.isPaged && this.parentNode.$buttons)
            this.$pHtmlNode = this.parentNode.$buttons;
        
        if (this.$isLeechingSkin) {
            this.$ext = apf.insertHtmlNode(
                this.parentNode.$getLayoutNode("divider"), this.$pHtmlNode);
        }
        else {
            this.$ext = this.$getExternal("main");
            this.$caption = this.$getLayoutNode("main", "caption", this.$ext);
        }
    };
}).call(apf.divider.prototype = new apf.Presentation);

apf.aml.setElement("divider", apf.divider);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An element allowing a user to select a value from a list, which is 
 * displayed when the user clicks a button.
 * 
 * #### Example: Simple Dropdown
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:dropdown initial-message="Choose a country" skin="black_dropdown">
 *       <a:item>America</a:item>
 *       <a:item>Armenia</a:item>
 *       <a:item>The Netherlands</a:item>
 *   </a:dropdown>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Loading Items From XML
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:dropdown model="../resources/xml/friends.xml" each="[friend]" caption="[@name]" skin="black_dropdown" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Capturing and Emitting Events
 *
 * A databound dropdown using the bindings element
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <a:table columns="100, 100, 100, 100" cellheight="19" padding="5">
 *   <!-- startcontent -->
 *       <a:model id="mdlDD5">
 *           <data>
 *               <friend name="Arnold"></friend>
 *               <friend name="Carmen"></friend>
 *               <friend name="Giannis"></friend>
 *               <friend name="Mike"></friend>
 *               <friend name="Rik"></friend>
 *               <friend name="Ruben"></friend>
 *           </data>
 *       </a:model>
 *       <a:textbox id="txtAr"></a:textbox>
 *       <a:dropdown
 *         id = "friendDD"
 *         model = "mdlDD5"
 *         each = "[friend]"
 *         caption = "[@name]"
 *         onslidedown = "txtAr.setValue('slide down')"
 *         onslideup = "txtAr.setValue('slide up')" />
 *       <a:button onclick="friendDD.slideDown()">Slide Down</a:button>
 *       <a:button onclick="friendDD.slideUp()">Slide Up</a:button>
 *   <!-- endcontent -->
 *   </a:table>
 * </a:application>
 * ```
 * 
 * #### Example: Dynamically Adding Entries
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="friendMdl">
 *       <data>
 *           <friend name="Arnold" />
 *           <friend name="Carmen" />
 *           <friend name="Giannis" />
 *           <friend name="Mike" />
 *           <friend name="Rik" />
 *           <friend name="Ruben" />
 *       </data>
 *   </a:model>
 *   <a:dropdown
 *     id = "dd"
 *     model = "friendMdl"
 *     each = "[friend]"
 *     caption = "[@name]">
 *   </a:dropdown>
 *   <a:button width="110" onclick="dd.add('&lt;friend name=&quot;Lucas&quot; />')">New Name?</a:button>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.dropdown
 * @define dropdown
 * @form
 * @allowchild item, {smartbinding}
 *
 *
 * @inherits apf.BaseList
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
/**
 * @event slidedown Fires when the dropdown slides open.
 * @cancelable Prevents the dropdown from sliding open
 */
/**
 *  @event slideup   Fires when the dropdown slides up.
 *  @cancelable Prevents the dropdown from sliding up
 *
 */
apf.dropdown = function(struct, tagName) {
    this.$init(tagName || "dropdown", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$animType = 1;
    this.$animSteps = 5;
    this.$animSpeed = 20;
    this.$itemSelectEvent = "onmouseup";
    
    // *** Properties and Attributes *** //
    
    this.dragdrop = false;
    this.reselectable = true;
    this.$focussable = apf.KEYBOARD;
    this.autoselect = false;
    this.multiselect = false;
    this.disableremove = true;
    this.delayedselect = false;
    this.maxitems = 5;
    
    this.$booleanProperties["disableremove"] = true;
    this.$supportedProperties.push("maxitems", "disableremove", 
        "initial-message", "fill");
    
    /**
     * @attribute {String} initial-message Sets or gets the message displayed by this element
     * when it doesn't have a value set. This property is inherited from parent 
     * nodes. When none is found it is looked for on the appsettings element. 
     *
     */
     /**
     * @attribute {Number} maxitems Sets or gets the number of items that are shown at the 
     * same time in the container.
     */
    this.$propHandlers["maxitems"] = function(value) {
        this.sliderHeight = value 
            ? (Math.min(this.maxitems || 100, value) * this.itemHeight)
            : 10;
        this.containerHeight = value
            ? (Math.min(this.maxitems || 100, value) * this.itemHeight)
            : 10;
        /*if (this.containerHeight > 20)
            this.containerHeight = Math.ceil(this.containerHeight * 0.9);*/
    };
    
    this.addEventListener("prop.class", function(e) {
        this.$setStyleClass(this.oSlider, e.value);
    });
    
    // *** Public methods *** //
    
    /*
     * Toggles the visibility of the container with the list elements. It opens
     * or closes it using a slide effect.
     * @private
     */
    this.slideToggle = function(e, userAction) {
        if (!e) e = event;
        if (userAction && this.disabled)
            return;

        if (this.isOpen)
            this.slideUp();
        else
            this.slideDown(e);
    };

    /*
     * Shows the container with the list elements using a slide effect.
     * @private
     */
    this.slideDown = function(e) {
        if (this.dispatchEvent("slidedown") === false)
            return false;
        
        this.isOpen = true;

        this.$propHandlers["maxitems"].call(this, this.xmlRoot && this.each 
            ? this.getTraverseNodes().length : this.childNodes.length); //@todo apf3.0 count element nodes
        
        this.oSlider.style.display = "block";
        if (!this.ignoreOverflow) {
            this.oSlider.style[apf.supportOverflowComponent
                ? "overflowY"
                : "overflow"] = "visible";
            this.$container.style.overflowY = "hidden";
        }
        
        this.oSlider.style.display = "";

        this.$setStyleClass(this.$ext, this.$baseCSSname + "Down");
        
        //var pos = apf.getAbsolutePosition(this.$ext);
        this.oSlider.style.height = (this.sliderHeight - 1)     + "px";
        this.oSlider.style.width = (this.$ext.offsetWidth - 2 - this.widthdiff) + "px";

        var _self = this;
        var _popupCurEl = apf.popup.getCurrentElement();
        apf.popup.show(this.$uniqueId, {
            x: 0,
            y: this.$ext.offsetHeight,
            animate: true,
            container: this.$getLayoutNode("container", "contents", this.oSlider),
            ref: this.$ext,
            width: this.$ext.offsetWidth - this.widthdiff,
            height: this.containerHeight,
            allowTogether: (_popupCurEl && apf.isChildOf(_popupCurEl.$ext, _self.$ext)),
            callback: function(container) {
                if (!_self.ignoreOverflow) {
                    _self.$container.style.overflowY = "auto";
                }
            }
        });
    };
    
    /*
     * Hides the container with the list elements using a slide effect.
     * @private
     */
    this.slideUp = function(){
        if (this.isOpen == 2) return false;
        if (this.dispatchEvent("slideup") === false) return false;
        
        this.isOpen = false;
        if (this.selected) {
            var htmlNode = apf.xmldb.findHtmlNode(this.selected, this);
            if (htmlNode) this.$setStyleClass(htmlNode, '', ["hover"]);
        }
        
        this.$setStyleClass(this.$ext, '', [this.$baseCSSname + "Down"]);
        if (apf.popup.last == this.$uniqueId)
            apf.popup.hide();
        return false;
    };
    
    // *** Private methods and event handlers *** //

    //@todo apf3.0 why is this function called 6 times on init.
    this.$setLabel = function(value) {
        
        this.oLabel.innerHTML = value || this["initial-message"] || "";
        

        this.$setStyleClass(this.$ext, value ? "" : this.$baseCSSname + "Initial",
            !value ? [] : [this.$baseCSSname + "Initial"]);
    };

    this.addEventListener("afterselect", function(e) {
        if (!e) e = event;

        this.slideUp();
        if (!this.isOpen)
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"]);
        
        this.$setLabel(e.selection.length
          ? this.$applyBindRule("caption", this.selected)
          : "");
    });
    
    function setMaxCount() {
        if (this.isOpen == 2)
            this.slideDown();
    }

    this.addEventListener("afterload", setMaxCount);
    this.addEventListener("xmlupdate", function(){
        setMaxCount.call(this);
        this.$setLabel(this.$applyBindRule("caption", this.selected));
    });
    
    // Private functions
    this.$blur = function(){
        this.slideUp();
        //this.$ext.dispatchEvent("mouseout")
        if (!this.isOpen)
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Over"])
        
        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
    };
    
    /*this.$focus = function(){
        apf.popup.forceHide();
        this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus");
    }*/
    
    this.$setClearMessage = function(msg) {
        this.$setLabel(msg);
    };
    
    this.$removeClearMessage = function(){
        this.$setLabel("");
    };

    this.addEventListener("popuphide", this.slideUp);
    
    // *** Keyboard Support *** //
    
    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode;
        //var ctrlKey = e.ctrlKey; << unused
        //var shiftKey = e.shiftKey;
        
        if (!this.xmlRoot) return;
        
        var node;
        
        switch (key) {
            case 32:
                this.slideToggle(e.htmlEvent);
            break;
            case 38:
                //UP
                if (e.altKey) {
                    this.slideToggle(e.htmlEvent);
                    return;
                }
                
                if (!this.selected) 
                    return;
                    
                node = this.getNextTraverseSelected(this.caret
                    || this.selected, false);

                if (node)
                    this.select(node);
            break;
            case 40:
                //DOWN
                if (e.altKey) {
                    this.slideToggle(e.htmlEvent);
                    return;
                }
                
                if (!this.selected) {
                    node = this.getFirstTraverseNode();
                    if (!node) 
                        return;
                } 
                else
                    node = this.getNextTraverseSelected(this.selected, true);
                
                if (node)
                    this.select(node);
                
            break;
            default:
                if (key == 9 || !this.xmlRoot) return;  
            
                //if(key > 64 && key < 
                if (!this.lookup || new Date().getTime() - this.lookup.date.getTime() > 1000)
                    this.lookup = {
                        str: "",
                        date: new Date()
                    };

                this.lookup.str += String.fromCharCode(key);
                
                var caption, nodes = this.getTraverseNodes();
                for (var i = 0; i < nodes.length; i++) {
                    caption = this.$applyBindRule("caption", nodes[i]);
                    if (caption && caption.indexOf(this.lookup.str) > -1) {
                        this.select(nodes[i]);
                        return;
                    }
                }
            return;
        }

        return false;
    }, true);
    
    
    // *** Init *** //
    
    this.$draw = function(){
        this.$getNewContext("main");
        this.$getNewContext("container");
        
        this.$animType = this.$getOption("main", "animtype") || 1;
        this.clickOpen = this.$getOption("main", "clickopen") || "button";

        //Build Main Skin
        this.$ext = this.$getExternal(null, null, function(oExt) {
            oExt.setAttribute("onmouseover", 'var o = apf.lookup(' + this.$uniqueId
                + ');o.$setStyleClass(o.$ext, o.$baseCSSname + "Over", null, true);');
            oExt.setAttribute("onmouseout", 'var o = apf.lookup(' + this.$uniqueId
                + ');if(o.isOpen) return;o.$setStyleClass(o.$ext, "", [o.$baseCSSname + "Over"], true);');
            
            //Button
            var oButton = this.$getLayoutNode("main", "button", oExt);
            if (oButton) {
                oButton.setAttribute("onmousedown", 'apf.lookup('
                    + this.$uniqueId + ').slideToggle(event, true);');
            }
            
            //Label
            var oLabel = this.$getLayoutNode("main", "label", oExt);
            if (this.clickOpen == "both") {
                oLabel.parentNode.setAttribute("onmousedown", 'apf.lookup('
                    + this.$uniqueId + ').slideToggle(event, true);');
            }
        });
        this.oLabel = this.$getLayoutNode("main", "label", this.$ext);
        
        
        if (this.oLabel.nodeType == 3)
            this.oLabel = this.oLabel.parentNode;
        
        
        this.oIcon = this.$getLayoutNode("main", "icon", this.$ext);
        if (this.$button)
            this.$button = this.$getLayoutNode("main", "button", this.$ext);
        
        this.oSlider = apf.insertHtmlNode(this.$getLayoutNode("container"),
            document.body);
        this.$container = this.$getLayoutNode("container", "contents", this.oSlider);
        this.$container.host = this;
        
        //Set up the popup
        this.$pHtmlDoc = apf.popup.setContent(this.$uniqueId, this.oSlider,
            apf.skins.getCssString(this.skinName));
        
        //Get Options form skin
        //Types: 1=One dimensional List, 2=Two dimensional List
        this.listtype = parseInt(this.$getLayoutNode("main", "type")) || 1;
        
        this.itemHeight = this.$getOption("main", "item-height") || 18.5;
        this.widthdiff = this.$getOption("main", "width-diff") || 0;
        this.ignoreOverflow = apf.isTrue(this.$getOption("main", "ignore-overflow")) || false;
    };
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(){
        if (typeof this["initial-message"] == "undefined")
            this.$setInheritedAttribute("initial-message");
        
        if (!this.selected && this["initial-message"])
            this.$setLabel();
    });
    
    this.$destroy = function(){
        apf.popup.removeContent(this.$uniqueId);
        apf.destroyHtmlNode(this.oSlider);
        this.oSlider = null;
    };

    
}).call(apf.dropdown.prototype = new apf.BaseList());

apf.config.$inheritProperties["initial-message"] = 1;

apf.aml.setElement("dropdown", apf.dropdown);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An element showing an error message when the attached element 
 * is in erroneous state and has the `invalidmsg=""` attribute specified.
 *
 * In most cases, the errorbox element is implicit and will be created 
 * automatically. 
 * 
 * #### Example
 *
 * ```xml
 *  <a:errorbox>
 *      Invalid e-mail address entered.
 *  </a:errorbox>
 * ```
 * 
 * #### Remarks
 *
 * In most cases the errorbox element is not created directly, but implicitly
 * by a validationgroup. An element that goes into an error state will
 * show the errorbox.
 * 
 * ```xml
 *  <a:bar validgroup="vgForm">
 *      <a:label>Phone number</a:label>
 *      <a:textbox id="txtPhone"
 *          required = "true" 
 *          pattern = "(\d{3}) \d{4} \d{4}" 
 *          invalidmsg = "Incorrect phone number entered" />
 *
 *      <a:label>Password</a:label>
 *      <a:textbox 
 *          required = "true" 
 *          mask = "password"
 *          minlength = "4"
 *          invalidmsg = "Please enter a password of at least four characters" />
 *      <a:button onclick="vgForm.validate()">Validate</a:button>
 *  </a:bar>
 * ```
 *
 * To check if the element has valid information entered, leaving the textbox
 * (focussing another element) will trigger a check. A programmatic check
 * can be done using the following code:
 * 
 * ```javascript
 *  txtPhone.validate();
 * 
 *  //Or use the html5 syntax
 *  txtPhone.checkValidity();
 * ```
 *
 * To check for the entire group of elements use the validation group. For only 
 * the first non-valid element the errorbox is shown. That element also receives
 * focus.
 * 
 * ```javascript
 *  vgForm.validate();
 * ```
 *
 * @class apf.errorbox
 * @define errorbox
 * 
 * @allowchild {anyxhtml}
 *
 *
 * @inherits apf.Presentation
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.errorbox = function(struct, tagName) {
    this.$init(tagName || "errorbox", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$positioning = "basic";
    this.display = function(host) {
        this.host = host;

        var refHtml = 
            
            host.$ext;

        document.body.appendChild(this.$ext);
        /*var pos = apf.getAbsolutePosition(refHtml, document.body);

        if (document != refHtml.ownerDocument) {
            var pos2 = apf.getAbsolutePosition(refHtml.ownerDocument.parentWindow.frameElement, document.body);
            pos[0] += pos2[0];
            pos[1] += pos2[1];
        }*/

        var x = (parseFloat(host.$getOption && host.$getOption("main", "erroffsetx") || 0)),
            y = (parseFloat(host.$getOption && host.$getOption("main", "erroffsety") || 0));
        //this.$ext.style.left = x + "px"
        //this.$ext.style.top = y + "px"

        this.show();
        apf.popup.show(this.$uniqueId, {
            x: x,
            y: y,
            animate: false,
            ref: refHtml,
            allowTogether: true
        });

        this.$setStyleClass(this.$ext,
            x + this.$ext.offsetWidth > this.$ext.offsetParent.offsetWidth
                ? "rightbox"
                : "leftbox", ["leftbox", "rightbox"]);
    };
    
    /**
     * Sets the message of the errorbox.
     * @param {String} value The message to set.
     */
    this.setMessage = function(value) {
        if (value && value.indexOf(";") > -1) {
            value = value.split(";");
            value = "<strong>" + value.shift() + "</strong>" + value.join(";");
        }
        this.$int.innerHTML = value || "";
    };
    
    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal(); 
        this.$int = this.$getLayoutNode("main", "container", this.$ext);
        this.oClose = this.$getLayoutNode("main", "close", this.$ext);
        
        if (this.oClose) {
            var _self = this;
            this.oClose.onclick = function(){
                _self.hide();

                if (apf.document.activeElement)
                    apf.document.activeElement.focus(true, {mouse:true});
            };
        }
        
        this.$ext.onmousedown = function(e) {
            (e || event).cancelBubble = true;
            
            
        }

        apf.popup.setContent(this.$uniqueId, this.$ext, "", null, null);
    };
    
    this.$loadAml = function(x) {
        if (!apf.isTrue(this.getAttribute("visible")))
            this.hide();
    };
    
    this.$destroy = function(){
        if (this.oClose)
            this.oClose.onclick = null;
        
        this.$ext.onmousedown = null;
        
        apf.popup.removeContent(this.$uniqueId);
    };
}).call(apf.errorbox.prototype = new apf.Presentation());

apf.aml.setElement("errorbox", apf.errorbox);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Displays a popup element with a message with optionally an icon at the
 * position specified by the position attribute. After the timeout has passed
 * the popup will dissapear automatically. When the mouse hovers over the popup
 * it doesn't dissapear.
 * 
 * @class apf.event
 * @define event
 * @inherits apf.AmlElement
 *
 */
/**
 * @event click Fires when the user clicks on the representation of this event.
 */
apf.event = function(struct, tagName) {
    this.$init(tagName || "event", apf.NODE_HIDDEN, struct);
};

(function() {
    this.$hasInitedWhen = false;

    this.$booleanProperties["repeat"] = true;
    this.$supportedProperties.push("when", "message", "icon", "repeat");

    this.$propHandlers["when"] = function(value) {
        if (this.$hasInitedWhen && value && this.parentNode && this.parentNode.popup) {
            var _self = this;
            $setTimeout(function() {
                _self.parentNode.popup(_self.message, _self.icon, _self);
            });
        }
        this.$hasInitedWhen = true;

        if (this.repeat)
            delete this.when;
    };

    this.$loadAml = function(x) {};
}).call(apf.event.prototype = new apf.AmlElement());

apf.aml.setElement("event", apf.event);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


apf.filler = function(struct, tagName) {
    this.$init(tagName || "filler", apf.NODE_VISIBLE, struct);
};

(function() {
    this.$focussable = false;
    this.flex = 1;

    this.$draw = function() {
        this.$ext = this.$pHtmlNode.appendChild(this.$pHtmlNode.ownerDocument.createElement("div"));
    };
}).call(apf.filler.prototype = new apf.GuiElement());

apf.aml.setElement("filler", apf.filler);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element displays a frame with a caption that can contain other elements. It's
 * element is analogous to the `<fieldset>` in HTML.
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *    <!-- startcontent -->
 *    <a:frame caption="Options">
 *      <a:radiobutton value="1">Option 1</a:radiobutton>
 *      <a:radiobutton value="2">Option 2</a:radiobutton>
 *      <a:radiobutton value="3">Option 3</a:radiobutton>
 *      <a:radiobutton value="4">Option 4</a:radiobutton>
 *    </a:frame>
 *    <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.frame
 * @define frame
 * @container
 * @allowchild {elements}, {anyaml}
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 *
 * @inherits apf.Presentation
 */
apf.panel = function(struct, tagName) {
    this.$init(tagName || "panel", apf.NODE_VISIBLE, struct);
};

apf.fieldset = function(struct, tagName) {
    this.$init(tagName || "fieldset", apf.NODE_VISIBLE, struct);
};

apf.frame = function(struct, tagName) {
    this.$init(tagName || "frame", apf.NODE_VISIBLE, struct);
};

(function(){
    this.implement(apf.BaseStateButtons);

    this.$focussable = false;
    
    
    
    // *** Properties and Attributes *** //
    
    /**
     * @attribute {String} caption Sets or gets the caption text. 
     */
    this.$supportedProperties.push("caption", "url");
    this.$propHandlers["caption"] = function(value) {
        if (!this.oCaption) return;
        
        if (this.oCaption.nodeType == 1)
            this.oCaption.innerHTML = value;
        else
            this.oCaption.nodeValue = value;
    };
    
    /**
     * @attribute {String} icon Sets or gets the location of the image.
     */
    this.$propHandlers["icon"] = function(value) {
        var oIcon = this.$getLayoutNode("main", "icon", this.$ext);
        if (!oIcon) return;

        if (oIcon.nodeType == 1)
            oIcon.style.display = value ? "block" : "none";
        apf.skins.setIcon(oIcon, value, this.iconPath);
    };

    /**
     * @attribute {String} icon Sets or gets the URL location (if this is an iframe).
     */
    this.$propHandlers["url"] = function(value) {
        var node = this.oCaption;
        if (node.tagName == "A" || node.nodeType != 1) 
            node = node.parentNode;

        node.innerHTML = "<a href='" + value + "' " 
            + (value.match(/^http:\/\//) ? "target='_blank'" : "") + ">" 
            + this.caption + "</a>";
        this.oCaption = this.oCaption.firstChild;
    };
    
    this.$propHandlers["activetitle"] = function(value) {
        var node = this.oCaption.parentNode;
        // if (node.nodeType != 1) node = node.parentNode;
        var _self = this;
        node.addEventListener("click", function(e) {
            if (e.target == node)
                _self.$toggle(value);
        }, false);
    };
    
    /** 
     * Sets the text of the title of this element.
     * @param {String} value The text of the title.
     */
    this.setTitle = function(value) {
        this.setProperty("title", value);
    };
    
    // *** Init *** //
    
    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal(null, null, function(oExt) {
            this.$initButtons(oExt);
        });
        this.oCaption = this.$getLayoutNode("main", "caption", this.$ext);
        this.$int = this.$getLayoutNode("main", "container", this.$ext);
        this.$buttons = this.$getLayoutNode("main", "buttons",  this.$ext);

        /*if (this.oCaption) {
            this.oCaption = this.oCaption.nodeType == 1 
                ? this.oCaption 
                : this.oCaption.parentNode;
        }*/
    };
    
    this.$loadAml = function(x) {
        // not implement now.
    };
    
        
}).call(apf.frame.prototype = new apf.Presentation());

apf.panel.prototype = 
apf.fieldset.prototype = apf.frame.prototype;

apf.aml.setElement("panel", apf.panel);
apf.aml.setElement("fieldset", apf.fieldset);
apf.aml.setElement("frame", apf.frame);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * A container that stacks its children vertically.
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:vbox width="100">
 *       <a:button height="28" edge="5">Button 1</a:button>
 *       <a:button height="28" edge="5">Button 2</a:button>
 *       <a:button height="28" edge="5">Button 3</a:button>
 *   </a:vbox>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.vbox
 * @layout
 * @define vbox 
 * 
 * 
 * @see element.hbox
 * 
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 * @layout
 */
/**
 * A container that stacks its children horizontally.
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:hbox height="29" width="300" lean="right" margin="5 0 0 0">
 *       <a:button width="100" edge="5">Button 1</a:button>
 *       <a:button width="100">Button 2</a:button>
 *       <a:button width="100">Button 3</a:button>
 *   </a:hbox>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * 
 * #### Remarks
 * 
 * Firefox has some issues:
 * 
 * 1. Sometimes it's necessary to put a fixed width to have it calculate the right
 * height value.
 * 2. Using flex="1" on non fixed height/width tree's will give unexpected results.
 * 
 *
 *
 * @class apf.hbox
 * @inherits apf.GuiElement
 * @define hbox
 * @layout
 * 
 * @see element.vbox
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 */
apf.hbox = function(struct, tagName) {
    this.$init(tagName || "hbox", apf.NODE_VISIBLE, struct);
};
apf.vbox = function(struct, tagName) {
    this.$init(tagName || "vbox", apf.NODE_VISIBLE, struct);
};

(function(){
    this.minwidth = 0;
    this.minheight = 0;
    
    // *** Properties and Attributes *** //

    this.$focussable = false;
    this.$useLateDom = true; 
    this.$box = true;
    this.$layout = true;
    
    var MOZSTACK = "-moz-stack";
    var input = {"INPUT":1, "SELECT":1, "TEXTAREA":1}

    /**
     * @attribute {String}  [padding=2]      Sets or gets the space between each element.
     */
    /**
     * @attribute {Boolean} reverse      Sets or gets whether the sequence of the elements is in reverse order.
     */
    /**
     * @attribute {String}  [edge="5,5,5,5"]         Sets or gets the space between the container and the elements, space seperated in pixels for each side. Similar to CSS in the sequence (_.i.e._. `top right bottom left`).
     * 
     * #### Example
     * 
     * ```xml
     *  <a:vbox edge="10 10 40 10" />
     * ```
     */
    // @todo Doc
    /**
     * @attribute {String} pack     
     *   
     *  Possible values include:
     * 
     *   - `"start"`:
     *   - `"center"`:
     *   - `"end"`:
     */
    /**
     * @attribute {Boolean} align
     *   
     *  Possible values include:
     * 
     *   - `"start"`:
     *   - `"center"`:
     *   - `"end"`:
     *   - `"stretch"`:
     */
    this.$booleanProperties["splitters"] = true;
    this.$supportedProperties.push("padding", "reverse", "edge", "pack", "align", "splitters");
    
    this.$propHandlers["padding"] = function(value) {
        this.padding = parseInt(value);
        
        var node, nodes = this.childNodes, elms = [];
        for (var i = 0, l = nodes.length; i < l; i++) {
            if ((node = nodes[i]).nodeFunc == apf.NODE_VISIBLE 
              && node.$ext && node.visible !== false)
                elms.push(node);
        }
        
        if (!elms.length)
            return;

        for (var last, b, el, i = elms.length - 2; i >= 0; i--) {
            b = (el = elms[i]).margin && apf.getBox(el.margin) || [0,0,0,0];
            
            if ((!last || !last.$splitter) && !el.$splitter) {
                b[this.$vbox ? 2 : 1] += this.padding;

                if (!apf.hasFlexibleBox && i != 0 && this.align == "stretch" && this.$vbox)
                    b[0] += this.padding;
            }
            
            el.$ext.style.margin = b.join("px ") + "px";
            last = el;
        }
        b = (el = elms[elms.length - 1]).margin && apf.getBox(el.margin) || [0,0,0,0];
        el.$ext.style.margin = b.join("px ") + "px";
        
        if (!apf.hasFlexibleBox)
            this.$resize();
    }
    
    this.$propHandlers["reverse"] = function(value) {
        if (apf.hasFlexibleBox)
            this.$int.style[apf.CSSPREFIX + "BoxDirection"] = value ? "reverse" : "normal";
        else {
            //@todo
        }
    };
    
    this.$propHandlers["edge"] = function(value) {
        var el = !apf.hasFlexibleBox && this.$vbox ? this.$ext : this.$int;
        el.style.padding = (this.$edge = apf.getBox(value)).join("px ") + "px";
        
        if (!apf.hasFlexibleBox)
            this.$resize();
    };
    
    this.$propHandlers["pack"] = function(value) {
        if (apf.hasFlexibleBox)
            this.$int.style[apf.CSSPREFIX + "BoxPack"] = value || "start";
        else if (this.$amlLoaded) {
            if (this.$vbox) {
                this.$int.style.verticalAlign = value == "center" ? "middle" : (value == "end" ? "bottom" : "top");
            }    
            else {
                this.$int.style.textAlign = "";
                
                var nodes = this.childNodes;
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || !node.$amlLoaded) //|| node.visible === false 
                        continue;

                    node.$ext.style.textAlign = apf.getStyle(node.$ext, "textAlign") || "left";
                }
                
                this.$int.style.textAlign = value == "center" ? "center" : (value == "end" ? "right" : "left");
            }
        }
    };
    
    //@todo change overflow when height/width changes depending on $vbox
    
    this.$propHandlers["align"] = function(value) {
        if (apf.hasFlexibleBox) {
            this.$int.style[apf.CSSPREFIX + "BoxAlign"] = value || "stretch";
            
            if (apf.isGecko)
                this.$int.style.overflow = "visible";

            //@todo this should probably be reinstated
            var stretch = !value || value == "stretch";
            var nodes = this.childNodes;
            var size = this.$vbox ? "width" : "height";
            
            var isInFixed = false, loopNode = this;
            while (!isInFixed && loopNode) {
                isInFixed = loopNode[size] || loopNode.anchors || (loopNode.$vbox ? loopNode.top && loopNode.bottom : loopNode.left && loopNode.right);
                if (!loopNode.flex)
                    break;
                loopNode = loopNode.parentNode || loopNode.$parentNode;
            }
            
            for (var i = 0, l = nodes.length; i < l; i++) {
                if (!(node = nodes[i]).$ext || node.$ext.nodeType != 1)
                    continue;

                //(this[size] || this.anchors || (this.$vbox ? this.top && this.bottom : this.left && this.right)
                if (stretch && !node[size]) //(node.$altExt || 
                    node.$ext.style[size] = apf.isGecko && (this.flex || node.flex) 
                        ? (isInFixed ? "1px" : "auto")
                        : (apf.isWebkit && input[node.$ext.tagName] 
                            ? "100%" 
                            : (false && apf.isWebkit && node[this.$vbox ? "minwidth" : "minheight"] && this.flex //nasty bug fix
                                ? "0px"
                                : "auto"));//(apf.isWebkit && node.flex && size == "height" ? "100%" : "auto"); // && (this.flex && node.flex)
                else if (node[size])
                    handlers["true"][size].call(node, node[size]);
            }
        }
        else if (this.$amlLoaded) {
            var stretch = !value || value == "stretch";
            
            if (!this.$vbox) {
                var nodes = this.childNodes;
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || !node.$amlLoaded) //|| node.visible === false 
                        continue;
                    
                    node.$ext.style.verticalAlign = value == "center" ? "middle" : (value == "end" ? "bottom" : "top");
                }
            }
            else {
                var el = !apf.hasFlexibleBox && this.$vbox ? this.$ext : this.$int;
                el.style.textAlign = "";
                
                var node, nodes = this.childNodes;
                for (var i = 0, l = nodes.length; i < l; i++) {
                    if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || !node.$amlLoaded) //|| node.visible === false 
                        continue;

                    if (node.visible !== false) {
                        node.$ext.style.display = value == "stretch" ? "block" : apf.INLINE;
                        node.$br.style.display = value == "stretch" ? "none" : "";
                        
                        if (apf.needZoomForLayout)
                            node.$ext.style.zoom = 1;
                    }
                    node.$ext.style.textAlign = apf.getStyle(node.$ext, "textAlign") || "left";
                }
                
                el.style.textAlign = value == "center" ? "center" : (value == "end" ? "right" : "left");
            }
        }
    };
    
    function visibleHandler(e) {
        
        if (this.parentNode.splitters && !this.$splitter) {
            if (!e.value) {
                if (this.nextSibling && this.nextSibling.$splitter)
                    this.nextSibling.removeNode();
                else if (this.previousSibling && this.previousSibling.$splitter)
                    this.previousSibling.removeNode();
            }
            else {
                var isLast = isLastVisibleChild(this);
                if (!isLast) {
                    if (!this.nextSibling.$splitter && !this.nextSibling.nosplitter
                      && !isFirstVisibleChild(this) && !this.nosplitter) {
                        this.parentNode.insertBefore(
                            this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), 
                            this.nextSibling);
                    }
                }
                else if (this.previousSibling && !this.previousSibling.$splitter
                   && !this.previousSibling.nosplitter) {
                    this.parentNode.insertBefore(
                        this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), 
                        this);
                }
            }
        }
        
        
        //@todo this can be more optimized by calcing if it WAS the last vis child.
        if (this.parentNode.$propHandlers["padding"]) {// && isLastVisibleChild(this)) {
            this.parentNode.$propHandlers["padding"]
                .call(this.parentNode, this.parentNode.padding);
        }
        
        apf.layout.forceResize(this.parentNode.$int);
        
        if (apf.hasFlexibleBox) {
            if (this.$altExt)
                this.$altExt.style.display = e.value 
                    ? (apf.isGecko ? MOZSTACK : apf.CSSPREFIX2 + "-box") 
                    : "none";
            return;
        }
        
        if (e.value) {
            this.$ext.style.display = this.parentNode.$vbox 
                && this.parentNode.align == "stretch" ? "block" : apf.INLINE;
            if (apf.needZoomForLayout)
                this.$ext.style.zoom = 1;
            if (this.$br)
                this.$br.style.display = this.parentNode.align == "stretch" ? "none" : "";
        }
        else {
            if (this.$br)
                this.$br.style.display = "none";
        }

        this.parentNode.$resize();
    }
    
    function resizeHandler(){
        if (!this.flex) {
            if (this.$isRszHandling || this.$lastSizeChild && 
              this.$lastSizeChild[0] == this.$ext.offsetWidth && 
              this.$lastSizeChild[1] == this.$ext.offsetHeight)
                return;
            
            /*if (this.$skipResizeOnce)
                delete this.$skipResizeOnce;
            else*/
                this.parentNode.$resize(true);
            
            this.$lastSizeChild = [this.$ext.offsetWidth, this.$ext.offsetHeight];
        }
    }
    
    var handlers = {
        //Handlers for flexible box layout
        "true" : {
            "optimize" : function(value) {
                this.optimize = apf.isTrue(value);
            },
            
            "width" : function(value) {
                //@todo this should check the largest and only allow that one
                //if (this.parentNode.$vbox && this.parentNode.align == "stretch")
                    //return;

                (this.$altExt || this.$ext).style.width = !apf.isNot(value) 
                    ? (parseFloat(value) == value 
                        ? value + "px"
                        : value)
                    : "";
            },
            
            "height" : function(value) {
                //@todo this should check the largest and only allow that one
                //if (!this.parentNode.$vbox && this.parentNode.align == "stretch")
                    //return;

                (this.$altExt || this.$ext).style.height = !apf.isNot(value) 
                    ? (parseFloat(value) == value 
                        ? value + "px"
                        : value)
                    : (apf.isGecko && this.flex && this.parentNode.$vbox ? "auto" : "");
            },
            
            "margin" : function(value) {
                var b = apf.getBox(value);
                if (!isLastVisibleChild(this))
                    b[this.parentNode.$vbox ? 2 : 1] += this.parentNode.padding;
                this.$ext.style.margin = b.join("px ") + "px";
            },
            
            "flex" : function(value) {
                this.flex = value = parseInt(value);
                if (value) {
                    if (!this.optimize && !this.$altExt) {
                        this.$altExt = this.$ext.ownerDocument.createElement("div");
                        this.parentNode.$int.replaceChild(this.$altExt, this.$ext);
                        this.$altExt.appendChild(this.$ext);
                        this.$altExt.style[apf.CSSPREFIX + "BoxSizing"] = "border-box";
                        this.$altExt.style.display = apf.CSSPREFIX2 + "-box";
                        this.$altExt.style[apf.CSSPREFIX + "BoxOrient"] = "vertical";
                        this.$ext.style[apf.CSSPREFIX + "BoxFlex"] = 1;
                        var size = this.parentNode.$vbox ? "height" : "width";
                        //var osize = this.parentNode.$vbox ? "width" : "height";
                        
                        if (apf.isWebkit) {
                            if (!this.preventforcezero)
                                this.$altExt.style[size] = "0px";
                        }
                        else if (apf.isGecko) {
                            this.$altExt.style[size] = "0px";
                            
                            //Possible hack to not do this for $box elements 
                            if (!this.$box)
                                this.$altExt.style.overflow = "hidden"; //Gecko
                            if (apf.getStyle(this.$ext, "overflow") == "visible")
                                this.$ext.style.overflow = "hidden"; //Gecko
                            this.$ext.style[size] = "1px";
                        
                            this.$altExt.style.minHeight = this.$ext.style.minHeight;
                            this.$altExt.style.maxHeight = this.$ext.style.maxHeight;
                            this.$altExt.style.minWidth = this.$ext.style.minWidth;
                            this.$altExt.style.maxWidth = this.$ext.style.maxWidth;
                        }
                    }
                    
                    (this.$altExt || this.$ext).style[apf.CSSPREFIX + "BoxFlex"] = parseInt(value) || 1;
                }
                else if (this.$altExt) {
                    this.parentNode.$int.replaceChild(this.$ext, this.$altExt);
                    this.$ext.style[apf.CSSPREFIX + "BoxFlex"] = "";
                    if (apf.isGecko)
                        this.$ext.style.overflow = "";
                    delete this.$altExt;
                }
            }
        },
        
        //Handlers for older browsers
        "false" : {
            "width" : function(value) {
                //@todo this should check the largest and only allow that one
                //if (this.parentNode.$vbox && this.parentNode.align == "stretch")
                    //return;
              
                this.$ext.style.width = value
                    ? (parseFloat(value) == value 
                        ? Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"
                        : value)
                    : "";
            },
            
            "height" : function(value) {
                //@todo this should check the largest and only allow that one
                //if (this.parentNode.localName == "hbox" && this.parentNode.align == "stretch")
                    //return;
      
                this.$ext.style.height = value 
                    ? (parseFloat(value) == value 
                        ? Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"
                        : value)
                    : "";
            },
            
            "margin" : function(value) {
                var b = apf.getBox(value);
                if (this.padding) {
                    if (!isLastVisibleChild(this))
                        b[this.parentNode.$vbox ? 2 : 1] += this.padding;
                    if (this != this.parentNode.firstChild && this.parentNode.align == "stretch" && this.parentNode.$vbox) //@todo
                        b[0] += this.padding;
                }
                this.$ext.style.margin = b.join("px ") + "px";
            },
            
            "flex" : function(value) {
                this.flex = parseInt(value);
                if (this.$amlLoaded)
                    this.parentNode.$resize(true);
            }
        }
    }
    
    function isFirstVisibleChild(amlNode) {
        var firstChild = amlNode.parentNode.firstChild;
        while (firstChild && (firstChild.nodeFunc != apf.NODE_VISIBLE 
          || firstChild.visible === false 
          || firstChild.visible == 2 && apf.isFalse(firstChild.getAttribute("visible")))) {
            firstChild = firstChild.nextSibling;
        }
        
        return firstChild && firstChild == amlNode;
    }
    
    function isLastVisibleChild(amlNode) {
        var lastChild = amlNode.parentNode.lastChild;
        while (lastChild && (lastChild.nodeFunc != apf.NODE_VISIBLE 
          || lastChild.visible === false 
          || lastChild.visible == 2 && apf.isFalse(lastChild.getAttribute("visible")))) {
            lastChild = lastChild.previousSibling;
        }
        
        return lastChild && lastChild == amlNode;
    }
    
    //@todo move this to enableTable, disableTable
    this.register = function(amlNode, insert) {
        if (amlNode.$altExt) //@todo hack, need to re-arch layouting
            return;

        amlNode.$propHandlers["left"] = 
        amlNode.$propHandlers["top"] = 
        amlNode.$propHandlers["right"] = 
        amlNode.$propHandlers["bottom"] = apf.K;

        var propHandlers = handlers[apf.hasFlexibleBox];
        for (var prop in propHandlers) {
            amlNode.$propHandlers[prop] = propHandlers[prop];
        }

        if (amlNode.nodeFunc == apf.NODE_VISIBLE) {
            if (apf.hasFlexibleBox) {
                //if (apf.isGecko && apf.getStyle(amlNode.$ext, "display") == "block")
                    //amlNode.$ext.style.display = MOZSTACK; //@todo visible toggle
                
                //input elements are not handled correctly by firefox and webkit
                if (amlNode.$ext.tagName == "INPUT" || apf.isWebkit && input[amlNode.$ext.tagName]) {
                    var doc = amlNode.$ext.ownerDocument;
                    amlNode.$altExt = doc.createElement("div");
                    amlNode.parentNode.$int.replaceChild(amlNode.$altExt, amlNode.$ext);
                    amlNode.$altExt.style[apf.CSSPREFIX + "BoxSizing"] = "border-box";
                    amlNode.$altExt.appendChild(amlNode.$ext);
                    
                    if (apf.isWebkit) {
                        var d = apf.getDiff(amlNode.$ext);
                        //amlNode.$altExt.style.padding = "0 " + d[0] + "px " + d[1] + "px 0";
                        amlNode.$altExt.style.height = "100%";
                        amlNode.$altExt.style.width = "0";
                        amlNode.$altExt.style.lineHeight = 0;
                        amlNode.$altExt.style.margin = "-1px 0 0 0";
                        amlNode.$ext.style.width = "100%";
                        amlNode.$ext.style.height = "100%";
                        amlNode.$ext.style.top = "1px";
                        amlNode.$ext.style.position = "relative";
                    }
                    else {
                        amlNode.$altExt.style.display = apf.CSSPREFIX2 + "-box";
                        amlNode.$altExt.style[apf.CSSPREFIX + "BoxOrient"] = "horizontal";
                        amlNode.$altExt.style[apf.CSSPREFIX + "BoxAlign"] = "stretch";
                        amlNode.$ext.style[apf.CSSPREFIX + "BoxFlex"] = 1;
                    }
                }
                else {
                    if (apf.getStyle(amlNode.$ext, "display") == "inline")
                        amlNode.$ext.style.display = "block"; //@todo undo
                    //This is nice for positioning elements in the context of an hbox/vbox
                    //if (apf.getStyle(amlNode.$ext, "position") == "absolute")
                        //amlNode.$ext.style.position = "relative"; //@todo undo
                }
                
                amlNode.$ext.style[apf.CSSPREFIX + "BoxSizing"] = "border-box";
            }
            else {
                if (this.$vbox) {
                    amlNode.$br = this.$int.insertBefore(amlNode.$ext.ownerDocument.createElement("br"), amlNode.$ext.nextSibling);
                    if (amlNode.visible === false)
                        amlNode.$br.style.display = "none";
                }
                else {
                    if (amlNode.visible !== false) {
                        amlNode.$ext.style.display = apf.INLINE;
                        if (apf.needZoomForLayout)
                            amlNode.$ext.style.zoom = 1;
                    }
                    this.$int.style.whiteSpace = "";
                    amlNode.$ext.style.whiteSpace = apf.getStyle(amlNode.$ext, "whiteSpace") || "normal";
                    this.$int.style.whiteSpace = "nowrap";
                }
                
                this.$int.style.fontSize = "0";
                if (!amlNode.$box) {
                    var fontSize = apf.getStyle(amlNode.$ext, "fontSize");
                    if (fontSize == "0px") {
                        amlNode.$ext.style.fontSize = "";
                        var pNode = this.$int.parentNode;
                        while (apf.getStyle(pNode, "fontSize") == "0px") {
                            pNode = pNode.parentNode;
                        }
                        fontSize = apf.getStyle(pNode, "fontSize");
                    }
                    amlNode.$ext.style.fontSize = fontSize;//apf.getStyle(amlNode.$ext, "fontSize") || "normal";
                }
                
                amlNode.addEventListener("resize", resizeHandler);
            }
            
            amlNode.addEventListener("prop.visible", visibleHandler);
    
            this.$noResize = true;
            
            if (amlNode.height)
                propHandlers.height.call(amlNode, amlNode.height);
            if (amlNode.width)
                propHandlers.width.call(amlNode, amlNode.width);
            if (amlNode.margin)
                propHandlers.margin.call(amlNode, amlNode.margin);
            if (amlNode.flex)
                propHandlers.flex.call(amlNode, amlNode.flex);    
                
            //Ie somehow sets the visible flags in between registration
            var isLast = isLastVisibleChild(amlNode); //apf.isIE ? this.lastChild == amlNode : 
            if (isLast || insert) {
                this.$propHandlers["padding"].call(this, this.padding);
                this.$propHandlers["align"].call(this, this.align);
                
                if (!apf.hasFlexibleBox)
                    this.$propHandlers["pack"].call(this, this.pack);
                    
                if (amlNode.visible !== false) //insert && - removed because for new nodes that are being attached to the tree insert is not set
                    visibleHandler.call(amlNode, {value: true});
                
                //@todo this needs more work
                if (insert && amlNode.previousSibling) {
                    var prev = amlNode.previousSibling;
                    while (prev && (prev.nodeType != 1 || prev.localName == "splitter"))
                        prev = prev.previousSibling;
                    if (prev)
                        visibleHandler.call(prev, {value: true});
                }
            }
            
            else if (this.splitters && !amlNode.$splitter && amlNode.visible !== false && !amlNode.nosplitter) {
                if (amlNode.$ext.nextSibling != (amlNode.nextSibling 
                  && (amlNode.nextSibling.$altExt || amlNode.nextSibling.$ext))) {
                    var _self = this;
                    setTimeout(function(){
                        _self.insertBefore(
                            _self.ownerDocument.createElementNS(apf.ns.aml, "splitter"), 
                            amlNode.nextSibling);
                    });
                }
                else {
                    this.insertBefore(
                        this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), 
                        amlNode.nextSibling);
                }
            }
            
        
            delete this.$noResize;
            
            if (!apf.hasFlexibleBox && isLast)
                this.$resize();
        }
    }
    
    this.unregister = function(amlNode) {
        if (!amlNode.$propHandlers)
            return;
        
        amlNode.$propHandlers["left"] = 
        amlNode.$propHandlers["top"] = 
        amlNode.$propHandlers["right"] = 
        amlNode.$propHandlers["bottom"] = null;
        
        var propHandlers = handlers[apf.hasFlexibleBox];
        for (var prop in propHandlers) {
            delete amlNode.$propHandlers[prop];
        }
        
        //Clear css properties and set layout
        if (amlNode.nodeFunc == apf.NODE_VISIBLE) {
            if (amlNode.flex) {
                var flex = amlNode.flex;
                propHandlers.flex.call(amlNode, 0);
                amlNode.flex = flex;
            }
            
            if (apf.hasFlexibleBox) {
                amlNode.$ext.style[apf.CSSPREFIX + "BoxSizing"] = "";
                
                if (apf.isGecko) {
                    this.$int.style.overflow = "visible";
                
                    if (amlNode.$ext.style.display == "block")
                        amlNode.$ext.style.display = "";
                }
            }
            else {
                amlNode.$ext.style.verticalAlign = "";
                amlNode.$ext.style.textAlign = "";
                amlNode.$ext.style.whiteSpace = "";
                //amlNode.$ext.style[apf.CSSFLOAT] = "";
                
                if (amlNode.$br) {
                    amlNode.$br.parentNode.removeChild(amlNode.$br);
                    delete amlNode.$br;
                    //amlNode.$ext.style.fontSize = "";
                }
                
                amlNode.removeEventListener("resize", resizeHandler);
            }
            
            amlNode.removeEventListener("prop.visible", visibleHandler);
            
            amlNode.$ext.style.display = amlNode.visible ? "block" : "none";
            
            if (amlNode.margin)
                amlNode.$ext.style.margin = "";
            
            if (amlNode.width)
                amlNode.$ext.style.width = "";

            
            if (this.splitters && !amlNode.$splitter) {
                if (amlNode.nextSibling && amlNode.nextSibling.$splitter)
                    amlNode.nextSibling.removeNode();
                if (isLastVisibleChild(amlNode) && amlNode.previousSibling 
                  && amlNode.previousSibling.$splitter)
                    amlNode.previousSibling.removeNode();
            }
            
        }
    }
    /*
         this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.register(this.parentNode);
    });
    */
    
    // *** DOM Hooks *** //
    
    this.addEventListener("DOMNodeRemoved", function(e) {
        if (e.$doOnlyAdmin || e.currentTarget == this)
            return;

        if (e.relatedNode == this) {
            this.unregister(e.currentTarget);
            //e.currentTarget.$setLayout();
        }
    });

    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget == this) {
            if (this.visible)
                this.$ext.style.display = apf.CSSPREFIX2 + "-box"; //Webkit issue
            return;
        }
        
        if (e.currentTarget.nodeType != 1 
          || e.currentTarget.nodeFunc != apf.NODE_VISIBLE)
            return;

        if (e.relatedNode == this && !e.$isMoveWithinParent) {
            e.currentTarget.$setLayout(this.localName, true);
            
            if (e.currentTarget.$altExt) {
                
                return false;
            }
        }
    });

    function myVisibleHandler(e) {
        if (e.value)
            this.$int.style.display = apf.CSSPREFIX2 + "-box";
    }
    
    function myHeightHandler(e) {
        clearInterval(this.$heighttimer);
        if (e.value || this.align != "stretch") {
            delete this.$heighttimer;
        }
        else if (!this.$heighttimer) {
            var _self = this;
            this.$heighttimer = $setInterval(function(){
                if (_self.$amlDestroyed)
                    return;

                var nodes = _self.childNodes;
                for (var $int, i = 0, l = nodes.length; i < l; i++) {
                    if (!($int = (node = nodes[i]).$int || node.$container))
                        continue;

                    if (Math.min($int.scrollHeight, node["maxheight"] || 10000) > $int.offsetHeight)
                        return _self.$resize(true);
                }
                
                if (_self.flex)
                    clearInterval(this.$heighttimer);
            }, this.flex ? 1 : 500);
        }
    }
    
    this.$draw = function(){
        var doc = this.$pHtmlNode.ownerDocument;
        this.$ext = this.$pHtmlNode.appendChild(doc.createElement("div"));
        if (this.getAttribute("style"))
            this.$ext.setAttribute("style", this.getAttribute("style"));
        this.$ext.className = this.localName;

        this.$vbox = this.localName == "vbox";
        this.$int = apf.isGecko && !(this.parentNode 
          && "hbox|vbox".indexOf(this.parentNode.localName) > -1) 
          || !apf.hasFlexibleBox && this.$vbox //@todo reparenting for gecko needs some admin work
            ? this.$ext.appendChild(doc.createElement("div")) 
            : this.$ext;
        this.$ext.host = this;
        
        if (apf.isGecko && !(this.parentNode 
          && "hbox|vbox".indexOf(this.parentNode.localName) > -1)) {
            this.$int.style.width = "100%";
            this.$int.style.height = "100%";
        }
        else if (!apf.hasFlexibleBox && this.$vbox) {
            this.$int.style.display = apf.INLINE;
            if (apf.needZoomForLayout)
                this.$int.style.zoom = 1;
            this.$int.style.width = "100%";
        }
        
        if (apf.hasFlexibleBox) {
            this.$display = "-" + apf.CSSPREFIX +"-box";
            
            this.$int.style.display = apf.CSSPREFIX2 + "-box";
            this.$int.style[apf.CSSPREFIX + "BoxOrient"] = this.localName == "hbox" ? "horizontal" : "vertical";
            if (apf.isGecko) //!webkit
                this.$int.style[apf.CSSPREFIX + "BoxSizing"] = "border-box";
            this.$int.style[apf.CSSPREFIX + "BoxAlign"] = "stretch";
            
            this.addEventListener("prop.visible", myVisibleHandler);
        }
        else {
            if (!this.$vbox) {
                this.$int.style.whiteSpace = "nowrap";
                this.addEventListener("prop.height", myHeightHandler);
            }

            var spacer = (!apf.hasFlexibleBox && this.$vbox ? this.$ext : this.$int)
                            .appendChild(doc.createElement("strong"));
            spacer.style.height = "100%";
            spacer.style.display = apf.INLINE;
            if (apf.needZoomForLayout)
                spacer.style.zoom = 1;
            //spacer.style.marginLeft = "-4px";
            spacer.style.verticalAlign = "middle";
            
            this.addEventListener("resize", this.$resize);
        }

        if (this.getAttribute("class")) 
            apf.setStyleClass(this.$ext, this.getAttribute("class"));
        
        this.$originalMin = [this.minwidth || 0,  this.minheight || 0];
    };
    
    this.$resize = function(force) {
        if (!this.$amlLoaded || this.$noResize) // || apf.isIE7 && force !== true)
            return;

        //Protection for stretch re-resizing
        if (force !== true && this.$lastSize && 
          this.$lastSize[0] == this.$int.offsetWidth && 
          this.$lastSize[1] == this.$int.offsetHeight)
            return;
        
        if (!apf.window.vManager.check(this, this.$uniqueId, this.$resize))
            return;
        
        this.$noResize = true;
        this.$lastSize = [this.$int.offsetWidth, this.$int.offsetHeight];

        //this.$ext.style.border = "1px solid " + (["red", "green", "blue", "orange", "pink", "yellow"])[Math.round(Math.random() * 5)];
        
        /*if (this.$table.offsetWidth >= this.$ext.offsetWidth)
            this.$ext.style.minWidth = (this.minwidth = Math.max(0, this.$table.offsetWidth 
                - apf.getWidthDiff(this.$ext))) + "px";
        else {
            this.$ext.style.minWidth = "";
            this.minwidth = this.$originalMin[0];
        }

        if (this.$table.offsetHeight >= this.$ext.offsetHeight)
            this.$ext.style.minHeight = (this.minheight = Math.max(0, this.$table.offsetHeight 
                - apf.getHeightDiff(this.$ext))) + "px";
        else {
            this.$ext.style.minHeight = "";
            this.minheight = this.$originalMin[1];
        }*/
        
        //if (!this.$vbox) alert("here");

        var total = 0;
        var size = this.$vbox ? "width" : "height";
        var minsize = this.$vbox ? "minWidth" : "minHeight";
        var osize = this.$vbox ? "height" : "width";
        var scroll = this.$vbox ? "scrollWidth"  : "scrollHeight";
        var offset = this.$vbox ? "offsetWidth" : "offsetHeight";
        var ooffset = this.$vbox ? "offsetHeight" : "offsetWidth";
        var getDiff = this.$vbox ? "getWidthDiff" : "getHeightDiff";
        var ogetDiff = this.$vbox ? "getHeightDiff" : "getWidthDiff";
        var inner = this.$vbox ? "getHtmlInnerWidth" : "getHtmlInnerHeight";
        var oinner = this.$vbox ? "getHtmlInnerHeight" : "getHtmlInnerWidth";
        var borders = this.$vbox ? "getVerBorders" : "getHorBorders";

        var nodes = this.childNodes, hNodes = [], fW = 0, max = 0;
        for (var node, i = 0; i < nodes.length; i++) {
            if ((node = nodes[i]).nodeFunc != apf.NODE_VISIBLE || node.visible === false || !node.$amlLoaded)
                continue;

            hNodes.push(node);
            if (!node[size]) {
                //if (!node.$skipResizeOnce) node.$skipResizeOnce = 1;
                //else node.$skipResizeOnce++;
                //node.$skipResizeOnce = 1
                //node.$ext.style[size] = ""; //@todo this is a sucky way of measuring
                var m = node.margin && apf.getBox(node.margin);
                if (m && this.$vbox) m.unshift();
                var mdiff = (m ? m[0] + m[2] : 0);
                max = Math.max(max, mdiff + Math.min(node.$ext[scroll] + apf[borders](node.$ext), node["max" + size] || 10000));
            }

            if (parseInt(node.flex))
                total += parseFloat(node.flex);
            else {
                var m = node.margin && apf.getBox(node.margin);
                if (m && !this.$vbox) m.shift();
                fW += node.$ext[ooffset] + (m ? m[0] + m[2] : 0); //this.padding + 
            }
        }
        if (!max && this[size]) {
            max = this[size] 
                //- (this.$vbox ? this.$edge[0] + this.$edge[2] : this.$edge[1] + this.$edge[3]);
                - apf[ogetDiff](this.$ext);
        }

        /*
             && (this[size] || this.flex)
        */
        if (this.align == "stretch") {
            //var hasSize = this[size] || this.flex;
            var l = hNodes.length;
            var pH = max;//this.$int[offset] - apf[getDiff](this.$int);// - (2 * this.padding);
            for (var i = 0; i < l; i++) {
                node = hNodes[i];

                if (!node[size] && !this.$vbox || this.$vbox && input[node.$ext.tagName]) {
                    var m = node.margin && apf.getBox(node.margin);
                    if (m && this.$vbox) m.unshift();
                    var mdiff = (m ? m[0] + m[2] : 0);
                    
                    /*shouldClear = !this[size] && !this.flex && node.$ext.offsetHeight == (pH - mdiff);
                    if (shouldClear)
                        node.$ext.style[size] = "";
                    else
                        node.$ext.style[size] = Math.max(0, pH - apf[getDiff](node.$ext) - mdiff) + "px";
                    node.$setResizeHeight = !shouldClear;*/

                    //!this[size] && !this.flex
                    if (max && Math.min(node.$ext[scroll], node["max" + size] || 10000) != max)
                        node.$ext.style[size] = Math.max(0, max - apf[getDiff](node.$ext) - mdiff) + "px";
                    else
                        node.$ext.style[size] = "";
                    
                    /*node.$ext.style[size] = !this[size] && !this.flex && node.$ext.offsetHeight == pH - mdiff
                        ? ""
                        : Math.max(0, pH - apf[getDiff](node.$ext) - mdiff) + "px";*/
                }
            }
        }

        //Flexing
        if (total > 0) {
            if (this.$vbox)
                this.$int.style.height = "100%";
            this.$int.style.overflow = "hidden";
            
            var splitterCount = apf.n(this).children("a:splitter").length() * 2;
            
            var rW = this.$int[ooffset] - apf[ogetDiff](this.$int) - fW 
              - ((hNodes.length - 1 - splitterCount) * this.padding);// - (2 * this.edge);
            var lW = rW, done = 0;
            for (var i = 0, l = hNodes.length; i < l; i++) {
                if ((node = hNodes[i]).flex) {
                    var v = (i % 2 == 0 ? Math.floor : Math.ceil)((rW / total) * parseInt(node.flex));
                    done += parseInt(node.flex);
                    var m = node.margin && apf.getBox(node.margin);
                    if (m && !this.$vbox) m.shift();
                    node.$ext.style[osize] = Math.max(0, (done == total ? lW : v) - apf[ogetDiff](node.$ext) - (m ? m[0] + m[2] : 0)) + "px";
                    lW -= v;
                }
            }
        }
        else {
            if (this.$vbox)
                this.$int.style.height = "";
            this.$int.style.overflow = "";
        }
        
        this.$noResize = false;
        /*this.$noResize = true;
        var _self = this;
        setTimeout(function(){
            _self.$noResize = false;
        });*/
    }
    
    this.$loadAml = function(x) {
        if (this.padding == undefined)
            this.padding = 0;
            //this.$propHandlers.padding.call(this, this.padding = 0);
        if (this.edge == undefined)
            this.$propHandlers.edge.call(this, this.edge = 0);
        if (this.pack == undefined)
            this.$propHandlers.pack.call(this, this.edge = "start");
        if (this.align == undefined)
            this.align = "stretch";
            //this.$propHandlers.align.call(this, this.align = "stretch");
        if (!apf.hasFlexibleBox && !this.$vbox && !this.height && this.align == "stretch")
            myHeightHandler.call(this, {});
    };
}).call(apf.vbox.prototype = new apf.GuiElement());

apf.hbox.prototype = apf.vbox.prototype;

apf.aml.setElement("hbox", apf.hbox);
apf.aml.setElement("vbox", apf.vbox);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * The element displays a picture. This element can read databound resources.
 * 
 * #### Example
 * 
 * This example shows a list with pictures. When one is selected its displayed
 * in the `<a:img>` element:
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <!-- startcontent -->
 *  <a:model id="mdlPictures"> 
 *       <data> 
 *           <picture title="Ceiling Cat" src="http://htstatic.ibsrv.net/forums/honda-tech/ceiling-cat/ceiling-cat-6.jpg" />
 *           <picture title="Maru" src="http://1.bp.blogspot.com/_4Cb_t7BLaIA/TCY3jyIx4SI/AAAAAAAAAbw/K-Ey_u36y8o/s400/maru+the+japanese+cat.jpg" />
 *           <picture title="Lime Cat" src="http://www.cs.brown.edu/orgs/artemis/2012/catsoftheworld/lime-cat.jpg" />
 *       </data> 
 *   </a:model>
 *   <a:list 
 *     id = "lstPics" 
 *     model = "mdlPictures">
 *       <a:each match="[picture]" >
 *           <a:caption match="[@title]" />
 *       </a:each>
 *   </a:list>
 *   <a:img 
 *     model = "{lstPics.selected}" 
 *     value = "[@src]" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.img
 * @define img
 * @media
 * @allowchild {smartbinding}
 *
 *
 * @inherits apf.BaseSimple
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 */
/**
 * @event click Fires when a user presses a mouse button while over this element.
 *
 */
/**
 *  @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * 
 * #### Example
 * 
 * Sets the image source based on data loaded into this component.
 * 
 * ```xml
 *  <a:model id="mdlPictures"> 
 *      <data src="path/to/image.jpg" /> 
 *  </a:model>
 *  <a:img 
 *    model = "mdlPictures" 
 *    value = "[@src]" 
 *    width = "300" 
 *    height = "300" />
 * ```
 */
apf.img = function(struct, tagName) {
    this.$init(tagName || "img", apf.NODE_VISIBLE, struct);
};

apf.preview = function(struct, tagName) {
    this.$init(tagName || "preview", apf.NODE_VISIBLE, struct);
};

(function(){
    
    
    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };
    
    /**
     * Returns the current value of this element.
     * @return {String} The current image
     */
    this.getValue = function(value) {
        return this.value;
    };
    
    
    
    this.$supportedProperties.push("value", "src");
    /**
     * @attribute {String} value Sets or gets the url location of the image displayed.
     */
    this.$propHandlers["src"] = 
    this.$propHandlers["value"] = function(value) {
        if (this.oImage.nodeType == 1)
            this.oImage.style.backgroundImage = "url(" + value + ")";
        else
            this.oImage.nodeValue = value;
        
        //@todo resize should become a generic thing
        if (this.oImage.nodeType == 2 && !this.$resize.done) {
            if (this.oImg) {
                
                //@todo add this to $destroy
                var pNode = apf.hasSingleRszEvent ? this.$pHtmlNode : this.$ext;
                apf.layout.setRules(pNode, this.$uniqueId + "_image",
                    "var o = apf.all[" + this.$uniqueId + "];\
                     if (o) o.$resize()");
                apf.layout.queue(pNode);
                
                this.oImg.onload = function(){
                    apf.layout.forceResize(pNode);
                }
                
            }
            
            this.$resize.done = true;
        }

        if (this.oImg) {
            this.oImg.style.display = value ? "block" : "none";
            
            //RLD: disabled lines below for the preview element. the image is probably not loaded yet.
            //if (value)
                //this.$resize();
        }
    };

    this.refetch = function(){
    this.$propHandlers["value"].call(this, "")
    this.$propHandlers["value"].call(this, this.value || this.src)
    }
    
    this.addEventListener("$clear", function(){
        this.value = "";
        
        if (this.oImg)
            this.oImg.style.display = "none";
    });
    
    // *** Init *** //
    
    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$ext.onclick = function(e) {
            this.host.dispatchEvent("click", {htmlEvent: e || event});
        };
        this.oImage = this.$getLayoutNode("main", "image", this.$ext);
        if (this.oImage.nodeType == 1)
            this.oImg = this.oImage.getElementsByTagName("img")[0];
        if (this.localName == "preview") {
            var _self = this;
            this.$ext.onclick = function() {
                if (!_self.sPreview) return;
                _self.$ext.innerHTML = _self.sPreview;
                this.onclick = null;
            };
        }
        
        var _self = this;
        apf.addListener(this.$ext, "mouseover", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("mouseover", {htmlEvent: e});
        });
        
        apf.addListener(this.$ext, "mouseout", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("mouseout", {htmlEvent: e});
        });
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function() {
        var node,
            val = "",
            i = this.childNodes.length;

        for (; i >= 0; --i) {
            if ((node = this.childNodes[i]) && node.nodeName
              && node.nodeName == "#cdata-section") {
                val = node.nodeValue;
                node.removeNode();
            }
        }

        this.sPreview = val;
    });
    
    this.$resize = function(){
        var diff = apf.getDiff(this.$ext);
        var wratio = 1, hratio = 1;

        this.oImg.style.width = "";
        this.oImg.style.height = "";
        
        if (this.oImg.offsetWidth > this.$ext.offsetWidth)
            wratio = this.oImg.offsetWidth / (this.$ext.offsetWidth - diff[0]);
        if (this.oImg.offsetHeight > this.$ext.offsetHeight)
            hratio = this.oImg.offsetHeight / (this.$ext.offsetHeight - diff[1]);

        if (wratio > hratio && wratio > 1)
            this.oImg.style.width = "100%";
        else if (hratio > wratio && hratio > 1)
            this.oImg.style.height = "100%";
        
        this.oImg.style.top = ((this.$ext.offsetHeight - apf.getHeightDiff(this.$ext) 
            - this.oImg.offsetHeight) / 2) + "px";
    }
}).call(apf.img.prototype = new apf.BaseSimple());

apf.preview.prototype = apf.img.prototype;

apf.aml.setElement("img", apf.img);
apf.aml.setElement("preview", apf.preview);

apf.aml.setElement("name", apf.BindingRule);
apf.aml.setElement("image", apf.BindingRule);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Represents an item in a menu, displaying a clickable area.
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:menu id="menu1">
 *      <a:item>Tutorials</a:item>
 *      <a:item>Contact</a:item>
 *   </a:menu>
 *   <a:toolbar>
 *      <a:menubar>
 *          <a:button submenu="menu1">File</a:button>
 *      </a:menubar>
 *   </a:toolbar>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.item
 * @selection
 * @define item
 * @inherits apf.Presentation
 *
 */
/**
 * @event click Fires when a user presses the mouse button while over this element.
 * @param {Object} e The standard event object. It contains the following properties:
 *  - xmlContext ([[XMLElement]]): The XML data node that was selected in the opener at the time of showing the context menu.
 *  - opener ([[apf.AmlElement]]): The element that was clicked upon when showing the context menu.
 */
apf.item = function(struct, tagName) {
    this.$init(tagName || "item", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$focussable = false;
    this.$childProperty = "caption";
    this.$canLeechSkin = "item";

    this.checked = false;
    this.selected = false;

    this.implement(apf.ChildValue);

    // *** Properties and Attributes *** //
    
    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        "match" : 1
    }, this.$attrExcludePropBind);

    this.$booleanProperties["checked"] = true;
    this.$booleanProperties["selected"] = true;

    this.$supportedProperties.push("submenu", "value", "match", "group", "icon",
                                   "checked", "selected", "disabled", "caption", 
                                   "type", "values");

    /**
     * @attribute {String} submenu Sets or gets the id of the menu that is shown
     * when the user hovers over this menu item.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:menu id="msub">
     *      <a:item icon="tbicons:12">test</a:item>
     *      <a:item icon="tbicons:14">test2</a:item>
     *  </a:menu>
     *
     *  <a:menu id="mmain">
     *      <a:item submenu="msub">Sub menu</a:item>
     *  </a:menu>
     *  
     *  <a:toolbar>
     *      <a:menubar>
     *          <a:button submenu="mmain">File</a:button>
     *      </a:menubar>
     *  </a:toolbar>
     * ```
     */
    this.$propHandlers["submenu"] = function(value) {
        apf.setStyleClass(this.$ext, "submenu");
    }
    
    /**
     * @attribute {String} value Sets or gets the value of this element.
     */

    /**
     * @attribute {String} [select] Sets or gets the XPath statement which works on the
     * XML context of the parent menu element to determine whether this
     * item is shown.
     * 
     * #### Example
     * 
     * This example shows a list:
     * 
     * ```xml
     *   <a:menu id="mnuTest">
     *       <a:item match="[person]" method="send">Send an E-mail</a:item>
     *       <a:item match="[phone]" method="call">Call Number</a:item>
     *       <a:divider />
     *       <a:item match="[phone]" method="remove">Remove</a:item>
     *       <a:divider />
     *       <a:item match="[person|phone]" method="viewpictures">View Pictures</a:item>
     *   </a:menu>
     *   
     *   <a:menu id="mnuXY">
     *       <a:item method="reboot">Reboot</a:item>
     *   </a:menu>
     *   
     *   <a:text contextmenu="mnuXY" width="200" height="200">
     *       Please right-click on this plane
     *   </a:text>
     *   
     *   <a:list id="lstTest" allow-deselect="true" width="200" height="200">
     *       <a:each match="[person|phone|computer]">
     *           <a:caption match="[@caption]" />
     *           <a:icon match="[person]" value="user.png" />
     *           <a:icon match="[phone]" value="phone.png" />
     *           <a:icon match="[computer]" value="computer.png" />
     *       </a:each>
     *       <a:model>
     *           <data>
     *               <person caption="Ruben Daniels" />
     *               <person caption="Rik Arends" />
     *               <phone caption="+31 555 544486" />
     *               <phone caption="+1 555 2392" />
     *               <computer caption="Mail Server" />
     *               <computer caption="File Server" />
     *           </data>
     *       </a:model>
     *       <a:contextmenu menu="mnuXY" match="[computer]" />
     *       <a:contextmenu menu="mnuTest" />
     *   </a:list>
     * ```
     */
    this.$propHandlers["select"] = function(value) {
        this.select = value
            ? "self::" + value.split("|").join("|self::")
            : value;
    }
    
    /**
     * @attribute {String} [group] Sets or gets the name of the group this item belongs
     * to.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:menu>
     *      <a:item type="radio" group="example">item 1</a:item>
     *      <a:item type="radio" group="example">item 2</a:item>
     *      <a:item type="radio" group="example">item 3</a:item>
     *      <a:item type="radio" group="example">item 4</a:item>
     *  </a:menu>
     * ```
     */
    this.$propHandlers["group"] = function(value) {
        if (this.$group && this.$group.$removeRadio)
            this.$group.$removeRadio(this);
            
        if (!value) {
            this.$group = null;
            return;
        }
        
        var group = typeof value == "string"
            ? 
            
            apf.nameserver.get("group", value)
            
            : value;
        if (!group) {
            
            group = apf.nameserver.register("group", value, 
                new apf.$group());
            group.setAttribute("id", value);
            group.dispatchEvent("DOMNodeInsertedIntoDocument");
            group.parentNode = this;
            
        }
        this.$group = group;
        
        this.$group.$addRadio(this);
    };

    
    /**
     * @attribute {String} hotkey Sets or gets the key combination a user can press
     * to active the function of this element. Use any combination of
     * [[keys: Ctrl]], [[keys: Shift]], [[keys: Alt]], [[keys: F1]]-[[keys: F12]], and alphanumerical characters. Use a
     * space, a minus or plus sign as a seperator.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:item hotkey="Ctrl+Q">Quit</a:item>
     * ```
     */
    this.$propHandlers["hotkey"] = function(value) {
        if (!this.$amlLoaded) {
            var _self = this;
            this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
                if (_self.$hotkey && _self.hotkey)
                    apf.setNodeValue(this.$hotkey, apf.isMac 
                      ? apf.hotkeys.toMacNotation(_self.hotkey) : _self.hotkey);
            });
        }
        else if (this.$hotkey)
            apf.setNodeValue(this.$hotkey, apf.isMac ? apf.hotkeys.toMacNotation(value) : value);

        if (this.$lastHotkey) {
            apf.hotkeys.remove(this.$lastHotkey[0], this.$lastHotkey[1]);
            delete this.$lastHotkey[0];
        }

        if (value) {
            this.$lastHotkey = [value];
            var _self = this;
            apf.hotkeys.register(value, this.$lastHotkey[1] = function(){
                if (_self.disabled || !_self.visible)
                    return;
                
                //hmm not very scalable...
                if (_self.parentNode) {
                    var buttons = apf.document.getElementsByTagNameNS(apf.ns.aml, "button");
                    for (var i = 0; i < buttons.length; i++) {
                        if (buttons[i].submenu == _self.parentNode.name) {
                            var btn = buttons[i];
                            btn.$setState("Over", {});
    
                            $setTimeout(function(){
                                btn.$setState("Out", {});
                            }, 200);
    
                            break;
                        }
                    }
                }
                
                _self.$down();
                _self.$up();
                _self.$click();
            });
        }
    }
    
    /**
     * @attribute {String} icon Sets or gets the URL of the image used as an icon or
     * a reference to an iconmap.
     */
    this.$propHandlers["icon"] = function(value) {
        if (this.$icon)
            apf.skins.setIcon(this.$icon, value, this.parentNode.iconPath);
    }
    
    /**
     * @attribute {String} caption Sets or gets the text displayed on the item.
     */
    this.$propHandlers["caption"] = function(value) {
        if (this.$caption)
            apf.setNodeValue(this.$caption, value);
    }
    
    /**
     * @attribute {String} type Sets or gets the function of this item.
     * 
     * Possible values include:
     * - `"item"`
     * - `"check"`
     * - `"radio"`
     */
    this.$propHandlers["type"] = function(value) {
        apf.setStyleClass(this.$ext, value, ["item", "check", "radio"]);
    }
    
    this.$propHandlers["values"] = function(value) {
        this.$values = typeof value == "string"
            ? value.split("\|")
            : (value || [1, 0]);

        this.$propHandlers["value"].call(this, this.value);
    };
    
    this.$propHandlers["value"] = function(value) {
        if (this.type != "check")
            return;
        
        value = (typeof value == "string" ? value.trim() : value);
        
        var checked;
        if (this.$values) {
            checked = (typeof value != "undefined" && value !== null
                && value.toString() == this.$values[0].toString());
        }
        else {
            checked = apf.isTrue(value);
        }
        
        if (checked != this.checked) {
            this.checked = checked;
            this.$propHandlers.checked.call(this, checked);
        }
    };
    
    /**
     * @attribute {Boolean} checked Sets or gets whether the item is checked.
     */
    this.$propHandlers["checked"] = function(value) {
        if (this.type != "check")
            return;

        if (apf.isTrue(value))
            apf.setStyleClass(this.$ext, "checked");
        else
            apf.setStyleClass(this.$ext, "", ["checked"]);
        
        if (!this.$values) {
            if (this.getAttribute("values"))
                this.$propHandlers["values"].call(this, this.getAttribute("values"));
            //else
                //this.$values = [true, false];
        }
        
        if (this.$values && this.$values[value ? 0 : 1] != this.value)
            this.setProperty("value", this.$values ? this.$values[value ? 0 : 1] : true);
    }
    
    this.select = function(){
        this.parentNode.select(this.group, this.value || this.caption);
    }
    
    this.check = function(){
        this.setProperty("value", this.$values
            ? this.$values[0]
            : true);
    }
    this.uncheck = function(){
        this.setProperty("value", this.$values
            ? this.$values[1]
            : false);
    }
    
    this.$check = function(){
        apf.setStyleClass(this.$ext, "selected");
    }
    
    this.$uncheck = function(){
        apf.setStyleClass(this.$ext, "", ["selected"]);
    }

    /**
     * @attribute {Boolean} selected Sets or gets whether the item is selected.
     */
    this.$propHandlers["selected"] = function(value) {
        if (this.type != "radio")
            return;


        if (apf.isTrue(value)) {
            if (this.$group)
                this.$group.setProperty("value", this.value);
            this.$check();
        }
        else
            this.$uncheck();
    }
    
    /**
     * @attribute {Boolean} disabled Sets or gets whether the item is active.
     */
    this.$propHandlers["disabled"] = function(value) {
        if (apf.isTrue(value) || value == -1)
            apf.setStyleClass(this.$ext, "disabled");
        else
            apf.setStyleClass(this.$ext, "", ["disabled"]);
    }

    // *** Dom Hooks *** //

    //@todo apf3.0
    this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent) {
        if (!this.$amlLoaded)
            return;

        if (!withinParent && this.skinName != pNode.skinName) {
            //@todo for now, assuming dom garbage collection doesn't leak
            this.loadAml();
        }
    });

    // *** Events *** //

    this.$down = function(){
    
    };

    this.$up = function(){
   
        
        if (this.type == "radio")
            this.parentNode.select(this.group, this.value || this.caption);

        else if (this.type == "check") {
            this.setProperty("checked", !this.checked);
            //this.$handlePropSet("checked", !this.checked);
        }

        if (this.submenu) {
            this.$over(null, true);
            return;
        }

        this.parentNode.$hideTree = true;
        
        //@todo This statement makes the menu loose focus.
        if (!this.parentNode.sticky)
            this.parentNode.hide();//true not focus?/

        this.parentNode.dispatchEvent("itemclick", {
            value: this.value || this.caption,
            relatedNode: this,
            checked: this.checked,
            selected: this.selected
        });

        //@todo Anim effect here?
        
        this.dispatchEvent("click", {
            xmlContext: this.parentNode.xmlReference,
            opener: this.parentNode.opener
        });
        
        
    };

    this.$click = function(){
        
    };

    var timer;
    this.$out = function(e) {
        if (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget)
          || apf.isChildOf(this.$ext, e.srcElement || e.target))  //@todo test FF
            return;

        clearTimeout(timer);
        if (!this.submenu || this.$submenu(true)) {
            apf.setStyleClass(this.$ext, "", ['hover']);

            var sel = this.parentNode.$selected;
            if (sel && sel != this)
                apf.setStyleClass(sel.$ext, "", ["hover"]);

            this.parentNode.$selected = null;
        }
        
        
    };

    this.$over = function(e, force) {
        if (this.parentNode.$selected == this && e)
            return;

        if (this.parentNode.$selected)
            apf.setStyleClass(this.parentNode.$selected.$ext, "", ["hover"]);

        apf.setStyleClass(this.$ext, "hover");
        this.parentNode.$selected = this;

        if (!force && (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget)
          || apf.isChildOf(this.$ext, e.fromElement || e.target)))  //@todo test FF
            return;

        var _self = this, ps = this.parentNode.$showingSubMenu;
        if (ps && ps.name && ps.name == this.submenu) {
            this.parentNode.$selected = null;
            this.parentNode.$showingSubMenu = null;
            _self.$submenu();
            return;
        }
            
        clearTimeout(timer);
        
        if (ps && ps.visible) {
            ps.hide();
            
            if (_self.parentNode.$showingSubMenu == ps)
                _self.parentNode.$showingSubMenu = null;
        }
        
        function submenu(){
            if (_self.submenu && _self.parentNode.opener 
              && _self.parentNode.opener.visible)
                _self.$submenu();
        }

        if (force)
            submenu();
        else {
            timer = $setTimeout(function(){
                submenu();
                timer = null;
            }, 210);
        }
    };

    this.$submenu = function(hide, force) {
        if (!this.submenu)
            return true;

        var menu = self[this.submenu] || this.submenu;
        if (!menu) {
            

            return;
        }

        if (!hide) {
            //if (this.parentNode.showingSubMenu == this.submenu)
                //return;

            this.parentNode.$showingSubMenu = menu;

            var pos = apf.getAbsolutePosition(this.$ext, this.parentNode.$ext.offsetParent);
            menu.display(pos[0] + this.$ext.offsetWidth - 3,
                pos[1] + 3, true, this,
                this.parentNode.xmlReference, this.parentNode.$uniqueId);
            menu.setAttribute("zindex", (this.parentNode.zindex || this.parentNode.$ext.style.zIndex || 1) + 1);
        }
        else {
            if (menu.visible && !force) {
                return false;
            }
            
            if (this.parentNode.$showingSubMenu == menu)
                this.parentNode.$showingSubMenu = null;
            
            apf.setStyleClass(this.$ext, '', ['hover']);
            menu.hide();
            return true;
        }
    };

    // *** Init *** //
    
    this.$draw = function(isSkinSwitch) {
        var p = this.parentNode;
        while (p.$canLeechSkin == "item")
            p = p.parentNode;
        
        //@todo apf3.0 rename doesnt work yet.
        //@todo apf3.0 implement DOM Mutation events for multiselect widgets
        //@todo apf3.0 implement attribute change triggers for icon, image, value, caption to updateNode this.$container
        //@todo apf3.x this should be rearchitected
        //@todo apf3.x the functions dont need to be overloaded if selectNodes would work properly
        if (p.hasFeature(apf.__MULTISELECT__)) {
            var _self = this;
            
            //@todo DOMNodeInserted should reset this
            //@todo DOMNodeRemoved should reset this
            if (!this.$hasSetSkinListener) {
                var f;
                this.parentNode.addEventListener("$skinchange", f = function(){
                    if (_self.$amlDestroyed) //@todo apf3.x
                        return;
                    
                    if (_self.$ext.parentNode)
                        this.$deInitNode(_self, _self.$ext);
    
                    var oInt = p == _self.parentNode ? p.$container : _self.parentNode.$container;
                    var node = oInt.lastChild;//@todo this should be more generic
                    p.$add(_self, _self.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId, 
                        _self.parentNode, oInt != p.$container && oInt, null);
                    p.$fill();
                    
                    if (p.$isTreeArch) {
                        _self.$container = p.$getLayoutNode("item", "container", 
                           _self.$ext = node && node.nextSibling || oInt.firstChild);//@todo this should be more generic
                    }
                    else _self.$ext = node && node.nextSibling || oInt.firstChild;
                    
                    var ns = _self;
                    while ((ns = ns.nextSibling) && ns.nodeType != 1);
        
                    if (!ns || ns.$canLeechSkin != "item")
                        p.dispatchEvent("afterload");
                });
                this.addEventListener("DOMNodeRemoved", function(e) {
                    if (e.currentTarget == this)
                        this.parentNode.removeEventListener("$skinchange", f);
                });
                
                this.$hasSetSkinListener = true;
            }
            
            if (!p.$itemInited) {
                p.canrename = false; //@todo fix rename
                p.$removeClearMessage(); //@todo this should be more generic
                p.$itemInited = [p.getTraverseNodes, p.getFirstTraverseNode, p.getTraverseParent];
                
                p.getTraverseNodes = function(xmlNode) {
                    return (xmlNode || p).getElementsByTagNameNS(apf.ns.apf, "item");
                }
                p.getFirstTraverseNode = function(xmlNode) {
                    return (xmlNode || p).getElementsByTagNameNS(apf.ns.apf, "item")[0];
                }
                p.getTraverseParent = function(xmlNode) {
                    return xmlNode && xmlNode.parentNode;
                }
                p.each = (this.prefix ? this.prefix + ":" : "") + "item";

                //@todo this is all an ugly hack (copied to baselist.js line 868)
                p.$preventDataLoad = true;//@todo apf3.0 add remove for this

                p.$initingModel = true;
                p.$setDynamicProperty("icon", "[@icon]");
                p.$setDynamicProperty("image", "[@image]");
                p.$setDynamicProperty("caption", "[label/text()|@caption|text()]");
                p.$setDynamicProperty("eachvalue", "[value/text()|@value|text()]");
                p.$canLoadDataAttr = false;
                
                if (!p.xmlRoot)
                    p.xmlRoot = p;
            }
            
            this.$loadAml = function(){
                //hack
                if (!this.getAttribute("caption"))
                    this.setAttribute("caption", this.caption);
                
                var oInt = p == this.parentNode ? p.$container : this.parentNode.$container;
                var node = oInt.lastChild;//@todo this should be more generic
                if (!p.documentId)
                    p.documentId = apf.xmldb.getXmlDocId(this);
                p.$add(this, apf.xmldb.nodeConnect(p.documentId, this, null, p), 
                    this.parentNode, oInt != p.$container && oInt, null);
                p.$fill();
    
                if (p.$isTreeArch) {
                    this.$container = p.$getLayoutNode("item", "container", 
                       this.$ext = node && node.nextSibling || oInt.firstChild);//@todo this should be more generic
                }
                else this.$ext = node && node.nextSibling || oInt.firstChild;
                
                var ns = this;
                while ((ns = ns.nextSibling) && ns.nodeType != 1);
    
                if (!ns || ns.$canLeechSkin != "item") {
                    p.dispatchEvent("afterload");
                    if (p.autoselect)
                        p.$selectDefault(this.parentNode);
                }
            }
            
            return;
        }
        
        this.$ext = this.$getExternal(this.$isLeechingSkin
          ? "item" //this.type 
          : "main", null, function($ext) {
            var o = 'var o = apf.lookup(' + this.$uniqueId + '); if (!o || o.disabled) return; o';
            $ext.setAttribute("onmouseup",   o + '.$up(event)');
            $ext.setAttribute("onmousemove", o + '.$over(event)');
            $ext.setAttribute("onmouseout",  o + '.$out(event)');
            $ext.setAttribute("onmousedown", o + '.$down()');
            $ext.setAttribute("onclick",     o + '.$click()');
        });

        var _self = this;
        apf.addListener(this.$ext, "mouseover", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("mouseover", {htmlEvent: e});
        });
        
        apf.addListener(this.$ext, "mouseout", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("mouseout", {htmlEvent: e});
        });
        
        /*p.$getNewContext("item");
        var elItem = p.$getLayoutNode("item");*/
        
        //@todo if not elItem try using own skin
        
        //this.$ext = apf.insertHtmlNode(elItem, this.parentNode.$container);
        this.$caption = this.$getLayoutNode("item", "caption", this.$ext)
        this.$icon = this.$getLayoutNode("item", "icon", this.$ext);
        this.$hotkey = this.$getLayoutNode("item", "hotkey", this.$ext);

        if (!isSkinSwitch && this.nextSibling && this.nextSibling.$ext)
            this.$ext.parentNode.insertBefore(this.$ext, this.nextSibling.$ext);
    };
    
    /*
     * @private
     */
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        //var x = this.$aml;

        //this.skinName = this.parentNode.skinName;
        var isSkinSwitch = this.$ext ? true : false;
        if (isSkinSwitch) {
            if (typeof this.checked !== "undefined")
                this.$handlePropSet("checked", this.checked);
            else if (typeof this.selected !== "undefined")
                this.$handlePropSet("selected", this.selected);

            if (this.disabled)
                this.$handlePropSet("disabled", this.disabled);

            if (this.caption)
                this.$handlePropSet("caption", this.caption);
        }
    });
}).call(apf.item.prototype = new apf.Presentation());

//apf.aml.setElement("radio", apf.radio);
//apf.aml.setElement("check", apf.check);
apf.aml.setElement("item",  apf.item);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * A symbolic link to an AML element. The visibility of this element determines
 * the true parent of the references AML element. Multiple junctions will compete
 * to determine the parent. Make sure only one junction reference is visible
 * at the same time.
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.junction = function(){
    this.$init("junction", apf.NODE_HIDDEN);
};

(function(){
    this.$focussable = false;
    
    this.autoshow = true;
    
    /**
     * @attribute {String} for
     * @attribute {String} autoshow
     */
    this.$booleanProperties["autoshow"] = true;
    
    this.$propHandlers["for"] = function(value) {
        if (this.$amlLoaded) //@todo remove vManager
            init.call(this);
    }
    
    function init(e) {
        var _self = this;
        if (apf.window.vManager.permanent(this.parentNode, function(){
            //Show
            _self.$reparent();
        }, function(){
            //Hide
            
        })) {
            this.$reparent();
        }
    }
    
    this.$reparent = function(){
        var amlNode = self[this["for"]];
        if (!amlNode)
            return; //@todo warn?
        
        if (this.autoshow)
            amlNode.show();
        
        this.parentNode.insertBefore(amlNode, this);
    }
    
    this.addEventListener("DOMNodeInsertedIntoDocument", init);
}).call(apf.junction.prototype = new apf.AmlElement());

apf.aml.setElement("junction", apf.junction);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * An element displaying a text in the user interface, usually specifying
 * a description of another element. When the user clicks on the label, it 
 * can set the focus to the connected AML element.
 * 
 * #### Example: Connecting with "For"
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:label 
 *     for = "txtAddress"
 *     disabled = "true" 
 *     caption = "Disabled label"></a:label>
 *   <a:textbox id="txtAddress" />
 *   <a:label 
 *     for = "txtAddress2">Not Disabled</a:label>
 *   <a:textbox id="txtAddress2" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.label
 * @define label
 * @allowchild {smartbinding}
 *
 * @form 
 * @inherits apf.BaseSimple
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
/**
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * 
 * #### Example
 * 
 * Sets the label text based on data loaded into this component.
 * 
 * ```xml
 *  <a:model id="mdlLabel">
 *      <data text="Some text"></data>
 *  </a:model>
 *  <a:label model="mdlLabel" value="[@text]" />
 * ```
 * 
 * A shorter way to write this is:
 * 
 * ```xml
 *  <a:model id="mdlLabel">
 *      <data text="Some text"></data>
 *  </a:model>
 *  <a:label value="[mdlLabel::@text]" />
 * ```
 */
apf.label = function(struct, tagName) {
    this.$init(tagName || "label", apf.NODE_VISIBLE, struct);
};

(function(){
    this.implement(
        
        apf.DataAction,
        
        apf.ChildValue
    );

    var _self = this;
    
    this.$focussable = false;
    var forElement;
    
    
    
    /**
     * Sets the value of this element. This should be one of the values
     * specified in the values attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };
    
    /**
     * Returns the current value of this element.
     * @return {String} The current value
     */
    this.getValue = function(){
        return this.value;
    }
    
    
    
    /** 
     * @attribute {String} caption Sets or gets the text displayed in the area defined by this 
     * element. Using the value attribute provides an alternative to using
     * the text using a text node.
     *
     */
    /**
     * @attribute {String} for Sets or gets the id of the element that receives the focus 
     * when the label is clicked on.
     */
    /**
     * @attribute {String} textalign Sets or gets the text alignment value for the label.
     */
    this.$supportedProperties.push("caption", "for", "textalign");
    this.$propHandlers["caption"] = function(value) {
        this.$caption.innerHTML = value;
    };
    this.$propHandlers["for"] = function(value) {
        forElement = typeof value == "string" ? self[value] : value;
    };
    this.$propHandlers["textalign"] = function(value) {
        this.$caption.style.textAlign = value || "";
    };

    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$caption = this.$getLayoutNode("main", "caption", this.$ext);
        if (this.$caption.nodeType != 1) 
            this.$caption = this.$caption.parentNode;
        
        this.$ext.onmousedown = function(){
            if (forElement && forElement.$focussable && forElement.focussable)
                forElement.focus();
        }
        
        var _self = this;
        apf.addListener(this.$ext, "click", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("click", {htmlEvent: e});
        });
        
        apf.addListener(this.$ext, "mouseover", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("mouseover", {htmlEvent: e});
        });
        
        apf.addListener(this.$ext, "mouseout", function(e) {
            if (!_self.disabled)
                _self.dispatchEvent("mouseout", {htmlEvent: e});
        });
    };
    
    this.$childProperty = "caption";
    
}).call(apf.label.prototype = new apf.BaseSimple());

apf.aml.setElement("label", apf.label);


apf.colorbox = function(struct, tagName) {
    this.$init(tagName || "colorbox", apf.NODE_VISIBLE, struct);
};

(function(){
    this.implement(
        
        apf.DataAction,
        
        apf.ChildValue
    );

    var _self = this;
    
    this.$focussable = false;
    var forElement;
    
    
    
    /**
     * Sets the value of this element. This should be one of the values
     * specified in the values attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };
    
    /**
     * Returns the current value of this element.
     * @return {String} The current value
     */
    this.getValue = function(){
        return this.value;
    }
    
    
    
    /** 
     * @attribute {String} caption Sets or gets the text displayed in the area defined by this 
     * element. Using the value attribute provides an alternative to using
     * the text using a text node.
     *
     */
    /**
     * @attribute {String} for Sets or gets the id of the element that receives the focus 
     * when the colorbox is clicked on.
     */
    /**
     * @attribute {String} textalign Sets or gets the text alignment value for the colorbox.
     */
    this.$supportedProperties.push("value");
    this.$propHandlers["value"] = function(value) {
        this.$input.value = value;
    };

    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$input = this.$getLayoutNode("main", "input", this.$ext);
        
        var _self = this;
        this.$input.onchange = function(){
            _self.change(this.value);
        }
    };
    
    this.$childProperty = "value";
    
}).call(apf.colorbox.prototype = new apf.BaseSimple());

apf.aml.setElement("colorbox", apf.colorbox);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * This element displays a skinnable list of options which can be selected.
 * 
 * Selection of multiple items is allowed. Items can be renamed
 * and removed. The list can be used as a collection of checkboxes or 
 * radiobuttons. This is especially useful for use in forms.
 * 
 * This element is one of the most often used elements. It can display lists
 * of items in a CMS-style interface, or display a list of search results in 
 * a more website like interface.
 * 
 * #### Example: A Simple List
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:list>
 *      <a:item>The Netherlands</a:item>
 *      <a:item>United States of America</a:item>
 *      <a:item>United Kingdom</a:item> 
 *   </a:list>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Loading from a Model
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:list 
 *     model = "/api/resources/xml/friends.xml"
 *     each = "[friend]"
 *     caption = "[@name]"
 *     icon = "[@icon]"
 *     width = "300">
 *   </a:list>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Using XPaths
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:list model="/api/apf/resources/xml/friends.xml" width="300">
 *       <a:each match="[friend]">
 *           <a:caption match="[@name]" />
 *           <a:icon 
 *             match = "[node()[@name='Ruben' or @name='Matt']]" 
 *             value = "/api/apf/resources/icons/medal_gold_1.png" />
 *           <a:icon value="/api/apf/resources/icons/medal_silver_1.png" />
 *       </a:each>
 *   </a:list>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * @class apf.list
 * @define list
 * @allowchild {smartbinding}
 *
 * @selection
 * @inherits apf.BaseList
 * @inherits apf.Rename
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
/**
 * @event click Fires when a user presses a mouse button while over this element.
 */

apf.list = function(struct, tagName) {
    this.$init(tagName || "list", apf.NODE_VISIBLE, struct);
};

apf.thumbnail = function(struct, tagName) {
    this.$init(tagName || "thumbnail", apf.NODE_VISIBLE, struct);
};

apf.select = function(struct, tagName) {
    this.$init(tagName || "select", apf.NODE_VISIBLE, struct);
};

apf.select1 = function(struct, tagName) {
    this.$init(tagName || "selectl", apf.NODE_VISIBLE, struct);
};

(function(){
    this.morePos = "end";
    
    
    if (!apf.isIphone)
        this.implement(apf.Rename);
    
    
    
    this.$getCaptionElement = function(){
        if (!(this.$caret || this.$selected))
            return;
        
        var x = this.$getLayoutNode("item", "caption", this.$caret || this.$selected);
        if (!x) 
            return;
        return x.nodeType == 1 ? x : x.parentNode;
    };
    
    
    
    
    
    
    
    // *** Properties and Attributes *** //
    
    this.$supportedProperties.push("appearance", "mode", "more", "thumbsize", "morepos");
    
    this.$propHandlers["morepos"] = function(value) {
        this.morePos = value; 
    };
    
    this.$propHandlers["thumbsize"] = function(value) {
        var className = this.thumbclass;
        
        if (apf.isIE) { //@todo detection??
            className = className.splitSafe(",");
            for (var i = 0; i < className.length; i++) {
                apf.setStyleRule(className[i], "width", value + "px");
                apf.setStyleRule(className[i], "height",  value + "px");
            }
            return;
        }
        
        apf.setStyleRule(className, "width", value + "px");
        apf.setStyleRule(className, "height",  value + "px");
    };
    
    
    /**
     * @attribute {String} appearance Sets or gets the type of select this element is. 
     * This is an xforms property and only available if APF is compiled 
     * with `__WITH_XFORMS` set to `1`.
     *   
     * Possible values include:
     * 
     *   - `"full"` :    depending on the tagName this element is either a list of radio options or of checked options.
     *   - `"compact"`:  this elements functions like a list with multiselect off.
     *   - `"minimal"`:  this element functions as a dropdown element.
     */
    this.$propHandlers["appearance"] = function(value) {
        
    };
    
    
    /**
     * @attribute {String} more Adds a new item to the list and lets the users 
     * type in the new name. This is especially useful in the interface when 
     * the mode is set to check or radio--for instance in a form.
     * 
     * #### Example
     * 
     * This example shows a list in form offering the user several options. The
     * user can add a new option. A server script could remember the addition
     * and present it to all new users of the form.
     * 
     * ```xml
     *  <a:model id="mdlSuggestions">
     *      <suggestions>
     *          <question key="krant">
     *              <answer>Suggestion 1</answer>
     *              <answer>Suggestion 2</answer>
     *          </question>
     *      </suggestions>
     * </a:model>
     * <a:label>Which newspapers do you read?</a:label>
     * <a:list value="[krant]" 
     *   more = "caption:Add new suggestion" 
     *   model = "[mdlSuggestions::question[@key='krant']]">
     *     <a:bindings>
     *         <a:caption match="[text()]" />
     *         <a:value match="[text()]" />
     *         <a:each match="[answer]" />
     *     </a:bindings>
     *     <a:actions>
     *         <a:rename match="[node()[@custom='1']]" />
     *         <a:remove match="[node()[@custom='1']]" />
     *         <a:add>
     *             <answer custom="1">New Answer</answer>
     *         </a:add>
     *     </a:actions>
     *  </a:list>
     * ```
     */
    this.$propHandlers["more"] = function(value) {
        if (value) {
            this.delayedselect = false;
            this.addEventListener("xmlupdate", $xmlUpdate);
            this.addEventListener("afterload", $xmlUpdate);
            //this.addEventListener("afterrename", $afterRenameMore);
            //this.addEventListener("beforeselect", $beforeSelect);
            
            this.$addMoreItem = function(msg) {
                if (!this.moreItem)
                    this.$fill();
                if (this.morePos == "begin")
                    this.$container.insertBefore(this.moreItem, this.$container.firstChild);
                else
                    this.$container.appendChild(this.moreItem);
            };
            this.$updateClearMessage = function(){}
            this.$removeClearMessage = function(){};
        }
        else {
            this.removeEventListener("xmlupdate", $xmlUpdate);
            this.removeEventListener("afterload", $xmlUpdate);
            //this.removeEventListener("afterrename", $afterRenameMore);
            //this.removeEventListener("beforeselect", $beforeSelect);
        }
    };
    
    function $xmlUpdate(e) {
        if ((!e.action || "insert|add|synchronize|move".indexOf(e.action) > -1) && this.moreItem) {
            if (this.morePos == "begin")
                this.$container.insertBefore(this.moreItem, this.$container.firstChild);
            else
                this.$container.appendChild(this.moreItem);
        }
    }
    
    /*function $afterRenameMore(){
        var caption = this.$applyBindRule("caption", this.caret)
        var xmlNode = this.findXmlNodeByValue(caption);

        var curNode = this.caret;
        if (xmlNode != curNode || !caption) {
            if (xmlNode && !this.isSelected(xmlNode)) 
                this.select(xmlNode);
            this.remove(curNode);
        }
        else 
            if (!this.isSelected(curNode)) 
                this.select(curNode);
    }
    
    function $beforeSelect(e) {
        //This is a hack
        if (e.xmlNode && this.isSelected(e.xmlNode) 
          && e.xmlNode.getAttribute('custom') == '1') {
            this.setCaret(e.xmlNode);
            this.selected = e.xmlNode;
            $setTimeout(function(){
                _self.startRename()
            });
            return false;
        }
    }*/
    
    
    // *** Keyboard support *** //
    
    
    this.addEventListener("keydown", this.$keyHandler, true);
    
    
    // *** Init *** //
    
    this.$draw = function(){
        this.appearance = this.getAttribute("appearance") || "compact";

        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$container = this.$getLayoutNode("main", "container", this.$ext);
        
        if (apf.hasCssUpdateScrollbarBug && !this.mode)
            this.$fixScrollBug();
        
        var _self = this;
        this.$ext.onclick = function(e) {
            _self.dispatchEvent("click", {
                htmlEvent: e || event
            });
        }
        
        
        
        //Get Options form skin
        //Types: 1=One dimensional List, 2=Two dimensional List
        this.listtype = parseInt(this.$getOption("main", "type")) || 1;
        //Types: 1=Check on click, 2=Check independent
        this.behaviour = parseInt(this.$getOption("main", "behaviour")) || 1; 
        
        this.thumbsize = this.$getOption("main", "thumbsize");
        this.thumbclass = this.$getOption("main", "thumbclass");
    };
    
    this.$loadAml = function(x) {
    };
    
    this.$destroy = function(){
        if (this.$ext)
            this.$ext.onclick = null;
        apf.destroyHtmlNode(this.oDrag);
        this.oDrag = null;
    };
}).call(apf.list.prototype = new apf.BaseList());

apf.thumbnail.prototype =
apf.select.prototype = 
apf.select1.prototype = apf.list.prototype;

apf.aml.setElement("thumbnail", apf.thumbnail);
apf.aml.setElement("select",    apf.select);
apf.aml.setElement("select1",   apf.select1);
apf.aml.setElement("list",      apf.list);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @todo description
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.loader = function(){
    this.$init("loader", apf.NODE_HIDDEN);
    
    this.show = function(){
        this.$ext.style.display = "block";
    }
    
    this.hide = function(){
        this.$ext.style.display = "none";
    }
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var pHtmlNode;
        if (!(pHtmlNode = this.parentNode.$int)) 
            return;

        this.$ext = apf.insertHtmlNode(null, pHtmlNode, null, (this.$aml 
            ? (this.$aml.serialize ? this.$aml.serialize() : this.$aml.xml)
            : this.serialize()).replace(/^<[^>]*>\s*|\s*<[^>]*>$/g, ""));
        
        if (!apf.loaded) {
            var _self = this;
            if (apf.config.loaderAnimIndicator)
                _self.animateLoader(10);
            apf.addEventListener("load", function(){
                if (apf.isTrue(apf.config.autoHideLoading)) {
                    apf.queue.empty();
                    _self.hide();
                }
                else {
                    if (apf.config.loaderAnimIndicator)
                        _self.animateLoader(20);
                }
            });
        }
    });
    
    this.lastLoaderStep = 0;
    
    /*
     * Animates a loader indiacator
     * 
     * the step is in the % of the total width of the indicator
     *
     */
    this.animateLoader = function(step) {      
        var _self = this,
            loaderEl = document.getElementById(apf.config.loaderAnimIndicator);
        if (!loaderEl)
            return;
            
        step = apf.config.loaderWidth * (step/100);

        var fromStep = this.lastLoaderStep,
            toStep = this.lastLoaderStep + step;
        
        this.lastLoaderStep = toStep;

        if (toStep > apf.config.loaderWidth)
            toStep = apf.config.loaderWidth;

        apf.tween.single(document.getElementById('animatedLoader'), {
            steps: 5,
            anim: apf.tween.EASEOUT,
            type: "width",
            from: fromStep,
            to: toStep,
            onfinish: function() {
                if (toStep >= apf.config.loaderWidth) {
                    apf.queue.empty();
                    _self.hide();
                }
            }
        });
    };
};

apf.loader.prototype = new apf.AmlElement();

apf.aml.setElement("loader", apf.loader);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * This element displays a skinnable menu of items which can be choosen.
 * 
 * Based on the context of the menu, items can be shown and hidden. 
 * 
 *
 * #### Example
 * 
 * ```xml, demo
 *  <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:menu id="menu1">
 *       <a:item>Tutorials</a:item>
 *       <a:item icon="email.png">Contact</a:item>
 *       <a:divider></a:divider>
 *       <a:item 
 *         icon = "application_view_icons.png"
 *         hotkey = "Ctrl+T"
 *         onclick = "setTimeout(function(){alert('You did it');}, 1000)">
 *         Tutorials</a:item>
 *       <a:divider />
 *       <a:item disabled="true">Visit Ajax.org</a:item>
 *       <a:item>Exit</a:item>
 *   </a:menu>
 *   <a:window
 *     width = "400"
 *     height = "150"
 *     visible = "true"
 *     resizable = "true"
 *     title = "Mail message"
 *     skin = "bk-window2">
 *       <a:toolbar>
 *           <a:menubar>
 *               <a:button submenu="menu1">File</a:button>
 *               <a:button submenu="menu1" disabled="true">Edit</a:button>
 *           </a:menubar>
 *       </a:toolbar>
 *   </a:window>
 *   <!-- endcontent -->
 *  </a:application>
 * ```
 *
 * @class apf.menu
 * @define menu
 * @selection
 * @allowchild item, divider, check, radio
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @inherits apf.Presentation
 */
/**
 * @event display   Fires when the contextmenu is shown.
 */
/**
 * @event itemclick Fires when a user presses the mouse button while over a child of this element.
 * @param {Object} e The standard event object. The following property is available:
 *   - `value` ([[String]]): the value of the clicked element.
 *
 */
apf.menu = function(struct, tagName) {
    this.$init(tagName || "menu", apf.NODE_VISIBLE, struct);
    
    this.animate = apf.enableAnim;
};

(function(){
    this.$focussable = apf.MENU;
    this.$positioning = "basic"
    //var _self = this;
    //var blurring = false;

    // *** Properties and Attributes *** //
    
    //this.zindex = 10000000;
    this.visible = false;
    this.matchhide = false;

    this.$booleanProperties["animate"] = true;
    this.$booleanProperties["pinned"] = true;
    this.$booleanProperties["sticky"] = true;
    this.$booleanProperties["matchhide"] = true;
    
    this.$propHandlers["visible"] = function(value, prop, force, nofocus, hideOpener) {
        if (!this.$ext)
            return;
        
        if (value) {
            this.$ext.style.display = "block";
            if (this.opener && this.opener.localName.indexOf('item') > -1)
                this.opener.parentNode.$showingSubMenu = this;
        }
        else {
            this.$ext.style.display = "none";

            var lastFocus = apf.menu.lastFocus;
            var opener = this.opener;
            //@todo test this with a list being the opener of the menu
            if (lastFocus != this.opener && this.opener && this.opener.$blur)
                this.opener.$blur();

            if (this.opener && this.opener.parentNode && this.opener.parentNode.localName == "menu") {
                if (!this.$hideTree)
                    this.$hideTree = -1
                this.opener.parentNode.focus();
            }
            
            
            else if (lastFocus) {
                //We're being hidden because some other object gets focus
                if (apf.window.$settingFocus) {
                    if (apf.window.$settingFocus != lastFocus && lastFocus.$blur)
                        lastFocus.$blur();
                    this.$blur();

                    if (apf.window.$settingFocus.localName != "menu") //not menu walking
                        apf.menu.lastFocus = null;
                }
                //We're being hidden because window looses focus
                
                //We're just being hidden
                else if (this.$hideTree) {
                    if (!this.$hideTree)
                        this.$hideTree = -1

                    var visTest = (lastFocus.disabled || lastFocus.$ext 
                        && !lastFocus.$ext.offsetHeight || !lastFocus.visible)
                        && lastFocus != apf.document.documentElement;

                    if (nofocus || visTest) {
                        if (lastFocus.$blur)
                            lastFocus.$blur();
                        this.$blur();
                        apf.document.activeElement = null;

                        if (visTest && apf.window.moveNext() === false)
                            apf.window.$focusRoot();
                    }
                    else {
                        lastFocus.focus(null, null, true);
                    }

                    apf.menu.lastFocus = null;
                }
            }
            

            clearTimeout(this.$submenuTimer);

            if (this.$showingSubMenu) {
                this.$showingSubMenu.hide();
                this.$showingSubMenu = null;
            }

            if (this.opener && this.opener.$submenu) {
                this.opener.$submenu(true, true);

                //@todo problem with loosing focus when window looses focus
                if (this.$hideTree === true && this.opener
                  && this.opener.parentNode 
                  && this.opener.parentNode.localName == "menu"
                  && this.opener.parentNode.$hideTree != -1) {
                    this.opener.parentNode.$hideTree = true
                    this.opener.parentNode.hide();
                }
                
                this.opener = null;
            }
            this.$hideTree = null;

            if (this.$selected) {
                apf.setStyleClass(this.$selected.$ext, "", ["hover"]);
                this.$selected = null;
            }
            
            this.dispatchEvent("hide", {opener: opener});
        }
    };

    // *** Public Methods *** //

    var lastFocus;

    /**
     * Shows the menu, optionally within a certain context.
     * @param {Number}     x        The left position of the menu.
     * @param {Number}     y        The top position of the menu.
     * @param {Boolean}    noanim   Whether to animate the showing of this menu.
     * @param {apf.AmlElement} opener   The element that is the context of this menu.
     * @param {XMLElement} xmlNode  The {@link term.datanode data node} that provides data context to the menu child nodes.
     * @see apf.GuiElement@contextmenu
     */
    this.display = function(x, y, noanim, opener, xmlNode, openMenuId, btnWidth) {
        this.opener = opener;
        
        //Show / hide Child Nodes Based on XML
        if (xmlNode && !this.disabled) {
            var last, i, node,
                nodes = this.childNodes,
                c = 0,
                l = nodes.length, result;
            for (i = 0; i < l; i++) {
                node = nodes[i];
                if (node.nodeType != 1 || node.localName != "item")
                    continue;
    
                result = !xmlNode || !node.match || (node.cmatch || (node.cmatch = apf.lm.compile(node.match, {
                    xpathmode: 3,
                    injectself: true
                })))(xmlNode)
    
                if (result) {
                    if (this.matchhide)
                        node.show();
                    else
                        node.enable();
    
                    if (node.localName == "divider" && this.matchhide) {
                        last = node;
                        if (c == 0)
                            node.hide();
                        c = 0;
                    }
                    else c++;
                }
                else {
                    if (this.matchhide)
                        node.hide();
                    else
                        node.disable();
    
                    if (!node.nextSibling && c == 0 && last)
                        last.hide();
                }
            }
        }

        if (this.oOverlay) {
            if (btnWidth) {
                this.oOverlay.style.display = "block";
                this.oOverlay.style.width = btnWidth + "px";
            }
            else
                this.oOverlay.style.display = "none";
        }

        function afterRender(){
            if (x === null) {
                apf.popup.show(this.$uniqueId, {
                    x: 0, 
                    y: this.ref ? 0 : opener.$ext.offsetHeight, 
                    animate: noanim || !this.animate ? false : "fade",
                    steps: 10,
                    ref: (this.ref || opener).$ext,
                    allowTogether: openMenuId,
                    autohide: !this.pinned,
                    noleft: this.left !== undefined,
                    setZindex: this.zindex ? false : true,
                    up: (this.ref || opener).submenudir == "up"
                });
            }
            else {
                //var bodyPos = apf.getAbsolutePosition(document.body);
                apf.popup.show(this.$uniqueId, {
                    x: x, 
                    y: y - (apf.isIE && apf.isIE < 8 ? 1 : 0), 
                    animate: noanim || !this.animate ? false : "fade",
                    steps: 10,
                    //ref          : this.$ext.offsetParent,
                    allowTogether: openMenuId,
                    autohide: !this.pinned,
                    setZindex: this.zindex ? false : true
                    //autoCorrect  : false
                });
            }
            
            var lastFocus = 
            apf.menu.lastFocus = opener && opener.$focussable === true
                ? opener
                : apf.menu.lastFocus || apf.document.activeElement;
            
            apf.popup.last = null;
            
            //if (!apf.isGecko) //This disables keyboard support for gecko - very strange behaviour
                this.focus();
    
            //Make the component that provides context appear to have focus
    
            if (lastFocus && lastFocus != this && lastFocus.$focus)
                lastFocus.$focus();
    
            this.xmlReference = xmlNode;

            //@todo consider renaming this to onshow and onhide
            this.dispatchEvent("display", {opener: opener});
        }
        
        this.visible = false;
        
        if (!this.parentNode)
            apf.document.documentElement.appendChild(this);
        
        if (this.$rendered !== false) {
            this.show();
            afterRender.call(this);
        }
        else {
            this.addEventListener("afterrender", afterRender);
            this.show();
        }                
    };

    /**
     * Returns the current group value of this element.
     * @return {String} The current selected value.
     */
    this.getValue = function(group) {
        return this.getSelected(group).value || "";
    };

    /**
     * Retrieves the selected element from a group of radio elements.
     * @param {String} group The name of the group.
     * @return {apf.radiobutton} The selected radio element.
     */
    this.getSelected = function(group) {
        var nodes = this.childNodes;
        var i, l = nodes.length;
        for (i = 0; i < l; i++) {
            if (nodes[i].group != group)
                continue;

            if (nodes[i].selected)
                return nodes[i];
        }

        return false;
    };

    /**
     * Selects an element within a radio group.
     * @param {String} group  The name of the group.
     * @param {String} value  The value of the item to select.
     */
    this.select = function(group, value) {
        var nodes = this.childNodes;
        var i, l = nodes.length;
        for (i = 0; i < l; i++) {
            if (nodes[i].group != group)
                continue;

            if (value && (nodes[i].value == value || !nodes[i].value && nodes[i].caption == value))
                nodes[i].setProperty("selected", true, false, true);
                //nodes[i].$handlePropSet("selected", true);
            else if (nodes[i].selected)
                nodes[i].setProperty("selected", false, false, true);
                //nodes[i].$handlePropSet("selected", false);
        }
    };

    // *** Events *** //

    
    this.addEventListener("keydown", function(e) {
        var node, key = e.keyCode;
        //var ctrlKey = e.ctrlKey;
        //var shiftKey = e.shiftKey;

        switch (key) {
            case 13:
                if (!this.$selected)
                    return;

                node = this.$selected;
                node.$down();
                node.$up();
                node.$click();
                break;
            case 27:
                this.hide();
                break;
            case 38:
                //UP
                node = this.$selected && this.$selected.previousSibling
                  || this.lastChild;

                if (node && node.localName == "divider")
                    node = node.previousSibling;

                if (!node)
                    return;

                if (this.$selected)
                    apf.setStyleClass(this.$selected.$ext, "", ["hover"]);

                apf.setStyleClass(node.$ext, "hover");
                this.$selected = node;
                break;
            case 40:
                //DOWN
                node = this.$selected && this.$selected.nextSibling
                  || this.firstChild;

                if (node && node.localName == "divider")
                    node = node.nextSibling;

                if (!node)
                    return;

                if (this.$selected)
                    apf.setStyleClass(this.$selected.$ext, "", ["hover"]);

                apf.setStyleClass(node.$ext, "hover");
                this.$selected = node;
                break;
            case 37:
                //LEFT
                //if (this.$selected && this.$selected.submenu)
                    //this.$selected.$submenu(true, true);

                if (!this.opener)
                    return;

                if (this.opener.localName == "button") {
                    node = this.opener.previousSibling;
                    while (node && !node.submenu)
                        node = node.previousSibling;

                    if (node) {
                        node.dispatchEvent("mouseover");

                        var btnMenu = node.parentNode.menuIsPressed;
                        if (btnMenu) {
                            self[btnMenu.submenu].dispatchEvent("keydown", {
                                keyCode: 40
                            });
                        }
                    }
                }
                else if (this.opener.parentNode.localName == "menu") {
                    //@todo Ahum bad abstraction boundary
                    var op = this.opener;
                    this.hide();
                    apf.setStyleClass(op.$ext, "hover");
                    op.parentNode.$showingSubMenu = null;
                }

                break;
            case 39:
                //RIGHT
                if (this.$selected && this.$selected.submenu) {
                    this.$selected.$submenu(null, true);
                    this.$showingSubMenu.dispatchEvent("keydown", {
                       keyCode: 40
                    });

                    return;
                }

                if (this.opener) {
                    var op = this.opener;
                    while (op && op.parentNode && op.parentNode.localName == "menu")
                        op = op.parentNode.opener;

                    if (op && op.localName == "button") {
                        node = op.nextSibling;
                        while (node && !node.submenu)
                            node = node.nextSibling;

                        if (node) {
                            node.dispatchEvent("mouseover");

                            var btnMenu = node.parentNode.menuIsPressed;
                            if (btnMenu) {
                                (self[btnMenu.submenu] || btnMenu.submenu).dispatchEvent("keydown", {
                                    keyCode: 40
                                });
                            }

                            return;
                        }
                    }
                }

                if (!this.$selected) {
                    arguments.callee.call(this, {
                       keyCode: 40
                    });
                }

                break;
            default:
                return;
        }

        return false;
    }, true);
    

    //Hide menu when it looses focus or when the popup hides itself
    function forceHide(e) {
        if (this.$showingSubMenu || this.pinned
                || apf.isChildOf(e.fromElement, e.toElement)
                || apf.isChildOf(e.toElement, e.fromElement)
                || apf.isChildOf(this, e.toElement) 
                || (e.name !== "popuphide" && !e.toElement)
                || e.toElement && apf.popup.cache[e.toElement.$uniqueId])
            return;

        if (this.$hideTree != -1) {
            this.$hideTree = true;
            this.hide();
        }

        return false;
    }

    this.addEventListener("focus", function(){
        apf.popup.last = this.$uniqueId;
    });

    this.addEventListener("blur", forceHide);
    this.addEventListener("popuphide", forceHide);

    // *** Init *** //

    this.$draw = function(){
        this.$pHtmlNode = document.body;

        //Build Main Skin
        this.$ext = this.$getExternal();
        this.oOverlay = this.$getLayoutNode("main", "overlay", this.$ext);

        apf.popup.setContent(this.$uniqueId, this.$ext, "", null, null);
    };

    this.$loadAml = function(x) {
        this.$int = this.$getLayoutNode("main", "container", this.$ext);
    };

    this.$destroy = function(){
        apf.popup.removeContent(this.$uniqueId);
    };
}).call(apf.menu.prototype = new apf.Presentation());

apf.aml.setElement("menu", apf.menu);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * element specifying a method available within the rpc element.
 *
 * @attribute {String}  name             the name of the method. This name will
 *                                       be available on the rpc object as a
 *                                       javascript method.
 * Example:
 * <code>
 *  <a:rpc id="comm" protocol="xmlrpc">
 *      <a:method name="save" />
 *  </a:rpc>
 *
 *  <a:script>
 *      comm.save(data);
 *  </a:script>
 * </code>
 * @attribute {String}  [callback]       the name of the method that handles
 *                                       the return of the call.
 * @attribute {Boolean} [async]          whether the call is executed in the
 *                                       backround. Default is true. When set
 *                                       to false the application hangs while
 *                                       this call is executed.
 * @attribute {Boolean} [caching]        whether the call is cached. Default
 *                                       is false. When set to true any call
 *                                       with the same data will return immediately
 *                                       with the cached result.
 * @attribute {Boolean} [ignore-offline] whether the method should not be stored
 *                                       for later execution when offline.
 * @attribute {Boolean} [method-name]    the name sent to the server.
 *
 * @attribute {String}  [type]           the type of the returned data
 * Possible values:
 * xml  returns the response as an xml document
 *
 * @allowchild variable
 */
apf.method = function(struct, tagName) {
    this.$init(tagName || "method", apf.NODE_HIDDEN, struct);

    this.async = true;
    this.caching = false;
    this["ignore-offline"] = false;
};

(function(){
    this.$parsePrio = "002";
    
    this.$booleanProperties["async"] = true;
    this.$booleanProperties["caching"] = true;
    this.$booleanProperties["ignore-offline"] = true;
    this.$booleanProperties["cors-with-credentials"] = true;

    this.$supportedProperties.push("name", "receive", "async", "caching",
        "ignore-offline", "method-name", "type", "url", "cors-with-credentials");

    this.$propHandlers["ignore-offline"] = function(value) {
        this.ignoreOffline = value;
    };
    
    this.$propHandlers["method-name"] = function(value) {
        this.methodName = value;
    };
    
    this.$propHandlers["cors-with-credentials"] = function(value) {
        this.withCredentials = value;
    };

    // *** DOM Handlers *** //

    this.addEventListener("DOMNodeInserted", function(e) {
        if (this.parentNode.$addMethod && e.currentTarget == this)
            this.parentNode.$addMethod(this);
    });
    
    this.addEventListener("DOMNodeRemoved", function(e) {
        if (this.parentNode.$removeMethod && e.currentTarget == this)
            this.parentNode.$removeMethod(this);
    });

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (this.parentNode.$addMethod)
            this.parentNode.$addMethod(this);
    });
}).call(apf.method.prototype = new apf.AmlElement());

apf.aml.setElement("method", apf.method);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/*
 * @private
 */
apf.WinServer = {
    count: 9000,
    wins: [],

    setTop: function(win, norecur) {
        if (win.zindex || win.modal) 
            return;
        
        if (win.$opened) {
            if (win.$opened.visible)
                return;
            else 
                delete win.$opened;
        }
        
        var topmost;
        if (!norecur && this.wins.length) {
            var topmost = this.wins[this.wins.length - 1];
            if (topmost == win)
                return;
            
            if (!topmost.modal || !topmost.visible)
                topmost = null;
            else if (topmost && win.modal) {
                win.$opener = topmost;
                topmost.$opened = win;
                topmost = null;
            }
        }
        
        this.count += 2;

        win.setProperty("zindex", this.count);
        this.wins.remove(win);
        this.wins.push(win);

        if (topmost)
            this.setTop(topmost, true);

        return win;
    },

    setNext: function(){
        if (this.wins.length < 2) return;
        var nwin, start = this.wins.shift();
        do {
            if (this.setTop(nwin || start).visible)
                break;
            nwin = this.wins.shift();
        } while (start != nwin);
    },

    setPrevious: function(){
        if (this.wins.length < 2) return;
        this.wins.unshift(this.wins.pop());
        var nwin, start = this.wins.pop();
        do {
            if (this.setTop(nwin || start).visible)
                break;
            nwin = this.wins.pop();
        } while (start != nwin);
    },

    remove: function(win) {
        this.wins.remove(win);
    }
}

/**
 * This element displays a skinnable, draggable window. It can be given
 * a minimum and miximum width and height, as well as keybindings and various buttons. 
 * 
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *     <a:window 
 *       id = "winMail"
 *       buttons = "min|max|close"
 *       title = "Mail Message"
 *       visible = "true"
 *       resizable = "true"
 *       width = "500"
 *       modal = "true"
 *       height = "400"
 *       skin = "bk-window2">
 *       <a:vbox>
 *           <a:hbox margin="5px">
 *               <a:label for="to" caption="To:"/>
 *               <a:textbox id="to" margin="0 0 0 5" width="140" />
 *           </a:hbox>
 *           <a:hbox margin="5">
 *               <a:label for="subject" caption="Subject:" />
 *               <a:textbox id="subject" width="140" />
 *           </a:hbox>
 *           <a:textarea height="200" width="400"/>
 *       </a:vbox>
 *     </a:window>
 *   <!-- endcontent -->
 * </a:application> 
 * ```
 *
 * @class apf.window 
 * @define window
 * @container
 * @allowchild {elements}, {smartbinding}, {anyaml}
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @inherits apf.Presentation
 * @inheritsElsewhere apf.Transaction
 *
 */
 /**
 * @event show          Fires when the window is opened.
 */
 /**
 * @event close         Fires when the window is closed.
 */
 /**
 * 
 * @event editstart     Fires before the user edits the properties of this window. Used mostly for when this window is part of the portal.
 */
 /** 
  * @event editstop      Fires after the user edited the properties of this window. Used mostly for when this window is part of the portal.
 *  @cancelable   Prevents the edit panel from being closed.
 */
/**
 * @event statechange   Fires after the state of this window changed.
 * @param {Object} e The standard event object. The following properties are available:
 * 
 *   - `minimized` ([[Boolean]]):   Specifies whether the window is minimized.
 *   - `maximized` ([[Boolean]]):   Specifies whether the window is maximized.
 *   - `normal` ([[Boolean]]):      Specifies whether the window has it's normal size and position.
 *   - `edit` ([[Boolean]]):        Specifies whether the window is in the edit state.
 *   - `closed` ([[Boolean]]):      Specifies whether the window is closed.
 */
apf.toolwindow = function(struct, tagName) {
    this.$init(tagName || "toolwindow", apf.NODE_VISIBLE, struct);
};

apf.modalwindow = function(struct, tagName) {
    this.$init(tagName || "modalwindow", apf.NODE_VISIBLE, struct);
};

apf.AmlWindow = function(struct, tagName) {
    this.$init(tagName || "window", apf.NODE_VISIBLE, struct);
};

(function(){
    this.implement(
        apf.BaseStateButtons
    );

    this.$isWindowContainer = true;
    this.collapsedHeight = 30;
    this.canHaveChildren = 2;
    this.visible = false;
    this.showdragging = false;
    this.kbclose = false;
    this.$focussable = apf.KEYBOARD;
    this.$editableCaption = ["title"];

    // *** Public Methods *** //

    

    /**
     * Sets the title of the window.
     * @chainable
     * @param {String} caption The text of the title.
     */
    this.setTitle = function(caption) {
        this.setProperty("title", caption, false, true);
        return this;
    };

    /**
     * Sets the icon of the window.
     * @chainable
     * @param {String} icon The location of the image.
     */
    this.setIcon = function(icon) {
        this.setProperty("icon", icon, false, true);
        return this;
    };
    
    //For modal elements
    this.show = function(callback) {
        this.execAction = callback; //@todo Proper error handling??
        this.setProperty("visible", true, false, true);
        return this;
    }
    
    
    

    this.bringToFront = function(){
        apf.WinServer.setTop(this);
        return this;
    };

    // *** Properties and Attributes *** //

    this.$booleanProperties["modal"] = true;
    this.$booleanProperties["center"] = true;
    this.$booleanProperties["transaction"] = true;
    this.$booleanProperties["hideselects"] = true;
    this.$booleanProperties["showdragging"] = true;
    this.$booleanProperties["kbclose"] = true;
    this.$supportedProperties.push("title", "icon", "modal", "minwidth",
        "minheight", "hideselects", "center", "kbclose",
        "maxwidth", "maxheight", "showdragging", "transaction");

    /**
     * @attribute {Boolean} modal Specifies whether the window prevents access to the
     * layout below it.
     */
    this.$propHandlers["modal"] = function(value) {
        if (value) {
            if (this.visible)
                apf.plane.show(this.$ext, false, null, null, {
                    color: "black", 
                    opacity: this.cover && this.cover.getAttribute("opacity") || 0.5,
                    protect: this.$uniqueId,
                    customCover: this.cover || ""
                });
        }
        else { 
            apf.plane.hide(this.$uniqueId);
        }
    };

    /**
     * @attribute {Boolean} center Centers the window relative to its parent's
     * containing rect when shown.
     */
    this.$propHandlers["center"] = function(value) {
        this.$ext.style.position = "absolute"; //@todo no unset
    };

    /**
     * @attribute {String} title Specifies the text of the title.
     */
    this.$propHandlers["title"] = function(value) {
        if (this.oTitle)
            this.oTitle.nodeValue = value;
    };

    /**
     * @attribute {String} icon Specifies the location of the image.
     */
    this.$propHandlers["icon"] = function(value) {
        if (!this.oIcon) return;

        this.oIcon.style.display = value ? "" : "none";
        apf.skins.setIcon(this.oIcon, value, this.iconPath);
    };
    
    this.$afterRender = function(){
        if (this.center && !this.left && !this.top && !this.right && !this.bottom && !this.anchors) {
            
            apf.layout.processQueue();
            
    
            var size = !this.$ext.offsetParent || this.$ext.offsetParent.tagName == "BODY"
                ? [apf.getWindowWidth(), apf.getWindowHeight()]
                : [this.$ext.offsetParent.offsetWidth, this.$ext.offsetParent.offsetHeight, 0, 0];
    
            if (size.length == 2) {
                size.push(document.documentElement.scrollLeft, 
                  document.documentElement.scrollTop);
            }
            
            //@todo it's better to add this to the layout queue
            this.$ext.style.left = (Math.max(0, ((
                size[0] - parseInt((this.width || this.$ext.offsetWidth) || 0))/2)) + size[2]) + "px";
            this.$ext.style.top = (Math.max(0, ((
                size[1] - parseInt(this.$ext.offsetHeight || 0))/3)) + size[3]) + "px";
        }            
        
        
        //@todo make widget a tagname and alias
        if (this.$amlLoaded && (this.model 
          || (!this.dockable || !this.aData) && !this.$isWidget 
          && this.localName != "toolwindow"))
            this.focus(false, {mouse:true});
        
        
        this.dispatchEvent("show");
    }

    var hEls = [], wasVisible;
    this.$propHandlers["visible"] = function(value) {
        if (apf.isTrue(value)){
            if (this.dispatchEvent("beforeshow") === false)
                return (this.visible = false);
            
            if (this.modal) {
                apf.plane.show(this.$ext, false, null, null, {
                    color: "black", 
                    opacity: this.cover && this.cover.getAttribute("opacity") || 0.5,
                    protect: this.$uniqueId,
                    customCover: this.cover || ""
                });
            }

            this.state = this.state.split("|").remove("closed").join("|");

            this.$ext.style.display = ""; //Some form of inheritance detection

            //if (this.modal) 
                //this.$ext.style.position = "fixed";
            
            if (!apf.canHaveHtmlOverSelects && this.hideselects) {
                hEls = [];
                var nodes = document.getElementsByTagName("select");
                for (var i = 0; i < nodes.length; i++) {
                    var oStyle = apf.getStyle(nodes[i], "display");
                    hEls.push([nodes[i], oStyle]);
                    nodes[i].style.display = "none";
                }
            }

            //if (this.modal)
                //this.$ext.style.zIndex = apf.plane.$zindex - 1;
            
            if (apf.isIE) {
                var cls = this.$ext.className;
                this.$ext.className = "rnd" + Math.random();
                this.$ext.className = cls;
            }
            
            if (this.$rendered === false)
                this.addEventListener("afterrender", this.$afterRender);
            else
                this.$afterRender();
        }
        else { 
            if (this.modal)
                apf.plane.hide(this.$uniqueId);

            this.$ext.style.display = "none";

            if (!apf.canHaveHtmlOverSelects && this.hideselects) {
                for (var i = 0; i < hEls.length; i++) {
                    hEls[i][0].style.display = hEls[i][1];
                }
            }

            if (this.hasFocus())
                apf.window.moveNext(true, this, true);//go backward to detect modals

            this.visible = false;
            
            this.dispatchEvent("hide");
        }
        
        
        if (apf.layout && this.$int)
            apf.layout.forceResize(this.$int); //@todo this should be recursive down
        

        wasVisible = value;
    };

    this.$propHandlers["zindex"] = function(value) {
        this.$ext.style.zIndex = value + 1;
    };

    // *** Keyboard *** //

    
    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode;
        var ctrlKey = e.ctrlKey;
        var shiftKey = e.shiftKey;

        /*if (key > 36 && key < 41) {
            if (this.hasFeature && this.hasFeature(apf.__ANCHORING__))
                this.$disableAnchoring();
        }*/

        var retValue = false;
        switch (key) {
            /*case 9:
                break;
            case 13:
                break;
            case 32:
                break;*/
            case 38:
            //UP
                if (shiftKey && this.resizable)
                    this.setProperty("height", Math.max(this.minheight || 0,
                        this.$ext.offsetHeight - (ctrlKey ? 50 : 10)));
                else if (this.draggable)
                    this.setProperty("top",
                        this.$ext.offsetTop - (ctrlKey ? 50 : 10));
                break;
            case 37:
            //LEFT
                if (shiftKey && this.resizable)
                    this.setProperty("width", Math.max(this.minwidth || 0,
                        this.$ext.offsetWidth - (ctrlKey ? 50 : 10)));
                else if (this.draggable)
                    this.setProperty("left",
                        this.$ext.offsetLeft - (ctrlKey ? 50 : 10));
                break;
            case 39:
            //RIGHT
                if (shiftKey && this.resizable)
                    this.setProperty("width", Math.min(this.maxwidth || 10000,
                        this.$ext.offsetWidth + (ctrlKey ? 50 : 10)));
                else if (this.draggable)
                    this.setProperty("left",
                        this.$ext.offsetLeft + (ctrlKey ? 50 : 10));
                break;
            case 40:
            //DOWN
                if (shiftKey && this.resizable)
                    this.setProperty("height", Math.min(this.maxheight || 10000,
                        this.$ext.offsetHeight + (ctrlKey ? 50 : 10)));
                else if (this.draggable)
                    this.setProperty("top",
                        this.$ext.offsetTop + (ctrlKey ? 50 : 10));
                break;
            default:
                retValue = null;
                return;
        }
        
        if (apf.hasSingleRszEvent)
            apf.layout.forceResize(this.$int);
        
        return retValue;
    }, true);
    
    this.addEventListener("keydown", function(e) {
        if (e.keyCode == 27 && this.buttons.indexOf("close") > -1 
          && (!this.dockable || !this.aData) && this.kbclose)
            this.close();
    });
    

    

    // *** Init *** //

    this.$draw = function(){
        this.popout = apf.isTrue(this.getAttribute("popout"));
        if (this.popout)
            this.$pHtmlNode = document.body;

        this.$ext = this.$getExternal(null, null, function(oExt) {
            this.$initButtons(oExt);
        });
        this.oTitle = this.$getLayoutNode("main", "title", this.$ext);
        this.oIcon = this.$getLayoutNode("main", "icon",  this.$ext);
        this.oDrag = this.$getLayoutNode("main", "drag",  this.$ext);
        this.$buttons = this.$getLayoutNode("main", "buttons",  this.$ext);
        this.cover = this.$getLayoutNode("cover");

        if (this.popout)
            this.$ext.style.position = "absolute";

        if (this.oIcon)
            this.oIcon.style.display = "none";

        
        
        var _self = this;
        if (this.oDrag) {
            this.oDrag.host = this;
            this.oDrag.onmousedown = function(e) {
                if (!e) e = event;
    
                //because of some issue I don't understand oExt.onmousedown is not called
                if (!_self.$isWidget && (!_self.aData || !_self.dockable || _self.aData.hidden == 3))
                    apf.WinServer.setTop(_self);
    
                if (_self.$lastState.maximized)
                    return false;
    
                
                if (_self.aData && _self.dockable) {
                    if (_self.$lastState.normal) //@todo
                        _self.startDocking(e);
                    return false;
                }
                
            };
        }

        this.$ext.onmousedown = function(){
            
            var p = apf.document.activeElement;
            if (p && p.$focusParent != _self && p.$focusParent.modal)
                return false;
            
            
            //Set ZIndex on oExt mousedown
            if (!_self.$isWidget && (!_self.aData || !_self.dockable || _self.aData.hidden == 3))
                apf.WinServer.setTop(_self);

            if (!_self.$lastState.normal)
                return false;
        }
        this.$ext.onmousemove = function(){
            if (!_self.$lastState.normal)
                return false;
        }
        
        

        /*var v;
        if (!((v = this.getAttribute("visible")).indexOf("{") > -1 || v.indexOf("[") > -1)) {
            this.$aml.setAttribute("visible", "{" + apf.isTrue(v) + "}");
        }*/
    };

    this.$loadAml = function(x) {
        apf.WinServer.setTop(this);

        this.$int = this.$getLayoutNode("main", "container", this.$ext);

        
            if (this.oTitle) {
                var _self = this;
                (this.oTitle.nodeType != 1
                  ? this.oTitle.parentNode
                  : this.oTitle).ondblclick = function(e) {
                    if (_self.state.indexOf("normal") == -1)
                        _self.restore();
                    else if (_self.buttons.indexOf("max") > -1)
                        _self.maximize();
                    else if (_self.buttons.indexOf("min") > -1)
                        _self.minimize();
                }
            }
    
            if (typeof this.draggable == "undefined") {
                (this.$propHandlers.draggable
                    || apf.GuiElement.propHandlers.draggable).call(this, true);
                this.draggable = true;
            }

            if (typeof this.buttons == "undefined")
                this.buttons = "";
                //this.setProperty("buttons", "min|max|close");
        

        if (this.modal === undefined) { 
            this.$propHandlers.modal.call(this, true);
            this.modal = true;
        }

        //Set default visible hidden
        if (!this.visible) {
            this.$ext.style.display = "none";
        }
        
        else if (this.modal) {
            var _self = this;
            apf.queue.add("focus", function(){
                _self.focus(false, {mouse:true});
            });
        }
        

        if (this.minwidth === undefined)
            this.minwidth = this.$getOption("Main", "min-width");
        if (this.minheight === undefined)
            this.minheight = this.$getOption("Main", "min-height");
        if (this.maxwidth === undefined)
            this.maxwidth = this.$getOption("Main", "max-width");
        if (this.maxheight === undefined)
            this.maxheight = this.$getOption("Main", "max-height");

        if (this.center && this.visible) {
            this.visible = false;
            this.$ext.style.display = "none"; /* @todo temp done for project */
            
            var _self = this;
            $setTimeout(function(){
                _self.setProperty("visible", true);
            });
        }
    };
    
    

    
    this.addEventListener("$skinchange", function(){
        if (this.title)
            this.$propHandlers["title"].call(this, this.title);

        if (this.icon)
            this.$propHandlers["icon"].call(this, this.icon);
    });
    

    this.$destroy = function(skinChange) {
        if (this.oDrag) {
            this.oDrag.host = null;
            this.oDrag.onmousedown = null;
            apf.destroyHtmlNode(this.oDrag);
            this.oDrag = null;
        }

        this.oTitle =  this.oIcon = null;

        if (this.$ext && !skinChange) {
            this.$ext.onmousedown = null;
            this.$ext.onmousemove = null;
        }
    };
}).call(apf.modalwindow.prototype = new apf.Presentation());

apf.AmlWindow.prototype = apf.toolwindow.prototype = apf.modalwindow.prototype;

apf.aml.setElement("toolwindow",  apf.toolwindow);
apf.aml.setElement("modalwindow", apf.modalwindow);
apf.aml.setElement("window",      apf.modalwindow);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element functions as the central access point for XML data. Data can be
 * retrieved from any data source using data instructions. Data can also be
 * submitted using data instructions in a similar way to HTML form posts. 
 *
 * The modal can be reset to its original state. It has support for offline use and
 * synchronization between multiple clients.
 * 
 * #### Example: Loading A Model 
 * 
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <!-- startcontent -->
 *  <a:model id="mdl1">
 *      <data>
 *          <row content="This is a row 1" />
 *          <row content="This is a row 2" />
 *          <row content="This is a row 3" />
 *      </data>
 *  </a:model>
 *  <a:hbox height="20">
 *      <a:label>List component:</a:label>
 *  </a:hbox>
 *  <a:list 
 *    model = "mdl1" 
 *    each = "[row]"
 *    caption = "[@content]" 
 *    icon = "[@icon]" 
 *    width = "400">
 *  </a:list>
 *  <a:hbox height="30" margin="7 0 3 0">
 *      <a:label>Datagrid component:</a:label>
 *  </a:hbox>
 *  <a:datagrid width="400" height="100" model="mdl1">
 *      <a:each match="[row]">
 *          <a:column 
 *            caption = "Name" 
 *            value = "[@content]" 
 *            width = "100%" />
 *      </a:each>
 *  </a:datagrid>
 *  <!-- endcontent -->
 * </a:application>
 * 
 * #### Example
 *
 * A small form where the bound data is submitted to a server using a model:
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="mdlForm" submission="save_form.asp">
 *       <data name="Lukasz" address="Poland"></data>
 *   </a:model>
 *  
 *   <a:frame model="mdlForm">
 *       <a:label>Name</a:label>
 *       <a:textbox value="[@name]" />
 *       <a:label>Address</a:label>
 *       <a:textarea 
 *         value = "[@address]" 
 *         width = "100" 
 *         height = "50" />
 *       <a:button 
 *         default = "true" 
 *         action = "submit">Submit</a:button>
 *   </a:frame>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.model
 * @inherits apf.AmlElement
 * @define model
 * @logic
 * @allowchild [cdata], instance, load, submission
 *
 * 
 * 
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 * 
 */
/**
 * @attribute  {String}  src          Sets or gets the data instruction on how to load data from the data source into this model.
 */
/**
 * @attribute  {String}  submission   Sets or gets the data instruction on how to record the data from the data source from this model.
 */
/**
 * @attribute  {String}  session      Sets or gets the data instruction on how to store the session data from this model.
 */
/**
 * @attribute  {Boolean} autoinit     Sets or gets whether to initialize the model immediately. If set to false you are expected to call init() when needed. This is useful when the system has to log in first.
 */
/**
 * @attribute  {Boolean} enablereset  Sets or gets whether to save the original state of the data. This enables the use of the reset() call.
 */
/**
 * @attribute  {String}  remote       Sets or gets the id of the remote element to use for data synchronization between multiple clients.
 */
/**
 * @event beforeretrieve    Fires before a request is made to retrieve data.
 * @cancelable Prevents the data from being retrieved.
 */
/**
 * @event afterretrieve     Fires when the request to retrieve data returns both on success and failure.
 */
/**
 * @event receive           Fires when data is successfully retrieved
 * @param {Object} e The standard event object. It contains the following property:
 *  - `data` (([String])):  the retrieved data
 */
/**
 * @event beforeload        Fires before data is loaded into the model.
 * @cancelable
 */
/**
 * @event afterload         Fires after data is loaded into the model.
 */
/**
 * @event beforesubmit      Fires before data is submitted.
 * @cancelable Prevents the submit.
 * @param {Object} e The standard event object. It contains the following property:
 *   - `instruction` ([[String]]):  the data instruction used to store the data.
 */
/**
 * @event submiterror       Fires when submitting data has failed.
 */
/**
 * @event submitsuccess     Fires when submitting data was successfull.
 */
/**
 * @event aftersubmit       Fires after submitting data.
 */
/**
 * @event error             Fires when a communication error has occured while making a request for this element.
 * @cancelable Prevents the error from being thrown.
 * @bubbles
 * @param {Object} e The standard event object. It contains the following properties:
 *   - `error` ([[Error]]): the error object that is thrown when the event callback doesn't return false.
 *   - `state` ([[Number]]): the state of the call. Possible values include:
 *     - `apf.SUCCESS`:  the request was successfull
 *     - `apf.TIMEOUT`:  the request has timed out.
 *     - `apf.ERROR`:  an error has occurred while making the request.
 *     - `apf.OFFLINE`:  the request was made while the application was offline.
 *   - `userdata` (`Mixed`): data that the caller wanted to be available in the callback of the HTTP request.
 *   - `http` ([[XMLHttpRequest]]): The object that executed the actual HTTP request.
 *   - `url` ([[String]]): the URL that was requested.
 *   - `tpModule` ([[apf.http]]): the teleport module that is making the request.
 *   - `id` ([[Number]]): the id of the request.
 *   - `message` ([[String]]): the error message.
 *
 */
apf.model = function(struct, tagName) {
    this.$init(tagName || "model", apf.NODE_HIDDEN, struct);
    
    this.$amlNodes = {};
    this.$propBinds = {};
    
    this.$listeners = {};
    this.$proplisteners = {};
};

(function(){
    this.$parsePrio = "020";
    this.$isModel = true;
    this.$createModel = true;
    
    this.canHaveChildren = false;
    this.enablereset = false;

    this.$state = 0;//1 = loading

    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        submission: 1,
        src: 1,
        session: 1
    }, this.$attrExcludePropBind);

    this.$booleanProperties["whitespace"] = true;
    this.$booleanProperties["create-model"] = true;
    this.$booleanProperties["autoinit"] = true;
    this.$booleanProperties.enablereset = true;
    this.$supportedProperties = ["submission", "src", "session", "autoinit", 
        "enablereset", "remote", "whitespace", "create-model"];
    
    this.$propHandlers["src"] = 
    this.$propHandlers["get"] = function(value, prop) {
        if (this.$amlLoaded)
            this.$loadFrom(value);
    };
    
    this.$propHandlers["create-model"] = function(value, prop) {
        this.$createModel = value;
    };

    
    //Connect to a remote databinding
    this.$propHandlers["remote"] = function(value, prop) {
        if (value) {
            if (this.src && this.src.indexOf("rdb://") === 0) {
                var _self = this;
                apf.queue.add("rdb_load_" + this.$uniqueId, function(){
                    _self.unshare();
                    _self.share();
                });
            }
        }
        else
            this.unshare();
    };

    this.share = function(xpath) {
        this.rdb = typeof this.remote == "string"
            ? 
            
            apf.nameserver.get("remote", this.remote)
            
            : this.remote;

        

        this.rdb.createSession(this.src, this, xpath);
    };

    this.unshare = function(xpath) {
        if (!this.rdb) return;
        this.rdb.endSession(this.src);
        this.rdb = null;
    };
    

    /**
     * Registers an AML element to this model in order for the AML element to
     * receive data loaded in this model.
     *
     * @param  {apf.AmlElement}  amlNode  The AML element to be registered.
     * @param {String}       [xpath]  The XPath query which is executed on the
     *                                data of the model to select the node to be
     *                                loaded in the `amlNode`.
     * @return  {apf.model}  This model
     * @private
     */
    this.register = function(amlNode, xpath) {
        if (!amlNode || !amlNode.load) //hasFeature(apf.__DATABINDING__))
            return this;

        var isReloading = amlNode.$model == this;

        //Remove previous model
        if (amlNode.$model && !isReloading)
            amlNode.$model.unregister(amlNode);

        //Register the AML node
        var item = this.$amlNodes[amlNode.$uniqueId] = {
            amlNode: amlNode, 
            xpath: xpath
        };
        amlNode.$model = this;

        if (typeof amlNode.noloading == "undefined"
          && amlNode.$setInheritedAttribute 
          && !amlNode.$setInheritedAttribute("noloading"))
            amlNode.noloading = false;

        //amlNode.$model = this;
        if (this.$state == 1) {
            if (amlNode.clear && !amlNode.noloading)
                amlNode.clear("loading");//@todo apf3.0
        }
        else if (this.data) {
            this.$loadInAmlNode(item);
            //this.$loadInAmlProp(amlNode);
        }
        else { //@experimental
            if (amlNode.hasFeature(apf.__CACHE__)) // amlNode.clear
                amlNode.clear("empty");
        }

        var p, node, list = amlNode.$propsUsingMainModel, id = amlNode.$uniqueId;
        for (var prop in list) {
            this.$unbindXmlProperty(amlNode, prop);
            p = this.$bindXmlProperty(amlNode, prop, 
                    list[prop].xpath, list[prop].optimize);
            
            if (this.data) {
                //if (node = p.root || p.listen ? this.data.selectSingleNode(p.root || p.listen) : this.data) {
                if (node = p.listen ? this.data.selectSingleNode(p.listen) : this.data) {
                    amlNode.$execProperty(prop, node);
                }
                else
                    this.$waitForXml(amlNode, prop);
            }
        }
        
        return this;
    };

    this.$register = function(amlNode, xpath) {
        //@todo apf3.0 update this.$propBinds;
        
        this.$amlNodes[amlNode.$uniqueId].xpath = xpath;
    };

    /*
     * Removes an AML element from the group of registered AML elements.
     * The AML element will not receive any updates from this model, however
     * the data loaded in the AML element is not unloaded.
     *
     * @param  {apf.AmlElement}  amlNode  The AML element to be unregistered.
     * @private
     */
    this.unregister = function(amlNode) {
        delete this.$amlNodes[amlNode.$uniqueId];
        
        var list = amlNode.$propsUsingMainModel;
        for (var prop in list)
            this.$unbindXmlProperty(amlNode, prop);
            
        amlNode.dispatchEvent("unloadmodel");
    };

    /*
     * @private
     */
    this.getXpathByAmlNode = function(amlNode) {
        var n = this.$amlNodes[amlNode.$uniqueId];
        if (!n)
            return false;

        return n.xpath;
    };
    
    /*
     * @private
     */
    this.$loadInAmlNode = function(item) {
        var xmlNode;
        var xpath = item.xpath;
        var amlNode = item.amlNode;
        
        if (this.data && xpath) {
            xmlNode = this.data.selectSingleNode(xpath);
        }
        else
            xmlNode = this.data || null;
        
        if (xmlNode) {
            delete this.$listeners[amlNode.$uniqueId];
            if (amlNode.xmlRoot != xmlNode)
                amlNode.load(xmlNode);
        }
        else 
            this.$waitForXml(amlNode);
    };
    
    this.$loadInAmlProp = function(id, xmlNode) {
        var prop, node, p = this.$propBinds[id], amlNode = apf.all[id];
        if (!amlNode) {
             delete this.$propBinds[id];
             return;
        }
        

        for (prop in p) {
            if (xmlNode && (node = p[prop].listen 
              ? xmlNode.selectSingleNode(p[prop].listen) 
              : xmlNode)) {
                apf.xmldb.addNodeListener(xmlNode, amlNode, 
                  "p|" + id + "|" + prop + "|" + this.$uniqueId);
                
                delete this.$proplisteners[id + prop];
                amlNode.$execProperty(prop, node);
            }
            else
                this.$waitForXml(amlNode, prop);
        }            
    };
    
    /*
        We don't want to connect to the root, that would create a rush
        of unnecessary update messages, so we'll find the element that's
        closest to the node that is going to feed us the value
        
        mdlBlah: :bli/persons
        mdlBlah: :bli/persons/person
        
        $attrBindings
        //split / join, pop, indexOf
        
        <a:textbox value="[persons/person/@blah]" width="[persons/blah/@width]" height="[@height]" model="[mdlBlah::bli]"/>
    */
    this.$bindXmlProperty = function(amlNode, prop, xpath, optimize, listenRoot) {
        var q ,p, id = amlNode.$uniqueId;
        if (!this.$propBinds[id]) 
            this.$propBinds[id] = {};

        /*
            Store
            0 - Original xpath
            1 - Store point of change listener
            2 - Xpath to determine data node passed into load
        */
        p = this.$propBinds[id][prop] = {
            bind: xpath
        };

        //@todo apf3.0
        //Optimize root point, doesnt work right now because it doesnt change the original rule
        if (optimize && false) {
            //Find xpath for bind on this model of the amlNode
            if ((q = this.$amlNodes[id]) && q.xpath)
                xpath = (p.root = q.xpath) + "/" + xpath;
            
            var l = xpath.split("/"), z = l.pop();
            if (z.indexOf("@") == 0 
              || z.indexOf("text()") > -1 
              || z.indexOf("node()") > -1) {
                p.listen = l.join("/");
            }
            else p.listen = xpath;
        }
        else {
            if ((q = this.$amlNodes[id]) && q.xpath)
                p.listen = q.xpath;
        }
        
        if (listenRoot)
            p.listen = ".";

        if (this.data) {
            var xmlNode = 
              
              (p.listen ? this.data.selectSingleNode(p.listen) : this.data);

            if (xmlNode) {
                apf.xmldb.addNodeListener(xmlNode, amlNode, 
                  "p|" + amlNode.$uniqueId + "|" + prop + "|" + this.$uniqueId);
                
                return p;
            }
        }
        
        this.$waitForXml(amlNode, prop);
        
        return p;
    };
    
    this.$unbindXmlProperty = function(amlNode, prop) {
        var id = amlNode.$uniqueId;

        //@todo apf3.0
        var p = this.$propBinds[id] && this.$propBinds[id][prop];
        if (!p) return;
        
        if (this.data) {
            var xmlNode = p.listen ? this.data.selectSingleNode(p.listen) : this.data;
            if (xmlNode) {
                apf.xmldb.removeNodeListener(xmlNode, amlNode, 
                  "p|" + id + "|" + prop + "|" + this.$uniqueId);
            }
        }
        
        delete this.$proplisteners[id + prop];
        delete this.$propBinds[id][prop];
        return p;
    };

    /**
     * Gets a copy of current state of the XML of this model.
     *
     * @return  {XMLNode}  The context of this model, or `false` if there's no data
     * 
     */
    this.getXml = function(){
        return this.data
            ? apf.xmldb.cleanNode(this.data.cloneNode(true))
            : false;
    };

    /**
     * Sets a value of an XMLNode based on an XPath statement executed on the data of this model.
     *
     * @param {String}   xpath  The xpath used to select a XMLNode.
     * @param {String}   value  The value to set.
     * @return  {XMLNode}  The changed XMLNode
     */
    this.setQueryValue = function(xpath, value) {
        if (!this.data)
            return false;
        
        var node = apf.createNodeFromXpath(this.data, xpath);
        if (!node)
            return null;

        apf.setNodeValue(node, value, true);
        //apf.xmldb.setTextNode(node, value);
        return node;
    };
    
    /**
     * Sets a value of a set of XML nodes based on an XPath statement executed on the data of this model.
     *
     * @param {String}   xpath  The xpath used to select a the nodeset.
     * @param {String}   value  The value to set.
     * @return  {NodeList}  The changed XMLNodeSet
     */
    this.setQueryValues = function(xpath, value) {
        if (!this.data)
            return [];
        
        var nodes = this.data.selectNodes(xpath);
        for (var i = 0, l = nodes.length; i < l; i++)
            apf.setNodeValue(node, value, true);

        return nodes;
    };

    /**
     * Gets the value of an XMLNode based on a XPath statement executed on the data of this model.
     *
     * @param {String}   xpath  The XPath used to select a XMLNode.
     * @return  {String}  The value of the XMLNode
     */
    this.queryValue = function(xpath) {
        if (!this.data)
            return false;
        
        return apf.queryValue(this.data, xpath);
    };
    
    /**
     * Gets the values of an XMLNode based on a XPath statement executed on the data of this model.
     *
     * @param {String}   xpath  The xpath used to select a XMLNode.
     * @return  {[String]}  The values of the XMLNode
     */ 
    this.queryValues = function(xpath) {
        if (!this.data)
            return [];
        
        return apf.queryValue(this.data, xpath);
    };
    
    /**
     * Executes an XPath statement on the data of this model
     *
     * @param {String}    xpath    The XPath used to select the XMLNode(s).
     * @return  {Mixed} The result of the selection, either an [[XMLNode]] or a [[NodeList]]
     */
    this.queryNode = function(xpath) {
        if (!this.data)
            return null;
        
        return this.data.selectSingleNode(xpath)
    };

    /**
     * Executes XPath statements on the data of this model
     *
     * @param {String}    xpath    The XPath used to select the XMLNode(s).
     * @return  {Mixed} The result of the selection, either an [[XMLNode]] or a [[NodeList]]
     */
    this.queryNodes = function(xpath) {
        if (!this.data)
            return [];
        
        return this.data.selectNodes(xpath);
    };

    /**
     * Appends a copy of the `xmlNode` or model to this model as a child
     * of its root node
     * @param {XMLNode} xmlNode The XML node to append
     * @param {String} [xpath] The path to a node to append to
     * @returns {XMLNode} The appended node
     */
    this.appendXml = function(xmlNode, xpath) {
        var insertNode = xpath
          ? apf.createNodeFromXpath(this.data, xpath)
          : this.data;
        if (!insertNode)
            return null;
        
        if (typeof xmlNode == "string")
            xmlNode = apf.getXml(xmlNode);
        else if (xmlNode.nodeFunc)
            xmlNode = xmlNode.getXml();
        
        if (!xmlNode) return;

        xmlNode = apf.xmldb.appendChild(insertNode, xmlNode);
        
        this.dispatchEvent("update", {xmlNode: xmlNode});
        return xmlNode;
    };

    /**
     * Removes an XML node from this model.
     */
    this.removeXml = function(xmlNode) {
        if (!this.data) return;

        var xmlNodes;
        if (typeof xmlNode === "string") {
            xmlNodes = this.data.selectNodes(xmlNode);
        }
        else if (!xmlNode.length) {
            xmlNodes = [xmlNode];
        }
        
        if (xmlNodes.length) {
            apf.xmldb.removeNodeList(xmlNodes);
        }
    };

    /**
     * Clears the loaded data from this model.
     */
    this.clear = function(){
        this.load(null);
        doc = null; //Fix for safari refcount issue;
    };

    /**
     * Resets data in this model to the last saved point.
     *
     */
    this.reset = function(){
        var doc = this.data.ownerDocument;
        //doc.removeChild(this.data);
        //var node = doc.appendChild(apf.isWebkit ? doc.importNode(this.$copy, true) : this.$copy);
        this.data.parentNode.replaceChild(this.$copy, this.data);
        this.load(this.$copy);
    };

    /**
     * Sets a new saved point based on the current state of the data in this
     * model. The `reset()` method returns the model to this point.
     */
    this.savePoint = function(){
        this.$copy = apf.xmldb.getCleanCopy(this.data);
    };

    /**
     * @private
     */
    this.reloadAmlNode = function(uniqueId) {
        if (!this.data)
            return;

        var item = this.$amlNodes[uniqueId];
        var xmlNode = item.xpath 
            ? this.data.selectSingleNode(item.xpath) 
            : this.data;
        item.amlNode.load(xmlNode);
    };

    //@todo refactor this to use .blah instead of getAttribute
    //@todo move this to propHandlers
    /*
     * @private
     */
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var x = this.$aml;
        if (this.parentNode && this.parentNode.hasFeature(apf.__DATABINDING__)) {
            if (!this.name)
                this.setProperty("id", "model" + this.parentNode.$uniqueId);
            //this.parentNode.$aml.setAttribute("model", this.name); //@todo don't think this is necesary anymore...
            this.register(this.parentNode);
        }

        //Load literal model
        if (!this.src) {
            var strXml, xmlNode = x;
            if (xmlNode && xmlNode.childNodes.length) {
                if (apf.getNode(xmlNode, [0])) {
                    if ((strXml = xmlNode.xml || xmlNode.serialize()).match(/^[\s\S]*?>([\s\S]*)<[\s\S]*?$/)) {
                        strXml = RegExp.$1; //@todo apf3.0 test this with json
                        if (!apf.supportNamespaces)
                            strXml = strXml.replace(/xmlns=\"[^"]*\"/g, "");
                    }
                    
                    if (this.whitespace === false)
                        strXml = strXml.replace(/>[\s\n\r]*</g, "><");
                    
                    return this.load(apf.getXmlDom(strXml).documentElement);
                }
                // we also support JSON data loading in a model CDATA section
                else if (apf.isJson(xmlNode.childNodes[0].nodeValue)) {
                    return this.load(apf.getXmlDom(xmlNode.childNodes[0].nodeValue).documentElement);
                }
            }
            
            //Default data for XForms models without an instance but with a submission node
            if (this.submission)
                this.load("<data />");
        }

        //Load data into model if allowed
        if (!apf.isFalse(this.autoinit))
            this.init();

        //@todo actions apf3.0

        return this;
    });

    //callback here is private
    /**
     * Loads the initial data into this model.
     * @see apf.model.init
     */
    this.init = function(callback) {
        if (this.session) {
            this.$loadFrom(this.session, {isSession: true});
        }
        else {
            

            if (this.src)
                this.$loadFrom(this.src, {callback: callback});
        }
    };

    /* *********** LOADING ****************/

    /*
     * Loads data into this model using a data instruction.
     * @param {String}     instruction  The data instrution how to retrieve the data.
     * @param {Object}     options
     *   Properties:
     *   {XMLElement} xmlNode   the {@link term.datanode data node} that provides context to the data instruction.
     *   {Function}   callback  the code executed when the data request returns.
     *   {Mixed}      []        Custom properties available in the data instruction.
     */
    this.$loadFrom = function(instruction, options) {
        
        if (instruction.indexOf("rdb://") === 0) {
            this.src = instruction; //@todo
            return this.$propHandlers["remote"].call(this, this.remote);
        }
        
        var data = instruction.split(":");

        if (!options)
            options = {};

        if (!options.isSession) {
            this.src = instruction;
            this.$srcOptions = [instruction, options];
        }

        //Loading data in non-literal model
        this.dispatchEvent("beforeretrieve");
        
        //Set all components on loading...
        var uniqueId, item;
        for (uniqueId in this.$amlNodes) {
            if (!(item = this.$amlNodes[uniqueId]) || !item.amlNode)
                continue;

            //@todo apf3.0
            if (!item.amlNode.noloading)
                item.amlNode.clear("loading");
        }

        this.$state = 1;
        if (!this.$callCount)
            this.$callCount = 1;
        else
            this.$callCount++;

        var _self = this,
            callCount = this.$callCount,
            callback = options.callback;
        options.callback = function(data, state, extra) {
            if (callCount != _self.$callCount)
                return; //another call has invalidated this one
            
            _self.dispatchEvent("afterretrieve");

            

            if (state != apf.SUCCESS) {
                var oError;

                oError = new Error(apf.formatErrorString(1032,
                    _self, "Loading xml data", "Could not load data\n"
                  + "Instruction: " + instruction + "\n"
                  + "Url: " + extra.url + "\n"
                  + "Info: " + extra.message + "\n\n" + data));

                if (callback && callback.apply(this, arguments) === true)
                    return true;

                if (extra.tpModule && extra.tpModule.retryTimeout(extra, state, _self, oError) === true)
                    return true;

                _self.$state = 0;

                throw oError;
            }

            if (options && options.isSession && !data) {
                if (this.src)
                    return _self.$loadFrom(this.src);
            }
            else {
                if (options && options.cancel)
                    return;

                _self.load(data);
                _self.dispatchEvent("receive", {
                    data: data
                });

                if (callback)
                    callback.apply(this, arguments);
            }
        };

        return apf.getData(instruction, options);
    };
    
    /**
     * Loads the data from the datasource specified for [[apf.model.init]].
     */
    this.reload = function(){
        if (!this.data)
            return;
        
        if (this.$srcOptions)
            this.$loadFrom.apply(this, this.$srcOptions);
        else if (this.src)
            this.$loadFrom(this.src);
    };

    /**
     * Loads data into this model.
     *
     * @param {Mixed}  [xmlNode]  The data to load in this model. A string specifies the data instruction how to retrieve the data, which can be an XML string. `null` will clear the data from this model.
     * @param {Object} [options] Additional options to pass. This can contain the following properties:
     *   
     *   - `xmlNode` ([[XMLElement]]):   the {@link term.datanode data node} that provides context to the data instruction.
     *   - `callback` ([[Function]]): the code executed when the data request returns.
     *   - `[]` (`Mixed`): custom properties available in the data instruction.
     *   - `[nocopy]` ([[Boolean]]): specifies whether the data loaded will not overwrite the reset point.
     */
    this.load = function(xmlNode, options) {
        if (typeof xmlNode == "string") {
            if (xmlNode.charAt(0) == "<") { //xml
                if (xmlNode.substr(0, 5).toUpperCase() == "<!DOC")
                    xmlNode = xmlNode.substr(xmlNode.indexOf(">")+1);
                if (!apf.supportNamespaces)
                    xmlNode = xmlNode.replace(/xmlns\=\"[^"]*\"/g, "");
                xmlNode = apf.getXmlDom(xmlNode, null, true).documentElement; //@todo apf3.0 whitespace issue
            }
            
            else
                return this.$loadFrom(xmlNode, options);
        }

        if (this.ownerDocument && this.ownerDocument.$domParser.$isPaused(this)) {
            //if (!this.$queueLoading) {
                var _self = this;
                this.data = xmlNode; //@todo expirement //this.$copy = 
                apf.xmldb.getXmlDocId(xmlNode, this); //@todo experiment
                
                this.$queueLoading = true;
                apf.queue.add("modelload" + this.$uniqueId, function(){
                    if (_self.ownerDocument && _self.ownerDocument.$domParser.$isPaused(_self))
                        apf.queue.add("modelload" + _self.$uniqueId, arguments.callee);
                    else {
                        _self.load(xmlNode, options);
                        _self.$queueLoading = false;
                    }
                });
            //}
            return;
        }
        else if (this.$queueLoading)
            apf.queue.remove("modelload" + this.$uniqueId);
        
        this.$state = 0;

        if (this.dispatchEvent("beforeload", {xmlNode: xmlNode}) === false)
            return false;

        var doc = xmlNode ? xmlNode.ownerDocument : null; //Fix for safari refcount issue;

        //if (apf.isIE && this.$aml && this.getAttribute("ns"))
            //doc.setProperty("SelectionNamespaces", this.getAttribute("ns"));
        
        if (xmlNode) {
            if (!apf.supportNamespaces) {
                /* && (xmlNode.prefix || xmlNode.scopeName)) {
                doc.setProperty("SelectionNamespaces", "xmlns:"
                     + (xmlNode.prefix || xmlNode.scopeName) + "='"
                     + xmlNode.namespaceURI + "'");*/
                var xmlns = [], attr = xmlNode.attributes;
                for (var i = 0, l = attr.length; i < l; i++) {
                    if (attr[i].nodeName.substr(0, 5) == "xmlns") {
                        xmlns.push(attr[i].xml);
                    }
                }
                if (xmlns.length)
                    doc.setProperty("SelectionNamespaces", xmlns.join(" "));
            }
            
            apf.xmldb.addNodeListener(xmlNode, this); //@todo this one can be added for this.$listeners and when there are none removed
            apf.xmldb.nodeConnect(
                apf.xmldb.getXmlDocId(xmlNode, this), xmlNode, null, this);

            if ((!options || !options.nocopy) && this.enablereset)
                this.$copy = apf.xmldb.getCleanCopy(xmlNode);
        }

        this.data = xmlNode;
        
        this.dispatchEvent("afterload", {xmlNode: xmlNode});
        this.dispatchEvent("update", {xmlNode: xmlNode});
        
        for (var id in this.$amlNodes)
            this.$loadInAmlNode(this.$amlNodes[id]);

        for (id in this.$propBinds)
            this.$loadInAmlProp(id, xmlNode);

        return this;
    };
    
    //Listening nodes should be removed in unregister
    this.$waitForXml = function(amlNode, prop) {
        if (prop)
            this.$proplisteners[amlNode.$uniqueId + prop] = {
                id: amlNode.$uniqueId, 
                amlNode: amlNode, 
                prop: prop
            };
        else 
            this.$listeners[amlNode.$uniqueId] = amlNode;
        
        //When data is not available at model load but element had already data
        //loaded, it is cleared here.
        if (amlNode.xmlRoot)
            amlNode.clear();
    };
    
    this.$xmlUpdate = function(action, xmlNode, listenNode, UndoObj) {
        //@todo optimize by only doing this for add, sync etc actions
        
        if (action == "replacenode" && xmlNode == this.data.ownerDocument.documentElement) {
            var _self = this;
            $setTimeout(function(){
                _self.load(xmlNode);
            });
            return;
        }
        
        
        if (this.rdb && !this.$at && UndoObj)
            this.$at = UndoObj.at;
        

        
        
        var p, b;
        for (var id in this.$listeners) {
            if (xmlNode = this.data.selectSingleNode(this.$amlNodes[id].xpath || ".")) {
                this.$listeners[id].load(xmlNode);
                delete this.$listeners[id];
            }
        }

        for (id in this.$proplisteners) {
            p = this.$proplisteners[id];
            b = this.$propBinds[p.id][p.prop];
            if (xmlNode = b.listen ? this.data.selectSingleNode(b.listen) : this.data) {
                delete this.$proplisteners[id];
                
                apf.xmldb.addNodeListener(xmlNode, p.amlNode, 
                  "p|" + p.id + "|" + p.prop + "|" + this.$uniqueId);
                
                p.amlNode.$execProperty(p.prop, b.root 
                  ? this.data.selectSingleNode(b.root) 
                  : this.data);
            }
        }
        
        this.dispatchEvent("update", {xmlNode: xmlNode, action: action, undoObj: UndoObj});
    };

    // *** INSERT *** //

    /*
     * Inserts data into the data of this model using a data instruction.
     * @param {String}     instruction  The data instrution indicating how to retrieve the data.
     * @param {Object}     options      Additional options to pass. This can contain the following properties:
     *   
     *   - `insertPoint` ([[XMLElement]]): the parent element for the inserted data.
     *   - `clearContents` ([[Boolean]]): whether the contents of the insertPoint should be cleared before inserting the new children.
     *   - `copyAttributes` ([[Boolean]]): whether the attributes of the merged element are copied.
     *   - `callback` ([[Function]]): the code executed when the data request returns.
     *   - `[]` (`Mixed`): custom properties available in the data instruction.
     */
    this.$insertFrom = function(instruction, options) {
        if (!instruction) return false;

        this.dispatchEvent("beforeretrieve");

        

        var callback = options.callback, _self = this;
        options.callback = function(data, state, extra) {
            _self.dispatchEvent("afterretrieve");

            if (!extra)
                extra = {};

            if (state != apf.SUCCESS) {
                var oError;

                

                if (extra.tpModule.retryTimeout(extra, state, 
                  options.amlNode || _self, oError) === true)
                    return true;

                if (callback 
                  && callback.call(this, extra.data, state, extra) === false)
                    return;

                throw oError;
            }

            //Checking for xpath
            if (typeof options.insertPoint == "string")
                options.insertPoint = _self.data.selectSingleNode(options.insertPoint);

            if (typeof options.clearContents == "undefined" && extra.userdata) 
                options.clearContents = apf.isTrue(extra.userdata[1]); //@todo is this still used?

            if (options.whitespace == undefined)
                options.whitespace = _self.whitespace;

            //Call insert function
            (options.amlNode || _self).insert(data, options);

            if (callback)
                callback.call(this, extra.data, state, extra);
        };

        apf.getData(instruction, options);
    };

    /**
     * Inserts data in this model as a child of the currently loaded data.
     *
     * @param {XMLElement}  XMLRoot         The {@link term.datanode data node} to insert into this model.
     * @param {Object}     options Additional options to pass. This can contain the following properties:
     *   
     *   - `insertPoint` ([[XMLElement]]): the parent element for the inserted data.
     *   - `clearContents` ([[Boolean]]): specifies whether the contents of the `insertPoint` should be cleared before inserting the new children.
     *   - `copyAttributes` ([[Boolean]]): specifies whether the attributes of the merged element are copied.
     *   - `callback` ([[Function]]): the code executed when the data request returns.
     *   - `[]` (`Mixed`): Custom properties available in the data instruction.
     */
    this.insert = function(xmlNode, options) {
        if (typeof xmlNode == "string") {
            if (xmlNode.charAt(0) == "<") {
                if (xmlNode.substr(0, 5).toUpperCase() == "<!DOC")
                    xmlNode = xmlNode.substr(xmlNode.indexOf(">")+1);
                if (!apf.supportNamespaces)
                    xmlNode = xmlNode.replace(/xmlns\=\"[^"]*\"/g, "");
                
                if (this.whitespace === false)
                    xmlNode = xmlNode.replace(/>[\s\n\r]*</g, "><");
                
                xmlNode = apf.getXmlDom(xmlNode).documentElement;
            }
            
            else
                return this.$insertFrom(xmlNode, options);
        }

        if (!options.insertPoint)
            options.insertPoint = this.data;

        

        //if(this.dispatchEvent("beforeinsert", parentXMLNode) === false) return false;

        //Integrate XMLTree with parentNode
        if (typeof options.copyAttributes == "undefined")
            options.copyAttributes = true;
        
        var newNode = apf.mergeXml(xmlNode, options.insertPoint, options);

        //Call __XMLUpdate on all this.$listeners
        apf.xmldb.applyChanges("insert", options.insertPoint);//parentXMLNode);

        //this.dispatchEvent("afterinsert");

        return xmlNode;
    };

    /* *********** SUBMISSION ****************/

    /**
     * Serialize the full XML DOM to a format specified by 'type'.
     * 
     * @param {String} type  Indicates how to serialize the data. Possible values include:
     *   - `xml`, for `application/xml`
     *   - `form`, for `application/x-www-form-urlencoded`
     *   - `json`, for `application/json`
     * @returns {String} The serialized XML.
     */
    this.convertXml = function(type) {
        if (!type)
            return this.data.xml;

        return apf.convertXml(this.data, type);
    };

    /**
     * Submit the data of the model to a data source.
     * @param {String} instruction  The instruction for sending the data, or the URL to send the data to.
     * @param {String} [type]         Indicates how to serialize the data. Possible values include:
     *   - `xml`, for `application/xml`
     *   - `form`, for `application/x-www-form-urlencoded` (the default)
     *   - `json`, for `application/json`
     * @param {XMLElement} [xmlNode]  The data node to send to the server.
     * @param {Object} [options] A list of additional options to pass; these are the same as [[apf.saveData]]. Available properties include:
     *                           {:saveDataOptions}
     */
    this.submit = function(instruction, type, xmlNode, options){ //@todo rewrite this for apf3.0
        if (!instruction)
            instruction = this.submission;
        
        if (!xmlNode)
            xmlNode = this.data;

        

        if (!type)
            type = "form";

        if (this.dispatchEvent("beforesubmit", {
            instruction: instruction
        }) === false)
            return false;

        var model = this;
        function cbFunc(data, state, extra) {
            if ((state == apf.TIMEOUT 
              || (model.retryOnError && state == apf.ERROR))
              && extra.retries < apf.maxHttpRetries) {
                return extra.tpModule.retry(extra.id);
            }
            else {
                if (state != apf.SUCCESS) {
                    model.dispatchEvent("submiterror", extra);
                }
                else {
                    model.dispatchEvent("submitsuccess", apf.extend({
                        data: data
                    }, extra));
                }
            }
        }
        
        var data;
        if (type.indexOf("xml") > -1)
            data = apf.getXmlString(xmlNode);
        else if (type.indexOf("form") > -1)
            data = apf.convertXml(apf.xmldb.getCleanCopy(xmlNode), "cgiobjects");
        else if (type.indexOf("json") > -1)
            data = apf.convertXml(xmlNode, "json");

        apf.saveData(instruction, apf.extend({
            xmlNode: xmlNode,
            data: data,
            callback: cbFunc
        }, options));

        this.dispatchEvent("aftersubmit");
    };

    this.$destroy = function(){
        if (this.session && this.data)
            apf.saveData(this.session, {xmlNode: this.getXml()});
    };
}).call(apf.model.prototype = new apf.AmlElement());

apf.aml.setElement("model", apf.model);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * 
 * A notification element, which shows popups when events occur. Similar in concept
 * to [growl](http://growl.info/) on the OSX platform.
 * 
 * @class apf.notifier
 * @define notifier
 * @media
 * @inherits apf.Presentation
 * 
 * @version     %I%, %G% 
 * 
 * @allowchild event
 *
 */
/**
 * @attribute   {String}   position     Sets or gets the vertical and horizontal element's start
 *                                      position. The possible values include:
 *     - `"top-right"`:       the element is placed in top-right corner of browser window (this is the default)
 *     - `"top-left"`:        the element is placed in top-left corner of browser window
 *     - `"bottom-right"`:    the element is placed in bottom-right corner of browser window
 *     - `"bottom-left"`:     the element is placed in bottom-left corner of browser window
 *     - `"center-center"`:   the element is placed in the middle of browser window
 *     - `"right-top"`:       the element is placed in top-right corner of browser window
 *     - `"left-top"`:        the element is placed in top-left corner of browser window
 *     - `"right-bottom"`:    the element is placed in bottom-right corner of browser window
 *     - `"left-bottom"`:     the element is placed in bottom-left corner of browser window
 *     - `"center-center"`:   the element is placed in the middle of browser window
 */
/**
 * @attribute   {String}   margin       Defines the free space around a popup element.
 *                                      Defaults to '10 10 10 10' pixels
 */
/**
 * @attribute   {String|Number}   columnsize   Specifies the element's width and col width, where the
 *                                      element will be displayed. Defaults to 300px.
 */
/**
 * @attribute   {String}   [arrange="vertical"]      Sets or gets the how the popup elements are displayed, either rows (`"vertical"`)
 *                                      or columns (`"horizontal"`).
 */
/**
 * @attribute   {String}   [timeout=2]     After the timeout has passed, the popup
 *                                      disappears automatically. When the
 *                                      mouse is hovering over the popup, it doesn't
 *                                      disappears.
 */
/**
 * @attribute   {String}   onclick      An action executed after a user clicks
 *                                      on the notifier.
 * 
 */
apf.notifier = function(struct, tagName) {
    this.$init(tagName || "notifier", apf.NODE_VISIBLE, struct);
};

(function() {
    var _self = this;
    this.timeout = 2000;
    this.position = "top-right";
    this.columnsize = 300;
    this.arrange = "vertical";
    this.margin = "10 10 10 10";
    this.startPadding = 0;
    
    this.lastPos = null;
    this.showing = 0;
    this.sign = 1;

    this.$supportedProperties.push("margin", "position", "timeout",
        "columnsize", "arrange", "start-padding");

    this.$propHandlers["position"] = function(value) {
        this.lastPos = null;
    };
    
    this.$propHandlers["margin"] = function(value) {
        this.margin = value;
    };
    
    this.$propHandlers["start-padding"] = function(value) {
        this.startPadding = parseInt(value);
    };
    
    this.$propHandlers["timeout"] = function(value) {
        this.timeout = parseInt(value) * 1000;
    };
    
    function getPageScroll() {
        return [
            document.documentElement.scrollTop || document.body.scrollTop,
            document.documentElement.scrollLeft || document.body.scrollLeft
        ];
    }

    function getStartPosition(x, wh, ww, nh, nw, margin, startPadding) {
        var scrolled = getPageScroll();

        return [
             (x[0] == "top"
                 ? margin[0]
                 : (x[0] == "bottom"
                     ? wh - nh - margin[2]
                     : wh / 2 - nh / 2)) + scrolled[0] + startPadding,
             (x[1] == "left"
                 ? margin[3]
                 : (x[1] == "right"
                     ? ww - nw - margin[1]
                     : ww / 2 - nw / 2)) + scrolled[1]
        ];
    }

    /**
     * Creates a new notification popup.
     * 
     * @param {String}  [message=""]  The message content displayed in the popup element
     * @param {String}  [icon]     The path to the icon file ,relative to "icon-path" which
     *                           is set in the skin declaration
     * 
     */
    this.popup = function(message, icon, ev) {
        if (!this.$ext)
            return;

        this.$ext.style.width = this.columnsize + "px";

        var _self = this,
            oNoti = this.$pHtmlNode.appendChild(this.$ext.cloneNode(true)),
            ww = apf.isIE
                ? document.documentElement.offsetWidth
                : window.innerWidth,
            wh = apf.isIE
                ? document.documentElement.offsetHeight
                : window.innerHeight,
        
            removed = false,

            oIcon = this.$getLayoutNode("notification", "icon", oNoti),
            oBody = this.$getLayoutNode("notification", "body", oNoti);

        this.showing++;

        if (oIcon && icon) {
            if (oIcon.nodeType == 1) {
                oIcon.style.backgroundImage = "url("
                + this.iconPath + icon + ")";
            }
            else {
                oIcon.nodeValue = this.iconPath + icon;
            }

            this.$setStyleClass(oNoti, this.$baseCSSname + "ShowIcon");
        }

        oBody.insertAdjacentHTML("beforeend", message || "[No message]");
        oNoti.style.display = "block";

        var margin = apf.getBox(this.margin || "0"),
            nh = oNoti.offsetHeight,
            nw = oNoti.offsetWidth,
            /* It's possible to set for example: position: top-right or right-top */
            x = this.position.split("-"),
            _reset = false;

        if (x[1] == "top" || x[1] == "bottom" || x[0] == "left" || x[0] == "right")
            x = [x[1], x[0]];
        /* center-X and X-center are disabled */
        if ((x[0] == "center" && x[1] !== "center") || (x[0] !== "center" && x[1] == "center"))
            x = ["top", "right"];

        /* start positions */
        if (!this.lastPos) {
            this.lastPos = getStartPosition(x, wh, ww, nh, nw, margin, this.startPadding);
            this.sign = 1;
            _reset = true;
        }

        if ((!_reset && x[0] == "bottom" && this.sign == 1) ||
           (x[0] == "top" && this.sign == -1)) {
            if (this.arrange == "vertical") {
                this.lastPos[0] += x[1] == "center"
                    ? 0
                    : this.sign * (x[0] == "top"
                        ? margin[0] + nh
                        : (x[0] == "bottom"
                            ? - margin[2] - nh
                            : 0));
            }
            else {
                this.lastPos[1] += x[0] == "center"
                    ? 0
                    : this.sign * (x[1] == "left"
                        ? margin[3] + nw
                        : (x[1] == "right"
                            ? - margin[1] - nw
                            : 0));
            }
        }

        /* reset to next line, first for vertical, second horizontal */
        var scrolled = getPageScroll();
        
        if (this.lastPos[0] > wh + scrolled[0] - nh || this.lastPos[0] < scrolled[0]) {
            this.lastPos[1] += (x[1] == "left"
                ? nw + margin[3]
                : (x[1] == "right"
                    ? - nw - margin[3]
                    : 0));
            this.sign *= -1;
            this.lastPos[0] += this.sign*(x[0] == "top"
                ? margin[0] + nh
                : (x[0] == "bottom"
                    ? - margin[2] - nh
                    : 0));
        }
        else if (this.lastPos[1] > ww + scrolled[1] - nw || this.lastPos[1] < scrolled[1]) {
            this.lastPos[0] += (x[0] == "top"
                ? nh + margin[0]
                : (x[0] == "bottom"
                    ? - nh - margin[0]
                    : 0));
            this.sign *= -1;
            this.lastPos[1] += x[0] == "center"
                ? 0
                : this.sign * (x[1] == "left"
                    ? margin[3] + nw
                    : (x[1] == "right"
                        ? - margin[1] - nw
                        : 0));
        }

        /* Start from begining if entire screen is filled */
        if (this.lastPos) {
            if ((this.lastPos[0] > wh + scrolled[0] - nh || this.lastPos[0] < scrolled[1])
              && this.arrange == "horizontal") {
                this.lastPos = getStartPosition(x, wh, ww, nh, nw, margin, this.startPadding);
                this.sign = 1;
            }
            if ((this.lastPos[1] > ww + scrolled[1] - nw || this.lastPos[1] < scrolled[1])
              && this.arrange == "vertical") {
                this.lastPos = getStartPosition(x, wh, ww, nh, nw, margin, this.startPadding);
                this.sign = 1;
            }
        }  

        oNoti.style.left = this.lastPos[1] + "px";
        oNoti.style.top = this.lastPos[0] + "px";

        if ((x[0] == "top" && this.sign == 1) || (x[0] == "bottom" && this.sign == -1)) {
            if (this.arrange == "vertical") {
                this.lastPos[0] += x[1] == "center"
                    ? 0
                    : this.sign * (x[0] == "top"
                        ? margin[0] + nh
                        : (x[0] == "bottom"
                            ? - margin[2] - nh
                            : 0));
            }
            else {
                this.lastPos[1] += x[0] == "center"
                    ? 0
                    : this.sign * (x[1] == "left"
                        ? margin[3] + nw
                        : (x[1] == "right"
                            ? - margin[1] - nw
                            : 0));
            }
        };

        var isMouseOver = false;

        apf.tween.css(oNoti, "fade", {
            anim: apf.tween.NORMAL,
            steps: 10,
            interval: 10,
            onfinish: function(container) {
                oNoti.style.filter = "";
                $setTimeout(hideWindow, _self.timeout)
            }
        });

        function hideWindow() {
            if (isMouseOver)
                return;

            apf.tween.css(oNoti, "notifier_hidden", {
                anim: apf.tween.NORMAL,
                steps: 10,
                interval: 20,
                onfinish: function(container) {
                    apf.setStyleClass(oNoti, "", ["notifier_hover"]);
                    if (isMouseOver)
                        return;

                    if (oNoti.parentNode) {
                        if (oNoti.parentNode.removeChild(oNoti) && !removed) {
                            _self.showing--;
                            removed = true;
                        }
                    }

                    if (_self.showing == 0)
                        _self.lastPos = null;
                }
            });
        }

        /* Events */
        oNoti.onmouseover = function(e) {
            e = (e || event);
            var tEl = e.explicitOriginalTarget || e.toElement;
            if (isMouseOver)
                return;
            if (tEl == oNoti || apf.isChildOf(oNoti, tEl)) {
                apf.tween.css(oNoti, "notifier_hover", {
                    anim: apf.tween.NORMAL,
                    steps: 10,
                    interval: 20,
                    onfinish: function(container) {
                        apf.setStyleClass(oNoti, "", ["notifier_shown"]);
                    }
                });
                
                isMouseOver = true;
            }
        };

        oNoti.onmouseout = function(e) {
            e = (e || event);
            var tEl = e.explicitOriginalTarget || e.toElement;

            if (!isMouseOver)
                return;

            if (apf.isChildOf(tEl, oNoti) ||
               (!apf.isChildOf(oNoti, tEl) && oNoti !== tEl )) {
                isMouseOver = false;
                hideWindow();
            }
        };

        if (ev) {
            oNoti.onclick = function() {
                ev.dispatchEvent("click");
            };
        }
    };

    // *** Init *** //

    this.$draw = function() {
        //Build Main Skin
        this.$pHtmlNode = document.body;
        
        this.$ext = this.$getExternal("notification");
        this.$ext.style.display = "none";
        this.$ext.style.position = "absolute";
        apf.window.zManager.set("notifier", this.$ext);
    };
}).call(apf.notifier.prototype = new apf.Presentation());

apf.aml.setElement("notifier", apf.notifier);
apf.aml.setElement("event", apf.event);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * A tab in a pageable element (_i.e._ a tab in {@link apf.pane}).
 *
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <!-- startcontent -->
 *  <a:window 
 *    visible = "true" 
 *    width = "400" 
 *    height = "150" 
 *    title = "Simple Tab" >
 *      <a:pane anchors="10 10 10 10"> 
 *          <a:tab caption="General"> 
 *              <a:checkbox>Example</a:checkbox> 
 *              <a:button>Example</a:button> 
 *          </a:tab> 
 *          <a:tab caption="Advanced"> 
 *              <a:checkbox>Test checkbox</a:checkbox> 
 *              <a:checkbox>Test checkbox</a:checkbox> 
 *              <a:checkbox>Test checkbox</a:checkbox> 
 *          </a:tab> 
 *          <a:tab caption="Ajax.org"> 
 *              <a:checkbox>This ok?</a:checkbox> 
 *              <a:checkbox>This better?</a:checkbox> 
 *          </a:tab> 
 *      </a:pane> 
 *  </a:window>
 *  <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.tab
 * @define  tab
 * @container
 * @inherits apf.Presentation
 * @allowchild  {elements}, {anyaml}
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 */
apf.tab = function(struct, tagName) {
    this.$init(tagName || "tab", apf.NODE_VISIBLE, struct);
};

(function(){
    this.canHaveChildren = true;
    
    this.$focussable = false;
    this.closebtn = false;
    this.autofocus = true;

    

    
    /**
     * Sets the caption of the button of this element.
     * @param {String} caption The text displayed on the button of this element.
     */
    this.setCaption = function(caption) {
        this.setProperty("caption", caption, false, true);
    };

    /**
     * Sets the icon of the button of this element.
     * @param {String} icon The icon displayed on the button of this element.
     */
    this.setIcon = function(icon) {
        this.setProperty("icon", icon, false, true);
    };
    

    // *** Delayed Render Support *** //

    
    //Hack
    this.addEventListener("beforerender", function(){
        this.parentNode.dispatchEvent("beforerender", {
            tab: this
        });
    });

    this.addEventListener("afterrender",  function(){
        this.parentNode.dispatchEvent("afterrender", {
            tab: this
        });
    });
     

    // *** Properties *** //

    this.$booleanProperties["visible"] = true;
    this.$booleanProperties["fake"] = true;
    this.$booleanProperties["closebtn"] = true;
    this.$booleanProperties["autofocus"] = true;
    this.$supportedProperties.push("fake", "caption", "icon", "tooltip",
        "type", "buttons", "closebtn", "trans-in", "trans-out", "autofocus");

    
    /**
     * @attribute {Boolean} closebtn Sets or gets whether this tab's button shows a close button inside it.
     */
    this.$propHandlers["closebtn"] = function(value) {
        //if (!this.$amlLoaded || !this.parentNode.$hasButtons)
          //  return;
        var _self = this;
        
        if (value) {
            var btncontainer = this.parentNode.$getLayoutNode("button", "container", this.$button);
            
            this.parentNode.$getNewContext("btnclose");
            var elBtnClose = this.parentNode.$getLayoutNode("btnclose");

            if (elBtnClose) {
               // if (elBtnClose.nodeType == 1) {
                apf.setStyleClass(this.$button, "btnclose");
                
                elBtnClose.addEventListener("mousedown", function(e) {
                    apf.cancelBubble(e, apf.lookup(_self.$uniqueId));
                }, false);
                
                elBtnClose.addEventListener("click", function(e) {
                    var tab = apf.lookup(_self.$uniqueId);
                    tab.parentNode.remove(tab, e);
                }, false);

                btncontainer.appendChild(elBtnClose);
            }
            
        }
    };
    

    /**
     * @attribute {String} caption Sets or gets the text displayed on the button of this element.
     */
    this.$propHandlers["tooltip"] = function(value) {
        if (!this.parentNode)
            return;

        var node = this.parentNode
            .$getLayoutNode("button", "caption", this.$button);

        (node.nodeType == 1 ? node : node.parentNode).setAttribute("title", value || "");
    }

    /**
     * @attribute {String} caption Sets or gets the text displayed on the button of this element.
     */
    this.$propHandlers["caption"] = function(value) {
        if (!this.parentNode)
            return;

        var node = this.parentNode
            .$getLayoutNode("button", "caption", this.$button);

        if (node.nodeType == 1)
            node.innerHTML = value;
        else
            node.nodeValue = value;
    };

    this.$propHandlers["icon"] = function(value) {
        if (!this.parentNode)
            return;

        var node = this.parentNode
            .$getLayoutNode("button", "icon", this.$button);

        if (node && node.nodeType == 1)
            apf.skins.setIcon(node, value, this.parentNode.iconPath);
    };

    this.$propHandlers["visible"] = function(value) {
        if (!this.parentNode)
            return;

        if (value) {
            if (this.$fake) {
                this.parentNode.set(this.$fake); 
                this.visible = false;
                return;
            }
            
            this.$ext.style.display = "";
            if (this.parentNode.$hasButtons)
                this.$button.style.display = "block";

            if (!this.parentNode.$activepage)
                this.parentNode.set(this);
        }
        else {
            if (this.$active) {
                this.$deactivate();

                // Try to find a next tab, if any.
                var nextPage = this.parentNode.activepagenr + 1;
                var pages = this.parentNode.getPages()
                var len = pages.length
                while (nextPage < len && !pages[nextPage].visible)
                    nextPage++;

                if (nextPage == len) {
                    // Try to find a previous tab, if any.
                    nextPage = this.parentNode.activepagenr - 1;
                    while (nextPage >= 0 && len && !pages[nextPage].visible)
                        nextPage--;
                }

                if (nextPage >= 0)
                    this.parentNode.set(nextPage);
                else {
                    this.parentNode.activepage = 
                    this.parentNode.activepagenr =
                    this.parentNode.$activepage = null;
                }
            }

            this.$ext.style.display = "none";
            if (this.parentNode.$hasButtons)
                this.$button.style.display = "none";
        }
    };

    /**
     * @attribute {Boolean} fake Sets or gets whether this tab actually contains elements or
     * only provides a button in the pageable parent element.
     */
    this.$propHandlers["fake"] = function(value) {
        if (this.$ext) {
            apf.destroyHtmlNode(this.$ext);
            this.$int = this.$ext = null;
        }
    };

    this.$propHandlers["type"] = function(value) {
        this.setProperty("fake", true);
        
        if (this.relPage && this.$active)
            this.relPage.$deactivate();
        
        this.relPage = this.parentNode.getPage(value);
        if (this.$active)
            this.$activate();
    };

    // *** DOM Hooks *** //

    this.addEventListener("DOMNodeRemoved", function(e) {
        if (e && e.currentTarget != this)
            return;
        
        if (this.$button) {
            if (this.$position & 1)
                this.parentNode.$setStyleClass(this.$button, "", ["firstbtn", "firstcurbtn"]);
            if (this.$position & 2)
                this.parentNode.$setStyleClass(this.$button, "", ["lastbtn"]);
        }

        if (!e.$doOnlyAdmin) {
            if (this.$button)
                this.$button.parentNode.removeChild(this.$button);

            if (this.parentNode && this.parentNode.$activepage == this) {
                if (this.$button)
                    this.parentNode.$setStyleClass(this.$button, "", ["curbtn"]);
                this.parentNode.$setStyleClass(this.$ext, "", ["curpage"]);
            }
        }
    });
    
    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        if (this.fake && this.parentNode && this.parentNode.$activepage == this)
            this.$deactivate();
    });

    this.addEventListener("DOMNodeInserted", function(e) {
        if (e && e.currentTarget != this || !this.$amlLoaded) //|| !e.$oldParent
            return;
            
        if (!e.$isMoveWithinParent 
          && this.skinName != this.parentNode.skinName) {
            // this.$destroy(); //clean up button
            var skin = this.parentNode.skinName.split(":");
            this.$forceSkinChange(skin[1], skin[0]);
        }
        else if (this.$button && (!e.$oldParent || e.$oldParent.$hasButtons) && this.parentNode.$buttons)
            this.parentNode.$buttons.insertBefore(this.$button,
                e.$beforeNode && e.$beforeNode.$button || null);
        
        if (this.type)
            this.relPage = this.parentNode.getPage && this.parentNode.getPage(this.type);
        
    }, true);

    // *** Private state functions *** //

    this.$position = 0;
    this.$first = function(remove) {
        if (remove) {
            this.$isFirst = false;
            this.$position -= 1;
            this.parentNode.$setStyleClass(this.$button, "",
                ["firstbtn", "firstcurbtn"]);
        }
        else {
            this.$isFirst = true;
            this.$position = this.$position | 1;
            this.parentNode.$setStyleClass(this.$button, "firstbtn"
                + (this.parentNode.$activepage == this ? " firstcurbtn" : ""));
        }
    };

    this.$last = function(remove) {
        if (remove) {
            this.$isLast = false;
            this.$position -= 2;
            this.parentNode.$setStyleClass(this.$button, "", ["lastbtn"]);
        }
        else {
            this.$isLast = true;
            this.$position = this.$position | 2;
            this.parentNode.$setStyleClass(this.$button, "lastbtn");
        }
    };

    this.$deactivate = function(fakeOther) {
        this.$active = false;

        if (!this.parentNode)
            return;

        if (this.parentNode.$hasButtons) {
            if (this.$position > 0)
                this.parentNode.$setStyleClass(this.$button, "", ["firstcurbtn"]);
            this.parentNode.$setStyleClass(this.$button, "", ["curbtn"]);
        }

        if ((!this.fake || this.relPage) && !fakeOther) {
            this.parentNode.$setStyleClass(this.fake
                ? this.relPage.$ext
                : this.$ext, "", ["curpage"]);
            
            if (this.fake) {
                if (!this.relPage.visible)
                    this.relPage.$ext.style.display = "none";
                    
                this.relPage.dispatchEvent("prop.visible", {value:false});
            }
            
            this.dispatchEvent("prop.visible", {value:false});
        }
    };
    
    this.$deactivateButton = function() {
        if (this.parentNode && this.parentNode.$hasButtons) {
            if (this.$position > 0)
                this.parentNode.$setStyleClass(this.$button, "", ["firstcurbtn"]);
            this.parentNode.$setStyleClass(this.$button, "", ["curbtn"]);
        }
    };

    this.$activate = function(){
        //if (this.disabled)
            //return false;

        this.$active = true;

        if (!this.$drawn) {
            var f;
            this.addEventListener("DOMNodeInsertedIntoDocument", f = function(e) {
                this.removeEventListener("DOMNodeInsertedIntoDocument", f);
                if (!this.$active)
                    return;
                    
                this.$activate();
            });
            return;
        }

        if (this.parentNode.$hasButtons) {
            if (this.$isFirst)
                this.parentNode.$setStyleClass(this.$button, "firstcurbtn");
            this.parentNode.$setStyleClass(this.$button, "curbtn");
        }

        if (!this.fake || this.relPage) {
            if (this.fake) {
                if (this.relPage) {
                    this.relPage.$ext.style.display = "";
                    this.parentNode.$setStyleClass(this.relPage.$ext, "curpage");
                    this.relPage.$fake = this;

                    
                    if (this.relPage.$render)
                        this.relPage.$render();
                    
                    
                    this.relPage.dispatchEvent("prop.visible", {value:true});
                }
            }
            else {
                this.parentNode.$setStyleClass(this.$ext, "curpage");
            }
            
            
            if (apf.layout && this.relPage)
                apf.layout.forceResize(this.fake ? this.relPage.$int : this.$int);
            
        }

        
        if (this.$render)
            this.$render();
        
        
        if (!this.fake) {
            if (apf.isIE) {
                var cls = this.$ext.className;
                this.$ext.className = "rnd" + Math.random();
                this.$ext.className = cls;
            }
            
            this.dispatchEvent("prop.visible", {value:true});
        }
    };
    
    this.$activateButton = function() {
        if (this.$active)
            return;

        if (!this.$drawn) {
            var f;
            this.addEventListener("DOMNodeInsertedIntoDocument", f = function(e) {
                this.removeEventListener("DOMNodeInsertedIntoDocument", f);
                this.$activateButton();
            });
            return;
        }
        
        if (this.parentNode && this.parentNode.$hasButtons) {
            if (this.$isFirst)
                this.parentNode.$setStyleClass(this.$button, "firstcurbtn");
            this.parentNode.$setStyleClass(this.$button, "curbtn");
        }
        
        
        if (this.$render)
            this.$render();
        
    };

    this.addEventListener("$skinchange", function(){
        if (this.caption)
            this.$propHandlers["caption"].call(this, this.caption);

        if (this.icon)
            this.$propHandlers["icon"].call(this, this.icon);
    });

    this.$enable = function(){
        if (this.$button)
            this.$setStyleClass(this.$button, null, ["btnDisabled"]);//@todo this.$baseCSSname + 
    };

    this.$disable = function(){
        if (this.$button)
            this.$setStyleClass(this.$button, "btnDisabled");//@todo this.$baseCSSname + 
    };
    
    function $btnSet(oHtml) {
        this.parentNode.set(this);
        if (this.autofocus)
            this.canHaveChildren = 2;
        this.$setStyleClass(oHtml, "down", null, true);
    }
    
    this.$btnDown = function(oHtml, htmlEvent) {
        if (this.disabled) 
            return;
            
        if (htmlEvent.button == 2 && this.parentNode.contextmenu) {
            this.parentNode.contextPage = this;
            return;
        }
        
        if (this.parentNode.dispatchEvent("tabselectclick", {
            tab: this,
            htmlEvent: htmlEvent
        }) === false)
            return;
        
        this.$btnPressed = true;
        
        //if (!this.parentNode.$order)
            $btnSet.call(this, oHtml);
    }
    
    this.$btnUp = function(oHtml) {
        this.parentNode.$setStyleClass(oHtml, "", ["down"], true);
        
        if (this.disabled) 
            return;
        
        if (false && this.parentNode.$order && this.$btnPressed) {
            this.$dragging = false;
            
            $btnSet.call(this, oHtml);
        }
        
        this.$btnPressed = false;
        
        this.parentNode.dispatchEvent("tabselectmouseup");
    }
    
    this.$btnOut = function(oHtml) {
        this.parentNode.$setStyleClass(oHtml, "", ["over"], true);
        
        this.canHaveChildren = true;
        this.$dragging = false;
        this.$btnPressed = false;
    }

    // *** Init *** //

    this.$canLeechSkin = true;
    
    this.addEventListener("prop.class", function(e) {
        apf.setStyleClass(this.$button, e.value, this.$lastClassValueBtn ? [this.$lastClassValueBtn] : null);
        this.$lastClassValueBtn = e.value;
    });
    
    this.$draw = function(isSkinSwitch) {
        this.skinName = this.parentNode.skinName;

        var sType = this.getAttribute("type")
        if (sType) {
            this.fake = true;
            this.relPage = this.parentNode.getPage(sType) || null;
        }

        if (this.parentNode.$hasButtons) {
            //this.parentNode.$removeEditable(); //@todo multilingual support is broken when using dom

            this.parentNode.$getNewContext("button");
            var elBtn = this.parentNode.$getLayoutNode("button");
            elBtn.setAttribute(this.parentNode.$getOption("main", "select") || "onmousedown",
                'apf.lookup(' + this.$uniqueId + ').$btnDown(this, event);');
            elBtn.setAttribute("onmouseup", 
                'apf.lookup(' + this.$uniqueId + ').$btnUp(this)');
            elBtn.setAttribute("onmouseover", 'var o = apf.lookup('
                + this.$uniqueId + ').parentNode;if(apf.lookup(' + this.$uniqueId
                + ') != o.$activepage'  + (this.parentNode.overactivetab ? " || true" : "")  + ') o.$setStyleClass(this, "over", null, true);');
            elBtn.setAttribute("onmouseout", 'var o = apf.lookup('
                + this.$uniqueId + ');o&&o.$btnOut(this, event);');

            //var cssClass = this.getAttribute("class");
            //if (cssClass) {
            //    apf.setStyleClass(elBtn, cssClass);
            //    this.$lastClassValueBtn = cssClass;
            //}

            this.$button = apf.insertHtmlNode(elBtn, this.parentNode.$buttons);
            
            var closebtn = this.closebtn = this.getAttribute("closebtn");
            if ((apf.isTrue(closebtn) || ((this.parentNode.buttons || "").indexOf("close") > -1 && !apf.isFalse(closebtn))))
                this.$propHandlers["closebtn"].call(this, true);
            
            
//            if (this.parentNode.$scale) {
//                var w = apf.getHtmlInnerWidth(this.parentNode.$buttons);
//                var l = this.parentNode.getPages().length;
//                this.$button.style.width = Math.round(Math.min(w/l, this.parentNode.$maxBtnWidth)) + "px";
//            }
            

            if (!isSkinSwitch && this.nextSibling && this.nextSibling.$button)
                this.$button.parentNode.insertBefore(this.$button, this.nextSibling.$button);

            this.$button.host = this;
        }

        if (this.fake)
            return;

        if (this.$ext)
            this.$ext.parentNode.removeChild(this.$ext); //@todo mem leaks?

        this.$ext = this.parentNode.$getExternal("tab",
            this.parentNode.oPages, null, this);
        this.$ext.host = this;

        this.$int = this.parentNode
            .$getLayoutNode("Tab", "container", this.$ext);
        //if (this.$int)
            //this.$int.setAttribute("id", this.$int.getAttribute("id"));

        //@todo this doesnt support hidden nodes.
        if (this.visible) {
            if (this.$isLast)
                this.$last();
            if (this.$isFirst)
                this.$first();
        }
        
        var _self = this;
        this.parentNode && this.parentNode.getPages().forEach(function(tab) {
            if (tab && tab.type == _self.id) {
                tab.relPage = _self;
                if (tab.$active) {
                    _self.$fake = tab; 
                    tab.$activate();
                }
                _self.$button.style.display = "none";
                _self.visible = false;
            }
        })
    };

    this.$destroy = function(){
        if (this.$button) {
            if (this.parentNode && !this.parentNode.$amlDestroyed
              && this.$button.parentNode)
                this.$button.parentNode.removeChild(this.$button);
            
            this.$button.host = null;
            this.$button = null;
        }
    };
    
    
}).call(apf.tab.prototype = new apf.Presentation());

apf.aml.setElement("tab", apf.tab);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * element specifying an argument of a method in an rpc element.
 * @attribute {String}  name             the argument name.
 * @attribute {String}  [value]          the value of the argument.
 * @attribute {String}  [default]        the default value of the argument. If
 *                                       no value is specified when this function
 *                                       is called, the default value is used.
 */
apf.param = function(struct, tagName) {
    this.$init(tagName || "param", apf.NODE_HIDDEN, struct);
};

apf.param.prototype = new apf.AmlElement();
apf.param.prototype.$parsePrio = "002";
apf.aml.setElement("variable", apf.param); //backwards compatibility
apf.aml.setElement("param", apf.param);




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element graphically represents a percentage value which increases
 * automatically with time. 
 *
 * This element is most often used to show the progress
 * of a process. The progress can be either indicative or exact.
 * 
 * #### Example: A Simple Progressbar
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:progressbar 
 *     min = "0" 
 *     max = "100" 
 *     value = "40" 
 *     width = "300" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * #### Example: Progressbars with Varying Speeds
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:progressbar 
 *     id = "pb1"
 *     min = "0" 
 *     max = "100" 
 *     value = "40" 
 *     width = "300"><a:script>//<!--
 *     pb1.start();
 *   //--></a:script>
 *   </a:progressbar>
 * 
 *   <a:progressbar 
 *     id = "pb2"
 *     min = "0" 
 *     max = "100" 
 *     value = "40" 
 *     width = "300"><a:script>//<!--
 *     pb2.start(50);
 *   //--></a:script>
 *   </a:progressbar>
 * </a:application>
 * ```
 * 
 * #### Example: Dynmically Controlling the Progressbar
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:progressbar
 *     id = "pb3"
 *     min = "0"
 *     max = "100"
 *     value = "0"
 *     width = "300" />
 *   <a:table 
 *     columns = "80, 80, 80, 80"
 *     cellheight = "24" 
 *     margin = "15 0">
 *       <a:button onclick="pb3.start()">Start</a:button>
 *       <a:button onclick="pb3.pause()">Pause</a:button>
 *       <a:button onclick="pb3.stop()">Stop</a:button>
 *       <a:button onclick="pb3.clear()">Clear</a:button>
 *       <a:button onclick="pb3.enable()">Enable</a:button>
 *       <a:button onclick="pb3.disable()">Disable</a:button>
 *   </a:table>
 * </a:application>
 * ```
 * 
 * @class apf.progressbar
 * @define progressbar
 * @allowchild {smartbinding}
 *
 * @form
 * @inherits apf.StandardBinding
 * @inherits apf.DataAction
 * 
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 */
/**
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * 
 * #### Example
 * 
 * Sets the progress position based on data loaded into this component.
 * 
 * ```xml
 *  <a:model>
 *      <data progress="50"></data>
 *  </a:model>
 *  <a:progressbar min="0" max="100" value="[@progress]" />
 * ```
 *
 * A shorter way to write this is:
 * 
 * ```xml
 *  <a:model id="mdlProgress">
 *      <data progress="50"></data>
 *  </a:model>
 *  <a:progressbar value="[mdlProgress::@progress]" />
 * ```
 */
apf.progress = function(struct, tagName) {
    this.$init(tagName || "progress", apf.NODE_VISIBLE, struct);
};
apf.progressbar = function(struct, tagName) {
    this.$init(tagName || "progressbar", apf.NODE_VISIBLE, struct);
};

(function(){
    
    this.implement(apf.DataAction);
    

    this.$focussable = false; // This object can get the focus

    // *** Properties and Attributes *** //

    this.value = 0;
    this.min = 0;
    this.max = 100;
    
    this.$running = false;
    this.$timer;

    /**
     * @attribute {Boolean} autostart Sets or gets whether the progressbar starts automatically.
     */
    /**
     * @attribute {Boolean} autohide  Sets or gets whether the progressbar hides when the progress is at 100%. Setting this to `true` hides the progressbar at start when autostart is not set to `true`.
     */
    this.$booleanProperties["autostart"] = true;
    this.$booleanProperties["autohide"] = true;

    this.$supportedProperties.push("value", "min", "max", "autostart", "autohide");
    
    /**
     * @attribute {String} value Sets or gets the position of the progressbar stated between 
     * the min and max value.
     */
    this.$propHandlers["value"] = function(value) {
        this.value = parseInt(value) || this.min;

        if (this.value >= this.max)
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Complete", [this.$baseCSSname + "Running", this.$baseCSSname + "Half"]);
        else
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Running", [this.$baseCSSname + "Complete"]);
            
        if (this.value >= this.max / 2)
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Half", []);

        this.oSlider.style.width = (this.value * 100 / (this.max - this.min)) + "%"
        
        /*Math.max(0,
            Math.round((this.$ext.offsetWidth - 5)
            * (this.value / (this.max - this.min)))) + "px";*/

        this.oCaption.nodeValue =
            Math.round((this.value / (this.max - this.min)) * 100) + "%";
    };

    /**
     * @attribute {Number} min Sets or gets the minimum value the progressbar may have. This is
     * the value that the progressbar has when it is at its start position.
     */
    this.$propHandlers["min"] = function(value) {
        this.min = parseFloat(value);
    };

    /**
     * @attribute {Number} max Sets or gets the maximum value the progressbar may have. This is
     * the value that the progressbar has when it is at its end position.
     */
    this.$propHandlers["max"] = function(value) {
        this.max = parseFloat(value);
    };

    // *** Public Methods *** //

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };

    /**
     * Returns the current value of this element.
     * @return {String} The current value.
     */
    this.getValue = function(){
        return this.value;
    };
    
    

    /**
     * Resets the progress indicator.
     */
    this.clear = function(){
        this.$clear();
    };

    this.$clear = function(restart, restart_time) {
        clearInterval(this.$timer);
        this.setValue(this.min);
        //this.oSlider.style.display = "none";
        apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Running", this.$baseCSSname + "Complete"]);

        if (restart) {
            var _self = this;
            this.$timer = setInterval(function(){
                _self.start(restart_time);
            });
        }
        
        if (this.autohide)
            this.hide();
        
        this.$running = false;
    };

    /**
     * Starts the progress indicator.
     * @param {Number} start Sets or gets the time between each step in milliseconds.
     */
    this.start = function(time) {
        if (this.autohide)
            this.show();

        clearInterval(this.$timer);
        
        //if (this.value == this.max)
            //this.setValue(this.min + (this.max - this.min) * 0.5);
        
        //this.oSlider.style.display = "block";
        var _self = this;
        this.$timer = setInterval(function(){
            if (_self.$amlDestroyed)
                clearInterval(_self.$timer);
            else
                _self.$step();
        }, time || 1000);
        this.$setStyleClass(this.$ext, this.$baseCSSname + "Running");
    };

    /**
     * Pauses the progress indicator.
     */
    this.pause = function(){
        clearInterval(this.$timer);
    };

    /**
     * Stops the progress indicator from moving.
     * @param {Boolean} restart Specifies whether a `this.$timer` should start with a new indicative progress indicator.
     * @param {Number} [time=500] The internal (in milliseconds)
     * @param {Number} [restart_time] The time for the next restart to occur
     */
    this.stop = function(restart, time, restart_time) {
        clearInterval(this.$timer);
        this.setValue(this.max);
        
        var _self = this;
        this.$timer = setInterval(function(){
            _self.$clear(restart, (restart_time || 0));
        }, time || 500);
    };

    // *** Private methods *** //

    this.$step = function(){
        if (this.value == this.max) 
            return;
        
        this.setValue(this.value + 1);
    };

    // *** Init *** //

    this.$draw = function(clear, parentNode, Node, transform) {
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.oSlider = this.$getLayoutNode("main", "progress", this.$ext);
        this.oCaption = this.$getLayoutNode("main", "caption", this.$ext);
    };

    this.$loadAml = function(x) {
        if (this.autostart)
           this.start();

        if (this.autohide)
            this.hide();
    };

}).call(apf.progressbar.prototype = new apf.StandardBinding());


apf.progress.prototype = apf.progressbar.prototype;

apf.aml.setElement("progress",    apf.progress);
apf.aml.setElement("progressbar", apf.progressbar);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */







/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * This element displays a two state button which is one of a grouped set.
 * Only one of these buttons in the set can be selected at the same time.
 * 
 * #### Example: Settings Groups
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <a:table columns="100, 150" cellheight="20">
 *   <!-- startcontent -->
 *     <a:label>Options</a:label> 
 *     <a:label>Choices</a:label> 
 *     <a:radiobutton group="g2">Option 1</a:radiobutton> 
 *     <a:radiobutton group="g3">Choice 1</a:radiobutton> 
 *     <a:radiobutton group="g2">Option 2</a:radiobutton>
 *     <a:radiobutton group="g3">Choice 2</a:radiobutton>
 *   <!-- endcontent -->
 *  </a:table>
 * </a:application>
 * ```
 *
 * @class apf.radiobutton
 * @define radiobutton
 * @allowchild {smartbinding}
 *
 * @form
 * @inherits apf.Presentation
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 */
/**
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * 
 * #### Example
 *
 * Sets the selection based on data loaded into this component.
 * 
 * ```xml
 *  <a:radiobutton group="g2" bindings="bndExample" value="1">Choice 1</a:radiobutton>
 *  <a:radiobutton group="g2" value="2">Choice 2</a:radiobutton>
 *
 *  <a:bindings id="bndExample">
 *      <a:value match="[@value]" />
 *  </a:bindings>
 * ```
 *
 * A shorter way to write this is:
 *
 * ```xml
 *  <a:radiobutton group="g2" value="[@value]" value="1">Choice 1</a:radiobutton>
 *  <a:radiobutton group="g2" value="2">Choice 2</a:radiobutton>
 * ```
 *
 */
/**
 * @event click Fires when the user presses a mousebutton while over this element and then lets the mousebutton go. 
 * @see apf.AmlNode@afterchange
 */
apf.radiobutton = function(struct, tagName) {
    this.$init(tagName || "radiobutton", apf.NODE_VISIBLE, struct);
    
    /*this.$constructor = apf.radiobutton;
    var fEl = apf.aml.setElement("radiobutton", function(){
        this.$init(tagName || "radiobutton", apf.NODE_VISIBLE, struct);
    });
    fEl.prototype = apf.radiobutton.prototype;
    apf.radiobutton = fEl;*/
};

(function(){
    this.implement(apf.ChildValue);
    this.$childProperty = "label";
    
    this.$focussable = apf.KEYBOARD; // This object can get the focus
    
    //1 = force no bind rule, 2 = force bind rule
    /*this.$attrExcludePropBind = apf.extend({
        selected: 1
    }, this.$attrExcludePropBind);*/

    // *** Properties and Attributes *** //

    this.$booleanProperties["selected"] = true;
    this.$supportedProperties.push("value", "background", "group",
        "label", "selected", "tooltip", "icon");

    /**
     * @attribute {String} group Sets or gets the name of the group to which this radio
     * button belongs. Only one item in the group can be selected at the same
     * time. 
     * When no group is specified the parent container functions as the
     * group; only one radiobutton within that parent can be selected.
     */
    this.$propHandlers["group"] = function(value) {
        if (!this.$ext)
            return;
        
        if (this.$group && this.$group.$removeRadio)
            this.$group.$removeRadio(this);
            
        if (!value) {
            this.$group = null;
            return;
        }

        var group = typeof value == "string"
            ?
            
            apf.nameserver.get("group", value)
            
            : value;
        if (!group) {
            
            group = apf.nameserver.register("group", value, 
                new apf.$group());
            group.setAttribute("id", value);
            group.dispatchEvent("DOMNodeInsertedIntoDocument");
            group.parentNode = this;
            
        }
        this.$group = group;
        
        if (this.oInput)
            this.oInput.setAttribute("name", value);
        
        this.$group.$addRadio(this);
    };
    
    /**
     * @attribute {String} tooltip Sets or gets the tooltip of this radio button.
     */
    this.$propHandlers["tooltip"] = function(value) {
        this.$ext.setAttribute("title", value);
    };

    /**
     * @attribute {String} icon Sets or gets the icon for this radiobutton
     */
    this.$propHandlers["icon"] = function(value) {
        
        if (!this.oIcon) return;
        

        if (value)
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Icon");
        else
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Icon"]);

        apf.skins.setIcon(this.oIcon, value, this.iconPath);
    };

    /**
     * @attribute {String} label Sets or gets the label for this radiobutton
     */
    this.$propHandlers["label"] = function(value) {
        if (value)
            this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Empty"]);
        else
            this.$setStyleClass(this.$ext, this.$baseCSSname + "Empty");
        
        if (this.oLabel)
            this.oLabel.innerHTML = value;
    };

    /**
     * @attribute {Boolean} selected Sets or gets  whether this radiobutton is the selected one in the group it belongs to.
     */
    this.$propHandlers["selected"] = function(value) {
        if (!this.$group)
            return;

        if (value)
            this.$group.setProperty("value", this.value);
        //else if (this.$group.value == this.value)
            //this.$group.setProperty("value", "");
    };
    
    this.addEventListener("prop.model", function(e) {
        if (this.$group)
            this.$group.setProperty("model", e.value);
    });

    /**
     * @attribute {String} background Sets a multistate background. The arguments
     * are seperated by pipes (`'|'`) and are in the order of: `'imagefilename|mapdirection|nrofstates|imagesize'`
     * 
     * {:multiStateDoc}
     *
     * 
     * #### Example
     * 
     * Here's a three state picture where each state is 16px high, vertically spaced:
     * 
     * ```xml
     * background="threestates.gif|vertical|3|16"
     * ```
     * @see apf.BaseButton
     */
    this.$propHandlers["background"] = function(value) {
        var oNode = this.$getLayoutNode("main", "background", this.$ext);
        if (value) {
            var b = value.split("|");
            this.$background = b.concat(["vertical", 2, 16].slice(b.length - 1));

            oNode.style.backgroundImage = "url(" + this.mediaPath + b[0] + ")";
            oNode.style.backgroundRepeat = "no-repeat";
        }
        else {
            oNode.style.backgroundImage = "";
            oNode.style.backgroundRepeat = "";
            this.$background = null;
        }
    };

    // *** Public methods *** //

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };

    /**
     * Returns the current value of this element.
     * @return {String} The current value
     */
    this.getValue = function(){
        return this.value;
    };
    
    this.select = function(){
        this.setProperty("selected", true, false, true);
    };
    
    /*this.uncheck = function(){
        this.setProperty("selected", false, false, true);
    }*/
    
    this.getGroup = function(){
        return this.$group;
    };
    
    

    /*
     * Sets the selected state and related value
     */
    this.$check = function(visually) {
        this.$setStyleClass(this.$ext, this.$baseCSSname + "Selected");
        this.selected = true;
        if (this.oInput)
            this.oInput.selected = true;
        this.doBgSwitch(2);
    };

    this.$uncheck = function(){
        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Selected"]);
        this.selected = false;
        if (this.oInput)
            this.oInput.selected = false;
        this.doBgSwitch(1);
    };

    // *** Private methods *** //

    this.$enable = function(){
        if (this.oInput)
            this.oInput.disabled = false;

        var _self = this;
        this.$ext.onclick = function(e) {
            if (!e) e = event;
            if ((e.srcElement || e.target) == this)
                return;

            _self.dispatchEvent("click", {
                htmlEvent: e
            });
            _self.$group.change(_self.value);
        }

        this.$ext.onmousedown = function(e) {
            if (!e) e = event;
            if ((e.srcElement || e.target) == this)
                return;

            apf.setStyleClass(this, _self.$baseCSSname + "Down");
        }

        this.$ext.onmouseover = function(e) {
            if (!e) e = event;
            if ((e.srcElement || e.target) == this)
                return;

            apf.setStyleClass(this, _self.$baseCSSname + "Over");
        }

        this.$ext.onmouseout =
        this.$ext.onmouseup = function(){
            apf.setStyleClass(this, "", [_self.$baseCSSname + "Down", _self.$baseCSSname + "Over"]);
        }
    };

    this.$disable = function(){
        if (this.oInput)
            this.oInput.disabled = true;

        this.$ext.onclick = 
        this.$ext.onmousedown =
        this.$ext.onmouseover =
        this.$ext.onmouseout = 
        this.$ext.onmouseup = null;
    };

    /**
     * @private
     */
    this.doBgSwitch = function(nr) {
        if (this.bgswitch && (this.bgoptions[1] >= nr || nr == 4)) {
            if (nr == 4)
                nr = this.bgoptions[1] + 1;

            var strBG = this.bgoptions[0] == "vertical"
                ? "0 -" + (parseInt(this.bgoptions[2]) * (nr - 1)) + "px"
                : "-"   + (parseInt(this.bgoptions[2]) * (nr - 1)) + "px 0";

            this.$getLayoutNode("main", "background", this.$ext)
                .style.backgroundPosition = strBG;
        }
    };

    this.$focus = function(){
        if (!this.$ext)
            return;
        if (this.oInput && this.oInput.disabled)
            return false;

        this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
    };

    this.$blur = function(){
        if (!this.$ext)
            return;
        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
    };

    // *** Keyboard support *** //

    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode;

        if (key == 13 || key == 32) {
            //this.check();
            //this.$group.current = this;
            this.$group.change(this.value);
            return false;
        }
        //Up
        else if (key == 38) {
            var node = this;
            while (node && node.previousSibling) {
                node = node.previousSibling;
                if (node.localName == "radiobutton" && !node.disabled
                  && node.$group == this.$group) {
                    node.check();
                    node.focus();
                    return;
                }
            }
        }
        //Down
        else if (key == 40) {
            var node = this;
            while (node && node.nextSibling) {
                node = node.nextSibling;
                if (node.localName == "radiobutton" && !node.disabled
                  && node.$group == this.$group) {
                    node.check();
                    node.focus();
                    return;
                }
            }
        }
    }, true);
    

    // *** Init *** //

    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.oInput = this.$getLayoutNode("main", "input", this.$ext);
        this.oLabel = this.$getLayoutNode("main", "label", this.$ext);
        this.oIcon = this.$getLayoutNode("main", "icon", this.$ext);

        if (this.oLabel && this.oLabel.nodeType != 1)
            this.oLabel = this.oLabel.parentNode;

        //Set events
        this.$enable();
    };

    this.$childProperty = "label";
    this.$loadAml = function(x) {
        if (this.group)
            this.$propHandlers["group"].call(this, this.group);
        
        else if (this.parentNode.localName == "group")
            this.$propHandlers["group"].call(this, this.parentNode);

        if (!this.$group) {
            this.$propHandlers["group"].call(this,
                "group" + this.parentNode.$uniqueId);
        }
    };
    
    this.$destroy = function(){
        if (this.$group)
            this.$group.$removeRadio(this);
    };
    
    
}).call(apf.radiobutton.prototype = new apf.Presentation());

apf.aml.setElement("radiobutton", apf.radiobutton);

/**
 * An element that defines groups for radio buttons.
 * 
 * #### Example
 *
 * This example shows radio buttons with an explicit group set:
 *
 * ```xml
 *  <a:label>Options</a:label>
 *  <a:radiobutton group="g1">Option 1</a:radiobutton>
 *  <a:radiobutton group="g1">Option 2</a:radiobutton>
 *
 *  <a:label>Choices</a:label>
 *  <a:group id="g2" value="[mdlForm::choice]">
 *      <a:radiobutton value="c1">Choice 1</a:radiobutton>
 *      <a:radiobutton value="c2">Choice 2</a:radiobutton>
 *  </a:group>
 * ```
 *
 * @class apf.group
 * @define group
 */
apf.$group = apf.group = function(struct, tagName) {
    this.$init(tagName || "group", apf.NODE_VISIBLE, struct);
    
    this.implement(
        apf.StandardBinding,
        
        apf.DataAction
        
        
    );

    var radiobuttons = [];

    this.$supportedProperties.push("value", "selectedItem");
    this.$propHandlers["value"] = function(value) {
        for (var i = 0; i < radiobuttons.length; i++) {
            if (radiobuttons[i].value == value) {
                return this.setProperty("selectedItem", radiobuttons[i]);
            }
        }
        return this.setProperty("selectedItem", null);
    };
    
    var lastSelected;
    this.$propHandlers["selectedItem"] = function(rb) {
        if (lastSelected)
            lastSelected.$uncheck();
        if (!rb)
            return;
            
        rb.$check();
        lastSelected = rb;
        
        for (var i = 0; i < radiobuttons.length; i++)
            radiobuttons[i].setProperty("selectedItem", rb);
    };

    this.$addRadio = function(rb) {
        var id = radiobuttons.push(rb) - 1;
        
        if (!rb.value)
            rb.setProperty("value", id);
        
        var _self = this;
        rb.addEventListener("prop.value", function(e) {
            if (this.selected)
                _self.setProperty("value", e.value);
            else if (_self.value == e.value)
                this.select();
        });
        
        if (this.value && rb.value == this.value)
            this.setProperty("selectedItem", rb);
        else if (rb.selected)
            this.setProperty("value", rb.value);
    };

    this.$removeRadio = function(rb) {
        radiobuttons.remove(rb);
        
        if (rb.value === rb.id)
            rb.setProperty("value", "");
        
        if (rb.selectedItem == rb)
            this.setProperty("value", null);
    };

    /**
     * Sets the current value of this element.
     */
    this.setValue = function(value) {
        this.setProperty("value", value);
    };
    
    /**
     * Returns the current value of this element.
     * @return {String} The current value.
     */
    this.getValue = function(){
        return this.value;
    };

    this.$draw = function(){
        this.$ext = this.$int = this.$pHtmlNode;
    };
};
apf.$group.prototype = new apf.GuiElement();

apf.aml.setElement("group", apf.$group);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Element allowing data synchronization between multiple clients using the same
 * application or application part. This element is designed as thecore of
 * collaborative application logic for Ajax.org Platform. The children of this
 * element specify how the uniqueness of {@link term.datanode data nodes} is determined. By pointing
 * models to this element, all changes to their data will be streamed through
 * this element to all listening client over a choosen protocol.
 * Example:
 * This example shows a small application which is editable by all clients that
 * have started it. Any change to the data is synchronized to all participants.
 * <code>
 *  <a:teleport>
 *      <a:xmpp id="myXMPP"
 *        url = "http://ajax.org:5280/http-bind"
 *        model = "mdlRoster"
 *        connection = "bosh"
 *  </a:teleport>
 *
 *  <a:remote transport="myXMPP" id="rmtPersons" />
 *
 *  <a:model id="mdlPersons" remote="rmtPersons">
 *      <persons>
 *          <person id="1">mike</person>
 *          <person id="2">ruben</person>
 *      </persons>
 *  </a:model>
 *
 *  <a:list id="lstPersons" model="mdlPersons" width="200" height="100">
 *      <a:bindings>
 *          <a:each match="[person]" />
 *          <a:caption match="[text()]" />
 *          <a:icon value="icoUsers.gif" />
 *      </a:bindings>
 *  </a:list>
 *
 *  <a:button action="remove" target="lstPersons">Remove</a:button>
 *  <a:button action="rename" target="lstPersons">Rename</a:button>
 *
 *  <a:button onclick="myXMPP.connect('testuser@ajax.org', 'testpass')">
 *      Login
 *  </a:button>
 * </code>
 * Remarks:
 * Although locking is solved in smartbindings it is directly connected
 * to remote databindings. When multiple people are working within the same
 * application it's important to have a system that prevents corruption of data
 * and data loss by either user overwriting records edited during the same period.
 * Ajax.org Platform has built in support for optimistic and pessimistic locking
 * in smartbindings. For more information please see {@link term.locking}.
 *
 * Advanced:
 * There is a very small theoretical risk that a user initiates and finishes an
 * action during the latency period of the rdb communication. Usually this
 * latency is no more than 100 to 300ms which is near impossible for such action
 * to be performed. Therefor this is deemed acceptable.
 *
 * Working in a multi user environment usually implies that data has a high
 * probability of changing. This might become a problem when syncing offline
 * changes after several hours. This should be a consideration for the
 * application architect.
 *
 * Another concern for offline use is the offline messaging feature of certain
 * collaborative protocols (i.e. xmpp). In many cases offline rdb messages should
 * not be stored after the user has been offline for longer then a certain time.
 * For instance 10 minutes. An accumulation of change messages would create a
 * serious scaling problem and is not preferred. apf.offline has built in support
 * for this type of timeout. By setting the rdb-timeout attribute it is aware
 * of when the server has timed out. When this timeout is reached the application
 * will reload all its data from the server and discard all offline rdb
 * messages before reconnecting to the server.
 *
 * @attribute {String} transport the name of the teleport element that provides a
 * bidirectional connection to (a pool of) other clients.
 *
 * 
 *
 * @define remote
 * @allowchild unique, {any}
 *
 *
 * @define unique Element defining what is unique about a set of data elements.
 * This enables remote databindings to point to xml data in  the same way on all
 * clients. This way changes that happen to these elements are described
 * non-ambiguously. The tagName can be replaced by the tagName of the
 * {@link term.datanode data node} for which the uniqueness is specified.
 * Example:
 * This example shows a complex data set and a remote databinding that
 * specifies the uniqueness of all nodes concerned.
 * <code>
 *  <a:model id="mdlPersons" remote="rmtPersons">
 *      <universe>
 *          <galaxy name="milkyway">
 *              <planet id="ALS-3947">
 *                  <species>3564</species>
 *                  <species>8104</species>
 *              </planet>
 *              <planet id="Earth">
 *                  <person number="802354897">Mike</person>
 *                  <person number="836114798">Rik</person>
 *              </planet>
 *          </galaxy>
 *      </universe>
 *  </a:model>
 *
 *  <a:remote transport="myXMPP" id="rmtPersons" />
 * </code>
 * @attribute {String} transport   ID of a Teleport element that is able to serve
 *                                 as a transport for RDB message like xmpp
 */
/**
 * @author      Mike de Boer (mike AT ajax DOT org)
 * @version     %I%, %G%
 * @since       3.0
 *
 * @default_private
 * @constructor
 *
 * @todo Think about wrapping multiple messages in a single call
 * @todo Make RDB support different encoding protocols (think REX)
 */
apf.remote = function(struct, tagName) {
    this.$init(tagName || "remote", apf.NODE_HIDDEN, struct);

//    this.lookup = {};
//    this.select = [];
    this.$sessions = {};
    this.rdbQueue = {};
    this.queueTimer = null;
    this.pendingTerminations = {};
    this.offlineQueue = [];
};

apf.remote.SESSION_INITED = 0x0001; //Session has not started yet.
apf.remote.SESSION_STARTED = 0x0002; //Session is started
apf.remote.SESSION_TERMINATED = 0x0004; //Session is terminated

(function(){
    

    this.logprefix = "";
    if (!apf.isO3) {
        this.log = function(msg) {
            apf.console.log(msg);
        }
    }

    //1 = force no bind rule, 2 = force bind rule
    this.$attrExcludePropBind = apf.extend({
        match: 1
    }, this.$attrExcludePropBind);

    this.$supportedProperties.push("transport");

    /* @todo move this to the rdb-xmpp transport layer
    function checkProtocol(uri) {
        if (uri.indexOf("rdb__") === 0)
            return "rdb:" + uri.substr(3).replace(/_/g, "/");
        return uri;
    } */

    this.$propHandlers["transport"] = function(value) {
        this.transport = typeof value == "object" ? value : self[this["transport"]];

        

        var _self = this;
        this.transport.addEventListener("connect", function() {
            var uri, oSession;
            for (uri in _self.$sessions) {
                oSession = _self.$sessions[uri];
                if (oSession.state == apf.remote.SESSION_STARTED)
                    continue;

                this.join(uri, function(uri, iTime) {
                    //_self.$startSession(uri, iTime);
                });
            }
        });

        this.transport.addEventListener("disconnect", function() {
            var uri, oSession;
            for (uri in _self.$sessions) {
                oSession = _self.$sessions[uri];
                oSession.state = apf.remote.SESSION_TERMINATED;
            }
        });

        this.transport.addEventListener("update", function(e) {
            _self.$update(e);
        });

        this.transport.addEventListener("join", function(e) {
            if (!e.uri)
                return;

            var uri = e.uri,
                oSession = _self.$sessions[e.uri];
            //if document isn't passed this must be a join request from a peer
            if (!e.document) {
                

                return _self.dispatchEvent("joinrequest", e);
            }

            //Create sesssion if it doesn't exist
            if (!oSession)
                oSession = _self.createSession(uri, null, null, e.document, e.basetime);
            else {
                oSession.model.load(e.document);
                _self.$startSession(uri, e.basetime);
            }
        });

        this.transport.addEventListener("leave", function(e) {
            _self.endSession(e.uri);
        });
    };

    this.$update = function(e) {
        var sData = e.message.args ? [e.message] : e.message;
        var oData = typeof sData == "string"
                ? apf.unserialize(sData)
                : sData;
        var oSession = this.$sessions[e.uri];
        var i = 0;
        var l = oData.length;

        for (; i < l; i++)
            this.$receiveChange(oData[i], oSession, e.annotator);
    };

    this.clear = function(){
        this.$sessions = {};
    };

    /**
     * Create a new RDB session based on a URI.
     * @param uri
     * @param model
     * @param xpath
     */
    this.createSession = function(uri, model, xpath, doc, iTime) {
        this.log && this.log(this.logprefix + "Creating session for " + uri);

        if (!model)
            model = this.dispatchEvent("modelfind", {uri: uri});
        if (model) {
            delete model.src;
            //@todo if this model is in a session stop that session
        }
        else
            model = new apf.model(); //apf.nameserver.register("model", id, );

        model.setProperty("remote", this);
        model.rdb = this;
        model.src = uri;

        var oSession = this.$addSession(uri, model, xpath);

        //We received the document and load it
        if (doc) {
            model.load(doc);
            this.$startSession(uri, iTime);
        }
        //We did not receive a document and will issue a join request to the server
        else {
            //If the transport is already connected, let
            if (this.transport && this.transport.isConnected()) {
                var _self = this;
                this.transport.join(uri, function(uri, iTime) {
                    _self.$startSession(uri, iTime);
                });
            }
        }

        return oSession;
    };

    /**
     * Terminate an RDB session based on a URI.
     * @param uri
     */
    this.endSession = function(uri) {
        if (!this.$sessions || !this.$sessions[uri])
            return;

        var oSession = this.$sessions[uri];
        if (this.transport && this.transport.isConnected() && oSession.state != apf.remote.SESSION_TERMINATED)
            this.transport.leave(uri);

        oSession.state = apf.remote.SESSION_TERMINATED;

        delete this.$sessions[uri];
    };

    this.$addSession = function(uri, model, xpath) {
        delete this.$sessions[uri];

        return this.$sessions[uri] = {
            uri: uri,
            model: model,
            xpath: xpath,
            state: apf.remote.SESSION_INITED
        };
    };

    this.$startSession = function(uri, basetime) {
        var oSession = this.$sessions[uri];

        if (!oSession) {
            
            return false;
        }

        oSession.state = apf.remote.SESSION_STARTED;
        if (basetime && !oSession.basetime)
            oSession.basetime = basetime;

        
    };

    this.$queueMessage = function(args, model, qHost) {
        if (!qHost.rdbQueue)
            qHost.rdbQueue = {};

        var uri = model.src;
        var oSession = this.$sessions[uri];

        

        if (!qHost.rdbQueue[uri]) {
            qHost.rdbQueue[uri] = [];
            qHost.rdbModel = model;
        }

        for (var node, i = 0, l = args.length; i < l; ++i) {
            if ((node = args[i]) && node.nodeType) {
                //@todo some changes should not be sent to the server
                if (args[0] == "setAttribute" && args[2] == "level"
                  && args[1] == args[1].ownerDocument.documentElement)
                    return false; //@todo refactor and make configurable

                args[i] = this.xmlToXpath(args[i], model.data);
            }
            else if (node && node.dataType == apf.ARRAY) {
                for (var j = 0; j < node.length; j++) {
                    if (node[j] && node[j].nodeType)
                        node[j] = this.xmlToXpath(node[j], model.data);
                }
            }
        }

        qHost.rdbQueue[uri].push({
            uri: uri,
            args: args,
            currdelta: (new Date()).toISOString() - oSession.basetime
        });
    };

    this.$processQueue = function(qHost) {
        if (qHost === this)
            clearTimeout(this.queueTimer);
        if (apf.xmldb.disableRDB)
            return;

        var list;
        for (var uri in qHost.rdbQueue) {
            if (!(list = qHost.rdbQueue[uri]).length)
                continue;

            

            if (this.transport)
                this.transport.sendUpdate(uri, JSON.stringify(list));

            this.dispatchEvent("rdbsend", {
                uri: uri,
                message: list
            });
        }

        qHost.rdbQueue = {};
    };

    this.$receiveChange = function(oMessage, oSession, sAnnotator) {
        //if (apf.xmldb.disableRDB) {
            this.log && this.log(this.logprefix + "Receiving change. disableRDB=" + apf.xmldb.disableRDB);
            //return;
        //}

        

        if (!oSession && oMessage.uri)
            oSession = this.$sessions[oMessage.uri];

        if (!oSession) {
            
            return;
        }

        //if (oMessage.timestamp < this.discardBefore) //@todo discardBefore
            //return;

        var model = oSession.model;
        if (!model) {
            
            return;
        }
        if (!model.$at)
            model.$at = apf.window.$at; //@todo find better solution to the case of a missing ActionTracker...

        var oError, xmlNode, disableRDB = apf.xmldb.disableRDB;
        apf.xmldb.disableRDB = 2; //Feedback prevention

        // Correct timestamp with the session basetime
        var time = oSession.basetime + parseInt(oMessage.currdelta, 10);

        

        // Undo all items until state is equal to when message was executed on original client.
        var aUndos = []; //model.$at.getDone(time),
        var i = 0;
        var l = aUndos.length;
        if (l) {
            for (; i < l; ++i)
                aUndos[i].$dontapply = true;
            model.$at.undo(l);
        }

        //Fetch node based on their xpath
        var q = oMessage.args.slice();
        var xpath = q[1];
        xmlNode = q[1] = this.xpathToXml(xpath, model.data);
        if (xmlNode) {
            var action = q.shift();

            if (action == "addChildNode")
                q[3] = this.xpathToXml(q[3], model.data);
            else if (action == "appendChild") {
                q[1] = typeof q[1] == "string" ? apf.getXml(q[1]) : q[1];
                q[2] = q[2] ? this.xpathToXml(q[2], model.data) : null;
            }
            else if (action == "moveNode") {
                q[1] = this.xpathToXml(q[1], model.data);
                q[2] = q[2] ? this.xpathToXml(q[2], model.data) : null;
            }
            else if (action == "replaceNode") {
                q[0] = typeof q[1] == "string" ? apf.getXml(q[1]) : q[1];
                q[1] = xmlNode;
            }
            else if (action == "removeNodeList") {
                var arr = q[0];
                for (var i = 0; i < arr.length; i++) {
                    arr[i] = this.xpathToXml(arr[i], model.data);
                }
            }
            else if (action == "setValueByXpath") {}

            // pass the action to the actiontracker to execute it
            model.$at.execute({
                action: action,
                args: q,
                annotator: sAnnotator,
                message: oMessage,
                rdb: true
            });

            this.dispatchEvent("change", {
                uri: oMessage.uri,
                model: model,
                xmlNode: xmlNode,
                message: oMessage
            });
        }
        

        if (l) {
            model.$at.redo(l);
            for (i = 0; i < l; ++i)
                delete aUndos[i].$dontapply;
        }

        apf.xmldb.disableRDB = disableRDB;

        if (oError) {
            apf.console.error(this.logprefix + oError.message)
        }
    };

    this.xmlToXpath = apf.xmlToXpath;
    this.xpathToXml = apf.xpathToXml;

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        

        
    });

    this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
        for (var i = 0, l = this.$sessions.length; i < l; ++i)
            this.endSession(this.$sessions[i].uri);
    });
}).call(apf.remote.prototype = new apf.AmlElement());

apf.aml.setElement("remote", apf.remote);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Baseclass for rpc in teleport. Modules are available for
 * SOAP, XML-RPC, CGI, JSON-RPC and several proprietary protocols.
 *
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" 
 *    protocol = "soap" 
 *    url = "http://example.com/show-product.php" 
 *    soap-prefix = "m" 
 *    soap-xmlns = "http://example.com">
 *      <a:method 
 *        name = "searchProduct" 
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method 
 *        name = "loadProduct">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 *
 * Example:
 * This example shows an rpc element using the xmlrpc protocol. It contains
 * two methods which can be called. The return of the first method is handled
 * by a javascript function called processSearch.
 * <code>
 *  <a:rpc 
 *    id = "flickr" 
 *    protocol = "xmlrpc" 
 *    url = "http://www.flickr.com/services/xmlrpc/"> 
 *      <a:method 
 *        name = "search" 
 *        receive = "flickrResult" 
 *        method-name = "flickr.photos.search" />
 *  </a:rpc>
 *  
 *  <a:script>//<!-- 
 *      //This function is called when the search returns
 *      function flickrResult(data, state, extra) {
 *          alert(data)
 *      };
 *      
 *      //Execute a search for the flowers keyword
 *      flickr.search({
 *          api_key  : '5ab84bdb606e86015a15a45ffe8d022b',
 *          text     : "flowers", 
 *          per_page : 4, 
 *          tab     : 1
 *      });
 *  //--></a:script>
 * </code>
 *
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" protocol="cgi">
 *      <a:method
 *        name = "searchProduct"
 *        url = "http://localhost/search.php"
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *  </a:rpc>
 *  
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *         alert(data)
 *      }
 *  
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 *
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" protocol="jsonrpc">
 *      <a:method 
 *        name = "searchProduct" 
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method 
 *        name = "loadProduct">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 *
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" protocol="cgi">
 *      <a:method
 *        name = "searchProduct"
 *        url = "http://example.com/search.php"
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method
 *        name = "loadProduct"
 *        url = "http://example.com/show-product.php">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 *
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" protocol="jsonrpc">
 *      <a:method 
 *        name = "searchProduct" 
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method 
 *        name = "loadProduct">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 *
 * @attribute {String}  protocol         the name of the plugin that is used
 *                                       to provide the messages.
 * @attribute {Boolean} [multicall]      whether the call is stacked until
 *                                       purge() is called.
 * @attribute {String}  [route-server]   the location of the proxy script that 
 *                                       allows for cross domain communication.
 * @attribute {String}  [http-method]    the http method used to send the data.
 *                                       This attribute is only used by the cgi protocol.
 *   Possible values:
 *   post   Used to store large chunks of data (on a resource).
 *   get    Used to retrieve data from a resource.
 *   delete Used to delete a resource.
 *   head   Returns only the headers.
 *   put    Used to store data at a resource.
 * @attribute {String}  [method-name]    the variable name used to sent the
 *                                       name of the method called to the
 *                                       server. This attribute is only used
 *                                       by the cgi protocol.
 * @attribute {String}  [soap-xmlns]     the url that uniquely identifies the
 *                                       xml namespace for the message. This
 *                                       attribute is only used by the soap
 *                                       protocol.
 * @attribute {String}  [soap-prefix]    the prefix that is paired with the
 *                                       message xml namespace. This attribute
 *                                       is only used by the soap protocol.
 * @define rpc
 * @allowchild method
 *
 * @constructor
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @author      Mike de Boer (mike AT javeline DOT com)
 * @version     %I%, %G%
 * @since       0.4
 * @default_private
 */
apf.rpc = function(struct, tagName) {
    this.$init(tagName || "rpc", apf.NODE_HIDDEN, struct);

    if (!this.supportMulticall)
        this.multicall = false;
    this.stack = {};
    this.urls = {};
    this.$methods = {};
};

(function(){
    this.useHTTP = true;
    this.namedArguments = false;

    this["route-server"] = apf.host + "/cgi-bin/rpcproxy.cgi";
    this.autoroute = false;

    this.$auth = false;

    this.$booleanProperties["multicall"] = true;

    this.$supportedProperties.push("protocol", "type", "multicall", "http-method");

    this.$propHandlers["route-server"] = function(value) {
        this.autoroute = value ? true : false;
    };

    //@todo change this to use prototype
    this.$propHandlers["protocol"] = function(value) {
        if (!value)
            return;
        
        if (!apf[value]) {
            
            return;
        }
        var _self = this;
        // use a timeout, so that these protocols may override default methods
        // inherited from http.js and the like.
        $setTimeout(function() {_self.implement(apf[value]);})
    };

    this.$propHandlers["type"] = function(value) {
        this.$useXml = (typeof value == "string" && value.toUpperCase() == "XML");
    };

    /**
     * Sets the callback for a method on this object.
     * Example:
     * <code>
     *  comm.setCallback("login", function(data, state, extra) {
     *      alert(data);
     *  });
     *
     *  comm.login(user, pass);
     * </code>
     * @param {String}   name the name of the method defined on this object.
     * @param {Function} func the function that is called when the rpc method returns.
     */
    this.setCallback = function(name, func) {
        
            
        this.$methods[name].callback = func;
    };

    /**
     * Sets the target url for a method on this object.
     * Example:
     * <code>
     *  comm.setCallback("login", "scripts/login.php");
     *
     *  comm.login(user, pass);
     * </code>
     * @param {String} name the name of the method defined on this object.
     * @param {String} url  the target url of method defined on this object.
     */
    this.setUrl = function(name, url) {
        

        this.$methods[name].setProperty("url", url);
    };

    this.$convertArgs = function(name, args) {
        if (!this.namedArguments)
            return Array.prototype.slice.call(args);

        var nodes = this.$methods[name].names;
        if (!nodes || !nodes.length)
            return {};

        var value, j, i, l, result = {};
        for (j = 0, i = 0, l = nodes.length; i < l; i++) {
            name = nodes[i].name;
            value = nodes[i].value;

            if (value) {
                value = apf.parseExpression(value);
            }
            else {
                value = args[j++];

                if (apf.isNot(value) && nodes[i]["default"])
                    value = apf.parseExpression(nodes[i]["default"]);
            }

            //Encode string optionally
            value = apf.isTrue(nodes[i].encoded)
                ? encodeURIComponent(value)
                : value;

            result[name] = value;
        }

        return result;
    };

    function getCallback(node) {
        var p, f;
        if (typeof node.callback == "string") {
            // support objects and namespaced functions
            p = node.callback.split("."),
            f = self[p.shift()];
            while (f && p.length)
                f = f[p.shift()];
        }
        else {
            f = node.callback;
        }
        return f || apf.K;
    }

    this.call = function(name, args, options) {
        var callback,
            node = this.$methods[name];
        
        if (typeof args[args.length - 1] == "function") {
            args = Array.prototype.slice.call(args); //@todo optimize?
            callback = args.pop();
        }
        else {
            callback = getCallback(node);
        }

        args = this.$convertArgs(name, args);

        // Set up multicall
        if (this.multicall) {
            if (!this.stack[this.url])
                this.stack[this.url] = this.getMulticallObject
                    ? this.getMulticallObject()
                    : [];

            this.getSingleCall(name, args, this.stack[this.url])
            return true;
        }

        // Get Data
        var _self = this,
            data = options && options.message
                ? options.message
                : this.createMessage(node["method-name"] || name, args); //function of module

        function pCallback(data, state, extra) {
            extra.data = data;

            if (state != apf.SUCCESS)
                callback.call(_self, null, state, extra);
            else if (_self.isValid && !_self.isValid(extra))
                callback.call(_self, null, apf.ERROR, extra);
            else
                callback.call(_self, _self.unserialize(extra.data), state, extra);
        }

        // Send the request
        var auth,
            url = apf.getAbsolutePath(this.baseurl || apf.config.baseurl, this.url),
            o = apf.extend({
                callback: pCallback,
                async: node.async,
                userdata: node.userdata,
                nocache: (this.nocache === false) ? false : true,
                data: data,
                useXML: this.$useXml || node.type == "xml",
                caching: node.caching,
                ignoreOffline: node["ignore-offline"],
                withCredentials: node.withCredentials === false ? false : true
            }, options);

        
        //@todo this shouldn't be in here
        if (node.auth && this.$auth) {
            if (auth = this.$auth.$credentials) {
                o.username = auth.username;
                o.password = auth.password;
            }
            else {
                return this.$auth.authRequired(function() {
                    auth = _self.$auth.$credentials
                    o.username = auth.username;
                    o.password = auth.password;
                    _self.$get(url, o);
                });
            }
        }
        

        return this.$get(url, o);
    };

    /**
     * Purge multicalled requests
     */
    this.purge = function(callback, userdata, async, extradata) {
        

        // Get Data
        var data = this.createMessage("multicall", [this.stack[this.url]]), //function of module
            url = apf.getAbsolutePath(this.baseurl || apf.config.baseurl, this.url);
        if (extradata) {
            for (var vars = [], i = 0; i < extradata.length; i++) {
                vars.push(encodeURIComponent(extradata[i][0]) + "="
                    + encodeURIComponent(extradata[i][1] || ""))
            }
            url = url + (url.match(/\?/) ? "&" : "?") + vars.join("&");
        }

        var info = this.$get(url, {
            callback: callback,
            async: async,
            userdata: userdata,
            nocache: true,
            data: data,
            useXML: this.$useXml
        });

        this.stack[this.url] = this.getMulticallObject
            ? this.getMulticallObject()
            : [];

        //return info[1];
    };

    this.revert = function(modConst) {
        this.stack[modConst.url] = this.getMulticallObject
            ? this.getMulticallObject()
            : [];
    };

    this.getStackLength = function(){
        return this.stack[this.url] ? this.stack[this.url].length : 0;
    };

    /**
     * Loads aml definition
     */
    this.$addMethod = function(amlNode) {
        if (amlNode.localName != "method"){
            
            return false;
        }
        
        var name = amlNode.name,
            cb = amlNode.receive || this.receive,
            i = 0,
            l = amlNode.childNodes.length,
            node;

        this[name] = function(){
            return this.call(name, arguments);
        };

        if (cb)
            amlNode.callback = cb;

        this.$methods[name] = amlNode;

        if (!amlNode.names)
            amlNode.names = [];
        for (; i < l; i++) {
            node = amlNode.childNodes[i];
            if (node.localName == "param" || node.localName == "variable") //@todo deprecate variable
                amlNode.names.push(node);
        }

        return true;
    };

    this.$removeMethod = function(amlNode) {
        var name = amlNode.name;
        delete this[name];
        delete this.$methods[name];
    };

    this.$setAuth = function(amlNode) {
        this.$auth = amlNode;
    };

    /*
    this.addEventListener("DOMNodeInserted", function(e) {
        var node = e.currentTarget;
        if (node.parentNode != this)
            return;

        this.register(node);
    });

    this.addEventListener("DOMNodeRemoved", function(e) {
        var node = e.currentTarget;
        // we support two levels deep:
        if (!(node.parentNode == this || node.parentNode.parentNode == this))
            return;

        this.unregister(node);
    });*/
    
    
    this.exec = function(method, args, callback, options) {
        if (!options) options = {};

        //force multicall if needed;
        if (options.multicall)
            this.forceMulticall = true;
    
        //Set information later neeed
        
        
        var props = this.$methods[method];

        if (options.userdata)
            props.userdata = options.userdata;
    
        if (!this.multicall)
            props.callback = callback; //&& this[method].async
    
        //Call method
        var retvalue = this.call(method, args, options);
    
        if (this.multicall)
            return this.purge(callback, "&@^%!@"); //Warning!! @todo Make multicall work with offline
        else if (options.multicall) {
            this.forceMulticall = false;
            return this;
        }
    
        
    
        //Call callback for sync calls
        if (!this.multicall && !props.async && callback)
            callback(retvalue, apf.SUCCESS, {tpModule: this});
    };
    

    /*
     * Post a form with ajax
     *
     * @param form     form
     * @param function callback  Called when http result is received
     * /
     this.submitForm = function(form, callback, callName) {
         this.addMethod('postform', callback);
         this.urls['postform'] = form.action;
         var args = [];
         for (var i = 0; i < form.elements.length; i++) {
             var name = form.elements[i].name.split("[");
             for (var j = 0; j < name.length; j++) {
                 //Hmm problem with sequence of names... have to get that from the variable sequence...
             }
             args[] = form.elements[i].value;
         }

         this['postform'].apply(this, args);
     };
     */
}).call(apf.rpc.prototype = new apf.Teleport());

apf.config.$inheritProperties["baseurl"] = 1;

apf.aml.setElement("rpc", apf.rpc);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element loads JavaScript into the application.
 * 
 * #### Example
 *
 * ```xml
 *  <a:script src="code.js" />
 * ```
 *
 * #### Example
 *
 * ```xml
 *  <a:script>//<!-- 
 *      for (var i = 0; i < 2; i++) {
 *          alert(i);
 *      }
 *  //--></a:script>
 * ```
 *
 * @class apf.script
 * @inherits apf.AmlElement
 * @define script
 * @logic
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 */
/**
 * @attribute {String} src the location of the script file.
 *
 *
 */
apf.script = function(){
    this.$init("script", apf.NODE_HIDDEN);
};

(function(){
    this.$propHandlers["src"] = function(value) {
        if (!this.type)
            this.type = this.getAttribute("type");

        if (!this.type || this.type == "text/javascript") {
            if (apf.isOpera) {
                $setTimeout(function(){
                    apf.window.loadCodeFile(apf.hostPath
                        + value);
                }, 1000);
            }
            else {
                apf.window.loadCodeFile(apf.getAbsolutePath(apf.hostPath,
                    value));
            }
        }
        else {
            var _self = this;
            apf.ajax(value, {callback: function(data, state, extra) {
                if (state != apf.SUCCESS) {
                    return apf.console.warn("Could not load script " + value);
                }
                
                _self.$execute(data);
            }});
        }
    }
    
    this.$execute = function(code, e) {
        if (!this.type || this.type == "text/javascript") {
            apf.jsexec(code);
        }
        else if (this.type.indexOf("livemarkup") > -1
          || this.type.indexOf("lm") > -1) { //@todo this is wrong, it should start in code mode
            var func = apf.lm.compile(code, {event: true, parsecode: true, funcglobal: true, nostring: true});
            func(e || {});
        }
    }
    
    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget.nodeType == 3 || e.currentTarget.nodeType == 4) {
            this.$execute(e.currentTarget.nodeValue, apf.isIE && window.event);
        }
    });
    
    //@todo this should use text node insertion
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        var nodes = this.childNodes, s = [];
        for (var i = 0, l = nodes.length; i < l; i++) {
            s[s.length] = nodes[i].nodeValue;
        }
        
        var code = s.join("\n");
        
        this.$execute(code);
    });
}).call(apf.script.prototype = new apf.AmlElement());

apf.aml.setElement("script", apf.script);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



//@todo: fix the stuff with all the uppercase variable and function names...wazzup?

/**
 * @constructor
 * @private
 */
apf.scrollbar = function(struct, tagName) {
    this.$init(tagName || "scrollbar", apf.NODE_VISIBLE, struct);
};

(function(){
    this.realtime = true;
    this.visible = false;
    this.overflow = "scroll";
    this.position = 0;
    
    this.$visible = true;
    this.$scrollSizeValue = 0;
    this.$stepValue = 0.03;
    this.$bigStepValue = 0.1;
    this.$timer = null;
    this.$scrollSizeWait;
    this.$slideMaxSize;
    
    this.$booleanProperties = ["showonscroll"];

    this.addEventListener("focus", function(){
        this.$viewport.focus();
    });

    this.$propHandlers["showonscroll"] = function(value) {
        clearTimeout(this.$hideOnScrollTimer);
        
        if (value) {
            this.$ext.style.display = "none";
        }
        else {
            this.$ext.style.display = "block";
            this.show(); //Trigger positioning event
        }
    };

    this.$propHandlers["overflow"] = function(value) {
        if (this.showonscroll)
            return;
        
        if (value == "auto") {
            this.$ext.style.display = "none";
            this.$resize();
        }
        else if (value == "scroll") {
            this.setProperty("visible", true);
        }
    }
    
    this.$propHandlers["for"] = function(value) {
        if (value) {
            var amlNode = typeof value == "string" ? self[value] : value;
            if (!amlNode || !amlNode.$amlLoaded) {
                var _self = this;
                apf.queue.add("scrollbar" + this.$uniqueId, function(){
                    if (!amlNode) {
                        amlNode = typeof value == "string" ? self[value] : value;
                        
                        if (!amlNode) {
                            throw new Error(apf.formatErrorString(0, _self,
                               "Attaching scrollbar to element",
                               "Could not find element to attach scrollbar to: " + value));
                        }
                    }
                    _self.attach(amlNode);
                });
            }
            else
                this.attach(amlNode);
        }
    }
    
    this.addEventListener("prop.visible", function(e) {
        if (!this.$updating) {
            this.$visible = e.value;
        }
    });
    
    this.attach = function(viewport) {
        if (viewport.nodeFunc) {
            
            if (viewport.hasFeature(apf.__VIRTUALVIEWPORT__))
                viewport = viewport.$viewport;
            else
            
                viewport = new apf.ViewPortAml(viewport);
        }
        else if (viewport.style)
            viewport = new apf.ViewPortHtml(viewport);
        
        this.$attach(viewport);
    };
    
    /**
     * @todo detach
     */
    this.$attach = function(viewport) {
        if (!viewport)
            return apf.console.warn("Scrollbar could not connect to viewport");
        
        var _self = this;
        
        this.$viewport = viewport;
        
        if (this.$viewport.scrollbar != this) {
            this.$viewport.setScrollbar(this, function(e) {
                if (_self.$viewport != viewport)
                    return;
                 
                _self.$update();
                
                if (_self.showonscroll) { // && e.byUser) {
                    if (!_self.scrolling)
                        _self.$resize();
                
                    _self.scrolling = true;
                    
                    clearTimeout(_self.$hideOnScrollTimer);
                    if (_self.$hideOnScrollControl)
                        _self.$hideOnScrollControl.stop();
                    
                    apf.setOpacity(_self.$ext, 1);
                    !_self.visible ? _self.show() : _self.$ext.style.display = "block";
                    _self.$update();
                    
                    _self.$hideOnScrollTimer = _self.animHideScrollbar(500, function(){
                        _self.scrolling = false;
                    });
                }
            });
        }

        this.$recalc();
        this.$update();
        
        return this;
    };
    
    this.$resize = function(){
        if (!this.$viewport || !this.$viewport.isVisible())
            return;
            
        this.$recalc();
        this.$update();
        
        if (!this.$viewport.virtual)
            return;
        
        this.setScrollPosition(this.position, true);
    }
    
    this.$recalc = function(){
        this.$viewheight = this.$viewport.getHeight();
        this.$scrollSizeheight = this.$viewheight;
        this.$scrollSizeWait = 0;//(this.$host.len * COLS)/2;
        this.$stepValue = (this.$viewheight / this.$scrollSizeheight) / 20;
        this.$bigStepValue = this.$stepValue * 3;
        this.$slideMaxSize = this.$caret.parentNode[this.$offsetSize] 
            - (this.$btnDown ? this.$btnDown[this.$offsetSize] : 0)
            - (this.$btnUp ? this.$btnUp[this.$offsetSize] : 0);
    }
    
    //@todo this function is called way too many times
    this.$update = function(){
        // Commented this out because otherwise a tree expansion wouldn't
        // show the scrollbar again
        //if (this.animating || !this.$visible)
        //    return;

        if (this.showonscroll && !this.$ext.offsetHeight)
            return;

        var viewport = this.$viewport;
        if (!viewport || !viewport.isVisible())
            return;

        this.$updating = true;
        
        //Disable scrollbar
        var vp = viewport.getHeight();
        var sz = viewport.getScrollHeight();

        if (vp >= sz) {
            if (this.overflow == "scroll") {
                this.$caret.style.display = "none";
                this.disable();
            }
            else if (this.visible) {
                this.hide();
                
                //this.$ext.style.display = "none";
            }
            //if (this.id == "sbtest") console.log(vp + ":" + sz);
            //oHtml.style.overflowY = "visible";
        }
        //Enable scrollbar
        else {
            if (this.overflow == "scroll") {
                this.$caret.style.display = "block";
                this.enable();
            }
            else if (!this.visible) {
                this.show();
                //this.$ext.style.display = "block";
                //this.$caret.style.display = "block";
            }
            
            if (!this.$slideMaxSize)
                this.$recalc();
            if (!this.$slideMaxSize)
                return;
            
            //oHtml.style.overflowY = "scroll";
            
            //Set scroll size
            this.$caret.style[this.$size] = (Math.max(5, (vp / sz
                * this.$slideMaxSize)) - apf[this.$getDiff](this.$caret)) + "px";
            //if (this.$caret.offsetHeight - 4 == this.$slideMaxSize) 
                //this.$ext.style.display = "none";

            this.position = viewport.getScrollTop() / (sz - vp);

            var bUpHeight = this.$btnUp ? this.$btnUp[this.$offsetSize] : 0;
            this.$caret.style[this.$pos] = (bUpHeight + (apf[this.$getInner](this.$caret.parentNode)
            - (bUpHeight * 2) - this.$caret[this.$offsetSize]) * this.position) + "px";
        }
        
        this.$updating = false;
    }
    
    this.setScrollPosition = function(position, preventEvent) {
        if (position == NaN) {
            
            return;
        }
        
        if (position > 1) 
            position = 1;
        if (position < 0) 
            position = 0;
        
        this.position = position;
        
        // Set the caret position
        var bUpHeight = this.$btnUp ? this.$btnUp[this.$offsetSize] : 0;
        this.$caret.style[this.$pos] = (bUpHeight + (apf[this.$getInner](this.$caret.parentNode)
            - (bUpHeight * 2) - this.$caret[this.$offsetSize]) * this.position) + "px";

        // Don't signal anything when animating or when not visible
        if (this.animating || !this.$visible) 
            return;

        var vp = this.$viewport;
        var to = (vp.getScrollHeight() - vp.getHeight()) * position;

        vp.setScrollTop(to, preventEvent);
    }
    
    this.animShowScrollbar = function(timeout, cb) {
        var _self = this;
        return setTimeout(function(){
            _self.$ext.style.display = "block";
            
            if (_self.$showOnScrollControl
              && _self.$showOnScrollControl.state == apf.tween.RUNNING)
                return;
            
            if (_self.$hideOnScrollControl)
                _self.$hideOnScrollControl.stop();
            
            _self.$resize();

            apf.tween.single(_self.$ext, {
                control: _self.$hideOnScrollControl = {},
                type: "fade",
                from: 0,
                to: 1,
                onfinish: function(){
                    cb && cb();
                }
            });
        }, timeout)
    }
    
    this.animHideScrollbar = function(timeout, cb) {
        var _self = this;
        return setTimeout(function(){
            if (_self.$hideOnScrollControl
              && _self.$hideOnScrollControl.state == apf.tween.RUNNING)
                return;
            
            if (_self.$showOnScrollControl)
                _self.$showOnScrollControl.stop();
            apf.tween.single(_self.$ext, {
                control: _self.$hideOnScrollControl = {},
                type: "fade",
                from: 1,
                to: 0,
                steps: 20,
                onfinish: function(){
                    _self.$ext.style.display = "none";
                    apf.setOpacity(_self.$ext, 1);
                    
                    cb && cb();
                }
            });
        }, timeout)
    }
    
    this.scrollUp = function (v) {
        if (v > this.$caret[this.$offsetPos]) 
            return this.$ext.onmouseup();
        this.setScrollPosition(this.position + this.$bigStepValue);
        
        if (this.$slideFast) {
            this.$slideFast.style[this.$size] = Math.max(1, this.$caret[this.$offsetPos]
                - this.$btnUp[this.$offsetSize]) + "px";
            this.$slideFast.style[this.$pos] = this.$btnUp[this.$offsetSize] + "px";
        }
    }
    
    this.scrollDown = function (v) {
        if (v < this.$caret[this.$offsetPos] + this.$caret[this.$offsetSize]) 
            return this.$ext.onmouseup();
        this.setScrollPosition(this.position + this.$bigStepValue);
        
        if (this.$slideFast) {
            this.$slideFast.style[this.$pos] = (this.$caret[this.$offsetPos] + this.$caret[this.$offsetSize]) + "px";
            this.$slideFast.style[this.$size] = Math.max(1, apf[this.$getInner](this.$caret.parentNode) - this.$slideFast[this.$offsetPos]
                - this.$btnUp[this.$offsetSize]) + "px";
        }
    }
    
    this.$draw = function(){
        //Build Skin
        this.$getNewContext("main");
        this.$ext = this.$getExternal();
        //this.$ext.style.display = "none";

        this.$caret = this.$getLayoutNode("main", "indicator", this.$ext);
        this.$slideFast = this.$getLayoutNode("main", "slidefast", this.$ext);
        this.$btnUp = this.$getLayoutNode("main", "btnup",     this.$ext)
        this.$btnDown = this.$getLayoutNode("main", "btndown",   this.$ext);

        this.horizontal = apf.isTrue(this.$getOption("main", "horizontal"));
        
        this.$windowSize = this.horizontal ? "getWindowWidth" : "getWindowHeight";
        this.$offsetSize = this.horizontal ? "offsetWidth" : "offsetHeight";
        this.$size = this.horizontal ? "width" : "height";
        this.$offsetPos = this.horizontal ? "offsetLeft" : "offsetTop";
        this.$pos = this.horizontal ? "left" : "top";
        this.$scrollSize = this.horizontal ? "scrollWidth" : "scrollHeight";
        this.$scrollPos = this.horizontal ? "scrollLeft" : "scrollTop";
        this.$getDiff = this.horizontal ? "getWidthDiff" : "getHeightDiff";
        this.$getInner = this.horizontal ? "getHtmlInnerWidth" : "getHtmlInnerHeight"; 
        this.$eventDir = this.horizontal 
            ? (apf.isIE || apf.isWebkit ? "offsetX" : "layerX") 
            : (apf.isIE || apf.isWebkit ? "offsetY" : "layerY");
        this.$clientDir = this.horizontal ? "clientX" : "clientY";
        this.$posIndex = this.horizontal ? 0 : 1;
        
        this.$startPos = false;
        
        this.$caret.ondragstart = function(){
            return false
        };

        var _self = this;
        if (this.$btnUp) {
            this.$btnUp.onmousedown = function(e) {
                if (_self.disabled)
                    return;
                
                if (!e) 
                    e = event;
                this.className = "btnup btnupdown";
                clearTimeout(_self.$timer);
                
                _self.setScrollPosition(_self.position - _self.$stepValue);
                apf.stopPropagation(e);
                
                //apf.window.$mousedown(e);
                
                _self.$timer = $setTimeout(function(){
                    _self.$timer = setInterval(function(){
                        _self.setScrollPosition(_self.position - _self.$stepValue);
                    }, 20);
                }, 300);
            };
            
            this.$btnUp.onmouseout = this.$btnUp.onmouseup = function(){
                if (_self.disabled)
                    return;
                    
                this.className = "btnup";
                clearInterval(_self.$timer);
            };
        }
        
        if (this.$btnDown) {
            this.$btnDown.onmousedown = function(e) {
                if (_self.disabled)
                    return;
                    
                if (!e) 
                    e = event;
                this.className = "btndown btndowndown";
                clearTimeout(_self.$timer);
                
                _self.setScrollPosition(_self.position + _self.$stepValue)
                apf.stopPropagation(e);
                
                //apf.window.$mousedown(e);
                
                _self.$timer = $setTimeout(function(){
                    _self.$timer = setInterval(function(){
                        _self.setScrollPosition(_self.position + _self.$stepValue)
                    }, 20);
                }, 300);
            };
        
            this.$btnDown.onmouseout = this.$btnDown.onmouseup = function(){
                if (_self.disabled)
                    return;
                    
                this.className = "btndown";
                clearInterval(_self.$timer);
            };
        }
        
        this.$caret.onmousedown = function(e) {
            if (_self.disabled)
                return;

            if (!e) 
                e = event;
            
            var tgt = e.target || e.srcElement;
            var pos = tgt != this
                ? [tgt.offsetLeft, tgt.offsetTop] //Could be improved
                : [0, 0];
            
            var relDelta = e[_self.$eventDir] + pos[_self.$posIndex];
            _self.$startPos = relDelta + 
                (_self.$btnUp ? _self.$btnUp[_self.$offsetSize] : 0);

            if (this.setCapture)
                this.setCapture();

            _self.$setStyleClass(_self.$ext, _self.$baseCSSname + "Down");
            _self.dispatchEvent("mousedown", {});
            
            _self.dragging = true;

            document.onmousemove = function(e) {
                if (!e) 
                    e = event;
                //if(e.button != 1) return _self.onmouseup();
                if (_self.$startPos === false) 
                    return false;

                var bUpHeight = _self.$btnUp ? _self.$btnUp[_self.$offsetSize] : 0;
                var next = bUpHeight + (e[_self.$clientDir] - _self.$startPos
                    + (apf.isWebkit ? document.body : document.documentElement)[_self.$scrollPos]
                    - apf.getAbsolutePosition(_self.$caret.parentNode)[_self.horizontal ? 0 : 1]); // - 2
                var min = bUpHeight;
                if (next < min) 
                    next = min;
                var max = (apf[_self.$getInner](_self.$caret.parentNode)
                    - bUpHeight - _self.$caret[_self.$offsetSize]);
                if (next > max) 
                    next = max;
                //_self.$caret.style.top = next + "px"

                _self.setScrollPosition((next - min) / (max - min));
            };
            
            document.onmouseup = function(){
                _self.$startPos = false;
                if (!_self.realtime)
                    _self.setScrollPosition(_self.position);
                
                if (this.releaseCapture)
                    this.releaseCapture();
                
                _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Down"]);
                _self.dispatchEvent("mouseup", {});
                
                _self.dragging = false;
                
                document.onmouseup = 
                document.onmousemove = null;
            };
    
            apf.stopPropagation(e);
            //apf.window.$mousedown(e);
            
            return false;
        };
        
        this.$ext.onmousedown = function(e) {
            if (_self.disabled)
                return;
            if (!e) 
                e = event;
            clearInterval(_self.$timer);
            var offset;
            if (e[_self.$eventDir] > _self.$caret[_self.$offsetPos] + _self.$caret[_self.$offsetSize]) {
                _self.setScrollPosition(_self.position + _self.$bigStepValue);
                
                if (_self.$slideFast) {
                    _self.$slideFast.style.display = "block";
                    _self.$slideFast.style[_self.$pos] = (_self.$caret[_self.$offsetPos]
                        + _self.$caret[_self.$offsetSize]) + "px";
                    _self.$slideFast.style[_self.$size] = (apf[_self.$getInner](_self.$caret.parentNode) - _self.$slideFast[_self.$offsetPos]
                        - _self.$btnUp[_self.$offsetSize]) + "px";
                }
                
                offset = e[_self.$eventDir];
                _self.$timer = $setTimeout(function(){
                    _self.$timer = setInterval(function(){
                        _self.scrollDown(offset, null, null, true);
                    }, 20);
                }, 300);
            }
            else if (e[_self.$eventDir] < _self.$caret[_self.$offsetPos]) {
                _self.setScrollPosition(_self.position - _self.$bigStepValue);
                
                if (_self.$slideFast) {
                    _self.$slideFast.style.display = "block";
                    _self.$slideFast.style[_self.$pos] = _self.$btnUp[_self.$offsetSize] + "px";
                    _self.$slideFast.style[_self.$size] = (_self.$caret[_self.$offsetPos] - _self.$btnUp[_self.$offsetSize]) + "px";
                }
                
                offset = e[_self.$eventDir];
                _self.$timer = $setTimeout(function(){
                    _self.$timer = setInterval(function(){
                        _self.scrollUp(offset, null, null, true);
                    }, 20);
                }, 300);
            }
        };
        
        this.$ext.onmouseup = function(){
            if (_self.disabled)
                return;
                
            clearInterval(_self.$timer);
            if (!_self.realtime)
                _self.setScrollPosition(_self.position);
            if (_self.$slideFast)
                _self.$slideFast.style.display = "none";
        };

        this.$ext.onmouseover = function(e) {
            _self.dispatchEvent("mouseover", {htmlEvent : e || event});
        };

        this.$ext.onmouseout = function(e) {
            _self.dispatchEvent("mouseout", {htmlEvent : e || event});
        };
    }
    
    this.$loadAml = function(){
        if (this.overflow == "scroll")
            this.disable();
        else {
            this.$caret.style.display = "block";
            this.enable();
        }
        
        this.addEventListener("resize", this.$resize);
        this.$update();
    }
}).call(apf.scrollbar.prototype = new apf.Presentation());
apf.aml.setElement("scrollbar", apf.scrollbar);

apf.GuiElement.propHandlers["scrollbar"] = function(value) {
    if (this.$sharedScrollbar == undefined) {
        var values = value.split(" ");
        var name = values[0];
        var top = values[1] || 0;
        var right = values[2] || 0;
        var bottom = values[3] || 0;
        
        var _self = this;
        this.$sharedScrollbar = self[name] || false;
        if (!this.$sharedScrollbar)
            return;
        
        function hasOnScroll(){
            return sb && apf.isTrue(sb.getAttribute("showonscroll"));
        }
        
        var oHtml = this.$container || this.$int || this.$ext, timer, sb;
        var mouseMove;
        apf.addListener(oHtml, "mousemove", mouseMove = function(e) {
            if (!_self.$sharedScrollbar)
                _self.$sharedScrollbar = self[name];
            
            sb = _self.$sharedScrollbar;
            
            if (!sb.$addedMouseOut) {
                apf.addListener(sb.$ext, "mouseout", function(e) {
                    if (!hasOnScroll())
                        return;

                    if (apf.findHost(e.fromElement) == sb && apf.findHost(e.toElement) != sb) {
                        clearTimeout(timer);
                        hideScrollbar();
                    }
                });
                sb.$addedMouseOut = true;
            }
            
            if (!sb.$viewport || sb.$viewport.amlNode != _self) {
                var pNode = (_self.$ext == oHtml ? _self.$ext.parentNode : _self.$ext);
                pNode.appendChild(sb.$ext);
                
                if (apf.getStyle(pNode, "position") == "static") 
                    pNode.style.position = "relative";
                    
                sb.setProperty("showonscroll", true);
                sb.$ext.style.display = "block";
                sb.setAttribute("top", top);
                sb.setAttribute("right", right);
                sb.setAttribute("bottom", bottom);
                sb.setAttribute("for", _self);
                sb.$ext.style.display = "none";
                sb.dragging = false;

                if (sb.$hideOnScrollControl)
                    sb.$hideOnScrollControl.stop();
            }
            
            if (hasOnScroll() && e) {
                clearTimeout(timer);
                
                var pos = apf.getAbsolutePosition(oHtml);
                var rightPos = oHtml.offsetWidth - (e.clientX - pos[0]);
                var show = rightPos < 25 && rightPos > right;
                if (show && sb.$ext.style.display == "none" 
                  || !show && sb.$ext.style.display == "block") {
                    if (show)
                        showScrollbar();
                    else
                        hideScrollbar();
                }
                else if (!show)
                    sb.showonscroll = true;
            }
        });
        
        this.$sharedScrollbarMove = mouseMove;
        
        function showScrollbar(){
            sb.setProperty("showonscroll", false);
            sb.$ext.style.display = "none";
            timer = sb.animShowScrollbar(200);
        }
        
        function hideScrollbar(timeout) {
            if (sb.scrolling)
                return;
            
            if (!sb.dragging)
                timer = sb.animHideScrollbar(timeout || 200, function(){
                    sb.setProperty("showonscroll", true);
                });
            else
                apf.addListener(document, "mouseup", function(e) {
                    var tgt = apf.findHost(e.target);
                    if (tgt == sb)
                        return;
                        
                    if (tgt == _self)
                        mouseMove(e);
                    else
                        hideScrollbar();
                    
                    apf.removeListener(document, "mouseup", arguments.callee);
                });
        }
        
        apf.addListener(oHtml, "mouseout", function(e) {
            if (!hasOnScroll())
                return;

            var el = apf.findHost(e.toElement || e.rangeParent);
            if (el != sb && el != sbShared.$viewport.amlNode) {
                clearTimeout(timer);
                hideScrollbar();
            }
        });
    }
};

apf.ViewPortAml = function(amlNode) {
    this.amlNode = amlNode;
    
    var _self = this;
    var update = function(){
        if (_self.scrollbar)
            _self.scrollbar.$update();
    };
    
    amlNode.addEventListener("resize", update);
    if (amlNode.hasFeature(apf.__DATABINDING__)) {
        amlNode.addEventListener("afterload", update);
        amlNode.addEventListener("xmlupdate", update);
    }
    
    amlNode.addEventListener("prop.value", update);
    
    if (amlNode.$isTreeArch) {
        amlNode.addEventListener("collapse", update);
        amlNode.addEventListener("expand", update);
    }
    
    amlNode.addEventListener("mousescroll", function(e) {
        _self.$mousescroll(e);
    });
    
    var htmlNode = _self.$getHtmlHost();
    apf.addListener(htmlNode, "scroll", function(){
        if (_self.scrollbar.animating || !_self.scrollbar.$visible) 
            return;
        
        _self.setScrollTop(this.scrollTop);
    });
    
    if ("HTML|BODY".indexOf(htmlNode.tagName) > -1) {
        var lastHeight = htmlNode.scrollHeight;
        setInterval(function(){
            if (lastHeight != htmlNode.scrollHeight) {
                lastHeight = htmlNode.scrollHeight;
                _self.scrollbar.$recalc();
                _self.scrollbar.$update();
            }
        }, 100);
    }
};

(function(){
    this.setScrollbar = function(scrollbar, onscroll) {
       this.scrollbar = scrollbar;
       
       this.amlNode.addEventListener("scroll", onscroll);
    }
    
    this.isVisible = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.offsetHeight || htmlNode.offsetWidth ? true : false;
    }
    
    this.focus = function(){
        if (this.amlNode.focus && this.amlNode.$isWindowContainer !== true)
            this.amlNode.focus();
    }
    
    this.getScrollTop = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.scrollTop;
    }
    
    this.getScrollLeft = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.scrollLeft;
    }
    
    this.getScrollHeight = function(){
        var htmlNode = this.$getHtmlHost();
        return (apf.isIE && htmlNode.lastChild 
            ? htmlNode.lastChild.offsetTop 
                + htmlNode.lastChild.offsetHeight
                + apf.getBox(apf.getStyle(htmlNode, "padding"))[2]
                + (parseInt(apf.getStyle(htmlNode, "marginBottom")) || 0)
            : htmlNode.scrollHeight);
    }
    
    this.getScrollWidth = function(){
        var htmlNode = this.$getHtmlHost();
        return (apf.isIE && htmlNode.lastChild 
            ? htmlNode.lastChild.offsetLeft 
                + htmlNode.lastChild.offsetWidth
                + apf.getBox(apf.getStyle(htmlNode, "padding"))[1]
                + (parseInt(apf.getStyle(htmlNode, "marginRight")) || 0)
            : htmlNode.scrollWidth);
    }
    
    this.getHeight = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.tagName == "HTML" || htmlNode.tagName == "BODY" 
            ? apf.getWindowHeight() 
            : apf.getHtmlInnerHeight(htmlNode);
    }
    
    this.getWidth = function(){
        var htmlNode = this.$getHtmlHost();
        return htmlNode.tagName == "HTML" || htmlNode.tagName == "BODY" 
            ? apf.getWindowHeight() 
            : apf.getHtmlInnerWidth(htmlNode);
    }
    
    this.setScrollTop = function(value, preventEvent, byUser) {
        var htmlNode = this.$getHtmlHost();
        htmlNode.scrollTop = value;
        
        if (!preventEvent) {
            this.amlNode.dispatchEvent("scroll", {
                direction: "vertical",
                byUser: byUser,
                viewport: this,
                scrollbar: this.scrollbar
            });
        }
    }
    
    this.setScrollLeft = function(value, preventEvent, byUser) {
        var htmlNode = this.$getHtmlHost();
        htmlNode.scrollLeft = value;
        
        if (!preventEvent) {
            this.amlNode.dispatchEvent("scroll", {
                direction: "horizontal",
                byUser: byUser,
                viewport: this,
                scrollbar: this.scrollbar
            });
        }
    }
    
    // *** Private *** //
    
    this.$getHtmlHost = function(){
        var htmlNode = this.amlNode.$int || this.amlNode.$container;
        return (htmlNode.tagName == "BODY" || htmlNode.tagName == "HTML" 
            ? (apf.isSafari || apf.isChrome ? document.body : htmlNode.parentNode) 
            : htmlNode);
    }

    this.$mousescroll = function(e) {
        if (this.scrollbar.horizontal)
            return;
        
        if (e.returnValue === false)
            return;
    
        var oHtml = this.$getHtmlHost();
    
        var sb = this.scrollbar;
        var div = this.getScrollHeight() - this.getHeight();
        if (div) {
            if (oHtml[sb.$scrollPos] == 0 && e.delta > 0) {
                if (this.$lastScrollState === 0)
                    return;
                setTimeout(function(){this.$lastScrollState = 0;}, 300);
            }
            else if (oHtml[sb.$scrollPos] == this.getScrollHeight() - oHtml[sb.$offsetSize] && e.delta < 0) {
                if (this.$lastScrollState === 1)
                    return;
                setTimeout(function(){this.$lastScrollState = 1;}, 300);
            }
            delete this.$lastScrollState;
            
            this.setScrollTop(this.getScrollTop()
                + -1 * e.delta * Math.min(45, this.getHeight()/10), false, true);
            
            e.preventDefault();
        }
    }
}).call(apf.ViewPortAml.prototype);

apf.ViewPortHtml = function(htmlNode) {
    // *** Private *** //
    
    this.$getHtmlHost = function(){
        return htmlNode;
    }
    
    // *** Init *** //
    
    var _self = this;
    
    htmlNode = (htmlNode.tagName == "BODY" || htmlNode.tagName == "HTML" 
        ? (apf.isSafari || apf.isChrome ? document.body : htmlNode.parentNode) 
        : htmlNode);

    apf.addEventListener("mousescroll", function(e) {
        if (htmlNode == e.target 
          || (htmlNode == document.documentElement && e.target == document.body))
            _self.$mousescroll(e);
    })
    
    apf.addListener(htmlNode, "scroll", function(){
        _self.setScrollTop(this.scrollTop);
    });
    
    if ("HTML|BODY".indexOf(htmlNode.tagName) > -1) {
        var lastHeight = htmlNode.scrollHeight;
        setInterval(function(){
            if (lastHeight != htmlNode.scrollHeight) {
                lastHeight = htmlNode.scrollHeight;
                _self.scrollbar.$recalc();
                _self.scrollbar.$update();
            }
        }, 100);
    }
    
    this.amlNode = new apf.Class().$init();
}

apf.ViewPortHtml.prototype = apf.ViewPortAml.prototype;





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * This element specifies the skin of an application.
 *
 * For Cloud9, the skin is provided for you, and thus, you generally won't need
 * to provide a new skin for a piece of AML.
 * 
 * #### Example
 * 
 * ```xml
 * <a:skin src="perspex.xml"
 *  name = "perspex"
 *  media-path = "http://example.com/images"
 *  icon-path = "http://icons.example.com" />
 * ```
 *  
 * @class apf.skin
 * @inherits apf.AmlElement
 * @define skin
 * @layout
 * @allowchild  style, presentation
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since  0.4
 */
/**
 * @attribute {String} name       Sets or gets the name of the skinset.
 */
/**
 * @attribute {String} src        Sets or gets the location of the skin definition.
 */
/**
 * @attribute {String} media-path Sets or gets the basepath for the images of the skin.
 */
/**
 * @attribute {String} icon-path  Sets or gets the basepath for the icons used in the elements using this skinset.
 */
apf.skin = function(struct, tagName) {
    this.$init(tagName || "skin", apf.NODE_HIDDEN, struct);
};
apf.aml.setElement("skin", apf.skin);

(function(){
    this.$parsePrio = "002";
    this.$includesRemaining = 0;
    
    this.$propHandlers["src"] = function(value) {
        if (value.trim().charAt(0) == "<") {
            apf.skins.Init(apf.getXml(value), this, this.$path);
            return;
        }
        
        this.$path = apf.getAbsolutePath(apf.hostPath, value)
        getSkin.call(this, this.$path);
    }
    
    this.$propHandlers["name"] = function(value) {
        if (!this.src && !this.attributes.getNamedItem("src")) {
            this.$path = apf.getAbsolutePath(apf.hostPath, value) + "/index.xml";
            getSkin.call(this, this.$path);
        }
    };
    
    /**
     * @private
     */
    function checkForAmlNamespace(xmlNode) {
        if (!xmlNode.ownerDocument.documentElement)
            return false;

        var nodes = xmlNode.ownerDocument.documentElement.attributes;
        for (var found = false, i=0; i<nodes.length; i++) {
            if (nodes[i].nodeValue == apf.ns.aml) {
                found = true;
                break;
            }
        }

        

        return found;
    }
    
    function getSkin(path) {
        var domParser = this.ownerDocument.$domParser;
        
        if (!apf.skins.$first)
            apf.skins.$first = this;
        
        var defer = this.attributes.getNamedItem("defer");
        if (!defer || !apf.isTrue(defer.nodeValue)) {
            domParser.$pauseParsing.apply(domParser, 
                this.$parseContext = domParser.$parseContext || [this.ownerDocument.documentElement]);
        }
        
        //var basePath = apf.hostPath;//only for recursion: apf.getDirname(xmlNode.getAttribute("filename")) || 
        loadSkinFile.call(this, path);
        
    }
    
    function finish(xmlNode) {
        if (xmlNode)
            apf.skins.Init(xmlNode, this, this.$path);

        if (!this.defer) {// && this.$parseContext
            var domParser = this.ownerDocument.$domParser;
            domParser.$continueParsing(this.$parseContext[0]);
        }
    }
    
    
    
    function loadSkinFile(path) {
        

        var _self = this;
        
        apf.getData(
        
          path, {
          
          callback: function(xmlString, state, extra) {
             if (state != apf.SUCCESS) {
                var oError = new Error(apf.formatErrorString(1007,
                    _self, "Loading skin file", "Could not load skin file '"
                    + (path || _self.src)
                    + "'\nReason: " + extra.message));

                if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
                    return true;

                

                throw oError;
            }

            //if (!apf.supportNamespaces)
            xmlString = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "")
                .replace(/^[\r\n\s]*/, "") //.replace(/&nbsp;/g, " ")
                .replace(/xmlns\=\"[^"]*\"/g, "");
            
            if (!xmlString) {
                throw new Error(apf.formatErrorString(0, _self,
                    "Loading skin",
                    "Empty skin file. Maybe the file does not exist?", _self));
            }
            
            var xmlNode = apf.getXml(xmlString);//apf.getAmlDocFromString(xmlString);
            
            
            
            if (!xmlNode) {
                throw new Error(apf.formatErrorString(0, _self,
                    "Loading skin",
                    "Could not parse skin. Maybe the file does not exist?", _self));
            }
            
            xmlNode.setAttribute("filename", extra.url);
            
            
            {
                
                
                finish.call(_self, xmlNode);
            }
          }, 
          async: true,
          ignoreOffline: true
        });
    }
    
    //@todo use mutation events to update
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (this.src || this.name)
            return;
        
        apf.skins.Init(this.$aml || this);
        
        //@todo implied skin
        /*if (this.parentNode && this.parentNode.parentNode) {
            var name = "skin" + Math.round(Math.random() * 100000);
            q.parentNode.setAttribute("skin", name);
            apf.skins.skins[name] = {name: name, templates: {}};
            apf.skins.skins[name].templates[q.parentNode[apf.TAGNAME]] = q;
        }*/
    });
}).call(apf.skin.prototype = new apf.AmlElement());





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */ 





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */




 
/**
 * An element containing information on how databound elements process data.
 *
 * The {@link term.smartbinding smartbinding} element specifies how data is transformed and rendered 
 * in databound elements. It also specifies how changes on the bound data are 
 * send to their original data source ({@link apf.actions actions}) and
 * which {@link term.datanode data nodes} can be dragged and dropped ({@link apf.DragDrop dragdrop}).
 * 
 * #### Remarks
 *
 * Each element has its own set of binding rules it uses to render the data 
 * elements. The same goes for it's actions. To give an example, a slider has 
 * one action called 'change'. This action is called when then value of the 
 * slider changes. A tree element has several actions - among others: 'add',
 * 'remove', 'move', 'copy' and 'rename'. 
 * 
 * Smartbindings enable many other features in a Ajax.org Platform
 * application. Actions done by the user can be undone by calling 
 * {@link apf.actiontracker.undo} of the element. The 
 * Remote Databinding element can send changes on data to other clients.
 *
 * This element is created especially for reuse. Multiple elements can reference
 * a single smartbinding element by setting the value of the 'smartbinding'
 * attribute to the ID of this smartbinding element. If an element is only used
 * for a single other element it can be set as it's child. In fact, each of the
 * children of the smartbinding element can exist outside the smartbinding
 * element and referenced indepently.
 *
 * #### Example
 *
 * A simple example of a smartbinding transforming data into representation
 * 
 * ```xml
 *  <a:smartbinding id="sbProducts">
 *          <a:bindings>
 *               <a:caption match="[text()]" />
 *               <a:value match="[text()]" />
 *               <a:each match="[product]" />
 *           </a:bindings>
 *      </a:smartbinding>
 *     
 *      <a:list smartbinding="sbProducts" width="200">
 *          <a:model>
 *               <data>
 *                   <product>LCD Panel</product>
 *               </data>
 *           </a:model>
 *      </a:list>
 * ```
 * 
 * #### Example
 *
 * This is an elaborate example showing how to create a filesystem tree with
 * files and folders in a tree. The smartbinding element describes how the
 * files and folders are transformed to tree elements and how actions within
 * the tree are sent to the data source; in this case, WebDAV.
 * is used. The drag and drop rules specify which elements can be dragged and
 * where they can be dropped.
 * 
 * ```xml
 *  <a:smartbinding id="sbFilesystem" model="{myWebdav.getRoot()}">
 *      <a:bindings>
 *          <a:insert match="[folder]" get="{myWebdav.readdir([@path])}" />
 *          <a:each match="[file|folder]" sort="[@name]" sort-method="filesort" />
 *          <a:caption match="[@name]" />
 *          <a:icon match="[folder]" value="icoFolder.png" />
 *          <a:icon match="[file]" method="getIcon" />
 *          <a:drag match="[folder|file]" copy="event.ctrlKey" /> 
 *          <a:drop 
 *            match = "[folder|file]" 
 *            target = "[folder]" 
 *            action = "tree-append" /> 
 *      </a:bindings>
 *      <a:add type="folder" get="{myWebdav.mkdir([@id], 'New Folder')}" />
 *      <a:add type="file" get="{myWebdav.create([@id], 'New File', '')}" />
 *      <a:rename set="{myWebdav.move(oldValue, [@name], [@id])}"/>
 *      <a:copy match="[.]" set="{myWebdav.copy([@id], [../@id])}"/>
 *      <a:move match="[.]" set="{myWebdav.move()}"/>
 *      <a:remove match="[.]" set="{myWebdav.remove([@path])}"/>
 *  </a:smartbinding>
 *
 *  <a:tree 
 *    dragcopy = "true" 
 *    model = "mdlFilesystem" 
 *    smartbinding = "sbFilesystem" />
 *
 *  <a:script>
 *      function filesort(value, args, xmlNode) {
 *          return (xmlNode.tagName == "folder" ? 0 : 1) + value;
 *      }
 *
 *      function getIcon(xmlNode) {
 *          xmlNode.getAttribute('name').match(/\.([^\.]*)$/);
 *              
 *          var ext = RegExp.$1;
 *          return (SupportedIcons[ext.toUpperCase()]
 *              ? SupportedIcons[ext.toUpperCase()] + ".png" 
 *              : "unknown.png");
 *      }
 *  </a:script>
 * ```
 *
 * #### Example
 *
 * This example shows a smartbinding element which references to its children as
 * stand alone elements.
 * 
 * ```xml
 *  <a:bindings id="bndExample">
 *      ...
 *  </a:bindings>
 *  <a:actions id="actExample">
 *      ...
 *  </a:actions>
 *  <a:dragdrop id="ddExample">
 *      ...
 *  </a:dragdrop>
 *  <a:model id="mdlExample" />
 *
 *  <a:smartbinding id="sbExample"
 *    actions = "actExample" 
 *    bindings = "bndExample" 
 *    dragdrop = "ddExample" 
 *    model = "mdlExample" />
 *
 *  <a:list smartbinding="sbExample" />
 *  <a:tree binding="bndExample" action="actExample" model="example.php" />
 * ```
 *
 * 
 * #### Example
 *
 * The shortest method to add binding rules to an element is as follows:
 * 
 * ```xml
 *  <a:tree each="[file|folder]" caption="[@name]" icon="[@icon]" />
 * ```
 *
 * @see baseclass.databinding
 * @see baseclass.databinding.attribute.smartbinding
 * @see term.smartbinding
 * @see term.binding
 * @see term.action
 * 
 * @define smartbinding
 * @allowchild bindings, actions, ref, action, dragdrop, model
 *
 *
 * @class apf.smartbinding
 * @apfclass
 * @inherits apf.AmlElement
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.8
 *
 * @default_private
 */
apf.smartbinding = function(struct, tagName) {
    this.$init(tagName || "smartbinding", apf.NODE_HIDDEN, struct);

    this.$bindNodes = {};
};

(function(){
    this.$supportedProperties = ["bindings", "actions", "model"];
    this.$handlePropSet = function(prop, value, force) {
        switch(prop) {
            //@todo apf3 change this to use apf.setModel();
            case "model":
                
                if (typeof value == "string")
                    value = apf.nameserver.get("model", value);
                this.model = apf.nameserver.register("model", this.name, value);
                //this.modelBaseXpath = xpath;
                
                var amlNode;
                for (var uniqueId in this.$bindNodes) {
                    amlNode = this.$bindNodes[uniqueId];
                    this.model.unregister(amlNode);
                    this.model.register(amlNode, this.$modelXpath[amlNode.getHost
                        ? amlNode.getHost().$uniqueId
                        //this is a hack.. by making Models with links to other
                        //models possible, this should not be needed
                        : amlNode.$uniqueId] || this.modelBaseXpath);
                    //this.$bindNodes[uniqueId].load(this.model);
                }
                
                break;
            case "bindings":
                if (this.$bindings)
                    this.remove(this.$bindings);
                
                this.$bindings = typeof value == "object" 
                    ? value 
                    : 
                    
                    apf.nameserver.lookup("bindings", value);
                    
                
                this.add(this.$bindings);
                
                break;
            case "actions":
                if (this.$actions)
                    this.remove(this.$actions);
                
                this.$actions = typeof value == "object" 
                    ? value 
                    :
                    
                    apf.nameserver.lookup("actions", value);
                    
                
                this.add(this.$actions);
            
                break;
        }
        
        this[prop] = value;
        
        
    };
    
    this.add = function(node) {
        for (var uId in this.$bindNodes)
            node.register(this.$bindNodes[uId]);
        
        this["$" + node.localName] = node;
    };
    
    this.remove = function(node) {
        for (var uId in this.$bindNodes)
            node.unregister(this.$bindNodes[uId]);
    };
    
    this.register = function(amlNode) {
        this.$bindNodes[amlNode.$uniqueId] = amlNode;
        
        if (this.$bindings)
            this.$bindings.register(amlNode);
        if (this.$actions)
            this.$actions.register(amlNode);
        if (this.$model)
            this.$model.register(amlNode);
    };

    this.unregister = function(amlNode) {
        //unregister element
        this.$bindNodes[amlNode.$uniqueId] = null;
        delete this.$bindNodes[amlNode.$uniqueId];
        
        if (this.$bindings)
            this.$bindings.unregister(amlNode);
        if (this.$actions)
            this.$actions.unregister(amlNode);
        if (this.$model)
            this.$model.unregister(amlNode);
    };
    
    /**
     * Loads XML data in the model of this smartbinding element.
     * 
     * @param {Mixed}   xmlNode the {@link term.datanode data node} loaded into
     * the model of this smartbinding element. This can be an XMLElement, a 
     * string or null. 
     * @private
     */
    this.load = function(xmlNode) {
        //@todo fix this
        new apf.model().register(this).load(xmlNode);
    };
    
    this.clear = function(state) {
        //for all elements do clear(state);
    };
    
    /*
     * @private
     *
     * @attribute {String} bindings the id of the bindings element that contains 
     * the {@link term.binding binding rules} for all elements connected to 
     * this smartbinding element
     * 
     * #### Example
     *   
     * ```xml
     *  <a:smartbinding id="sbExample" bindings="bndExample" />
     * ```
     * @see element.bindings
     * @see term.binding
     * @see term.smartbinding
     *
     * @attribute {String} actions  the id of the actions element that provides 
     * the {@link term.action action rules} for all elements connected to 
     * this smartbinding element
     * 
     * #### Example
     * 
     * ```xml
     *  <a:smartbinding id="sbExample" actions="actExample" />
     * ```
     * @see apf.actions
     * @see term.action
     * @see term.smartbinding
     *
     * @attribute {String} dragdrop the id of the dragdrop element that provides 
     * the drag and drop rules for all elements connected to this smartbinding 
     * element
     * 
     * #### Example
     * 
     * ```xml
     *  <a:smartbinding id="sbExample" bindings="bndExample" />
     * ```
     * @see apf.DragDrop
     * @see term.smartbinding
     *
     * @attribute {String} model    the id of the model element that provides 
     * the data for all elements connected to this smartbinding element.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:smartbinding id="sbExample" model="mdlExample" />
     * ```
     * @see element.model
     * @see term.smartbinding
     *
     * @define bindings element containing all the binding rules for the data 
     * bound elements referencing this element.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:bindings id="bndFolders" >
     *      <a:caption match="[@name]" />
     *      <a:icon match="[@icon]" />
     *      <a:each match="[folder]" sort="[@name]" />
     *  </a:bindings>
     *
     *  <a:tree bindings="bndFolders" />
     * ```
     * @see apf.smartbinding
     * @allowchild {bindings}
     *
     *
     */
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (this.parentNode.hasFeature(apf.__DATABINDING__))
            this.register(this.parentNode);

        
    });
}).call(apf.smartbinding.prototype = new apf.AmlElement());

apf.aml.setElement("smartbinding", apf.smartbinding);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Element 
 *
 * @constructor
 *
 * @define source
 *
 *
 * @author      Mike de Boer (mike AT javeline DOT com)
 * @version     %I%, %G%
 * @since       3.0
 */
apf.source = function(struct, tagName) {
    this.$init(tagName || "source", apf.NODE_HIDDEN, struct);
};

(function(){
    this.$supportedProperties.push("src", "type");

    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (this.parentNode.$addSource)
            this.parentNode.$addSource(this);
    });
}).call(apf.source.prototype = new apf.AmlElement());

apf.aml.setElement("source", apf.source);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/** 
 * This element is used to choose a number via plus/minus buttons.
 *
 * When the plus button is clicked/held longer, the number increments faster. The same
 * situation occurs for the minus button. It's also possible to increment and decrement
 * value by moving mouse cursor up or down with clicked input. 
 * 
 * Max and min attributes define the range of allowed values.
 * 
 * #### Example: Setting Maximum and Minimum Ranges
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <!-- startcontent -->
 *    <a:spinner value="6" min="-6" max="12" width="200"></a:spinner>
 *  <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Loading Data
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="mdlSpinner">
 *       <data value="56"></data>
 *   </a:model>
 *   <a:spinner value="[@value]" model="mdlSpinner" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Connecting to a Textbox
 *
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="mdlTest">
 *     <overview tab="1" pages="10" />
 *   </a:model>
 *   <a:spinner 
 *     id = "spinner" 
 *     min = "0" 
 *     max = "[@pages]" 
 *     model = "mdlTest" 
 *     value = "[@tab]" 
 *     caption = "[@tab] of [@pages]">
 *   </a:spinner>
 *   <a:textbox value="{spinner.caption}"></a:textbox>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.spinner
 * @define spinner
 * @form
 * @version     %I%, %G%
 * 
 * @inherits apf.StandardBinding
 * @inherits apf.DataAction
 * @inheritsElsewhere apf.XForms
 *
 */
/**
 * @attribute {Number}   [max=64000]       Sets or gets the maximum allowed value
 */
/**
 * @attribute {Number}   [min=-64000]      Sets or gets the minimal allowed value
 */
/**
 *  @attribute {Number}   value     Sets or gets the actual value displayed in component
 * 
 */
/**
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 */
apf.spinner = function(struct, tagName) {
    this.$init(tagName || "spinner", apf.NODE_VISIBLE, struct);
    
    this.max = 64000;
    this.min = -64000;
    this.focused = false;
    this.value = 0;
    
    this.realtime = false;
};

(function() {
    this.implement(
        
        apf.DataAction
        
        
    );

    this.$supportedProperties.push("width", "value", "max", "min", "caption", "realtime");

    this.$booleanProperties["realtime"] = true;

    this.$propHandlers["value"] = function(value) {
        value = parseInt(value) || 0;
        
        this.value = this.oInput.value = (value > this.max
            ? this.max
            : (value < this.min
                ? this.min
                : value));
    };

    this.$propHandlers["min"] = function(value) {
        if (!(value = parseInt(value))) return;
        this.min = value;
        if (value > this.value)
            this.change(value);
    };

    this.$propHandlers["max"] = function(value) {
        if (!(value = parseInt(value))) return;
        this.max = value;

        if (value < this.value)
            this.change(value);
    };

    /* ********************************************************************
     PUBLIC METHODS
     *********************************************************************/

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
       this.setProperty("value", value, false, true);
    };

    /**
     * Returns the current value of this element.
     * @return {String} The current element value
     */
    this.getValue = function() {
        return this.value;
    };

    /**
     * Increments the spinner by one.
     */
    this.increment = function() {
        this.change(parseInt(this.oInput.value) + 1);
    };

    /**
     * Decrements the spinner by one.
     */    
    this.decrement = function() {
        this.change(parseInt(this.oInput.value) - 1);
    };
    
    

    this.$enable = function() {
        this.oInput.disabled = false;
        this.$setStyleClass(this.oInput, "", ["inputDisabled"]);
    };

    this.$disable = function() {
        this.oInput.disabled = true;
        this.$setStyleClass(this.oInput, "inputDisabled");
    };

    this.$focus = function(e) {
        if (!this.$ext || this.focused) //this.disabled || 
            return;

        

        this.focused = true;
        this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
        
        if (this.oLeft)
            this.$setStyleClass(this.oLeft, "leftFocus");
    };

    this.$blur = function(e) {
        if (!this.$ext && !this.focused)
            return;

        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
        
        if (this.oLeft)
            this.$setStyleClass(this.oLeft, "" ["leftFocus"]);
        
        this.setValue(this.oInput.value);
        
        this.focused = false;
    };

    /* ***********************
     Keyboard Support
     ************************/
    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode,

        keyAccess = (key < 8 || (key > 9 && key < 37 && key !== 12)
            || (key > 40 && key < 46) || (key > 46 && key < 48)
            || (key > 57 && key < 96) || (key > 105 && key < 109 && key !== 107)
            || (key > 109 && key !== 189));

        if (keyAccess)
            return false;
           
        switch(key) {
            case 38://Arrow up
                this.increment();
                break;
            case 40://Arrow down
                this.decrement();
                break;
        }
    }, true);

    this.addEventListener("keyup", function(e) {
        //this.setValue(this.oInput.value);
    }, true);
    
    
    this.increment = function() {
        this.change(parseInt(this.oInput.value) + 1);
    };
    
    this.decrement = function() {
        this.change(parseInt(this.oInput.value) - 1);
    };
    
    /**
     * @event click     Fires when the user presses a mousebutton while over this element and then lets the mousebutton go. 
     */
    /**
     * @event mouseup   Fires when the user lets go of a mousebutton while over this element. 
     */
    /**
     * @event mousedown Fires when the user presses a mousebutton while over this element. 
     */
    this.$draw = function() {
        var _self = this;

        //Build Main Skin
        this.$ext = this.$getExternal(null, null, function(oExt) {
            oExt.setAttribute("onmousedown",
                'if (!this.host.disabled) \
                    this.host.dispatchEvent("mousedown", {htmlEvent : event});');
            oExt.setAttribute("onmouseup",
                'if (!this.host.disabled) \
                    this.host.dispatchEvent("mouseup", {htmlEvent : event});');
            oExt.setAttribute("onclick",
                'if (!this.host.disabled) \
                    this.host.dispatchEvent("click", {htmlEvent : event});');
        });

        this.$int = this.$getLayoutNode("main", "container",   this.$ext);
        this.oInput = this.$getLayoutNode("main", "input",       this.$ext);
        this.$buttonPlus = this.$getLayoutNode("main", "buttonplus",  this.$ext);
        this.$buttonMinus = this.$getLayoutNode("main", "buttonminus", this.$ext);
        this.oLeft = this.$getLayoutNode("main", "left", this.$ext);

        

        var timer,
            doc = (!document.compatMode || document.compatMode == 'CSS1Compat')
                ? document.html : document.body,
            z = 0;

        /* Setting start value */
        this.oInput.value = this.value;

        this.oInput.onmousedown = function(e) {
            if (_self.disabled)
                return;
            
            e = e || window.event;

            clearTimeout(timer);

            var newval,
                value = parseInt(this.value) || 0,
                step = 0,
                cy = e.clientY,
                ot = _self.$int.offsetTop, ol = _self.$int.offsetLeft,
                ow = _self.$int.offsetWidth, oh = _self.$int.offsetHeight,
                func = function() {
                    clearTimeout(timer);
                    timer = $setTimeout(func, 10);
                    if (!step)
                        return;

                    newval = value + step;
                    if (newval <= _self.max && newval >= _self.min) {
                        value += step;
                        value = Math.round(value);
                        _self.oInput.value = value;
                        
                        if (_self.realtime)
                            _self.change(value);
                    }
                    else {
                        _self.oInput.value = step < 0
                            ? _self.min
                            : _self.max;
                    }
                };
            func();

            function calcStep(e) {
                e = e || window.event;
                var x = e.pageX || e.clientX + (doc ? doc.scrollLeft : 0),
                    y = e.pageY || e.clientY + (doc ? doc.scrollTop  : 0),
                    nrOfPixels = cy - y;

                if ((y > ot && x > ol) && (y < ot + oh && x < ol + ow)) {
                    step = 0;
                    return;
                }

                step = Math.pow(Math.min(200, Math.abs(nrOfPixels)) / 10, 2) / 10;
                if (nrOfPixels < 0)
                    step = -1 * step;
            }
            
            document.onmousemove = calcStep;

            document.onmouseup = function(e) {
                clearTimeout(timer);

                var value = parseInt(_self.oInput.value);

                if (value != _self.value)
                    _self.change(value);
                document.onmousemove = document.onmouseup = null;
            };
        };

        /* Fix for mousedown for IE */
        var buttonDown = false;
        this.$buttonPlus.onmousedown = function(e) {
            if (_self.disabled)
                return;
            
            e = e || window.event;
            buttonDown = true;

            var value = (parseInt(_self.oInput.value) || 0) + 1,
                func = function() {
                    clearTimeout(timer);
                    timer = $setTimeout(func, 50);
                    z++;
                    value += Math.pow(Math.min(200, z) / 10, 2) / 10;
                    value = Math.round(value);

                    _self.oInput.value = value <= _self.max
                        ? value
                        : _self.max;
                    
                    if (_self.realtime)
                       _self.change(value <= _self.max ? value : _self.max);
                };

            apf.setStyleClass(this, "plusDown", ["plusHover"]);

            func();
        };

        this.$buttonMinus.onmousedown = function(e) {
            if (_self.disabled)
                return;
            
            e = e || window.event;
            buttonDown = true;

            var value = (parseInt(_self.oInput.value) || 0) - 1,
                func = function() {
                    clearTimeout(timer);
                    timer = $setTimeout(func, 50);
                    z++;
                    value -= Math.pow(Math.min(200, z) / 10, 2) / 10;
                    value = Math.round(value);

                    _self.oInput.value = value >= _self.min
                        ? value
                        : _self.min;
                    
                    if (_self.realtime)
                       _self.change(value >= _self.min ? value : _self.min);
                };

            apf.setStyleClass(this, "minusDown", ["minusHover"]);

            func();
        };

        this.$buttonMinus.onmouseout = function(e) {
            if (_self.disabled)
                return;
            
            clearTimeout(timer);
            z = 0;

            var value = parseInt(_self.oInput.value);

            if (value != _self.value)
                _self.change(value);

            apf.setStyleClass(this, "", ["minusHover"]);

            if (!_self.focused)
               _self.$blur(e);
        };

        this.$buttonPlus.onmouseout = function(e) {
            if (_self.disabled)
                return;
            
            clearTimeout(timer);
            z = 0;

            var value = parseInt(_self.oInput.value);

            if (value != _self.value)
                _self.change(value);

            apf.setStyleClass(this, "", ["plusHover"]);

            if (!_self.focused)
               _self.$blur(e);
        };

        this.$buttonMinus.onmouseover = function(e) {
            if (_self.disabled)
                return;
                
            apf.setStyleClass(this, "minusHover");
        };

        this.$buttonPlus.onmouseover = function(e) {
            if (_self.disabled)
                return;
                
            apf.setStyleClass(this, "plusHover");
        };

        this.$buttonPlus.onmouseup = function(e) {
            if (_self.disabled)
                return;
            
            e = e || event;
            //e.cancelBubble = true;
            apf.cancelBubble(e, this);

            apf.setStyleClass(this, "plusHover", ["plusDown"]);

            clearTimeout(timer);
            z = 0;

            var value = parseInt(_self.oInput.value);

            if (!buttonDown) {
                value++;
                _self.oInput.value = value;
            }
            else {
                buttonDown = false;
            }

            if (value != _self.value)
                _self.change(value);
        };

        this.$buttonMinus.onmouseup = function(e) {
            if (_self.disabled)
                return;
            
            e = e || event;
            //e.cancelBubble = true;
            apf.cancelBubble(e, this);

            apf.setStyleClass(this, "minusHover", ["minusDown"]);

            clearTimeout(timer);
            z = 0;

            var value = parseInt(_self.oInput.value);

            if (!buttonDown) {
                value--;
                _self.oInput.value = value;
            }
            else {
                buttonDown = false;
            }


            if (value != _self.value)
                _self.change(value);
        };

        this.oInput.onselectstart = function(e) {
            e = e || event;
            e.cancelBubble = true;
        };

        this.oInput.host = this;
    };

    this.$destroy = function() {
        this.oInput.onkeypress =
        this.oInput.onmousedown =
        this.oInput.onkeydown =
        this.oInput.onkeyup =
        this.oInput.onselectstart =
        this.$buttonPlus.onmouseover =
        this.$buttonPlus.onmouseout =
        this.$buttonPlus.onmousedown =
        this.$buttonPlus.onmouseup =
        this.$buttonMinus.onmouseover =
        this.$buttonMinus.onmouseout =
        this.$buttonMinus.onmousedown =
        this.$buttonMinus.onmouseup = null;
    };
    
    


}).call(apf.spinner.prototype = new apf.StandardBinding());


apf.aml.setElement("spinner", apf.spinner);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * 
 * A container that stacks two children vertically. 
 * 
 * Programatically, this is identical to a regular [[vbox]], except that it can
 * only accept two children, and uses absolute positioning. Because of this, there
 * is more work required to construct AML that matches a regular `<a:vbox>`; however,
 * the performance improvements in using a `<a:vsplitbox>` are massive.
 *
 * @class apf.vsplitbox
 * @define vsplitbox
 * @layout
 *
 * @inheritDoc apf.hsplitbox
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       3.0
 * @see element.hsplitbox
 */
/**
 *
 * A container that stacks two children horizontally.
 * 
 * Programatically, this is identical to a regular [[apf.hbox]], except that it can
 * only accept two children, and uses absolute positioning. Because of this, there
 * is more work required to construct AML that matches a regular `<a:hbox>`; however,
 * the performance improvements in using a `<a:hsplitbox>` are massive.
 *
 * @class apf.hsplitbox
 * @define hsplitbox 
 * @layout
 * @inherits apf.GuiElement
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       3.0\
 * @see element.vsplitbox
 */
apf.hsplitbox = function(struct, tagName) {
    this.$init(tagName || "hsplitbox", apf.NODE_VISIBLE, struct);
};
apf.vsplitbox = function(struct, tagName) {
    this.$init(tagName || "vsplitbox", apf.NODE_VISIBLE, struct);
};

(function(){
    this.minwidth = 0;
    this.minheight = 0;
    
    this.padding = 0;
    this.edge = 0;
    this.$edge = [0,0,0,0];
    
    // *** Properties and Attributes *** //

    this.$focussable = false;
    this.$useLateDom = true; 
    this.$box = true;
    this.$layout = true;
    
    /**
     * @attribute {String}  [padding="2"]      Sets or gets the space between each element.
     */
    /**
     * @attribute {String}  [edge="5 5 5 5"]         Sets or gets the space between the container and the elements, space seperated in pixels for each side. Similar to CSS in the sequence of `top right bottom left`.
     */
    this.$booleanProperties["splitter"] = true;
    this.$supportedProperties.push("padding", "edge", "splitter");
    
    this.$propHandlers["padding"] = function(value) {
        this.padding = parseInt(value);
        
        if (!this.$amlLoaded)
            return;
            
        if (this.$handle)
            this.$handle.$ext.style[this.$vbox ? "height" : "width"] = value + "px";
        
        var firstChild = this.getFirstChild();
        var lastChild = this.getSecondChild();
        
        if (this.$vbox) {
            //Two flex children
            if (this.flexChild2) {
                if (firstChild.height) {
                    //String(firstChild.height).indexOf("%") == -1
                    lastChild.$ext.style.marginTop = firstChild.visible
                        ? (value 
                            + apf.getHeightDiff(firstChild.$ext)) + "px"
                        : 0;
                }
                else {
                    firstChild.$ext.style.marginBottom = lastChild.visible
                        ? (value 
                            + apf.getHeightDiff(lastChild.$ext)) + "px"
                        : 0;
                }
            }
            else if (this.fixedChild && this.fixedChild.visible) {
                //One flex child (first)
                if (this.flexChild1 == firstChild) {
                    if (this.fixedChild.visible) {
                        this.flexChild1.$ext.style.bottom = 
                            (parseInt(this.fixedChild.height) + value + this.$edge[2]) + "px";
                    }
                }
                    
                //One flex child (last)
                else if (lastChild && this.flexChild1 == lastChild) {
                    this.flexChild1.$ext.style.top = 
                        (parseInt(this.fixedChild.height) + value + this.$edge[2]) + "px";
                }
            }
        }
        else {
            //Two flex children
            if (this.flexChild2) {
                if (firstChild.width) {
                    lastChild.$ext.style.marginLeft = 
                        (value 
                            + apf.getWidthDiff(firstChild.$ext)
                            + apf.getMargin(firstChild.$ext)[0]) + "px";
                }
                else {
                    firstChild.$ext.style.marginRight = 
                        (value + (lastChild
                            ? apf.getWidthDiff(lastChild.$ext)
                                + apf.getMargin(lastChild.$ext)[0]
                            : 0)) + "px";
                }
            }
            else if (this.fixedChild && this.fixedChild.visible) {
                //One flex child (first)
                if (this.flexChild1 == firstChild) {
                    this.flexChild1.$ext.style.right =   
                        (parseInt(this.fixedChild.width) + value + this.$edge[1]) + "px";
                }
                    
                //One flex child (last)
                else if (lastChild && this.flexChild1 == lastChild) {
                    this.flexChild1.$ext.style.left = 
                        (parseInt(this.fixedChild.width) + value + this.$edge[3]) + "px";
                }
            }
        }
    }
    
    this.$propHandlers["splitter"] = function(value) {
        if (value) {
            if (this.$handle)
                this.$handle.show();
            else {
                this.$handle = this.insertBefore(
                    this.ownerDocument.createElementNS(apf.ns.aml, "splitter"), 
                    this.lastChild);
            }
        }
        else {
            this.$handle && this.$handle.hide();//destroy(true, true);
        }
    }
    
    this.$propHandlers["edge"] = function(value, setSize) {
        this.$edge = apf.getBox(value);
        
        if (!this.$amlLoaded)
            return;
        
        var fNode = this.getFirstVisibleChild();
        if (!fNode) {
            this.hide();
            return false;
        }
        fNode.$ext.style.left = (this.$edge[3] + fNode.$margin[3]) + "px";
        fNode.$ext.style.top = (this.$edge[0] + fNode.$margin[0]) + "px";
        if (this.$vbox)
            fNode.$ext.style.right = (this.$edge[1] + fNode.$margin[1]) + "px";
        else
            fNode.$ext.style.bottom = (this.$edge[2] + fNode.$margin[2]) + "px";
        
        var lNode = this.getSecondVisibleChild();
        if (lNode && lNode.visible && lNode.$ext) {
            lNode.$ext.style.right = (this.$edge[1] + lNode.$margin[1]) + "px";
            lNode.$ext.style.bottom = (this.$edge[2] + lNode.$margin[2]) + "px";
            if (this.$vbox) {
                var isPercentage;
                
                lNode.$ext.style.left = (this.$edge[3] + lNode.$margin[3]) + "px";
                if (fNode.height || fNode.height === 0) {
                    isPercentage = String(fNode.height).indexOf("%") > -1;
                    lNode.$ext.style.top = isPercentage 
                        ? fNode.height 
                        : ((parseInt(fNode.height) + this.padding 
                            + this.$edge[0] + fNode.$margin[0]) + "px");
                    if (isPercentage) {
                        fNode.$ext.style.height = "";
                        fNode.$ext.style.bottom = (100 - parseFloat(fNode.height)) + "%";
                    }
                    
                    if (this.$handle) {
                        this.$handle.$ext.style.top = isPercentage
                            ? fNode.height 
                            : ((parseInt(fNode.height) + this.$edge[0]) + "px");
//                        this.$handle.$ext.style.marginTop = isPercentage
//                            ? this.padding + "px"
//                            : "0";
                    }
                }
                else {
                    isPercentage = String(lNode.height).indexOf("%") > -1;
                    lNode.$ext.style.top = "";
                    fNode.$ext.style.bottom = isPercentage 
                        ? lNode.height 
                        : ((parseInt(lNode.height) + this.padding 
                            + this.$edge[2] + lNode.$margin[2]) + "px");
                    if (isPercentage) {
                        lNode.$ext.style.height = "";
                        lNode.$ext.style.top = (100 - parseFloat(lNode.height)) + "%";
                    }
                    
                    if (this.$handle) {
                        this.$handle.$ext.style.bottom = isPercentage
                            ? lNode.height 
                            : ((parseInt(lNode.height) + this.$edge[0]) + "px");
//                        this.$handle.$ext.style.marginBottom = isPercentage
//                            ? this.padding + "px"
//                            : "0";
                    }
                }
                
                if (this.$handle) {
                    this.$handle.$ext.style.left = this.$edge[3] + "px";
                    this.$handle.$ext.style.right = this.$edge[1] + "px";
                }
            }
            else {
                lNode.$ext.style.top = this.$edge[0] + "px";
                
                if (fNode.width || fNode.width === 0) {
                    var isPercentage = String(fNode.width).indexOf("%") > -1;
                    lNode.$ext.style.left = isPercentage
                        ? fNode.width 
                        : ((parseInt(fNode.width) + this.padding 
                            + this.$edge[3] + fNode.$margin[3]) + "px");
                    if (isPercentage) {
                        fNode.$ext.style.width = "";
                        fNode.$ext.style.right = (100 - parseFloat(fNode.width)) + "%";
                    }
                    
                    if (this.$handle) {
                        this.$handle.$ext.style.left = isPercentage
                            ? fNode.width 
                            : ((parseInt(fNode.width) + this.$edge[3]) + "px");
//                        this.$handle.$ext.style.marginLeft = isPercentage
//                            ? this.padding + "px"
//                            : "0";
                    }
                }
                else {
                    var isPercentage = String(lNode.width).indexOf("%") > -1;
                    lNode.$ext.style.left = "";
                    fNode.$ext.style.right = isPercentage
                        ? lNode.width 
                        : ((parseInt(lNode.width) + this.padding 
                            + this.$edge[1] + lNode.$margin[1]) + "px");
                    if (isPercentage) {
                        lNode.$ext.style.width = "";
                        lNode.$ext.style.left = (100 - parseFloat(lNode.width)) + "%";
                    }
                    
                    if (this.$handle) {
                        this.$handle.$ext.style.right = isPercentage
                            ? lNode.width 
                            : ((parseInt(lNode.width) + this.$edge[3]) + "px");
//                        this.$handle.$ext.style.marginRight = isPercentage
//                            ? this.padding + "px"
//                            : "0";
                    }
                }
                
                if (this.$handle) {
                    this.$handle.$ext.style.top = this.$edge[0] + "px";
                    this.$handle.$ext.style.bottom = this.$edge[2] + "px";
                }
            }
            
            if (this.$handle)
                this.$handle.$ext.style.position = "absolute";
        }
        else {
            if (!this.$vbox) {
                fNode.$ext.style.right = (this.$edge[1] + fNode.$margin[1]) + "px";
                fNode.$ext.style.width = "";
            }
            else {
                fNode.$ext.style.bottom = (this.$edge[2] + fNode.$margin[2]) + "px";
                fNode.$ext.style.height = "";
            }
            
            if (this.$handle)
                this.$handle.hide();
        }
        
        if (setSize === true) {
            var size = this.$vbox ? "height" : "width";
            fNode.$propHandlers[size].call(fNode, fNode[size]);
        }
    };
    
    this.getFirstChild = function(startNode) {
        var node = startNode || this.firstChild;
        while (node && node.$splitter) {
            node = node.nextSibling;
        }
        return node || false;
    }
    this.getSecondChild = function(){
        var node = this.getFirstChild();
        if (!node)
            return false;
        return node.nextSibling && this.getFirstChild(node.nextSibling);
    }
    
    this.getFirstVisibleChild = function(startNode) {
        var node = startNode || this.firstChild;
        while (node && (!node.visible || node.$splitter)) {
            node = node.nextSibling;
        }
        if (node && node.visible)
            return node;
        return false;
    }
    
    this.getSecondVisibleChild = function(){
        var node = this.getFirstVisibleChild();
        if (!node)
            return false;
        return node.nextSibling && this.getFirstVisibleChild(node.nextSibling);
    }
    
    function visibleHandler(e) {
        if (this.parentNode.$handle) {
            if (!e.value || this.parentNode.childNodes.length < 3)
                this.parentNode.$handle.hide();
            else
                this.parentNode.$handle.show();
        }
        
        if (e.value && !this.parentNode.visible)
            this.parentNode.show();
        
        this.parentNode.$propHandlers.edge
            .call(this.parentNode, this.parentNode.edge, true);
        
        // apf.layout.forceResize(this.parentNode.$int);
        
        //Change margin
        this.parentNode.$propHandlers.padding
            .call(this.parentNode, this.parentNode.padding)
    }
    
    var handlers = {
        "width" : function(value, isLast) {
            //@todo this should check the largest and only allow that one
            //if (this.parentNode.$vbox && this.parentNode.align == "stretch")
                //return;
            
            //@todo change fixedChild flexChild1 and flexChild2 based on this

            this.$ext.style.width = !apf.isNot(value) 
                ? (parseFloat(value) == value 
                    ? (value - apf.getWidthDiff(this.$ext)) + "px"
                    : value)
                : "";
            
            //This can be optimized
            if (this.$amlLoaded && isLast !== false)
                this.parentNode.$propHandlers["edge"].call(this.parentNode, 
                    this.parentNode.edge);
        },
        
        "height" : function(value, isLast) {
            //@todo this should check the largest and only allow that one
            //if (!this.parentNode.$vbox && this.parentNode.align == "stretch")
                //return;

            //@todo change fixedChild flexChild1 and flexChild2 based on this

            this.$ext.style.height = !apf.isNot(value) 
                ? (parseFloat(value) == value 
                    ? (value - apf.getHeightDiff(this.$ext)) + "px"
                    : value)
                : "";
            
            //This can be optimized
            if (this.$amlLoaded && isLast !== false)
                this.parentNode.$propHandlers["edge"].call(this.parentNode, 
                    this.parentNode.edge);
        },
        
        "margin" : function(value, isLast) {
            this.$margin = apf.getBox(value);
            
            //This can be optimized
            if (this.$amlLoaded && isLast !== false)
                this.parentNode.$propHandlers["edge"].call(this.parentNode, this.parentNode.edge);
        }
    }
    
    this.register = function(amlNode, insert) {
        if (amlNode.$splitter || amlNode.nodeFunc != apf.NODE_VISIBLE)
            return;

        amlNode.$margin = [0, 0, 0, 0];
        
        amlNode.$propHandlers["left"] = 
        amlNode.$propHandlers["top"] = 
        amlNode.$propHandlers["right"] = 
        amlNode.$propHandlers["bottom"] = apf.K;

        for (var prop in handlers) {
            amlNode.$propHandlers[prop] = handlers[prop];
        }

        if (this.flexChild1 && this.flexChild1 == amlNode){ }
        else if (this.$vbox) {
            if (!amlNode.height || String(amlNode.height).indexOf("%") > -1)
                this[!this.flexChild1 ? "flexChild1" : "flexChild2"] = amlNode;
            else
                this.fixedChild = amlNode;
        }
        else {
            if (!amlNode.width || String(amlNode.width).indexOf("%") > -1)
                this[!this.flexChild1 ? "flexChild1" : "flexChild2"] = amlNode;
            else
                this.fixedChild = amlNode;
        }

        amlNode.addEventListener("prop.visible", visibleHandler);
        amlNode.$ext.style.position = "absolute";
        amlNode.$ext.style.margin = "";

        if (amlNode.height)
            handlers.height.call(amlNode, amlNode.height, false);
        if (amlNode.width)
            handlers.width.call(amlNode, amlNode.width, false);
        if (amlNode.margin)
            handlers.margin.call(amlNode, amlNode.margin, false);
            
        var isLast = this.lastChild.$amlLoaded || this.lastChild == amlNode;
        if (isLast) {
            this.$propHandlers["padding"].call(this, this.padding);
            this.$propHandlers["edge"].call(this, this.edge, 
                !amlNode[this.$vbox ? "height" : "width"]);
        }
        
        if (this.$handle && this.childNodes.length > 2)
            this.$handle.show();
    }
    
    this.unregister = function(amlNode) {
        if (amlNode.$splitter || amlNode.nodeFunc != apf.NODE_VISIBLE)
            return;
        
        delete amlNode.$margin;
        
        amlNode.$propHandlers["left"] = 
        amlNode.$propHandlers["top"] = 
        amlNode.$propHandlers["right"] = 
        amlNode.$propHandlers["bottom"] = null;
        
        for (var prop in handlers) {
            delete amlNode.$propHandlers[prop];
        }
        
        if (this.fixedChild == amlNode)
            delete this.fixedChild;
        else if (this.flexChild1 == amlNode)
            delete this.flexChild1;
        else if (this.flexChild2 == amlNode)
            delete this.flexChild2;
        
        //Clear css properties and set layout
        amlNode.removeEventListener("prop.visible", visibleHandler);
        amlNode.$ext.style.display = amlNode.visible ? "block" : "none";
        
        if (amlNode.width)
            amlNode.$ext.style.width = "";
        if (amlNode.height)
            amlNode.$ext.style.height = "";
        amlNode.$ext.style.position = 
        amlNode.$ext.style.margin = 
        amlNode.$ext.style.left = 
        amlNode.$ext.style.top = 
        amlNode.$ext.style.right = 
        amlNode.$ext.style.bottom = "";
        
        if (this.$handle)
            this.$handle.hide();
    }
    
    // *** DOM Hooks *** //
    
    this.addEventListener("DOMNodeRemoved", function(e) {
        if (e.$doOnlyAdmin || e.currentTarget == this)
            return;

        if (e.relatedNode == this) {
            this.unregister(e.currentTarget);
            //e.currentTarget.$setLayout();
        }
    });

    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget == this) {
            if (this.visible)
                this.$ext.style.display = apf.CSSPREFIX2 + "-box"; //Webkit issue
            return;
        }
        
        if (e.currentTarget.nodeType != 1 
          || e.currentTarget.nodeFunc != apf.NODE_VISIBLE)
            return;
        
//        if (this.$handle) {
//            var _self = this;
//            setTimeout(function(){
//                if (_self.$handle.nextSibling != _self.lastChild)
//                    _self.insertBefore(_self.$handle, _self.lastChild);
//            });
//        }

        if (e.relatedNode == this && !e.$isMoveWithinParent) {
            e.currentTarget.$setLayout(this.localName, true);
            
            if (e.currentTarget.$altExt) {
                
                return false;
            }
        }
    });

    this.$draw = function(){
        var doc = this.$pHtmlNode.ownerDocument;
        this.$ext = this.$pHtmlNode.appendChild(doc.createElement("div"));
        if (this.getAttribute("style"))
            this.$ext.setAttribute("style", this.getAttribute("style"));
        this.$ext.className = this.localName;

        this.$vbox = this.localName == "vsplitbox";
        this.$int = this.$ext;
        this.$ext.host = this;
        
        if (this.getAttribute("class")) 
            apf.setStyleClass(this.$ext, this.getAttribute("class"));
    };
    
    this.$loadAml = function(x) {
    };
}).call(apf.vsplitbox.prototype = new apf.GuiElement());

apf.hsplitbox.prototype = apf.vsplitbox.prototype;

apf.aml.setElement("hsplitbox", apf.hsplitbox);
apf.aml.setElement("vsplitbox", apf.vsplitbox);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element displays a skinnable rectangle that can contain other
 * AML elements. 
 * 
 * It's used by other elements, such as the
 * toolbar and statusbar elements, to specify sections.
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <a:window 
 *    visible = "true" 
 *    width = "400" 
 *    height = "150" 
 *    title = "Simple Tab" >
 *  <!-- startcontent -->
 *    <a:splitbutton id="btnTestRun" caption = "Run tests"/>
 *  <!-- endcontent -->
 *  </a:window>
 * </a:application>
 * ```
 * 
 * #### Remarks
 *
 * This component is used in the accordian element to create its sections. In
 * the statusbar, the panel element is an alias of `bar`.
 *
 * @class apf.splitbutton
 * @define splitbutton
 * @container
 * @allowchild button
 * @allowchild {elements}, {anyaml}
 *
 * @inherits apf.GuiElement
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
/**
 * @attribute {String} icon Sets or gets the url pointing to the icon image.
 */
/**
 * @attribute {Boolean} [collapsed=false]  Sets or gets collapse panel on load.
 */
/**
 * @attribute {String} title   Describes the content in a panel
 */
apf.splitbutton = function(struct, tagName) {
    this.$init(tagName || "splitbutton", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$focussable = false;

    this.$booleanProperties["disabled-split"] = 1;
    //this.$supportedProperties.push("disabled-split", "button-skin");

    this.$propHandlers["caption"] = function(value) {
        this.$button1.setProperty("caption", value);
    };

    this.$propHandlers["icon"] = function(value) {
        this.$button1.setProperty("icon", value);
    };

    this.$propHandlers["tooltip"] = function(value) {
        this.$button1.setProperty("tooltip", value);
    };

    this.$propHandlers["hotkey"] = function(value) {
        this.$button1.setProperty("hotkey", value);
    };

    this.$propHandlers["disabled"] = function(value) {
        this.$button1.setProperty("disabled", value);
        this.$button2.setProperty("disabled", value);
    };

    this.$propHandlers["disabled-split"]= function(value) {
        this.$button2.setProperty("disabled", value);
    };

    this.$propHandlers["button-skin"] = function(value) {
        this.$button1.setProperty("skin", value);
        this.$button2.setProperty("skin", value);
    };

    this.$propHandlers["class"] = function(value) {
        apf.setStyleClass(this.$ext, value, this.$lastClassValue ? [this.$lastClassValue] : null);
        this.$lastClassValue = value;
    };

    this.$propHandlers["submenu"] = function(value) {
        this.$button2.setProperty("submenu", value);

        var _self = this;
        this.$button2.addEventListener("mousedown", function() {
            var menu = self[value] || value;
            
            if (!menu.$splitInited) {
                _self.dispatchEvent("submenu.init");
                menu.addEventListener("display", function(){
                    var split = this.opener.parentNode;
                    var diff = apf.getAbsolutePosition(split.$button2.$ext)[0]
                        - apf.getAbsolutePosition(split.$button1.$ext)[0];

                    this.$ext.style.marginLeft = "-" + diff + "px";
                });
                menu.$splitInited = true;
            }

            this.removeEventListener("mousedown", arguments.callee);
        });
    };

    this.$draw = function(){
        var _self = this;
        this.$ext = this.$pHtmlNode.appendChild(document.createElement("div"));

        var skin = this["button-skin"] || this.getAttribute("skin") || this.localName;

        this.$button1 = new apf.button({
            htmlNode: this.$ext,
            parentNode: this,
            skinset: this.getAttribute("skinset"),
            skin: skin,
            "class": "main",
            onmouseover: function() {
                apf.setStyleClass(this.$ext, "primary");
                if (_self.$button2.disabled)
                    return;
                _self.$button2.$setState("Over", {});

                _self.dispatchEvent("mouseover", { button: this });
            },
            onmouseout: function() {
                apf.setStyleClass(this.$ext, "", ["primary"]);
                if (_self.$button2.disabled)
                    return;
                _self.$button2.$setState("Out", {});

                _self.dispatchEvent("mouseout", { button: this });
            },
            onclick: function(e) {
                _self.dispatchEvent("click");
            }
        });

        this.$button2 = new apf.button({
            htmlNode: this.$ext,
            parentNode: this,
            skinset: this.getAttribute("skinset"),
            skin: skin,
            "class": "arrow",
            onmouseover: function() {
                apf.setStyleClass(this.$ext, "primary");
                _self.$button1.$setState("Over", {});

                _self.dispatchEvent("mouseover", { button: this });
            },
            onmouseout: function() {
                if (!_self.$button2.value) {
                    apf.setStyleClass(this.$ext, "", ["primary"]);
                    _self.$button1.$setState("Out", {});
                }
                else {
                    apf.setStyleClass(this.$ext, "primary");
                    _self.$button1.$setState("Over", {});
                }

                _self.dispatchEvent("mouseout", { button: this });
            },
            onclick: function(e) {
                _self.dispatchEvent("split.click", e);
            }
        });
    };

    this.$loadAml = function(x) {

    };

}).call(apf.splitbutton.prototype = new apf.GuiElement());

apf.aml.setElement("splitbutton",  apf.splitbutton);






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @constructor
 * @private
 */
apf.splitter = function(struct, tagName) {
    this.$init(tagName || "splitter", apf.NODE_VISIBLE, struct);
};

(function() {
    this.minwidth = 0;
    this.minheight = 0;
    
    this.$scale = 0; // 0 both, 1 left/top, 2 right/bottom 
    
    this.$focussable = false; // This object can get the focus
    this.$splitter = true;
    
    this.$booleanProperties["realtime"] = true;
    
    this.$propHandlers["realtime"] = function(value) {
        this.$setStyleClass(this.$ext, value && (this.$baseCSSname + "Realtime") || "", 
            [this.$baseCSSname + "Realtime"]);
    }
    
    this.$propHandlers["scale"] = function(value) {
        this.$scale = value == "left" || value == "top"
            ? 1 : (value == "right" || "bottom " 
                ? 2 : 0);
    }
    
    this.$propHandlers["parent"] = function(value) {
        this.$parent = typeof value == "object" ? value : self[value];
    }
    
    this.$propHandlers["type"] = function(value) {
        this.$setStyleClass(this.$ext, value,
            [value == "horizontal" ? "vertical" : "horizontal"]);
        
        if (value == "vertical")
            this.$setStyleClass(this.$ext, "w-resize", ["n-resize"]);
        else
            this.$setStyleClass(this.$ext, "n-resize", ["w-resize"]);

        //Optimize this to not recalc for certain cases
        if (value == "horizontal") {
            this.$info = {
                pos: "top",
                opos: "left",
                size: "width",
                osize: "height",
                offsetPos: "offsetTop",
                offsetSize: "offsetHeight",
                oOffsetPos: "offsetLeft",
                oOffsetSize: "offsetWidth",
                clientPos: "clientY",
                d1          : 1,
                d2          : 0,
                x1          : 0,
                x2          : 2
            };
        }
        else {
            this.$info = {
                pos: "left",
                opos: "top",
                size: "height",
                osize: "width",
                offsetPos: "offsetLeft",
                offsetSize: "offsetWidth",
                oOffsetPos: "offsetTop",
                oOffsetSize: "offsetHeight",
                clientPos: "clientX",
                d1          : 0,
                d2          : 1,
                x1          : 3,
                x2          : 1
            }
        }
    }
    
    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget != this)
            return;
        
        /*if (e.$oldParent) {
            e.$oldParent.removeEventListener("DOMNodeInserted", this.$siblingChange);
            e.$oldParent.removeEventListener("DOMNodeRemoved", this.$siblingChange);
        }*/
        
        this.init && this.init();
    });
    
    /*this.$siblingChange = function(e) {
        //if (e.currentTarget
        
        //this.init();
    }*/
    
    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();

        var template = "vbox|hbox".indexOf(this.parentNode.localName) > -1
            ? "box" : "splitbox";
            
        apf.extend(this, apf.splitter.templates[template]);
        this.decorate();
    };
        
    this.$loadAml = function(x) {
        if (this.realtime !== false) // && (!apf.isIE || apf.isIE > 8))
            this.$propHandlers.realtime.call(this, this.realtime = true);
    };
}).call(apf.splitter.prototype = new apf.Presentation());

apf.splitter.templates = {
    box: {
        update: function(newPos, finalPass) {
            with (this.$info) {
                //var pos = Math.ceil(apf.getAbsolutePosition(this.$ext, this.parentNode.$int)[d1] - posPrev[d1]);
                var max = this.$previous 
                    ? this.$previous.$ext[offsetSize] + this.$next.$ext[offsetSize]
                    : (this.parentNode).getWidth();
                var method = finalPass ? "setAttribute" : "setProperty";
                if (apf.hasFlexibleBox)
                    newPos -= this.$previous ? apf.getAbsolutePosition(this.$previous.$ext, this.parentNode.$int)[d1] : 0;
    
                //Both flex
                if (this.$previous && this.$next && (this.$previous.flex || this.$previous.flex === 0) && (this.$next.flex || this.$next.flex === 0)) {
                    if (!finalPass && !this.realtime) 
                        newPos -= this.$ext[offsetSize];
    
                    //var totalFlex = this.$previous.flex + this.$next.flex - (finalPass && !this.realtime ? this.parentNode.padding : 0);
                    if (!this.$scale || this.$scale == 1)
                        this.$previous[method]("flex", newPos);
                    if (!this.$scale || this.$scale == 2)
                        this.$next[method]("flex", this.$totalFlex - newPos);
                }
                //Fixed
                else {
                    if (this.$next && !this.$next.flex && (!this.$scale || this.$scale == 2))
                        this.$next[method](osize, max - newPos);
                    if (this.$previous && !this.$previous.flex && (!this.$scale || this.$scale == 1))
                        this.$previous[method](osize, newPos);
                }
            }
    
            if (apf.hasSingleResizeEvent)
                apf.layout.forceResize(this.$ext.parentNode);
        },
        
        $setSiblings: function(){
            this.$previous = this.previousSibling;
            while (this.$previous && (this.$previous.nodeType != 1 
              || this.$previous.visible === false 
              || this.$previous.nodeFunc != apf.NODE_VISIBLE))
                this.$previous = this.$previous.previousSibling;
            this.$next = this.nextSibling;
            while (this.$next && (this.$next.nodeType != 1 
              || this.$next.visible === false 
              || this.$next.nodeFunc != apf.NODE_VISIBLE))
                this.$next = this.$next.nextSibling;
        },
        
        init: function(size, refNode, oItem) {
            //this.parentNode.addEventListener("DOMNodeInserted", this.$siblingChange);
            //this.parentNode.addEventListener("DOMNodeRemoved", this.$siblingChange);
            
            this.$setSiblings();
            
            this.$thickness = null;
            if (this.parentNode && this.parentNode.$box) {
                this.setProperty("type", this.parentNode.localName == "vbox" 
                    ? "horizontal" 
                    : "vertical");
                this.$thickness = parseInt(this.parentNode.padding);
            }
            
            if (!this.$previous || !this.$next)
                return this;
            
            with (this.$info) {
                var diff = apf.getDiff(this.$ext);
                if (!this.parentNode.$box) {
                    var iSize = Math.max(
                        this.$previous.$ext[offsetSize], this.$next.$ext[offsetSize]);
                    this.$ext.style[size] = (iSize - diff[d1]) + "px";
                }
    
                var iThick = this[osize] = this.$thickness 
                    || (this.$next[oOffsetPos] - this.$previous[oOffsetPos] 
                        - this.$previous[oOffsetSize]);
    
                this.$ext.style[osize] = (iThick - diff[d2]) + "px";
            }
            
            return this;
        },
        
        decorate: function(){
            var _self = this;
            this.$ext.onmousedown = function(e) {
                if (!e)
                    e = event;
                
                if (_self.dispatchEvent("dragstart") === false)
                    return;
                
                apf.dragMode = true; //prevent selection
                
                _self.$setSiblings();
    
                var changedPosition, pHtml = _self.parentNode.$int, diff = 0;
                if ("absolute|fixed|relative".indexOf(apf.getStyle(pHtml, "position")) == -1) {
                    pHtml.style.position = "relative";
                    changedPosition = true;
                }
    
                _self.$totalFlex = 0;
                with (_self.$info) {
                    if (_self.$parent) {
                        if (!_self.$previous) {
                            var posNext = apf.getAbsolutePosition(_self.$next.$ext, _self.parentNode.$int);
                            var wd = _self.$parent.getWidth();
                            
                            if (_self.$scale == 2) {
                                var max = posNext[d1] + _self.$next.$ext[offsetSize] - this[offsetSize];
                                diff = (_self.parentNode.$int[offsetSize] - max);
                                var min = max - wd - diff;
                            }
                        }
                        else if (!_self.$next) {
                            //@todo
                        }
                    }
                    else {
                        if (_self.$previous) {
                            var posPrev = apf.getAbsolutePosition(_self.$previous.$ext, _self.parentNode.$int);
                            var min = _self.$scale 
                                ? 0 
                                : (posPrev[d1] || 0) + (parseInt(_self.$previous.minwidth) || 0);
                        }
                        if (_self.$next) {
                            var posNext = apf.getAbsolutePosition(_self.$next.$ext, _self.parentNode.$int);
                            var max = posNext[d1] + _self.$next.$ext[offsetSize] 
                                - this[offsetSize] - (parseInt(_self.$next.minwidth) || 0);
                        }
                    }
                    
                    //Set flex to pixel sizes
                    if (_self.$previous && _self.$next) {
                        if ((_self.$previous.flex || _self.$previous.flex === 0) 
                          && (_self.$next.flex || _self.$next.flex === 0)) {
                            var set = [], nodes = _self.parentNode.childNodes, padding = 0;
                            for (var node, i = 0, l = nodes.length; i < l; i++) {
                                if ((node = nodes[i]).visible === false 
                                  || node.nodeFunc != apf.NODE_VISIBLE || node.$splitter)
                                    continue;
                                
                                if (node.flex)
                                    set.push(node, node.$ext[offsetSize] 
                                        + (apf.hasFlexibleBox && !_self.realtime && node == _self.$previous 
                                            ? 2 * _self.parentNode.padding : 0));
                            }
                            for (var i = 0, l = set.length; i < l; i+=2) {
                                set[i].setAttribute("flex", set[i+1]);
                            }
                        }
                        
                        _self.$totalFlex += _self.$next.flex + _self.$previous.flex;
                    }
                    
                    var startPos, startOffset;
                    if (apf.hasFlexibleBox) {
                        var coords = apf.getAbsolutePosition(this);
                        startPos = e[clientPos] - coords[d1];
    
                        if (!_self.realtime) {
                            if (_self.$previous.flex && !_self.$next.flex) {
                                var mBox = apf.getBox(_self.$next.margin);
                                mBox[x1] = _self.parentNode.padding;
                                _self.$next.$ext.style.margin = mBox.join("px ") + "px";
                            }
                            else {
                                var mBox = apf.getBox(_self.$previous.margin);
                                mBox[x2] = _self.parentNode.padding;
                                _self.$previous.$ext.style.margin = mBox.join("px ") + "px";
                            }
                            
                            var diff = apf.getDiff(this);
                            this.style.left = coords[0] + "px";
                            this.style.top = coords[1] + "px"; //(apf.getHtmlTop(this) - Math.ceil(this.offsetHeight/2))
                            this.style.width = (this.offsetWidth - diff[0]) + "px";
                            this.style.height = (this.offsetHeight - diff[1]) + "px";
                            this.style.position = "absolute";
                        }
                    }
                    else {
                        var coords = apf.getAbsolutePosition(this.offsetParent);
                        startOffset = apf.getAbsolutePosition(_self.$previous.$ext)[d1];
                        startPos = e[clientPos] - coords[d1];
                        
                        if (!_self.realtime) {
                            this.style.left = "0px";
                            this.style.top = "0px";
                            this.style.position = "relative";
                        }
                        min = -1000; //@todo
                    }
                }
                
                //e.returnValue = false;
                //e.cancelBubble = true;
                //apf.stopEvent(e);
                
                
                apf.plane.show(this);
                
    
                _self.$setStyleClass(this, _self.$baseCSSname + "Moving");
                
                _self.$setStyleClass(document.body,
                    _self.type == "vertical" ? "w-resize" : "n-resize",
                    [_self.type == "vertical" ? "n-resize" : "w-resize"]);
                
                //@todo convert to proper way
                document.onmouseup = function(e) {
                    if (!e) e = event;
                    
                    with (_self.$info) {
                        var newPos;
                        if (e[clientPos] >= 0) {
                            var coords = apf.getAbsolutePosition(_self.$ext.offsetParent);
                            newPos = (Math.min(max, Math.max(min, (e[clientPos] - coords[d1]) - 
                                (apf.hasFlexibleBox ? startPos : startOffset)))) + diff;
                        }
                    }
    
                    _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Moving"]);
                    _self.$setStyleClass(document.body, "", ["n-resize", "w-resize"]);
                    
                    if (changedPosition)
                        pHtml.style.position = "";
                    
                    if (apf.hasFlexibleBox && !_self.realtime)
                        (_self.$previous.flex && !_self.$next.flex
                          ? _self.$next : _self.$previous).$ext.style.margin 
                            = apf.getBox(_self.$previous.margin).join("px ") + "px";
                    
                    if (newPos)
                        _self.update(newPos, true);
                    
                    
                    apf.plane.hide();
                    
                    
                    if (!_self.realtime) {
                        _self.$ext.style.left = "";
                        _self.$ext.style.top = "";
                        _self.$ext.style[_self.$info.size] = "";
                        _self.$ext.style.position = "";
                    }
                    
                    _self.dispatchEvent("dragdrop");
                    
                    document.onmouseup = 
                    document.onmousemove = null;
                    
                    apf.dragMode = false; //return to default selection policy
                };
                
                //@todo convert to proper way
                document.onmousemove = function(e) {
                    if (!e) e = event;
            
                    with (_self.$info) {
                        var newPos;
                        if (e[clientPos] >= 0) {
                            var coords = apf.getAbsolutePosition(_self.$ext.offsetParent);
                            newPos = (Math.min(max, Math.max(min, (e[clientPos] - coords[d1]) - 
                                (apf.hasFlexibleBox || !_self.realtime ? startPos : startOffset)))) + diff;
    
                            if (_self.realtime)
                                _self.update(newPos);
                            else {
                                _self.$ext.style[pos] = newPos + "px";
                            }
                        }
                    }
                    
                    apf.stopEvent(e);
                    //e.returnValue = false;
                    //e.cancelBubble = true;
                    
                    _self.dispatchEvent("dragmove");
                };
            }
            
            apf.queue.add("splitter" + this.$uniqueId, function(){
                _self.init();
            });
        }
    },
    
    splitbox: {
        update: function(newPos, finalPass) {
            this[this.parentNode.$vbox ? "updateV" : "updateH"](newPos, finalPass);
        },
        
        updateV: function(newPos, finalPass) {
            var method = finalPass ? "setAttribute" : "setProperty";
            
            var pNode = this.$parent || this.parentNode;
            var firstChild = pNode.firstChild;
            if (firstChild.localName == "splitter")
                firstChild = firstChild.nextSibling;
            if (pNode.fixedChild) {
                if (pNode.fixedChild == pNode.firstChild) {
                    pNode.fixedChild[method]("height", newPos - pNode.$edge[0]);
                }
                else {
                    pNode.fixedChild[method]("height", 
                        apf.getHtmlInnerHeight(pNode.$int) - newPos 
                        - pNode.padding - pNode.$edge[1]);
                }
            }
            else if (firstChild.height) {
                var total = apf.getHtmlInnerHeight(pNode.$int);
                firstChild[method]("height", 
                    ((newPos - pNode.$edge[0])/total*100) + "%");
            }
            else {
                var total = apf.getHtmlInnerHeight(pNode.$int) ;
                pNode.lastChild[method]("height", 
                    ((total - newPos - pNode.$edge[2] - pNode.padding)/total*100) + "%");
            }
    
            apf.dispatchEvent("splitter.resize", { splitter: this, final: finalPass });
        },
        
        updateH: function(newPos, finalPass) {
            var method = finalPass ? "setAttribute" : "setProperty";

            var pNode = this.$parent || this.parentNode;
            var firstChild = pNode.firstChild;
            if (firstChild.localName == "splitter")
                firstChild = firstChild.nextSibling;
            if (pNode.fixedChild) {
                if (pNode.fixedChild == pNode.firstChild) {
                    pNode.fixedChild[method]("width", newPos - pNode.$edge[3]);
                }
                else {
                    pNode.fixedChild[method]("width", 
                        apf.getHtmlInnerWidth(pNode.$int) - newPos 
                        - pNode.padding - pNode.$edge[2]);
                }
            }
            else if (firstChild.width) {
                var total = apf.getHtmlInnerWidth(pNode.$int);
                firstChild[method]("width", 
                    ((newPos - pNode.$edge[3])/total*100) + "%");
            }
            else {
                var total = apf.getHtmlInnerWidth(pNode.$int) ;
                pNode.lastChild[method]("width", 
                    ((total - newPos - pNode.$edge[1] - pNode.padding)/total*100) + "%");
            }
    
            apf.dispatchEvent("splitter.resize", { splitter: this, final: finalPass });
        },
        
        $setSiblings: function(){
            this.$previous = this.parentNode.firstChild
            this.$next = this.parentNode.lastChild;
        },
        
        decorate: function(){
            var _self = this;
            
            if (this.parentNode && this.parentNode.$box) {
                this.setProperty("type", this.parentNode.$vbox
                    ? "horizontal" 
                    : "vertical");
            }
            
            this.$ext.onmousedown = function(e) {
                if (!e)
                    e = event;

                if (_self.dispatchEvent("dragstart") === false)
                    return;

                apf.dragMode = true; //prevent selection
                
                _self.$setSiblings();

                var pNode = _self.$parent || _self.parentNode;
                var firstChild = pNode.firstChild.$splitter ? pNode.childNodes[1] : pNode.firstChild;
                if (pNode.$vbox) {
                    var min = parseInt(firstChild.minheight) + pNode.$edge[0];
                    var max = apf.getHtmlInnerHeight(pNode.$ext) - pNode.lastChild.minheight 
                        - pNode.$edge[2] - pNode.padding;
                    var offset = e.layerY || e.offsetY;
                }
                else {
                    var min = parseInt(firstChild.minwidth) + pNode.$edge[3];
                    var max = apf.getHtmlInnerWidth(pNode.$ext) - pNode.lastChild.minwidth 
                        - pNode.$edge[1] - pNode.padding;
                    var offset = e.layerX || e.offsetX;
                }
                
                function update(e, final) {
                    var newPos, coords;
                    if (pNode.$vbox) {
                        if (e.clientY >= 0) {
                            coords = apf.getAbsolutePosition(_self.$parent ? _self.$parent.$ext : _self.$ext.offsetParent);
                            newPos = Math.min(max, Math.max(min, (e.clientY - coords[1] - offset)));
                        }
                    }
                    else {
                        if (e.clientX >= 0) {
                            coords = apf.getAbsolutePosition(_self.$parent ? _self.$parent.$ext : _self.$ext.offsetParent);
                            newPos = Math.min(max, Math.max(min, (e.clientX - coords[0] - offset)));
                        }
                    }
                    
                    if (!newPos) return;
                    
                    if (_self.realtime || final)
                        _self.update(newPos, final);
                    else {
                        _self.$ext.style[pNode.$vbox ? "top" : "left"] = newPos + "px";
                    }
                }
    
                
                apf.plane.show(this);
                
    
                _self.$setStyleClass(this, _self.$baseCSSname + "Moving");
                
                _self.$setStyleClass(document.body,
                    _self.type == "vertical" ? "w-resize" : "n-resize",
                    [_self.type == "vertical" ? "n-resize" : "w-resize"]);
                
                //@todo convert to proper way
                document.onmouseup = function(e) {
                    if (!e) e = event;
                    
                    _self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Moving"]);
                    _self.$setStyleClass(document.body, "", ["n-resize", "w-resize"]);
                    
                    update(e, true);
                    
                    
                    apf.plane.hide();
                    
                    
                    if (!_self.realtime) {
                        _self.$ext.style.left = "";
                        _self.$ext.style.top = "";
                        _self.$ext.style[_self.$info.size] = "";
                        _self.$ext.style.position = "";
                    }
                    
                    _self.dispatchEvent("dragdrop");
                    
                    document.onmouseup = 
                    document.onmousemove = null;
                    
                    apf.dragMode = false; //return to default selection policy
                };
                
                //@todo convert to proper way
                document.onmousemove = function(e) {
                    if (!e) e = event;
            
                    update(e);
                    
                    apf.stopEvent(e);
                    //e.returnValue = false;
                    //e.cancelBubble = true;
                    
                    _self.dispatchEvent("dragmove");
                };
            }
        }
    }
};

apf.aml.setElement("splitter", apf.splitter);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * An element that groups state elements together and
 * provides a way to set a default state.
 * 
 * #### Example
 *
 * ```xml
 *  <a:state-group
 *    loginMsg.visible = "false"
 *    winLogin.disabled = "false">
 *      <a:state id="stFail"
 *          loginMsg.value = "Username or password incorrect"
 *          loginMsg.visible = "true" />
 *      <a:state id="stError"
 *          loginMsg.value = "An error has occurred. Please check your network."
 *          loginMsg.visible = "true" />
 *      <a:state id="stLoggingIn"
 *          loginMsg.value = "Please wait while logging in..."
 *          loginMsg.visible = "true"
 *          winLogin.disabled = "true" />
 *      <a:state id="stIdle" />
 *  </a:state-group>
 * ```
 *
 * @class apf.stateGroup
 * @define state-group
 * @logic
 * @inherits apf.AmlElement
 *
 * @see apf.state
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 */
apf.stateGroup = function(){
    this.$init("state-group", apf.NODE_HIDDEN);
};
apf.aml.setElement("state-group", apf.stateGroup);

(function(){
    this.$handlePropSet = function(prop, value, force) {
        if (prop == "id")
            return;
        
        var node, nodes = this.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++){
            node = nodes[i];
        
            if (node.nodeType != 1 || node.localName != "state")
                continue;

            if (!node[prop] || node.$inheritProperties[prop] == 2) {
                node.$inheritProperties[prop] = 2;
                node.setProperty(prop, value);
            }
        }
    };
    
    //@todo this should use text node insertion
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        if (!this.id)
            this.id = "stategroup" + this.$uniqueId;
        
        //apf.StateServer.addGroup(this.id, null, this.parentNode); //@todo rearch this
        
        var nodes = this.childNodes;
        for (var i = 0, l = nodes.length; i < l; i++){
            nodes[i].setProperty("group", this.id);
        }
    });
}).call(apf.stateGroup.prototype = new apf.AmlElement());






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * @private
 */
apf.StateServer = {
    states: {},
    groups: {},
    locs: {},

    removeGroup: function(name, elState) {
        this.groups[name].remove(elState);
        if (!this.groups[name].length) {
            if (self[name]) {
                self[name].destroy();
                self[name] = null;
            }

            delete this.groups[name];
        }
    },

    addGroup: function(name, elState, pNode) {
        if (!this.groups[name]) {
            this.groups[name] = [];

            var pState = new apf.state({
                id: name
            });
            pState.parentNode = pNode;
            //pState.implement(apf.AmlNode);
            //pState.name = name;
            pState.toggle = function(){
                for (var next = 0, i = 0; i < apf.StateServer.groups[name].length; i++) {
                    if (apf.StateServer.groups[name][i].active) {
                        next = i + 1;
                        break;
                    }
                }

                apf.StateServer.groups[name][
                    (next == apf.StateServer.groups[name].length) ? 0 : next
                  ].activate();
            }

            this.groups[name].pState = self[name] = pState;
        }

        if (elState)
            this.groups[name].push(elState);

        return this.groups[name].pState;
    },

    removeState: function(elState) {
        delete this.states[elState.name];
    },

    addState: function(elState) {
        this.states[elState.name] = elState;
    }
}

/**
 * This element specifies a certain state of (a part of) the application. By
 * "state", we mean a collection of properties on objects that have a certain
 * value at one time. 
 * 
 * This element allows you to specify which properties on
 * which elements should be set when a state is activated. This element can
 * belong to a state-group containing multiple elements with a default state.
 * 
 * #### Example
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:state 
 *     group = "stRole" 
 *     id = "stUser" 
 *     caption = "You are a user" 
 *     active = "true" />
 *   <a:state 
 *      group = "stRole" 
 *      id = "stAdmin" 
 *      caption = "You have super powers" />
 *   <a:label caption="{stRole.caption}" />
 *   <a:hbox height="34" width="200" margin="10 0 0 0">
 *       <a:button 
 *         width = "100" 
 *         onclick = "stUser.activate()">State - User</a:button>
 *       <a:button 
 *         width = "100" 
 *         onclick = "stAdmin.activate()">State - Admin</a:button>
 *   </a:hbox>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.state
 * @define state
 *
 * @logic
 * @inherits apf.AmlElement
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 */
/**
 * @event change Fires when the active property of this element changes.
 *
 */
apf.state = function(struct, tagName) {
    this.$init(tagName || "state", apf.NODE_HIDDEN, struct);
    
    this.$signalElements = [];
    this.$groupAdded = {};
    this.$locationAdded = '';
};

(function(){
    // *** Properties and Attributes *** //

    this.$supportedProperties.push("active");
    this.$booleanProperties["active"] = true;
    
    /**
     * @attribute {Boolean} active whether this state is the active state
     */
    this.$propHandlers["active"] = function(value) {
        //Activate State
        if (apf.isTrue(value)) {
            if (this.group) {
                var nodes = apf.StateServer.groups[this.group];
                if (!nodes) {
                    apf.StateServer.addGroup(this.group, this);
                    nodes = apf.StateServer.groups[this.group];
                }
                
                for (var i = 0; i < nodes.length; i++) {
                    if (nodes[i] != this && nodes[i].active !== false)
                        nodes[i].deactivate();
                }
            }

            var q = this.$signalElements;
            for (var i = 0; i < q.length; i++) {
                if (!self[q[i][0]] || !self[q[i][0]].setProperty) {
                    
                    
                    continue;
                }

                self[q[i][0]].setProperty(q[i][1], this[q[i].join(".")]);
            }

            if (this.group) {
                var attr = this.attributes;
                for (var i = 0; i < attr.length; i++) {
                    if (attr[i].nodeName.match(/^on|^(?:group|id)$|^.*\..*$/))
                        continue;
                    self[this.group].setProperty(attr[i].nodeName,
                        attr[i].nodeValue);
                }
                apf.StateServer.groups[this.group].pState.dispatchEvent("change");
            }

            this.dispatchEvent("activate");

            
        }

        //Deactivate State
        else {
            this.setProperty("active", false);
            this.dispatchEvent("deactivate");

            
        }
    };


    // *** Public methods *** //

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the values attribute.
     * @param {String} value Sets the new value of this element
     */
    this.setValue = function(value) {
        this.active = 9999;
        this.setProperty("active", value, false, true);
    };

    /**
     * Actives this state, setting all the properties on the elements that
     * were specified.
     */
    this.activate = function(){
        this.active = 9999;
        this.setProperty("active", true, false, true);
    };

    /**
     * Deactivates the state of this element. This is mostly a way to let all
     * elements that have property bound to this state know it is no longer
     * active.
     */
    this.deactivate = function(){
        this.setProperty("active", false, false, true);
    };
    
    

    // *** Init *** //

    this.$propHandlers["group"] = function(value) {  
        if (value) {
            apf.StateServer.addGroup(value, this);
            this.$groupAdded = {'value' : value, elState : this};
        }
        else {
            apf.StateServer.removeGroup(this.$groupAdded.value, this.$groupAdded.elState);
            this.$groupAdded = {};
        }
    }

    this.$propHandlers["location"] = function(value) {
        if (value) {
            apf.StateServer.locs[value] = this;
            this.$locationAdded = value;
        }
        else {
            delete apf.StateServer.locs[this.$locationAdded];
            this.$locationAdded = '';
        }
    }
    
    this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        apf.StateServer.addState(this);

        //Properties initialization
        var attr = this.attributes;
        for (var s, i = 0; i < attr.length; i++) {
            s = attr[i].nodeName.split(".");
            if (s.length == 2)
                this.$signalElements.push(s);
        }
    });

    this.addEventListener("DOMNodeRemovedFromDocument", function(){
        this.$signalElements = null;
        apf.StateServer.removeState(this);
        if (this.group)
            apf.StateServer.removeGroup(this.group, this);
    });
}).call(apf.state.prototype = new apf.AmlElement());

apf.aml.setElement("state", apf.state);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An element displaying a tab and several buttons allowing a
 * user to switch between the pages. Each tab can contain
 * arbitrary AML. Each tab can render its content during
 * startup of the application, or when the tab is activated.
 *
 * #### Example
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 * <!-- startcontent -->
 *  <a:pane id="pane" width="300" height="100">
 *      <a:tab caption="General">
 *          <a:checkbox>Example</a:checkbox>
 *          <a:button>Example</a:button>
 *      </a:tab>
 *      <a:tab caption="Advanced">
 *          <a:checkbox>Test checkbox</a:checkbox>
 *          <a:checkbox>Test checkbox</a:checkbox>
 *          <a:checkbox>Test checkbox</a:checkbox>
 *      </a:tab>
 *      <a:tab caption="Ajax.org">
 *          <a:checkbox>This ok?</a:checkbox>
 *          <a:checkbox>This better?</a:checkbox>
 *      </a:tab>
 *  </a:pane>
 * <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.pane
 * @define pane
 * @container
 * @allowchild tab
 *
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.1
 *
 * @inherits apf.BaseTab
 */

apf["switch"] = function(struct, tagName) {
    this.$hasButtons = false;
    this.$init(tagName || "switch", apf.NODE_VISIBLE, struct);
};

apf.pages = function(struct, tagName) {
    this.$hasButtons = false;
    this.$init(tagName || "pages", apf.NODE_VISIBLE, struct);
    
    this.$focussable = false;
};

apf.pane = function(struct, tagName) {
    this.$hasButtons = true;
    this.$init(tagName || "pane", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$focussable = apf.KEYBOARD; // This object can get the focus from the keyboard

    // *** Init *** //

    this.$draw = function(bSkinChange) {
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$loadChildren();
    };
}).call(apf.pane.prototype = new apf.BaseTab());

apf["switch"].prototype =
apf.pages.prototype = apf.pane.prototype;

apf.aml.setElement("switch", apf["switch"]);
apf.aml.setElement("pages",  apf.pages);
apf.aml.setElement("pane",    apf.pane);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Any child element of this element is placed in a table. The size of the 
 * columns and rows of the table can be set by attributes. Child elements can
 * span multiple columns. Using `'*' `as a size indicator will use the remaining
 * size for that column or row, when the table's size is set.
 * 
 * #### Example
 *
 * This example shows a window with a table and two buttons that change the 
 * orientation of the table runtime. The textarea and its label have a span set
 * to `'*'`. This means they will span the entire width of all columns, no matter
 * how many columns there are.
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 * <a:window visible="true" width="500" height="260" title="Form">
 *  <!-- startcontent -->
 *      <a:table id="tableTest" 
 *        columns = "80, *"
 *        edge = "10 10 10 10"
 *        padding = "5"
 *        bottom = "35"
 *        top = "0">
 *        <a:label>Name</a:label>
 *        <a:textbox />
 *        <a:label>Address</a:label>
 *        <a:textarea height="50" />
 *        <a:label>Country</a:label>
 *        <a:dropdown>
 *            <a:item>America</a:item>
 *            <a:item>Armenia</a:item>
 *            <a:item>The Netherlands</a:item>
 *        </a:dropdown>
 *  
 *        <a:label span="*">Message</a:label>
 *        <a:textarea id="txtMessage" 
 *          height = "*" 
 *          span = "*" />
 *      </a:table>
 * 
 *      <a:button 
 *        caption = "Two Columns"
 *        bottom = "10"
 *        left = "10"
 *        onclick = "tableTest.setAttribute('columns', '80, *');"/>
 * 
 *      <a:button 
 *        bottom = "10"
 *        left = "125"
 *        caption = "Four Columns"
 *        onclick = "tableTest.setAttribute('columns', '60, 120, 60, *');"/>
 *  <!-- endcontent -->
 *  </a:window>
 * </a:application>
 * ```
 *
 * @class apf.table
 * @define table
 * @allowchild {elements}, {anyaml}
 *
 * @container
 * @inherits apf.GuiElement
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       1.0
 */
apf.table = function(struct, tagName) {
    this.$init(tagName || "table", apf.NODE_VISIBLE, struct);
};

(function(){
    // *** Properties and Attributes *** //
    
    this.$focussable = false;
    this.$useLateDom = true; 
    this.$layout = true;
    
    this.columns = null;//"150,200";
    this.padding = 2;
    this.$edge = [5, 5, 5, 5];
    this.cellheight = 19;
    
    /**
     * @attribute {Number | String} columns      Sets or gets a comma seperated list of column sizes. 
     *                                  A column size can be specified in a number (size in pixels), or using a number and a `"%"` sign to indicate a percentage. 
     *                                  A `'*'` indicates the column spans the rest space. There can be only one `'*'` in the column string.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:table columns="150, *, 20%" />
     * ```
     *
     */
    /**
     * @attribute {String} [padding=2]     Sets or gets the space between each element.
     */
    /**
     * @attribute {String} [edge="5 5 5 5"]        Sets or gets the space between the container and the elements, space seperated in pixels for each side. Similar to CSS in the sequence of `top right bottom left`.
     * 
     * #### Example
     * 
     * ```xml
     *  <a:table edge="10 10 40 10" />
     * ````
     */
    this.$supportedProperties.push("columns", "padding", "edge", 
        "cellheight", "span");
    
    this.$propHandlers["columns"] = function(value) {
        if (!value.match(/^((?:\d+\%?|\*)\s*(?:,\s*|\s*$))+$/)) {
            
            return;
        }
        
        var col, colsize = this.$columns = value.splitSafe(",");

        var total = 0, cols = this.$table.getElementsByTagName("col");
        if (cols.length) {
            for (var sz, i = 0, l = Math.min(cols.length, colsize.length); i < l; i++) {
                cols[i].style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px";
                total += parseInt(sz);
            }
        }
        
        var start = cols.length - colsize.length;
        if (start > 0) {
            for (var i = cols.length - start; i < cols.length; i++) {
                cols[i].parentNode.removeChild(cols[i]);
            }
        }
        else if (start < 0) {
            for (var i = colsize.length + start; i < colsize.length; i++) {
                col = this.$table.appendChild(document.createElement("col"));
                col.style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px";
                col.setAttribute("valign", "top");
                total += parseInt(sz);
            }
        }

        this.$table.style.width = String(value).indexOf("%") > -1 
            ? "auto" 
            : (total + ((colsize.length - 1) * this.padding) 
                + this.$edge[0] + this.$edge[2]) + "px";

        var cells = this.$tbody.firstChild.getElementsByTagName("td");
        for (var i = cells.length - 1; i >= 0; i--)
            cells[i].parentNode.removeChild(cells[i]);
        
        for (var sz, c, i = 0; i < colsize.length; i++) {
            c = this.$tbody.firstChild.appendChild(document.createElement("td"));
            c.style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px";
            
            /*if (colsize[i].indexOf("%") > -1)
                c.appendChild(document.createElement("div")).style.width = "50px";*/
        }
        
        if (start && this.$amlLoaded)
            visibleHandler({sync: true, parentNode: this});
        
        this.$resize();
    }

    this.$propHandlers["padding"] = function(value) {
        if (!this.$columns) return;
        var cells = this.$table.getElementsByTagName("td");
        var lastCol, lastRow, cell, lRow = this.$tbody.lastChild;
        for (var i = this.$columns.length, l = cells.length; i < l; i++) {
            lastCol = (cell = cells[i]).parentNode.lastChild == cell;
            lastRow = cell.parentNode == lRow;
            cell.style.padding = "0px " + (lastCol ? 0 : value) + "px " + (lastRow ? 0 : value) + "px 0px";
        }
        this.$resize();
    }
    
    this.$propHandlers["edge"] = function(value) {
        this.$table.style.padding = (this.$edge = apf.getBox(value)).join("px ") + "px";
        this.$resize();
    }
    
    function visibleHandler(e) {
        var table = e.parentNode || this.parentNode;
        if (e.sync || e.value && !this.$altExt || !e.value && this.$altExt) {
            var nodes = table.childNodes;

            var cells = apf.getArrayFromNodelist(table.$tbody.getElementsByTagName("td"));
            var rows = table.$tbody.getElementsByTagName("tr");
            var empty = [], row = 1, cs, rs, collen = table.$columns.length;
            var z = table.$columns.length, lastCol;
            for (var node, td, last, l = nodes.length, i = 0; i < l; i++) {
                if ((node = nodes[i]).visible === false)
                    continue;

                td = node.$altExt = last = cells[z++];
                if (!td) break;
                    //td = node.$altExt = last = document.createElement("td");
                
                if (!rows[row])
                    table.$tbody.appendChild(document.createElement("tr"));
                
                rows[row].appendChild(td);
                td.appendChild(node.$ext);
                td.setAttribute("colspan", cs = Math.min(collen - (empty[0] || 0), parseInt(node.colspan || node.span || 1)));
                td.setAttribute("rowspan", rs = parseInt(node.rowspan || 1));
                
                //@todo this is wrong it should be cs * rs
                if (!empty[0])
                    empty[0] = 0;
                empty[0] += cs;

                if (rs > 1) {
                    for (var k = 1; k < rs; k++) {
                        if (!empty[k])
                            empty[k] = 0;
                        empty[k] += cs;
                    }
                }

                if (empty[0] >= collen) {
                    lastCol = true;
                    empty.shift();
                    row++;
                }
                else lastCol = false;

                td.style.padding = "0px " + (lastCol ? 0 : table.padding) 
                    + "px " + (i == l - 1 ? 0 : table.padding) + "px 0px";
            }
            
            //Fix padding of last row
            var lastCells = rows[rows.length - 1].getElementsByTagName("td");
            for (i = 0, il = lastCells.length; i < il; i++) {
                lastCells[i].style.padding = "0 " 
                    + (i == il - 1 ? 0 : table.padding) + "px 0 0"
            } 
            
            for (;z < cells.length; z++)
                cells[z].parentNode.removeChild(cells[z]);
            
            if (e.sync) return;

            if (e.value)
                table.$addTd(nodes[l - 1]); //what if it's not visible
            else {
                //last.parentNode.removeChild(last);
                this.$altExt = null;
            }
        }
    }
    
    this.$addTd = function(amlNode) {
        var cells = this.$table.getElementsByTagName("td");
        var total = 0, collen = this.$columns.length;
        for (var cell, i = 0; i < cells.length; i++) {
            total +=  Math.min(collen, 
                (parseInt((cell = cells[i]).getAttribute("colspan") || 1) 
                * parseInt(cell.getAttribute("rowspan") || 1)));
        }
        
        if (total % collen == 0) { //New Row
            var row = this.$tbody.appendChild(document.createElement("tr"));
        }
        else
            row = cells[cells.length - 1].parentNode;

        //Add a new cell in the last row
        var cel = row.appendChild(document.createElement("td"));
        cel.style.position = "relative";
        
        if (amlNode.colspan || amlNode.span)
            cel.setAttribute("colspan", amlNode.colspan || amlNode.span);
        if (amlNode.rowspan)
            cel.setAttribute("rowspan", amlNode.rowspan);

        cel.appendChild(amlNode.$ext);

        amlNode.$altExt = cel;
    }
    
    var propHandlers = {
        "width" : function(value) {
            this.$ext.style.width = "";/*value 
                ? Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"
                : "";*/
        },
        
        "height" : function(value) {
            this.$ext.style.height = value 
                ? Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"
                : "";
            this.parentNode.$resize();
        },
        
        "margin" : function(value) {
            this.$ext.style.margin = apf.getBox(value).join("px ") + "px";
            this.parentNode.$resize();
        },
        
        "colspan" : function(value) {
            if (!value)
                this.$altExt.removeAttribute("colspan");
            else
                this.$altExt.setAttribute("colspan", value);

            visibleHandler.call(this, {sync: true});
            this.parentNode.$resize();
        },
        
        "rowspan" : function(value) {
            if (!value)
                this.$altExt.removeAttribute("rowspan");
            else
                this.$altExt.setAttribute("rowspan", value);
        
            visibleHandler.call(this, {sync: true});
            this.parentNode.$resize();
        },
        
        "valign" : function(value) {
            this.$altExt.valign = value;
        },
        
        "align" : function(value) {
            if ("left|right".indexOf(value) == -1)
                return;
            
            this.$altExt.align = value;
        }
    }
    propHandlers.span = propHandlers.colspan;
    
    //@todo move this to enableTable, disableTable
    this.register = function(amlNode) {
        if (amlNode.$altExt) //@todo hack, need to rearch layouting
            return;

        amlNode.$propHandlers["left"] = 
        amlNode.$propHandlers["top"] = 
        amlNode.$propHandlers["right"] = 
        amlNode.$propHandlers["bottom"] = apf.K;
        
        for (var prop in propHandlers) {
            amlNode.$propHandlers[prop] = propHandlers[prop];
        }
        
        amlNode.addEventListener("prop.visible", visibleHandler);

        this.$addTd(amlNode);
        
        this.$noResize = true;
        
        if (amlNode.margin)
            propHandlers.margin.call(amlNode, amlNode.margin);
        
        //Why was this commented out?
        if (amlNode.$ext.tagName == "INPUT") {
            //amlNode.$ext.style.width = "100%";
        }
        else
            amlNode.$ext.style.width = "auto";
        
        if (this.lastChild == amlNode)
            this.$propHandlers["padding"].call(this, this.padding);
        
        delete this.$noResize;
    }
    
    this.unregister = function(amlNode) {
        amlNode.$propHandlers["left"] = 
        amlNode.$propHandlers["top"] = 
        amlNode.$propHandlers["right"] = 
        amlNode.$propHandlers["bottom"] = null;
        
        for (var prop in propHandlers) {
            delete amlNode.$propHandlers[prop];
        }
        
        amlNode.removeEventListener("prop.visible", visibleHandler);
        visibleHandler.call(amlNode, {value: false}); //maybe parent is already reset here?
        
        if (amlNode.margin)
            amlNode.$ext.style.margin = "";
        
        if (amlNode.width)
            amlNode.$ext.style.width = "";
    }
    /*
         this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
        this.register(this.parentNode);
    });
    */
    
    // *** DOM Hooks *** //
    
    this.addEventListener("DOMNodeRemoved", function(e) {
        if (e.$doOnlyAdmin || e.currentTarget == this)
            return;

        if (e.relatedNode == this) {
            this.unregister(e.currentTarget);
            //e.currentTarget.$setLayout();
        }
    });

    this.addEventListener("DOMNodeInserted", function(e) {
        if (e.currentTarget == this || e.currentTarget.nodeType != 1)
            return;

        if (e.relatedNode == this) {
            if (e.$isMoveWithinParent) {
                visibleHandler.call(e.currentTarget, {sync: true}); 
            }
            else {
                e.currentTarget.$setLayout("table");
                if (e.currentTarget.nextSibling)
                    visibleHandler.call(e.currentTarget, {sync: true});
            }
        }
    });
    
    this.$draw = function(){
        this.$ext = apf.insertHtmlNode(null, this.$pHtmlNode, null, 
            "<div><table cellSpacing='0' cellPadding='0'><tbody><tr class='first'></tr></tbody></table></div>");
        this.$table = this.$ext.firstChild;
        this.$tbody = this.$table.firstChild;
        this.$ext.className = "table " + (this.getAttribute("class") || "");
        //this.$ext.style.overflow = "hidden";
        this.$int = this.$ext;
        this.$ext.host = this;

        if (this.getAttribute("class")) 
            apf.setStyleClass(this.$ext, this.getAttribute("class"));
        
        this.addEventListener("resize", this.$resize);
        this.$originalMin = [this.minwidth || 0,  this.minheight || 0];
    };
    
    //@todo implement percentage by using fixed and add functionality here
    this.$resize = function(){
        if (!this.$amlLoaded || this.$noResize)
            return;
        
        if (this.$table.offsetWidth >= this.$ext.offsetWidth)
            this.$ext.style.minWidth = (this.minwidth = Math.max(0, this.$table.offsetWidth 
                - apf.getWidthDiff(this.$ext))) + "px";
        else {
            this.$ext.style.minWidth = "";
            this.minwidth = this.$originalMin[0];
        }

        if (this.$table.offsetHeight >= this.$ext.offsetHeight)
            this.$ext.style.minHeight = (this.minheight = Math.max(0, this.$table.offsetHeight 
                - apf.getHeightDiff(this.$ext))) + "px";
        else {
            this.$ext.style.minHeight = "";
            this.minheight = this.$originalMin[1];
        }
    }
    
    this.$loadAml = function(x) {
        this.$amlLoaded = false; //@todo hack

        if (!this.$columns)
            this.$propHandlers.columns.call(this, this.columns = "150, 200");
        this.$amlLoaded = true; //@todo hack
    };
}).call(apf.table.prototype = new apf.GuiElement());

apf.aml.setElement("table", apf.table);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



apf.aml.setElement("teleport", apf.AmlElement);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * This element displays a rectangle containing arbitrary (X)HTML.
 *
 * This element can be databound and use databounding rules to
 * convert data into (X)HTML using--for instance--XSLT or JSLT.
 *
 * #### Example: Some simple text
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:text width="300">
 *   Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur congue, nunc sed convallis gravida, justo nunc egestas nisi, eu iaculis nunc ipsum vel orci. Morbi mauris urna, rutrum at imperdiet at, molestie eu risus. Curabitur eu tincidunt eros. Donec in massa ut dolor vulputate commodo. Cras pulvinar urna ut ipsum pulvinar mollis sit amet in dui. Nam lobortis ligula sed tortor dapibus eget tincidunt dui pretium. 
 *   </a:text>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * #### Example: Using Scrolldown
 * 
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:text id="txtExample"
 *     width = "300"
 *     height = "100" 
 *     scrolldown = "true">
 *       Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur congue, nunc sed convallis gravida, justo nunc egestas nisi, eu iaculis nunc ipsum vel orci. Morbi mauris urna, rutrum at imperdiet at, molestie eu risus. Curabitur eu tincidunt eros. Donec in massa ut dolor vulputate commodo. Cras pulvinar urna ut ipsum pulvinar mollis sit amet in dui. Nam lobortis ligula sed tortor dapibus eget tincidunt dui pretium. Quisque semper sem dignissim quam ullamcorper et lobortis arcu eleifend. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce ac commodo mi. Pellentesque sit amet magna sed velit volutpat volutpat. Nam lobortis sem sed tortor accumsan dictum. Donec scelerisque rhoncus cursus. Mauris dui dolor, vehicula quis lacinia quis, facilisis et eros. Nulla facilisi. Donec urna velit, adipiscing non sollicitudin at, sodales id lorem. Fusce fringilla, magna id pellentesque egestas, neque risus luctus mauris, vel porttitor libero tortor et augue. Integer euismod porttitor mi, at viverra nulla pharetra vel. Etiam odio elit, volutpat a porttitor eu, posuere nec augue. Phasellus placerat lacus ut augue tempor consectetur. 
 *   </a:text>
 *   <a:button 
 *     onclick="txtExample.setValue(txtExample.getValue() + '&lt;br />A new line!')">
 *       Add a line
 *   </a:button>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.text
 * @define text
 *
 * @form
 * @inherits apf.Cache
 * @inherits apf.StandardBinding
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.1
 * 
 */
// @todo Please refactor this object
apf.text = function(struct, tagName) {
    this.$init(tagName || "text", apf.NODE_VISIBLE, struct);
    
    this.$nodes = [];
};

(function(){
    this.implement(
        
        apf.Cache,
        
        apf.ChildValue
    );

    this.$focussable = true; // This object can't get the focus
    this.focussable = false;
    this.textselect = true;
    this.$hasStateMessages = true;

    this.$textTimer = this.$lastMsg = this.$lastClass = this.$changedHeight = null;

    // *** Properties and Attributes *** //

    /**
     * @attribute {Boolean} scrolldown  Sets or gets whether this element's viewport is always
     *                                  scrolled down. This is especially useful
     *                                  when this element is used to displayed
     *                                  streaming content such as a chat conversation.
     *
     */
    /**
     * @attribute {Boolean} secure      Sets or gets whether the content loaded in this element
     *                                  should be filtered in order for it to not
     *                                  be able to execute JavaScript. This is
     *                                  especially useful when the content does
     *                                  not come from a trusted source, like a
     *                                  web service or xmpp feed.
     */
    this.$booleanProperties["scrolldown"] = true;
    this.$booleanProperties["secure"] = true;
    this.$booleanProperties["textselect"] = true;
    this.$supportedProperties.push("behavior", "scrolldown", "secure", "value");

    this.$isTextInput = function(){
        return this.textselect;
    }

    this.$propHandlers["scrolldown"] = function(value) {
        var _self = this;
        
        if (value) {
            //this.addEventListener("resize", this.$resize);
            this.$scrolldown = true;
            apf.addListener(this.$scrollArea, "scroll", this.$scrollFunc = function(){
                _self.$scrolldown = this.scrollTop >= this.scrollHeight
                    - this.offsetHeight + apf.getVerBorders(this);
            });
            this.addEventListener("scroll", this.$scroll);
            this.addEventListener("afterload", this.$scroll);
            this.addEventListener("resize", function(){
                if (_self.$scrollArea && _self.$scrolldown && _self.scrolldown)
                    _self.$scrollArea.scrollTop = _self.$scrollArea.scrollHeight;
            });
            clearInterval(this.$textTimer);
            this.$textTimer = setInterval(function(){
                if (_self.$scrollArea && _self.$scrolldown && _self.scrolldown)
                    _self.$scrollArea.scrollTop = _self.$scrollArea.scrollHeight;
            }, 200);
        }
        else {
            //this.removeEventListener("resize", this.$resize);
            
            this.removeEventListener("scroll", this.$scroll);
            this.removeEventListener("afterload", this.$scroll);
            clearInterval(this.$textTimer);
            if (this.$scrollArea)
                apf.removeListener(this.$scrollArea, "scoll", this.$scrollFunc);
        }
    }
    
    this.$scroll = function(e) {
        var html = this.$scrollArea;
        
        if (e.name == "afterload") {
            this.$scrolldown = true;
            html.scrollTop = html.scrollHeight;
            return;
        }
        
        this.$scrolldown = html.scrollTop >= html.scrollHeight
            - html.offsetHeight + apf.getVerBorders(html);
    };
    
    /*this.$resize = function(){
        if (this.scrolldown && this.$scrolldown)
            this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight;
    }*/

    /**
     * @attribute {String} value Sets or gets the contents of this element. This can be text, html, xhtml.
     */
    this.$propHandlers["value"] = function(value, prop, force, forceAdd) {
        if (this.each)
            return;
        
        if (typeof value != "string") {
            if (value.nodeType)
                value = value.nodeType > 1 && value.nodeType < 5
                    ? value.nodeValue
                    : value.firstChild && value.firstChild.nodeValue || "";
            else
                value = value ? value.toString() : "";
        }

        if (this.secure) {
            value = value.replace(/<a /gi, "<a target='_blank' ")
                .replace(/<object.*?\/object>/g, "")
                .replace(/<script.*?\/script>/g, "")
                .replace(new RegExp("ondblclick|onclick|onmouseover|onmouseout"
                    + "|onmousedown|onmousemove|onkeypress|onkeydown|onkeyup|onchange"
                    + "|onpropertychange", "g"), "ona");
        }

        value = value.replace(/\<\?xml version="1\.0" encoding="UTF-16"\?\>/, "");
        
        if (forceAdd) {
            apf.insertHtmlNodes(null, this.$container, null, value);
            if (!this.value) this.value = "";
            this.value += value;
        }
        else
            this.$container.innerHTML = value;//.replace(/<img[.\r\n]*?>/ig, "")

        //Iframe bug fix for IE (leaves screen white);
        if (apf.cannotSizeIframe && this.oIframe)
            this.oIframe.style.width = this.oIframe.offsetWidth + "px";

        if (this.scrolldown && this.$scrolldown)
            this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight;
    };
    
    this.$eachHandler = function(value) {
        this.$attrExcludePropBind = apf.extend({}, this.$attrExcludePropBind);
        this.$attrExcludePropBind.value = value ? 2 : 0;
    };
    this.addEventListener("prop.each", this.$eachHandler);
    
    this.addEventListener("$clear", function(){
        this.$container.innerHTML = "";
        this.value = "";
        this.dispatchEvent("prop.value", {value: ""});
    });

    // @todo replace this stub with something that does something
    this.$moveNode = function() {};

    // *** Public methods *** //

    

    this.addValue = function(value) {
        this.$propHandlers["value"].call(this, value, null, null, true);
        this.dispatchEvent("prop.value", {value: this.value});
    };

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        this.setProperty("value", value, false, true);
    };

    /**
     * Returns the current value of this element.
     * @return {String} The current value.
     */
    this.getValue = function(){
        return this.$container.innerHTML;
    };
    
    

    // *** Keyboard Support *** //

    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode;

        switch (key) {
            case 33:
                //PGUP
                this.$container.scrollTop -= this.$container.offsetHeight;
                break;
            case 34:
                //PGDN
                this.$container.scrollTop += this.$container.offsetHeight;
                break;
            case 35:
                //END
                this.$container.scrollTop = this.$container.scrollHeight;
                break;
            case 36:
                //HOME
                this.$container.scrollTop = 0;
                break;
            case 38:
                this.$container.scrollTop -= 10;
                break;
            case 40:
                this.$container.scrollTop += 10;
                break;
            default:
                return;
        }

        return false;
    }, true);
    

    // *** Private methods *** //

    this.$canLoadData = function(){
        return this.$attrBindings.value ? true : false;
    }

    this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode) {
        var f = this.$attrBindings.value.cvalue;
        var html = f(xmlNode);
        html = "<div id='" + Lid + "'>" + html + "</div>";
        if (htmlParentNode) {
            if (beforeNode)
                beforeNode.insertAdjacentHTML("beforebegin", html);
            else 
                this.$container.insertAdjacentHTML("beforeend", html);
            //apf.insertHtmlNode(oItem, htmlParentNode, beforeNode);
            
            //Iframe bug fix for IE (leaves screen white);
            if (apf.cannotSizeIframe && this.oIframe)
                this.oIframe.style.width = this.oIframe.offsetWidth + "px";
    
            if (this.scrolldown && this.$scrolldown)
                this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight;
        }
        else
            this.$nodes.push(html);
    }
    
    this.$fill = function(){
        //apf.insertHtmlNode(null, this.$container, null, this.$nodes.join(""));
        this.$container.insertAdjacentHTML("beforeend", this.$nodes.join(""));
        this.$nodes = [];
    }
    
    this.$deInitNode = 
    this.$updateNode =
    this.$moveNode = apf.K;

    // *** Init *** //

    this.$draw = function(){
        this.$ext = this.$getExternal();
        this.$container = this.$getLayoutNode("main", "container", this.$ext);

        if (apf.hasCssUpdateScrollbarBug && !apf.getStyle(this.$container, "padding"))
            this.$fixScrollBug();

        this.$scrollArea = this.oFocus ? this.oFocus.parentNode : this.$container;

        if (this.$container.tagName.toLowerCase() == "iframe") {
            if (apf.isIE) {
                this.oIframe = this.$container;
                var iStyle = this.skin.selectSingleNode("iframe_style");
                this.oIframe.contentWindow.document.write(
                    "<!DOCTYPE html>\
                    <head>\
                        <style>" + (iStyle ? iStyle.firstChild.nodeValue : "") + "</style>\
                        <script>\
                            document.onkeydown = function(e){\
                                if (!e) e = event;\
                                if (" + 'top.apf.disableF5' + " && e.keyCode == 116) {\
                                    e.keyCode = 0;\
                                    return false;\
                                }\
                            }\
                        </script>\
                    </head>\
                    <body oncontextmenu='return false'></body>");
                this.$container = this.oIframe.contentWindow.document.body;
            }
            else {
                var node = document.createElement("div");
                this.$ext.parentNode.replaceChild(node, this.$ext);
                node.className = this.$ext.className;
                this.$ext = this.$container = node;
            }
        }
        
        if (this.getAttribute("each"))
            this.$eachHandler();
    };

    this.addEventListener("DOMNodeRemovedFromDocument", function() {
        clearInterval(this.$textTimer);
        apf.destroyHtmlNode(this.oDrag);
        
        if (this.$scrollArea)
            this.$scrollArea.onscoll = this.$scrollArea = null;

        this.oDrag = this.oIframe = this.oFocus = this.$container = this.$ext = null;
    });
}).call(apf.text.prototype = new apf.StandardBinding());

apf.aml.setElement("text", apf.text);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



//@todo DOCUMENT the modules too


/**
 * This element displays a rectangular area which allows a
 * user to type information. 
 *
 * The information typed can be
 * restricted by using `this.$masking`. The information can also
 * be hidden from view when used in password mode. 
 *
 * By adding an 
 * autocomplete element as a child, the 
 * value for the textbox can be looked up as you type. By setting the 
 * {@link apf.textbox.mask mask attribute}, complex data input 
 * validation is done while the user types.
 *
 * #### Example: Simple Boxes
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <a:table columns="150">
 *   <!-- startcontent -->
 *   <a:textbox value="Text"></a:textbox>
 *   <a:textbox value="Text" disabled="true" initial-message="I'm disabled!"></a:textbox>
 *   <!-- endcontent -->
 *  </a:table>
 * </a:application>
 * ```
 *
 * #### Example: Validation
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:label for="lbl2">Please enter a minimum of three characters</a:label>
 *   <a:textbox 
 *     id = "lbl2"
 *     minlength = "3" 
 *     maxlength = "5" 
 *     invalidmsg = "Invalid! Please enter a minimum of three characters" />
 *     <a:label for="lbl3">Enter your email address</a:label>
 *   <a:textbox 
 *     id = "lbl3"
 *     datatype = "a:email" 
 *     invalidmsg = "Invalid! Please enter a proper email address" />
 *   <a:label 
 *     caption = "A US Phone Number" 
 *     for = "txt71">
 *   </a:label>
 *   <a:textbox 
 *     mask = "(000)0000-0000;;_" 
 *     id = "txt71" />
 *   <a:label 
 *     caption = "A Date"
 *     for = "txt73">
 *   </a:label>
 *   <a:textbox 
 *     mask = "00-00-0000;;_"
 *     datatype = "xsd:date"
 *     invalidmsg = "Invalid date; Please enter a correct date"
 *     id = "txt73" />
 *   <a:label 
 *     caption = "A MAC Address" 
 *     for = "txt75" ></a:label>
 *   <a:textbox 
 *     mask = "XX-XX-XX-XX-XX-XX;;_"
 *     id = "txt75" />
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * #### Example: A Regular Box
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *     <!-- startcontent -->
 *     <a:bar id="winGoToFile"
 *       width = "500" 
 *       skin = "winGoToFile"
 *       minheight = "35"
 *       maxheight = "400">
 *         <a:vbox id="vboxGoToFile" edge="5 5 5 5" padding="5" anchors2="0 0 0 0">
 *             <a:textbox id="txtGoToFile" realtime="true" skin="searchbox_textbox" focusselect="true" />
 *             <a:list id="dgGoToFile"
 *               class = "searchresults noscrollbar"
 *               skin = "lineselect"
 *               maxheight = "350"
 *               scrollbar = "sbShared 32 7 7"
 *               viewport = "virtual"
 *               multiselect = "true"
 *               empty-message = "A filelist would go here.">
 *             </a:list>
 *         </a:vbox>
 *     </a:bar>
 *     <!-- endcontent -->
 * </a:application>
 * ```
 * 
 * @class apf.textbox
 * @define textbox
 * @allowchild autocomplete, {smartbinding}
 *
 * @form
 * @inherits apf.StandardBinding
 * @inheritsElsewhere apf.XForms
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.1
 *
 *
 */
/**
 * @binding value  Determines the way the value for the element is retrieved 
 * from the bound data.
 * 
 * #### Example
 *
 * Sets the value based on data loaded into this component.
 * 
 * ```xml
 *  <a:model id="mdlTextbox">
 *      <data name="Lukasz"></data>
 *  </a:model>
 *  <a:textbox model="mdlTextbox" value="[@name]" />
 * ```
 *
 * A shorter way to write this is:
 * ```xml
 *  <a:model id="mdlTextbox">
 *      <data name="Lukasz"></data>
 *  </a:model>
 *  <a:textbox value="[mdlTextbox::@name]" />
 * ```
 *
 */
/**
 * @event click     Fires when the user presses a mousebutton while over this element and then let's the mousebutton go. 
 */
/**
 * @event mouseup   Fires when the user lets go of a mousebutton while over this element. 
 */
/**
 * @event mousedown Fires when the user presses a mousebutton while over this element. 
 */
/**
 * @event keyup     Fires when the user lets go of a keyboard button while this element is focussed. 
 * @param {Object} e The standard event object. It contains the following property:
 *  - keyCode ([[Number]]): which key was pressed. This is an ascii number.
 */
/**
 * @event clear     Fires when the content of this element is cleared. 
 */
apf.input = function(struct, tagName) {
    this.$init(tagName || "input", apf.NODE_VISIBLE, struct);
};

apf.secret = function(struct, tagName) {
    this.$init(tagName || "secret", apf.NODE_VISIBLE, struct);
};

apf.password = function(struct, tagName) {
    this.$init(tagName || "password", apf.NODE_VISIBLE, struct);
};

apf.textarea = function(struct, tagName) {
    this.$init(tagName || "textarea", apf.NODE_VISIBLE, struct);
    
    this.multiline = true;
};

// HTML5 email element
apf.email = function(struct, tagName) {
    this.$init(tagName || "email", apf.NODE_VISIBLE, struct);
};

apf.textbox = function(struct, tagName) {
    this.$init(tagName || "textbox", apf.NODE_VISIBLE, struct);
};

(function(){
    this.implement(
        
        apf.DataAction
        
        
    );

    this.$focussable = true; // This object can get the focus
    this.$masking = false;
    this.$autoComplete = false;

    this.$childProperty = "value";

    //this.realtime = false;
    this.value = "";
    this.readonly = false;
    this.$isTextInput = true;
    this.multiline = false;

    /**
     * @attribute {Boolean} realtime Defines whether the value of the bound data is
     * updated as the user types it, or only when this element loses focus or
     * the user presses enter.
     */
    this.$booleanProperties["readonly"] = true;
    this.$booleanProperties["focusselect"] = true;
    this.$booleanProperties["realtime"] = true;
    this.$booleanProperties["kbclear"] = true;
    this.$supportedProperties.push("value", "mask", "initial-message",
        "focusselect", "realtime", "type", "rows", "cols", "kbclear");

    /**
     * @attribute {String} value Sets or gets the text of this element
     * 
     */
    this.$propHandlers["value"] = function(value, prop, force, initial) {
    // @todo apf3.0 check use of this.$propHandlers["value"].call
        if (!this.$input || !initial && this.getValue() == value)
            return;

        // Set Value
        if (!initial && !value && !this.hasFocus()) //@todo apf3.x research the use of clear
            return this.$clear();
        else if (this.isHTMLBox) {
            if (this.$input.innerHTML != value)
                this.$input.innerHTML = value;
        }
        else if (this.$input.value != value)
            this.$input.value = value;
        
        if (!initial)
            apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Initial"]);
        
        if (this.$button)
            this.$button.style.display = value && !initial ? "block" : "none";
    };

    //See validation
    //var oldPropHandler = this.$propHandlers["maxlength"];
    this.addEventListener("prop.maxlength", function(e) {
        //Special validation support using nativate max-length browser support
        if (this.$input.tagName.toLowerCase().match(/input|textarea/))
            this.$input.maxLength = parseInt(e.value) || null;
    });
    
    this.addEventListener("prop.editable", function(e) {
        if (apf.isIE)
            this.$input.unselectable = e.value ? "On" : "Off";
        else {
            if (e.value) 
                apf.addListener(this.$input, "mousedown", apf.preventDefault);
            else
                apf.removeListener(this.$input, "mousedown", apf.preventDefault);
        }
    });

    /**
     * @attribute {String} mask Sets or gets a complex input pattern that the user should
     * adhere to. 
     * 
     * This is a string which is a combination of special and normal
     * characters. It is comma seperated, and thus has two options. The first option
     * specifies whether the non-input characters (the chars not typed by the
     * user) are in the value of this element. The second option specifies the
     * character that is displayed when the user hasn't yet filled in a
     * character.
     *
     *  The following characters are possible:
     *
     *   - `0`: any digit
     *   - `1`: the number 1 or 2.
     *   - `9`: any digit or a space.
     *   - `#`: user can enter a digit, space, plus or minus sign.
     *   - `L`: any alpha character, case insensitive.
     *   - `?`: any alpha character, case insensitive or space.
     *   - `A`: any alphanumeric character.
     *   - `a`: any alphanumeric character or space.
     *   - `X`: hexadecimal character, case insensitive.
     *   - `x`: hexadecimal character, case insensitive or space.
     *   - `&`: any whitespace.
     *   - `C`: any character.
     *   - `!`: causes the input mask to fill from left to right instead of from right to left.
     *   - `'`: the start or end of a literal part.
     *   - `"`: the start or end of a literal part.
     *   - `>`: converts all characters that follow to uppercase.
     *   - `<`: converts all characters that follow to lowercase.
     *   - `\`: cancel the special meaning of a character.
     * 
     * #### Example
     *
     * An American phone number:
     *
     * ```xml
     *  <a:textbox mask="(000)0000-0000;;_" />
     * ```
     * 
     * #### Example
     *
     * A Dutch postal code:
     *
     * ```xml
     *  <a:textbox mask="0000 AA;;_" />
     * ```
     * 
     * #### Example
     *
     * A date
     * 
     * ```xml
     *  <a:textbox mask="00-00-0000;;_" datatype="xsd:date" />
     * ```
     * 
     * #### Example
     *
     * A serial number
     * 
     * ```xml
     *  <a:textbox mask="'WCS74'0000-00000;1;_" />
     * ```
     * 
     * #### Example
     *
     * A MAC address
     * 
     * ```xml
     *  <a:textbox mask="XX-XX-XX-XX-XX-XX;;_" />
     * ```
     */
    this.$propHandlers["mask"] = function(value) {
        if (this.mask.toLowerCase() == "password")// || !apf.hasMsRangeObject)
            return;

        if (!value) {
            throw new Error("Not Implemented");
        }

        if (!this.$masking) {
            this.$masking = true;
            this.implement(apf.textbox.masking);
            this.focusselect = false;
            //this.realtime = false;
        }

        this.setMask(this.mask);
    };

    //this.$propHandlers["ref"] = function(value) {
    //    this.$input.setAttribute("name",  value.split("/").pop().split("::").pop()
    //        .replace(/[\@\.\(\)]*/g, ""));
    //};

    /**
     * @attribute {String} initial-message Sets or gets the message displayed by this element
     * when it doesn't have a value set. This property is inherited from parent
     * nodes. When none is found, it is looked for on the appsettings element.
     */
    this.$propHandlers["initial-message"] = function(value) {
        if (value) {
            
            
            //this.$propHandlers["value"].call(this, value, null, true);
        }
        
        if (!this.value)
            this.$clear(true);
            
        if (this.type == "password" && this.$inputInitFix) {
            this.$inputInitFix.innerHTML = value;
            apf.setStyleClass(this.$inputInitFix, "initFxEnabled");
        } 
    };

    /**
     * @attribute {Number} rows Sets or gets the row length for a text area.
     */
    this.$propHandlers["rows"] = function(value) {
        if (this.$input.tagName.toLowerCase() == "textarea" && value) {
            this.setProperty("rows", value);
            if (this.$ext) {
                this.$ext.rows = value;
            }
        }
    };

    /**
     * @attribute {Number} cols Sets or gets the column height for a text area.
     */
    this.$propHandlers["cols"] = function(value) {
        if (this.$input.tagName.toLowerCase() == "textarea" && value) {
            this.setProperty("cols", value);
            if (this.$ext) {
                this.$ext.cols = value;
            }            
        } 
    };
    
    /**
     * @attribute {Boolean} focusselect Sets or gets whether the text in this element is
     * selected when this element receives focus.
     */
    this.$propHandlers["focusselect"] = function(value) {
        var _self = this;
        this.$input.onmousedown = function(){
            _self.focusselect = false;
        };

        this.$input.onmouseup = 
        this.$input.onmouseout = function(){
            _self.focusselect = value;
        };
    };

    /**
     * @attribute {String} type Sets or gets the type or function this element represents.
     * This can be any arbitrary name, although there are some special values:
     *   
     *   - `"username"`: this element is used to type in the name part of login credentials.
     *   - `"password"`: this element is used to type in the password part of login credentials.
     */
    this.$propHandlers["type"] = function(value) {
        if (value && "password|username".indexOf(value) > -1
          && typeof this.focusselect == "undefined") {
            this.focusselect = true;
            this.$propHandlers["focusselect"].call(this, true);
        }
    };

    this.$isTextInput = function(e) {
        return true;
    };

    // *** Public Methods *** //

    

    /**
     * Sets the value of this element. This should be one of the values
     * specified in the `values` attribute.
     * @param {String} value The new value of this element
     */
    this.setValue = function(value) {
        return this.setProperty("value", value, false, true);
    };

    /**
     * Clears an element's value.
     */    
    this.clear = function(){
        this.setProperty("value", "");
    };
    
    //@todo cleanup and put initial-message behaviour in one location
    this.$clear = function(noEvent) {
        if (this["initial-message"]) {
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial");
            this.$propHandlers["value"].call(this, this["initial-message"], null, null, true);
        }
        else {
            this.$propHandlers["value"].call(this, "", null, null, true);
        }
        
        if (!noEvent)
            this.dispatchEvent("clear");//@todo this should work via value change
    };

    /**
     * Returns the current value of this element.
     * @return {String} The current value.
     */
    this.getValue = function(){
        var v;
        
        if (this.isHTMLBox) { 
            if (this.$input.innerText)
                v = this.$input.innerText;
            else {
                //Chrome has a bug, innerText is cleared when display property is changed
                v = apf.html_entity_decode(this.$input.innerHTML
                    .replace(/<br\/?\>/g, "\n")
                    .replace(/<[^>]*>/g, ""));
            }
            if (v.charAt(v.length - 1) == "\n")
                v = v.substr(0, v.length - 1); //Remove the trailing new line
        }
        else 
            v = this.$input.value;
            
        return v == this["initial-message"] ? "" : v.replace(/\r/g, "");
    };
    
    

    /**
     * Selects the text in this element.
     */
    this.select = function(){ 
        this.$input.select(); 
    };

    /**
     * Deselects the text in this element.
     */
    this.deselect = function(){this.$input.deselect();};

    /**** Private Methods *****/

    this.$enable = function(){this.$input.disabled = false;};
    this.$disable = function(){this.$input.disabled = true;};

    this.$insertData = function(str) {
        return this.setValue(str);
    };

    /**
     * @private
     */
    this.insert = function(text) {
        if (apf.hasMsRangeObject) {
            try {
                this.$input.focus();
            }
            catch (e) {}
            var range = document.selection.createRange();
            if (this.oninsert)
                text = this.oninsert(text);
            range.pasteHTML(text);
            range.collapse(true);
            range.select();
        }
        else {
            this.$input.value += text;
        }
    };

    this.addEventListener("$clear", function(){
        this.value = "";//@todo what about property binding?
        
        if (this["initial-message"] && apf.document.activeElement != this) {
            this.$propHandlers["value"].call(this, this["initial-message"], null, null, true);
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial");
        }
        else {
            this.$propHandlers["value"].call(this, "");
        }
        
        if (!this.$input.tagName.toLowerCase().match(/input|textarea/i)) {
            if (apf.hasMsRangeObject) {
                try {
                    var range = document.selection.createRange();
                    range.moveStart("sentence", -1);
                    //range.text = "";
                    range.select();
                }
                catch (e) {}
            }
        }
        
        this.dispatchEvent("clear"); //@todo apf3.0
    });

    this.$keyHandler = function(key, ctrlKey, shiftKey, altKey, e) {
        if (this.$button && key == 27 && this.kbclear) {
            //this.$clear();
            if (this.value) {
                this.change("");
                
                this.dispatchEvent("keydown", {
                    keyCode: key,
                    ctrlKey: ctrlKey,
                    shiftKey: shiftKey,
                    altKey: altKey,
                    htmlEvent: e});
                
                e.stopPropagation();
            }
            //this.focus({mouse:true});
        }
        
        // Disabled this because it was being fired via the document as well
        // Tested using the gotoline plugin
        // if (this.dispatchEvent("keydown", {
        //     keyCode   : key,
        //     ctrlKey   : ctrlKey,
        //     shiftKey  : shiftKey,
        //     altKey    : altKey,
        //     htmlEvent : e}) === false)
        //         return false;

        /*// @todo: revisit this IF statement - dead code?
        if (false && apf.isIE && (key == 86 && ctrlKey || key == 45 && shiftKey)) {
            var text = window.clipboardData.getData("Text");
            if ((text = this.dispatchEvent("keydown", {
                text: this.onpaste(text)}) === false))
                    return false;
            if (!text)
                text = window.clipboardData.getData("Text");

            this.$input.focus();
            var range = document.selection.createRange();
            range.text = "";
            range.collapse();
            range.pasteHTML(text.replace(/\n/g, "<br />").replace(/\t/g, "&nbsp;&nbsp;&nbsp;"));

            return false;
        }*/
    };

    this.$registerElement = function(oNode) {
        if (!oNode) return;
        if (oNode.localName == "autocomplete")
            this.$autoComplete = oNode;
    };

    var fTimer;
    this.$focus = function(e) {
        if (!this.$ext || this.$ext.disabled)
            return;

        this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");

        var value = this.getValue();
        if (this["initial-message"] && !value) {
            this.$propHandlers["value"].call(this, "", null, null, true);
            apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Initial"]);
        }
        
        var _self = this;
        function delay(){
            try {
                if (!fTimer || document.activeElement != _self.$input) {
                    _self.$input.focus();
                }
                else {
                    clearInterval(fTimer);
                    return;
                }
            }
            catch (e) {}

            if (_self.$masking)
                _self.setPosition();

            if (_self.focusselect)
                _self.select();
        };

        if ((!e || e.mouse) && apf.isIE) {
            clearInterval(fTimer);
            fTimer = setInterval(delay, 1);
        }
        else
            delay();
    };

    this.$blur = function(e) {
        if (!this.$ext)
            return;
        
        if (!this.realtime)
            this.change(this.getValue());
    
        if (e)
            e.cancelBubble = true;

        this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus", "capsLock"]);

        var value = this.getValue();
        if (this["initial-message"] && !value) {
            this.$propHandlers["value"].call(this, this["initial-message"], null, null, true);
            apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial");
        }

        /*if (apf.hasMsRangeObject) {
            var r = this.$input.createTextRange();
            r.collapse();
            r.select();
        }*/

        try {
            if (apf.isIE || !e || e.srcElement != apf.window)
                this.$input.blur();
        }
        catch (e) {}

        // check if we clicked on the oContainer. ifso dont hide it
        if (this.oContainer) {
            $setTimeout("var o = apf.lookup(" + this.$uniqueId + ");\
                o.oContainer.style.display = 'none'", 100);
        }
        
        clearInterval(fTimer);
    };

    // *** Init *** //

    this.$draw = function(){
        var _self = this,
            typedBefore = false;
        
        
        if (this.localName == "codeeditor") {
            this.skin = "textarea";
            this.$loadSkin();
        }
        
        
        //Build Main Skin
        this.$ext = this.$getExternal(null, null, function(oExt) {
            var mask = this.getAttribute("mask");

            if ((typeof mask == "string" && mask.toLowerCase() == "password")
              || "secret|password".indexOf(this.localName) > -1) {
                this.type = "password";
                this.$getLayoutNode("main", "input").setAttribute("type", "password");
            }
            
            

            oExt.setAttribute("onmousedown", "if (!this.host.disabled) \
                this.host.dispatchEvent('mousedown', {htmlEvent : event});");
            oExt.setAttribute("onmouseup",   "if (!this.host.disabled) \
                this.host.dispatchEvent('mouseup', {htmlEvent : event});");
            oExt.setAttribute("onclick",     "if (!this.host.disabled) \
                this.host.dispatchEvent('click', {htmlEvent : event});");
        });
        this.$input = this.$getLayoutNode("main", "input", this.$ext);
        this.$button = this.$getLayoutNode("main", "button", this.$ext);
        this.$inputInitFix = this.$getLayoutNode("main", "initialfix", this.$ext);
        
        if (this.type == "password")
            this.$propHandlers["type"].call(this, "password");

        if (!apf.hasContentEditable && "input|textarea".indexOf(this.$input.tagName.toLowerCase()) == -1) {
            var node = this.$input;
            this.$input = node.parentNode.insertBefore(document.createElement("textarea"), node);
            node.parentNode.removeChild(node);
            this.$input.className = node.className;
            if (this.$ext == node)
                this.$ext = this.$input;
        }
        
        if (this.$button) {
            this.$button.onmouseup = function(){
                _self.$clear(); //@todo why are both needed for doc filter
                _self.change(""); //@todo only this one should be needed
                _self.focus({mouse:true});
            }
        }

        //@todo for skin switching this should be removed
        if (this.$input.tagName.toLowerCase() == "textarea") {
            this.addEventListener("focus", function(e) {
                //if (this.multiline != "optional")
                    //e.returnValue = false
            });
        }
        
        this.$input.onselectstart = function(e) {
            if (!e) e = event;
            e.cancelBubble = true;
        }
        this.$input.host = this;

        this.$input.onkeydown = function(e) {
            e = e || window.event;
            
            if (this.host.disabled) {
                e.returnValue = false;
                return false;
            }

            //Change
            if (!_self.realtime) {
                var value = _self.getValue();
                if (e.keyCode == 13 && value != _self.value)
                    _self.change(value);
            }
            else if (apf.isWebkit && _self.xmlRoot && _self.getValue() != _self.value) //safari issue (only old??)
                $setTimeout("var o = apf.lookup(" + _self.$uniqueId + ");\
                    o.change(o.getValue())");

            if (_self.readonly || _self.multiline == "optional" && e.keyCode == 13 && !e.shiftKey
              || e.ctrlKey && (e.keyCode == 66 || e.keyCode == 73
              || e.keyCode == 85)) {
                e.returnValue = false;
                return false;
            }

            if (typedBefore && this.getAttribute("type") == "password" && this.value != "") {
                var hasClass = (_self.$ext.className.indexOf("capsLock") > -1),
                    capsKey = (e.keyCode === 20);
                if (capsKey) // caps off
                    apf.setStyleClass(_self.$ext, hasClass ? null : "capsLock", hasClass ? ["capsLock"] : null);
            }

            //Autocomplete
            if (_self.$autoComplete || _self.oContainer) {
                var keyCode = e.keyCode;
                $setTimeout(function(){
                    if (_self.$autoComplete)
                        _self.$autoComplete.fillAutocomplete(keyCode);
                    else
                        _self.fillAutocomplete(keyCode);
                });
            }

            //Non this.$masking
            if (!_self.mask) {
                return _self.$keyHandler(e.keyCode, e.ctrlKey,
                    e.shiftKey, e.altKey, e);
            }
        };

        this.$input.onkeyup = function(e) {
            if (!e)
                e = event;
                
            if (this.host.disabled)
                return false;

            var keyCode = e.keyCode;
            
            if (_self.$button)
                _self.$button.style.display = _self.getValue() ? "block" : "none";

            if (_self.realtime) {
                $setTimeout(function(){
                    var v;
                    if (!_self.mask && (v = _self.getValue()) != _self.value)
                        _self.change(v); 
                     if (apf.isIE) 
                        _self.dispatchEvent("keyup", {keyCode : keyCode});//@todo
                });
            }
            else if (apf.isIE) {
                _self.dispatchEvent("keyup", {keyCode : keyCode});//@todo
            }

            
            if (_self.isValid && _self.isValid() && e.keyCode != 13 && e.keyCode != 17)
                _self.clearError();
            
        };

        

        if (apf.hasAutocompleteXulBug)
            this.$input.setAttribute("autocomplete", "off");

        if ("INPUT|TEXTAREA".indexOf(this.$input.tagName) == -1) {
            this.isHTMLBox = true;

            this.$input.unselectable = "Off";
            this.$input.contentEditable = true;
            //this.$input.style.width = "1px";

            this.$input.select = function(){
                if (apf.hasMsRangeObject) {
                    var r = document.selection.createRange();
                    r.moveToElementText(this);
                    r.select();
                }
                else if (_self.isHTMLBox) {
                    var r = document.createRange();
                    r.setStart(_self.$input.firstChild || _self.$input, 0);
                    var lastChild = _self.$input.lastChild || _self.$input;
                    r.setEnd(lastChild, lastChild.nodeType == 1
                        ? lastChild.childNodes.length
                        : lastChild.nodeValue.length);
                    
                    var s = window.getSelection();
                    s.removeAllRanges();
                    s.addRange(r);
                }
            }
            
            this.$input.onpaste = function(e) {
                if (apf.hasMsRangeObject)
                    return;
                
                if (e.clipboardData.types.indexOf("text/html") == -1)
                    return;
                    
                var sel = window.getSelection();
                var range = sel.getRangeAt(0);
                
                setTimeout(function(){
                    var range2 = sel.getRangeAt(0);
                    range2.setStart(range.startContainer, range.startOffset);
                    var c = range2.cloneContents();
                    range2.deleteContents();
                    
                    var d = document.body.appendChild(document.createElement("div")); 
                    d.appendChild(c); 
                    var p = d.innerText;
                    d.parentNode.removeChild(d);
                    
                    range2.insertNode(document.createTextNode(p));
                });
            };
        };

        this.$input.deselect = function(){
            if (!document.selection) return;

            var r = document.selection.createRange();
            r.collapse();
            r.select();
        };

        var f;
        apf.addListener(this.$input, "keypress", f = function(e) {
            if (_self.$input.getAttribute("type") != "password")
                return apf.removeListener(_self.$input, "keypress", f);
            e = e || window.event;
            // get key pressed
            var which = -1;
            if (e.which)
                which = e.which;
            else if (e.keyCode)
                which = e.keyCode;

            // get shift status
            var shift_status = false;
            if (e.shiftKey)
                shift_status = e.shiftKey;
            else if (e.modifiers)
                shift_status = !!(e.modifiers & 4);

            if (((which >= 65 && which <=  90) && !shift_status) ||
                ((which >= 97 && which <= 122) && shift_status)) {
                // uppercase, no shift key
                apf.setStyleClass(_self.$ext, "capsLock");
            }
            else {
                apf.setStyleClass(_self.$ext, null, ["capsLock"]);
            }
            typedBefore = true;
        });
    };

    this.$loadAml = function() {
        if (typeof this["initial-message"] == "undefined")
            this.$setInheritedAttribute("initial-message");

        if (typeof this.realtime == "undefined")
            this.$setInheritedAttribute("realtime");
    }

    this.addEventListener("DOMNodeRemovedFromDocument", function(){
        if (this.$button)
            this.$button.onmousedown = null;
        
        if (this.$input) {
            this.$input.onkeypress = 
            this.$input.onmouseup = 
            this.$input.onmouseout = 
            this.$input.onmousedown = 
            this.$input.onkeydown = 
            this.$input.onkeyup = 
            this.$input.onselectstart = null;
        }
    });

}).call(apf.textbox.prototype = new apf.StandardBinding());


apf.config.$inheritProperties["initial-message"] = 1;
apf.config.$inheritProperties["realtime"] = 1;

apf.input.prototype = 
apf.secret.prototype = 
apf.password.prototype =
apf.textarea.prototype =
apf.email.prototype = apf.textbox.prototype;

apf.aml.setElement("input",    apf.input);
apf.aml.setElement("secret",   apf.secret);
apf.aml.setElement("password", apf.password);
apf.aml.setElement("textarea", apf.textarea);
apf.aml.setElement("textbox",  apf.textbox);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * This element displays a bar containing buttons and other AML elements.
 * 
 * This element is usually positioned in the top of an application allowing
 * the user to choose from grouped buttons.
 *
 * #### Example
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *  <a:window 
 *    id = "winMail"
 *    contextmenu = "fileMenu"
 *    width = "300"
 *    height = "200" 
 *    visible = "true"
 *    resizable = "true" 
 *    title = "An App">
 *  <!-- startcontent -->
 *      <a:toolbar>
 *          <a:menubar>
 *              <a:button submenu="fileMenu">File</a:button>
 *              <a:button submenu="editMenu">Edit</a:button>
 *          </a:menubar>
 *      </a:toolbar>
 * 
 *     <a:menu id="editMenu">
 *          <a:item>About us</a:item>
 *          <a:item>Help</a:item>
 *      </a:menu>
 *      <a:menu id="fileMenu">
 *          <a:item icon="email.png">Tutorials</a:item>
 *          <a:item>Live Helps</a:item>
 *          <a:divider></a:divider>
 *          <a:item>Visit Ajax.org</a:item>
 *          <a:item>Exit</a:item>
 *      </a:menu>
 *  <!-- endcontent -->
 *  </a:window>
 * </a:application>
 * ```
 *
 * @class apf.toolbar
 * @define toolbar
 * @container
 *
 * @allowchild bar, menubar
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @inherits apf.Presentation
 */

apf.toolbar = function(struct, tagName) {
    this.$init(tagName || "toolbar", apf.NODE_VISIBLE, struct);
};

(function(){
    this.$focussable = false;
    
    // *** DOM Hooks *** //
    
    
    // *** Init *** //

    this.$draw = function(){
        //Build Main Skin
        this.$ext = this.$getExternal();
        this.$int = this.$getLayoutNode("main", "container", this.$ext);
    };
}).call(apf.toolbar.prototype = new apf.Presentation());

apf.aml.setElement("toolbar", apf.toolbar);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * An element displaying data in a list, where each item in the list can contain
 * such a list. 
 *
 * This element gives the user the ability to walk through this
 * tree of data by clicking open elements to show more elements. The tree
 * can grow by fetching more data when the user requests it.
 *
 *
 * #### Example: A Simple Tree
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:tree id="tree" align="right">
 *       <a:item caption="root" icon="/api/apf/resources/icons/icoUsers.gif">
 *           <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test">
 *               <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test" />
 *               <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test" />
 *               <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test" />
 *           </a:item>
 *           <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test" />
 *           <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test" />
 *           <a:item icon="/api/apf/resources/icons/icoUsers.gif" caption="test" />
 *       </a:item>
 *   </a:tree>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * #### Example: Using a Model
 *
 * A tree using a model:
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="filesystem">
 *       <data>
 *           <folder caption="goo">
 *             <file caption="goo" filename="goo.js" />
 *             <file caption="goo2.js" filename="goo2.js"></file>
 *             <file caption="goo3.js"  />
 *           </folder>
 *           <folder caption="foo_folder" >
 *             <file caption="foo.js" filename="foo.hs"></file>
 *           </folder>
 *       </data>
 *   </a:model>
 *   <a:tree model="filesystem">
 *        <a:caption match="[@caption]" />
 *        <a:caption match="[@filename]" />
 *        <a:each match="[drive|file|folder]" />
 *    </a:tree>
 *   <!-- endcontent -->
 * </a:application>
 * ```
 *
 * #### Example: An Inline Tree
 * 
 * An inline tree that draws the same as the above example:
 *
 * ```xml, demo
 * <a:application xmlns:a="http://ajax.org/2005/aml">
 *   <!-- startcontent -->
 *   <a:model id="filesystem">
 *       <data>
 *           <folder caption="goo">
 *             <file caption="goo" filename="goo.js" />
 *             <file caption="goo2.js" filename="goo2.js"></file>
 *             <file caption="goo3.js"  />
 *           </folder>
 *           <folder caption="foo_folder" >
 *             <file caption="foo.js" filename="foo.hs"></file>
 *           </folder>
 *       </data>
 *   </a:model>
 *  <a:tree 
 *    model = "filesystem"
 *    caption = "[@caption|@filename]"
 *    each = "[drive|file|folder]" />
 *  <!-- endcontent -->
 * </a:application>
 * ```
 *
 * @class apf.tree
 * @define tree
 * @selection
 * @allowchild {smartbinding}
 *
 * @inherits apf.BaseTree
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 *
 */
/**
 * @binding insert Determines how new data is loaded when the user expands 
 * an item. For instance by clicking on the + button. This way only the root nodes
 * need to be loaded at the start of the application. All other children are
 * received on demand when the user requests it by navigating throught the tree.
 *
 * #### Example
 *
 * This example shows an insert rule that only works on folder elements. It will
 * read the directory contents using webdav and insert it under the selected 
 * tree node.
 *
 * ```xml
 *  <a:tree model="filesystem.xml">
 *      <a:bindings>
 *          <a:caption match="[@caption|@filename]" />
 *          <a:insert match="[folder]" get="{myWebdav.readdir([@id])}" />
 *          <a:each match="[drive|file|folder]" />
 *      </a:bindings>
 *  </a:tree>
 * ```
 *
 */
/**
 * @attribute {String} get Sets or gets the {@link term.datainstruction data instruction} that is used to load the new data.
 *
 */
/**
 * @binding caption  Determines the caption of a tree node.
 */
/**
 * @binding icon     Determines the icon of a tree node.
 */
/**
 * @binding css      Determines a css class for a tree node.
 *
 * #### Example
 *
 * In this example a node is bold when the folder contains unread messages:
 *
 * ```xml
 *
 *  <a:tree model="messages.xml">
 *      <a:caption match="[@caption]" />
 *      <a:css match="[folder/message[@unread]]" value="highlighUnread" />
 *      <a:icon match="[@icon]" />
 *      <a:icon match="[folder]" value="icoDir.png" />
 *      <a:each match="[folder|message]" />
 *  </a:tree>
 * ```
 */
/**
 * @binding tooltip  Determines the tooltip of a tree node.
 */
/**
 * @binding empty    Determines the empty message of a node.
 *
 * #### Example
 *
 * This example shows a grouped contact list that displays a message under 
 * empty groups.
 *
 * ```xml
 *  <a:tree model="xml/contacts.xml">
 *      <a:caption match="[@caption]" />
 *      <a:icon match="[contact]" value="icoContact.png" />
 *      <a:icon match="[group]" value="icoFolder.png" />
 *      <a:empty match="[group]" value="Drag a contact to this group." />
 *      <a:each match="[group|contact]" />
 *  </a:tree>
 * ```
 */
apf.tree = function(struct, tagName) {
    this.$init(tagName || "tree", apf.NODE_VISIBLE, struct);
};

(function(){
    var HAS_CHILD = 1 << 1,
        IS_CLOSED = 1 << 2,
        IS_LAST = 1 << 3,
        IS_ROOT = 1 << 4,
        treeState = this.$treeState;
    
    // *** Properties and Attributes *** //
    
    
    
    /**
     * @attribute {String} mode Sets the way this element interacts with the user.
     *   
     * The possible values are:
     *   - `"check"`:  the user can select a single item from this element. The selected item is indicated.
     *   - `"radio"`:  the user can select multiple items from this element. Each selected item is indicated.
     */
    this.$mode = 0;
    this.$propHandlers["mode"] = function(value) {
        if ("check|radio".indexOf(value) > -1) {
            if (!this.hasFeature(apf.__MULTICHECK__))
                this.implement(apf.MultiCheck);
            
            this.addEventListener("afterrename", $afterRenameMode); //what does this do?
            
            this.multicheck = value == "check"; //radio is single
            this.$mode = this.multicheck ? 1 : 2;
        }
        else {
            //@todo undo actionRules setting
            this.removeEventListener("afterrename", $afterRenameMode);
            //@todo unimplement??
            this.$mode = 0;
        }
    };
    
    //@todo apf3.0 retest this completely
    function $afterRenameMode(){
        
    }
    
    
    this.$initNode = function(xmlNode, state, Lid) {
        //Setup Nodes Interaction
        this.$getNewContext("item");
        
        var hasChildren = state & HAS_CHILD || this.emptyMessage 
            && this.$applyBindRule("empty", xmlNode),
            //should be restructured and combined events set per element
            oItem = this.$getLayoutNode("item");
        //@todo this should use dispatchEvent, and be moved to oExt
        oItem.setAttribute("onmouseover",
            "var o = apf.lookup(" + this.$uniqueId + ");\
            o.$setStyleClass(this, 'hover', null, true);");
        oItem.setAttribute("onmouseout",
            "var o = apf.lookup(" + this.$uniqueId + ");\
            o.$setStyleClass(this, '', ['hover'], true);");
        /*oItem.setAttribute("onmousedown",
            "var o = apf.lookup(" + this.$uniqueId + ");\
            if (o.onmousedown) o.onmousedown(event, this);");*/
        
        //Set open/close skin class & interaction
        this.$setStyleClass(this.$getLayoutNode("item", "class"), treeState[state]).setAttribute(apf.xmldb.htmlIdTag, Lid);
        this.$setStyleClass(this.$getLayoutNode("item", "container"), treeState[state])
        //this.$setStyleClass(oItem, xmlNode.tagName)
        var elOpenClose = this.$getLayoutNode("item", "openclose");
        if (elOpenClose) { //hasChildren && 
            elOpenClose.setAttribute("children", hasChildren);
            elOpenClose.setAttribute("onmousedown",
                "if (this.getAttribute('children') == false) return;\
                var o = apf.lookup(" + this.$uniqueId + ");\
                o.slideToggle(this, null, null, true);\
                apf.cancelBubble(event, o);");
            
            elOpenClose.setAttribute("ondblclick", "event.cancelBubble = true");
        }
        
        
        if (this.$mode) {
            var elCheck = this.$getLayoutNode("item", "check");
            if (elCheck) {
                elCheck.setAttribute("onmousedown",
                    "var o = apf.lookup(" + this.$uniqueId + ");\
                    o.checkToggle(this, true);\o.$skipSelect = true;");

                if (apf.isTrue(this.$applyBindRule("checked", xmlNode))) {
                    this.$checkedList.push(xmlNode);
                    this.$setStyleClass(oItem, "checked");
                }
                else if (this.isChecked(xmlNode))
                    this.$setStyleClass(oItem, "checked");
            }
            else {
                
                return false;
            }
        }
        
        
        var ocAction = this.opencloseaction || "ondblclick";
        
        //Icon interaction
        var elIcon = this.$getLayoutNode("item", "icon");
        if (elIcon && elIcon != elOpenClose) {
            if (ocAction != "ondblclick") {
                elIcon.setAttribute(ocAction, 
                  "var o = apf.lookup(" + this.$uniqueId + ");" +
                   (ocAction == "onmousedown" ? "o.select(this, apf.getCtrlKey(event), event.shiftKey, event.button);" : "") +
                   (true ? "o.slideToggle(this, null, null, true);" : ""));
            }
            if (ocAction != "onmousedown") {
                elIcon.setAttribute("onmousedown", 
                  "apf.lookup(" + this.$uniqueId + ").select(this, apf.getCtrlKey(event), event.shiftKey, event.button);");
            }
            
            elIcon.setAttribute("ondblclick", 
              "var o = apf.lookup(" + this.$uniqueId + ");\
              o.choose(null, true);" + 
              
              "o.stopRename();" + 
              
              (true && !ocAction == "ondblclick" ? "o.slideToggle(this, null, null, true);" : "") +
              "apf.cancelBubble(event,o);");
        }

        //Select interaction
        var elSelect = this.$getLayoutNode("item", "select"),
            strMouseDown;
        
        
        if (this.hasFeature(apf.__RENAME__) || this.hasFeature(apf.__DRAGDROP__)) {
            strMouseDown =
                'var o = apf.lookup(' + this.$uniqueId + ');\
                 var xmlNode = apf.xmldb.findXmlNode(this);\
                 var isSelected = o.isSelected(xmlNode);\
                 this.hasPassedDown = true;\
                 if (event.button == 2) \
                    o.stopRename();\
                 else if (!o.renaming && o.hasFocus() && isSelected == 1) \
                    this.dorename = true;\
                 if (!o.hasFeature(apf.__DRAGDROP__) || !isSelected && !apf.getCtrlKey(event))\
                     o.select(this, apf.getCtrlKey(event), event.shiftKey, event.button);\
                 apf.cancelBubble(event, o);';
            
            elSelect.setAttribute("onmouseout", 'this.hasPassedDown = false;' + (elSelect.getAttribute("onmouseout") || ""));
            elSelect.setAttribute("onmouseup", 'if (!this.hasPassedDown) return;\
                var o = apf.lookup(' + this.$uniqueId + ');' +
                
                'if (this.dorename && !o.mode)\
                    o.startDelayedRename(event, null, true);' +
                
                'this.dorename = false;\
                 var xmlNode = apf.xmldb.findXmlNode(this);\
                 var isSelected = o.isSelected(xmlNode);\
                 if (o.hasFeature(apf.__DRAGDROP__))\
                     o.select(this, apf.getCtrlKey(event), event.shiftKey, event.button);');
        }
        else 
        
        {
            strMouseDown = "o.select(this, apf.getCtrlKey(event), event.shiftKey, event.button);\
                            apf.cancelBubble(event, o);";
        }
        
        if (ocAction != "ondblclick") {
            elSelect.setAttribute(ocAction, 
              "var o = apf.lookup(" + this.$uniqueId + ");" +
               (ocAction == "onmousedown" ? strMouseDown : "") +
               (true ? "o.slideToggle(this, null, null, true);" : ""));
        }
        if (ocAction != "onmousedown") {
            elSelect.setAttribute("onmousedown", 
              "var o = apf.lookup(" + this.$uniqueId + ");" + strMouseDown);
        }

        elSelect.setAttribute("ondblclick", 
          "var o = apf.lookup(" + this.$uniqueId + ");\
          o.choose(null, true);" + 
          
          "o.stopRename();this.dorename=false;" + 
          
          (ocAction == "ondblclick" ? "o.slideToggle(this, null, null, true);" : "") +
          "apf.cancelBubble(event, o);");
        
        //Setup Nodes Identity (Look)
        if (elIcon) {
            var iconURL = this.$applyBindRule("icon", xmlNode);
            if (iconURL) {
                if (elIcon.tagName.match(/^img$/i))
                    elIcon.setAttribute("src", apf.getAbsolutePath(this.iconPath, iconURL));
                else
                    elIcon.setAttribute("style", "background-image:url(" 
                        + apf.getAbsolutePath(this.iconPath, iconURL) + ")");
            }
        }

        var elCaption = this.$getLayoutNode("item", "caption");
        if (elCaption) {
            
            {
                apf.setNodeValue(elCaption,
                    this.$applyBindRule("caption", xmlNode));
            }
        }
        
        var strTooltip = this.$applyBindRule("tooltip", xmlNode)
        if (strTooltip)
            oItem.setAttribute("title", strTooltip);
        
        
        var cssClass = this.$applyBindRule("css", xmlNode);
        if (cssClass) {
            this.$setStyleClass(this.$getLayoutNode("item", null, oItem), cssClass);
            this.$setStyleClass(this.$getLayoutNode("item", "container", oItem), cssClass);
            this.$dynCssClasses.push(cssClass);
        }
        

        return oItem;
    };
    
    this.$updateNode = function(xmlNode, htmlNode) {
        if (!htmlNode) return;

        var elIcon = this.$getLayoutNode("item", "icon", htmlNode),
            iconURL = this.$applyBindRule("icon", xmlNode);
        if (elIcon && iconURL) {
            if (elIcon.tagName && elIcon.tagName.match(/^img$/i))
                elIcon.src = apf.getAbsolutePath(this.iconPath, iconURL);
            else
                elIcon.style.backgroundImage = "url(" 
                    + apf.getAbsolutePath(this.iconPath, iconURL) + ")";
        }
        
        
        //@todo
        

        var elCaption = this.$getLayoutNode("item", "caption", htmlNode);
        if (elCaption) {
            //if (elCaption.nodeType != 1)
                //elCaption = elCaption.parentNode;
            
            if (elCaption.nodeType == 1) {
                
                    elCaption.innerHTML = this.$applyBindRule("caption", xmlNode);
            }
            else
                elCaption.nodeValue = this.$applyBindRule("caption", xmlNode);
        }
        
        var strTooltip = this.$applyBindRule("tooltip", xmlNode);
        if (strTooltip) 
            htmlNode.setAttribute("title", strTooltip);

        
        var cssClass = this.$applyBindRule("css", xmlNode);
        if (cssClass || this.$dynCssClasses.length) {
            this.$setStyleClass(htmlNode, cssClass, this.$dynCssClasses); //@todo overhead!
            if (cssClass && !this.$dynCssClasses.contains(cssClass)) {
                if (this.$dynCssClasses.indexOf(cssClass) == -1)
                    this.$dynCssClasses.push(cssClass);
            }
        }
        
    };
    
    // *** Init *** //
    
    this.$draw = function(){
        this.$drawBase();
    };    
}).call(apf.tree.prototype = new apf.BaseTree());

apf.aml.setElement("tree", apf.tree);

apf.aml.setElement("checked", apf.BindingRule);





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/*
 * Element implementing WebDAV remote filesystem protocol.
 * WebDAV stands for "Web-based Distributed Authoring and Versioning". It is a
 * set of extensions to the HTTP protocol which allows users to collaboratively
 * edit and manage files on remote web servers (from: {@link http://www.webdav.org}.
 * This Object aims to be a complete implementation of {@link http://www.webdav.org/specs/rfc4918.html RFC4981}
 * and provides a scriptable interface to any WebDAV server.
 * Example:
 * Writing to a file with a WebDAV connector
 * <code>
 *  <a:webdav id="myWebDAV" url="http://my-webdav-server.com/dav_files/" />
 *
 *  <a:script>
 *      // write the text 'bar' to a file on the server called 'foo.txt'
 *      myWebDAV.writeFile('http://my-webdav-server.com/dav_files/foo.txt', 'bar');
 *  </a:script>
 * </code>
 *
 * Remarks:
 * Calls can be made to a server using a special {@link term.datainstruction data instruction}
 * format.
 * <code>
 *  get="{myWebdav.authenticate([@username], [@password])}"
 *  get="{myWebdav.login(...alias for authenticate...)}"
 *  set="{myWebdav.logout()}"
 *  get="{myWebdav.read([@path])}"
 *  get="{myWebdav.create([@path], 'New File.txt', '')}"
 *  set="{myWebdav.write([@path], [@data])}"
 *  set="{myWebdav.store(...alias for write...)}"
 *  set="{myWebdav.save(...alias for write...)}"
 *  set="{myWebdav.copy([@path], [../@path])}"
 *  set="{myWebdav.cp(...alias for copy...)}"
 *  set="{myWebdav.rename([@name], [@path])}"
 *  set="{myWebdav.move([@path], [../@path])}"
 *  set="{myWebdav.mv(...alias for move...)}"
 *  set="{myWebdav.remove([@path])}"
 *  set="{myWebdav.rmdir(...alias for remove...)}"
 *  set="{myWebdav.rm(...alias for remove...)}"
 *  set="{myWebdav.mkdir([@path])}"
 *  get="{myWebdav.readdir([@path])}"
 *  get="{myWebdav.scandir(...alias for readdir...)}"
 *  load="{myWebdav.getroot()}"
 *  set="{myWebdav.lock([@path])}"
 *  set="{myWebdav.unlock([@path])}"
 * </code>
 *
 * @event authfailure Fires when the authentication process failed or halted.
 *   bubbles: yes
 *   cancelable: Prevents an authentication failure to be thrown
 * @param {Object} e
 *     {String}        username   the username used for the login attempt
 *     {String}        server     the server address (URI) of the XMPP server
 *     {String}        message    a more detailed explanation of the error
 * @event connectionerror Fires when the connection with the WebDAV server dropped.
 *   bubbles: yes
 *   cancelable: Prevents an connection error to be thrown
 * @param {Object} e
 *     {String}        username   the username used for the last-active session
 *     {String}        server     the server address (URI) of the WebDAV server
 *     {String}        message    a more detailed explanation of the error
 * @event onfilecontents Fires when a {@link teleport.webdav.method.read read} request has
 * completed successfully and returns the content-body of the requested file
 *   bubbles: yes
 * @param {Object} e
 *      {String}       data       the ASCI representation of the file content-body
 *
 * @define webdav
 *
 *
 * @author      Mike de Boer
 * @version     %I%, %G%
 * @since       1.0
 * @constructor
 *
 * @inherits apf.Class
 * @inherits apf.BaseComm
 * @inherits apf.http
 *
 *
 * @default_private
 */

apf.webdav = function(struct, tagName) {
    this.$init(tagName || "webdav", apf.NODE_HIDDEN, struct);

    this.$locks = {};
    this.$lockedStack = [];
    this.$lockId = 0;
    this.$serverVars = {};
    this.$fsCache = {};
};

(function() {
    this.useHTTP = true;
    this.method = "GET";

    this.$showHidden = false;

    /**
     * @attribute {String}  [show-hidden] Flag that specifies if hidden files
     *                                    should be passed
     */
    this.$booleanProperties["showhidden"] = true;
    this.$booleanProperties["extra-properties"] = true;
    this.$supportedProperties.push("showhidden", "extra-properties", "force-host");

    this.$propHandlers["showhidden"] = function(value) {
        this.$showHidden = value;
    };

    /*
     * Simple helper function to store session variables in the private space.
     *
     * @param {String} name
     * @param {Mixed}  value
     * @type  {Mixed}
     * @private
     */
    this.$regVar = function(name, value) {
        this.$serverVars[name] = value;

        return value;
    };

    /*
     * Simple helper function to complete remove variables that have been
     * stored in the private space by register()
     *
     * @param {String} name
     *
     * @private
     */
    function unregister() {
        for (var i = 0; i < arguments.length; i++) {
            if (typeof this.$serverVars[arguments[i]] != "undefined") {
                this.$serverVars[arguments[i]] = null;
                delete this.$serverVars[arguments[i]];
            }
        }
    }

    /*
     * Simple helper function that retrieves a variable, stored in the private
     * space.
     *
     * @param {String} name
     * @type  {Mixed}
     * @private
     */
    this.$getVar = function(name) {
        return this.$serverVars[name] || "";
    };

    /**
     * Wrapper function that handles and executes each HTTP request. It also takes
     * care of streams that are incomplete or different than usual, thus produce
     * invalid XML that needs to be tidied prior to processing.
     * It also takes care of processing authentication process/ negotiation
     *
     * @param {Function}  fCallback    Function to execute when the request was successful
     * @param {String}    sPath        Path to the WebDAV resource
     * @param {String}    [sBody]      Optional body text (used for PUTs, for example)
     * @param {Object}    [oHeaders]   Additional headers in key: value format
     * @param {Boolean}   [bUseXml]    Tells the function whether to return XML. Defaults to FALSE
     * @param {Object}    [oBinary]    Object with properties for binary upload in modern browsers
     * @param {Function}  [fCallback2] Optional second callback, passed to fCallback as arguments. Used mainly by the data instructions
     *
     */
    this.doRequest = function(fCallback, sPath, sBody, oHeaders, bUseXml, oBinary, fCallback2) {
        if (!this.$getVar("authenticated")) {
            return onAuth.call(this, {
                method: this.doRequest,
                context: this,
                args: arguments
            });
        }

        if (bUseXml) {
            if (!oHeaders)
                oHeaders = {};
            oHeaders["Content-type"] = "text/xml; charset=utf-8";
        }

        var fHost = this["force-host"];
        if (fHost && sPath.indexOf(fHost) === -1)
            sPath = fHost.replace(/[\/]+$/, "") + "/" + sPath.replace(/^[\/]+/, "");

        var _self = this;
        return this.get(sPath || this.$server, {
            callback: function(data, state, extra) {
                if (state != apf.SUCCESS) {
                    var oError;

                    oError = WebDAVError.call(_self, "Url: " + extra.url + "\nInfo: " + extra.message);

                    //if (extra.tpModule.retryTimeout(extra, state, _self, oError) === true)
                    //    return true;
                    if (fCallback)
                        return fCallback.call(_self, data, state, extra, fCallback2);
                    else
                        throw oError;
                }

                extra.headers = oHeaders;

                var iStatus = parseInt(extra.status, 10);
                if (iStatus == 401) //authentication requested
                    return; // 401's are handled by the browser already, so no need for additional processing...

                var sResponse = (extra.http.responseText || "");
                if ((sResponse.length > 10 || sResponse.replace(/^[\s\n\r]+|[\s\n\r]+$/g, "") != "")
                  && sResponse.substr(0, 14) == "<?xml version=") {
                    try {
                        data = (extra.http.responseXML && extra.http.responseXML.documentElement)
                            ? apf.xmlParseError(extra.http.responseXML)
                            : apf.getXmlDom(extra.http.responseText);

                        if (!apf.supportNamespaces)
                            data.setProperty("SelectionLanguage", "XPath");

                        data = data.documentElement;
                        extra.data = data;//.documentElement;
                    }
                    catch (e) {
                        if (fCallback)
                            return fCallback.call(_self, data, state, extra, fCallback2);
                        else
                            throw WebDAVError.call(_self, "Received invalid XML\n\n" + e.message);
                    }
                }

                if (typeof fCallback == "function")
                    fCallback.call(_self, data, state, extra, fCallback2);
            },
            nocache: false,
            useXML: false,//true,
            ignoreOffline: true,
            data: sBody || "",
            binary: oBinary || false,
            headers: oHeaders,
            username: this.$getVar("auth-username") || null,
            password: this.$getVar("auth-password") || null
        });
    };

    /*
     * Something went wrong during the authentication process; this function
     * provides a central mechanism for dealing with this situation
     *
     * @param {String}      msg
     * @type      {Boolean}
     * @exception {Error} A general Error object
     * @private
     */
    function notAuth(msg) {
        unregister.call(this, "auth-password");

        var extra = {
            username: this.$getVar("auth-username"),
            server: this.$server,
            message: msg || "Access denied. Please check you username or password."
        }

        var cb = this.$getVar("auth-callback");
        if (cb) {
            cb(null, apf.ERROR, extra);
            unregister.call(this, "auth-callback");
        }

        

        return this.dispatchEvent("authfailure", extra);
    }

    /*
     * Our connection to the server has dropped, or the WebDAV server can not be
     * reached at the moment. We will cancel the authentication process and
     * dispatch a 'connectionerror' event
     *
     * @param {String}  msg
     * @type  {Boolean}
     * @private
     */
    function connError(msg) {
        unregister.call(this, "auth-password");

        var extra = {
            username: this.$getVar("auth-username"),
            server: this.$server,
            message: msg || "Could not connect to server, please contact your System Administrator."
        }

        var cb = this.$getVar("auth-callback");
        if (cb) {
            cb(null, apf.ERROR, extra);
            unregister.call(this, "auth-callback");
        }

        

        return this.dispatchEvent("connectionerror", extra);
    }

    /*
     * Wrapper function for generation WebDAV-specific Error reporting
     *
     * @param {String} sMsg Message that lists the error details
     * @type  {Error}
     * @private
     */
    function WebDAVError(sMsg) {
        return new Error(apf.formatErrorString(0, this,
                         "WebDAV Communication error", sMsg));
    }

    /*
     * Integration with {@link auth} to implement application wide single
     * sign-on for WebDAV
     *
     * @param {Function} callback Will be executed upon successful authentication
     *
     * @private
     */
    function onAuth(callback) {
        var oDoc, authRequired = false;
        if (apf.isIE) {
            try {
                oDoc = new ActiveXObject("Msxml2.DOMDocument");
            }
            catch (e) {}
        }
        else if (document.implementation && document.implementation.createDocument) {
            try {
                oDoc = document.implementation.createDocument("", "", null);
            }
            catch (e) {}
        }

        try {
            if (apf.isIE) { // only support IE for now, other browsers cannot detect 401's silently yet...
                oDoc.async = false;
                oDoc.load(this.$rootPath);
            }
        }
        catch (e) {
            authRequired = true;
        }

        var auth = this.ownerDocument
           && this.ownerDocument.getElementsByTagNameNS(apf.ns.apf, "auth")[0];
        if (authRequired) {
            auth.authRequired(callback);
        }
        else {
            this.$regVar("authenticated", true);
            if (callback && callback.method)
                callback.method.apply(callback.context, callback.args);
        }
    }

    /**
     * Attempts to authenticate the session using HTTP-AUTH (simple
     * authentication mechanism)
     *
     * @param {String}   username Username of the password-protected WebDAV resource
     * @param {String}   password Password in plaintext format
     * @param {Function} callback Function to be executed when the authentication succeeded
     *
     */
    this.authenticate = function(username, password, callback) {
        this.$regVar("auth-username", username);
        this.$regVar("auth-password", password);
        this.$regVar("auth-callback", callback);

        var auth = apf.document.getElementsByTagNameNS(apf.ns.apf, "auth")[0];
        if (!auth)
            return;

        this.doRequest(function(data, state, extra) {
            if (extra.status == 401)
                return auth.authRequired();
            this.$regVar("authenticated", true);
            var cb = this.$getVar("auth-callback");
            if (cb) {
                cb(null, state, extra);
                unregister.call(this, "auth-callback");
            }
        }, this.$rootPath, null, {}, false, null);
    };

    /**
     * Unset all cached values.
     *
     *
     */
    this.reset = function() {
        unregister.call(this, "authenticated");
        unregister.call(this, "auth-username");
        unregister.call(this, "auth-password");
        unregister.call(this, "auth-callback");
    };

    //------------ Filesystem operations ----------------//

    /**
     * Check whether a file or directory  exists on the remote filesystem and pass
     * that information to a callback function
     *
     * @param {String}   sPath    Path to the file or directory on the WebDAV server
     * @param {Function} callback Function to execute when the request was successful
     *
     */
    this.exists = function(sPath, callback) {
        this.getProperties(sPath, 0, function(data, state, extra) {
            callback(state === apf.SUCCESS);
        });
    };

    /**
     * Read the content of a file as plaintext and pass the data to a callback
     * function
     *
     * @param {String}   sPath    Path to the file on the WebDAV server
     * @param {Function} callback Function to execute when the request was successful
     *
     */
    this.readFile =
    this.read = function(sPath, callback) {
        this.method = "GET";
        this.doRequest(function(data, state, extra) {
            var iStatus = parseInt(extra.status, 10);
            if (iStatus == 403) { //Forbidden
                var oError = WebDAVError.call(this, "Unable to read file. Server says: "
                             + apf.webdav.STATUS_CODES["403"]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && !callback)
                    throw oError;
            }
            else {
                callback
                    ? callback.call(this, extra.http.responseText, state, extra)
                    : this.dispatchEvent("onfilecontents", {data: extra.http.responseTex});
            }
        }, sPath);
    };

    /**
     * Reads the contents of a directory resource (one level deep) and passes
     * the resulting XML to a  callback function to be processed further.
     *
     *
     * @param {String}   sPath    Path to the file on the WebDAV server
     * @param {Function} callback Function to execute when the request was successful
     *
     */
    this.readdir = function(sPath, callback) {
        if (sPath.charAt(sPath.length - 1) != "/")
            sPath += "/";
        return this.getProperties(sPath, 1, callback);
    };

    /**
     * Creates a new directory resource on the WebDAV server.
     *
     * @param {String}   sPath      Path of the new directory on the WebDAV server
     * @param {Boolean}  [bLock]    Whether to require a lock before copy
     * @param {Function} [callback] Function to execute when the request was successful
     *
     */
    this.mkdir = function(sPath, bLock, callback) {
        if (bLock) {
            var oLock = this.lock(sPath);
            if (!oLock.token)
                return updateLockedStack.call(this, oLock, "mkdir", arguments);
        }

        var _self = this;

        this.method = "MKCOL";
        this.doRequest(function(data, state, extra) {
            bLock && unregisterLock.call(this, sPath);
            var iStatus = parseInt(extra.status, 10);
            if (iStatus == 201) { //Created
                _self.readdir(sPath.substr(0, sPath.lastIndexOf("/")), callback);
            }
            else if (iStatus == 400 || iStatus == 403 || iStatus == 405 || iStatus == 409
              || iStatus == 415 || iStatus == 507) {
                var oError = WebDAVError.call(this, "Unable to create directory '" + sPath
                             + "'. Server says: "
                             + apf.webdav.STATUS_CODES[String(iStatus)]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && callback)
                    callback(oError);
            }
        }, sPath, null, bLock && oLock.token
            ? {"If": "<" + oLock.token + ">"}
            : null, true);
    };

    /**
     * Reads the properties of a resource on the server.
     *
     *
     * @param {String}   sPath    Path to the resource on the WebDAV server
     * @param {Function} callback Function to execute when the request was successful
     *
     */
    this.list = function(sPath, callback) {
        return this.getProperties(sPath, 0, callback);
    };

    /**
     * Write new contents (plaintext) to a file resource on the server, with or
     * without an existing lock on the resource.
     *
     * @param {String}   sPath     Path to the file on the WebDAV server
     * @param {String}   sContent  New content-body of the file
     * @param {Boolean}  [bLock]   Whether to require a lock before write
     * @param {Object}   [oBinary] Object with properties for binary upload in modern browsers
     * @param {Function} callback  Function to execute when the request was successful
     *
     */
    this.writeFile =
    this.write = function(sPath, sContent, bLock, oBinary, callback) {
        if (bLock) {
            var oLock = this.lock(sPath);
            if (!oLock.token)
                return updateLockedStack.call(this, oLock, "write", arguments);
        }
        // binary option has been added later, keep fallback possible
        if (typeof callback == "undefined" && typeof oBinary == "function") {
            callback = oBinary;
            oBinary = null;
        }

        this.method = "PUT";
        var _self = this;
        this.doRequest(function(data, state, extra) {
            var iStatus = parseInt(extra.status, 10);
            if (state != apf.SUCCESS) {
                var oError = WebDAVError.call(this, "Unable to write to file. Server says: "
                             + apf.webdav.STATUS_CODES[String(iStatus)]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && !callback)
                    throw oError;
                return callback && callback.call(this, data, apf.ERROR, extra);
            }
            else {
                _self.getProperties(sPath, 0, callback);
            }
        }, sPath, sContent, bLock && oLock.token
            ? {"If": "<" + oLock.token + ">"}
            : null, null, oBinary);
    };

    /**
     * Copies a file or directory resource to any location on the same WebDAV
     * server.
     *
     * @param {String}   sFrom        Path to the file on the WebDAV server to be copied
     * @param {String}   sTo          New location to place the copy at
     * @param {Boolean}  [bOverwrite] Tells whether to overwrite any existing resource
     * @param {Boolean}  [bLock]      Whether to require a lock before copy
     * @param {Function} callback     Function to execute when the request was successful
     *
     */
    this.copy = function(sFrom, sTo, bOverwrite, bLock, callback) {
        if (!sTo || sFrom == sTo)
            return (callback && callback("", apf.SUCCESS, {}));

        if (bLock) {
            var oLock = this.lock(sFrom);
            if (!oLock.token)
                return updateLockedStack.call(this, oLock, "copy", arguments);
        }

        this.method = "COPY";
        var oHeaders = {
            "Destination": sTo || this.$server
        };
        if (typeof bOverwrite == "undefined")
            bOverwrite = true;
        if (!bOverwrite)
            oHeaders["Overwrite"] = "F";
        if (bLock && oLock.token)
            oHeaders["If"] = "<" + oLock.token + ">";
        this.doRequest(function(data, state, extra) {
            bLock && unregisterLock.call(this, sFrom);
            var iStatus = parseInt(extra.status, 10);
            if (iStatus == 400 || iStatus == 403 || iStatus == 409 || iStatus == 412
              || iStatus == 423 || iStatus == 424 || iStatus == 502
              || iStatus == 507) {
                var oError = WebDAVError.call(this, "Unable to copy file '" + sFrom
                             + "' to '" + sTo + "'. Server says: "
                             + apf.webdav.STATUS_CODES[String(iStatus)]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && !callback)
                    throw oError;
            }
            callback && callback.call(this, data, state, extra);
        }, sFrom, null, oHeaders);
    };

    /**
     * Moves a file or directory resource to any location on the same WebDAV
     * server.
     *
     * @param {String}   sFrom        Path to the file on the WebDAV server to be moved
     * @param {String}   sTo          New location to move the resource to
     * @param {Boolean}  [bOverwrite] Tells whether to overwrite any existing resource
     * @param {Boolean}  [bLock]      Whether to require a lock before move
     * @param {Function} callback     Function to execute when the request was successful
     *
     */
    this.rename =
    this.move = function(sFrom, sTo, bOverwrite, bLock, callback) {
        if (!sTo || sFrom == sTo)
            return (callback && callback("", apf.SUCCESS, {}));

        if (bLock) {
            var oLock = this.lock(sFrom);
            if (!oLock.token)
                return updateLockedStack.call(this, oLock, "move", arguments);
        }

        this.method = "MOVE";
        var oHeaders = {
            "Destination": sTo || this.$server
        };
        if (typeof bOverwrite == "undefined")
            bOverwrite = true;
        if (!bOverwrite)
            oHeaders["Overwrite"] = "F";
        if (bLock && oLock.token)
            oHeaders["If"] = "<" + oLock.token + ">";
        this.doRequest(function(data, state, extra) {
            bLock && unregisterLock.call(this, sFrom);
            var iStatus = parseInt(extra.status, 10);
            if (iStatus == 400 || iStatus == 403 || iStatus == 409 || iStatus == 412
              || iStatus == 423 || iStatus == 424 || iStatus == 501 || iStatus == 502 || iStatus == 500) {
                var oError = WebDAVError.call(this, "Unable to move file '" + sFrom
                             + "' to '" + sTo + "'. Server says: "
                             + apf.webdav.STATUS_CODES[String(iStatus)]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && !callback)
                    throw oError;
            }
            else { //success!!
                if (this.$fsCache[sFrom]) {
                    this.$fsCache[sTo] = this.$fsCache[sFrom];
                    this.$fsCache[sTo].path = sTo;
                    delete this.$fsCache[sFrom];
                }
            }
            callback && callback.call(this, data, state, extra);
        }, sFrom, null, oHeaders);
    };

    /**
     * Removes an existing directory or file resource from the WebDAV server.
     *
     * @param {String}   sPath    Path to the resource to be removed from the WebDAV server
     * @param {Boolean}  [bLock]  Whether to require a lock before remove
     * @param {Function} callback Function to execute when the request was successful
     *
     */
    this.remove = function(sPath, bLock, callback) {
        if (bLock) {
            var oLock = this.lock(sPath);
            if (!oLock.token)
                return updateLockedStack.call(this, oLock, "remove", arguments);
        }

        this.method = "DELETE";
        this.doRequest(function(data, state, extra) {
            bLock && unregisterLock.call(this, sPath);
            var iStatus = parseInt(extra.status, 10);
            if (iStatus == 400 || iStatus == 423 || iStatus == 424) { //Failed dependency (collections only)
                var oError = WebDAVError.call(this, "Unable to remove file '" + sPath
                             + "'. Server says: "
                             + apf.webdav.STATUS_CODES[String(iStatus)]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && !callback)
                    throw oError;
            }
            callback && callback.call(this, data, state, extra);
        }, sPath, null, bLock && oLock.token
            ? {"If": "<" + oLock.token + ">"}
            : null);
    };

    this.report = function(sPath, reportName, oProperties, callback) {
        var aCont = ['<?xml version="1.0" encoding="utf-8" ?>',
            '<D:' + reportName + ' xmlns:D="', apf.webdav.NS.D, '">'];
        if (oProperties) {
            for (var prop in oProperties) {
                aCont.push("<D:" + prop, (oProperties[prop]
                    ? ">" + apf.escapeXML(oProperties[prop]) + "</D:" + prop + ">"
                    : "/>"));
            }
        }
        aCont.push("</D:", reportName, ">");

        this.method = "REPORT";

        return this.doRequest(function(data, state, extra) {
            var iStatus = parseInt(extra.status, 10);
            if (state != apf.SUCCESS) {
                var oError = WebDAVError.call(this, "Unable to fetch report on '" + sPath
                             + "'. Server says: "
                             + apf.webdav.STATUS_CODES[String(iStatus)]);
                if (this.dispatchEvent("error", {
                    error: oError,
                    bubbles: true
                  }) === false && !callback)
                    throw oError;
            }
            callback && callback.call(this, data, state, extra);
        }, sPath, aCont.join(""));
    };

    /**
     * Wrapper function that centrally manages locking of resources. Files and
     * directories (resources) can be locked prior to any modifying operation to
     * prevent the resource being modified by another user before the transaction
     * of this user has finished or even started.
     *
     *
     * @param {String}   sPath      Path to the resource on the server to be locked
     * @param {Number}   [iDepth]   Depth of lock recursion down the tree, should be '1' or 'Infinity'
     * @param {Number}   [iTimeout] Lifetime of the lock, in seconds. Defaults to Infinite.
     * @param {String}   [sLock]    Previous lock token
     * @param {Function} [callback] Function that is executed upon a successful LOCK request
     * @type  {Object}
     */
    this.lock = function(sPath, iDepth, iTimeout, sLock, callback) {
        // first, check for existing lock
        var oLock = this.$locks[sPath];
        if (oLock && oLock.token) {
            //@todo renew the lock (if needed - check timeout)...
            return oLock;
        }

        this.method = "LOCK"

        iTimeout = iTimeout ? "Infinite, Second-4100000000" : "Second-" + iTimeout;
        var oHeaders = {
            "Timeout": iTimeout
        };
        if (iDepth)
            oHeaders["Depth"] = iDepth || "Infinity";
        if (sLock)
            oHeaders["If"] = "<" + sLock + ">";
        var xml = '<?xml version="1.0" encoding="utf-8"?>'
                + '<D:lockinfo xmlns:D="' + apf.webdav.NS.D + '">'
                +     '<D:lockscope><D:exclusive /></D:lockscope>'
                +     '<D:locktype><D:write /></D:locktype>'
                +     '<D:owner><D:href>'
                +      document.location.toString().escapeHTML() +
                +     '</D:href></D:owner>'
                + '</D:lockinfo>';
        this.doRequest(registerLock, sPath, xml, oHeaders, true, null, callback);
        return newLock.call(this, sPath);
    };

    /**
     * Wrapper function that centrally manages the unlocking of resources that
     * have been locked earlier on.
     *
     * @param {Object}   oLock    Object representing a Lock on a resource
     * @param {Function} callback Function that is executed upon a successful UNLOCK request
     *
     */
    this.unlock = function(oLock, callback) {
        if (typeof oLock == "string")
            oLock = this.$locks[oLock];
        if (!oLock || !oLock.token) return;

        this.method = "UNLOCK";
        this.doRequest(function(data, state, extra) {
            unregisterLock.call(this, extra.url.replace(this.$server, ""));
        }, oLock.path, null, {
            "Lock-Token": "<" + oLock.token + ">"
        }, true, callback);
    };

    /*
     * Add a new lock token/ object to the stack
     *
     * @path {String} sPath Path pointing to the resource on the server
     * @type {Object}
     * @private
     */
    function newLock(sPath) {
        return this.$locks[sPath] = {
            path: sPath,
            id: this.$lockId++,
            token: null
        };
    }

    /*
     * Handler function that registers a lock globally when a LOCK request was
     * successful. It parses all the info it received from the server response
     * and caches that info for reuse.
     *
     * @param {XmlDocument} data  Actual XML data, received from the server
     * @param {Number}      state Internal - APF defined - state of the request
     * @param {Object}      extra Simple object that contains additional request data
     *
     * @private
     */
    function registerLock(data, state, extra) {
        var iStatus = parseInt(extra.status, 10),
            sPath = extra.url.replace(this.$server, ""),
            oLock = this.$locks[sPath] || newLock.call(this, sPath);
        if (iStatus == 400 || iStatus == 409 || iStatus == 423 || iStatus == 412) {
            // lock failed, so unregister it immediately
            unregisterLock.call(this, extra.url.replace(this.$server, ""));
            var oError = WebDAVError.call(this, "Unable to apply lock to '" + sPath
                         + "'. Server says: "
                         + apf.webdav.STATUS_CODES[String(iStatus)]);
            if (this.dispatchEvent("error", {
                error: oError,
                bubbles: true
              }) === false)
                throw oError;
        }

        var NS = apf.webdav.NS,
            oOwner = $xmlns(data, "owner",     NS.ns0)[0],
            oToken = $xmlns(data, "locktoken", NS.D)[0];
        oLock.path = sPath;
        oLock.type = "write";
        oLock.scope = $xmlns(data,   "exclusive", NS.D).length ? "exclusive" : "shared";
        oLock.depth = $xmlns(data,   "depth",     NS.D)[0].firstChild.nodeValue;
        oLock.owner = $xmlns(oOwner, "href",      NS.ns0)[0].firstChild.nodeValue;
        oLock.timeout = $xmlns(data,   "timeout",   NS.D)[0].firstChild.nodeValue;
        oLock.token = $xmlns(oToken, "href",      NS.D)[0].firstChild.nodeValue.split(":")[1];

        purgeLockedStack.call(this, oLock);
    }

    /*
     * Removes a Lock token/ object from the stack.
     *
     * @param {String} sPath Path pointing to the resource on the server
     *
     * @private
     */
    function unregisterLock(sPath) {
        var oLock = this.$locks[sPath];
        if (!oLock) return;
        purgeLockedStack.call(this, oLock, true);
        this.$locks[sPath] = oLock = null;
        delete this.$locks[sPath];
    }

    /*
     * Update the stack of lock requests (NOT the stack of valid locks!) with a
     * new Lock, or an updated one (lifetime may have changed)
     *
     * @param {Object} oLock Object that contains specific info about the Lock
     * @param {String} sFunc Name of the function that requested the lock
     * @param {Array}  aArgs List of arguments that should get passed to that function when the lock is available
     * @type  {Object}
     * @private
     */
    function updateLockedStack(oLock, sFunc, aArgs) {
        return this.$lockedStack.push({
            lockId: oLock.id,
            func: sFunc,
            args: aArgs
        });
    }

    /*
     * Purge the stack of lock requests, called when a lock request returned a
     * result. If bFailed is set to TRUE, the function that requested the lock
     * will be executed.
     *
     * @param {Object}  oLock   Simple object that represents a validated Lock
     * @param {Boolean} bFailed Tells whether the requesting function may be excuted
     *
     * @private
     */
    function purgeLockedStack(oLock, bFailed) {
        for (var i = this.$lockedStack.length - 1; i >= 0; i--) {
            if (this.$lockedStack[i].lockId != oLock.id) continue;
            if (!bFailed)
                this[this.$lockedStack[i].func].apply(this, this.$lockedStack[i].args);
            this.$lockedStack.remove(i);
        }
    }

    /**
     * Request the server to list the properties of a specific resource and,
     * possibly, its children.
     *
     * @param {String}   sPath    Path pointing to the resource on the server
     * @param {Number}   iDepth   Depth of lock recursion down the tree, should be '1' or 'Infinity'
     * @param {Function} callback Function that is executed upon a successful LOCK request
     * @param {Object}   oHeaders Additional headers in key: value format
     *
     */
    this.getProperties = function(sPath, iDepth, callback, oHeaders) {
        // Note: caching is being done by an external model
        this.method = "PROPFIND";
        // XXX maybe we want to change this to allow getting selected props
        var xml = '<?xml version="1.0" encoding="utf-8" ?>'
                + '<D:propfind xmlns:D="' + apf.webdav.NS.D + '">'
                +       '<D:allprop />'
                + '</D:propfind>';
        oHeaders = oHeaders || {};
        oHeaders["Depth"] = typeof iDepth != "undefined" ? iDepth : 1
        this.doRequest(parsePropertyPackets, sPath, xml, oHeaders, true, null, callback);
    };

    /**
     * Request the server to change and set properties of a specific resource
     * with different values.
     * @todo Untested functionality
     *
     * @param {String} sPath     Path pointing to the resource on the server
     * @param {Object} oPropsSet A mapping from namespace to a mapping of key/value pairs (where value is an *entitized* XML string)
     * @param {Object} oPropsDel A mapping from namespace to a list of names
     * @param {String} sLock     Lock identifier
     * @private
     */
    this.setProperties = function(sPath, oPropsSet, oPropsDel, sLock) {
        this.method = "PROPPATCH";

        this.doRequest(function(data, state, extra) {
            
        }, sPath, buildPropertiesBlock.call(this, oPropsSet, oPropsDel),
           sLock ? {"If": "<" + sLock + ">"} : null, true);
    };

    /*
     * create the XML for a PROPPATCH request.
     *
     * @param {Object} oPropsSet A mapping from namespace to a mapping of key/value pairs (where value is an *entitized* XML string)
     * @param {Object} oPropsDel A mapping from namespace to a list of names
     * @type  {String}
     * @private
     */
    function buildPropertiesBlock(oPropsSet, oPropsDel) {
        var aOut = ['<?xml version="1.0" encoding="utf-8" ?>',
            '<D:propertyupdate xmlns:D="', apf.webdav.NS.D, '">'];

        var bHasProps = false, ns, i, j;
        for (ns in oPropsSet) {
            bHasProps = true;
            break;
        }
        if (bHasProps) {
            aOut.push("<D:set>");
            for (ns in oPropsSet) {
                for (i in oPropsSet[ns])
                    aOut.push("<D:prop>", oPropsSet[ns][i], "</D:prop>")
            }
            aOut.push("</D:set>");
        }
        bHasProps = false;
        for (ns in oPropsDel) {
            bHasProps = true;
            break;
        }
        if (bHasProps) {
            aOut.push("<D:remove><D:prop>");
            for (ns in oPropsDel) {
                for (i = 0, j = oPropsDel[ns].length; i < j; i++)
                    aOut.push('<', oPropsDel[ns][i], ' xmlns="', ns, '"/>')
            }
            aOut.push("</D:prop></D:remove>");
        }

        aOut.push("</D:propertyupdate>");
        return aOut.join("");
    }

    /*
     * Handler function that parses the response of a successful PROPFIND
     * request. It parses all the info it received from the server response
     * and caches that info for reuse.
     *
     * @param {XmlDocument} data     Actual XML data, received from the server
     * @param {Number}      state    Internal - APF defined - state of the request
     * @param {Object}      extra    Simple object that contains additional request data
     * @param {Function}    callback Function to be executed when all the property packets have been parsed
     *
     * @private
     */
    function parsePropertyPackets(oXml, state, extra, callback) {
        var status = parseInt(extra.status, 10);
        if (status == 403 || status == 401 || !oXml)
            return callback ? callback.call(this, null, state, extra) : notAuth.call(this);

        if (typeof oXml == "string")
            oXml = apf.getXml(oXml);

        var aResp = $xmlns(oXml, "response", apf.webdav.NS.D);
        var aOut = [];
        if (aResp.length) //we got a valid result set, so assume that any possible AUTH has succeeded
            this.$regVar("authenticated", true);

        var sPath;
        for (var sa = [], data, i = 0, j = aResp.length; i < j; i++) {
            // Exclude requesting URL if it matches node's HREF (same node)
            sPath = decodeURIComponent($xmlns(aResp[i], "href", apf.webdav.NS.D)[0].firstChild.nodeValue);
            if (sPath === extra.url)
                continue;

            parseItem.call(this, aResp[i], data = {});
            if (data.data) {
                sa.push({
                    toString: function(){
                        return this.v;
                    },
                    data: data.data,
                    v: (data.data.type == "file" ? 1 : 0) + "" + data.data.name.toLowerCase()
                });
            }
        }

        sa.sort();

        for (var i = 0, l = sa.length; i < l; i++) {
            aOut.push(sa[i].data.xml);
        }

//        var start = (extra.headers && typeof extra.headers.Depth != "undefined" && extra.headers.Depth == 0) ? 0 : 1;
//        for (var i = start, j = aResp.length; i < j; i++)
//            aOut.push(parseItem.call(this, aResp[i]));

        callback && callback.call(this, "<files>" + aOut.join("") + "</files>", state, extra);
    }

    /*
     * Turn an XML WebDAV node that represents a resource and turn it into a
     * reusable JS object, cache it so that it can be reused later.
     *
     * @param {XmlNode} oNode
     * @type  {String}
     * @private
     */
    function parseItem(oNode, extra) {
        var NS = apf.webdav.NS,
            sPath = decodeURIComponent($xmlns(oNode, "href", NS.D)[0].firstChild
                      .nodeValue.replace(/[\\\/]+$/, "")),
            sName = sPath.split("/").pop(),
            bHidden = (sName.charAt(0) == ".");

        if (!this.$showHidden && bHidden)
            return "";

        var t, oItem,
            sType = $xmlns(oNode, "collection", NS.D).length > 0 ? "folder" : "file",
            aCType = $xmlns(oNode, "getcontenttype", NS.D),
            aExec = $xmlns(oNode, "executable", NS.lp2);
        oItem = this.$fsCache[sPath] = apf.extend(this.$fsCache[sPath] || {}, {
            path: sPath,
            type: sType,
            size: parseInt(sType == "file"
                ? (t = $xmlns(oNode, "getcontentlength", NS.lp1)).length ? t[0].firstChild.nodeValue : 0
                : 0, 10),
            name: sName,
            contentType: (sType == "file" && aCType.length
                ? aCType[0].firstChild.nodeValue
                : ""),
            creationDate: (t = $xmlns(oNode, "creationdate", NS.lp1)).length ? t[0].firstChild.nodeValue : "",
            lastModified: (t = $xmlns(oNode, "getlastmodified", NS.lp1)).length ? t[0].firstChild.nodeValue : "",
            etag: (t = $xmlns(oNode, "getetag", NS.lp1)).length ? t[0].firstChild.nodeValue : "",
            lockable: ($xmlns(oNode, "locktype", NS.D).length > 0),
            executable: (aExec.length > 0 && aExec[0].firstChild.nodeValue == "T")
        });

        if (extra)
            extra.data = oItem;

        var aXml = ["<" + sType + " path=\"" + apf.escapeXML(sPath) +
            "\" type=\"" + sType + "\" size=\"" + oItem.size + "\" name=\"" +
            apf.escapeXML(oItem.name) + "\" contenttype=\"" + oItem.contentType +
            "\" modifieddate=\"" + oItem.lastModified + "\" creationdate=\"" +
            oItem.creationDate + "\" lockable=\"" + oItem.lockable.toString() +
            "\" hidden=\"" + bHidden.toString() + "\" executable=\"" +
            oItem.executable.toString() + "\""];

        if (this["extra-properties"]) {
            var oNode,
                aNodes = oNode.selectNodes("//a:*"),
                i = 0,
                l = aNodes.length;
            for (; i < l; ++i) {
                oNode = aNodes[i];
                if (oNode.firstChild && oNode.firstChild.nodeValue) {
                    aXml.push(" " + oNode.localName + "=\"" +
                        oNode.firstChild.nodeValue + "\"");
                }
            }
        }

        return oItem.xml = aXml.join("") + "/>";
    }

    

    /**
     * Instruction handler for WebDav protocols.
     */
    this.exec = function(method, args, callback) {
        var cb = function(data, state, extra) {
            extra.originalArgs = args
            if (typeof args[args.length - 1] == "function")
                args[args.length - 1](data, state, extra);
            callback && callback(data, state, extra);
        };
        // RULE for case aliases: first, topmost match is the preferred term for any
        //                        action and should be used in demos/ examples in
        //                        favor of other aliases.
        switch (method) {
            case "login":
            case "authenticate":
                this.authenticate(args[0], args[1], cb);
                break;
            case "logout":
                this.reset();
                break;
            case "exists":
                this.exists(args[0], cb);
                break;
            case "read":
                this.readFile(args[0], cb);
                break;
            case "create":
                var path = args[0] ? args[0] : "";
                if (path.charAt(path.length - 1) != "/")
                    path = path + "/";
                this.writeFile(path + args[1], args[2], args[3] || false, cb);
                break;
            case "write":
            case "store":
            case "save":
                this.writeFile(args[0], args[1], args[2] || false, cb);
                break;
            case "copy":
            case "cp":
                this.copy(args[0], args[1], args[2] || true, args[3] || false, cb);
                break;
            case "rename":
                var sBasepath = args[1].substr(0, args[1].lastIndexOf("/") + 1);
                this.rename(args[1], sBasepath + args[0], args[2] || false, args[3] || false, cb);
                break;
            case "move":
            case "mv":
                path = args[1];
                if (path.charAt(path.length - 1) != "/")
                    path = path + "/";
                this.rename(args[0], path + args[0].substr(args[0].lastIndexOf("/") + 1),
                    args[2] || false, args[3] || false, cb);
                break;
            case "remove":
            case "rmdir":
            case "rm":
                this.remove(args[0], args[1] || false, cb);
                break;
            case "readdir":
            case "scandir":
                this.readdir(args[0], cb);
                break;
            case "getroot":
                this.getProperties(this.$rootPath, 0, cb);
                break;
            case "mkdir":
                path = args[0] ? args[0] : "";
                if (path.charAt(path.length - 1) != "/")
                    path = path + "/";
                this.mkdir(path + args[1], args[2] || false, cb)
                break;
            case "lock":
                this.lock(args[0], null, null, null, cb);
                break;
            case "unlock":
                this.unlock(args[0], cb);
                break;
            case "report":
                this.report(args[0], args[1], args[2], cb);
                break;
            default:
                
                break;
        }
    };

    
}).call(apf.webdav.prototype = new apf.Teleport());

apf.aml.setElement("webdav", apf.webdav);

// Collection of shorthands for all namespaces known and used by this class
apf.webdav.NS = {
    D: "DAV:",
    ns0  : "DAV:",
    lp1  : "DAV:",
    lp2  : "http://apache.org/dav/props/"
};

apf.webdav.STATUS_CODES = {
    "100": "Continue",
    "101": "Switching Protocols",
    "102": "Processing",
    "200": "OK",
    "201": "Created",
    "202": "Accepted",
    "203": "None-Authoritive Information",
    "204": "No Content",
    "1223": "No Content",
    "205": "Reset Content",
    "206": "Partial Content",
    "207": "Multi-Status",
    "300": "Multiple Choices",
    "301": "Moved Permanently",
    "302": "Found",
    "303": "See Other",
    "304": "Not Modified",
    "305": "Use Proxy",
    "307": "Redirect",
    "400": "Bad Request",
    "401": "Unauthorized",
    "402": "Payment Required",
    "403": "Forbidden",
    "404": "Not Found",
    "405": "Method Not Allowed",
    "406": "Not Acceptable",
    "407": "Proxy Authentication Required",
    "408": "Request Time-out",
    "409": "Conflict",
    "410": "Gone",
    "411": "Length Required",
    "412": "Precondition Failed",
    "413": "Request Entity Too Large",
    "414": "Request-URI Too Large",
    "415": "Unsupported Media Type",
    "416": "Requested range not satisfiable",
    "417": "Expectation Failed",
    "422": "Unprocessable Entity",
    "423": "Locked",
    "424": "Failed Dependency",
    "500": "Internal Server Error",
    "501": "Not Implemented",
    "502": "Bad Gateway",
    "503": "Service Unavailable",
    "504": "Gateway Time-out",
    "505": "HTTP Version not supported",
    "507": "Insufficient Storage"//,
//    12002 ERROR_INTERNET_TIMEOUT
//    12007 ERROR_INTERNET_NAME_NOT_RESOLVED
//    12029 ERROR_INTERNET_CANNOT_CONNECT
//    12030 ERROR_INTERNET_CONNECTION_ABORTED
//    12031 ERROR_INTERNET_CONNECTION_RESET
//    12152 ERROR_HTTP_INVALID_SERVER_RESPONSE
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * UndoData is the command object for the actiontracker. Each instance of this class
 * contains information about a single event in the application. It can be undone,
 * and it knows how to synchronize the change to a (remote) data source.
 *
 * @class apf.UndoData
 * @default_private
 */
apf.UndoData = function(settings, at) {
    this.localName = "UndoData";
    this.extra = {};
    
    this.rdbQueue = {};
    
    apf.extend(this, settings);

    if (!this.timestamp)
        this.timestamp = (new Date()).getUTCTime();

    if (at)
        this.at = at;
    //Copy Constructor
    else if (settings && settings.tagName == "UndoData") {
        this.args = settings.args.slice();
        
        this.rdbArgs = settings.rdbArgs.slice();
        
    }
    //Constructor
    else {
        /*
            @todo: Please check the requirement for this and how to solve
            this. Goes wrong with multiselected actions!
        */
        this.selNode = this.selNode || (this.action == "removeNode"
            ? this.args[0]
            : (this.amlNode
                ? this.amlNode.selected
                : null));
    }

    var options, _self = this;

    

    
    //Send RDB Message..
    this.processRsbQueue = function(){
        if (this.rdbModel)
            this.rdbModel.rdb.$processQueue(this);
    };

    this.clearRsbQueue = function(){
        this.rdbQueue =
        this.rdbModel = null;
    };
    

    /**
     * Save the change to a data source.
     * @param {Boolean} undo Specifies whether the change is undone.
     */
    this.saveChange = function(undo, at, callback) {
        //Grouped undo/redo support
        if (this.action == "group") {
            var rpcNodes = this.args[1];
            at.$addToQueue(rpcNodes, undo, true);
            return at.$queueNext(this);
        }

        var dataInstruction;
        if (this.xmlActionNode) {
            dataInstruction = this.xmlActionNode.getAttribute(undo ? "undo" : "set");
            if (undo && !dataInstruction)
                dataInstruction = this.xmlActionNode.getAttribute("set");
        }

        if (!dataInstruction) {
            
            this.processRsbQueue();
            
            return at.$queueNext(this);
        }

        this.state = undo ? "restoring" : "saving";

        if (!options.asyncs) { //Precall didnt contain any async calls
            return at.$receive(null, apf.SUCCESS, {amlNode: this.amlNode},
                this, callback);
        }

        options.ignoreOffline = true;
        apf.saveData(dataInstruction, options);
    };

    this.preparse = function(undo, at, multicall, callback) {
        var dataInstruction;
        if (this.xmlActionNode) {
            dataInstruction = this.xmlActionNode.getAttribute(undo ? "undo" : "set");
            if (undo && !dataInstruction)
                dataInstruction = this.xmlActionNode.getAttribute("set");
        }

        if (!dataInstruction)
            return this;

        options = apf.extend({
            xmlNode: this.action == "multicall"
              ? this.args[0].xmlNode
              : this.selNode || this.xmlNode,
            userdata: apf.isTrue(this.xmlActionNode.getAttribute("ignore-fail")),
            multicall: multicall,
            undo: undo,
            precall: true,

            //Callback is only for async calls
            callback: function(data, state, extra) {
                if (options.asyncs) { //Set by apf.saveData
                    extra.amlNode = _self.amlNode;
                    return at.$receive(data, state, extra, _self, callback);
                }
            }
        }, this.extra);

        

        apf.saveData(dataInstruction, options); //@todo please check if at the right time selNode is set

        return this;
    };
};




/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Default actions, that are known to the actiontracker
 * @todo test if the .extra speed impact matters
 * @todo ifdef the undo sections to only be there when the actiontracker is enabled
 * @private
 */
apf.actiontracker.actions = {
    "setTextNode" : function(undoObj, undo) {
        var q = undoObj.args;

        // Set Text Node
        if (!undo)
            apf.xmldb.setTextNode(q[0], q[1], q[2], undoObj);
        else //Undo Text Node Setting
            apf.xmldb.setTextNode(q[0], undoObj.extra.oldValue, q[2], undoObj);
    },

    "setAttribute" : function(undoObj, undo) {
        var q = undoObj.args;

        // Set Attribute
        if (!undo) {
            //Set undo info
            undoObj.extra.name = q[1];
            undoObj.extra.oldValue = q[0].getAttribute(q[1]);

            apf.xmldb.setAttribute(q[0], q[1], q[2], q[3], undoObj);
        }
        // Undo Attribute Setting
        else {
            if (!undoObj.extra.oldValue)
                apf.xmldb.removeAttribute(q[0], q[1], null, undoObj);
            else
                apf.xmldb.setAttribute(q[0], q[1], undoObj.extra.oldValue, q[3], undoObj);
        }
    },

    "removeAttribute" : function(undoObj, undo) {
        var q = undoObj.args;

        // Remove Attribute
        if (!undo) {
            // Set undo info
            undoObj.extra.name = q[1];
            undoObj.extra.oldValue = q[0].getAttribute(q[1]);

            apf.xmldb.removeAttribute(q[0], q[1], q[2], undoObj);
        }
        //Undo Attribute Removal
        else
            apf.xmldb.setAttribute(q[0], q[1], undoObj.extra.oldValue, q[2], undoObj);
    },

    "replaceNode" : function(undoObj, undo) {
        var q = undoObj.args;

        //Set Attribute
        if (!undo)
            apf.xmldb.replaceNode(q[0], q[1], q[2], undoObj);
        //Undo Attribute Setting
        else
            apf.xmldb.replaceNode(q[1], q[0], q[2], undoObj);
    },

    "addChildNode" : function(undoObj, undo) {
        var q = undoObj.args;

        //Add Child Node
        if (!undo)
            apf.xmldb.addChildNode(q[0], q[1], q[2], q[3], undoObj);
        //Remove Child Node
        else
            apf.xmldb.removeNode(undoObj.extra.addedNode, null, undoObj);
    },

    "appendChild" : function(undoObj, undo) {
        var q = undoObj.args;

        //Append Child Node
        if (!undo)
            apf.xmldb.appendChild(q[0], q[1], q[2], q[3], q[4], undoObj);
        //Remove Child Node
        else
            apf.xmldb.removeNode(undoObj.xmlNode, null, undoObj);//q[1]
    },

    "moveNode" : function(undoObj, undo) {
        var q = undoObj.args;

        //Move Node
        if (!undo)
            apf.xmldb.moveNode(q[0], q[1], q[2], q[3], undoObj);
        //Move Node to previous position
        else
            apf.xmldb.moveNode(undoObj.extra.oldParent, q[1],
                undoObj.extra.beforeNode, q[3], undoObj);
    },

    "removeNode" : function(undoObj, undo) {
        var q = undoObj.args;

        //Remove Node
        if (!undo)
            apf.xmldb.removeNode(q[0], q[1], undoObj);
        //Append Child Node
        else
            apf.xmldb.appendChild(undoObj.extra.parent,
                undoObj.extra.removedNode, undoObj.extra.beforeNode,
                null, null, undoObj);
    },

    /**
     * @deprecated Use "multicall" from now on
     */
    "removeNodeList" : function(undoObj, undo) {
        if (undo) {
            var d = undoObj.extra.removeList;
            for (var i = d.length - 1; i >= 0; i--) {
                apf.xmldb.appendChild(d[i].pNode,
                    d[i].removedNode, d[i].beforeNode, null, null, undoObj);
            }
        }
        else
            apf.xmldb.removeNodeList(undoObj.args, undoObj);
    },

    "group" : function(undoObj, undo, at) {
        if (!undoObj.$undostack) {
            var done = undoObj.args[0];
            undoObj.$undostack = done;
            undoObj.$redostack = [];
        }

        at[undo ? "undo" : "redo"](undoObj.$undostack.length, false,
            undoObj.$undostack, undoObj.$redostack);
    },

    /*"setProperty" : function(undoObj, undo) {
        var q = undoObj.args;//amlNode, name, value

        if (!undo) {
            undoObj.extra.oldValue = q[0][q[1]];
            q[0].setProperty(q[1], q[2], q[3], q[4]);
        }
        // Undo
        else {
            q[0].setProperty(q[1], undoObj.extra.oldValue);
        }
    },*/

    "setValueByXpath" : function(undoObj, undo) {
        var newNode, q = undoObj.args;//xmlNode, value, xpath

        // Setting NodeValue and creating the node if it doesnt exist
        if (!undo) {
            if (newNode = undoObj.extra.newNode) {
                if (newNode.nodeType == 2) {
                    apf.xmldb.setAttribute(undoObj.extra.ownerElement,
                      newNode.nodeName, newNode.nodeValue);
                    undoObj.extra.newNode = undoObj.extra.ownerElement
                      .getAttributeNode(newNode.nodeName);
                }
                else
                    apf.xmldb.appendChild(undoObj.extra.parentNode,
                        undoObj.extra.newNode, null, null, null, undoObj);
            }
            else {
                var newNodes = [];
                apf.setNodeValue(q[0], q[1], true, {
                    undoObj: undoObj,
                    xpath: q[2],
                    newNodes: newNodes,
                    forceNew: q[3]
                });

                newNode = undoObj.extra.newNode = newNodes[0];
                if (newNode) {
                    if (newNode.nodeType == 2)
                        undoObj.extra.ownerElement = newNode.ownerElement
                          || newNode.selectSingleNode("..");
                    else
                        undoObj.extra.parentNode = undoObj.extra.newNode.parentNode;
                }
            }
        }
        // Undo Setting NodeValue
        else {
            if (newNode = undoObj.extra.newNode) {
                if (newNode.nodeType == 2)
                    apf.xmldb.removeAttribute(undoObj.extra.ownerElement,
                      newNode.nodeName, null, undoObj);
                else
                    apf.xmldb.removeNode(undoObj.extra.newNode, null, undoObj);
            }
            else
                apf.setNodeValue(undoObj.extra.appliedNode,
                    undoObj.extra.oldValue, true, {undoObj: undoObj});
        }
    },

    //@todo please change .func to .action for consistency reasons
    "multicall" : function(undoObj, undo, at) {
        var q = undoObj.args;

        var dUpdate = apf.xmldb.delayUpdate;
        apf.xmldb.delayUpdate = true;

        // Set Calls
        if (!undo) {
            for (var i = 0; i < q.length; i++) {
                if (!q[i].extra)
                    q[i].extra = {};
                
                if (q[0].rdbModel)
                    q[i].rdbQueue = q[0].rdbQueue;
                
                apf.actiontracker.actions[q[i].action](q[i], false, at);
            }
            
            if (q[0].rdbModel) {
                undoObj.rdbModel = q[0].rdbModel;
                undoObj.rdbQueue = q[0].rdbQueue;
            }
            
        }
        // Undo Calls
        else {
            for (var i = q.length - 1; i >= 0; i--)
                apf.actiontracker.actions[q[i].action](q[i], true, at);
        }

        apf.xmldb.delayUpdate = dUpdate;

        //if (!dUpdate)
            //apf.xmldb.notifyQueued();
    }
};





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Implementation of the Common Gateway Interface (CGI) as a module for the RPC
 * plugin of apf.teleport.
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" protocol="cgi">
 *      <a:method
 *        name = "searchProduct"
 *        url = "http://example.com/search.php"
 *        receive = "processSearch">
 *          <a:param name="search" />
 *          <a:param name="tab" />
 *          <a:param name="textbanner" value="1" />
 *      </a:method>
 *      <a:method
 *        name = "loadProduct"
 *        url = "http://example.com/show-product.php">
 *          <a:param name="id" />
 *          <a:param name="search_id" />
 *      </a:method>
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 * Remarks:
 * Calls can be made to a server using cgi variables with a special
 * {@link term.datainstruction data instruction}
 * format.
 * <code>
 *  get="http://www.bla.nl?blah=10&foo=[@bar]&example=[10+5]"
 *  set="post http://www.bla.nl?blah=10&foo={/bar}&example=[10+5]"
 * </code>
 *
 * @addenum rpc[@protocol]:cgi
 *
 * @constructor
 *
 * @inherits apf.Teleport
 * @inherits apf.http
 * @inherits apf.rpc
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @default_private
 */
apf.cgi = function(){
    this.supportMulticall = false;
    this.namedArguments = true;

    this.unserialize = function(str) {
        return str;
    };

    this.getSingleCall = function(name, args, obj) {
        obj.push(args);
    };

    // Create message to send
    this.createMessage = function(functionName, args) {
        var prop,
            vars = [];

        function recur(o, stack) {
            if (o && o.dataType == apf.ARRAY) {
                for (var j = 0; j < o.length; j++)
                    recur(o[j], stack + "%5B" + j + "%5D");//" + j + "
            }
            else if (o && typeof o == "object") {
                if (o.nodeType) {
                    try{
                        var s = o.outerHTML || o.serialize && o.serialize() 
                          || apf.getCleanCopy(o).xml;
                    }
                    catch (e) {
                        var s = "Could not serialize object";
                    }
                    vars.push(stack + "=" + encodeURIComponent(s));
                }
                else {
                    for (prop in o) {
                        
    
                        if (typeof o[prop] == "function")
                            continue;
                        recur(o[prop], stack + "%5B" + encodeURIComponent(prop) + "%5D");
                    }
                }
            }
            else {
                if (typeof o != "undefined" && o !== null) 
                    vars.push(stack + "=" + encodeURIComponent(o));
            }
        };

        if (this.multicall) {
            vars.push("func" + "=" + this.mcallname);
            for (var i = 0; i < args[0].length; i++)
                recur(args[0][i], "f%5B" + i + "%5D");
        }
        else {
            for (prop in args) {
                

                recur(args[prop], prop);
            }
        }

        if (!this.baseUrl)
            this.baseUrl = this.url;

        this.url = this.$methods[functionName].url
            ? this.$methods[functionName].url
            : this.baseUrl;

        if (this.method != "GET")
            return vars.join("&");

        this.url = this.url + (vars.length
            ? (this.url.indexOf("?") > -1 ? "&" : "?") + vars.join("&")
            : "");

        return "";
    };

    this.addEventListener("DOMNodeInsertedIntoDocument", function() {
        this.method = (this["http-method"] || "GET").toUpperCase();
        this.contentType = this.method == "GET"
            ? null
            : "application/x-www-form-urlencoded";
    });

    /**
     * Submit a form with ajax (GET)
     *
     * @param {HTMLElement} form      the html form element to submit.
     * @param {Function}       callback  called when the http call returns.
     */
    this.submitForm = function(form, callback) {
        if (!this['postform'])
            this.addMethod('postform', callback);

        var args = [];
        for (var i = 0, l = form.elements.length; i < l; i++) {
            if (!form.elements[i].name)
                continue;
            if (form.elements[i].tagname == "input"
              && (form.elements[i].type  == "checkbox"
              || form.elements[i].type   == "radio")
              && !form.elements[i].checked)
                continue;

            if (form.elements[i].tagname = "select" && form.elements[i].multiple) {
                for (var j = 0; j < form.elements[i].options.length; j++) {
                    if (form.elements[i].options[j].selected)
                        args.push(form.elements[i].name
                            + "="
                            + encodeURIComponent(form.elements[i].options[j].value));
                }
            }
            else {
                args.push(form.elements[i].name
                    + "="
                    + encodeURIComponent(form.elements[i].value));
            }
        }

        var loc = (form.action || location.href);
        this.urls['postform'] = loc + (loc.indexOf("?") > -1 ? "&" : "?") + args.join("&");
        this['postform'].call(this);

        return false;
    };
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * Implementation of the Common Gateway Interface (REST) as a module for the RPC
 * plugin of apf.teleport.
 * Example:
 * Ajax.org Markup Language
 * <code>
 *  <a:rpc id="comm" protocol="rest">
 *      <a:method
 *        name = "deleteProduct"
 *        url = "http://example.com/products"
 *        http-method = "DELETE"
 *        receive = "processDelete" />
 *      <a:method
 *        name = "createProduct"
 *        url = "http://example.com/products"
 *        http-method = "POST" />
 *  </a:rpc>
 *
 *  <a:script>
 *      //This function is called when the search returns
 *      function processSearch(data, state, extra) {
 *          alert(data)
 *      }
 *
 *      //Execute a search for the product car
 *      comm.searchProduct('car', 10);
 *  </a:script>
 * </code>
 * Remarks:
 * Calls can be made to a server using rest variables with a special
 * {@link term.datainstruction data instruction}
 * format.
 * <code>
 *  get="http://www.bla.nl?blah=10&foo=[@bar]&example=[10+5]"
 *  set="post http://www.bla.nl?blah=10&foo={/bar}&example=[10+5]"
 * </code>
 *
 * @addenum rpc[@protocol]:rest
 *
 * @constructor
 *
 * @inherits apf.Teleport
 * @inherits apf.http
 * @inherits apf.rpc
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @author      Mike de Boer (mike AT javeline DOT com)
 * @version     %I%, %G%
 * @since       0.4
 *
 * @default_private
 */
apf.rest = function(){
    this.supportMulticall = false;
    this.namedArguments = false;
    this.nocache = false;

    this.unserialize = function(str) {
        return str;
    };

    this.getSingleCall = function(name, args, obj) {
        obj.push(args);
    };

    // Create message to send
    this.createMessage = function(functionName, args) {
        if (!this.baseUrl)
            this.baseUrl = this.url;

        var options = this.$methods[functionName];

        
        
        var body = "NOTIFY|SEND|POST|PUT".indexOf(options["http-method"].toUpperCase()) > -1 ? args.pop() : "",
            url;

        this.method = options["http-method"];
        if (options["content-type"])
            this.contentType = options["content-type"];

        var i = 0,
            l = args.length;
        for (; i < l; ++i)
            args[i] = encodeURIComponent(args[i]);

        this.url = (url = options.url
            ? options.url
            : this.baseUrl) + (l 
                ? (url.charAt(url.length - 1) == "/" 
                    ? ""
                    : "/") + args.join("/")
                : "");

        this.contentType = body ? "application/x-www-form-urlencoded" : null;
        return body;
    };
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */











/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */



/**
 * @constructor
 * @private
 */
apf.textbox.masking = function(){
    /*
        Special Masking Values:
        - PASSWORD
        
        <a:Textbox name="custref" mask="CS20999999" maskmsg="" validation="/CS200[3-5]\d{4}/" invalidmsg="" bind="custref/text()" />
    */
    
    var _FALSE_ = 9128748732;

    var _REF = {
        "0" : "\\d",
        "1" : "[12]",
        "9" : "[\\d ]",
        "#" : "[\\d +-]",
        "L" : "[A-Za-z]",
        "?" : "[A-Za-z ]",
        "A" : "[A-Za-z0-9]",
        "a" : "[A-Za-z0-9 ]",
        "X" : "[0-9A-Fa-f]",
        "x" : "[0-9A-Fa-f ]",
        "&" : "[^\s]",
        "C" : "."
    };
    
    var lastPos = -1;
    var masking = false;
    var oInput = this.$input;
    var pos = [];
    var initial, myvalue, format, fcase, replaceChar;

    this.setPosition = function(setpos) {
        setPosition(setpos || lastPos || 0);
    };

    this.addEventListener("$clear", function(){
        this.value = "";
        if (this.mask) 
            return this.setValue("");
    });
    
    this.$propHandlers["value"] = function(value) {
        var data = "";
        if (this.includeNonTypedChars) {
            for (var i = 0; i < initial.length; i++) {
                if (initial.substr(i, 1) != value.substr(i, 1))
                    data += value.substr(i, 1);//initial.substr(i,1) == replaceChar
            }
        }

        this.$insertData(data || value);
        setPosition(myvalue.length);
    };
    
    //Char conversion
    var numpadKeys = {
        "96": "0",
        "97": "1",
        "98": "2",
        "99": "3",
        "100": "4",
        "101": "5",
        "102": "6",
        "103": "7",
        "104": "8",
        "105": "9",
        "106": "*",
        "107": "+",
        "109": "-",
        "110": ".",
        "110": "/"
    };
    
    this.addEventListener("keydown", function(e) {
        var key = e.keyCode,
            stop = false;

        switch (key) {
            case 39:
                //RIGHT
                setPosition(lastPos + 1);
                stop = true;
                break;
            case 37:
                //LEFT
                setPosition(lastPos - 1);
                stop = true;
                break;
            case 35:
            case 34:
                setPosition(myvalue.length);
                stop = true;
                break;
            case 33:
            case 36:
                setPosition(0);
                stop = true;
                break;
            case 8:
                //BACKSPACE
                deletePosition(lastPos - 1);
                setPosition(lastPos - 1);
                stop = true;
                break;
            case 46:
                //DEL
                deletePosition(lastPos);
                setPosition(lastPos);
                stop = true;
                break;
            default:
                if (key == 67 && e.ctrlKey) {
                    window.clipboardData.setData("Text", this.getValue());  
                    stop = true;
                }
            break;
        }

        //@todo why isnt the conversion not always good? Check backtick.
        var chr = numpadKeys[key] || String.fromCharCode(key);
        if (setCharacter(chr))
            setPosition(lastPos + 1);

        var value, pos = lastPos;
        if (this.realtime && (value = this.getValue()) != this.value) {
            this.change(value);
            setPosition(pos);
        }

        if (apf.isCharacter(e.keyCode) || stop)
            return false;
    }, true);
    
    /* ***********************
            Init
    ************************/
    
    this.$initMasking = function(){
        ///this.keyHandler = this._keyHandler;
        this.$keyHandler = null; //temp solution
        masking = true;

        this.$input[apf.isIphone ? "onclick" : "onmouseup"] = function(e) {
            var pos = Math.min(calcPosFromCursor(), myvalue.length);
            setPosition(pos);
            return false;
        };
        
        this.$input.onpaste = function(e) {
            e = e || window.event;
            e.returnValue = false;
            this.host.setValue(window.clipboardData.getData("Text") || "");
            //setPosition(lastPos);
            $setTimeout(function(){
                setPosition(lastPos);
            }, 1); //HACK good enough for now...
        };
        
        this.getValue = function(){
            if (this.includeNonTypedChars)
                return initial == this.$input.value
                    ? "" 
                    : this.$input.value.replace(new RegExp(replaceChar, "g"), "");
            else
                return myvalue.join("");
        };
        
        this.setValue = function(value) {
            this.$propHandlers["value"].call(this, value);
        };
    };
    
    this.setMask = function(m) {
        if (!masking)
            this.$initMasking();
        
        m = m.split(";");
        replaceChar = m.pop();
        this.includeNonTypedChars = parseInt(m.pop(), 10) !== 0;
        var mask = m.join(""); //why a join here???
        var validation = "";
        var visual = "";
        var mode_case = "-";
        var strmode = false;
        var startRight = false;
        var chr;
        pos = [];
        format = "";
        fcase = "";
        
        for (var looppos = -1, i = 0; i < mask.length; i++) {
            chr = mask.substr(i, 1);
            
            if (!chr.match(/[\!\'\"\>\<\\]/)) {
                looppos++;
            }
            else {
                if (chr == "!")
                    startRight = true;
                else if (chr == "<" || chr == ">")
                    mode_case = chr;
                else if (chr == "'" || chr == "\"")
                    strmode = !strmode;
                continue;
            }
            
            if (!strmode && _REF[chr]) {
                pos.push(looppos);
                visual     += replaceChar;
                format     += chr;
                fcase      += mode_case;
                validation += _REF[chr];
            }
            else
                visual += chr;
        }

        this.$input.value = visual;
        initial = visual;
        //pos = pos;
        myvalue = [];
        //format = format;
        //fcase = fcase;
        replaceChar = replaceChar;
        
        //setPosition(0);//startRight ? pos.length-1 : 0);
        
        //validation..
        //forgot \ escaping...
    };
    
    function checkChar(chr, p) {
        var f = format.substr(p, 1);
        var c = fcase.substr(p, 1);

        if (chr.match(new RegExp(_REF[f])) == null)
            return _FALSE_;
        if (c == ">")
            return chr.toUpperCase();
        if (c == "<")
            return chr.toLowerCase();
        return chr;
    }

    function setPosition(p) {
        if (p < 0)
            p = 0;

        if (apf.hasMsRangeObject) {
            var range = oInput.createTextRange();
            range.expand("textedit");
            range.select();

            if (pos[p] == null) {
                range.collapse(false);
                range.select();
                lastPos = pos.length;
                return false;
            }

            range.collapse();
            range.moveStart("character", pos[p]);
            range.moveEnd("character", 1);
            range.select();
        }
        else {
            if (typeof pos[p] == "undefined") {
                oInput.selectionStart = oInput.selectionEnd = pos[pos.length - 1] + 1;
                lastPos = pos.length;
                return false;
            }
            oInput.selectionStart = pos[p];
            oInput.selectionEnd = pos[p] + 1;
        }

        lastPos = p;
    }
    
    function setCharacter(chr) {
        if (pos.length && pos[lastPos] == null)
            return false;
        
        chr = checkChar(chr, lastPos);
        if (chr == _FALSE_)
            return false;

        if (apf.hasMsRangeObject) {
            var range = oInput.createTextRange();
            range.expand("textedit");
            range.collapse();
            range.moveStart("character", pos[lastPos]);
            range.moveEnd("character", 1);
            range.text = chr;
            if (apf.document.activeElement == this)
                range.select();
        }
        else {
            var val = oInput.value;
            var start = oInput.selectionStart;
            var end = oInput.selectionEnd;
            oInput.value = val.substr(0, start) + chr + val.substr(end);
            oInput.selectionStart = start;
            oInput.selectionEnd = end;
        }
        
        myvalue[lastPos] = chr;
        
        return true;
    }
    
    function deletePosition(p) {
        if (pos[p] == null)
            return false;
        
        if (apf.hasMsRangeObject) {
            var range = oInput.createTextRange();
            range.expand("textedit");
            range.collapse();

            range.moveStart("character", pos[p]);
            range.moveEnd("character", 1);
            range.text = replaceChar;
            range.select();
        }
        else {
            var val = oInput.value;
            var start = pos[p];
            var end = pos[p] + 1;
            oInput.value = val.substr(0, start) + replaceChar + val.substr(end);
            oInput.selectionStart = start;
            oInput.selectionEnd = end;
        }
        
        //ipv lastPos
        myvalue[p] = " ";
    }
    
    this.$insertData = function(str) {
        if (str == this.getValue())
            return;

        var i, j;
        
        try {
            if (!apf.hasMsRangeObject && oInput.selectionStart == oInput.selectionEnd)
                setPosition(0); // is this always correct? practice will show...
        }
        catch (ex) {
            // in FF (as we know it), we cannot access the selectStart property
            // when the control/ input doesn't have the focus or is not visible.
            // A workaround is provided here...
            if (!str)
                return;
            var chr, val;
            for (i = 0, j = str.length; i < j; i++) {
                lastPos = i;
                if (pos[lastPos] == null)
                    continue;
                chr = checkChar(str.substr(i, 1), i);
                if (chr == _FALSE_)
                    continue;
                val = oInput.value;
                oInput.value = val.substr(0, pos[i]) + chr + val.substr(pos[i] + 1);
            }
            if (str.length)
                lastPos++;
            return; // job done, bail out
        }

        str = this.dispatchEvent("insert", { data : str }) || str;
        
        if (!str) {
            if (!this.getValue()) return; //maybe not so good fix... might still flicker when content is cleared
            for (i = this.getValue().length - 1; i >= 0; i--)
                deletePosition(i);
            setPosition(0); 
            return;
        }
        
        for (i = 0, j = str.length; i < j; i++) {
            lastPos = i;
            setCharacter(str.substr(i, 1));
            if (!apf.hasMsRangeObject)
                setPosition(i + 1);
        }
        if (str.length)
            lastPos++;
    };
    
    function calcPosFromCursor(){
        var range, lt = 0;

        if (!apf.hasMsRangeObject) {
            lt = oInput.selectionStart;
        }
        else {
            range = document.selection.createRange();
            var r2 = range.duplicate();
            r2.expand("textedit");
            r2.setEndPoint("EndToStart", range);
            lt = r2.text.length;
        }
    
        for (var i = 0; i < pos.length; i++) {
            if (pos[i] > lt)
                return (i == 0) ? 0 : i - 1;
        }

        return myvalue.length; // always return -a- value...
    }
};






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */


/**
 * Live Markup processor for a processing instruction
 *
 * @author      Ruben Daniels (ruben AT ajax DOT org)
 * @version     %I%, %G%
 * @since       0.9
 */

apf.LiveMarkupPi = function(){
    //this.$data;
    this.$init();
};

/*
    @todo optimize the pi, possible with this code:
    var div, doc = this.ownerDocument, domParser = doc.$domParser, 
        docFrag = doc.createDocumentFragment(),
        sStart = "<a:application xmlns:a='" + apf.ns.aml + "'>",
        sEnd = "</a:application>";
    
    docFrag.$int = div;
    
    domParser.parseFromXml(apf.getXml(sStart + this.$bindingQueue[i] + sEnd), { //@todo might be optimized by doing it only once
        doc: doc,
        amlNode: docFrag,
        beforeNode: null,
        include: true
    });
*/
(function(){
    this.mainBind = "data";
    
    this.implement(apf.StandardBinding);

    this.getDocument = function(){
        return this.$data && this.$data.ownerDocument;
    };
    
    this.clear = function(msg) {
        if (msg == "loading" && apf.getInheritedAttribute(this, "loading-message")) {
            this.$propHandlers["calcdata"].call(this, "<span class='loading'>Loading...</span>");
            this.calcdata = "";
        }
    };

    this.$propHandlers["calcdata"] = function(data) {
        if (this.$skipChange) //Used by liveedit.js
            return;

        if (this.$data) {
            
            
            var nodes = this.$data.childNodes;
            for (var i = nodes.length - 1; i >= 0; i--)
                nodes[i].destroy(true);
        }

        //var dt = new Date().getTime();

        //if (!this.xmlRoot)
            //return this.$ext.innerHTML = "loading...";

        if (typeof data == "string" && data.indexOf("<a:") > -1) { //@todo use the .hasAml attribute
            this.$ext.innerHTML = "";//data;

            var doc = this.ownerDocument.$domParser.parseFromString("<a:application xmlns:a='" 
              + apf.ns.apf + "'>" + data + "</a:application>", "text/xml", {
                htmlNode: this.$ext,
                host: this
                //nodelay  : true
            })
            this.$data = doc.documentElement;
            
            //apf.queue.empty();
            
            //alert(new Date().getTime() - dt);
        }
        else {
            if (this.$data) {
                var nodes = this.$data.childNodes;
                for (var i = 0; i < nodes.length; i++)
                    nodes[i].destroy(true);
            }
            if (this.$ext)
                this.$ext.innerHTML = data || "";
        }
    };
}).call(apf.LiveMarkupPi.prototype = new apf.AmlProcessingInstruction(true));

apf.aml.setProcessingInstruction("lm", apf.LiveMarkupPi);
apf.aml.setProcessingInstruction("lm-debug", apf.LiveMarkupPi);
apf.aml.setProcessingInstruction("livemarkup", apf.LiveMarkupPi);








//Conditional compilation workaround... (can this be improved??)
if (document.all) {
    var oldWinError = window.onerror, z;
    window.onerror = z = function(m) {
        apf.console.error("Error caught from early startup. Might be a html parser syntax error (not your fault). " + m);

        if (!arguments.caller)
            return true;
    }
}
apf.Init.addConditional(function(){
    if (document.all && window.onerror == z) //Conditional compilation workaround... (can this be improved??)
        window.onerror = oldWinError;

    apf.dispatchEvent("domready");
}, null, ["body", "class"]);

/*if(document.body)
    apf.Init.run("body");
else*/
    apf.addDomLoadEvent(function(){apf.Init.run('body');});

//Start
apf.start();






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */





/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

/*
 * Bootloader for Ajax.org Platform
 *
 * Include apf.js, then just go about it as you would with the
 * packaged version. Adapt this file to include your preferred modules
 */






/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 *
 */

/*
 * Bootloader for Ajax.org Platform
 *
 * Include apf.js, then just go about it as you would with the 
 * packaged version. Adapt this file to include your preferred modules
 */
        register(null, {apf: apf});
    }
