define(["require", "module", "exports", "./lib/menu/menu", "./lib/page", "./lib/dropdown", "./lib/splitbox", "./lib/flexbox"], 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) delete apf.nameserver.lookup.all[e.plugin.name]; }); /** * @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]; if (ua.match(/Trident\/.*rv:([0-9]{1,}[\.0-9]{0,})/)) UA = ["ie", null, Regexp.$2]; var 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(){ apf.isIE11 = (!apf.isGecko && !apf.isWebkit && !apf.isOpera && !apf.isIE); //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 && !apf.isIE11; this.hasConditionCompilation = apf.isIE; this.supportOverflowComponent = apf.isIE; 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.supportPng24 = !apf.isIE6 && !apf.isIE5; this.hasDynamicItemList = !apf.isIE || apf.isIE >= 7; 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; // use display: flex; instead of old version http://css-tricks.com/snippets/css/a-guide-to-flexbox/ this.hasFlex = "flexFlow" in t.style; if (!this.hasFlex && apf.isWebkit) { // http://robertnyman.com/2010/12/02/css3-flexible-box-layout-module-aka-flex-box-introduction-and-demostest-cases/ t.style.display = "-webkit-box"; this.hasFlexibleBox = t.style.display == "-webkit-box"; } else { this.hasFlexibleBox = this.hasFlex; } // 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.CSSPREFIX = apf.isGecko ? "Moz" : (apf.isWebkit ? "webkit" : ""); this.CSSPREFIX2 = apf.isGecko ? "-moz" : (apf.isWebkit ? "-webkit" : ""); if (apf.hasFlex) { apf.CSS_FLEX_PROP = "flex"; apf.CSS_DISPLAY_FLEX = "flex"; } else { apf.CSS_FLEX_PROP = apf.CSSPREFIX + "BoxFlex"; apf.CSS_DISPLAY_FLEX = apf.CSSPREFIX2 + "-box"; } //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 : "", * 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(); else if (apf.isWebkit) apf.runWebkit(); else if (this.isGecko) apf.runGecko(); else if (!this.isOpera) apf.runIE(); // ie11 // apf.runIE(); // Load user defined includes this.Init.addConditional(this.parseAppMarkup, apf, ["body"]); //@todo, as an experiment I removed 'HTTP' and 'Teleport' 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, " ").replace(/= 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) { return xmlNode.querySelectorAll(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"; apf.browserDetect(); apf.Init.run("apf"); /** * 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 * * * ``` * * @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(/ -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"); 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)); } }; /** * 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(); }; // 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; function defineProp(obj, name, val) { Object.defineProperty(obj, name, { value: val, enumerable: false, writable: true, configurable: true, }); } defineProp(Array.prototype, "dataType", apf.ARRAY); defineProp(Number.prototype, "dataType", apf.NUMBER); defineProp(Date.prototype, "dataType", apf.DATE); defineProp(Boolean.prototype, "dataType", apf.BOOLEAN); defineProp(String.prototype, "dataType", apf.STRING); defineProp(RegExp.prototype, "dataType", apf.REGEXP); defineProp(Function.prototype, "dataType", apf.FUNCTION); /* * Extends a Function object with properties from other objects, specified as * arguments. * * @param {Mixed} obj1, obj2, obj3, etc. * @type Function * @see apf.extend */ defineProp(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 */ defineProp(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))); } }); /* * Copy an array, like this statement would: 'this.concat([])', but then do it * recursively. */ defineProp(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} */ defineProp(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 */ defineProp(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} */ defineProp(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") { defineProp(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 { defineProp(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 */ defineProp(Array.prototype, "contains", function(obj, from) { return this.indexOf(obj, from) != -1; }); /* * Like Array.push, but only invoked when the value 'item' is already present * inside the array instance. * * @param {Mixed} item, item, ... * @type {Array} */ defineProp(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. */ defineProp(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} */ defineProp(Array.prototype, "each", 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} */ defineProp(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 */ defineProp(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} */ defineProp(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} */ defineProp(Array.prototype, "invert", Array.prototype.reverse); /* * 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} */ if (!String.prototype.repeat) { 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("" + this + ""); 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 '&' to '&amp;' * and '<' to '&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;' to '&' * and '&lt;' to '<'. * * @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() !== "<\n>") String.prototype.escapeHTML = null; if ("<\n>".unescapeHTML() !== "<\n>") String.prototype.unescapeHTML = null; } if (!String.prototype.escapeHTML) { String.prototype.escapeHTML = function() { return this.replace(/&/g,"&").replace(//g,">"); }; } if (!String.prototype.unescapeHTML) { String.prototype.unescapeHTML = function() { return this.stripTags().replace(/</g,"<").replace(/>/g,">").replace(/&/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 %s 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 //@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" : "⌘", // ⌘ "command" : "⌘", "alt" : "⌥", // ⌥ "option" : "⌥", "shift" : "⇧", // ⇧ //"esc" : "⎋", // ⎋ "ctrl" : "ࣿ" // ⌃ TODO // "backspace": "èB;", // ⌫ TODO // "del" : "ख", // ⌦ TODO // "enter" : "A9;" // ↩ 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; var str = hotkey.trim(); if (!str) return ""; var keys = str.splitSafe("\\-+"); var i = 0; var l = keys.length; for (; i < l; ++i) { if (!keys[i]) keys[i] = "-"; 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); /** * @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; } }; /** * @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); } } if (options && options.zIndex) apf.window.zManager.set(options && options.zClass || "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) { 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; } }; } }; function findCssRule(name, stylesheet, win) { // chrome normalizes pseudo-elements to :: and firefox to : name = name.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1') .replace(/::?(after|before)/g, "::?$1"); var nameRe = new RegExp("^" + name + "$", "i"); 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 (nameRe.test(rules.item(i).selectorText)) { return rules.item(i); } } } catch (e) {} } } else { if (typeof stylesheet == "number") stylesheet = (win || self).document.styleSheets[stylesheet || 0]; var rules = stylesheet[apf.styleSheetRules]; if (!rules) return false; for (var i = 0; i < rules.length; i++) { if (nameRe.test(rules.item(i).selectorText)) { return rules.item(i); } } } } /** * 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) { var rule = findCssRule(name, stylesheet, win); if (rule) { if (value.indexOf("!important") > -1) { rule.style.cssText = type + ":" + value; } else { type = type.replace(/-(\w)/g, function(_, a) {return a.toUpperCase()}); rule.style[type] = value; } } return !!rule; }; apf.removeStyleRule = function(name, stylesheet, win) { var rule = findCssRule(name, stylesheet, win); if (rule) { var i = Array.prototype.indexOf.call(rule.parentStyleSheet.cssRules, rule); if (i != -1) rule.parentStyleSheet.deleteRule(i); } return !!rule; } /** * 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) { var rule = findCssRule(name, stylesheet, win); if (rule) { return rule.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", "."); /*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; 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 (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; 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, top]; }; //@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 }; }; /** * 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(/</g, "<") .replace(/>/g, ">").replace(/&/g, "&").replace(/ /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 * * * @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 (!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(); }; /** * 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) { } }; /** * 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 "&", 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, "&"); else str = (str || "").replace(/&(?!#[0-9]{2,5};|[a-zA-Z]{2,};)/g, "&"); var map = apf.xmlEntityMap; var isArray = apf.isArray; return str .replace(/"/g, """) .replace(//g, ">") .replace(/'/g, "'") .replace(/&([a-zA-Z]+);/gi, function(a, m) { var x = map[m.toLowerCase()]; if (x) return "&#" + (isArray(x) ? x[0] : x) + ";"; return a; }); }; /** * Unescapes `"&"` and other similar XML entities into HTML entities, and then replaces * 'special' ones (`'`, `>`, `<`, `"`, `&`) 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(/'/gi, "'") .replace(/>/gi, ">") .replace(/</gi, "<") .replace(/"/gi, "\"") .replace(/&/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--) apf.xmldb.removeNode(nodes[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, "&"); 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; while (xml && (xml.nodeType != 1 || !(result = attr && ((avalue = xml.getAttribute(attr)) || typeof avalue == "string") || func && func(xml)))) { 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; }; /** * 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\]+[^\/]\>/) ? 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); /** * 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: 195000 }, "notifier" : { level: 300000 }, "popup+" : { level: 350000 }, "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; } }; /** * @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.page.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, page: 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 page = location.hash.replace("#", ""); apf.history.hasChanged(decodeURI(page)); }; }); } 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 page = location.href.replace(/^.*#(.*)$/, "$1") var page = location.hash.replace("#", "");//.replace(/^.*#(.*)$/,"$1"); apf.history.hasChanged(decodeURI(page)); }, 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.page == name || !force && decodeURIComponent(location.hash) == "#" + decodeURIComponent(name)) { this.to_name = name; return; } if (!this.inited) return this.init(name); window.location.href = "#" + name; if (!apf.isGecko && !apf.isIphone) apf.history.lastUrl = location.href.toString(); }, timer: null, changePage: function(page, force) { if (!apf.supportHashChange && apf.isIE) { } }, update: function(page) { var i, l, idx = 0; // check past: for (i = 0, l = this.past.length; i < l && idx === 0; i++) { if (this.past[i] == page) idx = i + 1; } if (idx > 0) { // part of past up till page (Array.slice), EXCLUDING page 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] == page) { idx = i + 1; // current past + part of the future up till page // (Array.splice), INCLUDING page this.past = this.past.concat(this.future .splice(0, this.future.length - idx)).makeUnique(); } } if (idx === 0) { this.past.push(page); idx = 1; } } return idx; }, hasChanged: function(page, force) { if (page == this.page && !force) return; this.changing = true; if (apf.dispatchEvent("hashchange", { oldURL: this.page, newURL: page, page: page, index: this.update(page) }) === false) { page = location.hash = this.page; }; this.changing = false; this.page = page; } }; 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; }, "debug" : function(value) { apf.debug = value; } } }); if (apf.history) apf.addEventListener("load", function(){ apf.history.init(apf.config.defaultPage, "page"); }); apf.offline = { onLine: true }; /** * 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 * * * * * * * * * * * * * * * * * * * * * * * * * ``` * * @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); }}); }; /* * @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:
         shortDatePattern    dateElementOrder
         ------------------  ---------------- 
         "M/d/yyyy"          "mdy"
         "dd/MM/yyyy"        "dmy"
         "yyyy-MM-dd"        "ymd"
         
* * 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

    Date.today().add( { days: 1, months: 1 } )
     
    new Date().add( { years: -1 } )
    
* @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

    Date.today().set( { day: 20, month: 1 } )

    new Date().set( { millisecond: 0 } )
    
* * @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
    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"
    
* @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(); }; }()); /** * @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 * * * * * * * * * ``` * * This is the layout file containing two layouts (_layout.xml_): * * ```xml * * * * * * * * * * * * * * * * * * * * * * * * * * * * ``` * * By binding on the _layout.xml_ you can easily create a layout manager. * * ```xml * * * * * * * * * * * * * Add Layout * * ``` * * @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 window.innerWidth; }; /** * @private */ apf.getWindowHeight = function(){ return window.innerHeight; }; // 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(); } } } }; /** * * 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.preProcessCSS(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 { var isQualified = strQuery.match(/^(https?|file):/); oHtml.style.backgroundImage = "url(" + (isQualified ? "" : iconPath || "") + strQuery + ")"; } } }; /** * 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); }; /** * 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.CSS_FLEX_PROP] = value; }, boxFlexGrow: function(oHtml, value, center) { oHtml.style[apf.CSS_FLEX_PROP + "-grow"] = 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) { 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) { 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 func {Function} * @param fromValue {String} * @param fromValue {String} * @param nrOfSteps {Number} */ 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 animtype {Function} * @param fromValue {String} * @param fromValue {String} * @param nrOfSteps {Number} */ 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++) { try { rules = document.styleSheets[i][apf.styleSheetRules]; } catch(e) { rules = false; } 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); /** * 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]+= 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; }; }); /** * 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: * * * * @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)[^>]*>/, / /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) { if (amlNode.$amlDestroyed) return; 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.value)); 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; } }; /** * 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: * * * * @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); 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 * * * * ``` * * * 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 * * * * * ``` * * #### 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()); /** * 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("")); } }); 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 * * * alert(barExample); * * ``` */ "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.$amlDestroyed && 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 = "
loading...
"; //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.$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()); //@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(); 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()); 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 (host.localName != "page" && 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()); 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 ""; }; apf.AmlComment = function(isPrototype) { this.nodeType = this.NODE_COMMENT; this.nodeName = "#comment"; this.$init(isPrototype); }; (function(){ this.serialize = function(){ return ""; }; this.$setValue = function(value) { this.dispatchEvent("DOMCharacterDataModified", { bubbles: true, newValue: value, prevValue: this.nodeValue }); } }).call(apf.AmlComment.prototype = new apf.AmlCharacterData()); 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()); /** * 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 `` * @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()); 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; /** * 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; } }; //@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 ? {} : [] 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.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()); 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(); /* * 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 } } */ 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(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); 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); /** * @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); 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); 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); 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); 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); //XForms /** * 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); /** * 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(/ /g, " ") if (!apf.supportNamespaces) xmlString = xmlString.replace(/xmlns\=\"[^"]*\"/g, ""); if (xmlString.indexOf("" + xmlString + ""; 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()); apf.__LIVEEDIT__ = 1 << 23; 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 * * * * ``` * * ### 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 * * ``` */ /** * @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 * * ``` */ /** * @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 * * ``` */ /** * @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 * * ``` */ /** * @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 * * ``` */ /** * @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 * * ``` */ /* * 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(); }); }; apf.__CONTENTEDITABLE__ = 1 << 24; 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 = 0; this.minheight = 0; /*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?? if (this.minwidth || this.minheight || this.maxwidth != 10000 || this.maxheight != 10000) { var diff = apf.getDiff(this.$ext); if (this.minwidth) this.$ext.style.minWidth = Math.max(0, this.minwidth - diff[0]) + "px"; if (this.minheight) this.$ext.style.minHeight = Math.max(0, this.minheight - diff[1]) + "px"; if (this.maxwidth != 10000) this.$ext.style.maxWidth = Math.max(0, this.maxwidth - diff[0]) + "px"; if (this.maxheight != 10000) 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 tab 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 * * test * test2 * * * * * ``` */ "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 * * * * * * * ``` */ "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? }; 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 * * * ... * * * * * * ... * * ... * * * ``` * * 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 * * ``` */ 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 * * ``` * * 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) { // console.warn("Possible missing skin: ", this.baseSkin); 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); //option = option ? option.value : "" var attr = node.getAttribute(section) || "" //if (option != attr) // debugger return attr; }; 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; 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 * * * * Number * * Name * * Message * * * * Validate * * * * * ``` * * @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 * 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 * * Phone number * * * Password * * * ``` * * 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} [page] The page 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, page) { var found = checkValidChildren.call(this, page || this, ignoreReq, nosetError); if (page) { if (page.validation && !eval(page.validation)) { alert(page.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; require("./lib/dropdown")(apf); require("./lib/splitbox")(apf); apf.__ALIGNMENT__ = 1 << 29; /** * 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 || (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 && (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()); /** * 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()); /** * 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]; h -= diff[0]; } 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("apf.lookup(" + 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; } }); }; 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`, `tab`, `pages`, `form` and c`ontainer`. * For instance, a tab page in a container is initally hidden and does not * need to be rendered. When the tab button is pressed to activate the page, * the page 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 tab becomes active. * * ```xml * * * ... * * * OK * * * ``` * @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 != "page") { this.visible = false; this.$ext.style.display = "none"; } if (typeof this["render-delay"] == "undefined") this.$setInheritedAttribute("render-delay"); } }; apf.config.$inheritProperties["render-delay"] = 1; 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 * * ``` * * * #### 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 * * * * * * * * * * * * * * * ``` * * #### 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 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *``` * * @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 * * * item 1 * item 2 * item 3 * *``` * */ /** * @attribute {Boolean} dragcopy whether dragged items are copied. * * #### Example * * ```xml * * * * * * * * * * ``` * * #### Example * * Items are only copied when the user holds the [[keys: Ctrl]] key * * ```xml * * item 1 * item 2 * item 3 * * ``` */ /** * @attribute {Boolean} drop Sets or gets whether the element allows items to be dropped. * * #### Example * * * ```xml * * item 1 * item 2 * item 3 * * ``` * @attribute {String} dragdrop Sets or gets the name of the dragdrop element for this element. * * ```xml * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ``` */ 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: 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(); 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 tab 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; }; }; apf.__INTERACTIVE__ = 1 << 21; /** * All elements inheriting from this {@link term.baseclass baseclass} have interactive features, making an * element draggable and resizable. * * #### Example * * ```xml * * ``` * * @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 * * ``` */ /** * @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 * * ``` */ /** * @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; var host = apf.findHost(e.target) if (host && host.textselect) return; 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); 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) { 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; }; 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); if (_self.dispatchEvent) _self.dispatchEvent("resize"); //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"); apf.__MEDIA__ = 1 << 20; 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 * object: * * @event aftercheck Fires after a check is made * object: * {XMLElement} xmlNode the {@link term.datanode data node} that is deselected. */ this.checkList = function(xmlNodeList, uncheck, noClear, noEvent, userAction) { 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); }); }; apf.__TRANSACTION__ = 1 << 3; apf.__VIRTUALVIEWPORT__ = 1 << 19; apf.__XFORMS__ = 1 << 17; /** * 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; if (nodes) { 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, "PRE": 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 (amlNode && amlNode.name === "editor::ace") { canSelect = true; } 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) { 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} var keyPressed = false; apf.addListener(window, "blur", function(e) { keyPressed = false; }) apf.addListener(document, "keyup", function(e) { e = e || event; if (!keyPressed) return; keyPressed = false; 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; } // todo is there better way to prevent blur on ctrl-tab? if (apf.activeElement && apf.activeElement.editor) return; 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; keyPressed = true; 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 == "true" || htmlNode.contentEditable == "plaintext-only") && !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 page /*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(); /** * 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>"); 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>"); 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)]; }; }; /** * Compatibility layer for Internet Explorer browsers. * @private */ apf.runIE = function(){ apf.runWebkit(); // return; var silent HTMLDocument.prototype.sn = XMLDocument.prototype.sn = HTMLDocument.prototype.sn || HTMLDocument.prototype.selectNodes; HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) { if (/^\w+$/.test(sExpr) && contextNode) { var all = contextNode.querySelectorAll(sExpr); var nodeList = new Array(all.length); for (var i = nodeList.length - 1; i >= 0; i--) nodeList[i] = all[i]; return nodeList; } silent || console.log(sExpr, contextNode) return this.sn(sExpr, contextNode) }; HTMLDocument.prototype.sns = XMLDocument.prototype.sns = HTMLDocument.prototype.sns || HTMLDocument.prototype.selectSingleNode HTMLDocument.prototype.selectSingleNode = XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) { var n = findNode(contextNode, sExpr); if (sExpr.lastIndexOf("descendant-or-self::node()[@") == 0) n = contextNode.querySelector(sExpr.replace("descendant-or-self::node()[@", "*[")); silent = true try {var m = this.sns(sExpr, contextNode); } catch(e) {} silent = !true if (n != m && m) { console.log(sExpr) debugger n = m findNode(contextNode, sExpr); } return n }; apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) { var node, frag, a, i, l; if (nodeList) { frag = document.createElement("div"); 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 || (frag ? frag.innerHTML : "") .replace(/<([^>]+)\/>/g, "<$1>")); }; }; function findNode(htmlNode, textNode, parts, maxRecur) { if (!parts) parts = textNode.split("/") textNode = parts.shift() if (textNode == ".") { return htmlNode } if (textNode == "text()") { var ch = htmlNode.childNodes; for (var i = 0; i < ch.length; i++) { if (ch[i].nodeType == 3 || ch[i].nodeType == 4) return ch[i]; } debugger throw new Error("can't find node " + textNode); } else if (textNode[0] == "@") { var name = textNode.substr(1); return htmlNode.getAttributeNode(name); var value = htmlNode.getAttribute(name); return { name: name, value: value, nodeValue: value, nodeType:2 }; } else { var index = 0; textNode = textNode.replace(/\[\d+\]/, function(x){ index = parseInt(x.slice(1, -1)) - 1; return ""; }); var ch = htmlNode.childNodes; for (var i = 0; i < ch.length; i++) { if (ch[i].tagName && ch[i].tagName.toLowerCase() === textNode) { if (index) index--; else if (parts.length) return findNode(ch[i], "", parts); else return ch[i]; } } } } /** * @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, "&") .replace(//g, ">"); }); 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; defineProp(Array.prototype, "item", function(i){return this[i];}); defineProp(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]*<"); 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; }; }; /** * 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 . */ 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>")); }; 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>")); 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 */ 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 * @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 , 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; */ 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 tab = "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(tab.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 */ (function(global) { function rotate_left(n,s) { var t4 = ( 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); /** * @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, "{": 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-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; // -------- 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; // 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 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 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 == "")) { 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 [xpath] 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 + ""); 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=='"'?""":"'"; } 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' + ((n?__val(n,m):__valm(m,x)) || " ") + ''; } // function _edit(n, opts) { // return '' + ((n?__val(n,m):__valm(m,x)) || " ") + ''; // } 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, "
"); if (editMode !== false) { var value = res || options && options.initial || " "; if (!options || !options.richtext) value = apf.htmlentities(value); if (options && options.multiline) value = value .replace(/<br ?\/?>/g, "
") .replace(/<(\/?div)>/g, "<$1>"); return '' + value + ''; } 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 ac?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; })(); /** * 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, ")"); }; }; }; /** * An element containing all the binding rules for the data * bound elements referencing this element. * * #### Example * * ```xml * * * * * * * * * * * * * ``` * * @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); /** * 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); /** * @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); /** * 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 page 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-page Sets or gets the name of the default page 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-page", "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); /** * This element displays a skinnable rectangle which can contain other * AML elements. Often, it's also used in place of a regular HTML `
`. * * * #### Example * * ```xml, demo * * * * * * * * * * * * ``` * * #### 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); /** * @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); /** * 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. * * * * * * * Example: * A shorter way to write this is: * * * * * * */ 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 page */ this.back = function(){ this.$browser.contentWindow.history.back(); }; /** * Browses to the next page */ this.forward = function(){ this.$browser.contentWindow.history.forward(); }; /** * Reload the current page */ this.reload = function(){ this.$browser.src = this.$browser.src; }; /** * Print the currently displayed page */ this.print = function(){ this.$browser.contentWindow.print(); }; /** * Execute a string of javascript on the page. This is subject to browser * security and will most likely only work when the browsed page 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 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); /** * 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 * * * Onclick event * * Example button * Onmouseover event * * Example button * Onmouseout event * * Example button * * * ``` * * #### Example: Interactions and Colors * * ```xml, demo * * * * * Click me to resize Test button to 200px * * Click me to resize Test button to 50px * Test * * * * ``` * * @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["iconsize"] = function(value) { if (!this.oIcon) return; this.oIcon.style.backgroundSize = value; } 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 * Undo * ``` */ 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; apf.setStyleClass(this.$ext, 'submenu'); menu.display(null, null, false, this, null, null, this.$ext.offsetWidth - 2); menu.addEventListener("prop.visible", function listen(e) { apf.setStyleClass(this.$ext, '', ['submenu']); menu.removeEventListener("prop.visible", listen); }); this.parentNode.hasMoved = false; if (e && e.htmlEvent) apf.stopPropagation(e.htmlEvent); return false; } function menuOver(){ var menuPressed = this.parentNode.menubar && 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); apf.setStyleClass(this.$ext, 'submenu'); 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); /** * This element displays a clickable rectangle with two states that * can be toggled by user interaction. * * #### Example: Setting and Retrieving Values * * ```xml, demo * * * Full * * * * ``` * * #### Example: Disabled Values * * ```xml, demo * * * Option 1 * Option 2 * Option 3 * Option 4 * * * * * ``` * * @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 * * * * Caption * ``` * * A shorter way to write this is: * * ```xml * * * * Caption * ``` */ 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); /** * 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); /** * 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 * * * * Choice 1! * Choice 2! * * * * The Netherlands * United States of America * Poland * * * Right-click on the list to reveal the context menu! * * ``` * * @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); /** * 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 * * Invalid e-mail address entered. * * ``` * * #### 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 * * Phone number * * * Password * * Validate * * ``` * * 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 = "" + value.shift() + "" + 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); /** * 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); 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); /** * This element displays a frame with a caption that can contain other elements. It's * element is analogous to the `
` in HTML. * * #### Example * * ```xml, demo * * * * Option 1 * Option 2 * Option 3 * Option 4 * * * * ``` * * @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 = "" + this.caption + ""; 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 || e.target == _self.oCaption) _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); /** * 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 `` element: * * ```xml, demo * * * * * * * * * * * * * * * * * * ``` * * @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 * * * * * ``` */ 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); /** * 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 * * * * * Not Disabled * * * * ``` * * @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 shorter way to write this is: * * ```xml * * * * * ``` */ 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); /* * @private */ apf.WinServer = { count: 150000, 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 * * * * * * * * * * * * * * * * * * ``` * * @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 e {Object} 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 || "", zIndex: true, zClass: "popup+" }); this.$setStyleClass(this.$ext, "", ["relative"]); } else { apf.plane.hide(this.$uniqueId); this.$setStyleClass(this.$ext, "relative"); } }; /** * @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 || "", zIndex: true, zClass: "popup+" }); } 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 (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); /** * * 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, persistent, callback) { 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); oClose = this.$getLayoutNode("notification", "close", 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"; oClose.addEventListener("click", function(){ hideWindow(null, true); }); 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 = ""; if (!persistent) $setTimeout(hideWindow, _self.timeout) } }); function hideWindow(e, force) { if (isMouseOver && !force) return; apf.tween.css(oNoti, "notifier_hidden", { anim: apf.tween.NORMAL, steps: 10, interval: 20, onfinish: function(container) { if (callback) callback(); _self.dispatchEvent("closed", {html: oNoti}); apf.setStyleClass(oNoti, "", ["notifier_hover"]); if (isMouseOver && !force) 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 || persistent) return; if (apf.isChildOf(tEl, oNoti) || (!apf.isChildOf(oNoti, tEl) && oNoti !== tEl )) { isMouseOver = false; hideWindow(); } }; if (ev) { oNoti.onclick = function() { ev.dispatchEvent("click"); }; } oNoti.hideWindow = hideWindow; return oNoti; }; // *** 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); /** * 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); /** * 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 * * * * * * ``` * * #### Example: Progressbars with Varying Speeds * * ```xml, demo * * * // * * * // * * * ``` * * #### Example: Dynmically Controlling the Progressbar * * ```xml, demo * * * * * Start * Pause * Stop * Clear * Enable * Disable * * * ``` * * @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 shorter way to write this is: * * ```xml * * * * * ``` */ 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); /** * 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 * * * * Options * Choices * Option 1 * Choice 1 * Option 2 * Choice 2 * * * * ``` * * @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 * Choice 1 * Choice 2 * * * * * ``` * * A shorter way to write this is: * * ```xml * Choice 1 * Choice 2 * ``` * */ /** * @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 * Options * Option 1 * Option 2 * * Choices * * Choice 1 * Choice 2 * * ``` * * @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); /** * This element loads JavaScript into the application. * * #### Example * * ```xml * * ``` * * #### Example * * ```xml * // * ``` * * @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); //@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 (htmlNode.scrollHeight); } this.getScrollWidth = function(){ var htmlNode = this.$getHtmlHost(); return (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; /** * 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 * * ``` * * @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]*>/, "") .replace(/^[\r\n\s]*/, "") //.replace(/ /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()); /** * 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 * * * * * * * * * * * * LCD Panel * * * * ``` * * #### 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 * * * * * * * * * * * * * * * * * * * * * * 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"); * } * * ``` * * #### Example * * This example shows a smartbinding element which references to its children as * stand alone elements. * * ```xml * * ... * * * ... * * * ... * * * * * * * * ``` * * * #### Example * * The shortest method to add binding rules to an element is as follows: * * ```xml * * ``` * * @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 * * ``` * @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 * * ``` * @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 * * ``` * @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 * * ``` * @see element.model * @see term.smartbinding * * @define bindings element containing all the binding rules for the data * bound elements referencing this element. * * #### Example * * ```xml * * * * * * * * ``` * @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); /** * 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); /** * 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 * * * * * * ``` * * #### Example: Loading Data * * ```xml, demo * * * * * * * * * ``` * * #### Example: Connecting to a Textbox * * * ```xml, demo * * * * * * * * * * * ``` * * @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.setProperty("value", this.oInput.value, false, false); 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) { if (this.realtime) this.setProperty("value", 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); /** * 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 * * * * * * * * ``` * * #### 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 = ~this.$ext.className.indexOf("moveleft") ? 0 : "-" + diff + "px"; }); menu.$splitInited = true; } this.removeEventListener("mousedown", arguments.callee); }); }; this.$draw = function(){ var _self = this; this.$ext = this.$pHtmlNode.appendChild(document.createElement("div")); this.$ext.className = "splitbutton"; if (this.getAttribute("style")) this.$ext.setAttribute("style", this.getAttribute("style")); 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 }); }, onmousedown: function() { _self.dispatchEvent("mousedown", { 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 }); }, onmousedown: function() { _self.dispatchEvent("mousedown", { 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); /** * An element that groups state elements together and * provides a way to set a default state. * * #### Example * * ```xml * * * * * * * ``` * * @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()); /** * @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 * * * * * * * State - User * State - Admin * * * * ``` * * @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); /** * 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 * * * * * Name * * Address * * Country * * America * Armenia * The Netherlands * * * Message * * * * * * * * * * ``` * * @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 * * ``` * */ /** * @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 * * ```` */ 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, "
"); 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); /** * 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 * * * * 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. * * * * ``` * * #### Example: Using Scrolldown * * ```xml, demo * * * * 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. * * * Add a line * * * * ``` * * @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(//g, "") .replace(//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(//ig, "") 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 = "
" + html + "
"; if (htmlParentNode) { if (beforeNode) beforeNode.insertAdjacentHTML("beforebegin", html); else this.$container.insertAdjacentHTML("beforeend", html); //apf.insertHtmlNode(oItem, htmlParentNode, beforeNode); 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") { 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); //@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 * * * * * * * * * ``` * * #### Example: Validation * * ```xml, demo * * * Please enter a minimum of three characters * * Enter your email address * * * * * * * * * * * * ``` * * #### Example: A Regular Box * * ```xml, demo * * * * * * * * * * * * ``` * * @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 shorter way to write this is: * ```xml * * * * * ``` * */ /** * @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 * * ``` * * #### Example * * A Dutch postal code: * * ```xml * * ``` * * #### Example * * A date * * ```xml * * ``` * * #### Example * * A serial number * * ```xml * * ``` * * #### Example * * A MAC address * * ```xml * * ``` */ 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.textContent) v = this.$input.textContent; else { //Chrome has a bug, innerText is cleared when display property is changed v = apf.html_entity_decode(this.$input.innerHTML .replace(//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; }; 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 || _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 (_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; if (!_self.multiline) p = p.replace(/([\r\n])/g, ""); 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); /** * 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 * * * * * * File * Edit * * * * * About us * Help * * * Tutorials * Live Helps * * Visit Ajax.org * Exit * * * * * ``` * * @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); apf.aml.setElement("checked", apf.BindingRule); /** * @constructor * @private */ apf.textbox.masking = function(){ /* Special Masking Values: - PASSWORD */ 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... } }; /** * 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 = "", sEnd = ""; 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, "Loading..."); 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(" -1) { //@todo use the .hasAml attribute this.$ext.innerHTML = "";//data; var doc = this.ownerDocument.$domParser.parseFromString("" + data + "", "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); require("./lib/menu/menu")(apf); require("./lib/flexbox")(apf); require("./lib/page")(apf); apf.Init.addConditional(function(){ apf.dispatchEvent("domready"); }, null, ["body", "class"]); apf.addDomLoadEvent(function(){apf.Init.run('body');}); //Start apf.start(); /* * 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}); } });