c9-core/plugins/c9.ide.ui/lib_apf.js

36439 wiersze
1.1 MiB

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 : "<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, "&nbsp;").replace(/</g, "&lt;"));
}
},
html_entity_decode: function(s){return s},
htmlentities: function(s){return s},
/**
* Formats an Ajax.org Platform error message.
* @param {Number} number The number of the error. This can be used to look up more information about the error.
* @param {apf.AmlElement} control The aml element that will throw the error.
* @param {String} process The action that was being executed.
* @param {String} message The actual error message.
* @param {XMLElement} amlContext The XML relevant to the error. For instance, this could be a piece of Ajax.org Markup Language XML.
*/
formatErrorString: function(number, control, process, message, amlContext, outputname, output) {
apf.lastErrorMessage = message;
return message;
},
/* Init */
/**
* Returns the directory portion of a URL.
* @param {String} url The URL to retrieve from
* @returns {String} The directory portion of a URL
*/
getDirname: function(url) {
//(?:\w+\:\/\/)?
return ((url || "").match(/^([^#]*\/)[^\/]*(?:$|\#)/) || {})[1]; //Mike will check out how to optimize this line
},
/**
* Returns the file portion of a URL.
* @param {String} url The URL to retrieve from.
* @return {String} The file portion of a URL.
*/
getFilename: function(url) {
return ((url || "").split("?")[0].match(/(?:\/|^)([^\/]+)$/) || {})[1];
},
/**
* Returns an absolute url based on url.
* @param {String} base The start of the URL where relative URLs work.
* @param {String} url The URL to transform.
* @return {String} The absolute URL.
*/
getAbsolutePath: function(base, url) {
return url && url.charAt(0) == "/"
? url
: (!url || !base || url.match(/^\w+\:\/\//) ? url : base.replace(/\/$/, "") + "/" + url.replace(/^\//, ""));
},
getCtrlKey: function(event) {
return apf.isMac ? event.metaKey : event.ctrlKey;
},
/**
* Loads Javascript from a specific URL.
*
* @param {String} sourceFile The URL where the JavaScript is located.
* @param {Boolean} [doBase] Checks for a base path via [[apf.getAbsolutePath]]
* @param {String} [type] Sets the type of a script tag, for later use
* @param {String} [text]
* @param {Function} [callback] Calls this function after the script is loaded
* @returns {String} The constructed script tag
*/
include: function(sourceFile, doBase, type, text, callback) {
var sSrc = doBase ? apf.getAbsolutePath(apf.basePath || "", sourceFile) : sourceFile;
var head = document.getElementsByTagName("head")[0],//$("head")[0]
elScript = document.createElement("script");
//elScript.defer = true;
if (type)
elScript.setAttribute("_apf_type", type);
if (text) {
elScript.text = text;
}
else
elScript.src = sSrc;
head.appendChild(elScript);
if (callback)
elScript["onload"] = callback;
return elScript;
},
$required: [],
require: function(){
var dir = apf.getDirname(location.href),
i = 0,
l = arguments.length;
for (; i < l; i++)
this.$required.push(apf.getAbsolutePath(dir, arguments[i]));
},
/**
* @private
*/
Init: {
queue: [],
cond: {
combined: []
},
done: {},
add: function(func, o) {
if (this.inited)
func.call(o);
else if (func)
this.queue.push([func, o]);
},
addConditional: function(func, o, strObj) {
if (typeof strObj != "string") {
if (this.checkCombined(strObj))
return func.call(o);
this.cond.combined.push([func, o, strObj]);
}
else if (self[strObj]) {
func.call(o);
}
else {
if (!this.cond[strObj])
this.cond[strObj] = [];
this.cond[strObj].push([func, o]);
this.checkAllCombined();
}
},
checkAllCombined: function(){
for (var i = 0; i < this.cond.combined.length; i++) {
if (!this.cond.combined[i]) continue;
if (this.checkCombined(this.cond.combined[i][2])) {
this.cond.combined[i][0].call(this.cond.combined[i][1])
this.cond.combined[i] = null;
}
}
},
checkCombined: function(arr) {
for (var i = 0; i < arr.length; i++) {
if (!this.done[arr[i]])
return false;
}
return true;
},
run: function(strObj) {
this.inited = this.done[strObj] = true;
this.checkAllCombined();
var data = strObj ? this.cond[strObj] : this.queue;
if (!data) return;
for (var i = 0; i < data.length; i++)
data[i][0].call(data[i][1]);
}
},
/**
* Determines the way APF tries to render this application. Set this value
* before APF is starts parsing.
*
* Possible values include:
* - 0 Auto (The default)
* - 1 Partial
* - 11 Partial from a comment
* - 2 Full from serialized document or file fallback
* - 21 Full from file
* @type {Number}
*/
parseStrategy: 0,
/**
* @private
*/
parseAppMarkup: function(docElement) {
var isEmptyDocument = false;
if (document.documentElement.getAttribute("skipParse") == "true") {
return;
}
},
namespaces: {},
setNamespace: function(namespaceURI, oNamespace) {
this.namespaces[namespaceURI] = oNamespace;
oNamespace.namespaceURI = namespaceURI;
},
/**
* @private
*/
initialize: function(xmlStr) {
apf.console.info("Initializing...");
clearInterval(apf.Init.interval);
// Run Init
apf.Init.run(); //Process load dependencies
var bodyMarginTop = parseFloat(apf.getStyle(document.body, "marginTop"));
apf.doesNotIncludeMarginInBodyOffset = (document.body.offsetTop !== bodyMarginTop);
{
apf.window.init(xmlStr);
}
},
/**
* @private
*/
execDeferred: function() {
// execute each function in the stack in the order they were added
var len = apf.load_events.length;
while (len--)
(apf.load_events.shift())();
},
load_events: [],
load_timer: null,
load_done: false,
load_init: null,
/**
* @private
*/
addDomLoadEvent: function(func) {
if (!this.$bdetect)
this.browserDetect();
if (apf.load_done)
return func();
// create event function stack
//apf.done = arguments.callee.done;
if (!apf.load_init) {
apf.load_init = function() {
if (apf.load_done) return;
// kill the timer
clearInterval(apf.load_timer);
apf.load_timer = null;
apf.load_done = true;
if (apf.started)
apf.execDeferred();
};
}
apf.load_events.push(func);
if (func && apf.load_events.length == 1) {
// Catch cases where addDomLoadEvent() is called after the browser
// event has already occurred.
var doc = document, UNDEF = "undefined";
if ((typeof doc.readyState != UNDEF && doc.readyState == "complete")
|| (doc.getElementsByTagName("body")[0] || doc.body))
return apf.load_init();
// for Mozilla/Opera9.
// Mozilla, Opera (see further below for it) and webkit nightlies
// currently support this event
if (doc.addEventListener && !apf.isOpera) {
// We're using "window" and not "document" here, because it results
// in a memory leak, especially in FF 1.5:
// https://bugzilla.mozilla.org/show_bug.cgi?id=241518
// See also:
// http://bitstructures.com/2007/11/javascript-method-callbacks
// http://www-128.ibm.com/developerworks/web/library/wa-memleak/
window.addEventListener("DOMContentLoaded", apf.load_init, false);
}
else if (apf.isWebkit && !apf.isIphone) {
var aSheets = doc.getElementsByTagName("link"),
i = aSheets.length,
iSheets;
for (; i >= 0; i++) {
if (!aSheets[i] || aSheets[i].getAttribute("rel") != "stylesheet")
aSheets.splice(i, 0);
}
iSheets = aSheets.length;
apf.load_timer = setInterval(function() {
if (/loaded|complete/.test(doc.readyState)
&& doc.styleSheets.length == iSheets)
apf.load_init(); // call the onload handler
}, 10);
}
// for other browsers set the window.onload, but also execute the
// old window.onload
else {
var old_onload = window.onload;
window.onload = function () {
apf.load_init();
if (old_onload)
old_onload();
};
}
}
},
fireEvent: function(el, type, e, capture) {
if (el.dispatchEvent)
el.dispatchEvent(type, e, capture);
else
el.fireEvent("on" + type, e);
},
addListener: function(el, type, fn, capture) {
if (el.addEventListener)
el.addEventListener(type, fn, capture || false);
else if (el.attachEvent)
el.attachEvent("on" + type, fn);
return this;
},
removeListener: function(el, type, fn, capture) {
if (el.removeEventListener)
el.removeEventListener(type, fn, capture || false);
else if (el.detachEvent)
el.detachEvent("on" + type, fn);
return this;
},
stopEvent: function(e) {
this.stopPropagation(e).preventDefault(e);
return false;
},
stopPropagation: function(e) {
if (e.stopPropagation)
e.stopPropagation();
else
e.cancelBubble = true;
return this;
},
preventDefault: function(e) {
if (e.preventDefault)
e.preventDefault();
else
e.returnValue = false;
return this;
},
/* Destroy */
/**
* Unloads the aml application.
*/
unload: function(exclude) {
}
};
/*
* Replacement for getElementsByTagNameNS because some browsers don't support
* this call yet.
*/
var $xmlns = function(xmlNode, tag, xmlns, prefix) {
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
* <a:button visible="{rbTest.value == 'up'}" />
* <a:textbox id="rbTest" value="" />
* ```
*
* @param {String} prop The name of the property of this element to set
* using a dynamic rule.
* @param {String} pValue The dynamic property binding rule.
*/
this.$attrExcludePropBind = false;
this.$setDynamicProperty = function(prop, pValue) {
var exclNr = this.$attrExcludePropBind[prop],
options;
//@todo apf3.0, please generalize this - cache objects, seems slow
if (SEL.indexOf(prop) > -1 || exclNr == 3) {
options = {
xpathmode: 2
//parsecode : true //@todo is this also good for exclNr 3 ?
}
}
else if (exclNr == 2) {
options = {nostring : true};
}
else if (exclNr === 0) {
options = {
parsecode: true
};
}
if (this.liveedit)
(options || (options = {})).liveedit = true;
//Compile pValue through JSLT parser
{
var fParsed = apf.lm.compile(pValue, options);
}
//Special case for model due to needed extra signalling
if (prop == MODEL)
(this.$modelParsed = fParsed).instruction = pValue
//if it's only text return setProperty()
if (fParsed.type == 2) {
this[prop] = !pValue; //@todo apf3.0 is this needed?
return this.setProperty(prop, fParsed.str, null, null, 10); //@todo is 10 here right?
}
//if there's xpath: Add apf.DataBinding if not inherited.
//Add compiled binding rule. Load databinding if not loaded.
var check = 1;
if (exclNr == 2 || fParsed.xpaths.length && exclNr != 1) {
if (!this.hasFeature(apf.__DATABINDING__)) {
this.implement(apf.StandardBinding);
if (this.$attrExcludePropBind[prop] == 1)
check = 0;
}
if (check)
this.$addAttrBind(prop, fParsed, pValue);
}
//if there's prop binding: Add generated function to each obj/prop in the list
var matches = exclNr && exclNr != 3 && prop != MODEL ? {} : fParsed.props, //@todo apf3.0 sign of broken abstraction, please fix this with a bit mask
found = false,
_self = this,
o, node, bProp, p;
for (p in matches) {
o = p.split(".");
if (o.length > 2) { //apf.offline.syncing
bProp = o.pop();
// try{
// try { node = eval(o.join(".")); } catch (e) {}
// }
// catch (e) {
// if (arguments[2]) {
// apf.console.warn("[287] Could not execute binding test : "
// + pValue.replace(/</g, "&lt;") + "\n\n" + e.message);
// }
// else {
// apf.queue.add(prop + ":" + this.$uniqueId, function(){
// _self.$clearDynamicProperty(prop);
// _self.$setDynamicProperty(prop, pValue, true);
// });
// }
// continue;
// }
for (var i = 0; i < o.length; i++) {
if (!node)
node = apf.nameserver.get("all", o[i]);
else
node = node[o[i]];
}
if (!node) {
return //console.warn("ignored:", p);
}
// if (!node || typeof node != OBJ || (!node.$regbase && node.$regbase !== 0)) {
// bProp = o[1];
// node = self[o[0]] || apf.nameserver.get("all", o[0]);
// }
// else {
// o.push(bProp);
// }
}
else {
bProp = o[1];
node = self[o[0]] || apf.nameserver.get("all", o[0]) || o[0] == "this" && this;
}
if (!node) {
if (arguments[2]) {
apf.console.warn("[287] Could not create property binding: "
+ " '" + o[0] + "' does not exist. \n"
+ pValue.replace(/</g, "&lt;").substr(0, 400));
var _self = this;
apf.nameserver.waitFor(o[0], function(){
_self.$setDynamicProperty(prop, pValue);
})
return;
}
else {
//@todo this is sloppy and not efficient - shouldn't clear
//and reset and should check if was changed or removed when
//it's set
apf.queue.add(prop + ":" + this.$uniqueId, function(){
_self.$clearDynamicProperty(prop);
_self.$setDynamicProperty(prop, pValue, true);
});
return;
}
}
if (!node.$bindProperty)
continue; //return
if (!this.$funcHandlers[prop])
this.$funcHandlers[prop] = [];
var last;
this.$funcHandlers[prop].push(last = {
amlNode: node,
prop: bProp,
handler: node.$bindProperty(bProp, this, prop, fParsed,
//@todo check if it breaks something. I removed
// "&& exclNr != 3" from the expression to enable two way
// binding of selections
fParsed.type == 4 && SEL.indexOf(prop) == -1) /*,
bidir :
&& this.$bindProperty(prop, node, bProp, function(){
return _self[prop];
})*/
});
found = true;
}
if (found) {
last.handler({initial: true});
}
else {
//@todo optimize this
if (exclNr)
return this.setProperty(prop, pValue, null, null, 10); //@todo is 10 here right?
try {
if (fParsed.asyncs) { //if async
return fParsed.call(this, this.xmlRoot, function(value) {
_self.setProperty(prop, value, true, null, 10); //@todo is 10 here right?
});
}
else {
var value = fParsed.call(this, this.xmlRoot);
}
}
catch (e) {
apf.console.warn("[331] Could not execute binding test or: "
+ pValue.replace(/</g, "&lt;") + "\n\n" + e.message);
return;
}
this[prop] = !value; //@todo isnt this slow and unneccesary?
this.setProperty(prop, value, true, null, 10); //@todo is 10 here right?
}
};
//@todo setAttribute should delete this from apf.language when not doing
//$setDynamicProperty
this.$clearDynamicProperty = function(prop) {
if (this.$removeAttrBind)
this.$removeAttrBind(prop);
if (this.$inheritProperties)
delete this.$inheritProperties[prop];
if (prop == MODEL)
this.$modelParsed = null;
//Remove any bounds if relevant
var f, i, l, h = this.$funcHandlers[prop];
if (h && typeof h != FUN) {
for (i = 0, l = h.length; i < l; i++) {
(f = h[i]).amlNode.removeEventListener(PROP + f.prop, f.handler);
if (f.handler && f.handler.recip) //@todo handler shouldn't be unset - how does this happen?
this.removeEventListener(PROP + prop, f.handler.recip);
}
delete this.$funcHandlers[prop];
}
};
/**
* Gets an array of properties for this element which can be bound.
* @returns {Array}
*/
this.getAvailableProperties = function(){
return this.$supportedProperties.slice();
};
/**
* Sets the value of a property of this element.
*
* Note: The value is the only thing set. Dynamic properties remain bound and the
* value will be overridden.
*
* @param {String} prop The name of the property of this element to
* set using a dynamic rule.
* @param {String} value The value of the property to set.
* @param {Boolean} [forceOnMe] Specifies whether the property should be set even when
* it has the same value.
*/
this.setProperty = function(prop, value, forceOnMe, setAttr, inherited) {
var s, r, arr, e, i, l,
oldvalue = this[prop],
eventName = PROP + prop;//@todo prop event should be called too;
//Try catch here, because comparison of a string with xmlnode gives and error in IE
try{
var isChanged = (typeof value == OBJ)
? value != (typeof oldvalue == OBJ ? oldvalue : null)
: (this.$booleanProperties && this.$booleanProperties[prop]
? oldvalue != apf.isTrue(value)
: String(oldvalue) !== String(value));
} catch (e) {
var isChanged = true;
}
//Check if property has changed
if (isChanged) {
if (!forceOnMe) { //Recursion protection
//Check if this property is bound to data
if (typeof value != OBJ //this.xmlRoot &&
&& (!(s = this.$attrExcludePropBind[prop]))// || s == 2
&& (r = (this.$attrBindings && this.$attrBindings[prop]
|| prop != VALUE && this.xmlRoot && this.$bindings[prop]
&& this.$bindings[prop][0]))) {
//Check if rule has single xpath
if (r.cvalue.type == 3) {
//Set the xml value - this should probably use execProperty
return apf.setNodeValue(
this.$getDataNode(prop.toLowerCase(), this.xmlRoot, true),
value, true);
}
}
}
if (setAttr && !this.$funcHandlers[prop])
this.setAttribute(prop, value, true);
if (this.$handlePropSet(prop, value, forceOnMe) === false)
return;
value = this[prop];
}
//Optimized event calling
if ((arr = this.$eventsStack[eventName]) && isChanged) {
/*for (i = 0, l = arr.length; i < l; i++) {
if (arr[i].call(this, e || (e = new apf.AmlEvent(eventName, {
prop: prop,
value: value,
oldvalue: oldvalue
}))) === false) {
e.returnValue = false;
}
}*/
if (this.dispatchEvent(eventName, {
prop: prop,
value: value,
oldvalue: oldvalue,
changed: isChanged
}) === false) {
e.returnValue = false;
}
}
/*
States:
-1 Set
undefined Pass through
2 Inherited
3 Semi-inherited
10 Dynamic property
*/
//@todo this whole section should be about attribute inheritance and moved
// to AmlElement
if ((aci || (aci = apf.config.$inheritProperties))[prop]) {
//@todo this is actually wrong. It should be about removing attributes.
var resetting = value === "" || typeof value == "undefined";
if (inherited != 10 && !value) {
delete this.$inheritProperties[prop];
if (this.$setInheritedAttribute && this.$setInheritedAttribute(prop))
return;
}
else if (inherited != 10) { //Keep the current setting (for dynamic properties)
this.$inheritProperties[prop] = inherited || -1;
}
//cancelable, needed for transactions
//@todo the check on $amlLoaded is not as optimized as can be because $loadAml is not called yet
if (this.$amlLoaded && (!e || e.returnValue !== false) && this.childNodes) {
var inheritType = aci[prop];
(function recur(nodes) {
var i, l, node, n;
for (i = 0, l = nodes.length; i < l; i++) {
node = nodes[i];
if (node.nodeType != 1 && node.nodeType != 7)
continue;
//Pass through
n = node.$inheritProperties[prop];
if (inheritType == 1 && !n)
recur(node.childNodes);
//Set inherited property
//@todo why are dynamic properties overwritten??
else if (!(n < 0)) {//Will also pass through undefined - but why??? @todo seems inefficient
if (n == 3 || inherited == 3) { //Because when parent sets semi-inh. prop the value can be the same
var sameValue = node[prop];
node[prop] = null;
}
node.setProperty(prop, n != 3
? value
: sameValue, false, false, n || 2); //This is recursive already
}
}
})(this.childNodes);
}
}
return value;
};
var aci;
/**
* Gets the value of a property of this element.
*
* @param {String} prop The name of the property of this element for which to get the value.
*/
this.getProperty = function(prop) {
return this[prop];
};
// *** Event Handling ****/
apf.$eventDepth = 0;
this.$eventDepth = 0;
/**
* Calls all functions that are registered as listeners for an event.
*
* @param {String} eventName The name of the event to dispatch.
* @param {Object} [options] The properties of the event object that will be created and passed through. These can be:
* - bubbles ([[Boolean]]): Specifies whether the event should bubble up to it's parent
* - captureOnly ([[Boolean]]): Specifies whether only the captured event handlers should be executed
* @return {Mixed} return value of the event
*/
this.dispatchEvent = function(eventName, options, e) {
//var allowEvents = {"DOMNodeInsertedIntoDocument":1,"DOMNodeRemovedFromDocument":1};
var arr, result, rValue, i, l;
if (!apf.AmlEvent)
return;
apf.$eventDepth++;
this.$eventDepth++;
e = options && options.name ? options : e;
/*if (this.disabled && !allowEvents[eventName]) {
result = false;
}
else {*/
//@todo rewrite this and all dependencies to match w3c
if ((!e || !e.currentTarget) && (!options || !options.currentTarget)) {
if (!(options || (options = {})).currentTarget)
options.currentTarget = this;
//Capture support
if (arr = this.$captureStack[eventName]) {
for (i = 0, l = arr.length; i < l; i++) {
rValue = arr[i].call(this, e || (e = new apf.AmlEvent(eventName, options)));
if (typeof rValue != UNDEF)
result = rValue;
}
}
}
//@todo this should be the bubble point
if (options && options.captureOnly) {
return e && typeof e.returnValue != UNDEF ? e.returnValue : result;
}
else {
if (this["on" + eventName]) {
result = this["on" + eventName].call(this, e
|| (e = new apf.AmlEvent(eventName, options))); //Backwards compatibility
}
if (arr = this.$eventsStack[eventName]) {
for (i = 0, l = arr.length; i < l; i++) {
if (!arr[i]) continue;
rValue = arr[i].call(this, e
|| (e = new apf.AmlEvent(eventName, options)));
if (typeof rValue != UNDEF)
result = rValue;
}
}
}
//}
/*var p;
while (this.$removalQueue.length) {
p = this.$removalQueue.shift();
p[0].remove(p[1]);
}*/
if ((e && e.bubbles && !e.cancelBubble || !e && options && options.bubbles) && this != apf) {
rValue = (this.parentNode || this.ownerElement || apf).dispatchEvent(eventName, options, e);
// || (e = new apf.AmlEvent(eventName, options))
if (typeof rValue != UNDEF)
result = rValue;
}
if (--apf.$eventDepth == 0 && this.ownerDocument
&& !this.ownerDocument.$domParser.$parseContext
&& !apf.isDestroying && apf.loaded
&& apf.queue
) {
apf.queue.empty();
}
this.$eventDepth--;
if (options) {
try {
delete options.currentTarget;
}
catch (ex) {
options.currentTarget = null;
}
}
return e && typeof e.returnValue != UNDEF ? e.returnValue : result;
};
/**
* Adds a function to be called when a event is called.
*
* @param {String} eventName The name of the event for which to register
* a function.
* @param {Function} callback The code to be called when an event is dispatched.
*/
this.addEventListener = function(a, b, c) {
this.$bufferEvents.push([a,b,c]);
};
var realAddEventListener = function(eventName, callback, useCapture) {
if (eventName.substr(0, 2) == "on")
eventName = eventName.substr(2);
var s, stack = useCapture ? this.$captureStack : this.$eventsStack;
if (!(s = stack[eventName]))
s = stack[eventName] = [];
if (s.indexOf(callback) > -1)
return;
s.unshift(callback);
var f;
if (f = this.$eventsStack["$event." + eventName])
f[0].call(this, callback);
};
/**
* Removes a function registered for an event.
*
* @param {String} eventName The name of the event for which to unregister
* a function.
* @param {Function} callback The function to be removed from the event stack.
*/
this.removeEventListener = function(eventName, callback, useCapture) {
var stack = (useCapture ? this.$captureStack : this.$eventsStack)[eventName];
//@todo is this the best way?
if (stack) {
if (this.$eventDepth)
stack = (useCapture ? this.$captureStack : this.$eventsStack)[eventName] = stack.slice()
stack.remove(callback);
if (!stack.length)
delete (useCapture ? this.$captureStack : this.$eventsStack)[eventName];
}
};
/**
* Checks if there is an event listener specified for the event.
*
* @param {String} eventName The name of the event to check.
* @return {Boolean} Specifies whether the event has listeners
*/
this.hasEventListener = function(eventName) {
return (this.$eventsStack[eventName] && this.$eventsStack[eventName].length > 0);
};
/**
* The destructor of a Class.
* Calls all the destructor functions, and removes all memory leaking references.
* This function is called when exiting the application or closing the window.
* @param {Boolean} deep whether the children of this element should be destroyed.
* @param {Boolean} [clean]
*/
this.destroy = function(deep, clean) {
//Remove from apf.all
if (typeof this.$uniqueId == UNDEF && this.nodeType != 2)
return;
this.$amlLoaded = false;
this.$amlDestroyed = true;
if (this.$destroy)
this.$destroy();
this.dispatchEvent("DOMNodeRemoved", {
relatedNode: this.parentNode,
bubbles: !apf.isDestroying
});
this.dispatchEvent("DOMNodeRemovedFromDocument");
apf.all[this.$uniqueId] = undefined;
// != 2 && this.nodeType != 3
if (!this.nodeFunc && !this.nodeType) { //If this is not a AmlNode, we're done.
//Remove id from global js space
try {
if (this.id || this.name)
self[this.id || this.name] = null;
}
catch (ex) {}
return;
}
if (this.$ext && !this.$ext.isNative) { // && this.$ext.nodeType == 1
if (this.nodeType == 1 && this.localName != "a")
this.$ext.oncontextmenu = this.$ext.host = null;
if (clean) {
if (this.localName != "collection" && this.$ext.parentNode)
this.$ext.parentNode.removeChild(this.$ext);
}
}
if (this.$int && !this.$int.isNative && this.$int.nodeType == 1 && this.localName != "a")
this.$int.host = null;
//if (this.$aml && this.$aml.parentNode)
//this.$aml.parentNode.removeChild(this.$aml);
this.$aml = null;
//Clear all children too
if (deep && this.childNodes) {
var nodes = this.childNodes;
for (i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].destroy)
nodes[i].destroy(true, clean && this.localName == "collection");
}
this.childNodes = null;
}
//Remove from DOM tree if we are still connected
if (this.parentNode && this.removeNode)
this.removeNode();
else if (this.ownerElement && !this.ownerElement.$amlDestroyed)
this.ownerElement.removeAttributeNode(this);
//Remove from focus list - Should be in AmlNode
if (this.$focussable && this.focussable)
apf.window.$removeFocus(this);
//Remove dynamic properties
/*var f, i, l, h;
for (prop in this.$funcHandlers) {
h = this.$funcHandlers[prop];
//Remove any bounds if relevant
if (h && typeof h != FUN) {
for (i = 0, l = h.length; i < l; i++) {
(f = h[i]).amlNode.removeEventListener(PROP + f.prop, f.handler);
}
}
}*/
if (this.attributes) {
var attr = this.attributes;
for (var i = attr.length - 1; i >= 0; i--) {
this.$clearDynamicProperty(attr[i].nodeName);
attr[i].destroy();
}
}
//Remove id from global js space
try {
if (this.id || this.name)
delete self[this.id || this.name];
}
catch (ex) {}
for (var prop in this.$captureStack) this.$captureStack[prop] = null;
for (var prop in this.$eventsStack) this.$eventsStack[prop] = null;
for (var prop in this.$funcHandlers) this.$funcHandlers[prop] = null;
if (this.$bufferEvents) {
for (var i = this.$bufferEvents.length - 1; i >= 0; i--)
this.$bufferEvents = null;
}
apf.nameserver.remove(this.localName, this);
};
})();
apf.extend(apf, new apf.Class().$init());
apf.Init.run("class");
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("<root>" + this + "</root>");
if (node.childNodes.length == 1) {
return node.childNodes[0];
}
else {
var docFrag = node.ownerDocument.createDocumentFragment(),
nodes = node.childNodes;
while (nodes.length)
docFrag.appendChild(nodes[0]);
return docFrag;
}
};
if (typeof window != "undefined" && typeof window.document != "undefined"
&& typeof window.document.createElement == "function") {
/*
* Encode HTML entities to its HTML equivalents, like '&amp;' to '&amp;amp;'
* and '&lt;' to '&amp;lt;'.
*
* @type {String}
* @todo is this fast?
*/
String.prototype.escapeHTML = function() {
this.escapeHTML.text.data = this;
return this.escapeHTML.div.innerHTML;
};
/*
* Decode HTML equivalent entities to characters, like '&amp;amp;' to '&amp;'
* and '&amp;lt;' to '&lt;'.
*
* @type {String}
*/
String.prototype.unescapeHTML = function() {
var div = document.createElement("div");
div.innerHTML = this.stripTags();
if (div.childNodes[0]) {
if (div.childNodes.length > 1) {
var out = [];
for (var i = 0; i < div.childNodes.length; i++)
out.push(div.childNodes[i].nodeValue);
return out.join("");
}
else
return div.childNodes[0].nodeValue;
}
return "";
};
String.prototype.escapeHTML.div = document.createElement("div");
String.prototype.escapeHTML.text = document.createTextNode("");
String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);
if ("<\n>".escapeHTML() !== "&lt;\n&gt;")
String.prototype.escapeHTML = null;
if ("&lt;\n&gt;".unescapeHTML() !== "<\n>")
String.prototype.unescapeHTML = null;
}
if (!String.prototype.escapeHTML) {
String.prototype.escapeHTML = function() {
return this.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
};
}
if (!String.prototype.unescapeHTML) {
String.prototype.unescapeHTML = function() {
return this.stripTags().replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&");
};
}
/*
* Trim a string down to a specific number of characters. Optionally, append an
* ellipsis ('...') as a suffix.
*
* @param {Number} nr
* @param {Boolean} [ellipsis] Append an ellipsis
* @type {String}
*/
String.prototype.truncate = function(nr, ellipsis) {
return this.length >= nr
? this.substring(0, nr - (ellipsis ? 4 : 1)) + (ellipsis ? "..." : "")
: this;
};
/*
* Pad a string at the right or left end with a string 'pad' to a specific
* number of characters. Highly optimized version for speed, not readability.
*
* @param {Number} len Specifies the amount of characters required to pad to.
* @param {String} pad Specifies the character(s) to pad the string with
* @param {Boolean} [dir] Specifies at which end to append the 'pad' character (left or right).
* @type {String}
*/
String.prototype.pad = function(len, pad, dir) {
return dir ? (this + Array(len).join(pad)).slice(0, len)
: (Array(len).join(pad) + this).slice(-len);
};
apf.PAD_LEFT = false;
apf.PAD_RIGHT = true;
/*
* Special String.split; optionally lowercase a string and trim all results from
* the left and right.
*
* @param {String} separator
* @param {Number} limit Maximum number of items to return
* @param {Boolean} bLowerCase Flag to lowercase the string prior to split
* @type {String}
*/
String.prototype.splitSafe = function(separator, limit, bLowerCase) {
return (bLowerCase && this.toLowerCase() || this)
.replace(/(?:^\s+|\n|\s+$)/g, "")
.split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999);
};
/*
* Appends a random number with a specified length to this String instance.
*
* @see randomGenerator
* @param {Number} length
* @type {String}
*/
String.prototype.appendRandomNumber = function(length) {
for (var arr = [], i = 1; i <= length; i++)
arr.push(apf.randomGenerator.generate(1, 9));
// Create a new string from the old one, don't just create a copy
return this.toString() + arr.join("");
};
/*
* Prepends a random number with a specified length to this String instance.
*
* @see randomGenerator
* @param {Number} length
* @type {String}
*/
String.prototype.prependRandomNumber = function(length) {
for (var arr = [], i = 1; i <= length; i++)
arr.push(apf.randomGenerator.generate(1, 9));
// Create a new string from the old one, don't just create a copy
return arr.join("") + this.toString();
};
/*
* Returns a string produced according to the formatting string. It replaces
* all <i>%s</i> occurrences with the arguments provided.
*
* @link http://www.php.net/sprintf
* @type {String}
*/
String.prototype.sprintf = function() {
// Create a new string from the old one, don't just create a copy
var str = this.toString(),
i = 0,
inx = str.indexOf("%s");
while (inx >= 0) {
var replacement = arguments[i++] || " ";
str = str.substr(0, inx) + replacement + str.substr(inx + 2);
inx = str.indexOf("%s");
}
return str;
};
/*
* The now method returns the milliseconds elapsed since
* 1 January 1970 00:00:00 UTC up until now as a number.
*
* @type {Number}
*/
if (!Date.now) {
Date.now = function now() {
return +new Date();
};
}
//})(); //end closure
//@todo maybe generalize this to pub/sub event system??
/**
* @private
*/
apf.hotkeys = {};
(function() {
/**
* @private
*/
var keyMods = {"ctrl": 1, "alt": 2, "option" : 2, "shift": 4, "meta": 8, "command": 8};
/**
* @private
*/
this.keyNames = {
"8" : "Backspace",
"9" : "Tab",
"13" : "Enter",
"27" : "Esc",
"32" : "Space",
"33" : "PageUp",
"34" : "PageDown",
"35" : "End",
"36" : "Home",
"37" : "Left",
"38" : "Up",
"39" : "Right",
"40" : "Down",
"45" : "Insert",
"46" : "Del",
"107": "+",
"112": "F1",
"113": "F2",
"114": "F3",
"115": "F4",
"116": "F5",
"117": "F6",
"118": "F7",
"119": "F8",
"120": "F9",
"121": "F10",
"122": "F11",
"123": "F12",
"188": ",",
"219": "[",
"221": "]"
};
var macUnicode = {
"meta" : "\u2318", // ⌘
"command" : "\u2318",
"alt" : "\u2325", // ⌥
"option" : "\u2325",
"shift" : "\u21E7", // ⇧
//"esc" : "\u238B", // ⎋
"ctrl" : "\u2303" // ⌃
// "backspace": "\u232B", // ⌫
// "del" : "\u2326", // ⌦
// "enter" : "\u21A9" // ↩
};
var macUnicodeHtml = {
"meta" : "&#8984;", // ⌘
"command" : "&#8984;",
"alt" : "&#8997;", // ⌥
"option" : "&#8997;",
"shift" : "&#8679;", // ⇧
//"esc" : "&#9099;", // ⎋
"ctrl" : "&#2303;" // ⌃ TODO
// "backspace": "&#232B;", // ⌫ TODO
// "del" : "&#2326;", // ⌦ TODO
// "enter" : "&#21A9;" // ↩ TODO
};
// hash to store the hotkeys in
this.$keys = {};
var _self = this, trace = 0;
function register(hotkey, handler, remove) {
var key,
hashId = 0,
keys = hotkey.splitSafe("\\-", null, true),
i = 0,
l = keys.length;
for (; i < l; ++i) {
if (keyMods[keys[i]])
hashId = hashId | keyMods[keys[i]];
else
key = keys[i] || "-"; //when empty, the splitSafe removed a '-'
}
if (!key) return;
if (!_self.$keys[hashId])
_self.$keys[hashId] = {};
if (remove) {
if (handler == _self.$keys[hashId][key])
_self.$keys[hashId][key] = null;
}
else
_self.$keys[hashId][key] = handler;
}
/**
* Registers a hotkey handler to a key combination.
*
* #### Example:
* ```javascript
* apf.registerHotkey('Ctrl-Z', undoHandler);
* ```
* @param {String} hotkey The key combination to user. This is a
* combination of [[keys: Ctrl]], [[keys: Alt]], [[keys: Shift]] and a normal key to press. Use `+` to
* seperate the keys.
* @param {Function} handler The code to be executed when the key
* combination is pressed.
*/
apf.registerHotkey = this.register = function(hotkey, handler) {
var parts = hotkey.split("|"),
i = 0,
l = parts.length;
for (; i < l; ++i)
register(parts[i], handler);
};
this.$exec = function(eInfo) {
var handler
var hashId = 0 | (eInfo.ctrlKey ? 1 : 0) | (eInfo.altKey ? 2 : 0)
| (eInfo.shiftKey ? 4 : 0) | (eInfo.metaKey ? 8 : 0);
var code = eInfo.keyCode;
var key = _self.keyNames[code]
|| (code && code > 46 && code != 91 ? String.fromCharCode(code) : null);
if (!hashId && (!key || !key.match(/^F\d{1,2}$/)) || !key) //Hotkeys should always have one of the modifiers
return;
if (_self.$keys[hashId] && (handler = _self.$keys[hashId][key.toLowerCase()])) {
handler(eInfo.htmlEvent);
eInfo.returnValue = false;
apf.queue.empty();
}
return eInfo.returnValue;
};
/**
* Removes a registered hotkey.
* @param {String} hotkey The hotkey combination to remove
* @param {Function} handler The code to be executed when the key
* combination is pressed.
*/
apf.removeHotkey = this.remove = this.unregister = function(hotkey, handler) {
var parts = hotkey.split("|"),
i = 0,
l = parts.length;
for (; i < l; ++i)
register(parts[i], handler, true);
};
function toMacNotation(hotkey, bHtml) {
var t;
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", ".<style media='"
+ (media || "all") + "'>" + cssString + "</style>");
/*if(document.body) {
document.body.style.height = "100%";
$setTimeout('document.body.style.height = "auto"');
}*/
}
};
/**
* This method retrieves the current value of a property on a HTML element
* recursively. If the style isn't found on the element itself, its parent is
* checked.
* @param {HTMLElement} el The element to read the property from
* @param {String} prop The property to read
* @returns {String} The retrieved value
*/
apf.getStyleRecur = function(el, prop) {
var value = apf.hasComputedStyle
? document.defaultView.getComputedStyle(el,'').getPropertyValue(
prop.replace(/([A-Z])/g, function(m, m1) {
return "-" + m1.toLowerCase();
}))
: el.currentStyle[prop]
return ((!value || value == "transparent" || value == "inherit")
&& el.parentNode && el.parentNode.nodeType == 1)
? this.getStyleRecur(el.parentNode, prop)
: value;
};
/**
* This method imports a stylesheet defined by a multidimensional array.
* @param {Array} def A multidimensional array specifying stylesheets to import
* @param {Object} [win] A reference to a window
* @method
* @deprecated
*/
apf.importStylesheet = function (def, win, stylesheet) {
if (!def.length)
return;
if (!stylesheet) {
var re = new RegExp("^" + document.domain, 'g');
var doc = (win || window).document;
for (var index=document.styleSheets.length - 1; index >= 0; index--) {
if (!doc.styleSheets[index].href || doc.styleSheets[index].href.match(re)) {
break;
}
}
stylesheet = doc.styleSheets[index];
}
if (!stylesheet)
stylesheet = apf.createStylesheet(win);
for (var i = 0; i < def.length; i++) {
if (!def[i][1])
continue;
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(/&lt;/g, "<")
.replace(/&gt;/g, ">").replace(/&amp;/g, "&").replace(/&nbsp;/g, " ");
};
/**
* Determines whether the keyboard input was a character that can influence
* the value of an element (like a textbox).
* @param {Number} charCode The ascii character code
* @returns {Boolean} `true` if it was a character
*/
apf.isCharacter = function(charCode) {
return (charCode < 112 || charCode > 122)
&& (charCode == 32 || charCode > 42 || charCode == 8);
};
/**
* This random number generator has been added to provide a more robust and
* reliable random number spitter than the native Ecmascript Math.random()
* function.
*
* It is an implementation of the Park-Miller algorithm. (See 'Random Number
* Generators: Good Ones Are Hard to Find', by Stephen K. Park and Keith W.
* Miller, Communications of the ACM, 31(10):1192-1201, 1988.)
* @class apf.randomGenerator
* @author David N. Smith of IBM's T. J. Watson Research Center.
* @author Mike de Boer (mike AT c9 DOT io)
*
*/
apf.randomGenerator = {
d: new Date(),
seed: null,
A: 48271,
M: 2147483647,
Q: null,
R: null,
oneOverM: null,
/**
* Generates a random [[Number]] between a lower and upper boundary.
* The algorithm uses the system time, in minutes and seconds, to 'seed'
* itself, that is, to create the initial values from which it will generate
* a sequence of numbers. If you are familiar with random number generators,
* you might have reason to use some other value for the seed. Otherwise,
* you should probably not change it.
* @param {Number} lnr Lower boundary
* @param {Number} unr Upper boundary
* @returns A random number between `lnr` and`unr`
* @type Number
*/
generate: function(lnr, unr) {
if (this.seed == null)
this.seed = 2345678901 + (this.d.getSeconds() * 0xFFFFFF) + (this.d.getMinutes() * 0xFFFF);
this.Q = this.M / this.A;
this.R = this.M % this.A;
this.oneOverM = 1.0 / this.M;
return Math.floor((unr - lnr + 1) * this.next() + lnr);
},
/**
* Returns a new random number, based on the 'seed', generated by the
* [[apf.randomGenerator.generate]] method.
* @type Number
*/
next: function() {
var hi = this.seed / this.Q;
var lo = this.seed % this.Q;
var test = this.A * lo - this.R * hi;
if (test > 0)
this.seed = test;
else
this.seed = test + this.M;
return (this.seed * this.oneOverM);
}
};
/**
* Adds a time stamp to the URL to prevent the browser from caching it.
* @param {String} url The URL to add the timestamp to.
* @return {String} The url... with a timestamp!
*/
apf.getNoCacheUrl = function(url) {
return url
+ (url.indexOf("?") == -1 ? "?" : "&")
+ "nocache=" + new Date().getTime();
};
/**
* Checks if the string contains curly braces at the start and end. If so, it's
* processed as Javascript via `eval()`. Otherwise, the original string is returned.
* @param {String} str The string to parse.
* @return {String} The result of the parsing.
*/
apf.parseExpression = function(str) {
if (!apf.parseExpression.regexp.test(str))
return str;
return eval(RegExp.$1);
};
apf.parseExpression.regexp = /^\{([\s\S]*)\}$/;
/**
* @private
*/
apf.formatNumber = function(num, prefix) {
var nr = parseFloat(num);
if (!nr) return num;
var str = new String(Math.round(nr * 100) / 100).replace(/(\.\d?\d?)$/, function(m1) {
return m1.pad(3, "0", apf.PAD_RIGHT);
});
if (str.indexOf(".") == -1)
str += ".00";
return prefix + str;
};
/**
* Execute a script in the global scope.
*
* @param {String} str The JavaScript code to execute
* @param {Object} [win] A reference to a window
* @return {String} The executed JavaScript code
*/
apf.jsexec = function(str, win) {
if (!str)
return str;
if (!win)
win = self;
if (apf.isO3)
eval(str, self);
else if (apf.hasExecScript) {
win.execScript(str);
}
else {
var head = win.document.getElementsByTagName("head")[0];
if (head) {
var script = win.document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.text = str;
head.appendChild(script);
head.removeChild(script);
} else
eval(str, win);
}
return str;
};
/*
* Shorthand for an empty function.
*/
apf.K = function(){};
/**
* Reliably determines whether a variable is a Number.
*
* @param {Mixed} value The variable to check
* @type {Boolean} `true` if the argument is a number
*/
apf.isNumber = function(value) {
return parseFloat(value) == value;
};
/**
* Reliably determines whether a variable is an array. For more information, see
* <http://perfectionkills.com/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/>
*
* @param {Mixed} value The variable to check
* @type {Boolean} `true` if the argument is an array
*/
apf.isArray = function(value) {
return Object.prototype.toString.call(value) === "[object Array]";
};
/**
* Determines whether a string is true (in the HTML attribute sense).
* @param {Mixed} value The variable to check. Possible truth values include:
* - true
* - 'true'
* - 'on'
* - 1
* - '1'
* @return {Boolean} Whether the string is considered to imply truth.
*/
apf.isTrue = function(c) {
return (c === true || c === "true" || c === "on" || typeof c == "number" && c > 0 || c === "1");
};
/**
* Determines whether a string is false (in the HTML attribute sense).
* @param {Mixed} value The variable to check. Possible false values include:
* - false
* - 'false'
* - 'off'
* - 0
* - '0'
* @return {Boolean} whether the string is considered to imply untruth.
*/
apf.isFalse = function(c) {
return (c === false || c === "false" || c === "off" || c === 0 || c === "0");
};
/**
* Determines whether a value should be considered false. This excludes, amongst
* others, the number 0.
* @param {Mixed} value The variable to check
* @return {Boolean} Whether the variable is considered false.
*/
apf.isNot = function(c) {
// a var that is null, false, undefined, Infinity, NaN and c isn't a string
return (!c && typeof c != "string" && c !== 0 || (typeof c == "number" && !isFinite(c)));
};
/**
* Creates a relative URL based on an absolute URL.
* @param {String} base The start of the URL to which relative URL's work.
* @param {String} url The URL to transform.
* @return {String} The relative URL.
*/
apf.removePathContext = function(base, url) {
if (!url) return "";
if (url.indexOf(base) > -1)
return url.substr(base.length);
return url;
};
/*
* @private
* @todo why is this done like this?
*/
apf.cancelBubble = function(e, o, noPropagate) {
if (e.stopPropagation)
e.stopPropagation()
else
e.cancelBubble = true;
//if (o.$focussable && !o.disabled)
//apf.window.$focus(o);
if (!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 "&amp;", greater than, less than signs, quotation marks, and others into
* the proper XML entities.
*
* @param {String} str The XML string to escape.
* @param {Boolean} strictMode By default, this function attempts to NOT double-escape XML entities. This flag turns that behavior off when set to `true`.
* @return {String} The escaped string
*/
apf.escapeXML = function(str, strictMode) {
if (typeof str != "string")
return str;
if (strictMode)
str = (str || "").replace(/&/g, "&#38;");
else
str = (str || "").replace(/&(?!#[0-9]{2,5};|[a-zA-Z]{2,};)/g, "&#38;");
var map = apf.xmlEntityMap;
var isArray = apf.isArray;
return str
.replace(/"/g, "&#34;")
.replace(/</g, "&#60;")
.replace(/>/g, "&#62;")
.replace(/'/g, "&#39;")
.replace(/&([a-zA-Z]+);/gi, function(a, m) {
var x = map[m.toLowerCase()];
if (x)
return "&#" + (isArray(x) ? x[0] : x) + ";";
return a;
});
};
/**
* Unescapes `"&#38;"` and other similar XML entities into HTML entities, and then replaces
* 'special' ones (`&apos;`, `&gt;`, `&lt;`, `&quot;`, `&amp;`) into characters
* (`'`, `>`, `<`, `"`, `&`).
*
* @param {String} str The XML string to unescape
* @return {String} The unescaped string
*/
apf.unescapeXML = function(str) {
if (typeof str != "string")
return str;
var map = apf.xmlEntityMapReverse;
var isArray = apf.isArray;
if (!map) {
map = apf.xmlEntityMapReverse = {};
var origMap = apf.xmlEntityMap;
var keys = Object.keys(origMap);
for (var val, j, l2, i = 0, l = keys.length; i < l; ++i) {
val = origMap[keys[i]];
if (isArray(val)) {
for (j = 0, l2 = val.length; j < l2; ++j)
map[val[j]] = keys[i];
}
else
map[val] = keys[i];
}
}
return str
.replace(/&#([0-9]{2,5});/g, function(a, m) {
var x = map[m];
if (x)
return "&" + x + ";";
return a;
})
.replace(/&apos;/gi, "'")
.replace(/&gt;/gi, ">")
.replace(/&lt;/gi, "<")
.replace(/&quot;/gi, "\"")
.replace(/&amp;/gi, "&");
};
/**
* Determines whether a node is its parent's only child.
* @param {DOMNode} node The potential only child.
* @param {Array} nodeType List of the node types that this child can be.
* @returns {Boolean} Whether the node is the only child and of one of the specified node types.
*/
apf.isOnlyChild = function(node, nodeType) {
if (!node || !node.parentNode || nodeType && nodeType.indexOf(node.nodeType) == -1)
return false;
var i, l, cnode, nodes = node.parentNode.childNodes;
for (i = 0, l = nodes.length; i < l; i++) {
cnode = nodes[i];
if (cnode.nodeType == 1 && cnode != node)
return false;
if (cnode.nodeType == 3 && !cnode.nodeValue.trim())
return false;
}
return true;
};
/**
* Gets the position of a DOM node within the list of child nodes of its
* parent.
*
* @param {DOMNode} node The node for which the child position is being determined.
* @return {Number} The child position of the node.
*/
apf.getChildNumber = function(node, fromList) {
if (!node) return -1;
var p = node.parentNode, j = 0;
if (!p) return -1;
if (!fromList)
fromList = p.childNodes;
if (fromList.indexOf) {
var idx = fromList.indexOf(node);
return idx == -1 ? fromList.length : idx;
}
for (var i = 0, l = fromList.length; i < l; i++) {
if (fromList[i] == node)
return j;
j++;
}
return -1;
};
// @todo More information will follow....when?
/**
* Integrates nodes as children of a parent. Optionally, attributes are
* copied as well.
*
* @param {XMLNode} xmlNode The data to merge.
* @param {XMLNode} parent The node to merge on.
* @param {Object} options An object with the following optional properties:
* - [copyAttributes] ([[Boolean]]): Whether the attributes of `xmlNode` are copied as well.
* - [clearContents] ([[Boolean]]): Whether the contents of parent is cleared.
* - [start] ([[Number]]): This feature is used for the virtual viewport. More information will follow.
* - [length] ([[Number]]): This feature is used for the virtual viewport. More information will follow.
* - [documentId] ([[Number]]): This feature is used for the virtual viewport. More information will follow.
* - [marker] ([[XMLElement]]): This feature is used for the virtual viewport. More information will follow.
* @return {XMLNode} The created xml node
*/
apf.mergeXml = function(XMLRoot, parentNode, options) {
if (typeof parentNode != "object")
parentNode = apf.xmldb.getElementById(parentNode);
if (options && options.clearContents) {
//Signal listening elements
var node, j, i,
nodes = parentNode.selectNodes("descendant::node()[@" + apf.xmldb.xmlListenTag + "]");
for (i = nodes.length - 1; i >= 0; i--) {
var s = nodes[i].getAttribute(apf.xmldb.xmlListenTag).split(";");
for (j = s.length - 1; j >= 0; j--) {
node = apf.all[s[j]];
if (!node) continue;
if (node.dataParent && node.dataParent.xpath)
node.dataParent.parent.signalXmlUpdate[node.$uniqueId] = true;
else if (node.$model)
node.$model.$waitForXml(node);
}
}
//clean parent
nodes = parentNode.childNodes;
for (i = nodes.length - 1; i >= 0; i--)
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, "&amp;");
var oldValue = xmlNode.nodeValue;
xmlNode.nodeValue = nodeValue === undefined || nodeValue === null ||
nodeValue == NaN ? "" : String(nodeValue);
if (undoObj) {
undoObj.name = xmlNode.nodeName;
}
//AML support - getters/setters would be awesome
if (xmlNode.$triggerUpdate)
xmlNode.$triggerUpdate(null, oldValue);
if (applyChanges) {
apf.xmldb.applyChanges(xmlNode.nodeType == 2 ? "attribute" : "text", xmlNode.parentNode ||
xmlNode.ownerElement || xmlNode.selectSingleNode(".."), undoObj);
}
}
if (applyChanges) {
var node;
if (xpath) {
var node = undoObj.xmlNode;//.selectSingleNode(newNodes.foundpath);
if (node.nodeType == 9) {
node = node.documentElement;
xpath = xpath.replace(/^[^\/]*\//, "");//xpath.substr(newNodes.foundpath.length);
}
}
else
node = xmlNode;
apf.xmldb.applyRDB(["setValueByXpath", node, nodeValue, xpath,
options && options.forceNew],
undoObj || {xmlNode: xmlNode}
);
}
};
/**
* Sets a value of an XML node based on an xpath statement executed on a referenced XMLNode.
*
* @param {XMLNode} xmlNode The reference XML node.
* @param {String} xpath The xpath used to select a XML node.
* @param {String} value The value to set.
* @param {Boolean} local Whether the call updates databound UI.
* @return {XMLNode} The changed XML node
*/
apf.setQueryValue = function(xmlNode, xpath, value, local) {
var node = apf.createNodeFromXpath(xmlNode, xpath);
if (!node)
return null;
apf.setNodeValue(node, value, !local);
//apf.xmldb.setTextNode(node, value);
return node;
};
/**
* Removes an XML node based on an xpath statement executed on a referenced XML node.
*
* @param {XMLNode} xmlNode The reference XML node.
* @param {String} xpath The xpath used to select a XML node.
* @return {XMLNode} The changed XML node
*/
apf.removeQueryNode = function(xmlNode, xpath, local) {
var node = apf.queryNode(xmlNode, xpath);
if (!node)
return false;
if (local)
node.parentNode.removeChild(node);
else
apf.xmldb.removeNode(node);
return node;
};
/**
* Queries an XML node using xpath for a single string value.
* @param {XMLElement} xmlNode The XML element to query
* @param {String} xpath The xpath query
* @return {String} The value of the query result or empty string
*/
apf.queryValue = function (xmlNode, xpath) {
if (!xmlNode)
return "";
if (xmlNode.nodeType == 2)
return xmlNode.nodeValue;
if (xpath) {
xmlNode = xmlNode.selectSingleNode(xpath);
if (!xmlNode)
return "";
}
return xmlNode.nodeType == 1
? (!xmlNode.firstChild ? "" : xmlNode.firstChild.nodeValue)
: xmlNode.nodeValue;
};
/**
* Queries an xml node using xpath for multiple string value.
* @param {XMLElement} xmlNode The xml element to query
* @param {String} xpath The xpath query
* @return {Array} A list of values resulting from the query
*/
apf.queryValues = function(xmlNode, xpath) {
var out = [];
if (!xmlNode) return out;
var nodes = xmlNode.selectNodes(xpath);
if (!nodes.length) return out;
for (var i = 0; i < nodes.length; i++) {
var n = nodes[i];
if (n.nodeType == 1)
n = n.firstChild;
out.push(n.nodeValue || "");
}
return out;
};
/**
* Executes an xpath expression on any DOM node. This is especially useful
* for DOM nodes that don't have a good native xpath processor, such as HTML
* in some versions of Internet Explorer and XML in Webkit.
*
* @param {DOMNode} contextNode The XML node that is subject to the query
* @param {String} sExpr The xpath expression
* @returns {Array} A list of found XML nodes. The list can be empty
*/
apf.queryNodes = function(contextNode, sExpr) {
if (contextNode && (apf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style))
return contextNode.selectNodes(sExpr); //IE55
//if (contextNode.ownerDocument != document)
// return contextNode.selectNodes(sExpr);
return apf.XPath.selectNodes(sExpr, contextNode);
};
/**
* Executes an xpath expression on any DOM node.
* This is especially useful for DOM nodes that don't have a good native
* xpath processor such as html in some versions of internet explorer and XML in
* webkit. This function only returns the first node found.
*
* @param {DOMNode} contextNode The DOM node that is subject to the query.
* @param {String} sExpr The xpath expression.
* @returns {XMLNode} The DOM node, or `null` if none was found.
*/
apf.queryNode = function(contextNode, sExpr) {
if (contextNode && (apf.hasXPathHtmlSupport && contextNode.selectSingleNode || !contextNode.style))
return contextNode.selectSingleNode(sExpr); //IE55
//if (contextNode.ownerDocument != document)
// return contextNode.selectSingleNode(sExpr);
var nodeList = apf.queryNodes(contextNode ? contextNode : null,
sExpr + (apf.isIE ? "" : "[1]"));
return nodeList.length > 0 ? nodeList[0] : null;
};
/**
* Retrieves the attribute of an XML node, or the first parent node that has
* that attribute set. If no attribute is set, the value is searched for on
* the appsettings element.
*
* @param {XMLElement} xml The XML node that is the starting point of the search.
* @param {String} attr The name of the attribute.
* @param {Function} [func] A callback that is run for every node that is searched.
* @return {String} The found value, or empty string if none was found.
*/
apf.getInheritedAttribute = function(xml, attr, func) {
var result, avalue;
//@todo optimize this and below
if (xml.nodeType != 1)
xml = xml.parentNode;
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\</g, "<").replace(/\</g, "\n<").split("\n");
lines.removeIndex(0);//test if this is actually always fine
lines.removeIndex(lines.length);
for (i = 0, l = lines.length; i < l; i++)
lines[i] = " ".repeat((lines[i].match(/^\s*\<\//)
? (depth==0)?0:--depth
: (lines[i].match(/^\s*\<[^\?][^>]+[^\/]\>/) ? depth++ : depth))) + lines[i];
if (!strXml)
return "";
return lines.join("\n");
};
//@todo this function needs to be 100% proof, it's the core of the system
//for RDB: xmlNode --> Xpath statement
apf.xmlToXpath = function(xmlNode, xmlContext, useAID) {
if (!xmlNode) //@todo apf3.0
return "";
if (useAID === true && xmlNode.nodeType == 1 && xmlNode.getAttribute(apf.xmldb.xmlIdTag)) {
return "//node()[@" + apf.xmldb.xmlIdTag + "='"
+ xmlNode.getAttribute(apf.xmldb.xmlIdTag) + "']";
}
if (apf != this && this.lookup && this.select) {
var unique, def = this.lookup[xmlNode.tagName];
if (def) {
//unique should not have ' in it... -- can be fixed...
unique = xmlNode.selectSingleNode(def).nodeValue;
return "//" + xmlNode.tagName + "[" + def + "='" + unique + "']";
}
for (var i = 0, l = this.select.length; i < l; i++) {
if (xmlNode.selectSingleNode(this.select[i][0])) {
unique = xmlNode.selectSingleNode(this.select[i][1]).nodeValue;
return "//" + this.select[i][0] + "[" + this.select[i][1]
+ "='" + unique + "']";
}
}
}
if (xmlNode == xmlContext)
return ".";
if (xmlNode.nodeType != 2 && !xmlNode.parentNode && !xmlNode.ownerElement) {
return false;
}
var str = [], lNode = xmlNode;
if (lNode.nodeType == 2) {
str.push("@" + lNode.nodeName);
lNode = lNode.ownerElement || xmlNode.selectSingleNode("..");
}
var id;//, pfx = "";
while (lNode && lNode.nodeType == 1) {
if (lNode == xmlContext) {
//str.unshift("/");//pfx = "//";
break;
}
str.unshift((lNode.nodeType == 1 ? lNode.tagName : "text()") +
"[" + (useAID && (id = lNode.nodeType == 1 && lNode.getAttribute(apf.xmldb.xmlIdTag))
? "@" + apf.xmldb.xmlIdTag + "='" + id + "'"
: (apf.getChildNumber(lNode, lNode.parentNode.selectNodes(lNode.nodeType == 1 ? lNode.tagName : "text()")) + 1)) +
"]");
lNode = lNode.parentNode;
};
return (str[0] == "/" || xmlContext && xmlContext.nodeType == 1 ? "" : "/") + str.join("/"); //pfx +
};
//for RDB: Xpath statement --> xmlNode
apf.xpathToXml = function(xpath, xmlNode) {
if (!xmlNode) {
return false;
}
return xmlNode.selectSingleNode(xpath);
};
apf.n = function(xml, xpath) {
return new apf.xmlset(xml, xpath, true);
};
apf.b = function(xml, xpath) {
return new apf.xmlset(xml, xpath);
};
apf.b.$queue = [];
apf.b.$state = 0;
/*
* Naive jQuery like set implementation
* @todo add dirty flags
* @todo add query queue
* @todo rewrite to use arrays
*/
apf.xmlset = function(xml, xpath, local, previous) {
if (typeof xml == "string")
xml = apf.getXml(xml);
this.$xml = xml;
if (xml)
this.$nodes = xml.dataType == apf.ARRAY ? xml : (xpath ? xml.selectNodes(xpath) : [xml]);
this.$xpath = xpath || ".";
this.$local = local;
this.$previous = previous;
};
(function(){
this.add = function(){}; //@todo not implemented
this.begin = function(){
apf.b.$state = 1;
return this;
};
this.commit = function(at, rmt, uri) {
if (apf.b.$queue.length) {
if (rmt) {
var _self = this;
rmt.addEventListener("rdbsend", function(e) {
if (!uri || e.uri == uri) {
_self.rdbstack = e.message;
rmt.removeEventListener("rdbsend", arguments.callee);
}
});
}
at.execute({
action: 'multicall',
args: apf.b.$queue
});
if (rmt)
rmt.$processQueue(rmt);
}
apf.b.$queue = [];
apf.b.$state = 0;
return this;
};
this.rollback = function(){
apf.b.$queue = [];
apf.b.$state = 0;
return this;
};
this.getRDBMessage = function(){
return this.rdbstack || [];
};
this.before = function(el) {
el = typeof el == "function" ? el(i) : el;
for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
node = this.$nodes[i];
if (this.$local)
node.parentNode.insertBefore(el, node);
else
apf.xmldb.appendChild(node.parentNode, el, node);
}
return this;
};
this.after = function(el) {
el = typeof el == "function" ? el(i) : el;
for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
node = this.$nodes[i];
if (this.$local)
node.parentNode.insertBefore(el, node.nextSibling);
else
apf.xmldb.appendChild(node.parentNode, el, node.nextSibling);
}
return this;
};
this.andSelf = function(){};
this.append = function(el) {
for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) {
node = this.$nodes[i];
child = typeof el == "function" ? el(i, node) : el;
if (apf.b.$state)
apf.b.$queue.push({
action: 'appendChild',
args: [node, child]
});
else if (this.$local)
node.appendChild(child);
else
apf.xmldb.appendChild(node, child);
}
return this;
};
this.appendTo = function(target) {
for (var i = 0, l = this.$nodes.length; i < l; i++) {
target.appendChild(this.$nodes[i]);
}
return this;
};
this.prepend = function(el) {
for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
node = this.$nodes[i];
node.insertBefore(typeof el == "function" ? el(i, node) : el, node.firstChild);
}
return this;
};
this.prependTo = function(target) {
for (var i = 0, l = this.$nodes.length; i < l; i++) {
target.insertBefore(this.$nodes[i], target.firstChild);
}
return this;
};
this.attr = function(attrName, value) {
if (arguments.length === 1) {
return this.$nodes && this.$nodes[0] && this.$nodes[0].getAttribute(attrName) || "";
}
else {
for (var i = 0, l = this.$nodes.length; i < l; i++) {
if (apf.b.$state)
apf.b.$queue.push({
action: 'setAttribute',
args: [this.$nodes[i], attrName, value]
});
else if (this.$local)
this.$nodes[i].setAttribute(attrName, value);
else
apf.xmldb.setAttribute(this.$nodes[i], attrName, value);
}
}
return this;
};
this.removeAttr = function(attrName) {
for (var i = 0, l = this.$nodes.length; i < l; i++) {
if (apf.b.$state)
apf.b.$queue.push({
action: 'removeAttribute',
args: [this.$nodes[i], attrName]
});
else if (this.$local)
this.$nodes[i].removeAttribute(attrName);
else
apf.xmldb.removeAttribute(this.$nodes[i], attrName);
}
return this;
};
this.xml = function(){
var str = [];
for (var i = 0, l = this.$nodes.length; i < l; i++) {
str.push(this.$nodes[i].xml);
}
return str.join("\n");
};
this.get =
this.index = function(idx) {
idx = idx || 0;
return apf.getChildNumber(this.$nodes[idx], this.$nodes[idx].parentNode.getElementsByTagName("*"))
};
this.eq = function(index) {
return index < 0 ? this.$nodes[this.$nodes.length - index] : this.$nodes[index];
};
this.size =
this.length = function(){
return this.$nodes.length;
};
this.load = function(url) {
};
this.next = function(selector) {
if (!selector)
selector = "node()[local-name()]";
return new apf.xmlset(this.$xml, "((following-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
")[1])[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
};
this.nextAll = function(selector) {
if (!selector)
selector = "node()[local-name()]";
return new apf.xmlset(this.$xml, "(following-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
")[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
};
this.nextUntil = function(){};
this.prev = function(selector) {
if (!selector)
selector = "node()[local-name()]";
return new apf.xmlset(this.$xml, "((preceding-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
")[1])[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
};
this.prevAll = function(selector) {
if (!selector)
selector = "node()[local-name()]";
return new apf.xmlset(this.$xml, "(preceding-sibling::" + (this.$xpath == "." ? "node()" : this.$xpath) +
")[self::" + selector.split("|").join("|self::") + "]", this.$local, this);
};
this.not = function(){};
this.parent = function(selector) {
return new apf.xmlset(this.$xml.parentNode, this.$local, this);
};
this.parents = function(selector) {};
this.pushStack = function(){};
this.replaceAll = function(){};
this.replaceWith = function(el) {
for (var node, child, i = 0, l = this.$nodes.length; i < l; i++) {
node = this.$nodes[i];
child = typeof el == "function" ? el(i, node) : el;
if (apf.b.$state)
apf.b.$queue.push({
action: 'replaceNode',
args: [child, node]
});
else if (this.$local)
node.parentNode.replaceChild(child, node);
else
apf.xmldb.replaceNode(child, node);
}
return this;
};
this.siblings = function(selector) {
//preceding-sibling::
//return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/node()[self::" + selector.split("|").join("|self::") + "]");
};
this.text = function(){
};
this.toArray = function(){
var arr = [];
for (var i = 0, l = this.$nodes.length; i < l; i++) {
arr.push(this.$nodes[i]);
}
return arr;
};
this.detach = function(selector) {
var items = [];
for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
node = this.$nodes[i];
if (!node.selectSingleNode("self::node()[" + selector + "]"))
continue;
if (apf.b.$state)
apf.b.$queue.push({
action: 'removeNode',
args: [node]
});
else if (this.$local)
node.parentNode.removeChild(node);
else
apf.xmldb.removeNode(node);
items.push(node);
}
return new apf.xmlset(items, "", this.$local, this);
};
this.remove = function(selector) {
for (var node, n = this.$nodes, i = n.length - 1; i >= 0; i--) {
node = n[i];
if (selector && !node.selectSingleNode("self::node()[" + selector + "]"))
continue;
if (apf.b.$state)
apf.b.$queue.push({
action: 'removeNode',
args: [node]
});
else if (this.$local)
node.parentNode.removeChild(node);
else
apf.xmldb.removeNode(node);
}
return this;
};
this.children = function(selector) {
var nodes = [];
for (var node, i = 0, l = this.$nodes.length; i < l; i++) {
var list = (node = this.$nodes[i]).selectNodes(selector);
for (var j = 0, jl = list.length; j < jl; j++) {
nodes.push(list[j]);
}
}
return new apf.xmlset(nodes, null, this.$local, this);
};
this.children2 = function(selector) {
return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/node()[self::" +
selector.split("|").join("|self::") + "]", this.$local, this);
};
this.has =
this.find = function(path) {
return new apf.xmlset(this.$xml, "(" + this.$xpath + ")//" +
path.split("|").join("|self::"), this.$local, this);
};
this.query = function(path) {
return new apf.xmlset(this.$xml, "(" + this.$xpath + ")/" +
path.split("|").join("|(" + this.$xpath + ")/"), this.$local, this);
};
this.filter = function(filter) {
var newList = [];
for (var i = 0, l = this.$nodes.length; i < l; i++) {
if (this.$nodes[i].selectSingleNode("self::node()[" + filter + "]"))
newList.push(this.$nodes[i]);
}
return new apf.xmlset(newList, null, this.$local, this);
};
this.end = function(){
return this.$previous || this;
};
this.is = function(selector) {
return this.filter(selector) ? true : false;
};
this.contents = function(){
return this.children("node()");
};
this.has = function(){
//return this.children(
};
this.val = function(value) {
if (arguments.length) {
apf.setQueryValue(this.$xml, this.$xpath, value);
return this;
}
else
return apf.queryValue(this.$xml, this.$xpath);
};
this.vals = function(){
return apf.queryValues(this.$xml, this.$xpath);
};
this.node = function(){
return apf.queryNode(this.$xml, this.$xpath);
};
this.nodes = function(){
return apf.queryNodes(this.$xml, this.$xpath);
};
this.clone = function(deep) {
if (this.$nodes.length == 1)
return new apf.xmlset(this.$nodes[0].cloneNode(true), "", this.$local, this);
var nodes = [];
for (var i = 0, l = this.$nodes.length; i < l; i++) {
nodes.push(this.$nodes[i].cloneNode(deep == undefined ? true : deep));
}
return new apf.xmlset(nodes, "", this.$local, this);
};
this.context = function(){
return this.$xml;
};
this.data = function(data) {
for (var i = 0, l = this.$nodes.length; i < l; i++) {
apf.setQueryValue(this.$nodes[i], ".", data);
}
return this;
};
this.each = function(func) {
for (var i = 0, l = this.$nodes.length; i < l; i++) {
func.call(this.$nodes[i], i);
}
return this;
};
this.eachrev = function(func) {
for (var i = this.$nodes.length - 1; i >= 0; i--) {
func.call(this.$nodes[i], i);
}
return this;
};
this.map = function(callback) {
var values = [];
for (var i = 0, l = this.$nodes.length; i < l; i++) {
values.push(callback(this.$nodes[i]));
}
return new apf.xmlset(values, "", this.$local, this); //blrghhh
};
this.empty = function(){
this.children().detach();
return this;
};
this.first = function(){
return new apf.xmlset(this.$xml, "(" + this.$xpath + ")[1]", this.$local, this);
};
this.last = function(){
return new apf.xmlset(this.$xml, "(" + this.$xpath + ")[last()]", this.$local, this);
};
}).call(apf.xmlset.prototype);
/**
* 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
* <!-- loading aml from an xml file -->
* <a:bar aml="moreaml.xml" />
*
* <a:bindings>
* <!-- loads data using an remote procedure protocol -->
* <a:load get = "{comm.getData()}" />
*
* <!-- inserts data using an remote procedure protocol -->
* <a:insert get = "{comm.getSubData([@id])}" />
* </a:bindings>
*
* <a:actions>
* <!-- notifies the server that a file is renamed -->
* <a:rename set = "update_file.jsp?id=[@id]&name=[@name]" />
*
* <!-- adds a node by retrieving it's xml from the server. -->
* <a:add get = "new_user.xml" />
* </a:actions>
*
* <!-- creates a model which is loaded into a list -->
* <a:list model="{webdav.getRoot()}" />
*
* <!-- loads data into a model and when submitted sends the altered data back -->
* <a:model load="load_contact.jsp" submission="save_contact.jsp" />
* ```
*
* @method getData
* @param {String} instruction The {@link term.datainstruction data instruction} to be used to retrieve the data.
* @param {XMLElement} [xmlContext] The subject of the xpath queries
* @param {Object} [options] The options for this instruction. Available properties include:
* - multicall ([[Boolean]]): Whether this call should not be executed immediately, but saved for later sending using the `purge()` command
* - userdata (`Mixed`): Any data that is useful to access in the callback function
* - args ([[Array]]): The arguments of the call, overriding any specified in the data instruction
* - [xmlContext] ([[XMLElement]]): The subject of the xpath queries
* - [callback] ([[Function]]): The code that is executed when the call returns, either successfully or not
* @param {Function} [callback] The code that is executed when the call returns, either successfully or not
*/
apf.getData = function(instruction, options) {
if (!instruction) return false;
//Instruction type detection
var result, chr = instruction.charAt(0), callback = options.callback;
var gCallback =
function(data, state, extra) {
var callback = options.callback
if (state != apf.SUCCESS)
return callback(data, state, extra || {});
//Change this to warning?
/*if (!data) {
throw new Error(apf.formatErrorString(0, null,
"Loading new data", "Could not load data. \n\
Data instruction: '" + instruction + "'"));
}*/
return callback(data, state, extra || {});
}
if (!options) options = {}; //@todo optimize?
var fParsed = options.fParsed || (instruction.indexOf("{") > -1 || instruction.indexOf("[") > -1
? apf.lm.compile(instruction, {
withopt: true,
precall: options.precall,
alwayscb: true,
simplexpath: true
})
: {str: instruction, type: 2});
if (options.precall && !options._pc) {
options.asyncs = fParsed.asyncs;
options._pc = 1;
}
//@todo hack because we're not using compileNode.. an imperfection..
if (fParsed.type == 3) {
if (fParsed.xpaths[0]) {
var model = fParsed.xpaths[0], xpath = fParsed.xpaths[1];
//@todo can this be async?
if (model == "#" || xpath == "#") { //When there is a set model and not a generated xpath
var m = (apf.lm.compile(instruction, {
xpathmode: 5
}))();
//@todo apf3 this needs to be fixed in live markup
if (typeof m != "string") {
model = m.model && m.model.$isModel && m.model;
if (model)
xpath = m.xpath;
else if (m.model) {
model = apf.xmldb.findModel(m.model);
xpath = apf.xmlToXpath(m.model, model.data) + (m.xpath ? "/" + m.xpath : ""); //@todo make this better
}
else {
//Model is not yet available. When it comes available we will be recalled (at least for prop binds)
return;
}
}
else model = null;
}
else {
model = apf.nameserver.get("model", model)
}
return gCallback(model.data.selectSingleNode(xpath), apf.SUCCESS, {});
}
else {
return gCallback(options.xmlNode.data.selectSingleNode(fParsed.xpaths[1]), apf.SUCCESS, {});
}
}
//xml generation
if (chr == "<") {
//string only
if (fParsed.type == 2)
result = fParsed.str;
else
return fParsed(options.xmlNode, gCallback, options);
}
//jslt fetching data
else if ((chr == "[" || chr == "{")) { //(fParsed.asyncs || fParsed.models) &&
return fParsed(options.xmlNode, gCallback, options);
}
//url
else {
if (fParsed.type == 1 || fParsed.type == 3) {
var callback2 = callback;
callback = options.callback = function(data, state, extra) {
if (options._pc === true)
return;
if (state != apf.SUCCESS)
return callback2.apply(this, arguments);
var url = data.split(" "), method = "get";
if (url.length > 1 && url[0].length < 10) {
method = url.shift();
url = url.join(" ");
}
else url = data;
callback = options.callback = callback2;
apf.oHttp.exec(method, [url], gCallback, options);
}
fParsed(options.xmlNode, gCallback, options);
}
else {
if (options._pc === true)
return;
var url = instruction.split(" "), method = "get";
if (url.length > 1 && url[0].length < 10) {
method = url.shift();
url = url.join(" ");
}
else {
url = instruction;
}
apf.oHttp.exec(method, [url.replace(/\\/g, "")], gCallback, options);
}
}
if (result) {
if (callback)
gCallback(result, apf.SUCCESS, {});
else {
//apf.console.warn("Returning data directly in apf.getData(). \
//This means that all callback communication ends in void!");
return result;
}
}
};
/**
* Creates a model object based on a {@link term.datainstruction data instruction}.
*
* @param {String} instruction The {@link term.datainstruction data instruction} to be used to retrieve the data for the model
* @param {apf.AmlNode} amlNode The element the model is added to
*/
apf.setModel = function(instruction, amlNode) {
if (!instruction) return;
//Find existing model
var fParsed = instruction.indexOf("{") > -1 || instruction.indexOf("[") > -1
? apf.lm.compile(instruction, {
//precall : false,
alwayscb: true
})
: {
type: 2,
str: instruction
};
if (instruction == "@default" || fParsed.type == 2) {
var model = apf.nameserver.get("model", instruction);
if (model)
return model.register(amlNode);
else
if (instruction == "@default")
return;
//@todo apf3.0 check here if string is valid url (relative or absolute)
if (instruction.indexOf(".") == -1 && instruction.indexOf("/") == -1) {
return;
}
}
//Just an xpath doesnt work. We don't have context
//var l, x;
if (fParsed.type == 3) {//This won't work for complex xpaths
if (fParsed.models) { //check for # in xpaths[i] to determine if its calculated
if (fParsed.xpaths.length == 2 && fParsed.xpaths[0] != '#' && fParsed.xpaths [1] != '#') {
apf.nameserver.get("model", fParsed.xpaths[0]).register(amlNode, fParsed.xpaths[1]);
return;
}
}
}
if (amlNode.clear)
amlNode.clear("loading");
//Complex data fetch (possibly async) - data is loaded only once.
//Potential property binding has to take of the rest
apf.getData(instruction, {
parsed: fParsed,
xmlNode: amlNode && amlNode.xmlRoot,
callback: function(data, state, extra) {
//@todo apf3.0 call onerror on amlNode
if (state != apf.SUCCESS) {
throw new Error(apf.formatErrorString(0, null,
"Loading new data", "Could not load data into model. \
\nMessage: " + extra.message + "\
\nInstruction: '" + instruction + "'"));
}
if (!data)
return amlNode.clear && amlNode.clear();
if (typeof data == "string") {
if (data.charAt(0) == "<")
data = apf.getXml(data);
else {
//Assuming web service returned url
if (data.indexOf("http://") == 0)
return apf.setModel(data, amlNode);
else {
throw new Error("Invalid data from server");//@todo apf3.0 make proper apf error handling. apf.onerror
}
}
}
if (data.nodeFunc) { //Assuming a model was passed -- data.localName == "model" &&
data.register(amlNode);
return;
}
var model = apf.xmldb.findModel(data); //See if data is already loaded into a model
if (model)
model.register(amlNode, apf.xmlToXpath(data, model.data)); //@todo move function to xml library
else
new apf.model().register(amlNode).load(data);
}});
};
/*
* @version: 1.0 Alpha-1
* @author: Coolite Inc. http://www.coolite.com/
* @date: 2008-04-13
* @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved.
* @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
* @website: http://www.datejs.com/
*/
(function () {
var $C = {
/* Culture Name */
name: "en-US",
englishName: "English (United States)",
nativeName: "English (United States)",
/* Day Name Strings */
dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
abbreviatedDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
shortestDayNames: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
firstLetterDayNames: ["S", "M", "T", "W", "T", "F", "S"],
/* Month Name Strings */
monthNames: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
abbreviatedMonthNames: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
/* AM/PM Designators */
amDesignator: "AM",
pmDesignator: "PM",
firstDayOfWeek: 0,
twoDigitYearMax: 2029,
/**
* The dateElementOrder is based on the order of the
* format specifiers in the formatPatterns.DatePattern.
*
* Example:
<pre>
shortDatePattern dateElementOrder
------------------ ----------------
"M/d/yyyy" "mdy"
"dd/MM/yyyy" "dmy"
"yyyy-MM-dd" "ymd"
</pre>
*
* The correct dateElementOrder is required by the parser to
* determine the expected order of the date elements in the
* string being parsed.
*/
dateElementOrder: "mdy",
/* Standard date and time format patterns */
formatPatterns: {
shortDate: "M/d/yyyy",
longDate: "dddd, MMMM dd, yyyy",
shortTime: "h:mm tt",
longTime: "h:mm:ss tt",
fullDateTime: "dddd, MMMM dd, yyyy h:mm:ss tt",
sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
monthDay: "MMMM dd",
yearMonth: "MMMM, yyyy"
},
/**
* NOTE: If a string format is not parsing correctly, but
* you would expect it parse, the problem likely lies below.
*
* The following regex patterns control most of the string matching
* within the parser.
*
* The Month name and Day name patterns were automatically generated
* and in general should be (mostly) correct.
*
* Beyond the month and day name patterns are natural language strings.
* Example: "next", "today", "months"
*
* These natural language string may NOT be correct for this culture.
* If they are not correct, please translate and edit this file
* providing the correct regular expression pattern.
*
* If you modify this file, please post your revised CultureInfo file
* to the Datejs Forum located at http://www.datejs.com/forums/.
*
* Please mark the subject of the post with [CultureInfo]. Example:
* Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
*
* We will add the modified patterns to the master source files.
*
* As well, please review the list of "Future Strings" section below.
*/
regexPatterns: {
jan: /^jan(uary)?/i,
feb: /^feb(ruary)?/i,
mar: /^mar(ch)?/i,
apr: /^apr(il)?/i,
may: /^may/i,
jun: /^jun(e)?/i,
jul: /^jul(y)?/i,
aug: /^aug(ust)?/i,
sep: /^sep(t(ember)?)?/i,
oct: /^oct(ober)?/i,
nov: /^nov(ember)?/i,
dec: /^dec(ember)?/i,
sun: /^su(n(day)?)?/i,
mon: /^mo(n(day)?)?/i,
tue: /^tu(e(s(day)?)?)?/i,
wed: /^we(d(nesday)?)?/i,
thu: /^th(u(r(s(day)?)?)?)?/i,
fri: /^fr(i(day)?)?/i,
sat: /^sa(t(urday)?)?/i,
future: /^next/i,
past: /^last|past|prev(ious)?/i,
add: /^(\+|aft(er)?|from|hence)/i,
subtract: /^(\-|bef(ore)?|ago)/i,
yesterday: /^yes(terday)?/i,
today: /^t(od(ay)?)?/i,
tomorrow: /^tom(orrow)?/i,
now: /^n(ow)?/i,
millisecond: /^ms|milli(second)?s?/i,
second: /^sec(ond)?s?/i,
minute: /^mn|min(ute)?s?/i,
hour: /^h(our)?s?/i,
week: /^w(eek)?s?/i,
month: /^m(onth)?s?/i,
day: /^d(ay)?s?/i,
year: /^y(ear)?s?/i,
shortMeridian: /^(a|p)/i,
longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i,
timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
ordinalSuffix: /^\s*(st|nd|rd|th)/i,
timeContext: /^\s*(\:|a(?!u|p)|p)/i
},
timezones: [
{name:"UTC", offset:"-000"},
{name:"GMT", offset:"-000"},
{name:"EST", offset:"-0500"},
{name:"EDT", offset:"-0400"},
{name:"CST", offset:"-0600"},
{name:"CDT", offset:"-0500"},
{name:"MST", offset:"-0700"},
{name:"MDT", offset:"-0600"},
{name:"PST", offset:"-0800"},
{name:"PDT", offset:"-0700"}
]
};
var $D = Date,
$P = $D.prototype,
p = function(s, l) {
if (!l)
l = 2;
return ("000" + s).slice(l * -1);
};
/**
* Resets the time of this Date object to 12:00 AM (00:00), which is the
* start of the day.
* @return {Date} this
*/
$P.clearTime = function() {
this.setHours(0);
this.setMinutes(0);
this.setSeconds(0);
this.setMilliseconds(0);
return this;
};
/**
* Resets the time of this Date object to the current time ('now').
* @return {Date} this
*/
$P.setTimeToNow = function() {
var n = new Date();
this.setHours(n.getHours());
this.setMinutes(n.getMinutes());
this.setSeconds(n.getSeconds());
this.setMilliseconds(n.getMilliseconds());
return this;
};
/**
* Gets a date that is set to the current date. The time is set to the start
* of the day (00:00 or 12:00 AM).
* @return {Date} The current date.
*/
$D.today = function() {
return new Date().clearTime();
};
/**
* Compares the first date to the second date and returns an number indication
* of their relative values.
* @param {Date} First Date object to compare [Required].
* @param {Date} Second Date object to compare to [Required].
* @return {Number} -1 = date1 is lessthan date2. 0 = values are equal.
* 1 = date1 is greaterthan date2.
*/
$D.compare = function(date1, date2) {
if (isNaN(date1) || isNaN(date2))
throw new Error(date1 + " - " + date2);
else if (date1 instanceof Date && date2 instanceof Date)
return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
else
throw new TypeError(date1 + " - " + date2);
};
/**
* Compares the first Date object to the second Date object and returns true
* if they are equal.
* @param {Date} First Date object to compare [Required]
* @param {Date} Second Date object to compare to [Required]
* @return {Boolean} true if dates are equal. false if they are not equal.
*/
$D.equals = function(date1, date2) {
return (date1.compareTo(date2) === 0);
};
/**
* Gets the day number (0-6) if given a CultureInfo specific string which is
* a valid dayName, abbreviatedDayName or shortestDayName (two char).
* @param {String} The name of the day (eg. "Monday, "Mon", "tuesday", "tue", "We", "we").
* @return {Number} The day number
*/
$D.getDayNumberFromName = function(name) {
var n = $C.dayNames,
m = $C.abbreviatedDayNames,
o = $C.shortestDayNames,
s = name.toLowerCase();
for (var i = 0; i < n.length; i++) {
if (n[i].toLowerCase() == s || m[i].toLowerCase() == s || o[i].toLowerCase() == s)
return i;
}
return -1;
};
/**
* Gets the month number (0-11) if given a Culture Info specific string which
* is a valid monthName or abbreviatedMonthName.
* @param {String} The name of the month (eg. "February, "Feb", "october", "oct").
* @return {Number} The day number
*/
$D.getMonthNumberFromName = function(name) {
var n = $C.monthNames,
m = $C.abbreviatedMonthNames,
s = name.toLowerCase();
for (var i = 0; i < n.length; i++) {
if (n[i].toLowerCase() == s || m[i].toLowerCase() == s)
return i;
}
return -1;
};
/**
* Determines if the current date instance is within a LeapYear.
* @param {Number} The year.
* @return {Boolean} true if date is within a LeapYear, otherwise false.
*/
$D.isLeapYear = function(year) {
return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
};
/**
* Gets the number of days in the month, given a year and month value.
* Automatically corrects for LeapYear.
* @param {Number} The year.
* @param {Number} The month (0-11).
* @return {Number} The number of days in the month.
*/
$D.getDaysInMonth = function(year, month) {
return [31, ($D.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
};
$D.getTimezoneAbbreviation = function(offset) {
var z = $C.timezones, p;
for (var i = 0; i < z.length; i++) {
if (z[i].offset === offset)
return z[i].name;
}
return null;
};
$D.getTimezoneOffset = function(name) {
var z = $C.timezones, p;
for (var i = 0; i < z.length; i++) {
if (z[i].name === name.toUpperCase())
return z[i].offset;
}
return null;
};
/**
* Returns a new Date object that is an exact date and time copy of the
* original instance.
* @return {Date} A new Date instance
*/
$P.clone = function() {
return new Date(this.getTime());
};
/**
* Compares this instance to a Date object and returns an number indication
* of their relative values.
* @param {Date} Date object to compare [Required]
* @return {Number} -1 = this is lessthan date. 0 = values are equal.
* 1 = this is greaterthan date.
*/
$P.compareTo = function(date) {
return Date.compare(this, date);
};
/**
* Compares this instance to another Date object and returns true if they are equal.
* @param {Date} Date object to compare. If no date to compare, new Date()
* [now] is used.
* @return {Boolean} true if dates are equal. false if they are not equal.
*/
$P.equals = function(date) {
return Date.equals(this, date || new Date());
};
/**
* Determines if this instance is between a range of two dates or equal to
* either the start or end dates.
* @param {Date} Start of range [Required]
* @param {Date} End of range [Required]
* @return {Boolean} true is this is between or equal to the start and end
* dates, else false
*/
$P.between = function(start, end) {
return this.getTime() >= start.getTime() && this.getTime() <= end.getTime();
};
/**
* Determines if this date occurs after the date to compare to.
* @param {Date} Date object to compare. If no date to compare, new Date()
* ("now") is used.
* @return {Boolean} true if this date instance is greater than the date to
* compare to (or "now"), otherwise false.
*/
$P.isAfter = function(date) {
return this.compareTo(date || new Date()) === 1;
};
/**
* Determines if this date occurs before the date to compare to.
* @param {Date} Date object to compare. If no date to compare, new Date()
* ("now") is used.
* @return {Boolean} true if this date instance is less than the date to
* compare to (or "now").
*/
$P.isBefore = function(date) {
return (this.compareTo(date || new Date()) === -1);
};
/**
* Determines if the current Date instance occurs on the same Date as the supplied 'date'.
* If no 'date' to compare to is provided, the current Date instance is compared to 'today'.
* @param {Date} Date object to compare. If no date to compare, the current Date ("now") is used.
* @return {Boolean} true if this Date instance occurs on the same Day as the supplied 'date'.
*/
$P.isToday = $P.isSameDay = function(date) {
return this.clone().clearTime().equals((date || new Date()).clone().clearTime());
};
/**
* Adds the specified number of milliseconds to this instance.
* @param {Number} The number of milliseconds to add. The number can be
* positive or negative [Required]
* @return {Date} this
*/
$P.addMilliseconds = function(value) {
this.setMilliseconds(this.getMilliseconds() + value * 1);
return this;
};
/**
* Adds the specified number of seconds to this instance.
* @param {Number} The number of seconds to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addSeconds = function(value) {
return this.addMilliseconds(value * 1000);
};
/**
* Adds the specified number of seconds to this instance.
* @param {Number} The number of seconds to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addMinutes = function(value) {
return this.addMilliseconds(value * 60000); /* 60*1000 */
};
/**
* Adds the specified number of hours to this instance.
* @param {Number} The number of hours to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addHours = function(value) {
return this.addMilliseconds(value * 3600000); /* 60*60*1000 */
};
/**
* Adds the specified number of days to this instance.
* @param {Number} The number of days to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addDays = function(value) {
this.setDate(this.getDate() + value * 1);
return this;
};
/**
* Adds the specified number of weeks to this instance.
* @param {Number} The number of weeks to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addWeeks = function(value) {
return this.addDays(value * 7);
};
/**
* Adds the specified number of months to this instance.
* @param {Number} The number of months to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addMonths = function(value) {
var n = this.getDate();
this.setDate(1);
this.setMonth(this.getMonth() + value * 1);
this.setDate(Math.min(n, $D.getDaysInMonth(this.getFullYear(), this.getMonth())));
return this;
};
/**
* Adds the specified number of years to this instance.
* @param {Number} The number of years to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addYears = function(value) {
return this.addMonths(value * 12);
};
/**
* Adds (or subtracts) to the value of the years, months, weeks, days, hours,
* minutes, seconds, milliseconds of the date instance using given configuration
* object. Positive and Negative values allowed.
* Example
<pre><code>
Date.today().add( { days: 1, months: 1 } )
new Date().add( { years: -1 } )
</code></pre>
* @param {Object} Configuration object containing attributes (months, days, etc.)
* @return {Date} this
*/
$P.add = function(config) {
if (typeof config == "number") {
this._orient = config;
return this;
}
var x = config;
if (x.milliseconds)
this.addMilliseconds(x.milliseconds);
if (x.seconds)
this.addSeconds(x.seconds);
if (x.minutes)
this.addMinutes(x.minutes);
if (x.hours)
this.addHours(x.hours);
if (x.weeks)
this.addWeeks(x.weeks);
if (x.months)
this.addMonths(x.months);
if (x.years)
this.addYears(x.years);
if (x.days)
this.addDays(x.days);
return this;
};
var $y, $m, $d;
/**
* Get the week number. Week one (1) is the week which contains the first
* Thursday of the year. Monday is considered the first day of the week.
* This algorithm is a JavaScript port of the work presented by Claus
* Tøndering at http://www.tondering.dk/claus/cal/node8.html#SECTION00880000000000000000
* .getWeek() Algorithm Copyright (c) 2008 Claus Tondering.
* The .getWeek() function does NOT convert the date to UTC. The local datetime
* is used. Please use .getISOWeek() to get the week of the UTC converted date.
* @return {Number} 1 to 53
*/
$P.getWeek = function() {
var a, b, c, d, e, f, g, n, s, w;
$y = (!$y) ? this.getFullYear() : $y;
$m = (!$m) ? this.getMonth() + 1 : $m;
$d = (!$d) ? this.getDate() : $d;
if ($m <= 2) {
a = $y - 1;
b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
s = b - c;
e = 0;
f = $d - 1 + (31 * ($m - 1));
} else {
a = $y;
b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
s = b - c;
e = s + 1;
f = $d + ((153 * ($m - 3) + 2) / 5) + 58 + s;
}
g = (a + b) % 7;
d = (f + g - e) % 7;
n = (f + 3 - d) | 0;
if (n < 0) {
w = 53 - ((g - s) / 5 | 0);
} else if (n > 364 + s) {
w = 1;
} else {
w = (n / 7 | 0) + 1;
}
$y = $m = $d = null;
return w;
};
/**
* Get the ISO 8601 week number. Week one ("01") is the week which contains the
* first Thursday of the year. Monday is considered the first day of the week.
* The .getISOWeek() function does convert the date to it's UTC value.
* Please use .getWeek() to get the week of the local date.
* @return {String} "01" to "53"
*/
$P.getISOWeek = function() {
$y = this.getUTCFullYear();
$m = this.getUTCMonth() + 1;
$d = this.getUTCDate();
return p(this.getWeek());
};
/**
* Moves the date to Monday of the week set. Week one (1) is the week which
* contains the first Thursday of the year.
* @param {Number} A Number (1 to 53) that represents the week of the year.
* @return {Date} this
*/
$P.setWeek = function(n) {
return this.moveToDayOfWeek(1).addWeeks(n - this.getWeek());
};
// private
var validate = function(n, min, max, name) {
if (typeof n == "undefined")
return false;
else if (typeof n != "number")
throw new TypeError(n + " is not a Number.");
else if (n < min || n > max)
throw new RangeError(n + " is not a valid value for " + name + ".");
return true;
};
/**
* Validates the number is within an acceptable range for milliseconds [0-999].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateMillisecond = function(value) {
return validate(value, 0, 999, "millisecond");
};
/**
* Validates the number is within an acceptable range for seconds [0-59].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateSecond = function(value) {
return validate(value, 0, 59, "second");
};
/**
* Validates the number is within an acceptable range for minutes [0-59].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateMinute = function(value) {
return validate(value, 0, 59, "minute");
};
/**
* Validates the number is within an acceptable range for hours [0-23].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateHour = function(value) {
return validate(value, 0, 23, "hour");
};
/**
* Validates the number is within an acceptable range for the days in a month
* [0 - MaxDaysInMonth].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateDay = function(value, year, month) {
return validate(value, 1, $D.getDaysInMonth(year, month), "day");
};
/**
* Validates the number is within an acceptable range for months [0-11].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateMonth = function(value) {
return validate(value, 0, 11, "month");
};
/**
* Validates the number is within an acceptable range for years.
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateYear = function(value) {
return validate(value, 0, 9999, "year");
};
/**
* Set the value of year, month, day, hour, minute, second, millisecond of
* date instance using given configuration object.
* Example
<pre><code>
Date.today().set( { day: 20, month: 1 } )
new Date().set( { millisecond: 0 } )
</code></pre>
*
* @param {Object} Configuration object containing attributes (month, day, etc.)
* @return {Date} this
*/
$P.set = function(config) {
if ($D.validateMillisecond(config.millisecond))
this.addMilliseconds(config.millisecond - this.getMilliseconds());
if ($D.validateSecond(config.second))
this.addSeconds(config.second - this.getSeconds());
if ($D.validateMinute(config.minute))
this.addMinutes(config.minute - this.getMinutes());
if ($D.validateHour(config.hour))
this.addHours(config.hour - this.getHours());
if ($D.validateMonth(config.month))
this.addMonths(config.month - this.getMonth());
if ($D.validateYear(config.year))
this.addYears(config.year - this.getFullYear());
/* day has to go last because you can't validate the day without first knowing the month */
if ($D.validateDay(config.day, this.getFullYear(), this.getMonth()))
this.addDays(config.day - this.getDate());
if (config.timezone)
this.setTimezone(config.timezone);
if (config.timezoneOffset)
this.setTimezoneOffset(config.timezoneOffset);
if (config.week && validate(config.week, 0, 53, "week"))
this.setWeek(config.week);
return this;
};
/**
* Moves the date to the first day of the month.
* @return {Date} this
*/
$P.moveToFirstDayOfMonth = function() {
return this.set({ day: 1 });
};
/**
* Moves the date to the last day of the month.
* @return {Date} this
*/
$P.moveToLastDayOfMonth = function() {
return this.set({ day: $D.getDaysInMonth(this.getFullYear(), this.getMonth())});
};
/**
* Moves the date to the next n'th occurrence of the dayOfWeek starting from
* the beginning of the month. The number (-1) is a magic number and will return
* the last occurrence of the dayOfWeek in the month.
* @param {Number} The dayOfWeek to move to
* @param {Number} The n'th occurrence to move to. Use (-1) to return the
* last occurrence in the month
* @return {Date} this
*/
$P.moveToNthOccurrence = function(dayOfWeek, occurrence) {
var shift = 0;
if (occurrence > 0) {
shift = occurrence - 1;
}
else if (occurrence === -1) {
this.moveToLastDayOfMonth();
if (this.getDay() !== dayOfWeek)
this.moveToDayOfWeek(dayOfWeek, -1);
return this;
}
return this.moveToFirstDayOfMonth().addDays(-1)
.moveToDayOfWeek(dayOfWeek, +1).addWeeks(shift);
};
/**
* Move to the next or last dayOfWeek based on the orient value.
* @param {Number} The dayOfWeek to move to
* @param {Number} Forward (+1) or Back (-1). Defaults to +1. [Optional]
* @return {Date} this
*/
$P.moveToDayOfWeek = function(dayOfWeek, orient) {
var diff = (dayOfWeek - this.getDay() + 7 * (orient || +1)) % 7;
return this.addDays((diff === 0) ? diff += 7 * (orient || +1) : diff);
};
/**
* Move to the next or last month based on the orient value.
* @param {Number} The month to move to. 0 = January, 11 = December
* @param {Number} Forward (+1) or Back (-1). Defaults to +1. [Optional]
* @return {Date} this
*/
$P.moveToMonth = function(month, orient) {
var diff = (month - this.getMonth() + 12 * (orient || +1)) % 12;
return this.addMonths((diff === 0) ? diff += 12 * (orient || +1) : diff);
};
/**
* Get the Ordinal day (numeric day number) of the year, adjusted for leap year.
* @return {Number} 1 through 365 (366 in leap years)
*/
$P.getOrdinalNumber = function() {
return Math.ceil((this.clone().clearTime()
- new Date(this.getFullYear(), 0, 1)) / 86400000) + 1;
};
/**
* Get the time zone abbreviation of the current date.
* @return {String} The abbreviated time zone name (e.g. "EST")
*/
$P.getTimezone = function() {
return $D.getTimezoneAbbreviation(this.getUTCOffset());
};
$P.setTimezoneOffset = function(offset) {
var here = this.getTimezoneOffset(), there = Number(offset) * -6 / 10;
return this.addMinutes(there - here);
};
$P.setTimezone = function(offset) {
return this.setTimezoneOffset($D.getTimezoneOffset(offset));
};
/**
* Indicates whether Daylight Saving Time is observed in the current time zone.
* @return {Boolean} true|false
*/
$P.hasDaylightSavingTime = function() {
return (Date.today().set({month: 0, day: 1}).getTimezoneOffset()
!== Date.today().set({month: 6, day: 1}).getTimezoneOffset());
};
/**
* Indicates whether this Date instance is within the Daylight Saving Time
* range for the current time zone.
* @return {Boolean} true|false
*/
$P.isDaylightSavingTime = function() {
return Date.today().set({month: 0, day: 1}).getTimezoneOffset() != this.getTimezoneOffset();
};
/**
* Get the offset from UTC of the current date.
* @return {String} The 4-character offset string prefixed with + or - (e.g. "-0500")
*/
$P.getUTCOffset = function() {
var n = this.getTimezoneOffset() * -10 / 6, r;
if (n < 0) {
r = (n - 10000).toString();
return r.charAt(0) + r.substr(2);
}
else {
r = (n + 10000).toString();
return "+" + r.substr(1);
}
};
$P.getUTCTime = function() {
//Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
return Date.UTC(this.getUTCFullYear(), this.getUTCMonth(), this.getUTCDate(),
this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds(),
this.getUTCMilliseconds());
};
/**
* Returns the number of milliseconds between this date and date.
* @param {Date} Defaults to now
* @return {Number} The diff in milliseconds
*/
$P.getElapsed = function(date) {
return (date || new Date()) - this;
};
if (!$P.toISOString) {
/**
* Converts the current date instance into a string with an ISO 8601 format.
* The date is converted to it's UTC value.
* @return {String} ISO 8601 string of date
*/
$P.toISOString = function() {
// From http://www.json.org/json.js. Public Domain.
function f(n) {
return n < 10 ? '0' + n : n;
}
return '"' + this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z"';
};
}
// private
$P._toString = $P.toString;
/**
* Converts the value of the current Date object to its equivalent string representation.
* Format Specifiers
<pre>
CUSTOM DATE AND TIME FORMAT STRINGS
Format Description Example
------ --------------------------------------------------------------------------- -----------------------
s The seconds of the minute between 0-59. "0" to "59"
ss The seconds of the minute with leading zero if required. "00" to "59"
m The minute of the hour between 0-59. "0" or "59"
mm The minute of the hour with leading zero if required. "00" or "59"
h The hour of the day between 1-12. "1" to "12"
hh The hour of the day with leading zero if required. "01" to "12"
H The hour of the day between 0-23. "0" to "23"
HH The hour of the day with leading zero if required. "00" to "23"
d The day of the month between 1 and 31. "1" to "31"
dd The day of the month with leading zero if required. "01" to "31"
ddd Abbreviated day name. $C.abbreviatedDayNames. "Mon" to "Sun"
dddd The full day name. $C.dayNames. "Monday" to "Sunday"
M The month of the year between 1-12. "1" to "12"
MM The month of the year with leading zero if required. "01" to "12"
MMM Abbreviated month name. $C.abbreviatedMonthNames. "Jan" to "Dec"
MMMM The full month name. $C.monthNames. "January" to "December"
yy The year as a two-digit number. "99" or "08"
yyyy The full four digit year. "1999" or "2008"
t Displays the first character of the A.M./P.M. designator. "A" or "P"
$C.amDesignator or $C.pmDesignator
tt Displays the A.M./P.M. designator. "AM" or "PM"
$C.amDesignator or $C.pmDesignator
S The ordinal suffix ("st, "nd", "rd" or "th") of the current day. "st, "nd", "rd" or "th"
|| *Format* || *Description* || *Example* ||
|| d || The CultureInfo shortDate Format Pattern || "M/d/yyyy" ||
|| D || The CultureInfo longDate Format Pattern || "dddd, MMMM dd, yyyy" ||
|| F || The CultureInfo fullDateTime Format Pattern || "dddd, MMMM dd, yyyy h:mm:ss tt" ||
|| m || The CultureInfo monthDay Format Pattern || "MMMM dd" ||
|| r || The CultureInfo rfc1123 Format Pattern || "ddd, dd MMM yyyy HH:mm:ss GMT" ||
|| s || The CultureInfo sortableDateTime Format Pattern || "yyyy-MM-ddTHH:mm:ss" ||
|| t || The CultureInfo shortTime Format Pattern || "h:mm tt" ||
|| T || The CultureInfo longTime Format Pattern || "h:mm:ss tt" ||
|| u || The CultureInfo universalSortableDateTime Format Pattern || "yyyy-MM-dd HH:mm:ssZ" ||
|| y || The CultureInfo yearMonth Format Pattern || "MMMM, yyyy" ||
STANDARD DATE AND TIME FORMAT STRINGS
Format Description Example ("en-US")
------ --------------------------------------------------------------------------- -----------------------
d The CultureInfo shortDate Format Pattern "M/d/yyyy"
D The CultureInfo longDate Format Pattern "dddd, MMMM dd, yyyy"
F The CultureInfo fullDateTime Format Pattern "dddd, MMMM dd, yyyy h:mm:ss tt"
m The CultureInfo monthDay Format Pattern "MMMM dd"
r The CultureInfo rfc1123 Format Pattern "ddd, dd MMM yyyy HH:mm:ss GMT"
s The CultureInfo sortableDateTime Format Pattern "yyyy-MM-ddTHH:mm:ss"
t The CultureInfo shortTime Format Pattern "h:mm tt"
T The CultureInfo longTime Format Pattern "h:mm:ss tt"
u The CultureInfo universalSortableDateTime Format Pattern "yyyy-MM-dd HH:mm:ssZ"
y The CultureInfo yearMonth Format Pattern "MMMM, yyyy"
</pre>
* @param {String} A format string consisting of one or more format spcifiers [Optional].
* @return {String} A string representation of the current Date object.
*/
$P.toString = function(format) {
var x = this;
// Standard Date and Time Format Strings. Formats pulled from CultureInfo file and
// may vary by culture.
if (format && format.length == 1) {
var c = $C.formatPatterns;
x.t = x.toString;
switch (format) {
case "d":
return x.t(c.shortDate);
case "D":
return x.t(c.longDate);
case "F":
return x.t(c.fullDateTime);
case "m":
return x.t(c.monthDay);
case "r":
return x.t(c.rfc1123);
case "s":
return x.t(c.sortableDateTime);
case "t":
return x.t(c.shortTime);
case "T":
return x.t(c.longTime);
case "u":
return x.t(c.universalSortableDateTime);
case "y":
return x.t(c.yearMonth);
}
}
var ord = function (n) {
switch (n * 1) {
case 1:
case 21:
case 31:
return "st";
case 2:
case 22:
return "nd";
case 3:
case 23:
return "rd";
default:
return "th";
}
};
return format ? format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,
function (m) {
if (m.charAt(0) === "\\") {
return m.replace("\\", "");
}
x.h = x.getHours;
switch (m) {
case "hh":
return p(x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12));
case "h":
return x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12);
case "HH":
return p(x.h());
case "H":
return x.h();
case "mm":
return p(x.getMinutes());
case "m":
return x.getMinutes();
case "ss":
return p(x.getSeconds());
case "s":
return x.getSeconds();
case "yyyy":
return p(x.getFullYear(), 4);
case "yy":
return p(x.getFullYear());
case "dddd":
return $C.dayNames[x.getDay()];
case "ddd":
return $C.abbreviatedDayNames[x.getDay()];
case "dd":
return p(x.getDate());
case "d":
return x.getDate();
case "MMMM":
return $C.monthNames[x.getMonth()];
case "MMM":
return $C.abbreviatedMonthNames[x.getMonth()];
case "MM":
return p((x.getMonth() + 1));
case "M":
return x.getMonth() + 1;
case "t":
return x.h() < 12 ? $C.amDesignator.substring(0, 1) : $C.pmDesignator.substring(0, 1);
case "tt":
return x.h() < 12 ? $C.amDesignator : $C.pmDesignator;
case "S":
return ord(x.getDate());
default:
return m;
}
}
) : this._toString();
};
}());
/**
* @class apf.layout
*
* Takes care of the spatial order of elements within the display area
* of the browser. Layouts can be saved to XML and loaded again. Window
* elements are dockable, which means the user can change the layout as s/he
* wishes. The state of the layout can be saved as XML at any time.
*
* #### Example
*
* This example shows five windows which have a layout defined in layout.xml.
*
* ```xml
* <a:appsettings layout="[mdlLayouts::layout[1]]" />
* <a:model id="mdlLayouts" src="layout.xml" />
*
* <a:window title="Main Window" id="b1" />
* <a:window title="Tree Window" id="b2" />
* <a:window title="Window of Oppertunity" id="b3" />
* <a:window title="Small window" id="b4" />
* <a:window title="Some Window" id="b5" />
* ```
*
* This is the layout file containing two layouts (_layout.xml_):
*
* ```xml
* <layouts>
* <layout name="Layout 1" margin="2,2,2,2">
* <vbox edge="splitter">
* <node name="b1" edge="2"/>
* <hbox edge="2">
* <vbox weight="1">
* <node name="b2"/>
* <node name="b3"/>
* </vbox>
* <node name="b4" weight="1" />
* </hbox>
* <node name="b5" height="20" />
* </vbox>
* </layout>
*
* <layout name="Layout 2">
* <vbox edge="splitter">
* <node name="b1" edge="2" />
* <node name="b2" height="100" />
* <hbox edge="2">
* <node name="b3" width="20%" />
* <node name="b4" width="100" />
* </hbox>
* <node name="b5" height="20" />
* </vbox>
* </layout>
* </layouts>
* ```
*
* By binding on the _layout.xml_ you can easily create a layout manager.
*
* ```xml
* <a:list id="lstLayouts"
* model = "mdlLayouts"
* allowdeselect = "false"
* onafterselect = "
* if (!this.selected || apf.layout.isLoadedXml(this.selected))
* return;
*
* apf.layout.saveXml();
* apf.layout.loadXml(this.selected);
* "
* onbeforeremove = "return confirm('Do you want to delete this layout?')">
* <a:bindings>
* <a:caption match="[@name]" />
* <a:icon value="layout.png" />
* <a:each match="[layout]" />
* </a:bindings>
* <a:actions>
* <a:rename match="[.]" />
* <a:remove match="[.]" />
* </a:actions>
* </a:list>
* <a:button
* onclick = "
* if (!lstLayouts.selected)
* return;
*
* var newLayout = apf.layout.getXml(document.body);
* newLayout.setAttribute('name', 'New');
* apf.xmldb.appendChild(lstLayouts.selected.parentNode, newLayout);
* lstLayouts.select(newLayout, null, null, null, null, true);
* apf.layout.loadXml(newLayout);
* lstLayouts.startRename();
* ">
* Add Layout
* </a:button>
* ```
*
* @default_private
*/
// @todo a __WITH_DOM_REPARENTING should be added which can remove many of the functions of this element.
apf.layout = {
compile: function(oHtml) {
var l = this.layouts[oHtml.getAttribute("id")];
if (!l) return false;
var root = l.root.copy();//is there a point to copying?
l.layout.compile(root);
l.layout.reset();
},
removeAll: function(aData) {
aData.children.length = null
var htmlId = this.getHtmlId(aData.pHtml);
if (!this.rules[htmlId])
delete this.qlist[htmlId];
},
timer: null,
qlist: {},
dlist: [],
$hasQueue: false,
queue: function(oHtml, obj, compile, q) {
if (!q) {
this.$hasQueue = true;
q = this.qlist;
}
var id;
if (!(id = this.getHtmlId(oHtml)))
id = apf.setUniqueHtmlId(oHtml);
if (q[id]) {
if (obj)
q[id][2].push(obj);
if (compile)
q[id][1] = compile;
return;
}
q[id] = [oHtml, compile, [obj]];
if (!this.timer)
this.timer = apf.setZeroTimeout(function(){
apf.layout.processQueue();
});
},
processQueue: function(){
var i, id, l, qItem, list;
for (i = 0; i < this.dlist.length; i++) {
if (this.dlist[i].hidden)
this.dlist[i].hide();
else
this.dlist[i].show();
}
do {
var newq = {};
var qlist = this.qlist;
this.qlist = {};
this.$hasQueue = false;
for (id in qlist) {
qItem = qlist[id];
if (qItem[1])
apf.layout.compileAlignment(qItem[1]);
list = qItem[2];
for (i = 0, l = list.length; i < l; i++) {
if (list[i]) {
if (list[i].$amlDestroyed)
continue;
//if (list[i].$amlLoaded)
list[i].$updateLayout();
/*else
this.queue(qItem[0], list[i], null, newq);*/
}
}
apf.layout.activateRules(qItem[0]);
}
} while (this.$hasQueue);
if (apf.hasSingleRszEvent)
apf.layout.forceResize();
this.dlist = [];
apf.setZeroTimeout.clearTimeout(this.timer);
this.timer = null;
},
rules: {},
onresize: {},
getHtmlId: function(oHtml) {
return oHtml.getAttribute ? oHtml.getAttribute("id") : 1;
},
/**
* Adds layout rules to the resize event of the browser. Use this instead
* of `"onresize"` events to add rules that specify determine the layout.
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {String} id The identifier for the rules within the resize function of this element. Use this to easily update or remove the rules added.
* @param {String} rules The JavaScript code that is executed when the html element resizes.
* @param {Boolean} [overwrite] Whether the rules are added to the resize function or overwrite the previous set rules with the specified id.
*/
setRules: function(oHtml, id, rules, overwrite) {
if (!this.getHtmlId(oHtml))
apf.setUniqueHtmlId(oHtml);
if (!this.rules[this.getHtmlId(oHtml)])
this.rules[this.getHtmlId(oHtml)] = {};
var ruleset = this.rules[this.getHtmlId(oHtml)][id];
if (!overwrite && ruleset) {
this.rules[this.getHtmlId(oHtml)][id] = rules + "\n" + ruleset;
}
else
this.rules[this.getHtmlId(oHtml)][id] = rules;
},
/**
* Retrieves the rules set for the `"resize"` event of an HTML element specified by an identifier
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {String} id The identifier for the rules within the resize function of this element.
*/
getRules: function(oHtml, id) {
return id
? this.rules[this.getHtmlId(oHtml)][id]
: this.rules[this.getHtmlId(oHtml)];
},
/**
* Removes the rules set for the `"resize"` event of an html element specified by an identifier
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {String} id The identifier for the rules within the resize function of this element.
*/
removeRule: function(oHtml, id) {
var htmlId = this.getHtmlId(oHtml);
if (!this.rules[htmlId])
return;
var ret = this.rules[htmlId][id] || false;
delete this.rules[htmlId][id];
var prop;
for (prop in this.rules[htmlId]) {
}
if (!prop)
delete this.rules[htmlId]
if (apf.hasSingleRszEvent) {
if (this.onresize[htmlId])
this.onresize[htmlId] = null;
else {
var p = oHtml.parentNode;
while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) {
p = p.parentNode;
}
if (p && p.nodeType == 1) {
var x = this.onresize[p.getAttribute("id")];
if (x.children)
delete x.children[htmlId]
}
}
}
return ret;
},
/**
* Activates the rules set for an HTML element
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {Boolean} [no_exec]
*/
activateRules: function(oHtml, no_exec) {
if (!oHtml) { //!apf.hasSingleRszEvent &&
var prop, obj;
for (prop in this.rules) {
obj = document.getElementById(prop);
if (!obj || obj.onresize) // || this.onresize[prop]
continue;
this.activateRules(obj);
}
if (apf.hasSingleRszEvent && apf.layout.$onresize)
apf.layout.$onresize();
return;
}
var rsz, id, rule, rules, strRules = [];
if (!apf.hasSingleRszEvent) {
rules = this.rules[this.getHtmlId(oHtml)];
if (!rules) {
oHtml.onresize = null;
return false;
}
for (id in rules) { //might need optimization using join()
if (typeof rules[id] != "string")
continue;
strRules.push(rules[id]);
}
//apf.console.info(strRules.join("\n"));
rsz = apf.needsCssPx
? new Function(strRules.join("\n"))
: new Function(strRules.join("\n").replace(/ \+ 'px'|try\{\}catch\(e\)\{\}\n/g,""))
oHtml.onresize = rsz;
if (!no_exec)
rsz();
}
else {
var htmlId = this.getHtmlId(oHtml);
rules = this.rules[htmlId];
if (!rules) {
//@todo keep .children
//delete this.onresize[htmlId];
return false;
}
for (id in rules) { //might need optimization using join()
if (typeof rules[id] != "string")
continue;
strRules.push(rules[id]);
}
var p = oHtml.parentNode;
while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) {
p = p.parentNode;
}
var f = new Function(strRules.join("\n"));//.replace(/try\{/g, "").replace(/}catch\(e\)\{\s*\}/g, "\n")
if (this.onresize[htmlId])
f.children = this.onresize[htmlId].children;
if (p && p.nodeType == 1) {
var x = this.onresize[p.getAttribute("id")];
this.onresize[htmlId] = (x.children || (x.children = {}))[htmlId] = f;
}
else {
this.onresize[htmlId] = f;
}
if (!no_exec)
f();
if (!apf.layout.$onresize) {
var rsz = function(f) {
//@todo fix this
try{
var c = [];
for (var name in f)
if (f[name])
c.unshift(f[name]);
for (var i = 0; i < c.length; i++){
c[i]();
if (c[i].children) {
rsz(c[i].children);
}
}
}
catch (e) {
}
}
apf.addListener(window, "resize", apf.layout.$onresize = function(){
if (apf.config.resize !== false) {
rsz(apf.layout.onresize);
}
});
}
}
},
/**
* Forces calling the resize rules for an HTML element
* @param {HTMLElement} oHtml The element for which the rules are executed.
*/
forceResize: function(oHtml, force) {
if (!force) return;
if (apf.hasSingleRszEvent)
return apf.layout.$onresize && apf.layout.$onresize();
/* @todo this should be done recursive, old way for now
apf.hasSingleRszEvent
? this.onresize[this.getHtmlId(oHtml)]
:
*/
var rsz = oHtml.onresize;
if (rsz)
rsz();
var els = oHtml.getElementsByTagName("*");
for (var i = 0, l = els.length; i < l; i++) {
if (els[i] && els[i].onresize)
els[i].onresize();
}
},
paused: {},
/**
* Temporarily disables the resize rules for the HTML element.
* @param {HTMLElement} oHtml The element for which the rules are paused.
* @param {Function} func The resize code that is used temporarily for resize of the HTML element.
*/
pause: function(oHtml, replaceFunc) {
if (apf.hasSingleRszEvent) {
var htmlId = this.getHtmlId(oHtml);
this.paused[htmlId] = this.onresize[htmlId] || true;
if (replaceFunc) {
this.onresize[htmlId] = replaceFunc;
this.onresize[htmlId].children = this.paused[htmlId].children;
replaceFunc();
}
else
delete this.onresize[htmlId];
}
else {
this.paused[this.getHtmlId(oHtml)] = oHtml.onresize || true;
if (replaceFunc) {
oHtml.onresize = replaceFunc;
replaceFunc();
}
else
oHtml.onresize = null;
}
},
/**
* Enables paused resize rules for the HTML element
* @param {HTMLElement} oHtml The element for which the rules were paused.
*/
play: function(oHtml) {
if (!this.paused[this.getHtmlId(oHtml)])
return;
if (apf.hasSingleRszEvent) {
var htmlId = this.getHtmlId(oHtml);
var oldFunc = this.paused[htmlId];
if (typeof oldFunc == "function") {
this.onresize[htmlId] = oldFunc;
//oldFunc();
}
else
delete this.onresize[htmlId];
if (apf.layout.$onresize)
apf.layout.$onresize();
this.paused[this.getHtmlId(oHtml)] = null;
}
else {
var oldFunc = this.paused[this.getHtmlId(oHtml)];
if (typeof oldFunc == "function") {
oHtml.onresize = oldFunc;
oldFunc();
}
else
oHtml.onresize = null;
this.paused[this.getHtmlId(oHtml)] = null;
}
}
};
/**
* @private
*/
apf.getWindowWidth = function(){
return 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]+</g;
/**
* Clear XML document cache periodically when no model is referencing it
*/
this.garbageCollect = function(){
var xmlNode, cache = apf.xmldb.$xmlDocLut, docId, model;
for (var i = 0, l = cache.length; i < l; i++) {
xmlNode = cache[i];
if (!xmlNode || xmlNode.nodeFunc)
continue;
docId = i;//xmlNode.getAttribute(apf.xmldb.xmlDocTag);
model = apf.nameserver.get("model", docId);
if (!model || model.data != xmlNode) {
cache[i] = null;
}
}
};
this.$gcInterval = window.setInterval
? setInterval(function(){
_self.garbageCollect();
}, 60000)
: null;
/*
* @private
*/
this.getElementById = function(id, doc) {
if (!doc)
doc = this.$xmlDocLut[id.split("\|")[0]];
if (!doc)
return false;
return doc.selectSingleNode("descendant-or-self::node()[@"
+ this.xmlIdTag + "='" + id + "']") || doc.parentNode && doc.parentNode.selectSingleNode("descendant-or-self::node()[@"
+ this.xmlIdTag + "='" + id + "']");
};
/*
* @private
*/
this.getNode = function(htmlNode) {
if (!htmlNode || !htmlNode.getAttribute(this.htmlIdTag))
return false;
return this.getElementById(htmlNode.getAttribute(this.htmlIdTag)
.split("\|", 2).join("|"));
};
/*
* @private
*/
this.getNodeById = function(id, doc) {
var q = id.split("\|");
q.pop();
return this.getElementById(q.join("|"), doc);//id.split("\|", 2).join("|")
};
/*
* @private
*/
this.getDocumentById = function(id) {
return this.$xmlDocLut[id];
};
/*
* @private
*/
this.getDocument = function(node) {
return this.$xmlDocLut[node.getAttribute(this.xmlIdTag).split("\|")[0]];
};
/**
* @private
*/
this.getID = function(xmlNode, o) {
return xmlNode.getAttribute(this.xmlIdTag) + "|" + o.$uniqueId;
};
/*
* @private
*/
this.getElement = function(parent, nr) {
var nodes = parent.childNodes;
for (var j = 0, i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType != 1)
continue;
if (j++ == nr)
return nodes[i];
}
};
/**
* Sets the model of an element.
*
* @param {apf.model} The model to be set
*
*/
this.setModel = function(model) {
apf.nameserver.register("model", model.data.ownerDocument
.documentElement.getAttribute(this.xmlDocTag), model);
};
/**
* Finds the model of an element.
*
* @param {XMLNode} xmlNode The {@link term.datanode data node} to find its model.
*
*/
this.findModel = function(xmlNode) {
return apf.nameserver.get("model", xmlNode.ownerDocument
.documentElement.getAttribute(this.xmlDocTag));
};
/*
* @private
*/
this.getXmlId = function(xmlNode) {
return xmlNode.getAttribute(this.xmlIdTag) ||
this.nodeConnect(apf.xmldb.getXmlDocId(xmlNode), xmlNode);
};
/**
* Gets the HTML representation of an XML node for a certain element.
*
* @param {XMLNode} xmlNode The {@link term.datanode data node} which is represented by the HTML element.
* @param {apf.AmlNode} oComp The element that has created the representation.
* @return {DOMNode} The HTML node representing the XML node.
*/
this.getHtmlNode = function(xmlNode, oComp) {
if (xmlNode && xmlNode.nodeType == 1 && xmlNode.getAttribute(this.xmlIdTag)) {
return oComp.$findHtmlNode(xmlNode.getAttribute(this.xmlIdTag)
+ "|" + oComp.$uniqueId);
}
return null;
};
/**
* Finds the HTML representation of an XML node for a certain element.
*
* @param {XMLNode} xmlNode The {@link term.datanode data node} which is represented by the HTML element.
* @param {apf.AmlNode} oComp The element that has created the representation.
* @return {DOMNode} The HTML node representing the XML node.
*/
this.findHtmlNode = function(xmlNode, oComp) {
do {
if (xmlNode.nodeType == 1 && xmlNode.getAttribute(this.xmlIdTag)) {
return oComp.$findHtmlNode(xmlNode.getAttribute(this.xmlIdTag)
+ "|" + oComp.$uniqueId);
}
if (xmlNode == oComp.xmlRoot)
return null;
xmlNode = xmlNode.parentNode;
}
while (xmlNode && xmlNode.nodeType != 9)
return null;
};
/**
* Finds the {@link term.datanode data node} that is represented by the HTML node.
*
* @param {DOMNode} htmlNode The HTML node representing the an XML node.
* @return {XMLNode} The {@link term.datanode data node} for which the HTML node is its representation.
*/
this.findXmlNode = function(htmlNode) {
if (!htmlNode)
return false;
var id;
while (htmlNode && htmlNode.nodeType == 1 && (
htmlNode.tagName.toLowerCase() != "body" && !(id = htmlNode.getAttribute(this.htmlIdTag))
|| (id || (id = htmlNode.getAttribute(this.htmlIdTag))) && id.match(/^q/)
)) {
if (htmlNode.host && htmlNode.host.$ext == htmlNode)
return htmlNode.host.xmlRoot;
htmlNode = htmlNode.parentNode;
}
if (!htmlNode || htmlNode.nodeType != 1)
return false;
if (htmlNode.tagName.toLowerCase() == "body")
return false;
return this.getNode(htmlNode);
};
this.getXml = apf.getXml;
/*
* @private
*/
this.nodeConnect = function(documentId, xmlNode, htmlNode, o) {
if (!this.$nodeCount[documentId])
this.$nodeCount[documentId] = 0;
var xmlId;
xmlId = xmlNode.getAttribute(this.xmlIdTag)
|| xmlNode.setAttribute(this.xmlIdTag, (xmlId = documentId
+ "|" + ++this.$nodeCount[documentId])) || xmlId;
if (!o)
return xmlId;
var htmlId = xmlId + "|" + o.$uniqueId;
if (htmlNode)
htmlNode.setAttribute(this.htmlIdTag, htmlId);
return htmlId;
};
this.$listeners = [null];
// make sure that "0" is never a listener index
// @todo this is cleanup hell! Listeners should be completely rearchitected
/*
* @private
*
*/
this.addNodeListener = function(xmlNode, o, uId) {
var id, listen = String(xmlNode.getAttribute(this.xmlListenTag) || "");
//id || (id = String(o.$uniqueId));
if (!uId) uId = String(o.$uniqueId);
if (uId.charAt(0) == "p") {
var sUId = uId.split("|");
id = this.$listeners.push(function(args) {
//@todo apf3.0 should this be exactly like in class.js?
//@todo optimize this to check the async flag: parsed[3] & 4
var amlNode = apf.all[sUId[1]]; //It's possible the aml node dissapeared in this loop.
if (amlNode) {
var model = apf.all[sUId[3]];
if (!model)
return;
if (model.$propBinds[sUId[1]][sUId[2]]) {
if (!apf.isChildOf(model.data, xmlNode, true))
return false;
var xpath = model.$propBinds[sUId[1]][sUId[2]].listen; //root
var node = xpath
? apf.queryNode(model.data, xpath)
: xmlNode;
}
if (node)
amlNode.$execProperty(sUId[2], node, args[3]);
}
}) - 1;
this.$listeners[uId] = id;
}
else {
//@todo apf3 potential cleanup problem
id = "e" + uId;
if (!this.$listeners[id]) {
this.$listeners[id] = function(args) {
var amlNode = apf.all[uId];
if (amlNode)
amlNode.$xmlUpdate.apply(amlNode, args);
};
}
}
if (!listen || listen.indexOf(";" + id + ";") == -1)
xmlNode.setAttribute(this.xmlListenTag, (listen ? listen + id : ";" + id) + ";");
return xmlNode;
};
this.addListener = function(xmlNode, callback) {
var listen = String(xmlNode.getAttribute(this.xmlListenTag) || "");
var id = this.$listeners.push(callback) - 1;
(callback.ids || (callback.ids = [])).push(id);
if (!listen || listen.indexOf(";" + id + ";") == -1)
xmlNode.setAttribute(this.xmlListenTag, (listen ? listen + id : ";" + id) + ";");
};
this.removeListener = function(xmlNode, callback) {
var listen = xmlNode.getAttribute(this.xmlListenTag);
var nodes = (listen ? listen.split(";") : []);
var i, lut = {}
for (i = 0; i < nodes.length; i++) {
lut[nodes[i]] = 1;
}
var ids = callback.ids;
for (i = ids.length; i >= 0; i--) {
if (lut[ids[i]]) {
delete this.$listeners[ids[i]];
delete lut[ids[1]];
}
}
xmlNode.setAttribute(this.xmlListenTag, Object.keys(lut).join(";"));// + ";"
}
/*
* @todo Use this function when an element really unbinds from a
* piece of data and does not uses it for caching
* @private
*/
this.removeNodeListener = function(xmlNode, o, id) {
var listen = xmlNode.getAttribute(this.xmlListenTag);
var nodes = (listen ? listen.split(";") : []);
if (id && id.charAt(0) == "p") {
id = this.$listeners[id];
delete this.$listeners[id];
}
else {
id = "e" + o.$uniqueId;
}
for (var newnodes = [], i = 0; i < nodes.length; i++) {
if (nodes[i] != id)
newnodes.push(nodes[i]);
}
xmlNode.setAttribute(this.xmlListenTag, newnodes.join(";"));// + ";"
return xmlNode;
};
/**
* Sets the value of a text node. If the node doesn't exist, it is created.
*
* Changes are propagated to the databound elements listening for changes
* on the data changed.
*
* @param {XMLElement} pNode The parent of the text node.
* @param {String} value The value of the text node.
* @param {String} [xpath] The xpath statement which selects the text node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.setTextNode =
apf.setTextNode = function(pNode, value, xpath, undoObj, range) {
var tNode;
if (xpath) {
tNode = pNode.selectSingleNode(xpath);
if (!tNode)
return;
pNode = tNode.nodeType == 1 ? tNode : null;
}
if (pNode.nodeType != 1)
tNode = pNode;
else if (pNode || !tNode) {
tNode = pNode.selectSingleNode("text()");
if (!tNode)
tNode = pNode.appendChild(pNode.ownerDocument.createTextNode(""));//createCDATASection
}
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.extra.oldValue = tNode.nodeValue;
undoObj.$filled = true;
}
//Apply Changes
if (range) { //@todo apf3.0 range
undoObj.extra.range = range;
}
else {
tNode.nodeValue = value;
if (tNode.$regbase)
tNode.$setValue(value);
}
this.applyChanges("text", tNode.parentNode, undoObj);
this.applyRDB(["setTextNode", pNode, value, xpath], undoObj || {xmlNode: pNode}); //@todo apf3.0 for range support
};
/**
* Sets an attribute on a node. Changes are propagated to the databound
* elements listening for changes on the data changed.
*
* @param {XMLElement} xmlNode The XML node to set the attribute on.
* @param {String} name The name of the attribute.
* @param {String} value The value of the attribute.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.setAttribute =
apf.setAttribute = function(xmlNode, name, value, xpath, undoObj, range) {
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.name = name;
undoObj.$filled = true;
}
//Apply Changes
if (range) { //@todo apf3.0 range
undoObj.extra.range = range;
}
else
(xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).setAttribute(name, value);
this.applyChanges("attribute", xmlNode, undoObj);
this.applyRDB(["setAttribute", xmlNode, name, value, xpath], undoObj || {xmlNode: xmlNode}); //@todo apf3.0 for range support
};
/**
* Removes an attribute of an XML node. Changes are propagated to the
* databound elements listening for changes on the data changed.
*
* @param {XMLElement} xmlNode The XML node to delete the attribute from
* @param {String} name The name of the attribute.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.removeAttribute =
apf.removeAttribute = function(xmlNode, name, xpath, undoObj) {
//if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.name = name;
undoObj.$filled = true;
}
//Apply Changes
(xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).removeAttribute(name);
this.applyChanges("attribute", xmlNode, undoObj);
this.applyRDB(["removeAttribute", xmlNode, name, xpath], undoObj || {xmlNode: xmlNode});
};
/**
* Replace one node with another. Changes are propagated to the
* databound elements listening for changes on the data changed.
*
* @param {XMLElement} oldNode The XML node to remove.
* @param {XMLElement} newNode The XML node to set.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.replaceNode =
apf.replaceNode = function(newNode, oldNode, xpath, undoObj) {
//if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;
//Apply Changes
if (xpath)
oldNode = oldNode.selectSingleNode(xpath);
// @todo: only do this once! - should store on the undo object
if (oldNode.ownerDocument.importNode && newNode.ownerDocument != oldNode.ownerDocument) {
var oldNodeS = newNode;
newNode = oldNode.ownerDocument.importNode(newNode, true); //Safari issue not auto importing nodes
if (oldNodeS.parentNode)
oldNodeS.parentNode.removeChild(oldNodeS);
}
this.applyRDB(["replaceNode", oldNode, this.cleanXml(newNode.xml), xpath], undoObj || {xmlNode: oldNode});
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
undoObj.oldNode = oldNode;
undoObj.xmlNode = newNode;
}
this.cleanNode(newNode);
var parentNode = oldNode.parentNode;
if (!parentNode)
return;
parentNode.replaceChild(newNode, oldNode);
this.copyConnections(oldNode, newNode);
this.applyChanges("replacenode", newNode, undoObj);
return newNode;
};
/**
* Creates a new element under a parent XML node. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} pNode The parent XML node to add the new element to.
* @param {String} tagName The tagName of the {@link term.datanode data node} to add.
* @param {Array} attr list of the attributes to set. Each item is another array with the name and value.
* @param {XMLElement} beforeNode The XML node which indicates the insertion point.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.addChildNode =
apf.addChildNode = function(pNode, tagName, attr, beforeNode, undoObj) {
//Create New Node
var xmlNode = pNode.insertBefore(pNode.ownerDocument
.createElement(tagName), beforeNode);
//Set Attributes
for (var i = 0; i < attr.length; i++)
xmlNode.setAttribute(attr[i][0], attr[i][1]);
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.extra.addedNode = xmlNode;
undoObj.$filled = true;
}
this.applyChanges("add", xmlNode, undoObj);
this.applyRDB(["addChildNode", pNode, tagName, attr, beforeNode], undoObj || {xmlNode: pNode});
return xmlNode;
};
/**
* Appends an XML node to a parent. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} pNode The parent XML node to add the element to.
* @param {XMLElement} xmlNode The XML node to insert.
* @param {XMLElement} beforeNode The XML node which indicates the insertion point.
* @param {Boolean} unique Specifies whether the parent can only contain one element with a certain tag name.
* @param {String} [xpath] The xpath statement to select the parent node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.appendChild =
apf.appendChild = function(pNode, xmlNode, beforeNode, unique, xpath, undoObj) {
if (pNode == xmlNode.parentNode) //Shouldn't this be the same document?
return apf.xmldb.moveNode(pNode, xmlNode, beforeNode, null, xpath, undoObj);
if (unique && pNode.selectSingleNode(xmlNode.tagName))
return false;
// @todo: only do this once! - should store on the undo object
if (pNode.ownerDocument.importNode && pNode.ownerDocument != xmlNode.ownerDocument) {
var oldNode = xmlNode;
xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes
if (oldNode.parentNode)
oldNode.parentNode.removeChild(oldNode);
}
this.applyRDB(["appendChild", pNode, this.cleanXml(xmlNode.xml), beforeNode, unique, xpath], undoObj || {xmlNode: pNode});
//Add xmlNode to parent pNode or one selected by xpath statement
if (xpath) {
var addedNodes = [];
pNode = apf.createNodeFromXpath(pNode, xpath, addedNodes);
if (addedNodes.length) {
pNode.appendChild(xmlNode);
while (addedNodes.length) {
if (pNode == addedNodes.pop() && addedNodes.length)
pNode = pNode.parentNode;
}
}
}
else if (xmlNode.parentNode)
this.removeNode(xmlNode);
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
this.cleanNode(xmlNode);
}
else
this.cleanNode(xmlNode);
pNode.insertBefore(xmlNode, beforeNode);
//detect if xmlNode should be removed somewhere else
//- [17-2-2004] changed pNode (2nd arg applychange) into xmlNode
this.applyChanges("add", xmlNode, undoObj);
return xmlNode;
};
/**
* Moves an XML node to a parent node. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} pNode The new parent XML node of the node.
* @param {XMLElement} xmlNode The XML node to move.
* @param {XMLElement} beforeNode The XML node which indicates the insertion point.
* @param {String} [xpath] The xpath statement to select the parent node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.moveNode =
apf.moveNode = function(pNode, xmlNode, beforeNode, xpath, undoObj) {
//Action Tracker Support
if (!undoObj)
undoObj = {extra:{}};
undoObj.extra.oldParent = xmlNode.parentNode;
undoObj.extra.beforeNode = xmlNode.nextSibling;
undoObj.extra.parent = (xpath ? pNode.selectSingleNode(xpath) : pNode);
this.applyChanges("move-away", xmlNode, undoObj);
//Set new id if the node change document (for safari this should be fixed)
//@todo I don't get this if...
/*if (!apf.isWebkit
&& xmlNode.getAttribute(this.xmlIdTag)
&& apf.xmldb.getXmlDocId(xmlNode) != apf.xmldb.getXmlDocId(pNode)) {
xmlNode.removeAttribute(this.xmlIdTag));
this.nodeConnect(apf.xmldb.getXmlDocId(pNode), xmlNode);
}*/
// @todo: only do this once! - should store on the undo object
if (pNode.ownerDocument.importNode && pNode.ownerDocument != xmlNode.ownerDocument) {
var oldNode = xmlNode;
xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes
if (oldNode.parentNode)
oldNode.parentNode.removeChild(oldNode);
}
this.applyRDB(["moveNode", pNode, xmlNode, beforeNode, xpath], undoObj || {xmlNode: pNode}); //note: important that transport of rdb is async
undoObj.extra.parent.insertBefore(xmlNode, beforeNode);
this.applyChanges("move", xmlNode, undoObj);
};
/**
* Removes an XML node from its parent. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} xmlNode The XML node to remove from the dom tree.
* @param {String} [xpath] The xpath statement to select the parent node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.removeNode =
apf.removeNode = function(xmlNode, xpath, undoObj) {
if (xpath)
xmlNode = xmlNode.selectSingleNode(xpath);
//ActionTracker Support
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
undoObj.extra.parent = xmlNode.parentNode;
undoObj.extra.removedNode = xmlNode;
undoObj.extra.beforeNode = xmlNode.nextSibling;
}
this.applyRDB(["removeNode", xmlNode, xpath], undoObj || {xmlNode: xmlNode}); //note: important that transport of rdb is async
//Apply Changes
this.applyChanges("remove", xmlNode, undoObj);
var p = xmlNode.parentNode;
if (!p)
return;
p.removeChild(xmlNode);
this.applyChanges("redo-remove", xmlNode, null, p);//undoObj
//@todo clean xmlNode after removal??
};
/**
* Removes a list of XML nodes from their parent. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {Array} xmlNodeList A list of XML nodes to remove.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.removeNodeList =
apf.removeNodeList = function(xmlNodeList, undoObj) {
this.applyRDB(["removeNodeList", xmlNodeList, null], undoObj || {xmlNode: p});
//if(xpath) xmlNode = xmlNode.selectSingleNode(xpath);
for (var rData = [], i = 0; i < xmlNodeList.length; i++) { //This can be optimized by looping nearer to xmlUpdate
//ActionTracker Support
if (undoObj) {
rData.push({
pNode: xmlNodeList[i].parentNode,
removedNode: xmlNodeList[i],
beforeNode: xmlNodeList[i].nextSibling
});
}
//Apply Changes
this.applyChanges("remove", xmlNodeList[i], undoObj);
var p = xmlNodeList[i].parentNode;
p.removeChild(xmlNodeList[i]);
this.applyChanges("redo-remove", xmlNodeList[i], null, p);//undoObj
}
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
undoObj.extra.removeList = rData;
}
};
/*
* Looks for this.$listeners and executes their $xmlUpdate methods.
* @private
*/
var notifyQueue = {}, notifyTimer;
this.$hasQueue = false;
this.applyChanges = function(action, xmlNode, undoObj, nextloop) {
if (undoObj && undoObj.$dontapply) return;
if (undoObj && !undoObj.xmlNode) //@todo are we sure about this?
undoObj.xmlNode = xmlNode;
//Set Variables
var oParent = nextloop,
loopNode = (xmlNode.nodeType == 1 ? xmlNode : xmlNode.parentNode);
//var xmlId = xmlNode.getAttribute(this.xmlIdTag);
if (!this.delayUpdate && "|remove|move-away|".indexOf("|" + action + "|") > -1)
this.notifyQueued(); //empty queue
var listen, uId, uIds, i, j, hash, info, amlNode, runTimer, found, done = {};
while (loopNode && loopNode.nodeType == 1) {
//Get List of Node this.$listeners ID's
listen = loopNode.getAttribute(this.xmlListenTag);
if (listen) {
uIds = listen.split(";");
for (i = 0; i < uIds.length; i++) {
uId = uIds[i];
if (!uId || done[uId]) continue;
done[uId] = true;
//Property support
/*if (uId.charAt(0) == "p") {
uId = uId.split("|");
//@todo apf3.0 should this be exactly like in class.js?
//@todo optimize this to check the async flag: parsed[3] & 4
amlNode = apf.all[uId[1]]; //It's possible the aml node dissapeared in this loop.
if (amlNode) {
var model = apf.all[uId[3]];
var xpath = model.$propBinds[uId[1]][uId[2]].root;
amlNode.$execProperty(uId[2], xpath
? model.data.selectSingleNode(xpath)
: model.data);
}
continue;
}*/
hash = notifyQueue[uId];
if (!hash)
notifyQueue[uId] = hash = [];
// Filtering
if (!apf.isO3 && "|update|attribute|text|".indexOf("|" + action + "|") > -1) {
found = false;
for (j = 0; j < hash.length; j++) {
if (hash[j] && xmlNode == hash[j][1]
&& "|update|attribute|text|"
.indexOf("|" + hash[j][0] + "|") > -1) {
hash[j] = null;
found = true;
continue;
}
}
hash.push([action, xmlNode, loopNode, undoObj, oParent]);
runTimer = true;
continue;
}
//!this.delayUpdate && <- that doesnt work because of information that is destroyed
if (apf.isO3 || "|remove|move-away|move|add|".indexOf("|" + action + "|") > -1) {
if (this.$listeners[uId]) {
this.$listeners[uId]([action, xmlNode,
loopNode, undoObj, oParent]);
}
/*amlNode = apf.all[uId];
if (amlNode)
amlNode.$xmlUpdate(action, xmlNode,
loopNode, undoObj, oParent);*/
}
else {
hash.push([action, xmlNode, loopNode, undoObj, oParent]);
runTimer = true;
}
}
}
//Go one level up
loopNode = loopNode.parentNode || nextloop;
if (loopNode == nextloop)
nextloop = null;
}
if (true || undoObj && !this.delayUpdate) {
//Ok this was an action let's not delay execution
apf.xmldb.notifyQueued();
}
else if (runTimer) {
apf.setZeroTimeout.clearTimeout(notifyTimer);
//@todo find a better solution for this (at the end of a event stack unroll)
this.$hasQueue = true;
notifyTimer = apf.setZeroTimeout(function(){
//this.$hasQueue = true;
apf.xmldb.notifyQueued();
});
}
};
/*
* @todo in actiontracker - add stack auto purging
* - when undo item is purged which was a removed, remove cache item
* @todo shouldn't the removeNode method remove all this.$listeners?
* @todo rename to processQueue
* @private
*/
this.notifyQueued = function(){
this.$hasQueue = false;
var myQueue = notifyQueue;
notifyQueue = {};
apf.setZeroTimeout.clearTimeout(notifyTimer);
for (var uId in myQueue) {
if (!uId) continue;
var q = myQueue[uId];
var func = this.$listeners[uId];
//!amlNode ||
if (!q || !func)
continue;
//Run queue items
for (var i = 0; i < q.length; i++) {
if (!q[i])
continue;
//Update xml data
//amlNode.$xmlUpdate.apply(amlNode, q[i]);
func(q[i]);
}
}
};
/**
* @private
*/
this.notifyListeners = function(xmlNode) {
//This should be done recursive
var listen = xmlNode.getAttribute(apf.xmldb.xmlListenTag);
if (listen) {
listen = listen.split(";");
for (var j = 0; j < listen.length; j++) {
apf.all[listen[j]].$xmlUpdate("synchronize", xmlNode, xmlNode);
//load(xmlNode);
}
}
};
/*
* Sends Message through transport to tell remote databound this.$listeners
* that data has been changed
* @private
*/
this.applyRDB = function(args, undoObj) {
return;
var xmlNode = undoObj.localName || !undoObj.xmlNode
? args[1] && args[1].length && args[1][0] || args[1]
: undoObj.xmlNode;
if (xmlNode.nodeType == 2)
xmlNode = xmlNode.ownerElement || xmlNode.selectSingleNode("..");
var mdlId = apf.xmldb.getXmlDocId(xmlNode),
model = apf.nameserver.get("model", mdlId);
if (!model && apf.isO3)
model = self[mdlId];
if (!model) {
if (!apf.nameserver.getAll("remote").length)
return;
return;
}
if (!model.rdb) return;
var rdb = model.rdb;
// Add the messages to the undo object
if (undoObj.action)
rdb.$queueMessage(args, model, undoObj);
// Or send message now
else {
clearTimeout(rdb.queueTimer);
rdb.$queueMessage(args, model, rdb);
// use a timeout to batch consecutive calls into one RDB call
rdb.queueTimer = $setTimeout(function() {
rdb.$processQueue(rdb);
});
}
};
/**
* @private
*/
this.copyConnections = function(fromNode, toNode) {
//This should copy recursive
try {
toNode.setAttribute(this.xmlListenTag, fromNode.getAttribute(this.xmlListenTag));
}
catch (e) {}
try {
toNode.setAttribute(this.xmlIdTag, fromNode.getAttribute(this.xmlIdTag));
}
catch (e) {}
};
/**
* @private
*/
this.cleanXml = function(xml) {
if (typeof xml != "string")
return xml;
return xml.replace(cleanRE, "").replace(whiteRE, "><");
};
/**
* @private
*/
this.cleanNode = function(xmlNode) {
try {
var i, nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlListenTag + "]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute(this.xmlListenTag);
nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlIdTag + "]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute(this.xmlIdTag);
nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlDocTag + "]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute(this.xmlDocTag);
nodes = xmlNode.selectNodes("descendant-or-self::node()[@a_loaded]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute("a_loaded");
}
catch (e) {}
return xmlNode;
};
/**
* Returns a copy of the passed {@link term.datanode data node}. Bound
* data nodes contain special attributes to track them. These attributes
* are removed from the copied node when using this method.
*
* @param {XMLElement} xmlNode The {@link term.datanode data node} to copy.
* @return {XMLElement} The copy of the {@link term.datanode data node}.
*/
this.copy =
this.getCleanCopy =
apf.getCleanCopy = function(xmlNode) {
return apf.xmldb.cleanNode(xmlNode.cloneNode(true));
};
/**
* Unbind all APF Elements from a certain Form
* @private
*/
this.unbind = function(frm) {
//Loop through objects of all apf
for (var lookup = {}, i = 0; i < frm.apf.all.length; i++)
if (frm.apf.all[i] && frm.apf.all[i].unloadBindings)
lookup[frm.apf.all[i].unloadBindings()] = true;
//Remove Listen Nodes
for (var k = 0; k < this.$xmlDocLut.length; k++) {
if (!this.$xmlDocLut[k]) continue;
var Nodes = this.$xmlDocLut[k].selectNodes("//self::node()[@"
+ this.xmlListenTag + "]");
if (!Nodes) continue;
//Loop through Nodes and rebuild listen array
for (var i = 0; i < Nodes.length; i++) {
var listen = Nodes[i].getAttribute(this.xmlListenTag).split(";");
for (var nListen = [], j = 0; j < listen.length; j++)
if (!lookup[listen[j]])
nListen.push(listen[j]);
//Optimization??
if (nListen.length != listen.length)
Nodes[i].setAttribute(this.xmlListenTag, nListen.join(";"));
}
}
if (window.clearInterval)
window.clearInterval(this.$gcInterval);
};
/*
* @private
* @todo xml doc leakage
*/
this.getXmlDocId = function(xmlNode, model) {
var docEl = xmlNode.ownerDocument.documentElement;
if (!apf.isChildOf(docEl, xmlNode))
docEl = xmlNode;
var docId = (docEl || xmlNode).getAttribute(this.xmlDocTag)
|| this.$xmlDocLut.indexOf(docEl || xmlNode.ownerDocument || xmlNode);
if (model && apf.nameserver.get("model", docId) != model) {
docId = null;
docEl = xmlNode;
}
if (!docId || docId == -1) {
docId = this.$xmlDocLut.push(docEl || xmlNode.ownerDocument || xmlNode) - 1;
if (docEl)
docEl.setAttribute(this.xmlDocTag, String(docId));
}
if (model)
apf.nameserver.register("model", docId, model);
return docId;
};
});
/**
* The parser of the Ajax.org Markup Language. Besides aml this parser takes care
* of distributing parsing tasks to other parsers like the native html parser and
* the xsd parser.
* @parser
* @private
*
* @define include element that loads another aml files.
* Example:
* <code>
* <a:include src="bindings.aml" />
* </code>
* @attribute {String} src the location of the aml file to include in this application.
*
*/
apf.DOMParser = function(){};
apf.DOMParser.prototype = new (function(){
this.caseInsensitive = true;
this.preserveWhiteSpace = false; //@todo apf3.0 whitespace issue
this.$waitQueue = {}
this.$callCount = 0;
// privates
var RE = [
/\<\!(DOCTYPE|doctype)[^>]*>/,
/&nbsp;/g,
/<\s*\/?\s*(?:\w+:\s*)[\w-]*[\s>\/]/g
],
XPATH = "//@*[not(contains(local-name(), '.')) and not(translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = local-name())]";
this.parseFromString = function(xmlStr, mimeType, options) {
var xmlNode;
if (this.caseInsensitive) {
//replace(/&\w+;/, ""). replace this by something else
//.replace(RE[1], " ")
var str = xmlStr.replace(RE[0], "")
.replace(RE[2], //.replace(/^[\r\n\s]*/, "")
function(m){ return m.toLowerCase(); });
/* @todo apf3.0 integrate this
x.ownerDocument.setProperty("SelectionNamespaces",
"xmlns:a='" + apf.ns.aml + "'");
*/
if (!this.supportNamespaces)
str = str.replace(/xmlns\=\"[^"]*\"/g, "");
var xmlNode = apf.getXmlDom(str);
if (apf.xmlParseError) apf.xmlParseError(xmlNode);
xmlNode = xmlNode.documentElement;
}
else {
xmlNode = apf.getXmlDom(xmlStr, null, this.preserveWhiteSpace || apf.debug).documentElement;
}
return this.parseFromXml(xmlNode, options);
};
//@todo prevent leakage by not recording .$aml
this.parseFromXml = function(xmlNode, options) {
var doc, docFrag, amlNode, beforeNode;
if (!options)
options = {};
if (!options.delayedRender && !options.include) {
//Create a new document
if (options.doc) {
doc = options.doc;
docFrag = options.docFrag || doc.createDocumentFragment();
}
else {
doc = new apf.AmlDocument();
doc.$aml = xmlNode;
doc.$domParser = this;
}
if (options.host)
doc.$parentNode = options.host; //This is for sub docs that need to access the outside tree
//Let's start building our tree
amlNode = this.$createNode(doc, xmlNode.nodeType, xmlNode); //Root node
(docFrag || doc).appendChild(amlNode);
if (options.htmlNode)
amlNode.$int = options.htmlNode;
}
else {
amlNode = options.amlNode;
doc = options.doc;
if (options.include) {
var n = amlNode.childNodes;
var p = n.indexOf(options.beforeNode);
var rest = p ? n.splice(p, n.length - p) : [];
}
}
//Set parse context
this.$parseContext = [amlNode, options];
this.$addParseState(amlNode, options || {});
//First pass - Node creation
var nodes, nodelist = {}, prios = [], _self = this;
var recur;
(recur = function(amlNode, nodes) {
var cL, newNode, node, nNodes,
cNodes = amlNode.childNodes,
i = 0,
l = nodes.length;
for (; i < l; i++) {
//Create child
newNode = _self.$createNode(doc, (node = nodes[i]).nodeType, node);
if (!newNode) continue; //for preserveWhiteSpace support
cNodes[cL = cNodes.length] = newNode; //Add to children
//Set tree refs
newNode.parentNode = amlNode;
if (cL > 0)
(newNode.previousSibling = cNodes[cL - 1]).nextSibling = newNode;
//Create children
if (!newNode.render && newNode.canHaveChildren && (nNodes = node.childNodes).length)
recur(newNode, nNodes);
//newNode.$aml = node; //@todo should be deprecated...
//Store high prio nodes for prio insertion
if (newNode.$parsePrio) {
if (newNode.$parsePrio == "001") {
newNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
continue;
}
(nodelist[newNode.$parsePrio] || (prios.push(newNode.$parsePrio)
&& (nodelist[newNode.$parsePrio] = []))).push(newNode); //for second pass
}
}
amlNode.firstChild = cNodes[0];
amlNode.lastChild = cNodes[cL];
})(amlNode, xmlNode.childNodes);
if (options.include && rest.length) {
var index = n.length - 1;
n.push.apply(n, rest);
var last = n[index];
var next = n[index + 1];
(next.previousSibling = last).nextSibling = next;
amlNode.lastChild = n[n.length - 1];
}
if (options.delay) {
amlNode.$parseOptions = {
prios: prios,
nodelist: nodelist
};
return (docFrag || doc);
}
//Second pass - Document Insert signalling
prios.sort();
var i, j, l, l2;
for (i = 0, l = prios.length; i < l; i++) {
nodes = nodelist[prios[i]];
for (j = 0, l2 = nodes.length; j < l2; j++) {
nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
}
}
if (this.$waitQueue[amlNode.$uniqueId]
&& this.$waitQueue[amlNode.$uniqueId].$shouldWait)
return (docFrag || doc);
if (options.timeout) {
$setTimeout(function(){
_self.$continueParsing(amlNode, options);
});
}
else {
this.$continueParsing(amlNode, options);
}
return (docFrag || doc);
};
this.$isPaused = function(amlNode) {
return this.$waitQueue[amlNode.$uniqueId] &&
this.$waitQueue[amlNode.$uniqueId].$shouldWait > 0;
}
this.$addParseState = function(amlNode, options) {
var waitQueue = this.$waitQueue[amlNode.$uniqueId]
|| (this.$waitQueue[amlNode.$uniqueId] = [])
waitQueue.pushUnique(options);
return waitQueue;
}
this.$pauseParsing = function(amlNode, options) {
var waitQueue = this.$waitQueue[amlNode.$uniqueId];
if (!waitQueue.$shouldWait) waitQueue.$shouldWait = 0;
waitQueue.$shouldWait++;
}
this.$continueParsing = function(amlNode, options) {
if (!amlNode)
amlNode = apf.document.documentElement;
var uId = amlNode.$uniqueId;
if (uId in this.$waitQueue) {
var item = this.$waitQueue[uId];
if (item.$shouldWait && --item.$shouldWait)
return false;
var node = amlNode.parentNode;
while (node && node.nodeType == 1) {
if (this.$waitQueue[node.$uniqueId]
&& this.$waitQueue[node.$uniqueId].$shouldWait)
return false;
node = node.parentNode;
}
var parseAmlNode = apf.all[uId];
delete this.$waitQueue[uId];
if (parseAmlNode) {
for (var i = 0; i < item.length; i++)
this.$parseState(parseAmlNode, item[i]);
}
//@todo Check for shouldWait here?
}
else
this.$parseState(amlNode, options || {});
delete this.$parseContext;
}
this.$parseState = function(amlNode, options) {
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:
* <code>
* <a:include src="bindings.aml" />
* </code>
* @attribute {String} src the location of the aml file to include in this application.
*
*/
apf.aml = new apf.AmlNamespace();
apf.setNamespace("http://ajax.org/2005/aml", apf.aml);
apf.__AMLNODE__ = 1 << 14;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have Document Object Model (DOM) support. The DOM
* is the primary method for accessing and manipulating an XML document. This
* includes HTML documents and AML documents. Every element in the ajax.org
* markup language can be manipulated using the W3C DOM. This means
* that every element and attribute you can set in the XML format, can be
* changed, set, removed, reparented, _e.t.c._ at runtime. This offers a great deal of
* flexibility.
*
* Well known methods
* from this specification are: `appendChild`, `removeChild`, `setAttribute`, and
* `insertBefore`--to name a few. The Ajax.org Platform aims to implement DOM1
* completely and parts of DOM2. For more information see {@link http://www.w3.org/DOM/}
* or {@link http://www.w3schools.com/dom/default.asp}.
*
* #### Example:
*
* Here's a basic window using the Ajax.org Markup Language (AML):
*
* ```xml
* <a:window id="winExample" title="Example" visible="true">
* <a:button id="tstButton" />
* </a:window>
* ```
*
*
* Using the Document Object Model in JavaScript:
*
* ```javascript
* //The following line is only there for completeness sake. In fact apf
* //automatically adds a reference in javascript called winExample based
* //on the id it has.
* var winExample = apf.document.getElementById("winExample");
* winExample.setAttribute("title", "Example");
* winExample.setAttribute("icon", "icoFolder.gif");
* winExample.setAttribute("left", "100");
*
* var lblNew = apf.document.createElement("label");
* winExample.appendChild(lblNew);
* lblNew.setAttribute("caption", "Example");
*
* tstButton.setAttribute("caption", "Click me");
* ```
*
* That would be the same as having the following AML:
*
* ```xml
* <a:window id="winExample"
* title = "Example"
* icon = "icoFolder.gif"
* left = "100"
* visible = "true">
* <a:button id="tstButton" caption="Click me"/>
* <a:label caption="Example" />
* </a:window>
* ```
*
* #### Remarks
* Because the W3C DOM is native to all modern browsers the internet is full
* of tutorials and documentation for this API. If you need more information,
* it's a good idea to search for tutorials online.
*
* @class apf.AmlNode
* @baseclass
* @inherits apf.Class
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
*/
/**
* @event DOMNodeInserted Fires when a DOM node is inserted.
*/
/**
* @event DOMNodeInsertedIntoDocument Fires when a DOM node is inserted into the document.
*/
/**
* @event DOMNodeRemoved Fires when a DOM node is removed.
*/
/**
* @event DOMNodeRemovedFromDocument Fires when a DOM node is removed from a document.
*/
apf.AmlNode = function(){
this.$init(function(){
/**
* Nodelist containing all the child nodes of this element.
*/
this.childNodes = []; //@todo AmlNodeList
});
};
(function() {
/**
* Returns a string representation of this object.
* @returns A string defining the object.
*/
this.toString = function(){
if (this.nodeName)
return "[" + this.nodeName.uCaseFirst() + " Node]";
return "[" + this.localName.uCaseFirst() + " Element Node, <"
+ (this.prefix ? this.prefix + ":" : "") + this.localName + " "
+ this.attributes.join(" ")
+ " /> : " + (this.name || this.$uniqueId || "") + "]";
};
/**
* Number specifying the type of node within the document.
* @type {Number}
*/
this.$regbase = this.$regbase | apf.__AMLNODE__;
/**
* The constant for a DOM element node.
* @type {Number}
*/
this.NODE_ELEMENT = 1;
/**
* The constant for a DOM attribute node.
* @type {Number}
*/
this.NODE_ATTRIBUTE = 2;
/**
* The constant for a DOM text node.
* @type {Number}
*/
this.NODE_TEXT = 3;
/**
* The constant for a DOM cdata section node.
* @type {Number}
*/
this.NODE_CDATA_SECTION = 4;
/**
* The constant for a DOM entity reference node.
* @type {Number}
*/
this.NODE_ENTITY_REFERENCE = 5;
/**
* The constant for a DOM entity node.
* @type {Number}
*/
this.NODE_ENTITY = 6;
/**
* The constant for a DOM processing instruction node.
* @type {Number}
*/
this.NODE_PROCESSING_INSTRUCTION = 7;
/**
* The constant for a DOM comment node.
* @type {Number}
*/
this.NODE_COMMENT = 8;
/**
* The constant for a DOM document node.
* @type {Number}
*/
this.NODE_DOCUMENT = 9;
/**
* The constant for a DOM document type node.
* @type {Number}
*/
this.NODE_DOCUMENT_TYPE = 10;
/**
* The constant for a DOM document fragment node.
* @type {Number}
*/
this.NODE_DOCUMENT_FRAGMENT = 11;
/**
* The constant for a DOM notation node.
* @type {Number}
*/
this.NODE_NOTATION = 12;
/**
* The document node of this application
* @type {apf.AmlDocument}
*/
this.ownerDocument = null;
/**
* Returns the value of the current node.
* @type {apf.AmlNode}
*/
this.nodeValue = "";
/**
* The namespace URI of the node, or `null` if it is unspecified (read-only).
*
* When the node is a document, it returns the XML namespace for the current
* document.
* @type {String}
*/
this.namespaceURI = "";
/*
* @todo
*/
//this.baseURI = alsdjlasdj
/*
* @todo
*/
//this.prefix = asdkljahqsdkh
/**
*
* @inheritdoc apf.AmlNode.insertBefore
*
*/
this.appendChild =
/**
* Inserts an element before another element in the list of children of this
* element. If the element was already a child of another element it is
* removed from that parent before adding it this element.
*
* @method insertBefore
* @param {apf.AmlNode} amlNode The element to insert as child of this element.
* @param {apf.AmlNode} beforeNode The element which determines the insertion position of the element.
* @return {apf.AmlNode} The inserted node
*/
this.insertBefore = function(amlNode, beforeNode, noHtmlDomEdit) {
if (this.nodeType == this.NODE_DOCUMENT) {
if (this.childNodes.length) {
throw new Error(apf.formatErrorString(0, this,
"Insertbefore DOM operation",
"Only one top level element is allowed in an AML document."));
}
else this.documentElement = amlNode; //@todo apf3.0 removal
}
if (amlNode == beforeNode)
return amlNode;
if (this == amlNode) {
throw new Error(apf.formatErrorString(0, this,
"Insertbefore DOM operation",
"Cannot append node as a child of itself."));
}
if (amlNode.nodeType == this.NODE_DOCUMENT_FRAGMENT) {
var nodes = amlNode.childNodes.slice(0);
for (var i = 0, l = nodes.length; i < l; i++) {
this.insertBefore(nodes[i], beforeNode);
}
return amlNode;
}
var isMoveWithinParent = amlNode.parentNode == this,
oldParentHtmlNode = amlNode.$pHtmlNode,
oldParent = amlNode.parentNode,
index = -1,
_self = this;
if (beforeNode) {
index = this.childNodes.indexOf(beforeNode);
if (index < 0) {
return false;
}
}
if (!amlNode.ownerDocument)
amlNode.ownerDocument = this.ownerDocument || apf.ownerDocument;
if (amlNode.parentNode)
amlNode.removeNode(isMoveWithinParent, true);//noHtmlDomEdit);
amlNode.parentNode = this;
if (beforeNode)
index = this.childNodes.indexOf(beforeNode);
if (beforeNode) {
amlNode.nextSibling = beforeNode;
amlNode.previousSibling = beforeNode.previousSibling;
beforeNode.previousSibling = amlNode;
if (amlNode.previousSibling)
amlNode.previousSibling.nextSibling = amlNode;
}
if (index >= 0) {
this.childNodes = this.childNodes.slice(0, index).concat(amlNode,
this.childNodes.slice(index));
}
else {
index = this.childNodes.push(amlNode) - 1;
amlNode.nextSibling = null;
if (index > 0) {
amlNode.previousSibling = this.childNodes[index - 1];
amlNode.previousSibling.nextSibling = amlNode;
}
else {
amlNode.previousSibling = null;
}
}
this.firstChild = this.childNodes[0];
this.lastChild = this.childNodes[this.childNodes.length - 1];
//@todo fix event struture, fix tree events
var initialAppend = !amlNode.$amlLoaded;
function triggerUpdate(){
amlNode.$pHtmlNode = _self.canHaveChildren ? _self.$int : document.body;
//@todo this is a hack, a good solution should be found
if (document.adoptNode && amlNode.$ext && amlNode.$ext.nodeType == 1) {
var reappendlist = [];
var iframelist = apf.getArrayFromNodelist(
amlNode.$ext.getElementsByTagName("iframe"));
if (amlNode.$ext.tagName == "IFRAME")
document.adoptNode(amlNode.$ext);
for (var i = 0; i < iframelist.length; i++) {
reappendlist[i] = [
iframelist[i].parentNode,
iframelist[i].nextSibling,
document.adoptNode(iframelist[i]),
]
}
}
var nextNode = beforeNode;
if (!initialAppend && !noHtmlDomEdit && amlNode.$ext && !amlNode.$coreHtml) {
nextNode = beforeNode;
while (nextNode && !(nextNode.$altExt || nextNode.$ext)) {
nextNode = nextNode.nextSibling;
}
amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext,
nextNode && (nextNode.$altExt || nextNode.$ext) || null);
for (var i = reappendlist.length - 1; i >= 0; i--) {
reappendlist[i][0].insertBefore(
reappendlist[i][2],
reappendlist[i][1]);
}
reappendlist = [];
}
//Signal node and all it's ancestors
amlNode.dispatchEvent("DOMNodeInserted", {
$beforeNode: beforeNode,
relatedNode: _self,
$isMoveWithinParent: isMoveWithinParent,
$oldParentHtmlNode: oldParentHtmlNode,
$oldParent: oldParent,
bubbles: true
});
if (initialAppend && !noHtmlDomEdit && beforeNode && amlNode.$ext && !amlNode.$coreHtml) {
nextNode = beforeNode;
while (nextNode && !(nextNode.$altExt || nextNode.$ext)) {
nextNode = nextNode.nextSibling;
}
amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext,
nextNode && (nextNode.$altExt || nextNode.$ext) || null);
for (var i = reappendlist.length - 1; i >= 0; i--) {
reappendlist[i][0].insertBefore(
reappendlist[i][2],
reappendlist[i][1]);
}
}
}
var doc = this.nodeType == this.NODE_DOCUMENT ? this : this.ownerDocument;
if (!doc || doc.$domParser.$isPaused(this))
return amlNode;
// Don't update the tree if this is a doc fragment or if this element is not inited yet
if (this.nodeType == this.NODE_DOCUMENT_FRAGMENT || !this.$amlLoaded)
return amlNode;
//@todo review this...
if (initialAppend && !amlNode.render) { // && (nNodes = node.childNodes).length ??
(this.ownerDocument || this).$domParser.$continueParsing(amlNode, {delay: true});
}
triggerUpdate();
return amlNode;
};
/**
* Removes this element from the document hierarchy. Call-chaining is
* supported.
*
*/
this.removeNode = function(doOnlyAdmin, noHtmlDomEdit) {
if (!this.parentNode || !this.parentNode.childNodes)
return this;
this.parentNode.childNodes.remove(this);
//If we're not loaded yet, just remove us from the aml to be parsed
if (this.$amlLoaded && !apf.isDestroying) {
//this.parentNode.$aml.removeChild(this.$aml);
this.dispatchEvent("DOMNodeRemoved", {
relatedNode: this.parentNode,
bubbles: true,
$doOnlyAdmin: doOnlyAdmin
});
if (!noHtmlDomEdit && !doOnlyAdmin && this.$ext && this.$ext.parentNode) {
this.$ext.parentNode.removeChild(this.$ext);
//delete this.$ext; //WTF???
}
}
if (this.parentNode.firstChild == this)
this.parentNode.firstChild = this.nextSibling;
if (this.parentNode.lastChild == this)
this.parentNode.lastChild = this.previousSibling;
if (this.nextSibling)
this.nextSibling.previousSibling = this.previousSibling;
if (this.previousSibling)
this.previousSibling.nextSibling = this.nextSibling;
this.$pHtmlNode =
this.parentNode =
this.previousSibling =
this.nextSibling = null;
return this;
};
/**
* Removes a child from the node list of this element. Call-chaining is
* supported.
* @param {apf.AmlNode} childNode The child node to remove
*/
this.removeChild = function(childNode) {
childNode.removeNode();
return this;
};
//@todo
this.replaceChild = function(){};
/**
* Clones this element, creating an exact copy of it--but does not insert
* it in the document hierarchy.
*
* @param {Boolean} deep Specifies whether the elements are cloned recursively.
* @return {apf.AmlNode} The cloned element.
*/
this.cloneNode = function(deep) {
if (deep && this.nodeType == 1) {
return this.ownerDocument.$domParser.parseFromXml(this, {
doc: this.ownerDocument,
delay: true
}).childNodes[0];
}
else {
return this.ownerDocument.$domParser.$createNode(
this.ownerDocument, this.nodeType, this);
}
};
//@todo
this.canDispatch = function(namespaceURI, type) {};
//@todo
this.compareDocumentPosition = function(otherNode) {
/*
DOCUMENT_POSITION_DISCONNECTED = 0x01;
DOCUMENT_POSITION_PRECEDING = 0x02;
DOCUMENT_POSITION_FOLLOWING = 0x04;
DOCUMENT_POSITION_CONTAINS = 0x08;
DOCUMENT_POSITION_CONTAINED_BY = 0x10;
*/
};
this.hasAttributes = function(){
return this.attributes && this.attributes.length;
};
this.hasChildNodes = function(){
return this.childNodes && this.childNodes.length;
};
this.isDefaultNamespace = function(namespaceURI) {
if (node.nodeType == 1) {
if (!this.prefix)
return this.namespaceURI == namespaceURI;
//@todo Loop through attributes here
}
var node = this.parentNode || this.ownerElement;
return node && node.isDefaultNamespace(namespaceURI);
};
this.lookupNamespaceURI = function(prefix) {
if (node.nodeType == 1) {
if (this.namespaceURI && prefix == this.prefix)
return this.namespaceURI ;
//@todo Loop through attributes here
}
var node = this.parentNode || this.ownerElement;
return node && node.lookupNamespaceURI(prefix);
};
this.lookupPrefix = function(namespaceURI) {
if (this.nodeType == 1) {
if (namespaceURI == this.namespaceURI && this.prefix)
return this.prefix;
//@todo Loop through attributes here
}
var node = this.parentNode || this.ownerElement;
return node && node.lookupPrefix(namespaceURI);
};
this.normalize = function(){};
// *** Xpath support *** //
/**
* Queries the AML DOM using the W3C xPath query language and returns a node
* list. This is not an official API call, but can be useful in certain cases.
*
* @param {String} sExpr The xpath expression to query the AML DOM tree with.
* @param {apf.AmlNode} [contextNode] The element that serves as the starting point of the search. Defaults to this element.
* @returns {NodeList} List of found nodes.
*/
this.selectNodes = function(sExpr, contextNode) {
if (!apf) return;
if (!apf.XPath)
apf.runXpath();
return apf.XPath.selectNodes(sExpr,
contextNode || (this.nodeType == 9 ? this.documentElement : this));
};
/**
* Queries the AML dom using the W3C xPath query language and returns a single
* node. This is not an official API call, but can be useful in certain cases.
*
* @param {String} sExpr The xpath expression to query the AML DOM tree with.
* @param {apf.AmlNode} [contextNode] The element that serves as the starting point of the search. Defaults to this element.
* @returns {apf.AmlNode} The first node that matches the query.
*/
this.selectSingleNode = function(sExpr, contextNode) {
if (!apf) return;
if (!apf.XPath)
apf.runXpath();
return apf.XPath.selectNodes(sExpr,
contextNode || (this.nodeType == 9 ? this.documentElement : this))[0];
};
/*this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
}, true);*/
}).call(apf.AmlNode.prototype = new apf.Class());
/**
* Represents a single element within an AML node.
*
* @class apf.AmlElement
* @baseclass
* @inherits apf.AmlNode
*/
apf.AmlElement = function(struct, tagName) {
var $init = this.$init;
this.$init = function(tagName, nodeFunc, struct) {
this.$supportedProperties = this.$supportedProperties.slice();
var prop, p, q;
p = this.$propHandlers;
q = this.$propHandlers = {};
for (prop in p)
q[prop] = p[prop];
p = this.$booleanProperties;
q = this.$booleanProperties = {};
for (prop in p)
q[prop] = p[prop];
return $init.call(this, tagName, nodeFunc, struct);
};
this.$init(function(tagName, nodeFunc, struct) {
this.$events = {};
this.$inheritProperties = {};
/*
* A node list containing all the attributes. This is implemented according to the
* W3C specification.
*
* For more information, see [[apf.AmlElement.getAttribute]] and [[apf.AmlElement.setAttribute]].
*
* #### Example
*
* ```javascript
* for (var i = 0; i < obj.attributes.length; i++) {
* alert(obj.attributes.item(i));
* }
* ```
* @type {apf.AmlNamedNodeMap}
*/
this.attributes = new apf.AmlNamedNodeMap(this); //@todo apf3.0 move to init?
/**
* Defines the purpose of this element. Possible values include:
* - `apf.NODE_VISIBLE`: This element has a GUI representation
* - `apf.NODE_HIDDEN`: This element does not display a GUI
* @type {Number}
*/
this.nodeFunc = nodeFunc;
/**
* The local name of this element
* @type {String}
*/
this.localName = tagName; //@todo
//Parse struct to create attributes and child nodes
if (struct) {
var nodes, prop, i, l, attr;
if (struct.childNodes) {
nodes = struct.childNodes;
delete struct.childNodes; //why delete?
}
//Attributes
for (prop in struct) {
if (prop == "htmlNode") continue;
attr = new apf.AmlAttr(this, prop, struct[prop]);
//These exceptions should be generalized
if (prop == "id")
this.$propHandlers["id"].call(this, this.id = struct.id);
else if (prop == "hotkey")
this.$propHandlers["hotkey"].call(this, this.hotkey = struct.hotkey);
else if (prop.substr(0, 2) == "on")
attr.$triggerUpdate();
this.attributes.push(attr);
}
if (!this.ownerDocument) {
this.ownerDocument = apf.document;
this.prefix = "a";
this.namespaceURI = apf.ns.aml;
this.tagName = tagName;
}
if (nodes) {
this.childNodes = nodes;
for (i = 0, l = nodes.length; i < l; i++) {
nodes[i].nextSibling = nodes[i + 1] || null;
nodes[i].previousSibling = nodes[i - 1] || null;
nodes[i].parentNode = this;
}
this.firstChild = nodes[0] || null;
this.lastChild = nodes[nodes.length - 1] || null;
}
//Temp hack
this.$aml = apf.$emptyNode || (apf.$emptyNode = apf.getXml("<empty />"));
}
});
if (tagName) //of typeof is not function and not true
$init.call(this, tagName, apf.NODE_HIDDEN, struct);
};
(function(){
/**
* A number specifying the type of node within the document.
* @type {Number}
*/
this.nodeType = this.NODE_ELEMENT;
this.canHaveChildren = true;
this.$propHandlers = {
/**
* @attribute {String} id The identifier of this element. When set, this
* identifier is the name of the variable in JavaScript to access this
* element directly. This identifier is also the way to get a reference to
* this element using `apf.document.getElementById()`.
*
* #### Example
*
* ```xml
* <a:bar id="barExample" />
* <a:script>
* alert(barExample);
* </a:script>
* ```
*/
"id": function(value) {
if (this.name == value || !value)
return;
if (self[this.name] == this) {
self[this.name] = null;
apf.nameserver.remove(this.localName, this);
apf.nameserver.remove("all", this);
}
if (!self[value] || !self[value].hasFeature) {
try {
self[value] = this;
}
catch (ex) {
}
}
//@todo dispatch event for new name creation.
//@todo old name disposal
apf.nameserver.register(this.localName, value, this)
apf.nameserver.register("all", value, this)
this.name = value;
}
};
this.$booleanProperties = {};
this.$inheritProperties = {};
this.$supportedProperties = [];
/**
* Returns a list of elements with the given tag name.
*
* The subtree below the specified element is searched, excluding the
* element itself.
*
* @param {String} tagName The tag name to look for. The special string "*" represents any tag name.
* @param {Boolean} [norecur] If specified, defines whether or not to check recursively
* @return {NodeList} Contains any nodes matching the search string
*/
this.getElementsByTagName = function(tagName, norecur) {
tagName = tagName.toLowerCase();
var node, i, l,
nodes = this.childNodes,
result = [];
for (i = 0, l = nodes.length; i < l; i++) {
if ((node = nodes[i]).nodeType != 1)
continue;
if (node.tagName == tagName || tagName == "*")
result.push(node);
if (!norecur && node.nodeType == 1)
result = result.concat(node.getElementsByTagName(tagName));
}
return result;
};
/**
* Returns a list of elements with the given tag name and the specified namespace URI.
*
* The subtree below the specified element is searched, excluding the
* element itself.
*
* @param {String} namespaceURI The namespace URI name to look for.
* @param {String} localName The tag name to look for. The special string "*" represents any tag name.
* @param {Boolean} [norecur] If specified, defines whether or not to check recursively
* @return {NodeList} Contains any nodes matching the search string
*/
this.getElementsByTagNameNS = function(namespaceURI, localName, norecur) {
localName = localName.toLowerCase();
var node, i, l,
nodes = this.childNodes,
result = [];
for (i = 0, l = nodes.length; i < l; i++) {
if ((node = nodes[i]).nodeType != 1)
continue;
if (node.namespaceURI == namespaceURI && (node.localName == localName || localName == "*"))
result.push(node);
if (!norecur && !node.$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 = "<div class='loading'>loading...</div>";
//Do an insertMarkup
this.insertMarkup(amlDefNode, options);
};
/**
* Inserts new AML into this element.
* @param {Mixed} amlDefNode The AML to be loaded. This can be a string or a parsed piece of XML.
* @param {Object} options Additional options to pass. It can include the following properties:
* - callback ([[Function]]): A function to call once the insertion completes.
* - clear ([[Boolean]]): If set, the AML has the attribute "clear" attached to it
*/
this.insertMarkup = function(amlDefNode, options) {
var _self = this;
var include = new apf.XiInclude();
if (amlDefNode.trim().charAt(0) == "<")
amlDefNode = apf.getXml(amlDefNode);
include.setAttribute("href", amlDefNode);
if (options && options.clear)
include.setAttribute("clear", true);
include.options = options;
include.callback = function(){
_self.dispatchEvent("afteramlinserted", {src: amlDefNode});
options && options.callback && options.callback();
setTimeout(function(){
include.destroy(true, true);
});
};
this.appendChild(include);
};
//@todo prefix only needs on top element
this.serialize = function(shallow) {
if (shallow || !this.firstChild) {
return "<"
+ (this.prefix
? this.prefix + ":" + this.localName + " xmlns:"
+ this.prefix + "=\"" + this.namespaceURI + "\""
: this.localName) + (this.attributes && this.attributes.length ? " " : "")
+ (this.attributes && this.attributes.join(" ") || "")
+ "/>";
}
else {
var str = ["<"
+ (this.prefix
? this.prefix + ":" + this.localName + " xmlns:"
+ this.prefix + "=\"" + this.namespaceURI + "\""
: this.localName) + (this.attributes && this.attributes.length ? " " : "")
+ (this.attributes && this.attributes.join(" ") || "")
+ ">"];
for (var i = this.firstChild; i; i = i.nextSibling)
str.push(i.serialize());
return str.join("") + "</" + (this.prefix ? this.prefix
+ ":" + this.localName : this.localName) + ">";
}
};
this.$setInheritedAttribute = function(prop) {
var value, node = this, isInherit = false;
value = node.getAttribute(prop);
if (!value) {
node = node.parentNode;
//Second argument fetches special inheritance value, if any
while (node && node.nodeType == 1 && !(value = node.getAttribute(prop, true))) {
node = node.parentNode;
}
isInherit = true;
}
if (!value && apf.config && prop)
value = apf.config[prop];
if (value) {
//Remove any bounds if relevant
this.$clearDynamicProperty(prop);
}
if (isInherit)
this.$inheritProperties[prop] = 2;
if (value) {
if (typeof value == "string"
&& (value.indexOf("{") > -1 || value.indexOf("[") > -1)) {
this.$setDynamicProperty(prop, value);
}
else
this.setProperty(prop, value, false, false, 2);
}
return value;
};
//@todo in proper W3C implementation this needs to change
//@todo this won't work with a combo of remove/append
this.addEventListener("DOMNodeInserted", function(e) {
if (e.currentTarget != this || e.$isMoveWithinParent || !e.$oldParent)
return;
//Check inherited attributes for reparenting
/*
States:
-1 Set
undefined Pass through
2 Inherited
3 Semi-inherited
10 Dynamic property
*/
var vOld, vNew;
var aci = apf.config.$inheritProperties;
for (var prop in aci) {
vOld = apf.getInheritedAttribute(e.$oldParent, prop);
vNew = apf.getInheritedAttribute(this.parentNode, prop);
//Property has changed, lets recursively set it on inherited nodes
if (vOld != vNew) {
//@todo code duplication from class.js
(function recur(nodes) {
var i, l, node, n;
for (i = 0, l = nodes.length; i < l; i++) {
node = nodes[i];
if (node.nodeType != 1 && node.nodeType != 7)
continue;
//Pass through
n = node.$inheritProperties[prop];
if (aci[prop] == 1 && !n)
recur(node.childNodes);
//Set inherited property
//@todo why are dynamic properties overwritten??
else if (!(n < 0)) {//Will also pass through undefined - but why??? @todo seems inefficient
if (n == 3) {
var sameValue = node[prop];
node[prop] = null;
}
node.setProperty(prop, n != 3
? vNew
: sameValue, false, false, n); //This is recursive already
}
}
})([this]);
}
}
});
this.$handlePropSet = function(prop, value, force) {
if (this.$booleanProperties[prop])
value = apf.isTrue(value);
this[prop] = value;
var handler;
return (handler = this.$propHandlers && this.$propHandlers[prop]
|| this.nodeFunc == apf.NODE_VISIBLE && apf.GuiElement && apf.GuiElement.propHandlers[prop] || null)
&& handler.call(this, value, prop, force);
};
//var aci = apf.config.$inheritProperties; << UNUSED
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var a, i, l, attr = this.attributes;
//Set all attributes
for (i = 0, l = attr.length; i < l; i++) {
attr[i].dispatchEvent("DOMNodeInsertedIntoDocument");
}
}, true);
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
this.$amlLoaded = true;
});
}).call(apf.AmlElement.prototype = new apf.AmlNode());
//@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 "<![CDATA[" + this.nodeValue + "]]>";
};
apf.AmlComment = function(isPrototype) {
this.nodeType = this.NODE_COMMENT;
this.nodeName = "#comment";
this.$init(isPrototype);
};
(function(){
this.serialize = function(){
return "<!--" + this.nodeValue + "-->";
};
this.$setValue = function(value) {
this.dispatchEvent("DOMCharacterDataModified", {
bubbles: true,
newValue: value,
prevValue: this.nodeValue
});
}
}).call(apf.AmlComment.prototype = new apf.AmlCharacterData());
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 `<?_target_ ...?>`
* @param {String} data The data to be added to the PI
* @return {apf.AmlNode} The processing instruction node
*/
this.createProcessingInstruction = function(target, data) {
return this.$domParser.$createNode(this, this.NODE_PROCESSING_INSTRUCTION,
null, null, target, data);
};
/**
* Creates and returns a new CDATA section node.
* @param {String} nodeValue The data to be added to the CDATA node
* @return {apf.AmlNode} The CDATA section node
*/
this.createCDATASection = function(nodeValue) {
return this.$domParser.$createNode(this, this.NODE_CDATA_SECTION, null,
null, null, nodeValue);
};
/**
* Creates and returns a new Text node.
* @param {String} nodeValue The data to be added to the text node
* @return {apf.AmlNode} The Text node
*/
this.createTextNode = function(nodeValue) {
return this.$domParser.$createNode(this, this.NODE_TEXT, null, null,
null, nodeValue);
};
/**
* Creates and returns a new document fragment.
*/
this.createDocumentFragment = function(){
return this.$domParser.$createNode(this, this.NODE_DOCUMENT_FRAGMENT);
};
// @todo
this.querySelector = function(){};
// @todo
this.querySelectorAll = function(){};
// @todo
this.hasFocus = function(){
}
}).call(apf.AmlDocument.prototype = new apf.AmlNode());
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.target + "\n" + apf.escapeXML(this.nodeValue) + "\n?>";
};
this.reload = function(){
this.$handlePropSet("data", this.data);
};
//1 = force no bind rule, 2 = force bind rule
this.$attrExcludePropBind = apf.extend({
calcdata: 0 //Start in code mode
}, this.$attrExcludePropBind);
this.getAttribute = function(){};
this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute;
this.$supportedProperties = [];
this.$propHandlers = {};
this.$booleanProperties = {};
this.$inheritProperties = {};
this.$setValue = function(value) {
this.setProperty("data", value);
};
this.$handlePropSet = function(prop, value, force) {
this[prop] = value;
if (prop == "data") {
this.$clearDynamicProperty("calcdata");
this.$setDynamicProperty("calcdata", value);
}
else if (prop == "target") {
//not implemented
}
else if (this.$propHandlers[prop]) {
this.$propHandlers[prop].call(this, value, prop);
}
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var pHtmlNode = e.pHtmlNode;
if (!pHtmlNode && (this.parentNode.$bindingRule
|| !(pHtmlNode = this.parentNode.$int)))
return;
pHtmlNode.appendChild(this.$ext = document.createElement("span"));
this.$ext.host = this;
this.$setDynamicProperty("calcdata", this.data);
}, true);
/*this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
this.$clearDynamicProperty("calcdata");
});*/
this.$destroy = function(){
this.$clearDynamicProperty("calcdata");
this.$propHandlers["calcdata"].call(this, "");
};
}).call(apf.AmlProcessingInstruction.prototype = new apf.AmlNode());
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(/&nbsp;/g, " ")
if (!apf.supportNamespaces)
xmlString = xmlString.replace(/xmlns\=\"[^"]*\"/g, "");
if (xmlString.indexOf("<a:application") == -1)
xmlString = "<a:application xmlns:a='" + apf.ns.aml +"'>"
+ xmlString + "</a:application>";
var xmlNode = apf.getXml(xmlString, null, true);//apf.getAmlDocFromString(xmlString);
if (!xmlNode) {
throw new Error(apf.formatErrorString(0, null,
"Loading include",
"Could not parse include file. Maybe the file does not exist?", xmlNode));
}
xmlNode.setAttribute("filename", extra.url);
finish.call(_self, xmlNode); //@todo add recursive includes support here
},
async: true,
ignoreOffline: true
}));
}
}).call(apf.XiInclude.prototype = new apf.AmlElement());
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
* <a:bar width="80%" height="80%" top="10%" left="10%">
* <a:frame
* caption = "Example"
* left = "50%+10"
* top = "100"
* right = "10%"
* bottom = "Math.round(0.232*100)" />
* </a:bar>
* ```
*
* ### Remarks
*
* This is one of three positioning methods. The other two are Alignment and Grid.
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.3
* @baseclass
* @layout
*/
apf.Anchoring = function(){
this.$regbase = this.$regbase | apf.__ANCHORING__;
this.$anchors = [];
var VERTICAL = 1;
var HORIZONTAL = 2;
this.$updateQueue = 0;
this.$inited =
this.$parsed =
this.$anchoringEnabled = false;
this.$hordiff =
this.$verdiff = 0;
this.$rule_v =
this.$rule_h =
this.$rule_header = "";
var l = apf.layout;
this.$supportedProperties.push("anchors");
var propHandlers = {
"right" : function(value, prop) {
if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
return;
if (!value && value !== 0)
this.$ext.style[prop] = "";
//@note Removed apf.isParsing here to activate general queuing
if (!this.$updateQueue)
l.queue(this.$pHtmlNode, this);
this.$updateQueue = this.$updateQueue | HORIZONTAL;
},
"bottom" : function(value, prop) {
if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
return;
if (!value && value !== 0)
this.$ext.style[prop] = "";
//@note Removed apf.isParsing here to activate general queuing
if (!this.$updateQueue)
l.queue(this.$pHtmlNode, this);
this.$updateQueue = this.$updateQueue | VERTICAL;
}
};
propHandlers.left = propHandlers.width = propHandlers.right;
propHandlers.top = propHandlers.height = propHandlers.bottom;
this.$propHandlers["anchors"] = function(value) {
this.$anchors = value ? value.splitSafe("(?:, *| )") : [];
if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
return;
if (!this.$updateQueue && apf.loaded)
l.queue(this.$pHtmlNode, this);
this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL;
};
/**
* Turns anchoring off.
*
*/
this.$disableAnchoring = function(activate) {
//!this.$parsed ||
if (!this.$inited || !this.$anchoringEnabled || !this.$pHtmlNode)
return;
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
if (l.queue)
l.queue(this.$pHtmlNode);
for (var prop in propHandlers) {
delete this.$propHandlers[prop];
}
this.removeEventListener("DOMNodeRemoved", remove);
this.removeEventListener("DOMNodeInserted", reparent);
if (this.$ext) {
this.$ext.style.left =
this.$ext.style.right =
this.$ext.style.top =
this.$ext.style.bottom =
this.$ext.style.width =
this.$ext.style.height =
this.$ext.style.position = "";
}
/*if (this.right)
this.$ext.style.left = apf.getHtmlLeft(this.$ext) + "px";
if (this.bottom)
this.$ext.style.top = apf.getHtmlTop(this.$ext) + "px";*/
this.removeEventListener("prop.visible", visibleHandler);
this.$inited = false;
this.$anchoringEnabled = false; //isn't this redundant?
};
/**
* @attribute {Number | String} [left] Sets or gets a way to determine the amount of pixels from the left border of this element to the left edge of it's parent's border. This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
* <a:bar left="(20% + 10) * SOME_JS_VAR" />
* ```
*/
/**
* @attribute {Number | String} [right] Sets or gets a way to determine the amount of pixels from the right border of this element to the right edge of its parent's border.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
* <a:bar right="(20% + 10) * SOME_JS_VAR" />
* ```
*/
/**
* @attribute {Number | String} [width] Sets or gets a way to determine the amount of pixels from the left border to the right border of this element.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
* <a:bar width="(20% + 10) * SOME_JS_VAR" />
* ```
*/
/**
* @attribute {Number | String} [top] Sets or gets a way to determine the amount of pixels from the top border of this element to the top edge of its parent's border.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
* <a:bar top="(20% + 10) * SOME_JS_VAR" />
* ```
*/
/**
* @attribute {Number | String} [bottom] Sets or gets a way to determine the amount of pixels from the bottom border of this element to the bottom edge of its parent's border.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
* <a:bar bottom="(20% + 10) * SOME_JS_VAR" />
* ```
*/
/**
* @attribute {Number | String} [height] Sets or gets a way to determine the amount of pixels from the top border to the bottom border of this element.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
* <a:bar height="(20% + 10) * SOME_JS_VAR" />
* ```
*/
/*
* Enables anchoring based on attributes set in the AML of this element
*/
this.$enableAnchoring = function(){
if (this.$inited) //@todo add code to reenable anchoring rules (when showing)
return;
// *** Properties and Attributes *** //
apf.extend(this.$propHandlers, propHandlers);
// *** Event handlers *** //
this.addEventListener("DOMNodeRemoved", remove);
this.addEventListener("DOMNodeInserted", reparent);
this.addEventListener("prop.visible", visibleHandler);
this.$updateQueue = 0
| ((this.left || this.width || this.right || this.anchors) && HORIZONTAL)
| ((this.top || this.height || this.bottom || this.anchors) && VERTICAL) ;
if (this.$updateQueue)
l.queue(this.$pHtmlNode, this);
this.$inited = true;
this.$anchoringEnabled = true;
};
function visibleHandler(e) {
if (!(this.$rule_header || this.$rule_v || this.$rule_h) || !this.parentNode)
return;
if (e.value) {
if (this.$rule_v || this.$rule_h) {
var rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h;
l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules);
//this.$ext.style.display = "none";
l.queue(this.$pHtmlNode, this);
}
l.processQueue();
}
else {
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
l.queue(this.$pHtmlNode)
}
}
function remove(e) {
if (e && (e.$doOnlyAdmin || e.currentTarget != this))
return;
if (l.queue && this.$pHtmlNode) {
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
l.queue(this.$pHtmlNode)
}
}
function reparent(e) {
if (!this.$amlLoaded || e.currentTarget != this)
return;
if (!e.$isMoveWithinParent && this.$parsed) //@todo hmm weird state check
this.$moveAnchoringRules(e.$oldParentHtmlNode);
//else if (e.relatedNode == this) //@todo test this
//e.currentTarget.$setLayout("anchoring");
}
this.$moveAnchoringRules = function(oldParent, updateNow) {
var rules = oldParent && l.removeRule(oldParent, this.$uniqueId + "_anchors");
if (rules)
l.queue(oldParent);
if (!this.$rule_v && !this.$rule_h && !this.$rule_header)
return;
this.$rule_header = getRuleHeader.call(this);
rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h;
//@todo sometimes the content is not displayed anymore (when reparenting by xinclude)
//this.$ext.style.display = "none";
l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules);
l.queue(this.$pHtmlNode, this);
};
this.$hasAnchorRules = function(){
return this.$rule_v || this.$rule_h ? true : false;
};
function getRuleHeader(){
if (!this.$pHtmlDoc) return "";
return "try{\
var oHtml = " + (apf.hasHtmlIdsInJs
? this.$ext.getAttribute("id")
: "document.getElementById('"
+ this.$ext.getAttribute("id") + "')") + ";\
\
var pWidth = " + (this.$pHtmlNode == this.$pHtmlDoc.body
? "apf.getWindowWidth()" //@todo only needed for debug?
: "apf.getHtmlInnerWidth(oHtml.parentNode)") + ";\
\
var pHeight = " + (this.$pHtmlNode == this.$pHtmlDoc.body
? "apf.getWindowHeight()" //@todo only needed for debug?
: "apf.getHtmlInnerHeight(oHtml.parentNode)") + ";\
}catch(e){\
}";
}
/**
* Sets the anchoring percentage.
* @param {String} expr An expression that's converted to a string
* @param {Number} An integer value that's used to convert to a percentage; for example, 50 becomes .5
* @returns {String} The anchor percentage
*/
function setPercentage(expr, value) {
return String(expr).replace(apf.percentageMatch, "((" + value + " * $1)/100)");
}
this.$recalcAnchoring = function(queueDelay) {
this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL;
this.$updateLayout();
l.queue(this.$pHtmlNode, this);
if (!queueDelay)
l.processQueue();
};
function visCheck(){
if (this.$updateQueue) {
this.$updateLayout();
apf.layout.activateRules(this.$ext.parentNode);
}
}
this.$updateLayout = function(){
if (!this.$anchoringEnabled)
return;
if (!apf.window.vManager.check(this, "anchoring", visCheck))
return;
if (!this.$parsed) {
if (!this.$ext.getAttribute("id"))
apf.setUniqueHtmlId(this.$ext);
this.$rule_header = getRuleHeader.call(this);
this.$parsed = true;
}
if (!this.$updateQueue) {
if (this.visible && this.$ext.style.display == "none")
this.$ext.style.display = "";
return;
}
if (this.draggable == "relative") {
if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1) //@todo apf3.1 the IDE doesn't like this
this.$ext.style.position = "absolute";
}
else if (this.left || this.left === 0 || this.top || this.top === 0
|| this.right || this.right === 0 || this.bottom || this.bottom === 0
|| this.$anchors.length) {
if ("absolute|fixed".indexOf(apf.getStyle(this.$ext, "position")) == -1)
this.$ext.style.position = "absolute";
}
else if (!this.center) {
if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1)
this.$ext.style.position = "relative";
if (!this.width)
this.$ext.style.width = "";
if (!this.height)
this.$ext.style.height = "";
}
var rules;
if (this.$updateQueue & HORIZONTAL) {
rules = [];
this.$hordiff = apf.getWidthDiff(this.$ext);
var left = this.$anchors[3] || this.left,
right = this.$anchors[1] || this.right,
width = this.width, hasLeft = left || left === 0,
hasRight = right || right === 0,
hasWidth = width || width === 0;
if (right && typeof right == "string")
right = setPercentage(right, "pWidth");
if (hasLeft) {
if (parseInt(left) != left) {
left = setPercentage(left, "pWidth");
rules.push("oHtml.style.left = (" + left + ") + 'px'");
}
else
this.$ext.style.left = left + "px";
}
if ((apf.hasStyleAnchors || !hasLeft) && hasRight) {
if (parseInt(right) != right) {
right = setPercentage(right, "pWidth");
rules.push("oHtml.style.right = (" + right + ") + 'px'");
}
else
this.$ext.style.right = right + "px";
}
if (hasLeft && hasRight) { //right != null && left != null) {
if (!apf.hasStyleAnchors)
rules.push("oHtml.style.width = (pWidth - (" + right
+ ") - (" + left + ") - " + this.$hordiff + ") + 'px'");
else
this.$ext.style.width = "";
}
else if (hasWidth && typeof this.maxwidth == "number" && typeof this.minwidth == "number") {
if (parseInt(width) != width) {
this.width = width = (this.width || "").replace(/--(\d+)/, "-(-$1)");
width = setPercentage(width, "pWidth");
rules.push("oHtml.style.width = Math.max("
+ (this.minwidth - this.$hordiff)
+ ", Math.min(" + (this.maxwidth - this.$hordiff) + ", "
+ width + " - " + this.$hordiff + ")) + 'px'");
}
else {
this.$ext.style.width = ((width > this.minwidth
? (width < this.maxwidth
? width
: this.maxwidth)
: this.minwidth) - this.$hordiff) + "px";
}
}
this.$rule_h = (rules.length
? "try{" + rules.join(";}catch(e) {};try{") + ";}catch(e){};"
: "");
}
if (this.$updateQueue & VERTICAL) {
rules = [];
this.$verdiff = apf.getHeightDiff(this.$ext);
var top = this.$anchors[0] || this.top,
bottom = this.$anchors[2] || this.bottom,
height = this.height, hasTop = top || top === 0,
hasBottom = bottom || bottom === 0,
hasHeight = height || height === 0;
if (bottom && typeof bottom == "string")
bottom = setPercentage(bottom, "pHeight");
if (hasTop) {
if (parseInt(top) != top) {
top = setPercentage(top, "pHeight");
rules.push("oHtml.style.top = (" + top + ") + 'px'");
}
else
this.$ext.style.top = top + "px";
}
if ((apf.hasStyleAnchors || !hasTop) && hasBottom) {
if (parseInt(bottom) != bottom) {
rules.push("oHtml.style.bottom = (" + bottom + ") + 'px'");
}
else
this.$ext.style.bottom = bottom + "px";
}
if (hasTop && hasBottom) { //bottom != null && top != null) {
if (!apf.hasStyleAnchors)
rules.push("oHtml.style.height = (pHeight - (" + bottom +
") - (" + top + ") - " + this.$verdiff + ") + 'px'");
else
this.$ext.style.height = "";
}
else if (hasHeight && typeof this.minheight == "number") {
if (parseInt(height) != height) {
height = setPercentage(height, "pHeight");
rules.push("oHtml.style.height = Math.max("
+ (this.minheight - this.$verdiff)
+ ", Math.min(" + (this.maxheight - this.$verdiff) + ", "
+ height + " - " + this.$verdiff + ")) + 'px'");
}
else {
this.$ext.style.height = Math.max(0, (height > this.minheight
? (height < this.maxheight
? height
: this.maxheight)
: this.minheight) - this.$verdiff) + "px";
}
}
this.$rule_v = (rules.length
? "try{" + rules.join(";}catch(e) {};try{") + ";}catch(e){};"
: "");
}
if (this.$rule_v || this.$rule_h) {
l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors",
this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h, true);
}
else {
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
}
this.$updateQueue = 0;
if (this.$box && !apf.hasFlexibleBox) //temporary fix
apf.layout.forceResize(this.$ext);
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
//if (this.$updateQueue)
//this.$updateLayout();
});
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
this.$disableAnchoring();
});
};
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
* <a:menu id="mnuExample">
* <a:item>test</a:item>
* <a:item>test2</a:item>
* </a:menu>
*
* <a:list
* contextmenu = "mnuExample"
* width = "200"
* height = "150" />
* <a:bar
* contextmenu = "mnuExample"
* width = "200"
* height = "150" />
* ```
*/
"contextmenu": function(value) {
this.contextmenus = [value];
},
/**
* @attribute {String} actiontracker Sets or gets the name of the [[apf.actiontracker action tracker]] that
* is used for this element and its children. If the actiontracker doesn't
* exist yet, it is created.
*
* #### Example
*
* In this example, the list uses a different action tracker than the two
* textboxes which determine their actiontracker based on the one that
* is defined on the bar.
*
* ```xml
* <a:list actiontracker="newAT" />
*
* <a:bar actiontracker="someAT">
* <a:textbox />
* <a:textbox />
* </a:bar>
* ```
*/
"actiontracker": function(value) {
if (!value) {
this.$at = null;
}
else if (typeof value == "object") {
this.$at = value;
}
else {
this.$at = typeof value == "string" && self[value]
? apf.nameserver.get("actiontracker", value) || self[value].getActionTracker()
: apf.setReference(value,
apf.nameserver.register("actiontracker",
value, new apf.actiontracker()));
if (!this.$at.name)
this.$at.name = value;
}
},
//Load subAML
/**
* @attribute {String} aml Sets or gets the {@link term.datainstruction data instruction}
* that loads new AML as children of this element.
*/
"aml": function(value) {
this.replaceMarkup(value);
}
/*
* @attribute {String} sets this aml element to be editable
* that loads new aml as children of this element.
*/
// @todo Doc WTF?
};
apf.__PRESENTATION__ = 1 << 9;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have skinning features. A skin is a description
* of how the element is rendered. In the web browser, this is done using HTML
* elements and CSS.
*
* #### Remarks
*
* The skin is set using the `skin` attribute. The skin of each element can be
* changed at run time. Other than just changing the look of an element, a skin
* change can help the user to perceive information in a different way. For
* example, a list element has a default skin, but can also use the thumbnail
* skin to display thumbnails of the {@link term.datanode data nodes}.
*
* #### Example
*
* A skin for an element is always built up out of a standard set of parts:
*
* ```xml
* <a:textbox name="textbox">
* <a:alias>
* ...
* </a:alias>
* <a:style><![CDATA[
* ...
* ]]></a:style>
*
* <a:presentation>
* <a:main>
* ...
* </a:main>
* ...
* </a:presentation>
* </a:textbox>
* ```
*
* The alias contains a name that contains alternative names for the skin. The
* style tags contain the CSS. The main tag contains the HTML elements that are
* created when the component is created. Any other skin items are used to render
* other elements of the widget. In this reference guide you will find these
* skin items described on the pages of each widget.
*
* @class apf.Presentation
* @define presentation
* @inherits apf.GuiElement
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
*/
apf.Presentation = function(){
this.$init(true);
};
(function(){
this.$regbase = this.$regbase | apf.__PRESENTATION__;
// *** Properties and Attributes *** //
this.$supportedProperties.push("skin");
/**
* @attribute {String} skinset Sets or gets the skinset for
* this element. If none are specified ,the `skinset` attribute
* of the app settings is used. When that's not defined, the default skinset
* is used.
*
* #### Example
*
* ```xml
* <a:list skinset="perspex" />
* ```
*/
this.$propHandlers["skinset"] =
/**
* @attribute {String} skin Sets or gets the name of the skin in the skinset that defines
* how this element is rendered. When a skin is changed, the full state of the
* element is kept, including its selection, all the
* AML attributes, loaded data, and focus and disabled states.
*
* #### Example
*
* In XML:
*
* ```xml
* <a:list id="lstExample" skin="thumbnails" />
* ```
*
* Or, in JavaScript:
*
* ```javascript
* lstExample.setAttribute("skin", "list");
* ```
*/
this.$propHandlers["skin"] = function(value) {
if (!this.$amlLoaded) //If we didn't load a skin yet, this will be done when we attach to a parent
return;
if (!this.$skinTimer) {
var _self = this;
clearTimeout(this.$skinTimer);
this.$skinTimer = $setTimeout(function(){
changeSkin.call(_self, _self.skin);
delete _self.$skinTimer;
});
}
}
/**
* @attribute {String} style Sets or gets the CSS style applied to the this element. This can be a string containing one or more CSS rules.
*/
this.$propHandlers["style"] = function(value) {
if (!this.styleAttrIsObj && this.$amlLoaded)
this.$ext.setAttribute("style", value);
}
/**
* @attribute {String} border Sets or gets border values. Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off borders.
*/
this.$propHandlers["border"] = function(value) {
if (!value)
this.$ext.style.borderWidth = "";
else
this.$ext.style.borderWidth = apf.getBox(value).join("px ") + "px";
}
/**
* @attribute {String | Number} margin Sets or gets margin values. Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off margins.
*/
this.$propHandlers["margin"] = function(value) {
if (!value)
this.$ext.style.margin = "";
else
this.$ext.style.margin = apf.getBox(value).join("px ") + "px";
}
/**
* @attribute {String} class Sets or gets the name of the CSS style class applied to this element.
*/
this.$propHandlers["class"] = function(value) {
this.$setStyleClass(this.$ext, value, this.$lastClassValue ? [this.$lastClassValue] : null);
this.$lastClassValue = value;
}
this.$forceSkinChange = function(skin, skinset) {
changeSkin.call(this, skin, skinset);
}
//@todo objects don't always have an $int anymore.. test this
function changeSkin(skin, skinset) {
clearTimeout(this.$skinTimer);
//var skinName = (skinset || this.skinset || apf.config.skinset)
// + ":" + (skin || this.skin || this.localName);
//Store selection
if (this.selectable)
var valueList = this.getSelection();//valueList;
//Store needed state information
var oExt = this.$ext,
oInt = this.$int,
pNode = this.$ext ? this.$ext.parentNode : this.$pHtmlNode,
beforeNode = oExt && oExt.nextSibling,
idExt = this.$ext && this.$ext.getAttribute("id"),
idInt = this.$int && this.$int.getAttribute("id"),
oldBase = this.$baseCSSname;
if (oExt && oExt.parentNode)
oExt.parentNode.removeChild(oExt);
//@todo changing skin will leak A LOT, should call $destroy here, with some extra magic
if (this.$destroy)
this.$destroy(true);
//Load the new skin
this.skin = skin;
this.$loadSkin(skinset ? skinset + ":" + skin : null);
//Draw
if (this.$draw)
this.$draw(true);
if (idExt)
this.$ext.setAttribute("id", idExt);
if (beforeNode || this.$ext && pNode != this.$ext.parentNode)
pNode.insertBefore(this.$ext, beforeNode);
//Style
//Border
//Margin
if (this.$ext) {
//Classes
var i, l, newclasses = [],
classes = (oExt.className || "").splitSafe("\\s+");
for (i = 0; i < classes.length; i++) {
if (classes[i] && classes[i].indexOf(oldBase) != 0)
newclasses.push(classes[i].replace(oldBase, this.$baseCSSname));
}
apf.setStyleClass(this.$ext, newclasses.join(" "));
//Copy events
var en, ev = apf.skins.events;
for (i = 0, l = ev.length; i < l; i++) {
en = ev[i];
if (typeof oExt[en] == "function" && !this.$ext[en])
this.$ext[en] = oExt[en];
}
//Copy css state (dunno if this is best)
this.$ext.style.left = oExt.style.left;
this.$ext.style.top = oExt.style.top;
this.$ext.style.width = oExt.style.width;
this.$ext.style.height = oExt.style.height;
this.$ext.style.right = oExt.style.right;
this.$ext.style.bottom = oExt.style.bottom;
this.$ext.style.zIndex = oExt.style.zIndex;
this.$ext.style.position = oExt.style.position;
this.$ext.style.display = oExt.style.display;
}
//Widget specific
//if (this.$loadAml)
//this.$loadAml(this.$aml);
if (idInt)
this.$int.setAttribute("id", idInt);
if (this.$int && this.$int != oInt) {
var node, newNode = this.$int, nodes = oInt.childNodes;
for (var i = nodes.length - 1; i >= 0; i--) {
if ((node = nodes[i]).host) {
node.host.$pHtmlNode = newNode;
if (node.host.$isLeechingSkin)
setLeechedSkin.call(node.host);
}
newNode.insertBefore(node, newNode.firstChild);
}
//this.$int.onresize = oInt.onresize;
}
//DragDrop
if (this.hasFeature(apf.__DRAGDROP__)) {
if (document.elementFromPointAdd) {
document.elementFromPointRemove(oExt);
document.elementFromPointAdd(this.$ext);
}
}
//Check disabled state
if (this.disabled)
this.$disable(); //@todo apf3.0 test
//Check focussed state
if (this.$focussable && apf.document.activeElement == this)
this.$focus(); //@todo apf3.0 test
//Dispatch event
this.dispatchEvent("$skinchange", {
ext: oExt,
"int": oInt
});
//Reload data
if (this.hasFeature(apf.__DATABINDING__) && this.xmlRoot)
this.reload();
else
if (this.value)
this.$propHandlers["value"].call(this, this.value);
//Set Selection
if (this.hasFeature(apf.__MULTISELECT__)) {
if (this.selectable)
this.selectList(valueList, true);
}
//Move layout rules
if (!apf.hasSingleRszEvent) {
apf.layout.activateRules(this.$ext);
if (this.$int)
apf.layout.activateRules(this.$int);
}
/*
if (this.hasFeature(apf.__ANCHORING__))
this.$recalcAnchoring();
if (this.hasFeature(apf.__ALIGNMENT__)) {
if (this.aData)
this.aData.oHtml = this.$ext;
if (this.pData) {
this.pData.oHtml = this.$ext;
this.pData.pHtml = this.$int;
var nodes = this.pData.childNodes;
for (i = 0; i < nodes.length; i++)
nodes[i].pHtml = this.$int; //Should this be recursive??
}
}
*/
if (this.draggable && this.$propHandlers["draggable"]) //@todo move these to the event below apf3.0)
this.$propHandlers["draggable"].call(this, this.draggable);
if (this.resizable && this.$propHandlers["resizable"])
this.$propHandlers["resizable"].call(this, this.resizable);
apf.layout.forceResize(this.$ext);
};
// *** Private methods *** //
this.$setStyleClass = apf.setStyleClass;
function setLeechedSkin(e) {
if (!this.$amlLoaded || e && (e.$isMoveWithinParent
|| e.currentTarget != this || !e.$oldParent))
return;
this.$setInheritedAttribute(this, "skinset");
if (this.attributes.getNamedItem("skin"))
return;
//e.relatedNode
var skinName, pNode = this.parentNode, skinNode;
if ((skinName = this.$canLeechSkin.dataType
== apf.STRING ? this.$canLeechSkin : this.localName)
&& pNode.$originalNodes
&& (skinNode = pNode.$originalNodes[skinName])
&& skinNode.getAttribute("inherit")) {
var link = skinNode.getAttribute("link");
this.$isLeechingSkin = true;
if (link) {
this.$forceSkinChange(link);
}
else {
var skin = pNode.skinName.split(":");
this.$forceSkinChange(skin[1], skin[0]);
}
}
else if (this.$isLeechingSkin) {
delete this.skin;
this.$isLeechingSkin = false;
this.$forceSkinChange();
}
}
//Skin Inheritance
//@todo Probably requires some cleanup
this.$initSkin = function(x) {
if (this.$canLeechSkin) {
this.addEventListener("DOMNodeInserted", setLeechedSkin);
}
if (!this.skin)
this.skin = this.getAttribute("skin");
var skinName, pNode = this.parentNode, skinNode;
if (this.$canLeechSkin && !this.skin
&& (skinName = this.$canLeechSkin.dataType == apf.STRING
? this.$canLeechSkin
: this.localName)
&& pNode && pNode.$originalNodes
&& (skinNode = pNode.$originalNodes[skinName])
&& skinNode.getAttribute("inherit")) {
var link = skinNode.getAttribute("link");
this.$isLeechingSkin = true;
if (link) {
this.skin = link;
this.$loadSkin();
}
else {
this.$loadSkin(pNode.skinName);
}
}
else {
if (!this.skinset)
this.skinset = this.getAttribute("skinset");
this.$loadSkin(null, this.$canLeechSkin);
}
}
/*
* Initializes the skin for this element when none has been set up.
*
* @param {String} skinName Identifier for the new skin (for example: 'default:List' or 'win').
* @param {Boolean} [noError]
*/
this.$loadSkin = function(skinName, noError) {
//this.skinName || //where should this go and why?
this.baseSkin = skinName || (this.skinset
|| this.$setInheritedAttribute("skinset"))
+ ":" + (this.skin || this.localName);
clearTimeout(this.$skinTimer);
if (this.skinName) {
this.$blur();
this.$baseCSSname = null;
}
this.skinName = this.baseSkin; //Why??
//this.skinset = this.skinName.split(":")[0];
this.$pNodes = {}; //reset the this.$pNodes collection
this.$originalNodes = apf.skins.getTemplate(this.skinName, true);
if (!this.$originalNodes) {
// 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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:bar validgroup="vgExample">
* <a:label>Number</a:label>
* <a:textbox required="true" min="3" max="10"
* invalidmsg="Invalid Entry;Please enter a number between 3 and 10" />
* <a:label>Name</a:label>
* <a:textbox required="true" minlength="3"
* invalidmsg="Invalid Entry;Please enter your name" />
* <a:label>Message</a:label>
* <a:textarea required="true"
* invalidmsg="Invalid Message;Please enter a message!" />
*
* <a:button onclick="if(vgExample.isValid()) alert('valid!')">
* Validate
* </a:button>
* </a:bar>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.Validation
* @inherits apf.AmlElement
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
*/
/**
* @event invalid Fires when this component goes into an invalid state.
*
*/
apf.Validation = function(){
this.$regbase = this.$regbase | apf.__VALIDATION__;
/**
* Checks if this element's value is valid.
*
* @param {Boolean} [checkRequired] Specifies whether this check also adheres to the `'required'` rule.
* @returns {Boolean} Specifies whether the value is valid
* @see apf.ValidationGroup
* @see element.submitform
*/
this.isValid = function(checkRequired) {
if (!this.$vOptions)
return true;
(this.$vOptions.isValid || (this.$vOptions.isValid
= apf.validator.compile(this.$vOptions))).call(this,
typeof this.getValue == "function" ? this.getValue(null, true) : null,
checkRequired, this.validityState ||
(this.validityState = new apf.validator.validityState()));
var valid = this.validityState.valid;
this.dispatchEvent(!valid ? "invalid" : "valid", this.validityState);
return valid;
};
/*
* @private
*/
this.setCustomValidity = function(message) {
//do stuff
}
/*
* @private
* @todo This method should also scroll the element into view
*/
this.showMe = function(){
var p = this.parentNode;
while (p) {
if (p.show)
p.show();
p = p.parentNode;
}
};
/**
* Puts this element in the error state, optionally showing the
* error box if this element is invalid.
*
* @method validate
* @param {Boolean} [ignoreReq] Specifies whether this element required check is turned on.
* @param {Boolean} [nosetError] Specifies whether the error box is displayed if this component does not validate.
* @param {Boolean} [force] Specifies whether this element is in the error state, and doesn't check if the element's value is invalid.
* @return {Boolean} Indicates whether the value is valid
* @see apf.ValidationGroup
*/
this.validate = function(ignoreReq, nosetError, force) {
//if (!this.$validgroup) return this.isValid();
if (force || !this.isValid(!ignoreReq) && !nosetError) {
this.setError();
return false;
}
else {
this.clearError();
return true;
}
};
/*
* @private
*/
this.setError = function(value) {
if (!this.$validgroup)
this.$propHandlers["validgroup"].call(this, "vg" + this.parentNode.$uniqueId);
var errBox = this.$validgroup.getErrorBox(this);
if (!this.$validgroup.allowMultipleErrors)
this.$validgroup.hideAllErrors();
errBox.setMessage(this.invalidmsg || value);
apf.setStyleClass(this.$ext, this.$baseCSSname + "Error");
this.showMe(); //@todo scroll refHtml into view
if (this.invalidmsg || value)
errBox.display(this);
if (apf.document.activeElement && apf.document.activeElement != this)
this.focus(null, {mouse:true}); //arguable...
};
/*
* @private
*/
this.clearError = function(value) {
if (this.$setStyleClass)
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Error"]);
if (this.$validgroup) {
var errBox = this.$validgroup.getErrorBox(null, true);
if (!errBox || errBox.host != this)
return;
errBox.hide();
}
};
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
if (this.$validgroup)
this.$validgroup.unregister(this);
});
/**
*
* @attribute {Boolean} required Sets or gets whether a valid value for this element is required.
*/
/**
* @attribute {RegExp} pattern Sets or gets the pattern tested against the value of this element to determine it's validity.
*/
/**
* @attribute {String} datatype Sets or gets the datatype that the value of this element should adhere to. This can be any
* of a set of predefined types, or a simple type created by an XML Schema definition.
*
* Some possible values (all of which are [[String]]s) include:
* - `xsd:dateTime`
* - `xsd:time`
* - `xsd:date`
* - `xsd:gYearMonth`
* - `xsd:gYear`
* - `xsd:gMonthDay`
* - `xsd:gDay`
* - `xsd:gMonth`
* - `xsd:string`
* - `xsd:boolean`
* - `xsd:base64Binary`
* - `xsd:hexBinary`
* - `xsd:float`
* - `xsd:decimal`
* - `xsd:double`
* - `xsd:anyURI`
* - `xsd:integer`
* - `xsd:nonPositiveInteger`
* - `xsd:negativeInteger`
* - `xsd:long`
* - `xsd:int`
* - `xsd:short`
* - `xsd:byte`
* - `xsd:nonNegativeInteger`
* - `xsd:unsignedLong`
* - `xsd:unsignedInt`
* - `xsd:unsignedShort`
* - `xsd:unsignedByte`
* - `xsd:positiveInteger`
* - `apf:url`
* - `apf:website`
* - `apf:email`
* - `apf:creditcard`
* - `apf:expdate`
* - `apf:wechars`
* - `apf:phonenumber`
* - `apf:faxnumber`
* - `apf:mobile`
*/
/**
* @attribute {Number} min Sets or gets the minimal value for which the value of this element is valid.
*/
/**
* @attribute {Number} max Sets or gets the maximum value for which the value of this element is valid.
*/
/**
* @attribute {Number} minlength Sets or gets the minimal length allowed for the value of this element.
*/
/**
* @attribute {Number} maxlength Sets or gets the maximum length allowed for the value of this element.
*/
/**
* @attribute {Boolean} notnull Sets or gets whether the value is filled. This rule is checked realtime when the element loses the focus.
*/
/**
* @attribute {String} checkequal Sets or gets the id of the element to check if it has the same value as this element.
*/
/**
* @attribute {String} invalidmsg Sets or gets the message displayed when this element has an invalid value. Use a `;` character to seperate the title from the message.
*/
/**
* @attribute {String} validgroup Sets or gets the identifier for a group of items to be validated at the same time. This identifier can be new. It is inherited from a AML node upwards.
*/
/**
* @attribute {String} validtest Sets or gets the instruction on how to test for success. This attribute is generally used to check the value on the server.
*
* #### Example
*
* This example shows how to check the username on the server. In this case,
* `comm.loginCheck` is an async RPC function that checks the availability of the
* username. If it exists, it will return `0`--otherwise, it's `1`. The value variable
* contains the current value of the element (in this case the textbox). It
* can be used as a convenience variable.
*
* ```xml
* <a:label>Username</a:label>
* <a:textbox
* validtest = "{comm.loginCheck(value) == 1}"
* pattern = "/^[a-zA-Z0-9_\-. ]{3,20}$/"
* invalidmsg = "Invalid username;Please enter a valid username." />
* ```
*/
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
//this.addEventListener(this.hasFeature(apf.__MULTISELECT__) ? "onafterselect" : "onafterchange", onafterchange);
/* Temp disabled, because I don't understand it (RLD)
this.addEventListener("beforechange", function(){
if (this.xmlRoot && apf.getBoundValue(this) === this.getValue())
return false;
});*/
// validgroup
if (!this.validgroup)
this.$setInheritedAttribute("validgroup");
});
//1 = force no bind rule, 2 = force bind rule
this.$attrExcludePropBind = apf.extend({
pattern: 1,
validtest: 3
}, this.$attrExcludePropBind);
this.$booleanProperties["required"] = true;
this.$supportedProperties.push("validgroup", "required", "datatype",
"pattern", "min", "max", "maxlength", "minlength", "validtest",
"notnull", "checkequal", "invalidmsg", "requiredmsg");
this.$fValidate = function(){
if (this.liveedit)
return;
if (!this.$validgroup)
this.validate(true);
else {
var errBox = this.$validgroup.getErrorBox(this);
if (!errBox.visible || errBox.host != this)
this.validate(true);
}
};
this.addEventListener("blur", this.$fValidate);
this.$propHandlers["validgroup"] = function(value) {
if (value) {
var vgroup;
if (typeof value != "string") {
this.$validgroup = value.name;
vgroup = value;
}
else {
vgroup = apf.nameserver.get("validgroup", value);
}
this.$validgroup = vgroup || new apf.ValidationGroup(value);
this.$validgroup.register(this);
/*
@todo What about children, when created after start
See button login action
*/
}
else {
this.$validgroup.unregister(this);
this.$validgroup = null;
}
};
this.$propHandlers["pattern"] = function(value, prop) {
if (value.substr(0, 1) != "/")
value = "/" + value + "/";
(this.$vOptions || (this.$vOptions = {}))[prop] = value;
delete this.$vOptions.isValid;
};
this.$propHandlers["required"] =
this.$propHandlers["custom"] =
this.$propHandlers["min"] =
this.$propHandlers["max"] =
this.$propHandlers["maxlength"] =
this.$propHandlers["minlength"] =
this.$propHandlers["notnull"] =
this.$propHandlers["checkequal"] = function(value, prop) {
(this.$vOptions || (this.$vOptions = {}))[prop] = value;
delete this.$vOptions.isValid;
};
//@todo rewrite this for apf3.0 - it should just execute a live markup
this.$propHandlers["validtest"] = function(value) {
var _self = this, rvCache = {};
/**
* Removes the validation cache created by the validtest rule.
*/
this.removeValidationCache = function(){
rvCache = {};
}
this.$checkRemoteValidation = function(){
var value = this.getValue();
if (typeof rvCache[value] == "boolean") return rvCache[value];
if (rvCache[value] == -1) return true;
rvCache[value] = -1;
apf.getData(this.validtest.toString(), {
xmlNode: this.xmlRoot,
value: this.getValue(),
callback: function(data, state, extra) {
if (state != apf.SUCCESS) {
if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries)
return extra.tpModule.retry(extra.id);
else {
var commError = new Error(apf.formatErrorString(0, _self,
"Validating entry at remote source",
"Communication error: \n\n" + extra.message));
if (_self.dispatchEvent("error", apf.extend({
error: commError,
state: status
}, extra)) !== false)
throw commError;
return;
}
}
rvCache[value] = apf.isTrue(data);//instr[1] ? data == instr[1] : apf.isTrue(data);
if (!rvCache[value]){
if (!_self.hasFocus())
_self.setError();
}
else _self.clearError();
}
});
return true;
};
(this.$vOptions || (this.$vOptions = {})).custom = "apf.lookup(" + this.$uniqueId + ").$checkRemoteValidation()";
delete this.$vOptions.isValid;
};
};
apf.GuiElement.propHandlers["required"] =
apf.GuiElement.propHandlers["pattern"] =
apf.GuiElement.propHandlers["min"] =
apf.GuiElement.propHandlers["max"] =
apf.GuiElement.propHandlers["maxlength"] =
apf.GuiElement.propHandlers["minlength"] =
apf.GuiElement.propHandlers["notnull"] =
apf.GuiElement.propHandlers["checkequal"] =
apf.GuiElement.propHandlers["validtest"] = function(value, prop) {
this.implement(apf.Validation);
this.$propHandlers[prop].call(this, value, prop);
}
/**
* This object allows for a set of AML elements to be validated. An element that
* is not valid shows an errorbox.
*
* #### Example
*
* ```xml
* <a:bar validgroup="vgForm">
* <a:label>Phone number</a:label>
* <a:textbox id="txtPhone"
* required = "true"
* pattern = "(\d{3}) \d{4} \d{4}"
* invalidmsg = "Incorrect phone number entered" />
*
* <a:label>Password</a:label>
* <a:textbox
* required = "true"
* mask = "password"
* minlength = "4"
* invalidmsg = "Please enter a password of at least four characters" />
* </a:bar>
* ```
*
* To check if the element has valid information entered, leaving the textbox
* (focussing another element) will trigger a check. Programmatically, a check
* can be done using the following code:
*
*
* ```javascript
* txtPhone.validate();
*
* // Or, use the html5 syntax
* txtPhone.checkValidity();
* ```
*
* To check for the entire group of elements, use the validation group. For only
* the first non-valid element the errorbox is shown. That element also receives
* focus.
*
* ```javascript
* vgForm.validate();
* ```
*
* @class apf.ValidationGroup
* @inherits apf.Class
* @default_private
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.9
*/
/**
* @event validation Fires when the validation group isn't validated.
*/
apf.ValidationGroup = function(name) {
this.$init();
this.childNodes = [];
if (name)
apf.setReference(name, this);
this.name = name || "validgroup" + this.$uniqueId;
apf.nameserver.register("validgroup", this.name, this);
};
(function(){
/**
* When set to true, only visible elements are validated.
* @type Boolean
*/
this.validateVisibleOnly = false;
/**
* When set to true, validation doesn't stop at the first invalid element.
* @type Boolean
*/
this.allowMultipleErrors = false;
/**
* Adds an AML element to this validation group.
* @param o {apf.AmlElement} The AML element to add
*/
this.register = function(o) {
if (o.hasFeature(apf.__VALIDATION__))
this.childNodes.push(o);
};
/**
* Removes a AML element from this validation group.
* @param o {apf.AmlElement} The AML element to remove
*/
this.unregister = function(o) {
this.childNodes.remove(o);
};
/**
* Returns a string representation of this object.
*/
this.toString = function(){
return "[APF Validation Group]";
};
//Shared among all validationgroups
var errbox;
/**
* Retrieves the {@link apf.errorbox} used for a specified element.
*
* @param {apf.AmlNode} o An AMLNode specifying the element for which the Errorbox should be found. If none is found, an Errorbox is created. Use the {@link apf.ValidationGroup.allowMultipleErrors} to influence when Errorboxes are created.
* @param {Boolean} no_create Boolean that specifies whether new Errorbox may be created when it doesn't exist already
* @return {apf.errorbox} The found (or created) Errorbox
*/
this.getErrorBox = function(o, no_create) {
if (this.allowMultipleErrors || !errbox && !no_create) {
errbox = new apf.errorbox();
errbox.$pHtmlNode = o.$ext.parentNode;
errbox.skinset = apf.getInheritedAttribute(o.parentNode, "skinset"); //@todo use skinset here. Has to be set in presentation
errbox.dispatchEvent("DOMNodeInsertedIntoDocument");
}
return errbox;
};
/**
* Hide all Errorboxes for the elements using this element as their validation group.
*
*/
this.hideAllErrors = function(){
if (errbox && errbox.host)
errbox.host.clearError();
};
function checkValidChildren(oParent, ignoreReq, nosetError) {
var found;
//Per Element
for (var v, i = 0; i < oParent.childNodes.length; i++) {
var oEl = oParent.childNodes[i];
if (!oEl)
continue;
if (!oEl.disabled
&& (!this.validateVisibleOnly && oEl.visible || !oEl.$ext || oEl.$ext.offsetHeight)
&& (oEl.hasFeature(apf.__VALIDATION__) && oEl.isValid && !oEl.isValid(!ignoreReq))) {
//|| !ignoreReq && oEl.required && (!(v = oEl.getValue()) || new String(v).trim().length == 0)
if (!nosetError) {
if (!found) {
oEl.validate(true, null, true);
found = true;
if (!this.allowMultipleErrors)
return true; //Added (again)
}
else if (oEl.errBox && oEl.errBox.host == oEl)
oEl.errBox.hide();
}
else if (!this.allowMultipleErrors)
return true;
}
if (oEl.canHaveChildren && oEl.childNodes.length) {
found = checkValidChildren.call(this, oEl, ignoreReq, nosetError) || found;
if (found && !this.allowMultipleErrors)
return true; //Added (again)
}
}
return found;
}
/**
*
* @inheritDoc apf.ValidationGroup.isValid
* @method
*/
this.validate =
/**
* Checks if (part of) the set of element's registered to this element are
* valid. When an element is found with an invalid value, the error state can
* be set for that element.
*
* @method isValid
* @param {Boolean} [ignoreReq] Specifies whether to adhere to the 'required' check.
* @param {Boolean} [nosetError Specifies whether to not set the error state of the element with an invalid value
* @param {apf.AmlElement} [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
* <a:tab width="200" height="150">
* <a:page caption="General">
* ...
* </a:page>
* <a:page caption="Advanced" render="runtime">
* <a:button>OK</a:button>
* </a:page>
* </a:tab>
* ```
* @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
* <a:list
* drag = "true"
* drop = "true"
* dragcopy = "true" />
* ```
*
*
* #### Example
*
* This example shows a smartbinding that represents files and folders. It uses a
* {@link term.datainstruction data instruction} to communicate to the webdav
* server when an item is copied or moved.
*
* ```xml
* <a:smartbinding>
* <a:bindings>
* <a:caption match="[@filename]" />
* <a:each match="[file|folder]" />
*
* <a:drag
* match = "[person]"
* copy = "event.ctrlKey" />
* <a:drop
* match = "[file]"
* target = "[folder]"
* action = "tree-append"
* copy = "event.ctrlKey" />
* <a:drop
* match = "[folder]"
* target = "[folder]"
* action = "insert-before"
* copy = "event.ctrlKey" />
* </a:bindings>
* <a:actions>
* <a:move
* match = "[folder]"
* set = "{myWebdav.move([@path], [../@path])}" />
* <a:copy
* match = "[file]"
* set = "{myWebdav.copy([@path], [../@path])}" />
* </a:actions>
* </a:smartbinding>
* ```
*
* #### Example
*
* This example shows a small mail application. The tree element displays a root
* node, accounts and folders in a tree. The datagrid contains the mails. This
* rule specifies which data nodes can be dropped where. Folders can be dropped
* in folders and accounts. Mails can be dropped in folders.
*
* ```xml
*
* <a:tree align="left" width="200">
* <a:each match="[root|account|folder|mail]">
* <a:caption match="[@name]" />
* <a:drag match = "[folder]" />
* <a:drop match = "[folder]"
* target = "[folder|account]"
* action = "tree-append" />
* <a:drop match = "[mail]"
* target = "[folder]"
* action = "tree-append" />
* </a:each>
* <a:model>
* <data>
* <root name="Root">
* <account name="Account 1">
* <folder name="Folder 1"></folder>
* </account>
* </root>
* </data>
* </a:model>
* </a:tree>
* <a:datagrid align="right">
* <a:each match="[mail]">
* <a:column
* caption = "Name"
* value = "[@name]"
* width = "100%" />
* <a:drag match="[mail]" />
* </a:each>
* <a:model>
* <data>
* <mail name="Mail 1"></mail>
* </data>
* </a:model>
* </a:datagrid>
*```
*
* @class apf.DragDrop
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
* @define dragdrop
* @allowchild drop, drag
* @define drag
*/
/**
* @event dragdata Fires before a drag & drop operation is started to determine the data that is dragged.
* @param {Object} e The standard event object. It contains the following property:
* - `data` ([[XMLElement]]): The default data for the drag & drop operation
*/
/**
* @event dragstart Fires before a drag operation is started.
* @cancelable Prevents the drag operation to start.
* @param {Object} e The standard event object. It contains the following properties:
* - `data` ([[XMLElement]]): The data for the drag & drop operation
* - `selection` ([[XMLElement]]): The selection at the start of the drag operation
* - `indicator` ([[HTMLElement]]): The HTML element that is shown while dragging the data
* - `host` ([[apf.AmlElement]]): The AML source element.
*/
/**
* @event dragover Fires when the users drags over this AML element.
* @cancelable Prevents the possibility to drop.
* @param {Object} e The standard event object. It contains the following properties:
* {XMLElement} data The data for the drag & drop operation
* {XMLElement} selection The selection at the start of the drag operation
* {HTMLElement} indicator The HTML element that is shown while dragging the data
* {apf.AmlElement} host the AML source element.
*/
/**
* @event dragout Fires when the user moves away from this AML element.
* @param {Object} e The standard event object. It contains the following properties:
* {XMLElement} data the data for the drag & drop operation
* {XMLElement} selection the selection at the start of the drag operation
* {HTMLElement} indicator the HTML element that is shown while dragging the data
* {apf.AmlElement} host the aml source element.
*/
/**
* @event dragdrop Fires when the user drops an item on this aml element.
* @cancelable Prevents the possibility to drop.
* @param {Object} e The standard event object. It contains the following properties:
* {XMLElement} data The data for the drag & drop operation
* {XMLElement} selection The selection at the start of the drag operation
* {HTMLElement} indicator The html element that is shown while dragging the data
* {apf.AmlElement} host The AML source element.
* {Boolean} candrop Specifies whether the data can be inserted at the point hovered over by the user
*
*
*/
/**
* @attribute {String} match An XPath statement querying the
* {@link term.datanode data node} that is
* dragged. If the query matches a node it
* is allowed to be dropped. The XPath is
* automatically prefixed by `'self::'`.
*/
/**
* @attribute {String} copy A JavaScript expression that determines
* whether the dragged element is a copy or
* a move. Use event.ctrlKey to use the Ctrl
* key to determine whether the element is copied.
*
*/
/**
* @attribute {String} match An XPath statement querying the
* {@link term.datanode data node} that is
* dragged. If the query matches a node it
* is allowed to be dropped. The XPath is
* automatically prefixed by `'self::'`.
*/
/**
* @attribute {String} target An XPath statement determining the new
* parent of the dropped {@link term.datanode data node}.
* The XPath is automatically prefixed by `'self::'`.
*/
/**
* @attribute {String} action The action to perform when the
* {@link term.datanode data node} is inserted.
* The possible values include:
*
* - `tree-append`: Appends the {@link term.datanode data node} to the element its dropped on.
* - `list-append`: Appends the {@link term.datanode data node} to the root element of this element.
* - `insert-before`: Inserts the {@link term.datanode data node} before the elements its dropped on.
*/
/**
* @attribute {String} copy A JavaScript expression that determines
* whether the drop is a copy or a move.
* Use event.ctrlKey to use the [[keys: Ctrl]] key to
* determine whether the element is copied.
*/
apf.DragDrop = function(){
this.$regbase = this.$regbase | apf.__DRAGDROP__;
this.$dragInited = false;
/* **********************
Actions
***********************/
/**
* Copies a {@link term.datanode data node} to the bound data of this element.
*
* @action
* @param {XMLElement} xmlNode The {@link term.datanode data node} which is copied.
* @param {XMLElement} [pNode] The new parent element of the copied
* {@link term.datanode data node}. If none is
* specified the root element of the data
* loaded in this element is used.
* @param {XMLElement} [beforeNode] The position where the {@link term.datanode data node}
* is inserted.
*/
this.copy = function(nodeList, pNode, beforeNode, isMove) {
if (nodeList.nodeType)
nodeList = [nodeList];
var exec,
changes = [],
i = 0,
l = nodeList.length;
for (; i < l; i++) {
changes.push({
action: isMove ? "moveNode" : "appendChild",
args: [pNode, isMove
? nodeList[i]
: nodeList[i] = nodeList[i].cloneNode(true), beforeNode]
});
}
if (this.$actions[(isMove ? "movegroup" : "copygroup")]) {
exec = this.$executeAction("multicall", changes,
(isMove ? "movegroup" : "copygroup"), nodeList[0]);
}
else {
exec = this.$executeAction("multicall", changes,
(isMove ? "move" : "copy"), nodeList[0], null, null,
nodeList.length > 1 ? nodeList : null);
}
if (exec !== false)
return nodeList;
return false;
};
/**
* Moves a {@link term.datanode data node} to the bound data of this element.
*
* @action
* @param {XMLElement} xmlNode The {@link term.datanode data node} which is copied.
* @param {XMLElement} [pNode] The new parent element of the moved
* {@link term.datanode data node}. If none
* specified the root element of the data
* loaded in this element is used.
* @param {XMLElement} [beforeNode] The position where the
* {@link term.datanode data node} is inserted.
*/
this.move = function(nodeList, pNode, beforeNode) {
return this.copy(nodeList, pNode, beforeNode, true);
};
/**
* Determines whether the user is allowed to drag the passed
* {@link term.datanode data node}.
*
* For instance, imagine a mail application with a root
* node, accounts and folders in a tree, and mails in a datagrid. The rules
* would specify you can drag & drop folders within an account, and emails between
* folders, but not on accounts or the root.
*
* @param {XMLElement} dataNode The {@link term.datanode data node} subject to the test.
* @return {Boolean} The result of the test
* @see apf.DragDrop.isDragAllowed
*/
this.isDragAllowed = function(x, data) {
if (!this.dragroot && this.xmlRoot.firstChild == x[0])
return false;
if (this.disabled || !x || !x.length || !x[0])
return false;
if (this.drag || this.dragcopy) {
if (data)
data.merge(x);
return true;
}
/*var rules = this.$bindings["drag"]
|| this.$attrBindings && this.$attrBindings["drag"];
if (!rules || !rules.length)
return false;*/
var d,
ruleList = [],
j = 0,
l = x.length;
for (; j < l; j++) {
d = this.$getDataNode("drag", x[j], null, ruleList);
if (!d) return false; //It's all or nothing
if (data)
data.push(d);
}
return ruleList.length ? ruleList : false;
};
/**
* Determines whether the user is allowed to drop the passed
* {@link term.datanode data node}.
*
* For instance, imagine a mail application with a root
* node, accounts and folders in a tree, and mails in a datagrid. The rules
* would specify you can drag & drop folders within an account, and emails between
* folders, but not on accounts or the root.
*
* @param {XMLElement} dataNode The {@link term.datanode data node} subject
* to the test.
* @param {XMLElement} target The {@link term.datanode data node} on which
* the dragged data node is dropped.
* @return {Boolean} The result of the test
* @see apf.DragDrop.isDragAllowed
*/
this.isDropAllowed = function(x, target) {
if (this.disabled || !x || !x.length || !target) //!x[0] ???
return false;
if (!this.dragroot == false && this.xmlRoot.firstChild == x[0])
return false;
for (var i = x.length - 1; i >= 0; i--)
if (apf.isChildOf(x[i], target, true))
return false;
var data, tgt, hasDropRule = this.$attrBindings && this.$attrBindings["drop"];
if (this.drop && (!hasDropRule || hasDropRule.value == "true")) {
this.$setDynamicProperty("drop", this.hasFeature(apf.__MULTISELECT__)
? "[" + this.each + "]"
: "[node()]"); //@todo apf3.0 make sure each is without {}
hasDropRule = true;
}
if (hasDropRule) {
for (var j = 0, l = x.length; j < l; j++) {
data = this.$getDataNode("drop", x[j]);
if (!data)
break;
}
if (j == l && target && !apf.isChildOf(data, target, true))
return [target, null];
}
var rules = this.$bindings["drop"];
if (!rules || !rules.length)
return false;
//@todo this can be optimized when needed
var rule, strTgt,
i = 0,
rl = rules.length;
for (; i < rl; i++) {
rule = this.$bindings.getRuleIndex("drop", i);
for (var j = 0, l = x.length; j < l; j++) {
data = rule.cvalue ? rule.cvalue(x[j]) : rule.cmatch(x[j]);
if (!data)
break;
}
if (j != l)
continue;
strTgt = rule.target;//node.getAttribute("target");
if (!strTgt || strTgt == ".") {
//op = node.getAttribute("action")
//|| (this.$isTreeArch ? "tree-append" : "list-append");
tgt = target;/*(op == "list-append" || target == this.xmlRoot
? this.xmlRoot
: null);*/
}
else {
tgt = (rule.ctarget || rule.compile("target"))(target);
}
if (tgt && !apf.isChildOf(data, tgt, true))
return [tgt, rule];
}
return false;
};
this.$dragDrop = function(xmlReceiver, xmlNodeList, rule, defaction, isParent, srcRule, event, forceCopy) {
/*
Possibilities:
tree-append [default]: xmlNode.appendChild(movedNode);
list-append : xmlNode.parentNode.appendChild(movedNode);
insert-before : xmlNode.parentNode.insertBefore(movedNode, xmlNode);
*/
var action = rule && rule.action;//node && node.getAttribute("action");
if (action)
action = (rule.caction || rule.compile("action"))(xmlNodeList[0]);
else
action = defaction;
// @todo apf3.0 action not known here yet... should be moved down?
if (action == "tree-append" && isParent)
return false;
if (!event)
event = {};
//copy convenience variables
var context = {
internal: apf.DragServer.dragdata && apf.DragServer.dragdata.host == this,
ctrlKey: event.ctrlKey,
keyCode: event.keyCode
},
//@todo apf3.0 below should actually be compileNode with with_options
ifcopy = rule && rule.copy;//.getAttribute("copy");
if (typeof forceCopy == "boolean")
ifcopy = forceCopy;
else if (ifcopy) {
context.event = event || {};
ifcopy = !apf.isFalse((rule.ccopy || rule.compile("copy"))(xmlNodeList[0], context));
}
else if (typeof this.dragcopy == "boolean" || typeof this.dropcopy == "boolean") { //@todo apf3.0 boolean here?
if (this.dropcopy) {
ifcopy = this.dropcopy;
}
else if (this.dragcopy) {
ifcopy = event.ctrlKey;
}
else {
//@todo read this from src
var copyRule = this.$attrBindings && this.$attrBindings["dragcopy"];
if (copyRule) {
ifcopy = !apf.isFalse((copyRule.cvalue2
|| copyRule.compile("value", {
withopt: true
}))(xmlNodeList[0], context));
}
}
}
if (!ifcopy && srcRule) { //Implemented one copy is all copy
for (var i = 0, l = srcRule.length; i < l; i++) {
ifcopy = typeof srcRule[i] == "object" && srcRule[i].copy
? !apf.isFalse((srcRule[i].ccopy || srcRule[i].compile("copy"))(xmlNodeList[0], context))
: event.ctrlKey;
if (ifcopy) break;
}
}
var sNode,
actRule = ifcopy ? "copy" : "move",
parentXpath = rule ? rule.getAttribute("parent") : null; //@todo apf3.0 Should be lm syntax
switch (action) {
case "list-append":
xmlReceiver = (isParent
? xmlReceiver
: this.getTraverseParent(xmlReceiver));
if (parentXpath) {
if (xmlReceiver.selectSingleNode(parentXpath))
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
else {
xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath));
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
}
}
sNode = this[actRule](xmlNodeList, xmlReceiver);
break;
case "insert-before":
sNode = isParent
? this[actRule](xmlNodeList, xmlReceiver)
: this[actRule](xmlNodeList, xmlReceiver.parentNode, xmlReceiver);
break;
case "tree-append":
if (parentXpath) {
if (xmlReceiver.selectSingleNode(parentXpath))
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
else {
xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath));
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
}
}
sNode = this[actRule](xmlNodeList, xmlReceiver);
break;
}
if (this.selectable && sNode) {
this.selectList(sNode);//, null, null, null, true);
this.setCaret(sNode[0]);
this.focus();
}
return sNode;
};
/* **********************
Init
***********************/
/*
* Loads the dragdrop rules from the dragdrop element
*
* @param {Array} rules The rules array created using {@link core.apf.method.getrules}
* @param {XMLElement} [node] The reference to the drag & drop element
* @see SmartBinding
* @private
*/
this.enableDragDrop = function(){
//Set cursors
//SHOULD come from skin
this.icoAllowed = "";//this.xmlDragDrop.getAttribute("allowed");
this.icoDenied = "";//this.xmlDragDrop.getAttribute("denied");
//Setup External Object
this.$ext.dragdrop = false;
var _self = this;
this.$ext[apf.isIphone ? "ontouchstart" : "onmousedown"] = function(e) {
if (_self.disabled)
return;
e = e || window.event;
var fEl,
srcEl = e.originalTarget || e.srcElement || e.target,
multiselect = _self.hasFeature(apf.__MULTISELECT__);
if (multiselect && srcEl == _self.$container)
return;
_self.dragging = 0;
try{ //Firefox can crash here because of some chrome permission issue
if (!apf.isIphone && _self.allowdeselect
&& (srcEl == this || srcEl.getAttribute(apf.xmldb.htmlIdTag)
&& _self.$getLayoutNode("item", "select", this) != this))
return; //This broke making a selection with the mouse in rename: _self.clearSelection(); //@todo hacky - should detect what element has the select from the skin
}catch(e) {return;}
//MultiSelect must have carret behaviour AND deselect at clicking white
if (_self.$findValueNode)
fEl = _self.$findValueNode(srcEl);
var el = (fEl
? apf.xmldb.getNode(fEl)
: apf.xmldb.findXmlNode(srcEl));
if (multiselect && (!_self.selected || !el || el == _self.xmlRoot))
return;
if (_self.isDragAllowed(multiselect ? _self.$getSelection() : el)) {
apf.DragServer.start(_self, srcEl, e);
}
//e.cancelBubble = true;
};
this.$ext[apf.isIphone ? "ontouchmove" : "onmousemove"] = function(e) {
if (this.host.dragging != 1 || _self.disabled) return;
};
{
this.$ext.onmouseup = function(){
if (_self.disabled)
return;
this.host.dragging = 0;
};
this.$ext.ondragcopy =
this.$ext.ondragstart = function(){return false;};
}
if (document.elementFromPointAdd)
document.elementFromPointAdd(this.$ext);
if (this.$initDragDrop && !this.$dragInited) {
this.$initDragDrop();
this.$dragInited = 2;
}
else {
this.$dragInited = true;
}
};
function disableDragDrop(){
this.$dragInited = false; //@todo solve oExt event conflicts
{
this.$ext.onmousedown = this.$ext.onmousemove
= this.$ext.onmouseup = null;
}
if (document.elementFromPointRemove)
document.elementFromPointRemove(this.$ext);
}
this.implement(
this.hasFeature(apf.__MULTISELECT__)
? apf.MultiselectDragDrop :
apf.StandardDragDrop);
//this.$booleanProperties["drag"] = true;
//this.$booleanProperties["dragcopy"] = true;
this.$supportedProperties.push("drop", "drag", "dragcopy");
/**
* @attribute {Boolean} drag Sets or gets whether the element allows dragging of its items.
*
* #### Example
*
* ```xml
*
* <a:list drag="true">
* <a:item>item 1</a:item>
* <a:item>item 2</a:item>
* <a:item>item 3</a:item>
* </a:list>
*```
*
*/
/**
* @attribute {Boolean} dragcopy whether dragged items are copied.
*
* #### Example
*
* ```xml
* <a:list
* drag = "true"
* align = "right"
* height = "300"
* caption = "[@name]"
* each = "[mail]">
* <a:model>
* <data>
* <mail name="Mail 1"></mail>
* <mail name="Mail 2"></mail>
* <mail name="Mail 3"></mail>
* </data>
* </a:model>
* </a:list>
* ```
*
* #### Example
*
* Items are only copied when the user holds the [[keys: Ctrl]] key
*
* ```xml
* <a:list dragcopy="[ctrlKey]">
* <a:item>item 1</a:item>
* <a:item>item 2</a:item>
* <a:item>item 3</a:item>
* </a:list>
* ```
*/
/**
* @attribute {Boolean} drop Sets or gets whether the element allows items to be dropped.
*
* #### Example
*
*
* ```xml
* <a:list drop="true">
* <a:item>item 1</a:item>
* <a:item>item 2</a:item>
* <a:item>item 3</a:item>
* </a:list>
* ```
* @attribute {String} dragdrop Sets or gets the name of the dragdrop element for this element.
*
* ```xml
* <a:tree align="left" width="200" height="300">
* <a:each match="[root|account|folder|mail]">
* <a:caption match = "[@name]" />
* <a:drag match = "[folder|mail]" />
* <a:drop match = "[folder]"
* target = "[folder|account]"
* action = "tree-append" />
* <a:drop match = "[mail]"
* target = "[folder]"
* action = "tree-append" />
* </a:each>
* <a:model>
* <data>
* <root name="Root">
* <account name="Account 1">
* <folder name="Folder 1">
* <mail name="Mail drag drop"></mail>
* </folder>
* </account>
* </root>
* </data>
* </a:model>
* </a:tree>
*
* <a:list bindings="bndDragdrop" align="right">
* <a:model>
* <data>
* <mail name="Mail 1"></mail>
* <mail name="Mail 2"></mail>
* <mail name="Mail 3"></mail>
* </data>
* </a:model>
* </a:list>
*
* <a:bindings id="bndDragdrop">
* <a:caption match="[@name]" />
* <a:each match="[mail]" />
* <a:drag match = "[mail]" />
* <a:drop
* match = "[mail]"
* action = "list-append" />
* </a:bindings>
* ```
*/
this.$propHandlers["dragcopy"] =
this.$propHandlers["dropcopy"] =
this.$propHandlers["drag"] =
this.$propHandlers["drop"] = function(value, prop) {
this[prop] = apf.isTrue(value);
if (this.$dragInited && prop == "drag" && value && this.$dragInited != 2) {
this.$initDragDrop();
this.$dragInited = 2;
return;
}
if (prop == "dragcopy" || prop == "dropcopy")
return;
if (!value && !this.drag && !this.drop && !this.$bindings
&& (this.$attrBindings && (!this.$attrBindings["drag"] || !this.$attrBindings["drop"])))
disableDragDrop.call(this);
else if (value && !this.$dragInited)
this.enableDragDrop();
};
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
disableDragDrop.call(this);
if (this.oDrag) {
apf.destroyHtmlNode(this.oDrag);
this.oDrag = null;
}
});
};
apf.GuiElement.propHandlers["dragcopy"] =
apf.GuiElement.propHandlers["dropcopy"] =
apf.GuiElement.propHandlers["drop"] =
apf.GuiElement.propHandlers["drag"] = function(value, prop) {
if (!apf.isFalse(value)) {
if (!this.hasFeature(apf.__DRAGDROP__)) {
this.implement(apf.DragDrop);
this.enableDragDrop();
}
this[prop] = apf.isTrue(value);
}
};
/*
* Central object for dragdrop handling.
* @private
*/
apf.DragServer = {
Init: function(){
apf.addEventListener("hotkey", function(e) {
if (apf.window.dragging && e.keyCode == 27) {
if (document.body.lastHost && document.body.lastHost.dragOut)
document.body.lastHost.dragOut(apf.dragHost);
return apf.DragServer.stopdrag();
}
});
},
start: function(amlNode, srcEl, e, customNode) {
if (document.elementFromPointReset)
document.elementFromPointReset();
amlNode.dragging = 1;
var d = window.document;
d = (!d.compatMode || d.compatMode == "CSS1Compat")
? d.html || d.documentElement
: d.body
var scrollX = (apf.isIE ? d.scrollLeft : window.pageXOffset),
scrollY = (apf.isIE ? d.scrollTop : window.pageYOffset),
oParent = amlNode.$ext.offsetParent,
pos
while (oParent && oParent != d && oParent.tagName != "BODY") {
scrollX -= oParent.scrollLeft;
scrollY -= oParent.scrollTop;
oParent = oParent.offsetParent;
}
//The coordinates need to be relative to the html element that
//represents the xml data node.
if (!srcEl && customNode) {
pos = [0, 0];
}
else {
var loopEl = srcEl, lastId;
while (loopEl && loopEl.nodeType == 1
&& !(lastId = loopEl.getAttribute(apf.xmldb.htmlIdTag))) {
loopEl = loopEl.parentNode;
}
if (!lastId)
return;
pos = apf.getAbsolutePosition(loopEl);
}
//Set coordinates object
apf.DragServer.coordinates = {
srcElement: srcEl,
doc: d,
scrollX: scrollX,
scrollY: scrollY,
offsetX: e.clientX - pos[0],
offsetY: e.clientY - pos[1],
clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
};
//Create Drag Data Object
var selection = customNode || amlNode.hasFeature(apf.__MULTISELECT__)
? amlNode.getSelection()
: [amlNode.xmlRoot],
data = [],
srcRules = amlNode.isDragAllowed(selection, data);
if (!srcRules) return;
if (amlNode.hasEventListener("dragdata"))
data = amlNode.dispatchEvent("dragdata", {data : data});
/*for(var i = 0, l = data.length; i < l; i++) {
data[i] = apf.getCleanCopy(data[i]);
}*/
this.dragdata = {
rules: srcRules,
selection: selection,
data: data,
indicator: amlNode.$showDragIndicator(selection, this.coordinates),
host: amlNode
};
//EVENT - cancelable: ondragstart
if (amlNode.dispatchEvent("dragstart", this.dragdata) === false)
return false;//(this.amlNode.$tempsel ? select(this.amlNode.$tempsel) : false);
amlNode.dragging = 2;
apf.dragMode = true;
document.onmousemove = this.onmousemove;
document.onmouseup = this.onmouseup;
},
stop: function(runEvent, success, e) {
if (this.last) this.dragout();
this.dragdata.host.dispatchEvent("dragstop", apf.extend(this.dragdata, {
success: success
}));
//Reset Objects
this.dragdata.host.dragging = 0;
this.dragdata.host.$hideDragIndicator(success);
/*if (runEvent && this.dragdata.host.$dragstop)
this.dragdata.host.$dragstop();*/
apf.dragMode = false;
document.onmousemove =
document.onmouseup = null;
this.dragdata = null;
},
dragover: function(o, el, e) {
var _self = this,
originalEl = el;
function checkPermission(targetEl) {
return o.isDropAllowed && o.xmlRoot
? o.isDropAllowed(_self.dragdata.data, targetEl)
: apf.isTrue(apf.getInheritedAttribute(o, "", function(p) {
if (p.drop) {
o = p;
if (o == apf.DragServer.last)
return false;
return true;
}
}));
}
e = e || window.event;
//@todo optimize by not checking the same node dragged over twice in a row
var fEl;
if (o.$findValueNode)
fEl = o.$findValueNode(el);
if (this.lastFel && this.lastFel == fEl
|| !this.lastFel && this.last == o) //optimization
return;
//Check Permission
var elSel = (fEl
? apf.xmldb.getNode(fEl)
: apf.xmldb.findXmlNode(el)),
candrop = checkPermission(elSel || o.xmlRoot);
if (this.last && this.last != o)
this.dragout(this.last, e);
this.last = o;
this.lastFel = fEl;
if (!candrop) {
if (o && o.$dragover) {
var parentNode = (elSel || o.xmlRoot).parentNode;
if (parentNode && (el = apf.xmldb.findHtmlNode(parentNode, o))) {
if (o.$findValueNode)
fEl = o.$findValueNode(el);
elSel = (fEl
? apf.xmldb.getNode(fEl)
: apf.xmldb.findXmlNode(el));
candrop = checkPermission(parentNode);
this.lastFel = el;
if (!candrop)
return;
}
else
return;
}
else
return;
}
//EVENT - cancelable: ondragover
if (o.dispatchEvent("dragover", this.dragdata, {
target: (elSel || o.xmlRoot),
lastEl: o.lastel,
originalEl: originalEl
}) === false)
candrop = false;
//Set Cursor
var srcEl = e.originalTarget || e.srcElement || e.target;
/*srcEl.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);
if (srcEl.onmouseout != this.m_out) {
srcEl.$onmouseout = srcEl.onmouseout;
srcEl.onmouseout = this.m_out;
}
o.$ext.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);*/
//REQUIRED INTERFACE: __dragover()
if (o && o.$dragover)
o.$dragover(el, this.dragdata, candrop);
},
dragout: function(o, e) {
//if (this.last == o)
//return false;
this.lastFel = null;
//EVENT: ondragout
if (o) {
this.dragdata.htmlEvent = e;
o.dispatchEvent("dragout", this.dragdata);
}
//REQUIRED INTERFACE: __dragout()
if (this.last && this.last.$dragout)
this.last.$dragout(null, this.dragdata);
//Reset Cursor
//o.$ext.style.cursor = "default";
this.last = null;
},
dragdrop: function(o, el, srcO, e) {
var _self = this;
function checkPermission(targetEl) {
return o.isDropAllowed && o.xmlRoot
? o.isDropAllowed(_self.dragdata.data, targetEl)
: apf.isTrue(apf.getInheritedAttribute(o, "", function(p) {
if (p.drop) {
o = p;
return true;
}
}));
}
//Check Permission
var isParent, lastTop,
elSel = (o.$findValueNode
? apf.xmldb.getNode(o.$findValueNode(el))
: apf.xmldb.findXmlNode(el)),
candrop = checkPermission(elSel || o.xmlRoot);
if (this.dragdata.indicator) {
lastTop = this.dragdata.indicator.style.top;
this.dragdata.indicator.style.top = "10000px";
}
if (!candrop) {
if (o && o.$dragover) {
var parentNode = (elSel || o.xmlRoot).parentNode,
htmlParentNode;
if (parentNode && (htmlParentNode = apf.xmldb.findHtmlNode(parentNode, o))) {
isParent = true;
candrop = checkPermission(parentNode);
el = htmlParentNode;
}
}
}
//EVENT - cancelable: ondragdrop
if (candrop) {
if (o.dispatchEvent("dragdrop", apf.extend({candrop : candrop, htmlEvent : e, top: lastTop},
this.dragdata)) === false) {
candrop = false;
}
else {
if (!o.xmlRoot) {
var m = o.getModel
? o.getModel(true)
:
apf.nameserver.get("model", o.model)
if (m)
m.load(this.dragdata.data[0])
//else warn??
return true;
}
else {
var action = candrop[1]
&& candrop[1].action
|| (o.$isTreeArch ? "tree-append" : "list-append");
if (action == "list-append" && (!o.$isTreeArch && o == this.dragdata.host))
candrop = false;
}
}
}
if (this.dragdata.indicator)
this.dragdata.indicator.style.top = lastTop;
//Exit if not allowed
if (!candrop) {
this.dragout(o, e);
return false;
}
if (o.$dragDrop) {
//Move XML
var rNode = o.$dragDrop(candrop[0], this.dragdata.data, candrop[1],
action, isParent || candrop[0] == o.xmlRoot, this.dragdata.rules, e);
this.dragdata.resultNode = rNode;
}
if (o.$dragdrop) {
o.$dragdrop(el, apf.extend({
htmlEvent: e,
xmlNode: rNode
}, this.dragdata), candrop);
}
//Reset Cursor
//o.$ext.style.cursor = "default";
this.last = null;
this.lastFel = null;
return true;
},
/* **********************
Mouse Movements
***********************/
onmousemove: function(e) {
if (!apf.DragServer.dragdata) return;
e = e || window.event;
var dragdata = apf.DragServer.dragdata,
c = {
clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
};
if (!dragdata.started
&& Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6
&& Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6)
return;
if (!dragdata.started) {
if (dragdata.host.$dragstart)
dragdata.host.$dragstart(null, dragdata);
dragdata.started = true;
}
//dragdata.indicator.style.top = e.clientY+"px";
//dragdata.indicator.style.left = e.clientX+"px";
if (dragdata.indicator) {
var storeIndicatorTopPos = dragdata.indicator.style.top;
//console.log("INDICATOR BEFORE: "+dragdata.indicator.style.top+" "+dragdata.indicator.style.left);
//get Element at x, y
dragdata.indicator.style.display = "block";
dragdata.indicator.style.top = "10000px";
}
apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE
? window.pageXOffset
: 0) : c.clientX;
apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE
? window.pageYOffset
: 0) : c.clientY;
var el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
if (!el) {
el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
}
if (dragdata.indicator)
dragdata.indicator.style.top = storeIndicatorTopPos;
//console.log("INDICATOR AFTER: "+dragdata.indicator.style.top+" "
//+dragdata.indicator.style.left+" "+apf.DragServer.dragdata.x+" "+apf.DragServer.dragdata.y);
//Set Indicator
dragdata.host.$moveDragIndicator(c);
//get element and call events
var receiver = apf.findHost(el);
//Run Events
if (receiver)
apf.DragServer.dragover(receiver, el, e);
else if (apf.DragServer.last)
apf.DragServer.dragout(apf.DragServer.last, e);
apf.DragServer.lastTime = new Date().getTime();
},
onmouseup: function(e) {
e = e || window.event;
var c = {
clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
};
if (!apf.DragServer.dragdata.started
&& Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6
&& Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6) {
apf.DragServer.stop(true, null, e)
return;
}
//get Element at x, y
var indicator = apf.DragServer.dragdata.indicator,
storeIndicatorTopPos = indicator.style.top;
//apf.console.info("INDICATOR UP BEFORE: "+indicator.style.top+" "+indicator.style.left);
if (indicator)
indicator.style.top = "10000px";
apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE
? window.pageXOffset
: 0) : c.clientX;
apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE
? window.pageYOffset
: 0) : c.clientY;
var el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
if (!el) {
el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
}
indicator.style.top = storeIndicatorTopPos;
//apf.console.info("INDICATOR UP AFTER: "+indicator.style.top+" "+indicator.style.left);
//get element and call events
var host = apf.findHost(el);
//Run Events
if (apf.DragServer.host && host != apf.DragServer.host)
apf.DragServer.dragout(apf.DragServer.host, e);
var success = apf.DragServer.dragdrop(host, el, apf.DragServer.dragdata.host, e);
apf.DragServer.stop(true, success, e);
}
};
/*
* @private
*/
apf.MultiselectDragDrop = function() {
// *** Drag & Drop *** //
this.diffX =
this.diffY = 0;
this.multiple = false;
this.lastDragNode = null;
this.lastel = null;
this.$showDragIndicator = function(sel, e) {
var srcEl = e.originalTarget || e.srcElement || e.target;
this.multiple = sel.length > 1;
if (this.multiple) {
this.diffX = e.scrollX;
this.diffY = e.scrollY;
}
else {
var itemNode = apf.xmldb.findHtmlNode(sel[0], this);
this.diffX = -1 * (e.offsetX - parseInt(apf.getStyleRecur(itemNode, "padding-left").replace(/px$/, "") - 10));
this.diffY = -1 * e.offsetY;
}
var prefix = this.oDrag.className.split(" ")[0]
//@todo the class should be removed here
this.$setStyleClass(this.oDrag, (this.multiple
? prefix + "_multiple" : "") + (this["class"] ? " " + this["class"] : ""), [prefix + "_multiple"]);
if (this.multiple) {
document.body.appendChild(this.oDrag);
return this.oDrag;
}
else if (this.localName == "datagrid") {
if (this.lastDragNode)
apf.destroyHtmlNode(this.lastDragNode);
sel = this.$selected || this.$caret;
var oDrag = sel.cloneNode(true);
oDrag.removeAttribute("onmousedown");oDrag.onmousedown = null;
oDrag.removeAttribute("onmouseup");oDrag.onmouseup = null;
oDrag.removeAttribute("onmouseout");oDrag.onmouseout = null;
oDrag.removeAttribute("ondblclick");oDrag.ondblclick = null;
document.body.appendChild(oDrag);
oDrag.style.position = "absolute";
oDrag.style.width = sel.offsetWidth + "px";
oDrag.style.display = "none";
oDrag.removeAttribute("id");
this.$setStyleClass(oDrag, "draggrid");
var nodes = sel.childNodes;
var dragnodes = oDrag.childNodes;
for (var i = nodes.length - 1; i >= 0; i--) {
if (dragnodes[i].nodeType == 1)
dragnodes[i].style.width = apf.getStyle(nodes[i], "width");
}
//@todo apf3.0 remove all the event handlers of the children.
return (this.lastDragNode = oDrag);
}
else {
var sel = this.$selected || this.$caret,
width = apf.getStyle(this.oDrag, "width");
if (!sel)
return;
// if (!width || width == "auto")
// this.oDrag.style.width = (sel.offsetWidth - apf.getWidthDiff(this.oDrag)) + "px";
this.$updateNode(this.selected, this.oDrag);
}
apf.window.zManager.set("drag", this.oDrag);
return this.oDrag;
};
this.$hideDragIndicator = function(success) {
var oDrag = this.lastDragNode || this.oDrag, _self = this;
if (!this.multiple && !success && oDrag.style.display == "block") {
if (!this.$selected && !this.$caret)
return;
var pos = apf.getAbsolutePosition(this.$selected || this.$caret);
apf.tween.multi(oDrag, {
anim: apf.tween.easeInOutCubic,
steps: 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
* <a:textarea draggable="true" resizable="true" />
* ```
*
* @class apf.Interactive
* @baseclass
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 1.0
*
* @see apf.appsettings.outline
* @see apf.appsettings.resize-outline
* @see apf.appsettings.drag-outline
*/
/**
* @attribute {Boolean} draggable Sets or gets whether an element is draggable. The user will
* able to move the element around while holding the mouse button down on the
* element.
*
* #### Example
*
* ```xml
* <a:bar
* draggable = "true"
* width = "200"
* height = "200"
* left = "10"
* top = "10" />
* ```
*/
/**
* @attribute {Boolean} resizable Sets or gets whether an element is resizable.
*
* The user will able
* to resize the element by grabbing one of the four edges of the element and
* pulling it in either direction. Grabbing the corners allows users to
* resize horizontally and vertically at the same time. The right bottom corner
* is special, because it offers an especially big grab area. The size of this
* area can be configured in the skin of the element.
*
* #### Example
*
* ```xml
* <a:window
* resizable = "true"
* visible = "true"
* width = "400"
* height = "200" />
* ```
*/
/**
* @attribute {Number} minwidth Sets or gets the minimum horizontal size the element can get when resizing.
*/
/**
* @attribute {Number} minheight Sets or gets the minimum vertical size the element can get when resizing.
*/
/**
* @attribute {Number} maxwidth Sets or gets the maximum horizontal size the element can get when resizing.
*/
/**
* @attribute {Number} maxheight Sets or gets the maximum vertical size the element can get when resizing.
*
*/
/**
* @event drag Fires when the widget has been dragged.
*/
/**
* @event resizestart Fires before the widget is resized.
* @cancelable Prevents this resize action to start.
* @param {Object} e The standard event object. It contains the following property:
* - type ([[String]]): the type of resize. This is a combination of the four directions--`"n"`, `"s"`, `"e"`, `"w"`.
*/
/**
* @event resize Fires when the widget has been resized.
*
*/
apf.Interactive = function(){
var nX, nY, rX, rY, startPos, lastCursor = null, l, t, r, b, lMax, tMax, lMin,
tMin, w, h, we, no, ea, so, rszborder, rszcorner, marginBox,
verdiff, hordiff, _self = this, posAbs, oX, oY, overThreshold,
dragOutline, resizeOutline, myPos, startGeo;
this.$regbase = this.$regbase | apf.__INTERACTIVE__;
this.$dragStart = function(e, reparent) {
var nativeEvent = e || event;
if (!reparent && nativeEvent.button == 2)
return;
{
dragStart.apply(nativeEvent.srcElement || this, arguments);
}
}
this.$propHandlers["draggable"] = function(value) {
if (apf.isFalse(value))
this.draggable = value = false;
else if (apf.isTrue(value))
this.draggable = value = true;
var o = this.editable ? this.$ext : this.oDrag || this.$ext;
if (value)
apf.addListener(o, "mousedown", this.$dragStart);
else
apf.removeListener(o, "mousedown", this.$dragStart);
//deprecated??
if (o.interactive & 1)
return;
o.interactive = (o.interactive||0)+1;
//this.$ext.style.position = "absolute";
};
this.$propHandlers["resizable"] = function(value) {
if (apf.isFalse(value))
this.resizable = false;
else if (apf.isTrue(value))
this.resizable = "true";
this.$ext.style.cursor = "";
var o = this.oResize || this.$ext;
if (o.interactive & 2)
return;
if (!_self.editable) {
apf.addListener(o, "mousedown", function(){
resizeStart.apply(o, arguments);
});
apf.addListener(o, "mousemove", function(){
resizeIndicate.apply(o, arguments);
});
}
o.interactive = (o.interactive||0)+2;
//this.$ext.style.position = "absolute";
rszborder = this.$getOption && parseInt(this.$getOption("Main", "resize-border")) || 3;
rszcorner = this.$getOption && parseInt(this.$getOption("Main", "resize-corner")) || 12;
marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth"));
};
/*
this.$propHandlers["minwidth"] =
this.$propHandlers["maxwidth"] =
this.$propHandlers["minheight"] =
this.$propHandlers["maxheight"] = function(value, prop) {
if (this.aData)
this.aData[prop] = parseInt(value);
}
if (this.aData) {
this.aData.minwidth = this.minwidth;
this.aData.minheight = this.minheight;
}*/
this.$cancelInteractive = function(){
document.onmouseup(null, true);
}
function dragStart(e, reparent) {
if (!e) e = event;
if (!reparent && (!_self.draggable || apf.dragMode))//_self.editable ||
return;
dragOutline = false;
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></$1>");
frag = document.createDocumentFragment();
for (i = 0, l = o.childNodes.length; i < l; i++) {
node = o.childNodes[0];
frag.appendChild(node);
}
if (beforeNode)
htmlNode.insertBefore(frag, beforeNode);
htmlNode.appendChild(frag);
};
apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) {
if (htmlNode.nodeType != 11 && !htmlNode.style)
return htmlNode.appendChild(xmlNode);
if (!s) {
s = apf.html_entity_decode(xmlNode.serialize
? xmlNode.serialize(true)
: ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2)
? xmlNode.nodeValue
: serializer.serializeToString(xmlNode)));
}
o.innerHTML = s.replace(/<([^>]+)\/>/g, "<$1></$1>");
if (beforeNode)
htmlNode.insertBefore(o.firstChild, beforeNode);
else
htmlNode.appendChild(o.firstChild);
return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild;
};
/* ******** Error Compatibility **********************************************
Error Object like IE
****************************************************************************/
function Error(nr, msg) {
this.message = msg;
this.nr = nr;
}
apf.getHtmlLeft = function(oHtml) {
return (oHtml.offsetLeft
+ (parseInt(apf.getStyle(oHtml.parentNode, "borderLeftWidth")) || 0));
};
apf.getHtmlRight = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowWidth()
: p.offsetWidth)
- oHtml.offsetLeft - oHtml.offsetWidth
- (2 * (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0))
- (parseInt(apf.getStyle(p, "borderRightWidth")) || 0));
};
apf.getHtmlTop = function(oHtml) {
return (oHtml.offsetTop
+ (parseInt(apf.getStyle(oHtml.parentNode, "borderTopWidth")) || 0));
};
apf.getHtmlBottom = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowHeight()
: p.offsetHeight)
- oHtml.offsetTop - oHtml.offsetHeight
- (2 * (parseInt(apf.getStyle(p, "borderTopWidth")) || 0))
- (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0));
};
apf.getBorderOffset = function(oHtml) {
return [-1 * (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0),
-1 * (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)];
};
};
/**
* 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></$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, "&amp;")
.replace(/</g, "&lt;").replace(/>/g, "&gt;");
});
HTMLElement.prototype.__defineGetter__("innerText", function(){
return this.innerHTML.replace(/<[^>]+>/g,"")
.replace(/\s\s+/g, " ").replace(/^\s+|\s+$/g, " ");
});
HTMLElement.prototype.__defineGetter__("outerHTML", function(){
return (new XMLSerializer()).serializeToString(this);
});
}
}
/* ******** XML Compatibility ************************************************
Giving the Mozilla XML Parser the same interface as IE's Parser
****************************************************************************/
var ASYNCNOTSUPPORTED = false;
//Test if Async is supported
try {
XMLDocument.prototype.async = true;
ASYNCNOTSUPPORTED = true;
} catch (e) {/*trap*/}
//Document.prototype.onreadystatechange = null;
Document.prototype.parseError = 0;
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]*</g, "><");
xmlParser = new DOMParser();
xmlParser = xmlParser.parseFromString(message, "text/xml");
if (!noError)
this.xmlParseError(xmlParser);
}
else {
xmlParser = document.implementation.createDocument("", "", null);
}
return xmlParser;
};
apf.xmlParseError = function(xml) {
//if (xml.documentElement.tagName == "parsererror") {
if (xml.getElementsByTagName("parsererror").length) {
var nodeValue = xml.documentElement.firstChild.nodeValue;
if (nodeValue != null) {
var str = nodeValue.split("\n"),
linenr = str[2].match(/\w+ (\d+)/)[1],
message = str[0].replace(/\w+ \w+ \w+: (.*)/, "$1");
} else {
if (nodeValue = xml.documentElement.firstChild.getElementsByTagName('div')[0].firstChild.nodeValue) {
var linenr = nodeValue.match(/line\s(\d*)/)[1] || "N/A",
message = nodeValue.match(/column\s\d*:(.*)/)[1] || "N/A";
}
else {
var linenr = "N/A",
message = "N/A";
}
}
var srcText = xml.documentElement.lastChild.firstChild,//.split("\n")[0];
srcMsg = "";
if (srcText && srcText.nodeValue) {
srcMsg = "\nSource Text : " + srcText.nodeValue.replace(/\t/gi, " ")
}
throw new Error(apf.formatErrorString(1050, null,
"XML Parse Error on line " + linenr, message + srcMsg));
}
return xml;
};
apf.xmldb.setReadyState = function(oDoc, iReadyState) {
oDoc.readyState = iReadyState;
if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function")
oDoc.onreadystatechange();
};
apf.xmldb.loadHandler = function(oDoc) {
if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror")
oDoc.parseError = -1;
apf.xmldb.setReadyState(oDoc, 4);
};
//
//Fix XML Data-Island Support Problem with Form Tag
apf.Init.add(function(){
var i, nodes = document.getElementsByTagName("form");
for (i = 0; i < nodes.length; i++)
nodes[i].removeNode();
nodes = document.getElementsByTagName("xml");
for (i = 0; i < nodes.length; i++)
nodes[i].removeNode();
nodes = null;
});
/*window.onerror = function(message, filename, linenr) {
if (++ERROR_COUNT > MAXMSG) return;
filename = filename ? filename.match(/\/([^\/]*)$/)[1] : "[Mozilla Library]";
new Error("---- APF Error ----\nProcess : Javascript code in '" + filename + "'\nLine : " + linenr + "\nMessage : " + message);
return false;
}*/
if (document.body)
document.body.focus = function(){};
apf.getOpacity = function(oHtml) {
return apf.getStyle(oHtml, "opacity");
};
apf.setOpacity = function(oHtml, value) {
oHtml.style.opacity = value;
};
};
/**
* Compatibility layer for Webkit based browsers.
* @private
*/
apf.runWebkit = function(){
if (XMLHttpRequest.prototype.sendAsBinary === undefined) {
if (window.ArrayBuffer) {
/**
* Binary support for Chrome 7+ which implements [ECMA-262] typed arrays
*
* For more information, see <http://www.khronos.org/registry/typedarray/specs/latest/>.
*/
XMLHttpRequest.prototype.sendAsBinary = function(string) {
var bytes = Array.prototype.map.call(string, function(c) {
return c.charCodeAt(0) & 0xff;
});
this.send(new Uint8Array(bytes).buffer);
};
}
}
HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) {
if (sExpr.substr(0,2) == "//")
sExpr = "." + sExpr;
try {
var oResult = this.evaluate(sExpr, (contextNode || this),
this.createNSResolver(this.documentElement),
7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
}
catch (ex) {
try {
var oResult = this.evaluate("child::" + sExpr, (contextNode || this),
this.createNSResolver(this.documentElement),
7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
}
catch (ex) {
throw new Error("XPath error: " + ex.message + "\nLine: " + ex.lineNumber + "\nExpression: '" + sExpr + "'");
}
}
var nodeList = new Array(oResult.snapshotLength);
nodeList.expr = sExpr;
for (var i = nodeList.length - 1; i >= 0; i--)
nodeList[i] = oResult.snapshotItem(i);
return nodeList;
};
//Element.selectNodes
Text.prototype.selectNodes =
Attr.prototype.selectNodes =
Element.prototype.selectNodes = function(sExpr) {
return this.ownerDocument.selectNodes(sExpr, this);
};
//XMLDocument.selectSingleNode
HTMLDocument.prototype.selectSingleNode = XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) {
var nodeList = this.selectNodes("(" + sExpr + ")[1]", contextNode ? contextNode : null);
return nodeList.length > 0 ? nodeList[0] : null;
};
//Element.selectSingleNode
Text.prototype.selectSingleNode =
Attr.prototype.selectSingleNode =
Element.prototype.selectSingleNode = function(sExpr) {
return this.ownerDocument.selectSingleNode(sExpr, this);
};
var serializer = new XMLSerializer();
apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
var node, frag, a, i, l;
if (nodeList) {
frag = document.createDocumentFragment();
a = [], i = 0, l = nodeList.length;
for (; i < l; i++) {
if (!(node = nodeList[i])) continue;
frag.appendChild(node);
}
}
(beforeNode || htmlNode).insertAdjacentHTML(beforeNode
? "beforebegin"
: "beforeend", s || apf.html_entity_decode(serializer.serializeToString(frag))
.replace(/<([^>]+)\/>/g, "<$1></$1>"));
};
apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) {
if (htmlNode.nodeType != 11 && !htmlNode.style)
return htmlNode.appendChild(xmlNode);
if (!s) {
s = apf.html_entity_decode(xmlNode.serialize
? xmlNode.serialize(true)
: ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2)
? xmlNode.nodeValue
: serializer.serializeToString(xmlNode)));
}
(beforeNode || htmlNode).insertAdjacentHTML(beforeNode
? "beforebegin"
: "beforeend", s.match(/<(IMG|LINK|META|BR|HR|BASEFONT)[^\/>]*/i) ? s.replace(/<([^>]+)\/>/g, "<$1 />") : s.replace(/<([^>]+)\/>/g, "<$1></$1>"));
return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild;
};
apf.getHtmlLeft = function(oHtml) {
return oHtml.offsetLeft;
};
apf.getHtmlRight = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowWidth()
: p.offsetWidth)
- oHtml.offsetLeft - oHtml.offsetWidth
- (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0)
- (parseInt(apf.getStyle(p, "borderRightWidth")) || 0));
};
apf.getHtmlTop = function(oHtml) {
return oHtml.offsetTop
};
apf.getHtmlBottom = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowHeight()
: p.offsetHeight)
- oHtml.offsetTop - oHtml.offsetHeight
- (parseInt(apf.getStyle(p, "borderTopWidth")) || 0)
- (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0));
};
apf.getBorderOffset = function(oHtml) {
return [0,0];
};
if (apf.runNonIe)
apf.runNonIe();
};
/*
* Crypt.Barrett, a class for performing Barrett modular reduction computations in
* JavaScript.
*
* Requires BigInt.js.
*
* Copyright 2004-2005 David Shapiro.
*
* You may use, re-use, abuse, copy, and modify this code to your liking, but
* please keep this header.
*
* Thanks!
*
* @author Dave Shapiro <dave AT ohdave DOT com>
*/
apf.crypto.Base64 = (function() {
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// public method for encoding
function encode(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "",
tmp_arr = [];
if (!data)
return data;
data = apf.crypto.UTF8.encode(data + "");
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3)
+ b64.charAt(h4);
}
while (i < data.length);
enc = tmp_arr.join("");
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
}
// public method for decoding
function decode(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = [];
if (!data) {
return data;
}
data += "";
do { // unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits>>16 & 0xff;
o2 = bits>>8 & 0xff;
o3 = bits & 0xff;
if (h3 == 64)
tmp_arr[ac++] = String.fromCharCode(o1);
else if (h4 == 64)
tmp_arr[ac++] = String.fromCharCode(o1, o2);
else
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
while (i < data.length);
return apf.crypto.UTF8.decode(tmp_arr.join(""));
}
return {
decode: decode,
encode: encode
};
})();
apf.crypto.UTF8 = {
// private method for UTF-8 encoding
encode: function (string) {
// Encodes an ISO-8859-1 string to UTF-8
//
// version: 905.1217
// discuss at: http://phpjs.org/functions/utf8_encode
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: sowberry
// + tweaked by: Jack
// + bugfixed by: Onno Marsman
// + improved by: Yves Sucaet
// + bugfixed by: Onno Marsman
// * example 1: utf8_encode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
string = (string + "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
var tmp_arr = [],
start = 0,
end = 0,
c1, enc;
for (var n = 0, l = string.length; n < l; n++) {
c1 = string.charCodeAt(n);
enc = null;
if (c1 < 128) {
end++;
}
else if ((c1 > 127) && (c1 < 2048)) {
enc = String.fromCharCode((c1 >> 6) | 192)
+ String.fromCharCode((c1 & 63) | 128);
}
else {
enc = String.fromCharCode((c1 >> 12) | 224)
+ String.fromCharCode(((c1 >> 6) & 63) | 128)
+ String.fromCharCode((c1 & 63) | 128);
}
if (enc !== null) {
if (end > start)
tmp_arr.push(string.substring(start, end));
tmp_arr.push(enc);
start = end = n + 1;
}
}
if (end > start)
tmp_arr.push(string.substring(start, string.length));
return tmp_arr.join("");
},
// private method for UTF-8 decoding
decode: function (str_data) {
// Converts a UTF-8 encoded string to ISO-8859-1
//
// version: 905.3122
// discuss at: http://phpjs.org/functions/utf8_decode
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + input by: Aman Gupta
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Norman "zEh" Fuchs
// + bugfixed by: hitwork
// + bugfixed by: Onno Marsman
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// * example 1: utf8_decode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0;
str_data += "";
while (i < str_data.length) {
c1 = str_data.charCodeAt(i);
if (c1 < 128) {
tmp_arr[ac++] = String.fromCharCode(c1);
i++;
}
else if ((c1 > 191) && (c1 < 224)) {
c2 = str_data.charCodeAt(i+1);
tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = str_data.charCodeAt(i+1);
c3 = str_data.charCodeAt(i+2);
tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12)
| ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return tmp_arr.join('');
}
};
/*
* BigInt, a suite of routines for performing multiple-precision arithmetic in
* JavaScript.
*
* Copyright 1998-2005 David Shapiro.
*
* You may use, re-use, abuse,
* copy, and modify this code to your liking, but please keep this header.
* Thanks!
*
* @author Dave Shapiro <dave AT ohdave DOT com>
* @author Ian Bunning
*
* IMPORTANT THING: Be sure to set maxDigits according to your precision
* needs. Use the setMaxDigits() function to do this. See comments below.
*
* Tweaked by Ian Bunning
* Alterations:
* Fix bug in function biFromHex(s) to allow
* parsing of strings of length != 0 (mod 4)
*
* Changes made by Dave Shapiro as of 12/30/2004:
*
* The BigInt() constructor doesn't take a string anymore. If you want to
* create a BigInt from a string, use biFromDecimal() for base-10
* representations, biFromHex() for base-16 representations, or
* biFromString() for base-2-to-36 representations.
*
* biFromArray() has been removed. Use biCopy() instead, passing a BigInt
* instead of an array.
*
* The BigInt() constructor now only constructs a zeroed-out array.
* Alternatively, if you pass <true>, it won't construct any array. See the
* biCopy() method for an example of this.
*
* Be sure to set maxDigits depending on your precision needs. The default
* zeroed-out array ZERO_ARRAY is constructed inside the setMaxDigits()
* function. So use this function to set the variable. DON'T JUST SET THE
* VALUE. USE THE FUNCTION.
*
* ZERO_ARRAY exists to hopefully speed up construction of BigInts(). By
* precalculating the zero array, we can just use slice(0) to make copies of
* it. Presumably this calls faster native code, as opposed to setting the
* elements one at a time. I have not done any timing tests to verify this
* claim.
* Max number = 10^16 - 2 = 9999999999999998;
* 2^53 = 9007199254740992;
*/
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 <dave AT ohdave DOT com>
*/
(function(global) {
function rotate_left(n,s) {
var t4 = ( n<<s ) | (n>>>(32-s));
return t4;
};
/*
function lsb_hex(val) { // Not in use; needed?
var str="";
var i;
var vh;
var vl;
for ( i=0; i<=6; i+=2 ) {
vh = (val>>>(i*4+4))&0x0f;
vl = (val>>>(i*4))&0x0f;
str += vh.toString(16) + vl.toString(16);
}
return str;
};
*/
function cvt_hex(val) {
var str="";
var i;
var v;
for ( i=7; i>=0; i-- ) {
v = (val>>>(i*4))&0x0f;
str += v.toString(16);
}
return str;
};
global.SHA1 = function(str) {
// Calculate the sha1 hash of a string
//
// version: 905.3122
// discuss at: http://phpjs.org/functions/sha1
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + namespaced by: Michael White (http://getsprink.com)
// + input by: Brett Zamir (http://brett-zamir.me)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// - depends on: utf8_encode
// * example 1: sha1('Kevin van Zonneveld');
// * returns 1: '54916d2e62f65b3afa6e192e6a601cdbe5cb5897'
var blockstart, i, j, W = new Array(80),
H0 = 0x67452301,
H1 = 0xEFCDAB89,
H2 = 0x98BADCFE,
H3 = 0x10325476,
H4 = 0xC3D2E1F0,
A, B, C, D, E, temp;
str = apf.crypto.UTF8.encode(str);
var str_len = str.length,
word_array = [];
for (i = 0; i < str_len - 3; i += 4) {
j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 |
str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3);
word_array.push(j);
}
switch (str_len % 4) {
case 0:
i = 0x080000000;
break;
case 1:
i = str.charCodeAt(str_len - 1) << 24 | 0x0800000;
break;
case 2:
i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1)
<< 16 | 0x08000;
break;
case 3:
i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2)
<< 16 | str.charCodeAt(str_len - 1) << 8 | 0x80;
break;
}
word_array.push( i );
while ((word_array.length % 16) != 14)
word_array.push( 0 );
word_array.push(str_len >>> 29);
word_array.push((str_len << 3) & 0x0ffffffff);
for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
for (i = 0; i < 16; i++)
W[i] = word_array[blockstart + i];
for (i = 16; i <= 79; i++)
W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
A = H0;
B = H1;
C = H2;
D = H3;
E = H4;
for (i = 0; i <= 19; i++) {
temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i]
+ 0x5A827999) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 20; i <= 39; i++) {
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1)
& 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 40; i <= 59; i++) {
temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i]
+ 0x8F1BBCDC) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 60; i <= 79; i++) {
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6)
& 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
H0 = (H0 + A) & 0x0ffffffff;
H1 = (H1 + B) & 0x0ffffffff;
H2 = (H2 + C) & 0x0ffffffff;
H3 = (H3 + D) & 0x0ffffffff;
H4 = (H4 + E) & 0x0ffffffff;
}
temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
return temp.toLowerCase();
};
})(apf.crypto);
/**
* @constructor
* @parser
*
* @author Rik Arends
* @version %I%, %G%
* @since 3.0
*/
apf.lm = new (function(){
var statement_lut = { // all js statements to see its NOT an expression
"var": 1, "for": 1, "while": 1, "do": 1, "if": 1, "else": 1,
"switch": 1, "case": 1, "break": 1, "continue": 1, "default": 1,
"function":2, "return": 1, "try": 1, "catch": 1, "throw":1,
"debugger": 1, "alert": 1, "confirm": 1,"setTimeout": 1,"setInterval": 1,"delete": 1, "export": 1, "import": 1,
"label": 1, "foreach":1, "each": 1, "eachrev":1, "foreachrev":1, "node": 1, "local": 1, "yield": 1,
"let":1, "finally":1, "delete":1
},
type_lut = { // used for optimizing the parse regexp
"\n": 1, "\r\n": 1, "==":2, "++":2, "--":2, '"': 5, "'": 5,
"<!--": 6, "-->": 6, "/*": 6, "//": 6, "*/": 6, "{": 7, "}": 8,
"[": 9, "]": 10, "(": 11, ")": 12, "<": 13, ">": 14, "+=":2,
"-=":2, "/=":2, "*=":2, "!=":2
},
type_close = { // handy
"{": "}", "[": "]", "(": ")", "{{":"}"
},
xpath_axes = { // used to detect xpath axes or model
"ancestor": 1, "ancestor-or-self": 1, "attribute": 1, "child": 1,
"descendant": 1, "descendant-or-self": 1, "following": 1,
"following-sibling": 1, "namespace": 1, "parent": 1, "preceding": 1,
"self": 1
},
misc_tok = { // misc token lookup
";":1, ",":2, "^":3, "=":4, "+=":4, "-=":4, "/=":4, "*=":4, "/":5, ":":6
},
xpath_lut_code = { // which autoxpath to use when doing macro({xpath})
"~": "_val(_n,", "%": "_nod(_n,", "*": "_nods(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_text = { // which autoxpath to use when doing xpath macros in textmode
"~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_attr = { // xpath lut for node attributes
"~": "_val(_n,", "%": "_val(_n,", "*": "_val(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_node,
xpath_lut_node_normal = { // normal xpath lookup
"~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_node_langedit = { // language edit xpath lookup
"~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lnged("
},
pre_regexp = {
"[":1, "(":1, ",":1, "=":1, "return":1, "throw":1
},
pre_xpath = {
"else":1, "return":1, "delete":1
},
pre_plain = {
"do":1, "else":1, "try":1
},
op_lut = { // obj.prop += operator lut
"=" : "_asn(", "+=": "_add(", "-=": "_sub(", "/=": "_div(", "*=": "_mul("
},
new_block = {
"+":1, "%":1, "-":1, "/":1, "=":1, "(":1, "?":1, "|":1, "^":1, "[":1,
"&":1, "*":1, "!":1, ":":1, "<":1, ",":1
},
out_context_word = { // token preceeding a word signalling a new output
"{":1, "} ":1, ")":1, ") ":1, ";":1, "\n":1, "else":1
},
out_context_paren = { // token preceeding a paren signalling a new output
"{":1, ";":1, "\n":1, "else":1
}, // special markers: ') ' tail of xpath macro. ') ' func def, tok=') ' its not an if while etc.
markup_in_code_lut = {
"} ":1, ") ":1,// the } used when it wasnt a code-expression
"(":1, /*")":1,*/ ";":1, "&":1, "^":1, "|":1, ",":1, '"':1, "'":1, "=":1,
"!=":2,"+=":2, "-=":2, "/=":2, "*=":2, "?":1, "{":1, "}":1, ">":1, "[":1,
/*"]":1,*/ "+":1, ":":1, "else":1, "return":1
},
block_autoappend = { // token preceeding block signalling auto append
'"':1, "'":1, ">":1, "]":1, "}":1
},
unesc_lut = { // unescape in code and xpath mode
"\\\"": "\"", "\\\'": "\'", "\\{": "{", "\\}": "}", "\\[": "[",
"\\]": "]", "\\(":"(", "\\)":")", "\\\\":"\\"
},
call_exclusion = {
"alert": 1, "confirm" :1, "setTimeout":1, "setInterval":1, "switch":1,
"call":1, "return":1, "throw":1, "case":1, "catch":1,
"abs":1,"acos":1,"asin":1,"atan":1,"atan2":1,"ceil":1,
"cos":1,"exp":1,"floor":1,"log":1,"max":1,"min":1,
"pow":1,"random":1,"round":1,"sin":1,"sqrt":1,"tan":1,"lin":1,"linear":1,
"idx":1,"sort":1,"typeof":1
},
is_out_space = {
" ":1, "\n":1
},
newline_notallowed = {
"{":1, ";":1, "(":1, "\n":1
},//@todo !verify and document! character escaping system
unesc_str = { // unescape in string mode
"\\{": "{", "\\}": "}", "\\[": "[", "\\]": "]", "\\(": "(", "\\)": ")"
},
unesc_txt = { // unescape in text mode
"\\{" : "{", "\\}" : "}", "\\[" : "[", "\\]" : "]", "\\(" : "(",
"\\)" : ")", "\\\\": "\\\\\\\\", "\\" :"\\\\", "\\<" : "<", "\\>" : ">"
},
xml_code_operators = { // word to operand lookup table for easy use in xml
"lte": "<=", "gte": ">=", "lt": "<", "gt": ">", "and": "&&", "or": "||",
"andbin": "&", "orbin": "|", "LTE": "<=", "GTE": ">=", "LT": "<",
"GT": ">", "AND": "&&", "OR": "||", "ANDBIN": "&", "ORBIN": "|"
},
xpath_macro = { // which autoxpath to use when doing macro({xpath})
0 : "_val(_n,",
1 : "_valcr(_n,_cr,",
2 : "_nod(_n,",
3 : "_nodcr(_n,_cr,",
4 : "_nods(_n,",
5 : "_xpt(_n,",
6 : "_valst(_n,",
7 : "_valed(_n,",
8 : "_valattr(_n,",
"foreach" : "_nods(_n,",
"each" : "_nods(_n,",
"foreachrev": "_nods(_n,",
"eachrev" : "_nods(_n,",
"xabs" : "_valst(_n,",
// "edit" : "_argwrap(_n,",
// "edit" : "_val(_n,", // toggled by liveedit
"local" : "_nod(_n,",
"tagName" : "_nod(_n,",
"localName" : "_nod(_n,",
"xml" : "_xmlq(",
"_call" : "_val(_n,"
},
xpath_model = { // which autoxpath to use when doing macro({xpath})
"_val(_n," : "_valm(",
"_valcr(_n,_cr,": "_valcr(0,_cr,",
"_nod(_n," : "_nodm(",
"_nodcr(_n,_cr,": "_nodcr(0,_cr,",
"_nods(_n," : "_nodsm(",
"_argwrap(_n," : "_argwrapm(",
"_xml(_n," : "_xml(0,",
"_xmls(_n," : "_xmls(0,",
"_cnt(_n," : "_cntm(",
"_xpt(_n," : "_xptm(",
"_valst(_n," : "_valm(",
"_valed(_n," : "_valed(0,",
"_lng(" : "_lng(",
"_lnged(" : "_lnged("
},
parserx = /(\r?[\n]|\/\*|\*\/|\/\/|\<\!\-\-|\-\-\>|[=\!+\/\*-]=|\+\+|\-\-|["'{(\[\])}\]\<\>]|$)|([ \t]+)|([a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF.$_][\w.$_]*)|(\d[x\d.]*)|(\\?[\w._?,:;!=+-\\\/^&|*"'[\]{}()%$#@~`<>]?)/g,
selfrx = /(^|\|)(?!\@|text\(\)|\.\.|[\w\-\:]+?\:\:)/g, // inject self regexp
macro_o = {},
macro_c = {},
macro_m = {},
// config vars
c_async_lut = apf.$asyncObjects || { // used to figure out if the thing before the. is an async obj
"comm" :1,
"rpc" :1,
"http" :1,
"apf.ajax" :1
},
c_process_async,
c_xpathmode, // guess 'node' as the type for {} o_xpathpairs, 1 = node, 2 = nodes
c_elemxpath, // which xpath macro to use inside an element
c_statexpath, // which xpath to use for the stateful value
c_injectself, // add self:: in some o_xpathpairs
c_propassign, // support property assigns
c_export, // export function to some object
// outputs
o, ol, // output and output len
o_asyncs, // number of async calls
o_xpathpairs, // all xpaths and their models in pairs
o_props, // the js properties found
o_segs, // segments at groundlevel
o_xpaths, // xpaths at groundlevel
o_models, // number of xpaths with models
// temp and state vars
s = [], sl, // scopestack and scopestack len
bt = [], // backtrack lut
bts = [], // backtrack string stack
parse_mode, // the parse parse_mode
scope, // ol of a scope begni
segment, // the ol of a segment begin
start_tok, // the token a string or comment was started with
str,str_len, // length of the input string
line_no, // line number we are at
nesting, // nesting count
// last state vars
last_tok, // last token
last_type, // last type
last_dot, // . pos when last token was a word
last_model, // last model found
last_prop, // last property found
last_cmt_mode, // the parse mode outside the comment
last_cmt_tok, // last token before comment
last_cmt_type, // last type before comment
last_line, // offset of last newline
last_ns, // last namespace found
last_word; // last word in code mode
// macros used in code()
macro_o["if"] = "if(",
macro_c["if"] = ")",
macro_o["while"] = "while(",
macro_c["while"] = ")",
macro_o["for"] = "for(",
macro_c["for"] = ")",
macro_o["switch"] = "switch(",
macro_c["switch"] = ")",
macro_o["catch"] = "catch(",
macro_c["catch"] = ")",
macro_c["function"] = ") ";
macro_o.foreach =
macro_o.each = "\nfor(var _t=_t||[],_t=(_t.push(_n,0,(",
macro_c.foreach =
macro_c.each = ")||[]),_t);(_n=_t[_t.length-1][_t[_t.length-2]++])||(_t.length-=2,_n=_t.pop(),0);)",
macro_o.foreachrev =
macro_o.eachrev = "\nfor(var _t=_t||[],_t=(_t.push(_n,0,(",
macro_c.foreachrev =
macro_c.eachrev = ")||[]),_t);(_n=_t[_t.length-1][_t[_t.length-1].length-(_t[_t.length-2]++)-1])||(_t.length-=2,_n=_t.pop(),0);)",
macro_o.local = "\nfor(var _t=_t||[],_t=(_t.push(_n,((_n=_local(",
macro_c.local = ")),1)),_t);(_t[_t.length-1]--&&_n)||(_t.length--,_n=_t.pop(),0);)",
macro_o._editlm = "_valedx(true, ", // only serves to switch default xpath in edit([xpath])
macro_o._editnormal = "_valedx(false, ", // only serves to switch default xpath in edit([xpath])
macro_c.edit = ")",
macro_o.xabs = " ( ",
macro_c.xabs = " ) ",
macro_o.localName = "_localName(_n",
macro_c.localName = ")",
macro_o.output = "_o.join(''",
macro_c.output = ")",
macro_o.reset = "(_o=[],l=0",
macro_c.reset = ")",
macro_o.index = "apf.getChildNumber.call(apf",
macro_c.index = ")",
macro_o.item = "(_t[_t.length-1][_t[_t.length-2]-1]",
macro_c.item = ")",
macro_o.first = "(_t[_t.length-2]==1",
macro_c.first = ")",
macro_o.last = "(_t[_t.length-2]==_t[_t.length-1].length",
macro_c.last = ")",
macro_o.total = "(_t[_t.length-1].length",
macro_c.total = ")",
macro_o.pos = "(_t[_t.length-2]-1",
macro_c.pos = ")",
macro_o.tagName = "_tagName(_n",
macro_c.tagName = ")",
macro_o._nodeValue = "_nodeValue(_n",
macro_c._nodeValue = ")",
macro_c.async = "])",
macro_c.precall = "])",
macro_c._call = ")";
var call_args_lut = {
_call: ".call(_n",
localName: macro_o.localName,
tagName: macro_o.tagName,
nodeValue: macro_o.nodeValue,
index: macro_o.index
},
// centralized code fragments used in parser/generator
cf_block_o = "(function(){var _o=[],_l=0;\n",
cf_block_c = ";return _l==1?_o[0]:_o.join('');}).call(this)",
cf_async_o = "_async(_n,_c,_a,_w,_f,this,",
cf_async_m = "',_a[++_a.i]||[",
cf_obj_output = "_r=",
cf_mode_output,
cf_str_output = "_o[_l++]=",
cf_def_output = "",
cf_func_o = "{var _o=[],_l=0,_n=this;\n",
cf_func_c = ";\nreturn _l==1?_o[0]:_o.join('');}",
// compile chunks used in compile/match
cc_async_o = "(_a=_a||{}).i=0;try{\n",
cc_async_c = "}catch(_e){if(_e.x)return;throw(_e);}\n",
//cc_async_o = "(_a=_a||{}).i=0;",
//cc_async_c = "",
cc_pc_o = "(_a=_a||{}).i=0;try{_precall(_w);",
cc_pc_c = "}catch(_e){if(_e.x)return;throw(_e);}",
cc_opt_o = "with(_w){",
cc_opt_c = "}",
cc_v_blk_o = "var _o=[],_l=0;_o[_l++]=",
cc_v_blk_ob = "var _o=[],_l=0;\n",
cc_v_blk_c = ";\nreturn _ret(_l==1?_o[0]:_o.join(''));",
cc_v_blk_cb = ";\n_c(_ret(_l==1?_o[0]:_o.join('')),apf.SUCCESS,apf.$lmx);apf.$lmx=null;",
cc_v_ret_o = "return _ret(",
cc_v_ret_c = ");",
cc_v_cb_o = "_c(_ret(",
cc_v_cb_c = "),apf.SUCCESS,apf.$lmx);apf.$lmx=null;\n",
cc_o_blk_o = "var _r=",
cc_o_blk_ob = "var _r;",
cc_o_blk_c = ";\nreturn _r;",
cc_o_blk_cb = ";\n_c(_r,apf.SUCCESS,apf.$lmx);apf.$lmx=null;",
cc_o_blk_ce = ";\n_c(0,apf.SUCCESS,apf.$lmx);apf.$lmx=null;;",
cc_o_ret_o = "return ",
cc_o_ret_c = "",
cc_o_cb_o = "_c(",
cc_o_cb_c = ",apf.SUCCESS);",
cc_f_async_o = "var _f=function(_n,_c,_w,_a){",
cc_f_opt_o = "var _f=function(_n,_w){",
cc_f_o = "var _f=function(_n){",
cc_fc_async_o = "var _f=function(_n,_c,_w,_cr,_a){",
cc_fc_opt_o = "var _f=function(_n,_w,_cr,){",
cc_fc_o = "var _f=function(_n,_cr){",
cc_fe_async_o = "var _f=function(event,_c,_w,_a,_n){",
cc_fe_opt_o = "var _f=function(event,_w,_n){",
cc_fe_o = "var _f=function(event,_n){",
cc_f_c = "}",
cc_f_match_o = "var _f=function(_m){",
cc_m_m_blk = ";\nif(_n=_r){if(!_n.nodeType)_n=_m;",
cc_m_m_value_o = ";\nif(_n=",
cc_m_m_value_c = "){if(!_n.nodeType)_n=_m;",
cc_m_v_string = "\nreturn ",
cc_m_v_o = "\nreturn _ret(",
cc_m_v_c = ");",
cc_m_n_string = "\nreturn _n;",
cc_m_n_o = "\nreturn (_r = (",
// decision point for compileMatch node-mode for the return type
cc_m_n_c = "))?(_r.nodeType?_r:_n):(_r===null?null:_n);",
cc_m_o = "var _r, _n = _m;",
cc_m_brk = ";\n_n = _m;",
cc_m_v_ret = "\nreturn _ret(_nodeValue(_n));" ,
cc_m_n_ret = "\nreturn _n;" ,
cc_m_c = "\n}";
function switchToBlock(no_output){ // used to switch expression mode to block mode
var u, v;
if (o[scope-1] == "{{")
u = scope-1; // scan for our root expression block to switch to block
else
for (v = sl - 2, u = 0; v >= 0 && o[u=(s[v] & 0xfffffff) - 1] != "{{"; v -=2 ){};
if (!no_output && ol > u + 1) // inject auto output unless no output or nothing to output in buffer
o[u] = cf_block_o + cf_str_output
else
o[u] = cf_block_o;
parse_mode = 1;
}
function parser(tok, rx_lut, rx_white, rx_word, rx_num, rx_misc, pos) {
var u, v, w,
type = rx_lut ? type_lut[rx_lut] : (rx_white ? 0 : (rx_word ? 3 : (rx_num ? 4 : (tok ? 2 : 15))));
switch (parse_mode) {
case 0: // ===================== expression parse_mode =========================
case 1: // ========================== block parse_mode =========================
switch (type) {
case 0: // -------- whitespace --------
if ((last_type == 3 && last_tok!='$') || last_type == 4)
o[ol++] = " ";
else if (xpath_lut_code[last_tok])
last_type = 0;// make last_type visible to xpathmode select
break;
case 1: // -------- newline --------
line_no++,
last_line = pos;
if (o[ol-1] != "\n" && !newline_notallowed[last_tok])
o[ol++] = "\n";
if (xpath_lut_code[last_tok])
last_type = 0;// make last_type visible to xpathmode select
break;
case 2: // -------- misc --------
if (v = misc_tok[tok]) {
switch (v) {
case 1: // ';'
if (!s[sl-1]) {// close = macro
o[ol++] = ")",
sl -= 2;
}
if (!parse_mode) { // dont do ; inject newline instead
if (o[ol-1] != "\n" && last_tok != "{" && last_tok != ";")
o[ol++] = "\n";
}
else if (!sl || s[sl - 1]) // dont inject ; if we are in nested assignment macros
o[ol++] = ";";
break;
case 2: // ','
if (!s[sl - 1]) { // close = macro
o[ol++] = ")",
sl -= 2;
}
o[ol++] = ",";
break;
case 3: //'^' // dont output
if (o[ol-1] == "\n" || o[ol - 1] == ";" || last_tok=="{"
|| last_tok == "} " || ol == scope) { // dont output-shortcut requirements
if (!parse_mode)
switchToBlock();
o[ol++] = " "; // two spaces make it modify the output-ifs outcome
}
else
o[ol++] = "^";
break;
case 4: //'= += -= assignment macro mode
if (last_tok!='<' && last_tok!='>'){
// we should only switch to block when we are not in a ( ) scope
if (!parse_mode && o[scope-1]!='(')
switchToBlock(true);
o[ol++] = tok;
// lets scan in reverse to see if we have an output or a non-output
for (v = ol; v >= scope && !statement_lut[o[v]] && !((o[v] == " "
|| o[v] == (nesting ? cf_str_output : cf_mode_output)) && (o[v]="",1)); v--){};
if (last_type == 3 && last_dot>0 && last_tok.charAt(0)!="."){ // prop = macro
if (c_propassign) {
ol -= 2;
while (is_out_space[o[ol]])
ol--;
w = last_tok;
o[ol++] = op_lut[tok], o[ol++] = w.slice(0,last_dot),
o[ol++] = ",'", o[ol++] = w.slice(last_dot+1),
o[ol++] = "',", s[sl++] = scope | (parse_mode << 28),
s[sl++] = ""; // notabene, this stored item is checked everywhere
}
}
}else{
o[ol++] = tok;
}break;
case 5: // '/' // regexp mode
if (pre_regexp[last_tok]) {
s[sl++] = scope | (parse_mode << 28);
s[sl++] = o[ol++] = tok;
scope = segment = ol - 1;
nesting++, parse_mode = 5, start_tok = tok;
}
else
o[ol++] = "/";
break;
case 6: // ':' // switch to {x:1} object mode
if (sl > 2 && s[sl - 1] == "{{" && (ol < scope + 4 && last_type == 5)
|| (ol < scope + 3 && (last_type == 3 || last_type == 4))) {
o[scope-1] = s[sl-1] = "{"
parse_mode = (v = s[sl - 2]) >> 28;
s[sl-2] = v & 0xfffffff,
nesting--;
}
else if (o[ol - 3] == "case" || (last_type == 5 && last_word == "case"))
tok = ";"; //fixes auto output problem
o[ol++] = ":";
break;
default:
o[ol++] = tok;
break;
}
}
else
o[ol++] = unesc_lut[tok] || tok;
break;
case 3: // -------- word --------
case 4: // ------- number -------
if ( v = xml_code_operators[tok] ){
o[ol++] = tok = v, type = 2;
} else {
v = u = w = 0;// last_word used for case 'bla bla':
last_dot = (last_word = tok).lastIndexOf(".");
if (tok.charAt(0) != '.' // .obj shouldnt trigger block
&& ((v = (u = ((out_context_word[last_tok] // check if we need to switch
|| o[ol - 1] == "\n") && !new_block[last_tok]))
&& !s[sl - 1].indexOf("{") && ol > scope)
|| (w = statement_lut[tok])) && !parse_mode){ // check statement
if (w == 2 && s[sl - 1].indexOf("{")) w = 0; // (function() shouldnt trigger blockmode
switchToBlock(w); // pass in statement_lut[tok] as outputflag
}
if (u && !s[sl - 1]) { // assign macro close
o[ol-1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v && parse_mode && !statement_lut[tok] && !call_exclusion[tok]) // inject output
o[ol++] = (nesting ? cf_str_output : cf_mode_output);
if (last_dot > 0 && tok.charAt(0) != ".") // store property
o_props[o[ol++] = last_prop = tok] = 1;
else o[ol++] = tok;
}
break;
case 5: // -------- stringquotes --------
if ((v = (u = ((out_context_word[last_tok] || o[ol - 1]== "\n" )
&& !new_block[last_tok])) && !s[sl - 1].indexOf("{")
&& ol > scope) && !parse_mode) // check if we need to switch to block mode
switchToBlock();
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) { // generate output
o[ol++] = (o[ol-2] != "\n" && block_autoappend[last_tok])
? "+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok])
o[ol++] = "+";
s[sl++] = scope | (parse_mode << 28), s[sl++] = o[ol++] = tok;
scope = segment = ol - 1, nesting++, parse_mode = 5, start_tok = tok;
break;
case 6: // -------- comment --------
if (tok == "*/" || tok== "-->")
throw {
t: "Unmatched comment "+tok,
p: pos
};
last_cmt_mode = parse_mode, last_cmt_tok = last_tok,
last_cmt_type = last_type, parse_mode = 6, start_tok = tok;
break;
case 7: // -------- { --------
if (o[ol - 1] == ") " || (o[ol - 2] == ") " && ol--)) { // ') ' is function def
if (s[sl - 1] != "(" && s[sl - 1] != "[") {
s[sl++] = scope | (parse_mode << 28),
s[sl++] = "{{", o[ol++] = cf_func_o,
scope = ol, parse_mode = 1, nesting++, o[ol++] = ""; // make the scope check pass
}
else {
s[sl++] = scope, s[sl++] = o[ol++] = tok, scope = ol;
parse_mode = 1;
}// for do else..etc below
}
else if ((macro_o[s[sl + 1]] && last_tok == ") ") || pre_plain[last_tok]) {
s[sl++] = scope, s[sl++] = o[ol++] = tok, scope = ol;
o[ol++] = "";
}
else {
if ((v = (u = ((out_context_word[last_tok]||o[ol - 1] == "\n")
&& !new_block[last_tok]))
&& !s[sl - 1].indexOf("{") && ol > scope) && !parse_mode)
switchToBlock(); // block mode detection
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""),
o[ol++] = ")", o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) { // inject output, +''+ is when between two { } { } (supposedly)
o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
? "+''+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok]) // inject append
o[ol++] = (last_tok == "}") ? "+''+" : "+";
s[sl++] = scope | (parse_mode << 28), s[sl++] = o[ol++] = "{{";
if (!nesting && scope != ol) // count output segments on nesting 0
o_segs++;
nesting++, segment = scope = ol, parse_mode = 0;
}
break;
case 8: // -------- } --------
if (!s[sl - 1]) // close = macro
o[ol++] = ")", o[ol++] = "\n",sl -= 2;
if (type_close[v = s[--sl]] != (o[ol++] = tok))
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
if (v == "{{") { // closing code block
if (scope == ol - 1) {
if ( (s[sl - 1] >> 28) <= 1) // empty code in code
o[scope-1] = "{", o[ol - 1] = "}";
else // empty code elsewhere
o[scope - 1] = o[ol - 1] = "'";
}
else {
if (!parse_mode) { // expression wraps in ()
o[scope - 1] = "(",
o[ol - 1] = ")";
}
else { // codeblock wraps in (function(){})()
if (o[scope - 1] == cf_func_o) {
if (scope == ol - 2)
o[scope - 1] = "{", o[ol - 1] = "}";
else
o[ol - 1] = cf_func_c;
}
else
o[ol - 1] = cf_block_c;
}
}
parse_mode = (v=s[--sl])>>28, scope = v&0x0fffffff;
segment = ol, nesting--;
if (!nesting) // count segs on nesting level 0
o_segs++;
if (parse_mode == 7) // attribute hack
o[ol++] = "+\"\\\"", parse_mode = 4;
} else scope = s[--sl]; // was object def or if (){}
break;
case 9: // -------- [ --------
if (((last_type == 3 && !pre_xpath[last_tok] && last_tok!='$') || last_tok == ")" || last_tok == "]") && o[ol - 1] != "\n") {
o[ol++] = "[", s[sl++] = scope | (parse_mode << 28), //was array index
s[sl++] = tok, segment = scope = ol;
}
else {
last_model = null;
if ((w = xpath_lut_code[last_tok])) {
ol--, last_tok = o[ol-1]; // xpath with *%$#
}
else {
w = xpath_macro[s[sl - 1]] || xpath_macro[nesting ? 0 : c_xpathmode];
}
if ((v = (u = ((out_context_word[last_tok] || o[ol - 1] == "\n")
&& !new_block[last_tok])) && !s[sl - 1].indexOf("{")
&& (ol > scope || s[sl - 1].length == 1)) && !parse_mode)
switchToBlock(); // check if we need to switch to block mode
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) { // inject output
o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
? "+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok]) // inject append
o[ol++] = "+";
if (!nesting && ol!=scope)
o_segs++;
// store scope in bt for reparse of array
nesting++, s[sl++] = scope|(parse_mode<<28), s[sl++] = o[ol++] = w,
segment = scope = ol, bt[scope] = pos, parse_mode = 3;
}
break;
case 10: // -------- ] --------
if (!s[sl-1]) // close = macro
o[ol++]=")",sl -=2;
if ( type_close[v = s[--sl]] != (o[ol++] = tok))
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
scope = s[--sl]&0xfffffff; // clean scope of possible parse_mode 1
break;
case 11: // -------- ( --------
if ( ((v = (u=((out_context_paren[last_tok]||o[ol-1]=="\n") &&
!new_block[last_tok])) && !s[sl-1].indexOf("{") &&
ol>scope)) && !parse_mode)
switchToBlock();
if (u && !s[sl-1]) // close = macro
o[ol-1]=="\n"&&(o[ol-1]=""),o[ol++]=")", o[ol++]="\n",v = 1,sl -=2;
if (v && parse_mode) // inject output
o[ol++] = (nesting?cf_str_output:cf_mode_output), last_type = 0;
if (w = macro_o[last_tok]) {
if (o[ol-1]==" ") ol--; // support func ()
o[ol-1] = w, s[sl++] = scope, s[sl++] = last_tok, scope = segment = ol;
}
else {
if (last_type == 3) { // word(
if (last_dot < 0) { // no dot
v = 0;
if (last_tok == "function" || o[ol - 3] == "function" || o[ol - 4] == "function") {
s[sl++] = scope, s[sl++] = "function", //func def
o[ol++] = "(", scope = segment = ol;
//TODO! check the depth with which functions are made global
if (last_tok!="function" && c_export && sl==4) {
o[v=(o[ol - 4] == "function")?(ol-4):(ol-5)] =
"var "+last_tok+" = "+c_export+"."+last_tok+" = function";
o[v+2] = "";
}
}
else { // its a call and not a new
if (!call_exclusion[last_tok] && o[ol-3]!="new") {
o[ol++] = ".call(_n", s[sl++] = scope,
s[sl++] = "_call", scope = segment = ol;
}
else { // its an excluded call
s[sl++] = scope, s[sl++] = o[ol++] = tok,
scope = segment = ol;
}
}
}
else {
if (last_dot > 1 && c_process_async && (c_async_lut[v = last_tok.substring(0,last_dot)] || c_async_lut[v = last_tok])) {// its an async call
if (o[--ol] == " ")
ol--;
o[ol++] = cf_async_o, o[ol++] = v, o[ol++] = ",'";
o[ol++] = last_tok.slice(last_dot + 1);
o[ol++] = cf_async_m, s[sl++] = scope, s[sl++] = "async",
scope = segment = ol, o_asyncs++;
}
else { // its a obj.prop() type call
if (last_tok.indexOf('.')!=last_dot) // obj.prop.call();
o_props[last_tok.slice(0,last_dot)] = 1;
s[sl++] = scope, s[sl++] = o[ol++] = tok,
scope = segment = ol;
}
}
}
else { // function object call
s[sl++] = scope, s[sl++] = o[ol++] = tok,
scope = segment = ol;
} // dont store calls as props
if (last_tok == last_prop)
delete o_props[last_tok];
}
break;
case 12: // -------- ) --------
if (!s[sl - 1]) { // close = macro
o[ol-1] == "\n" && (o[ol-1] = ""), o[ol++] = ")",
o[ol++]="\n", v = 1, sl -= 2;
}
if (w = macro_c[v = s[--sl]]) { // closing a macro
if (v != "_call")
tok = ") "; // make sure any [ ] doesnt get interpreted as array index
if ((u = call_args_lut[v]) && u != o[ol - 1])
o[scope - 1] = u + ",";// do , insertion for argless macros
o[ol++] = w; // write close-end of macro
}
else if (type_close[v] != (o[ol++] = tok)) {
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
}
scope = s[--sl] & 0xfffffff; // scope should be unimpacted
break;
case 13: // -------- < --------
// check if < is markup or not
if (ol == scope || markup_in_code_lut[last_tok] || o[ol - 1] == "\n"){
if ((v = (u = ((out_context_word[last_tok] || o[ol - 1] == "\n")
&& !new_block[last_tok])) && !s[sl - 1].indexOf("{")
&& ol > scope) && !parse_mode)
switchToBlock(); // switch to block mode
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) {
o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
? "+''+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok])
o[ol++] = "+";
// start markup mode with the markup-stack counting
last_ns = null, o[ol++] = '"', o[ol++] = "<", nesting++,
s[sl++] = scope | (parse_mode << 28), sl += 3,
s[sl - 2] = s[sl - 1] = 0;
segment = scope = ol - 1, parse_mode = 4;
}
else
o[ol++] = "<";
break;
case 14: // -------- < --------
o[ol++] = ">";
break;
case 15: // end
if (sl && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
break;
}
break;
case 2: // ========================== text parse_mode ==========================
switch (type) {
case 1: // -------- newline --------
line_no++, last_line = pos;
if (ol != scope && ol != segment) // only output when not first
o[ol++] = "\\n";
break;
case 2: // -------- misc --------
if (ol == segment) // segment connectors
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = unesc_txt[tok] || tok;
break;
case 3: // word
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break;
case 5: // -------- stringquotes --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = (tok == '"') ? "\\\"" : "'";
break;
case 7: // -------- { -------- code mode
if (ol == segment) {
if (ol != scope )
o[ol++] = "+";
}
else
o[ol++] = "\"+", nesting || o_segs++;
s[sl++] = scope | 0x20000000, s[sl++] = o[ol++] = "{{",
nesting++, segment = scope = ol, parse_mode = 0;
break;
case 9: // -------- [ -------- xpath mode
last_model = null; // get xpath macro
if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok) {
if (--ol - 1 == scope)
ol --; // remove first ""
}
else // only select c_xpathmode when nesting == 0
w = xpath_macro[(nesting || scope != ol) ? 0 : c_xpathmode];
if (ol != scope) {
o[ol] = (ol++ == segment) ? "+" : (nesting || o_segs++, "\"+");
if (!nesting)
o_segs++;
}
s[sl++] = scope | 0x20000000, s[sl++] = o[ol++] = w,
segment = scope = ol, nesting++, parse_mode = 3;
break;
case 15: // -------- end --------
if (sl)
throw {
t: "Unclosed " + s[sl-1] + " found at end in textmode",
p: pos
};
if (ol != scope && ol != segment)
o[ol++] = "\"", nesting || o_segs++;
break;
default: // -------- default --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = tok;
}
break;
case 3: // ========================== xpath parse_mode =========================
switch(type) {
case 0: // -------- whitespace --------
if (ol != scope){ // strip initial spaces\l
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = tok;
}
break;
case 1: // -------- newline --------
line_no++, last_line = pos;
break;
case 2: // -------- misc --------
if (tok == ":" && last_tok == ":" && !xpath_axes[w = o[ol - 2]]
&& ((v = s[sl - 2]) >> 28) != 6) { // found model::xpath split
if (o[ol - 2] == '+"') // model is calculated
o[ol - 2] = o[ol - 1] = "", last_model = "#";
else {
o[ol - 1] = '"';
if (segment == scope) // model is normal name
last_model = o.slice(segment + 1, ol - 1).join("");
else // model is calculated
last_model = "#";
}
if (!(w = xpath_model[o[scope - 1]]))
throw {
t: "Invalid model found for: "+o[scope-1],
p: pos
};
o[scope - 1] = w, o[ol++] = ",", segment = scope = ol;
}
else {
if (tok == "," && (v = (s[sl - 2] >> 28)) <= 1) { // xpath is an array in code
ol = scope-1, u = str.slice(bt[scope] + 1, pos + 1);
// fix up stack to not be an xpath but an array
last_type = 9, parse_mode = v, o[ol++] = last_tok = "[";
s[sl - 2] = (s[sl - 2] & 0xfffffff) | (parse_mode << 28),
s[sl - 1] = last_tok, segment = scope = ol, nesting--;
if (!nesting)
o_xpaths--;
if (u.length > 1) { // ignore [, optimized escaping
bts.push(str); // push str so str always is the string in replace
(str = u).replace(parserx, parser); // reparse it
str = bts.pop(); // pop it again
}
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = unesc_lut[tok] || tok;
}
}
break;
case 3: // word
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break
case 5: // -------- stringquotes --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (s[sl - 1] == "[") // strings only are used in [ ]
s[sl - 1] = tok;
else if (s[sl - 1] == tok) // close string
s[sl - 1] = "[";
if (tok == '"')
o[ol++] = "\\";
o[ol++] = tok;
break;
case 7: // -------- { --------
if (ol == segment) {
if (ol != scope)
o[ol++] = "+''+";
}
else
o[ol++] = "\"+";
s[sl++] = scope | 0x30000000, s[sl++] = o[ol++] = "{{",
nesting++, segment = scope = ol, parse_mode = 0;
if (last_model && s[sl - 3] != xpath_lut_text["$"]) {
o_xpathpairs.push(last_model, "#");
last_model = null, o_models++;
}
break;
case 9: // -------- [ --------
// lets see if we are an xpath
if (s[sl - 1] == "'" || s[sl - 1] == '"' ||
((last_type != 3 || last_tok=='$') && last_tok != ")" && last_tok != "]") ) {
if (last_model)
o_xpathpairs.push(last_model, "#"), o_models++;
last_model = null;
if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok)
ol--;
else
w = xpath_macro[0];
if (ol == segment) {
if (ol != scope)
o[ol++] = "+";
}
else o[ol++] = "\"+";
s[sl++] = scope | 0x30000000, s[sl++] = o[ol++] = w, nesting++,
segment = scope = ol, parse_mode = 3;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
s[sl++] = scope|0x60000000, s[sl++] = o[ol++] = "["; // keep track of [, abuse mode 6
}
break;
case 10: // -------- ] --------
sl--, parse_mode = (w = s[--sl]) >> 28, w = w & 0x0fffffff;
if (parse_mode == 6){ // was part of [] internally to xpath, see above
if (s[sl + 1] != "[")
throw {
t: "In xpath, cannot close " + s[sl + 1] + " with " + tok,
p: pos
};
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = "]";
parse_mode = 3;
}
else {
if (ol == scope ) {
if ((s[sl] >> 28) <= 1) // empty array in code
o[scope - 1] = "[", o[ol++] = "]";
else // empty xpath elsewhere
o[scope - 1] = o[ol++] = "\"" ;
segment = ol;
}
else {
//if( s[sl+1] != '[' )
// throw {t:"Unclosed string in xpath"+s[sl+1], p: pos};
if (ol != segment)
o[ol++] = "\"";
if (segment == scope){ // we might have an xpath name
v = o.slice(segment + 1, ol - 1).join("");
if (c_injectself && o[scope - 1] != "," // inject self
&& v != (u = v.replace(selfrx, "$1self::"))
&& s[sl + 1] != xpath_lut_text["$"]) {
o[scope+1] = v = u;
for (u = scope + 2; u < ol - 1; u++)
o[u] = "";
}
}
else {
if ((u = o[scope - 1]) != ",") {
v = "#";
if (c_injectself)// inject dyn self if dyn xpath
o[scope - 1] = u + "_injself(", o[ol++] = ")";
}
else
v = "";
}
if (s[sl + 1] != xpath_lut_text["$"] && v) {
o_xpathpairs.push(last_model, v); // only store if not _lng
if (last_model)
o_models++;
}
o[ol++] = ") ", segment = ol; // close xpath with ') ' marker
//logw("CLOSING XPATH"+o.join('#')+nesting);
if (parse_mode == 7) // attribute assign in xml mode
o[ol++] = "+\"\\\"", parse_mode = 4;
}
// lets output an xpath if we werent a language symbol
nesting--, last_model = null;
if (!nesting)
o_segs++, o_xpaths++;
}
scope = w;
break;
case 11: // -------- ( --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
s[sl++] = scope | 0x30000000, // keep track of () in xpath
s[sl++] = o[ol++] = "(";//, last_model = null;
break;
case 12: // -------- ) --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (type_close[v = s[--sl]] != (o[ol++] = tok))
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
scope = s[--sl] & 0xfffffff;
break;
case 15: // -------- end --------
throw {
t: "Unexpected end whilst parsing xpath",
p: pos
};
break;
default: // -------- default --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = tok;
break;
}
break;
case 4: // =========================== xml parse_mode ==========================
switch (type) {// stack: '<'sl+4,outside=0, '</'sl-4 '>'sl-2,outside=1 '/>'sl-4,outside=1
case 0: // -------- whitespace --------
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = " ", last_type = 0;
break;
case 1: // -------- newline --------
if (ol == segment)
o[ol++] = "+\"";
line_no++, last_line = pos, o[ol++] = "\\n", last_type = 1;
break;
case 2: // -------- misc --------
if (ol == segment)
o[ol++] = "+\"";
if (tok == "/" && last_tok == "<") {
sl -= 4; // </ closing tag, drop stacklevel by 4
if (s[sl] || s[sl + 2])
throw {
t: "Unexpected closing tag whilst parsing xml",
p: pos
};
}
else if (tok == ":" && last_type == 3 && o[ol - 2] == "<")
last_ns = last_tok; // found a namespace item in a tag
o[ol++] = unesc_txt[tok] || tok;
break;
case 3: // word
if (ol == segment)
o[ol++] = "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break
case 5: // -------- stringquotes --------
if (ol == segment)
o[ol++] = "+\"";
if (tok == '"')
o[ol++] = "\\";
o[ol++] = tok;
break;
case 6: // -------- comment --------
if (tok == "//" && !s[sl - 1]) {
if (ol == segment)
o[ol++] = "+\""; // < char ups stack by 4, outside= 0
o[ol++] = tok;
}
else {
if (tok == "*/")
throw {
t: "Unmatched comment "+tok,
p: pos
};
last_cmt_mode = parse_mode, last_cmt_tok = last_tok,
last_cmt_type = last_type, parse_mode = 6, start_tok = tok;
}
break;
case 13: // -------- < --------
last_ns = null;
if (ol == segment)
o[ol++] = "+\""; // < char ups stack by 4, outside= 0
o[ol++] = tok, s[sl] = s[sl + 2] = 0, sl += 4, s[sl - 1]=0;
break;
case 14: // -------- > --------
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = tok;
if (last_tok != "<") {
if (last_tok == "/") {
sl -= 4; // self close tag /> drops stack -4
if (s[sl + 2])
throw {
t: "Unexpected / whilst parsing xml",
p: pos
}
if (o[ol - 3] == "<") // remove empty </> from output
ol -= 2, o[ol - 1] = "";
}
else
sl -= 2; // <tag> nets stackdepth of 2
if (s[sl]) { // end of xml mode
nesting--, o[ol++] = "\"", scope = s[sl], segment = ol,
parse_mode = scope >> 28, scope = scope & 0x0fffffff;
}
else
s[sl - 1] = 1; // we are outside a tag, flag it on the stack
}
else // remove empty <> from output
ol--, o[ol - 1] = "";
break;
case 9: // -------- [ -------- xpath mode
last_model = null;
if (last_tok == "!" && o[ol - 2] == "<" && !s[sl - 1]) { // CDATA mode
o[ol++] = tok, s[sl++] = scope | (parse_mode << 28);
s[sl++] = "]]>", scope = segment = ol - 1;
nesting++, parse_mode = 5;
}
else {
if (s[sl - 1]) { // we are outside a tag
if ((v = xpath_lut_node[last_tok]))
ol --;
else
v = xpath_macro[c_elemxpath];
s[sl++] = scope | 0x40000000
}
else {
s[sl++] = scope | 0x40000000
if ((v = xpath_lut_attr[last_tok])) {
ol--;
if (o[ol - 1] == "=")
last_tok = "=";
}
else
v = xpath_macro[last_ns ? c_statexpath : 8];
if (last_tok == "=")//0x7 flags xpath-in-missing-quotes <a i=[xp]/>
o[ol++] = "\\\"", s[sl - 1] = scope | 0x70000000;
}
o[ol] = (ol++ == segment) ? "+''+" : "\"+";
nesting++, s[sl++] = o[ol++] = v,
segment = scope = ol, parse_mode = 3;
}
break;
case 7: // -------- { -------- code mode
if ( !s[sl - 1] && last_tok == "=") // 0x7 flags code-in-missing-quotes <a i={x}/>
o[ol++] = "\\\"", s[sl++] = scope | 0x70000000;
else
s[sl++] = scope | 0x40000000
o[ol] = (ol++ == segment) ? "+''+" : "\"+";
s[sl++] = o[ol++] = "{{", nesting++;
segment = scope = ol, parse_mode = 0;
break;
default:
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = tok;
break;
case 15: // -------- end --------
throw {
t: "Unexpected end whilst parsing xml",
p: pos
};
break;
}break
case 5: // ========================== string parse_mode ========================
switch (type) {
case 1: // -------- newline --------
line_no++, last_line = pos;
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = "\\n";
break;
case 2: // -------- misc --------
if (tok == "/" && s[sl - 1] == "/") { // regexp closing character
o[ol++] = "/", scope = s[sl -= 2], segment = ol,
parse_mode = scope >> 28,
scope = scope & 0x0fffffff, nesting--;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = (s[sl - 1] != "/" && unesc_str[tok]) || tok;
}
break;
case 3: // word
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break
case 5: // -------- stringquotes --------
if (s[sl - 1] == tok) { // closed by matching quote
if (scope != segment) // string is segmented, output )
o[ol] = (ol++ != segment) ? (tok + ")") : ")";
else
o[ol++] = tok; // else just close
scope = s[sl -= 2], segment = ol, parse_mode = scope >> 28;
scope = scope & 0x0fffffff, nesting--;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = tok == '"' ? "\\\"" : tok;
}
break;
case 6: // -------- default --------
if (s[sl - 1] == "/" && tok == "*/") { // caught faux comment in regexp /a*/, is close
o[ol++] = "*/", scope = s[sl -= 2], segment = ol,
parse_mode = scope >> 28, scope = scope & 0x0fffffff, nesting--;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
o[ol++] = tok;
}
break;
case 7: // -------- { -------- code mode
if (s[sl - 1] != "'" && s[sl - 1] != "/") {
if (s[sl - 1] == '"')
o[scope] = '("';
if (ol == segment) {
if (ol != scope)
o[ol++] = "+";
}
else
o[ol++] = "\"+";
s[sl++] = scope | 0x50000000, o[ol++] = s[sl++] = "{{",
nesting++, segment = scope = ol, parse_mode = 0;
}
else
o[ol++] = tok;
break;
case 9: // -------- [ -------- xpath mode
if (s[sl - 1] != "'" && s[sl - 1] != "/" // ignore in '' and CDATA[, else xpath
&& (s[sl - 1] == '"' && (o[scope] = '("') || ol != scope + 2
|| last_tok != "CDATA") ) {
last_model = null;
if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok)
ol--;
else
w = xpath_macro[0]
if (ol != scope)
o[ol] = (ol++ == segment) ? "+" : "\"+";
s[sl++] = scope | 0x50000000, s[sl++] = o[ol++] = w,
segment = scope = ol, nesting++, parse_mode = 3;
}
else
o[ol++] = tok;
break;
case 14: // -------- > --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
o[ol++] = tok;
if (s[sl - 1] == "]]>" && last_tok == "]" && o[ol - 3]=="]") { // check if CDATA close
scope = s[sl -= 2], parse_mode = scope >> 28;
scope = scope & 0x0fffffff, nesting--;
sl -= 4; // close the tag since we came from XML mode
if (s[sl]) // was last tag, jump up the stack one more.
nesting--, o[ol++] = "\"", scope = s[sl], segment = ol,
parse_mode = scope >> 28, scope = scope & 0x0fffffff;
else
s[sl - 1] = 1;
}
break;
case 15: // -------- end --------
throw {
t: "Unexpected end whilst parsing string",
p: pos
};
break;
default: // -------- default --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
o[ol++] = tok;
break;
}
break;
case 6: // ========================= comment parse_mode ========================
switch (type) {
case 1: // -------- newline --------
line_no++, last_line = pos;
if (start_tok == "//")
parse_mode = last_cmt_mode,
tok = last_tok = last_cmt_tok,
type = last_type = last_cmt_type;
break;
case 6: // -------- comment --------
if ((start_tok == "/*" && tok == "*/")
|| (start_tok == "<!--" && tok == "-->")) {
parse_mode = last_cmt_mode,
tok = last_tok = last_cmt_tok,
type = last_type = last_cmt_type;
}
break;
case 15: // -------- end --------
if (start_tok != "//"){
throw {
t: "Unexpected end whilst parsing comment",
p: pos
}
} else {
parse_mode = last_cmt_mode,
tok = last_tok = last_cmt_tok,
type = last_type = last_cmt_type;
if (sl && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
};
break;
}
break;
}
if (type > 1)
last_tok = tok, last_type = type;
}
this.lastCode = function(){
if (typeof(o) == "object")
return o.join("");
return o;
};
function handleError(e, last_line, part, linenr) {
// TODO: make a proper APF exception with this information:
if (e.t) {
throw new Error(apf.formatErrorString(0, null,
"Parsing live markup source",
"Error whilst parsing: " + e.t + " on line:"+ line_no
+ " col:" + (e.p - last_line - 2)
+ (part ? (" part: " + part) : "") + "\n" + str));
}
else {
throw new Error(apf.formatErrorString(0, null,
"Compiling live markup function on line " + linenr,
"Error whilst compiling: " + e.message
//+ "\nStack Trace:\n" + e.stack
+ "\nInput:\n" + str
+ "\nGenerated:\n" + apf.lm.lastCode()));
}
}
/**
* description of the method.
* Remarks:
* function{type:1,xpaths:[ model,name], props: ['obj.propname','obj2.otherpropname'], asyncs=1}
* this is a normal compiled function with extra properties
* if the xpath model and/or name is '#' it means it is a runtime calculated modelname or xpath.
* obj{type:2, str:str} it was single string by cfg option !alwayscode
* obj{type:3, xpaths:[ model, name ] } it was a single xpath by cfg simplexpath
*
* @param {String} str the code to compile
* @param {Object} options
* Properties:
* {Boolean} withopt creates with(_w){ code using an options block. (reqd for precall)
* {Boolean} precall wraps 1 async call into precallstore. call with _w._pc = 1 to precall, second time to execute.
* {Boolean} alwayscb always call callback function, even if not async
* {Boolean} nostring even generate code for a simple string
* {Number} xpathmode default type of root level xpath in code mode
* Possible values:
* 0: value
* 1: value with createnode
* 2: node
* 3: node with createnode
* 4: nodes
* 5: xpathobj returns a {model:model,xpath:xpath} object from xpaths
* {Boolean} parsecode start in codemode. if 0 its textmode.
* {Boolean} nostate dont' use _valst macro on [xpath] in namespaced xml.
* {Boolean} liveedit use the _valed macro for <xml>[xpath]</xml> in namespaced xml.
* {Boolean} langedit use of language items in namespaced xml text.
* {Boolean} injectself injects self:: to suitable xpaths
* {Boolean} event its an event thats being compiled, results in no returnvalue for this function.
* and the first argument is now an 'e' for the event object.
* {Boolean} funcglobal all functions defined in LM are made global
*
* @return {Function} returns a function with extra properties
* Properties:
* {Number} type description
* Possible values:
* 1 Function return type
* 2 Parsed data is a pure string
* 3 Function return type, but its a single xpath
* 4 Function return type, but single propxs
* {Array} xpaths array of [model,xpath, model,xpath] pairs if model
* or xpath is '#', its dynamic if model is null its a local xpath
* {Number} models number of models
* {Array} props description
* {Number} asyncs description
* {String] str optional, returned with type 2
*/
var cache = {},
emptyCfg = {};
this.resetCache = function(){
cache = {};
};
var lmcache_rx = /^\s*~~(c\d+)~~/;
this.compile = function(istr, cfg) {
if (!cfg)
cfg = emptyCfg;
if (istr == null || !istr.length) {
return (cfg.nostring || cfg.event)?function(){return istr}:{
type: 2,
str: istr
};
}
// lets see if we need to fetch precompiled cachemarker
var c, f, is_single_prop;
if (istr.charAt(0)=="~" && (c=istr.match(lmcache_rx))){
if (c=apf.lm_exec[c[1]]) return c;
alert("ERROR, undefined live markup cache marker found:"+istr);
return {type:2,str:istr};
}
var key = (cfg.xpathmode | (cfg.withopt && 0x10) | (cfg.precall && 0x20)
| (cfg.alwayscb && 0x40) | (cfg.nostring && 0x80) | (cfg.parsecode && 0x100)
| (cfg.nostate && 0x200) | (cfg.liveedit && 0x400)| (cfg.langedit && 0x800)
| (cfg.injectself && 0x1000) | (cfg.event && 0x2000) | (cfg.funcglobal && 0x4000)) + istr;
if (c = cache[key])
return c;
c_injectself = cfg.injectself, c_xpathmode = cfg.xpathmode||0,
c_statexpath = cfg.nostate ? 0 : 6, c_elemxpath = 0;
c_export = cfg.funcglobal?"self":(cfg.withopt?"_w":null);
c_process_async = !cfg.event;
xpath_macro.edit = cfg.liveedit ? "_argwrap(_n," : "_argwrap(_n,";//"_val(_n,";
macro_o.edit = cfg.liveedit ? macro_o._editlm : macro_o._editnormal;
xpath_lut_node = cfg.langedit ? xpath_lut_node_langedit : xpath_lut_node_normal;
o_props = {}, o_xpathpairs = [], s = [], o = ["","","",""], str = istr,
str_len = str.length;
ol = scope = segment = o.length,
o_segs = o_xpaths = o_asyncs = o_models = nesting = line_no = last_type = last_line = 0;
if (cfg.parsecode) {
parse_mode = 0, sl = 2, s[0] = ol, s[1] = "{{", last_tok = "{",
cf_mode_output = cfg.event ? "" : (c_xpathmode <= 1 ? cf_str_output : cf_obj_output);
}
else
parse_mode = 2, sl = last_tok = 0, cf_mode_output = cf_str_output;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e, last_line);
return null;
}
}
if (cfg.parsecode) {
if (nesting || s[sl - 1].length == 1)
handleError({
t: "Unclosed " + s[sl-1] + " found at end in codemode",
p: str_len
},last_line);
if (segment!=ol)
o_segs++
}else if( (ol==7 || ol==8) && o_segs == 1) {
is_single_prop = 0;
for (c in o_props)is_single_prop++;
if (is_single_prop!=1)is_single_prop = 0;
}
if ((!cfg.nostring && !cfg.event)&& (parse_mode == 2 && segment == 4 || ol == 4)) {
return {
type: 2,
str: o.slice(5, -1).join("").replace(/\\n/g, "\n").replace(/\\"/g, '"')
}; // string only
}
if (o_asyncs || cfg.alwayscb) {
if (cfg.event) { // event
if (parse_mode == 1)
o[3] = "";
o[ol++] = cc_o_blk_ce;
}
else if (c_xpathmode) { // object return
if (parse_mode == 1) {
o[3] = (o[3] != cf_block_o) ? cc_o_blk_o : cc_o_blk_ob,
o[ol++] = cc_o_blk_cb;
}
else
o[3] = cc_o_cb_o, o[ol++] = cc_o_cb_c;
}
else { // value return
if (parse_mode == 1)
o[3] = (o[3] != cf_block_o) ? cc_v_blk_o : cc_v_blk_ob,
o[ol++] = cc_v_blk_cb;
else
o[3] = cc_v_cb_o, o[ol++] = cc_v_cb_c;
}
if (o_asyncs) {
// for parse_mode == 1 we can squeeze in before [3] and cb close
// else we put var _r= in 3 and put our ending last and put
// the cb at the end
if (parse_mode==1) {
if (cfg.precall)
o[2] = cc_pc_o, o[ol-1] = cc_pc_c + o[ol-1];
else
o[2] = cc_async_o, o[ol-1] = cc_async_c + o[ol-1];
}else{
o[ol++] = o[3] + '_r' + o[ol-2];
if (cfg.precall)
o[2] = cc_pc_o, o[3] = cc_o_blk_o, o[ol-2] = cc_pc_c;
else
o[2] = cc_async_o, o[3] = cc_o_blk_o, o[ol-2] = cc_async_c;
}
}
if (cfg.withopt)
o[1] = cc_opt_o, o[ol++] = cc_opt_c;
o[0] = cfg.event
? cc_fe_async_o
: ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_async_o : cc_f_async_o);
o[ol++] = cc_f_c;
}
else {
if (cfg.event) { // event
if (parse_mode == 1)
o[3] = "";
}
else if (c_xpathmode) { // object return
if (parse_mode == 1) {
o[3] = (o[3] != cf_block_o) ? cc_o_blk_o : cc_o_blk_ob,
o[ol++] = cc_o_blk_c;
}
else
o[3] = cc_o_ret_o, o[ol++] = cc_o_ret_c;
}
else { // value return
if (parse_mode == 1) {
o[3] = (o[3] != cf_block_o) ? cc_v_blk_o : cc_v_blk_ob,
o[ol++] = cc_v_blk_c;
}
else
o[3] = cc_v_ret_o, o[ol++] = cc_v_ret_c;
}
if (cfg.withopt)
o[2] = cc_opt_o, o[ol++] = cc_opt_c;
o[0] = cfg.event
? (cfg.withopt ? cc_fe_opt_o : cc_fe_o)
: (cfg.withopt
? ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_opt_o : cc_f_opt_o)
: ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_o : cc_f_o));
o[ol++] = cc_f_c;
}
var code = "with(apf.nameserver.lookup.all){\n" + o.join("") + "\n}";
if (cfg.nothrow) {
f = apf.lm_exec.compile(code);
}
else {
try {
f = apf.lm_exec.compile(code);
}
catch (e) {
if (!apf.isIE) {
var oErr = window.onerror;
window.onerror = function(x,y,line) {
window.onerror = oErr;
handleError(e, last_line, null, line);
return true;
}
apf.include("", "", null, o.join(""));
window.onerror = oErr;
}
else {
handleError(e,last_line);
}
return null;
}
}
f.type = (o_segs == 1 && o_xpaths == 1) ? 3 : (is_single_prop?4:1);
f.xpaths = o_xpathpairs, f.models = o_models,
f.props = o_props, f.asyncs = o_asyncs;
cache[key] = f;
return f;
};
/**
* description of the method.
* Remarks:
* @param {String} str the code to compile
* @param {Object} options
* Properties:
* {Boolean} node tries to return a node, used as a dual-compile with 'normal mode'
*
* @return {Function} returns a function with extra properties
* Properties:
* {Number} type description
* Possible values:
* 1 Function return type
* 2 Parsed data is a pure string
* 3 Function return type, but its a single xpath
* {Array} xpaths array of [model,xpath, model,xpath] pairs if model
* or xpath is '#', its dynamic if model is null its a local xpath
* {Number} models number of models
* {Array} props description
* {Number} asyncs description
* {String] str optional, returned with type 2
*/
this.compileMatch = function(strarray, cfg) {
if (!cfg)
cfg = emptyCfg;
o_props = {}, o_xpathpairs = [], o = [cc_f_match_o, cc_m_o], s = [],
nesting = 0, ol = o.length, xpath_lut_node = xpath_lut_node_normal;
for (var st, ob, i = 0, j = strarray.length; i < j; i += 2) {
if (str = strarray[i]) {
str_len = s.length, c_xpathmode = 2;
if (i)
o[ol++] = cc_m_brk;
o[ol++] = "";
s[0] = ob = ol = scope = segment = o.length, cf_mode_output = cf_obj_output;
line_no = last_type = o_segs = o_xpaths = o_asyncs = parse_mode = last_line = 0;
sl = 2, s[1] = "{{", last_tok = "{";
c_injectself = 1;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e,last_line);
return null;
}
}
if (nesting || s[sl - 1].length == 1)
handleError({
t: "Unclosed " + s[sl - 1] + " found at end in codemode",
p: str_len
});
if (o_asyncs)
handleError({t:"Asynchronous calls not supported in match/value"});
if (parse_mode == 1) { // block mode
o[ob - 1] = (o[ob - 1] != cf_block_o) ? cf_mode_output : "",
o[ol++] = cc_m_m_blk;
}
else // value mode
o[ob-1] = cc_m_m_value_o, o[ol++] = cc_m_m_value_c;
}
if (str = strarray[i + 1]) {
str_len = s.length;
if (!strarray[i] && i)
o[ol++] = cc_m_brk;
o[ol++] = "";
ob = ol = scope = segment = o.length, cf_mode_output = cf_str_output;
c_xpathmode = c_injectself = last_tok = sl = line_no = o_segs = o_xpaths =
last_type = o_asyncs = last_line = 0;
if (cfg.node)
c_xpathmode = 2;
parse_mode = 2, c_injectself = 0;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e,last_line);
return null;
}
}
if (o_asyncs)
handleError({t:"Asynchronous calls not supported in match/value"});
if (cfg.node) {
if (parse_mode == 2 && segment == ob || ol == ob)
o[ob-1] = cc_m_n_string;
else
o[ob-1] = cc_m_n_o, o[ol++] = cc_m_n_c;
}else{
if (parse_mode == 2 && segment == ob || ol == ob)
o[ob-1] = cc_m_v_string;
else
o[ob-1] = cc_m_v_o, o[ol++] = cc_m_v_c;
}
if (strarray[i])
o[ol++] = cc_m_c;
else
break;
}
else {
if (!strarray[i])
handleError({t:"Both match and value are empty"});
if (cfg.node)
o[ol++] = cc_m_n_ret;
else
o[ol++] = cc_m_v_ret;
c_xpathmode = 2;
o[ol++] = cc_m_c;
}
}
o[ol++] = cc_f_c;
var f;
if (cfg.nothrow) {
f = apf.lm_exec.compile(o.join(""));
}
else {
try{
f = apf.lm_exec.compile(o.join(""));
}
catch (e) {
handleError(e,last_line);
return null;
}
}
f.type = 1, f.xpaths = o_xpathpairs,
f.props = o_props, f.asyncs = o_asyncs;
return f;
};
this.setWarnLevel = function(lvl) {
apf.lm_exec.setWarnLevel(lvl);
};
this.parseExpression = function(istr, cfg) {
if (!cfg)
cfg = emptyCfg;
o_props = {}, o_xpathpairs = [], o = [], s = [],
nesting = 0, xpath_lut_node = xpath_lut_node_normal;
str = istr, str_len = str.length;
ob = ol = scope = segment = o.length, cf_mode_output = cf_str_output;
c_xpathmode = c_injectself = last_tok = sl = line_no = o_segs = o_xpaths =
last_type = o_asyncs = last_line = 0;
parse_mode = 2;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e,last_line);
return null;
}
}
return o.join('');
}
})();
// apf lm_exec makes sure there is no scope pollution for eval'ed live markup.
apf.lm_exec = new (function(){
var wlvl = 1; // 0: no warnings 1: language/models missing, 2:nodes missing, 3:all failed xpaths
//warning functions
this.setWarnLevel = function(lvl) {
wlvl = lvl;
};
function wxpath(x, t) {
apf.console.warn("Live Markup warning in " + t + ", no results for xpath: '" + x + "'");
}
function wnode(x, t) {
apf.console.warn("Live Markup warning in " + t + ", xpath on null node: '" + x + "'");
}
function wmodel(m, x, t) {
apf.console.log("Live Markup warning in " + t + ", xpath on empty model: '" + m + "' xpath: '" + x + "'");
}
function wlang(x, t) {
apf.console.log("Live Markup warning in " + t + ", language symbol not found: '" + x + "'");
}
// xml parse function used by all livemarkup objects
function xmlParse(str) {
var n = apf.getXmlDom("<_apflmlist_>" + str + "</_apflmlist_>");
if (!n || !(n = n.documentElement))
return null;
return (n.firstChild == n.lastChild) ? n.firstChild : n;
}
// value of node by xpath
function __val(n, x) {
if (!n)
return ("")
return apf.escapeXML((n = (!n.nodeType && n || (n = n.selectSingleNode(x)) //!= 1
&& (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
&& n.nodeValue || (""));
}
var __valattrrx = /(["'])/g;
function __valattrrp(m,a) {
return m=='"'?"&quot;":"&apos;";
}
function __valattr(n, x) {
if (!n)
return ("")
return apf.escapeXML((n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x))
&& (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
&& n.nodeValue.replace(__valattrrx,__valattrrp) || (""));
}
// value of model node by xpath
function __valm(m, x) {
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return ("");
return (n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x))
&& (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
&& n.nodeValue || ("");
}
function __nod(n, x){ // node by xpath
return n ? n.selectSingleNode(x) : (null);
}
function _nods(n, x){ // array of nodes by xpath
return n ? n.selectNodes(x) : ([]);
}
function __nodm(m, x){ // node of model by xpath
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return (null);
return n.selectSingleNode(x);
}
function _nodsm(m, x){ // array of nodes from model by xpath
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return ([]);
return n.selectNodes(x);
}
function __cnt(n, x){ // count nodes by xpath
return n ? n.selectNodes(x).length:(0);
}
function __cntm(m, x){ // count nodes from model by xpath
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return (0);
return n.selectNodes(x).length;
}
function _xpt(n, x){ // return the query wrapped in an object
return {
xpath: x,
toString: function(){
return "LM Xpath object: " + this.x
}
};
}
function _xptm(m, x){ // return the query with model wrapped in an object
if (m && !m.$isModel) {
var node = m;
m = apf.xmldb.findModel(m);
x = apf.xmlToXpath(node, m.data) + "/" + x;
}
return {
model: m,
xpath: x,
toString: function(){
return "LM Xpath object with model: " + this.x
}
};
}
//----- the following functions are combined model and normal mode ------
function _xml(n, m, x){ // serialize node by xpath via .xml
if (n) x = m;
else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
((n = apf.nameserver.lookup.model[m]) && n.data))) ||
(m.$isModel?m.data:(m.charAt?0:m))))
return ("");
return (n && (n = n.selectSingleNode(x))) && n.xml ||
("");
}
function _xmls(n, m, x){ // serialize nodes by xpath with .xml concatenated
if (n) x = m;
else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
((n = apf.nameserver.lookup.model[m]) && n.data))) ||
(m.$isModel?m.data:(m.charAt?0:m))))
return ("");
for (var i = 0,j = ((n=n.selectNodes(x))).length,o = [];i<j;i++)
o[i] = n[i].xml;
return o.join("");
}
function _valcr(n, cr, m, x){ // value with a create flag
if (n) x = m;
else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
((n = apf.nameserver.lookup.model[m]) && n.data))) ||
(m.$isModel?m.data:(m.charAt?0:m))))
return ("");
if (cr) {
apf.createNodeFromXpath( ni, x );
}else
if ( n = ni.selectSingleNode(x) ){
return (n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x)) &&
(n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n))) && n.nodeValue || ""
}
return ("");
}
function _nodcr(n, cr, m, x){ // node with create flag
if (n) x = m;
else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
((n = apf.nameserver.lookup.model[m]) && n.data))) ||
(m.$isModel?m.data:(m.charAt?0:m))))
return (null);
return n.selectSingleNode(x) || (cr && apf.createNodeFromXpath( n, x ));
}
function _valst(n, x){ // a value with state holding
var m = apf.xmldb.findModel(n);
if (!m)
return ("");
return "[" + m.id + "::" + apf.xmlToXpath(n, m.data, true) + (!x || x == "." ? "" : "/" + x) + "]";
}
function _asn(o, p, v){ // assign propert
if (!o || typeof(o)!="object")
throw new Error(apf.formatErrorString(0,0,"LM Property Assign",
"Cannot assign property on non object, property:"+p));
if (o.setAttribute)
o.setAttribute(p,v);
else
o[p] = v;
return v;
}
function _add(o, p, v){ // += property
return _asn(o,p,o && o[p]+v);
}
function _sub(o, p, v){ // -= propery
return _asn(o,p,o && o[p]-v);
}
function _div(o, p, v){ // /= property
return _asn(o,p,o && o[p]/v);
}
function _mul(o, p, v){ // *= property
return _asn(o,p,o && o[p]*v);
}
// macro implementations
function _local(n){ // local(x) for local n
// check what n is.. if string parse
if (n && n.charAt && n.charAt(0)=="<")
return apf.getXmlDom(n).documentElement;
return n;
}
function _tagName(n1, n2){ // tagname macro
return (n2 && n2.tagName) || (n1 && n1.tagName);
}
function _localName(n1, n2){ // localname macro
return (n2 && n2[apf.TAGNAME]) || (n1 && n1[apf.TAGNAME]);
}
function _nodeValue(n,n2){ // value of a node, or localnode.
if (n2) n = n2;
return (n = (n.nodeType != 1 && n ||
(n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n))) && n.nodeValue || ""
}
// Language processing
function __ret(r){ // return function, translates $[lang] things in data
return r;
}
function __lng(x,x2){ // the language macro
return "$["+x+"]";
}
function _lnged(x,x2){ // editable language macro
return "$["+x+"]";
}
function _(n, m, x){ // wrap a value with editable div
return '<span class="liveEdit" xpath="' + (n
? (m.substr(0,1) != "/"
? apf.xmlToXpath(n, null, false)
: "") + "/" + m
: "") + '">' + ((n?__val(n,m):__valm(m,x)) || "&#32;") + '</span>';
}
// function _edit(n, opts) {
// return '<span class="liveEdit" xpath="' + (apf.xmlToXpath(n, null, false) '">' + ((n?__val(n,m):__valm(m,x)) || "&nbsp;") + '</span>';
// }
function _argwrap(n,x) {
return [n,x];
}
function _argwrapm(m,x) {
return [0,m,x];
}
function _valedx(editMode, args, opt){ // wrap a value with editable div
args[3] = opt;
args[4] = editMode;
return _valed.apply(this, args);
}
function _valed(n, m, x, options, editMode){ // wrap a value with editable div
var res = (n?__val(n,m):__valm(m,x));
if (options && options.multiline && options.editor != "richtext")
res = res.replace(/\n/g, "<br />");
if (editMode !== false) {
var value = res || options && options.initial || "&#32;";
if (!options || !options.richtext)
value = apf.htmlentities(value);
if (options && options.multiline)
value = value
.replace(/&lt;br ?\/?&gt;/g, "<br />")
.replace(/&lt;(\/?div)&gt;/g, "<$1>");
return '<div'
+ ' onmousedown="apf.LiveEdit.mousedown(this, event)" class="liveEdit' + (options && options.multiline ? ' liveeditMultiline' : '') + (!res && options && options.initial ? ' liveEditInitial' : '') + '" xpath="' + (n
? ((m.substr(0,1) != "/"
? apf.xmlToXpath(n, null, false)
: "") + "/" + m).replace(/([\[\{\}\]])/g, "\\$1")
: (self[m]
? (m + ".queryNode('" + x.replace(/'/g, "\\'") + "')").replace(/([\[\{\}\]])/g, "\\$1")
: "")) + '"'
+ (options
? ' options="' + JSON.stringify(options).escapeHTML()
.replace(/"/g, "&quot;")
.replace(/([\[\{\}\]])/g, "\\$1") + '"'
+ (options.editor ? ' editor="' + options.editor + '"' : "")
: "") + '>' + value
+ '</div>';
}
else {
return res;
}
}
var selfrx = /(^|\|)(?!\@|text\(\)|\.\.|[\w\-\:]+?\:\:)/g; // inject self regexp
function _injself(s){ // self inject helper func
return s.charAt?s.replace(selfrx, "$1self::"):s;
}
apf.$lmx = null;
function _async(_n,_c,_a,_w,_f,_this,obj,func,args){ // Async handling
var i = _a.i, v;
if (!_a.ret)_a.ret = [];
if (_a[i])
return _a.ret[i];
_a[i] = true; // flag this ID so args dont get computed again
if (!obj.exec)
return _a.ret[i]=(func)?obj[func].apply(obj,args):obj.apply(obj,args);
var cb = function(data, state, extra) {
if (_w)
delete _w._pc;
if (state != apf.SUCCESS) {
_c(null, state, extra);
}
else{
apf.$lmx = extra;
_a.ret[i] = data;
if (_w)
_f.call(_this,_n,_c,_w,_a);
else
_f.call(_this,_n,_c,_a);
}
};
if (_w && _w._pc) {
_w._pc = {
obj: obj,
func: func,
args: args,
message: obj.createMessage && obj.createMessage(func, args),
_c: _c,
cb: cb
};
}else{
obj.exec(func, args, cb);
}
throw({
x: 1
});
}
function _precall(_w){ // precall
var o;
if (typeof(o = _w._pc) != "object" || !o)
return;
o.obj.exec(o.func, o.args, o.cb, {message: o.message});
throw({x:1});
}
var _clut = apf.color?apf.color.colorshex:{}, _cparse = /^(rgb|hsv|hsb)\(\s+(\d+)\s+,\s+(\d+)\s+,\s+(\d+)\)/
function sort(set, xpath, options) {
var s = new apf.Sort();
options = options || {};
if (!xpath.charAt)xpath = "";
if (xpath.charAt(0)=='@'){
xpath = xpath.slice(1);
options.getValue = function(n) {
return n.getAttribute(xpath);
}
}else{
options.getValue = function(n) {
return apf.queryValue(n,xpath);
}
}
s.set(options);
return s.apply(apf.getArrayFromNodelist(set));
}
function _cthex(c) {
var t;
if ((t=typeof(c))=='string'){
if (c.indexOf('#')==0) {
if (c.length==7) return parseInt(c.slice(-1),16);
return parseInt(c.slice(-1),16); // compute repeat
}
if (t = _clut[a])return t;
if (c=c.match(_cparse)){
if ((t=c[1]) == 'rgb'){
return (((t=c[2])<0?0:(t>255?255:parseInt(t)))<<16)+
(((t=c[3])<0?0:(t>255?255:parseInt(t)))<<8)+
(((t=c[4])<0?0:(t>255?255:parseInt(t))));
} else { // hsv
var h=parseFloat(c[2]),s=parseFloat(c[3]),v=parseFloat(c[4]),
i,m=v*(1-s),n=v*(1-s*((i=floor(((h<0?-h:h)%1)*6))?h-i:1-(h-i)));
switch(i) {
case 6:case 0: return ((v&0xff)<<16)+((n&0xff)<<8)+(m&0xff);
case 1: return ((n&0xff)<<16)+((v&0xff)<<8)+(m&0xff);
case 2: return ((m&0xff)<<16)+((v&0xff)<<8)+(n&0xff);
case 3: return ((m&0xff)<<16)+((n&0xff)<<8)+(v&0xff);
case 4: return ((n&0xff)<<16)+((m&0xff)<<8)+(v&0xff);
default: case 5: return ((v&0xff)<<16)+((m&0xff)<<8)+(n&0xff);
}
}
}
} else if (t=='number')return t;
return null;
}
function lin(f, a, b) {
var fa = parseFloat(a), fb = parseFloat(b), fm = 1-(f = f<0?0:(f>1?1:f));
if (fa!=a || fb!=b)
return (((fa=_cthex(a))&0xff0000)*f+((fb=_cthex(b))&0xff0000)*fm)&0xff0000|
((fa&0xff00)*f+(fb&0xff00)*fm)&0xff00 |
((fa&0xff)*f+(fb&0xff)*fm)&0xff;
return f*fa+fm*fb;
}
var abs = Math.abs, acos = Math.acos, asin = Math.asin,
atan = Math.atan, atan2 = Math.atan2, ceil = Math.ceil,
cos = Math.cos, exp = Math.exp, floor = Math.floor,
log = Math.log, max = Math.max, min = Math.min,
pow = Math.pow, random = Math.random, round = Math.round,
sin = Math.sin, sqrt = Math.sqrt, tan = Math.tan, linear = lin;
function tsin(x){ return 0.5*sin(x)+0.5;}
function tcos(x){ return 0.5*cos(x)+0.5;}
function usin(x){ return 0.5-0.5*sin(x);}
function ucos(x){ return 0.5-0.5*cos(x);}
function snap(a,b){ return round(a/b)*b; }
function clamp(a,b,c){ return a<b?b:(a>c?c:a); }
this.compile = function(code) {
// up-scope much used functions
var _ret = __ret, _val = __val,_valm = __valm, _nod = __nod,
_nodm = __nodm, _cnt = __cnt, _cntm = __cntm, _lng = __lng, _valattr = __valattr;
eval(code);
return _f;
}
this.compileWith = function(code, withs) {
// up-scope much used functions
var _ret = __ret, _val = __val,_valm = __valm, _nod = __nod,
_nodm = __nodm, _cnt = __cnt, _cntm = __cntm, _lng = __lng, _valattr = __valattr;
eval(code);
return _f;
}
var LMBEGINCACHE;
/*LIVEMARKUP BEGIN CACHE
var _ret = __ret, _val = __val,_valm = __valm, _nod = __nod,
_nodm = __nodm, _cnt = __cnt, _cntm = __cntm, _lng = __lng, _valattr = __valattr;
this.c342 = function(_n,_a,_w) {
..cached LM function..
}
this.c342.type = 2;
this.c342.xpaths = {...};
this.c342.props = {...};
this.c723 = function(....){
}
// replace
d.replace(/var_LMBEGINCACHE;[\s\S]*var_LMBEGINCACHE;/,"code");
_async(_n,_c,_a,_w,_f,this,
_async(_n,_c,_a,_w,apf.lm_exec.c342,this,
LIVEMARKUP END CACHE*/
var LMENDCACHE;
})();
/**
* 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
* <a:model id="mdlList">
* <data>
* <item date="2009-11-12" deleted="0"></item>
* <item date="2009-11-11" deleted="0"></item>
* </data>
* </a:model>
* <a:bindings id="bndFolders" >
* <a:caption match="[@date]" />
* <a:icon match="[@icon]" />
* <a:each match="[item]" sort="[@date]" />
* </a:bindings>
* <a:list
* id = "list"
* width = "200"
* height = "200"
* model = "mdlList"
* bindings = "bndFolders" />
* ```
*
* @see apf.smartbinding
* @baseclass
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*
* @default_private
*/
apf.BindingRule = function(struct, tagName) {
this.$init(tagName || true, apf.NODE_HIDDEN, struct);
};
(function(){
this.$bindingRule = true;
this.compile = function(prop) {
return (this["c" + prop] = apf.lm.compile(this[prop], {
xpathmode: 3,
injectself: true
}));
};
this.$compile = function(prop, options) {
return (this["c" + prop + "2"] = apf.lm.compile(this[prop], options));
};
//1 = force no bind rule, 2 = force bind rule
this.$attrExcludePropBind = apf.extend({
value: 1,
match: 1
}, this.$attrExcludePropBind);
this.$booleanProperties["hasaml"] = true;
this.$propHandlers["value"] =
this.$propHandlers["match"] = function(value, prop) {
delete this["c" + prop];
if (this.$amlLoaded) {
//Find parent that this rule works on
var node = this;
while (node && node.$bindingRule)
node = node.parentNode;
if (!node) return;
//Reload parent to propagate change
apf.queue.add("reload" + node.$uniqueId, function(){
node.reload();
});
//Recompile ruleset
if (node.$bindings.$isCompiled)
node.$bindings.$compiled = node.$bindings.compile(
this.localName != "each" && this.localName);
}
};
// *** DOM Handlers *** //
/*this.addEventListener("DOMAttrModified", function(e) {
});*/
this.addEventListener("DOMNodeInserted", function(e) {
//Find parent that this rule works on
var node = this;
while (node.$bindingRule)
node = node.parentNode;
//Reload parent to propagate change
//@todo trigger should be maintained on node itself to prevent dual reload
if ("expanded|collapsed".indexOf(this.localName) == -1)
apf.queue.add("reload" + node.$uniqueId, function(){
node.reload();
});
//If this node is added, add to set
if (e.currentTarget == this) {
(node.$bindings[this.localName]
|| (node.$bindings[this.localName] = [])).pushUnique(this);
}
//@todo apf3.0 test if proc instr and cdata needs to be serialized
//Else just update the binding value
else if (!this.attributes.getNamedItem("value"))
this.value = apf.serializeChildren(this);
//Or do nothing
else return;
//Recompile ruleset
if (node.$bindings.$isCompiled)
node.$bindings.$compiled = node.$bindings.compile(
this.localName != "each" && this.localName);
});
this.addEventListener("DOMNodeRemoved", function(e) {
if (this.$amlDestroyed)
return;
//Find parent that this rule works on
var first, node = this;
while (node && node.$bindingRule)
node = node.parentNode;
if (!node)
return;
//If this node is removed, remove to set
if (e.currentTarget == this) {
if (node.$bindings && node.$bindings[this.localName])
node.$bindings[this.localName].remove(this);
else
return;
}
//@todo apf3.0 test if proc instr and cdata needs to be serialized
//Else just update the binding value
else if (!this.attributes.getNamedItem("value") && (first = this.firstChild)) {
if (first.nodeType == this.NODE_PROCESSING_INSTRUCTION) {
if (first.target == "lm")
this.value = "{" + first.nodeValue + "}";
else
this.value = first.nodeValue;
}
else
this.value = apf.serializeChildren(this).trim();
}
//Or do nothing
else return;
//Reload parent to propagate change
if ("expanded|collapsed".indexOf(this.localName) == -1)
apf.queue.add("reload" + node.$uniqueId, function(){
if (!node.$amlDestroyed)
node.reload();
});
//Recompile ruleset
if (node.$bindings.$isCompiled)
node.$bindings.$compiled = node.$bindings.compile(
this.localName != "each" && this.localName);
});
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var first;
if (!this.value && this.localName != "each" && (first = this.$aml
&& this.$aml.firstChild || this.firstChild)) {
if (first.nodeType == this.NODE_PROCESSING_INSTRUCTION) {
if (first.target == "lm")
this.value = "{" + first.nodeValue + "}";
else
this.value = first.nodeValue;
}
else
this.value = apf.serializeChildren(this.$aml).trim();
}
//Find the parent this rule works on
var pNode = this.parentNode;
while (pNode.$bindingRule)
pNode = pNode.parentNode;
//Add the rule to the set
var bindings = pNode.$bindings || (pNode.$bindings = new apf.ruleList());
(bindings[this.localName] || (bindings[this.localName] = [])).push(this);
//Compile if necessary
if (pNode.localName != "bindings" && (this.localName != "each" || !this.childNodes.length)) {
var ns = this;
while ((ns = ns.nextSibling) && ns.nodeType != 1);
if (!ns || !ns.$bindingRule) {
pNode.$cbindings = pNode.$bindings.compile(
pNode.$bindings.$isCompiled ? this.localName : null);
pNode.dispatchEvent("bindingsload", {
bindings: pNode.$bindings,
compiled: pNode.$cbindings
});
pNode.$checkLoadQueue();
}
}
});
}).call(apf.BindingRule.prototype = new apf.AmlElement());
apf.aml.setElement("icon", apf.BindingRule);
apf.aml.setElement("image", apf.BindingRule);
apf.aml.setElement("caption", apf.BindingRule);
apf.aml.setElement("tooltip", apf.BindingRule);
apf.aml.setElement("css", apf.BindingRule);
apf.aml.setElement("selectable", apf.BindingRule);
apf.aml.setElement("value", apf.BindingRule);
apf.aml.setElement("src", apf.BindingRule);
apf.aml.setElement("collapsed", apf.BindingRule);
apf.aml.setElement("expanded", apf.BindingRule);
apf.aml.setElement("empty", apf.BindingRule);
/**
* 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 `<div>`.
*
*
* #### Example
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:bar id="winGoToFile"
* width = "500"
* skin = "winGoToFile"
* minheight = "35"
* maxheight = "400"
* >
* <a:vbox id="vboxGoToFile" edge="5 5 5 5" padding="5" anchors2="0 0 0 0">
* <a:textbox id="txtGoToFile" realtime="true" skin="searchbox_textbox" focusselect="true" />
* <a:list id="dgGoToFile"
* class = "searchresults noscrollbar"
* skin = "lineselect"
* maxheight = "350"
* scrollbar = "sbShared 32 7 7"
* viewport = "virtual"
* multiselect = "true"
* empty-message = "A filelist would go here.">
* </a:list>
* </a:vbox>
* </a:bar>
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Remarks
*
* This component is used in the accordion element to create its sections. In
* the `apf.statusbar`, the panel element is an alias of [[apf.bar]].
*
* @class apf.bar
* @inherits apf.Presentation
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
* @define bar
* @container
* @allowchild button
* @allowchild {elements}, {anyaml}
*
*/
/**
* @attribute {String} icon Sets or gets the URL pointing to the icon image.
*/
/**
* @attribute {Boolean} [collapsed=false] Sets or gets the collapse panel on load
*
*/
/**
* @attribute {String} title Sets or gets the title string
*/
apf.section = function(struct, tagName) {
this.$init(tagName || "section", apf.NODE_VISIBLE, struct);
};
apf.menubar = function(struct, tagName) {
this.$init(tagName || "menubar", apf.NODE_VISIBLE, struct);
};
apf.bar = function(struct, tagName) {
this.$init(tagName || "bar", apf.NODE_VISIBLE, struct);
};
(function(){
this.$focussable = false;
this.$canLeechSkin = true;
this.$isLeechingSkin = false;
this.$propHandlers["caption"] = function(value) {
this.$int.innerHTML = value;
}
//@todo apf3.0 refactor
this.addEventListener("AMLReparent",
function(beforeNode, pNode, withinParent) {
if (!this.$amlLoaded)
return;
if (this.$isLeechingSkin && !withinParent
&& this.skinName != pNode.skinName
|| !this.$isLeechingSkin
&& this.parentNode.$hasLayoutNode
&& this.parentNode.$hasLayoutNode(this.localName)) {
this.$isLeechingSkin = true;
this.$forceSkinChange(this.parentNode.skinName.split(":")[0] + ":" + skinName);
}
});
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal(this.$isLeechingSkin
? this.localName
: "main");
//Draggable area support, mostly for a:toolbar
if (this.oDrag) //Remove if already exist (skin change)
this.oDrag.parentNode.removeChild(this.oDrag);
this.oDrag = this.$getLayoutNode(this.$isLeechingSkin
? this.localName
: "main", "dragger", this.$ext);
this.$int = this.$getLayoutNode(this.$isLeechingSkin
? this.localName
: "main", "container", this.$ext);
};
this.$loadAml = function(x) {
};
this.$skinchange = function(){
}
}).call(apf.bar.prototype = new apf.Presentation());
apf.menubar.prototype =
apf.section.prototype = apf.bar.prototype;
apf.aml.setElement("bar", apf.bar);
apf.aml.setElement("menubar", apf.menubar);
apf.aml.setElement("section", apf.section);
/**
* @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.
* <code>
* <a:model id="mdlBrowser">
* <data url="http://www.w3c.org"></data>
* </a:model>
* <a:browser
* model = "mdlBrowser"
* width = "800"
* height = "600"
* value = "[@url]" />
* </code>
* Example:
* A shorter way to write this is:
* <code>
* <a:model id="mdlBrowser">
* <data url="http://www.w3c.org"></data>
* </a:model>
* <a:browser
* width = "800"
* height = "600"
* value = "[mdlBrowser::@url]" />
* </code>
*/
apf.browser = function(struct, tagName) {
this.$init(tagName || "browser", apf.NODE_VISIBLE, struct);
};
(function(){
this.implement(
apf.DataAction
,apf.StandardBinding
);
/**
* @attribute {String} src the url to be displayed in this element
* @attribute {String} value alias for the 'url' attribute
*/
this.$supportedProperties.push("value", "src");
this.$propHandlers["src"] =
this.$propHandlers["value"] = function(value, force) {
try {
this.$browser.src = value || "about:blank";
}
catch (e) {
this.$browser.src = "about:blank";
}
};
this.$propHandlers["border"] = apf.Presentation.prototype.$propHandlers["border"];
this.getValue = function() {
return this.value || this.src;
};
/**
* Retrieves the current url that is displayed.
*/
this.getURL = function(){
return this.$browser.src;
};
/**
* Browses to the previous 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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:table columns="100, 100" cellheight="24">
* <a:label>Onclick event</a:label>
* <a:button
* skin = "btn-default-css3"
* class = "btn-green"
* width = "120"
* onclick = "alert('Button has been clicked')">
* Example button</a:button>
* <a:label>Onmouseover event</a:label>
* <a:button
* skin = "btn-default-css3"
* class = "btn-red"
* width = "120"
* onmouseover = "alert('Button has been hovered')">
* Example button</a:button>
* <a:label>Onmouseout event</a:label>
* <a:button
* width = "120"
* onmouseout = "alert('Mouse hover out button')">
* Example button</a:button>
* </a:table>
* </a:application>
* ```
*
* #### Example: Interactions and Colors
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:table columns="250" cellheight="24">
* <!-- startcontent -->
* <a:button
* onclick = "b1.setAttribute('width', '200')"
* width = "250">
* Click me to resize Test button to 200px</a:button>
* <a:button
* onclick = "b1.setAttribute('width', '50')"
* width = "250">
* Click me to resize Test button to 50px</a:button>
* <a:button id="b1" color="#FF8203">Test</a:button>
* <!-- endcontent -->
* </a:table>
* </a:application>
* ```
*
* @class apf.button
* @inherits apf.BaseButton
* @define button
*
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
* @form
* @inherits apf.BaseButton
*/
apf.submit = function(struct, tagName) {
this.$init(tagName || "submit", apf.NODE_VISIBLE, struct);
};
apf.trigger = function(struct, tagName) {
this.$init(tagName || "trigger", apf.NODE_VISIBLE, struct);
};
apf.reset = function(struct, tagName) {
this.$init(tagName || "reset", apf.NODE_VISIBLE, struct);
};
apf.button = function(struct, tagName) {
this.$init(tagName || "button", apf.NODE_VISIBLE, struct);
};
(function() {
this.$useExtraDiv;
this.$childProperty = "caption";
this.$inited = false;
this.$isLeechingSkin = false;
this.$canLeechSkin = true;
// *** Properties and Attributes *** //
this.$focussable = apf.KEYBOARD; // This object can get the focus
this.value = null;
this.$init(function(){
//@todo reparenting
var forceFocus, _self = this, lastDefaultParent;
this.$propHandlers["default"] = function(value) {
if (parseInt(value) != value)
value = apf.isTrue(value) ? 1 : 0;
this["default"] = parseInt(value);
if (!this.focussable && value || forceFocus)
this.setAttribute("focussable", forceFocus = value);
if (lastDefaultParent) {
lastDefaultParent.removeEventListener("focus", setDefault);
lastDefaultParent.removeEventListener("blur", removeDefault);
}
if (!value)
return;
var pNode = this.parentNode;
while (pNode && !pNode.focussable && --value)
pNode = pNode.parentNode;
//Currrently only support for parentNode, this might need to be expanded
if (pNode) {
pNode.addEventListener("focus", setDefault);
pNode.addEventListener("blur", removeDefault);
}
};
function setDefault(e) {
if (e.defaultButtonSet || e.returnValue === false)
return;
e.defaultButtonSet = true;
if (this.$useExtraDiv)
_self.$ext.appendChild(apf.button.$extradiv);
_self.$setStyleClass(_self.$ext, _self.$baseCSSname + "Default");
if (e.toElement != _self && e.toElement) {
// _self.$focusParent
e.toElement.addEventListener("keydown", btnKeyDown);
}
}
function removeDefault(e) {
if (this.$useExtraDiv && apf.button.$extradiv.parentNode == _self.$ext)
_self.$ext.removeChild(apf.button.$extradiv);
_self.$setStyleClass(_self.$ext, "", [_self.$baseCSSname + "Default"]);
if (e.fromElement != _self && e.fromElement) {
//_self.$focusParent
e.fromElement.removeEventListener("keydown", btnKeyDown);
}
}
function btnKeyDown(e) {
var ml;
var f = apf.document.activeElement;
if (f) {
if (f.hasFeature(apf.__MULTISELECT__))
return;
ml = f.multiline;
}
if (!_self.$ext.onmouseup)
return;
if (ml && ml != "optional" && e.keyCode == 13
&& e.ctrlKey || (!ml || ml == "optional")
&& e.keyCode == 13 && !e.ctrlKey && !e.shiftKey && !e.altKey) {
apf.preventDefault(e.htmlEvent);
_self.$ext.onmouseup(e.htmlEvent, true);
}
}
this.addEventListener("focus", setDefault);
this.addEventListener("blur", removeDefault);
this.$enable = function(){
if (this["default"]) {
setDefault({});
if (apf.document.activeElement)
apf.document.activeElement.focus(true);
}
if (this.state && this.value)
this.$setState("Down", {});
else if (this.$mouseOver)
this.$updateState({}, "mouseover");
else
this.$doBgSwitch(1);
};
this.$disable = function(){
if (this["default"])
removeDefault({});
this.$doBgSwitch(4);
this.$setStyleClass(this.$ext, "",
[this.$baseCSSname + "Over", this.$baseCSSname + "Down"]);
};
});
/**
* @attribute {String} icon Sets or gets the url from which the icon image is loaded.
*/
/**
* @attribute {Boolean} state Sets or gets whether this boolean is a multi state button.
*/
/**
* @attribute {String} value Sets or gets the initial value of a state button.
*/
/**
* @attribute {String} color Sets or gets the text color of the caption of this element.
*/
/**
* @attribute {String} caption Sets or gets the text displayed on this element indicating the action when the button is pressed.
*/
/**
* @attribute {String} action Sets or gets one of the default actions this button can perform when pressed.
*
* The possible values include:
*
* - `undo`: Executes undo on the action tracker of the target element.
* - `redo`: Executes redo on the action tracker of the target element.
* - `remove`: Removes the selected node(s) of the target element.
* - `add`: Adds a node to the target element.
* - `rename`: Starts the rename function on the target element.
* - `login`: Calls log in on the auth element with the values of the textboxes of type username and password.
* - `logout`: Calls lot out on the auth element.
* - `submit`: Submits the data of a model specified as the target.
* - `ok`: Executes a `commitTransaction()` on the target element, and closes or hides that element.
* - `cancel`: Executes a `rollbackTransaction()` on the target element, and closes or hides that element.
* - `apply`: Executes a `commitTransaction()` on the target element.
* - `close`: Closes the target element.
*/
/**
* @attribute {String} target Sets or gets the id of the element to apply the action to. Defaults to the parent container.
*/
/**
* @attribute {Number} default Sets or gets the search depth for which this button is the default action. `1` specifies the direct parent, `2` specifies the parent of this parent, _.e.t.c._
*/
/**
* @attribute {String} submenu Sets or gets the name of the contextmenu to display when the button is pressed.
*/
//this.$booleanProperties["default"] = true;
this.$booleanProperties["state"] = true;
this.$supportedProperties.push("icon", "value", "tooltip", "state",
"color", "caption", "action", "target", "default", "submenu", "hotkey");
this.$propHandlers["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
* <a:button hotkey="Ctrl-Z">Undo</a:button>
* ```
*/
this.$propHandlers["hotkey"] = function(value) {
if (this.$hotkey)
apf.setNodeValue(this.$hotkey, value);
if (this.$lastHotkey) {
apf.hotkeys.remove(this.$lastHotkey[0], this.$lastHotkey[1]);
delete this.$lastHotkey[0];
}
if (value) {
this.$lastHotkey = [value];
var _self = this;
apf.hotkeys.register(value, this.$lastHotkey[1] = function(){
//hmm not very scalable...
_self.$setState("Over", {});
$setTimeout(function(){
_self.$setState("Out", {});
}, 200);
if (_self.$clickHandler && _self.$clickHandler())
_self.$updateState(e || event, "click");
else
_self.dispatchEvent("click");
});
}
if (this.tooltip)
apf.GuiElement.propHandlers.tooltip.call(this, this.tooltip);
}
//@todo move this to menu.js
function menuKeyHandler(e) {
return;
var key = e.keyCode;
var next, nr = apf.getChildNumber(this);
if (key == 37) { //left
next = nr == 0
? this.parentNode.childNodes.length - 1
: nr - 1;
this.parentNode.childNodes[next].dispatchEvent("mouseover");
}
else if (key == 39) { //right
next = (nr >= this.parentNode.childNodes.length - 1)
? 0
: nr + 1;
this.parentNode.childNodes[next].dispatchEvent("mouseover");
}
}
function menuDown(e) {
var menu = self[this.submenu] || this.submenu,
$button1;
this.value = !this.value;
if (this.value)
this.$setState("Down", {});
var menuPressed = this.parentNode.menuIsPressed;
if (menuPressed && menuPressed != this) {
menuPressed.setValue(false);
var oldMenu = self[menuPressed.submenu] || menuPressed.submenu;
if (oldMenu != (self[this.submenu] || this.submenu))
oldMenu.$propHandlers["visible"].call(oldMenu, false, true);
}
if (!this.value) {
menu.hide();
this.$setState("Over", {}, "toolbarover");
if ($button1 = this.parentNode.$button1)
$button1.$setState("Over", {}, "toolbarover");
this.parentNode.menuIsPressed = false;
if (this.parentNode.hasMoved)
this.value = false;
if (apf.hasFocusBug)
apf.window.$focusfix();
return false;
}
this.parentNode.menuIsPressed = this;
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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:checkbox
* id = "ch1"
* values = "full|empty"
* checked = "true">Full</a:checkbox>
* <a:textbox value="the glass is {ch1.value}"></a:textbox>
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Example: Disabled Values
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:checkbox checked="true">Option 1</a:checkbox>
* <a:checkbox>Option 2</a:checkbox>
* <a:checkbox checked="true" disabled="true">Option 3</a:checkbox>
* <a:checkbox disabled="true">Option 4</a:checkbox>
* <a:checkbox label="Option 5" />
* <a:checkbox label="Option 6" />
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.checkbox
*
* @define checkbox
*
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
* @form
* @inherits apf.BaseButton
* @inheritsElsewhere apf.XForms
*
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*
* #### Example
*
* Sets the value of the checkbox based on data loaded into this component.
* ```xml
* <a:model id="mdlCheckbox">
* <data answer="Something"></data>
* </a:model>
* <a:checkbox
* model = "mdlCheckbox"
* value = "[@answer]">Caption</a:checkbox>
* ```
*
* A shorter way to write this is:
*
* ```xml
* <a:model id="mdlCheckbox">
* <data answer="Something"></data>
* </a:model>
* <a:checkbox value="[mdlCheckbox::@answer]">Caption</a:checkbox>
* ```
*/
apf.checkbox = function(struct, tagName) {
this.$init(tagName || "checkbox", apf.NODE_VISIBLE, struct);
};
(function() {
this.implement(
apf.DataAction
);
//Options
this.$focussable = apf.KEYBOARD; // This object can get the focus
this.checked = false;
// *** Properties and Attributes *** //
this.$booleanProperties["checked"] = true;
this.$supportedProperties.push("value", "checked", "label", "values");
/**
* @attribute {String} value Sets or gets the value of this element.
*/
this.$propHandlers["value"] = function(value) {
value = (typeof value == "string" ? value.trim() : value);
if (value == "" && this["default"])
value = this.value = apf.isTrue(this["default"]);
if (this.$values) {
this.checked = (typeof value != "undefined" && value !== null
&& value.toString() == this.$values[0].toString());
}
else {
this.checked = apf.isTrue(value);
}
if (this.checked)
apf.setStyleClass(this.$ext, this.$baseCSSname + "Checked");
else
apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Checked"]);
};
/**
* @attribute {Boolean} checked Sets or gets whether the element is in the checked state.
*/
this.$propHandlers["checked"] = function(value) {
if (!this.$values) {
if (this.getAttribute("values"))
this.$propHandler["values"].call(this, this.getAttribute("values"));
//else
//this.$values = [true, false];
}
this.setProperty("value", this.$values ? this.$values[value ? 0 : 1] : true);
};
/**
* @attribute {String} label Sets or gets the caption of the label explaining what
* the meaning of the checked state of this element is.
*/
this.$propHandlers["label"] = function(value) {
if (!this.$ext)
return;
var lbl = this.$getLayoutNode("main", "label", this.$ext);
if (!lbl)
return;
if (lbl.nodeType == 1)
lbl.innerHTML = value;
else
lbl.nodeValue = value;
};
/**
* @attribute {String} values Sets or gets a pipe seperated list of two values which
* correspond to the two states of the checkbox. The first for the checked
* state, the second for the unchecked state.
*/
this.$propHandlers["values"] = function(value) {
this.$values = typeof value == "string"
? value.split("\|")
: (value || [1, 0]);
this.$propHandlers["value"].call(this, this.value);
};
// *** Public Methods *** //
/**
* Sets the value of this element. This should be one of the values
* specified in the [[apf.checkbox.values]] attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
if (!this.$values) return;
this.setProperty("value", value, false, true);
};
/**
* Returns the current value.
*/
this.getValue = function(){
return this.xmlRoot ? (this.$values
? this.$values[this.checked ? 0 : 1]
: this.checked) : this.value;
};
/**
* Sets the checked state (and related value).
*/
this.check = function(){
this.setProperty("value", this.$values
? this.$values[0]
: true, false, true);
};
/**
* Sets the unchecked state (and related value).
*/
this.uncheck = function(){
this.setProperty("value", this.$values
? this.$values[1]
: false, false, true);
};
// *** Private state handling methods *** //
this.addEventListener("$clear", function(){
this.setProperty("value", this.$values ? this.$values[1] : false);
});
this.$enable = function(){
if (this.$input) this.$input.disabled = false;
this.$doBgSwitch(1);
};
this.$disable = function(){
if (this.$input) this.$input.disabled = true;
this.$doBgSwitch(4);
};
this.$setState = function(state, e, strEvent) {
//if (this.disabled) return;
this.$doBgSwitch(this.states[state]);
this.$setStyleClass(this.$ext, (state != "Out" ? this.$baseCSSname + state : ""),
[this.$baseCSSname + "Down", this.$baseCSSname + "Over"]);
this.state = state; // Store the current state so we can check on it coming here again.
if (strEvent)
this.dispatchEvent(strEvent, {htmlEvent: e});
/*if (state == "Down")
apf.cancelBubble(e, this);
else
e.cancelBubble = true;*/
};
this.$clickHandler = function(){
//this.checked = !this.checked;
this.change(this.$values
? this.$values[(!this.checked) ? 0 : 1]
: !this.checked);
if (this.validate) //@todo rewrite button
this.validate(true);
return true;
};
// *** Init *** //
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.$input = this.$getLayoutNode("main", "input", this.$ext);
this.$notfromext = this.$input && this.$input != this.$ext;
this.$setupEvents();
};
this.$childProperty = "label";
this.addEventListener("$skinchange", function(){
if (this.label)
this.$propHandlers["label"].call(this, this.label);
})
}).call(apf.checkbox.prototype = new apf.BaseButton());
apf.aml.setElement("checkbox", apf.checkbox);
/**
* 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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:menu id="ctxMenu">
* <a:item>Choice 1!</a:item>
* <a:item>Choice 2!</a:item>
* </a:menu>
* <a:list width="300" id="list1">
* <a:contextmenu menu="ctxMenu" />
* <a:item>The Netherlands</a:item>
* <a:item>United States of America</a:item>
* <a:item>Poland</a:item>
* </a:list>
* <!-- endcontent -->
* Right-click on the list to reveal the context menu!
* </a:application>
* ```
*
* @class apf.contextmenu
* @define contextmenu
* @inherits apf.AmlElement
* @selection
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
/**
* @attribute {String} menu Sets or gets the id of the menu element.
*/
/**
* @attribute {String} select Sets or gets the XPath executed on the selected element of the databound element which determines whether this context menu is shown.
*
*
*/
apf.contextmenu = function(){
this.$init("contextmenu", apf.NODE_HIDDEN);
};
(function(){
this.$amlNodes = [];
//1 = force no bind rule, 2 = force bind rule
this.$attrExcludePropBind = apf.extend({
"match" : 1
}, this.$attrExcludePropBind);
this.register = function(amlParent) {
if (!amlParent.contextmenus)
amlParent.contextmenus = [];
amlParent.contextmenus.push(this);
};
this.unregister = function(amlParent) {
amlParent.contextmenus.remove(this);
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
this.register(this.parentNode);
});
}).call(apf.contextmenu.prototype = new apf.AmlElement());
apf.aml.setElement("contextmenu", apf.contextmenu);
/**
* An element showing an error message when the attached element
* is in erroneous state and has the `invalidmsg=""` attribute specified.
*
* In most cases, the errorbox element is implicit and will be created
* automatically.
*
* #### Example
*
* ```xml
* <a:errorbox>
* Invalid e-mail address entered.
* </a:errorbox>
* ```
*
* #### Remarks
*
* In most cases the errorbox element is not created directly, but implicitly
* by a validationgroup. An element that goes into an error state will
* show the errorbox.
*
* ```xml
* <a:bar validgroup="vgForm">
* <a:label>Phone number</a:label>
* <a:textbox id="txtPhone"
* required = "true"
* pattern = "(\d{3}) \d{4} \d{4}"
* invalidmsg = "Incorrect phone number entered" />
*
* <a:label>Password</a:label>
* <a:textbox
* required = "true"
* mask = "password"
* minlength = "4"
* invalidmsg = "Please enter a password of at least four characters" />
* <a:button onclick="vgForm.validate()">Validate</a:button>
* </a:bar>
* ```
*
* To check if the element has valid information entered, leaving the textbox
* (focussing another element) will trigger a check. A programmatic check
* can be done using the following code:
*
* ```javascript
* txtPhone.validate();
*
* //Or use the html5 syntax
* txtPhone.checkValidity();
* ```
*
* To check for the entire group of elements use the validation group. For only
* the first non-valid element the errorbox is shown. That element also receives
* focus.
*
* ```javascript
* vgForm.validate();
* ```
*
* @class apf.errorbox
* @define errorbox
*
* @allowchild {anyxhtml}
*
*
* @inherits apf.Presentation
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
apf.errorbox = function(struct, tagName) {
this.$init(tagName || "errorbox", apf.NODE_VISIBLE, struct);
};
(function(){
this.$positioning = "basic";
this.display = function(host) {
this.host = host;
var refHtml =
host.$ext;
document.body.appendChild(this.$ext);
/*var pos = apf.getAbsolutePosition(refHtml, document.body);
if (document != refHtml.ownerDocument) {
var pos2 = apf.getAbsolutePosition(refHtml.ownerDocument.parentWindow.frameElement, document.body);
pos[0] += pos2[0];
pos[1] += pos2[1];
}*/
var x = (parseFloat(host.$getOption && host.$getOption("main", "erroffsetx") || 0)),
y = (parseFloat(host.$getOption && host.$getOption("main", "erroffsety") || 0));
//this.$ext.style.left = x + "px"
//this.$ext.style.top = y + "px"
this.show();
apf.popup.show(this.$uniqueId, {
x: x,
y: y,
animate: false,
ref: refHtml,
allowTogether: true
});
this.$setStyleClass(this.$ext,
x + this.$ext.offsetWidth > this.$ext.offsetParent.offsetWidth
? "rightbox"
: "leftbox", ["leftbox", "rightbox"]);
};
/**
* Sets the message of the errorbox.
* @param {String} value The message to set.
*/
this.setMessage = function(value) {
if (value && value.indexOf(";") > -1) {
value = value.split(";");
value = "<strong>" + value.shift() + "</strong>" + value.join(";");
}
this.$int.innerHTML = value || "";
};
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.$int = this.$getLayoutNode("main", "container", this.$ext);
this.oClose = this.$getLayoutNode("main", "close", this.$ext);
if (this.oClose) {
var _self = this;
this.oClose.onclick = function(){
_self.hide();
if (apf.document.activeElement)
apf.document.activeElement.focus(true, {mouse:true});
};
}
this.$ext.onmousedown = function(e) {
(e || event).cancelBubble = true;
}
apf.popup.setContent(this.$uniqueId, this.$ext, "", null, null);
};
this.$loadAml = function(x) {
if (!apf.isTrue(this.getAttribute("visible")))
this.hide();
};
this.$destroy = function(){
if (this.oClose)
this.oClose.onclick = null;
this.$ext.onmousedown = null;
apf.popup.removeContent(this.$uniqueId);
};
}).call(apf.errorbox.prototype = new apf.Presentation());
apf.aml.setElement("errorbox", apf.errorbox);
/**
* 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 `<fieldset>` in HTML.
*
* #### Example
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:frame caption="Options">
* <a:radiobutton value="1">Option 1</a:radiobutton>
* <a:radiobutton value="2">Option 2</a:radiobutton>
* <a:radiobutton value="3">Option 3</a:radiobutton>
* <a:radiobutton value="4">Option 4</a:radiobutton>
* </a:frame>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.frame
* @define frame
* @container
* @allowchild {elements}, {anyaml}
*
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.9
*
* @inherits apf.Presentation
*/
apf.panel = function(struct, tagName) {
this.$init(tagName || "panel", apf.NODE_VISIBLE, struct);
};
apf.fieldset = function(struct, tagName) {
this.$init(tagName || "fieldset", apf.NODE_VISIBLE, struct);
};
apf.frame = function(struct, tagName) {
this.$init(tagName || "frame", apf.NODE_VISIBLE, struct);
};
(function(){
this.implement(apf.BaseStateButtons);
this.$focussable = false;
// *** Properties and Attributes *** //
/**
* @attribute {String} caption Sets or gets the caption text.
*/
this.$supportedProperties.push("caption", "url");
this.$propHandlers["caption"] = function(value) {
if (!this.oCaption) return;
if (this.oCaption.nodeType == 1)
this.oCaption.innerHTML = value;
else
this.oCaption.nodeValue = value;
};
/**
* @attribute {String} icon Sets or gets the location of the image.
*/
this.$propHandlers["icon"] = function(value) {
var oIcon = this.$getLayoutNode("main", "icon", this.$ext);
if (!oIcon) return;
if (oIcon.nodeType == 1)
oIcon.style.display = value ? "block" : "none";
apf.skins.setIcon(oIcon, value, this.iconPath);
};
/**
* @attribute {String} icon Sets or gets the URL location (if this is an iframe).
*/
this.$propHandlers["url"] = function(value) {
var node = this.oCaption;
if (node.tagName == "A" || node.nodeType != 1)
node = node.parentNode;
node.innerHTML = "<a href='" + value + "' "
+ (value.match(/^http:\/\//) ? "target='_blank'" : "") + ">"
+ this.caption + "</a>";
this.oCaption = this.oCaption.firstChild;
};
this.$propHandlers["activetitle"] = function(value) {
var node = this.oCaption.parentNode;
// if (node.nodeType != 1) node = node.parentNode;
var _self = this;
node.addEventListener("click", function(e) {
if (e.target == node || 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 `<a:img>` element:
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:model id="mdlPictures">
* <data>
* <picture title="Ceiling Cat" src="http://htstatic.ibsrv.net/forums/honda-tech/ceiling-cat/ceiling-cat-6.jpg" />
* <picture title="Maru" src="http://1.bp.blogspot.com/_4Cb_t7BLaIA/TCY3jyIx4SI/AAAAAAAAAbw/K-Ey_u36y8o/s400/maru+the+japanese+cat.jpg" />
* <picture title="Lime Cat" src="http://www.cs.brown.edu/orgs/artemis/2012/catsoftheworld/lime-cat.jpg" />
* </data>
* </a:model>
* <a:list
* id = "lstPics"
* model = "mdlPictures">
* <a:each match="[picture]" >
* <a:caption match="[@title]" />
* </a:each>
* </a:list>
* <a:img
* model = "{lstPics.selected}"
* value = "[@src]" />
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.img
* @define img
* @media
* @allowchild {smartbinding}
*
*
* @inherits apf.BaseSimple
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
*/
/**
* @event click Fires when a user presses a mouse button while over this element.
*
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*
* #### Example
*
* Sets the image source based on data loaded into this component.
*
* ```xml
* <a:model id="mdlPictures">
* <data src="path/to/image.jpg" />
* </a:model>
* <a:img
* model = "mdlPictures"
* value = "[@src]"
* width = "300"
* height = "300" />
* ```
*/
apf.img = function(struct, tagName) {
this.$init(tagName || "img", apf.NODE_VISIBLE, struct);
};
apf.preview = function(struct, tagName) {
this.$init(tagName || "preview", apf.NODE_VISIBLE, struct);
};
(function(){
/**
* Sets the value of this element. This should be one of the values
* specified in the `values` attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current image
*/
this.getValue = function(value) {
return this.value;
};
this.$supportedProperties.push("value", "src");
/**
* @attribute {String} value Sets or gets the url location of the image displayed.
*/
this.$propHandlers["src"] =
this.$propHandlers["value"] = function(value) {
if (this.oImage.nodeType == 1)
this.oImage.style.backgroundImage = "url(" + value + ")";
else
this.oImage.nodeValue = value;
//@todo resize should become a generic thing
if (this.oImage.nodeType == 2 && !this.$resize.done) {
if (this.oImg) {
//@todo add this to $destroy
var pNode = apf.hasSingleRszEvent ? this.$pHtmlNode : this.$ext;
apf.layout.setRules(pNode, this.$uniqueId + "_image",
"var o = apf.all[" + this.$uniqueId + "];\
if (o) o.$resize()");
apf.layout.queue(pNode);
this.oImg.onload = function(){
apf.layout.forceResize(pNode);
}
}
this.$resize.done = true;
}
if (this.oImg) {
this.oImg.style.display = value ? "block" : "none";
//RLD: disabled lines below for the preview element. the image is probably not loaded yet.
//if (value)
//this.$resize();
}
};
this.refetch = function(){
this.$propHandlers["value"].call(this, "")
this.$propHandlers["value"].call(this, this.value || this.src)
}
this.addEventListener("$clear", function(){
this.value = "";
if (this.oImg)
this.oImg.style.display = "none";
});
// *** Init *** //
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.$ext.onclick = function(e) {
this.host.dispatchEvent("click", {htmlEvent: e || event});
};
this.oImage = this.$getLayoutNode("main", "image", this.$ext);
if (this.oImage.nodeType == 1)
this.oImg = this.oImage.getElementsByTagName("img")[0];
if (this.localName == "preview") {
var _self = this;
this.$ext.onclick = function() {
if (!_self.sPreview) return;
_self.$ext.innerHTML = _self.sPreview;
this.onclick = null;
};
}
var _self = this;
apf.addListener(this.$ext, "mouseover", function(e) {
if (!_self.disabled)
_self.dispatchEvent("mouseover", {htmlEvent: e});
});
apf.addListener(this.$ext, "mouseout", function(e) {
if (!_self.disabled)
_self.dispatchEvent("mouseout", {htmlEvent: e});
});
};
this.addEventListener("DOMNodeInsertedIntoDocument", function() {
var node,
val = "",
i = this.childNodes.length;
for (; i >= 0; --i) {
if ((node = this.childNodes[i]) && node.nodeName
&& node.nodeName == "#cdata-section") {
val = node.nodeValue;
node.removeNode();
}
}
this.sPreview = val;
});
this.$resize = function(){
var diff = apf.getDiff(this.$ext);
var wratio = 1, hratio = 1;
this.oImg.style.width = "";
this.oImg.style.height = "";
if (this.oImg.offsetWidth > this.$ext.offsetWidth)
wratio = this.oImg.offsetWidth / (this.$ext.offsetWidth - diff[0]);
if (this.oImg.offsetHeight > this.$ext.offsetHeight)
hratio = this.oImg.offsetHeight / (this.$ext.offsetHeight - diff[1]);
if (wratio > hratio && wratio > 1)
this.oImg.style.width = "100%";
else if (hratio > wratio && hratio > 1)
this.oImg.style.height = "100%";
this.oImg.style.top = ((this.$ext.offsetHeight - apf.getHeightDiff(this.$ext)
- this.oImg.offsetHeight) / 2) + "px";
}
}).call(apf.img.prototype = new apf.BaseSimple());
apf.preview.prototype = apf.img.prototype;
apf.aml.setElement("img", apf.img);
apf.aml.setElement("preview", apf.preview);
apf.aml.setElement("name", apf.BindingRule);
apf.aml.setElement("image", apf.BindingRule);
/**
* An element displaying a text in the user interface, usually specifying
* a description of another element. When the user clicks on the label, it
* can set the focus to the connected AML element.
*
* #### Example: Connecting with "For"
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:label
* for = "txtAddress"
* disabled = "true"
* caption = "Disabled label"></a:label>
* <a:textbox id="txtAddress" />
* <a:label
* for = "txtAddress2">Not Disabled</a:label>
* <a:textbox id="txtAddress2" />
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.label
* @define label
* @allowchild {smartbinding}
*
* @form
* @inherits apf.BaseSimple
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*
* #### Example
*
* Sets the label text based on data loaded into this component.
*
* ```xml
* <a:model id="mdlLabel">
* <data text="Some text"></data>
* </a:model>
* <a:label model="mdlLabel" value="[@text]" />
* ```
*
* A shorter way to write this is:
*
* ```xml
* <a:model id="mdlLabel">
* <data text="Some text"></data>
* </a:model>
* <a:label value="[mdlLabel::@text]" />
* ```
*/
apf.label = function(struct, tagName) {
this.$init(tagName || "label", apf.NODE_VISIBLE, struct);
};
(function(){
this.implement(
apf.DataAction,
apf.ChildValue
);
var _self = this;
this.$focussable = false;
var forElement;
/**
* Sets the value of this element. This should be one of the values
* specified in the values attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current value
*/
this.getValue = function(){
return this.value;
}
/**
* @attribute {String} caption Sets or gets the text displayed in the area defined by this
* element. Using the value attribute provides an alternative to using
* the text using a text node.
*
*/
/**
* @attribute {String} for Sets or gets the id of the element that receives the focus
* when the label is clicked on.
*/
/**
* @attribute {String} textalign Sets or gets the text alignment value for the label.
*/
this.$supportedProperties.push("caption", "for", "textalign");
this.$propHandlers["caption"] = function(value) {
this.$caption.innerHTML = value;
};
this.$propHandlers["for"] = function(value) {
forElement = typeof value == "string" ? self[value] : value;
};
this.$propHandlers["textalign"] = function(value) {
this.$caption.style.textAlign = value || "";
};
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.$caption = this.$getLayoutNode("main", "caption", this.$ext);
if (this.$caption.nodeType != 1)
this.$caption = this.$caption.parentNode;
this.$ext.onmousedown = function(){
if (forElement && forElement.$focussable && forElement.focussable)
forElement.focus();
}
var _self = this;
apf.addListener(this.$ext, "click", function(e) {
if (!_self.disabled)
_self.dispatchEvent("click", {htmlEvent: e});
});
apf.addListener(this.$ext, "mouseover", function(e) {
if (!_self.disabled)
_self.dispatchEvent("mouseover", {htmlEvent: e});
});
apf.addListener(this.$ext, "mouseout", function(e) {
if (!_self.disabled)
_self.dispatchEvent("mouseout", {htmlEvent: e});
});
};
this.$childProperty = "caption";
}).call(apf.label.prototype = new apf.BaseSimple());
apf.aml.setElement("label", apf.label);
apf.colorbox = function(struct, tagName) {
this.$init(tagName || "colorbox", apf.NODE_VISIBLE, struct);
};
(function(){
this.implement(
apf.DataAction,
apf.ChildValue
);
var _self = this;
this.$focussable = false;
var forElement;
/**
* Sets the value of this element. This should be one of the values
* specified in the values attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current value
*/
this.getValue = function(){
return this.value;
}
/**
* @attribute {String} caption Sets or gets the text displayed in the area defined by this
* element. Using the value attribute provides an alternative to using
* the text using a text node.
*
*/
/**
* @attribute {String} for Sets or gets the id of the element that receives the focus
* when the colorbox is clicked on.
*/
/**
* @attribute {String} textalign Sets or gets the text alignment value for the colorbox.
*/
this.$supportedProperties.push("value");
this.$propHandlers["value"] = function(value) {
this.$input.value = value;
};
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.$input = this.$getLayoutNode("main", "input", this.$ext);
var _self = this;
this.$input.onchange = function(){
_self.change(this.value);
}
};
this.$childProperty = "value";
}).call(apf.colorbox.prototype = new apf.BaseSimple());
apf.aml.setElement("colorbox", apf.colorbox);
/*
* @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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:window
* id = "winMail"
* buttons = "min|max|close"
* title = "Mail Message"
* visible = "true"
* resizable = "true"
* width = "500"
* modal = "true"
* height = "400"
* skin = "bk-window2">
* <a:vbox>
* <a:hbox margin="5px">
* <a:label for="to" caption="To:"/>
* <a:textbox id="to" margin="0 0 0 5" width="140" />
* </a:hbox>
* <a:hbox margin="5">
* <a:label for="subject" caption="Subject:" />
* <a:textbox id="subject" width="140" />
* </a:hbox>
* <a:textarea height="200" width="400"/>
* </a:vbox>
* </a:window>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.window
* @define window
* @container
* @allowchild {elements}, {smartbinding}, {anyaml}
*
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
* @inherits apf.Presentation
* @inheritsElsewhere apf.Transaction
*
*/
/**
* @event show Fires when the window is opened.
*/
/**
* @event close Fires when the window is closed.
*/
/**
*
* @event editstart Fires before the user edits the properties of this window. Used mostly for when this window is part of the portal.
*/
/**
* @event editstop Fires after the user edited the properties of this window. Used mostly for when this window is part of the portal.
* @cancelable Prevents the edit panel from being closed.
*/
/**
* @event statechange Fires after the state of this window changed.
* @param 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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:progressbar
* min = "0"
* max = "100"
* value = "40"
* width = "300" />
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Example: Progressbars with Varying Speeds
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:progressbar
* id = "pb1"
* min = "0"
* max = "100"
* value = "40"
* width = "300"><a:script>//<!--
* pb1.start();
* //--></a:script>
* </a:progressbar>
*
* <a:progressbar
* id = "pb2"
* min = "0"
* max = "100"
* value = "40"
* width = "300"><a:script>//<!--
* pb2.start(50);
* //--></a:script>
* </a:progressbar>
* </a:application>
* ```
*
* #### Example: Dynmically Controlling the Progressbar
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:progressbar
* id = "pb3"
* min = "0"
* max = "100"
* value = "0"
* width = "300" />
* <a:table
* columns = "80, 80, 80, 80"
* cellheight = "24"
* margin = "15 0">
* <a:button onclick="pb3.start()">Start</a:button>
* <a:button onclick="pb3.pause()">Pause</a:button>
* <a:button onclick="pb3.stop()">Stop</a:button>
* <a:button onclick="pb3.clear()">Clear</a:button>
* <a:button onclick="pb3.enable()">Enable</a:button>
* <a:button onclick="pb3.disable()">Disable</a:button>
* </a:table>
* </a:application>
* ```
*
* @class apf.progressbar
* @define progressbar
* @allowchild {smartbinding}
*
* @form
* @inherits apf.StandardBinding
* @inherits apf.DataAction
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.9
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*
* #### Example
*
* Sets the progress position based on data loaded into this component.
*
* ```xml
* <a:model>
* <data progress="50"></data>
* </a:model>
* <a:progressbar min="0" max="100" value="[@progress]" />
* ```
*
* A shorter way to write this is:
*
* ```xml
* <a:model id="mdlProgress">
* <data progress="50"></data>
* </a:model>
* <a:progressbar value="[mdlProgress::@progress]" />
* ```
*/
apf.progress = function(struct, tagName) {
this.$init(tagName || "progress", apf.NODE_VISIBLE, struct);
};
apf.progressbar = function(struct, tagName) {
this.$init(tagName || "progressbar", apf.NODE_VISIBLE, struct);
};
(function(){
this.implement(apf.DataAction);
this.$focussable = false; // This object can get the focus
// *** Properties and Attributes *** //
this.value = 0;
this.min = 0;
this.max = 100;
this.$running = false;
this.$timer;
/**
* @attribute {Boolean} autostart Sets or gets whether the progressbar starts automatically.
*/
/**
* @attribute {Boolean} autohide Sets or gets whether the progressbar hides when the progress is at 100%. Setting this to `true` hides the progressbar at start when autostart is not set to `true`.
*/
this.$booleanProperties["autostart"] = true;
this.$booleanProperties["autohide"] = true;
this.$supportedProperties.push("value", "min", "max", "autostart", "autohide");
/**
* @attribute {String} value Sets or gets the position of the progressbar stated between
* the min and max value.
*/
this.$propHandlers["value"] = function(value) {
this.value = parseInt(value) || this.min;
if (this.value >= this.max)
apf.setStyleClass(this.$ext, this.$baseCSSname + "Complete", [this.$baseCSSname + "Running", this.$baseCSSname + "Half"]);
else
apf.setStyleClass(this.$ext, this.$baseCSSname + "Running", [this.$baseCSSname + "Complete"]);
if (this.value >= this.max / 2)
apf.setStyleClass(this.$ext, this.$baseCSSname + "Half", []);
this.oSlider.style.width = (this.value * 100 / (this.max - this.min)) + "%"
/*Math.max(0,
Math.round((this.$ext.offsetWidth - 5)
* (this.value / (this.max - this.min)))) + "px";*/
this.oCaption.nodeValue =
Math.round((this.value / (this.max - this.min)) * 100) + "%";
};
/**
* @attribute {Number} min Sets or gets the minimum value the progressbar may have. This is
* the value that the progressbar has when it is at its start position.
*/
this.$propHandlers["min"] = function(value) {
this.min = parseFloat(value);
};
/**
* @attribute {Number} max Sets or gets the maximum value the progressbar may have. This is
* the value that the progressbar has when it is at its end position.
*/
this.$propHandlers["max"] = function(value) {
this.max = parseFloat(value);
};
// *** Public Methods *** //
/**
* Sets the value of this element. This should be one of the values
* specified in the `values` attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current value.
*/
this.getValue = function(){
return this.value;
};
/**
* Resets the progress indicator.
*/
this.clear = function(){
this.$clear();
};
this.$clear = function(restart, restart_time) {
clearInterval(this.$timer);
this.setValue(this.min);
//this.oSlider.style.display = "none";
apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Running", this.$baseCSSname + "Complete"]);
if (restart) {
var _self = this;
this.$timer = setInterval(function(){
_self.start(restart_time);
});
}
if (this.autohide)
this.hide();
this.$running = false;
};
/**
* Starts the progress indicator.
* @param {Number} start Sets or gets the time between each step in milliseconds.
*/
this.start = function(time) {
if (this.autohide)
this.show();
clearInterval(this.$timer);
//if (this.value == this.max)
//this.setValue(this.min + (this.max - this.min) * 0.5);
//this.oSlider.style.display = "block";
var _self = this;
this.$timer = setInterval(function(){
if (_self.$amlDestroyed)
clearInterval(_self.$timer);
else
_self.$step();
}, time || 1000);
this.$setStyleClass(this.$ext, this.$baseCSSname + "Running");
};
/**
* Pauses the progress indicator.
*/
this.pause = function(){
clearInterval(this.$timer);
};
/**
* Stops the progress indicator from moving.
* @param {Boolean} restart Specifies whether a `this.$timer` should start with a new indicative progress indicator.
* @param {Number} [time=500] The internal (in milliseconds)
* @param {Number} [restart_time] The time for the next restart to occur
*/
this.stop = function(restart, time, restart_time) {
clearInterval(this.$timer);
this.setValue(this.max);
var _self = this;
this.$timer = setInterval(function(){
_self.$clear(restart, (restart_time || 0));
}, time || 500);
};
// *** Private methods *** //
this.$step = function(){
if (this.value == this.max)
return;
this.setValue(this.value + 1);
};
// *** Init *** //
this.$draw = function(clear, parentNode, Node, transform) {
//Build Main Skin
this.$ext = this.$getExternal();
this.oSlider = this.$getLayoutNode("main", "progress", this.$ext);
this.oCaption = this.$getLayoutNode("main", "caption", this.$ext);
};
this.$loadAml = function(x) {
if (this.autostart)
this.start();
if (this.autohide)
this.hide();
};
}).call(apf.progressbar.prototype = new apf.StandardBinding());
apf.progress.prototype = apf.progressbar.prototype;
apf.aml.setElement("progress", apf.progress);
apf.aml.setElement("progressbar", apf.progressbar);
/**
* This element displays a two state button which is one of a grouped set.
* Only one of these buttons in the set can be selected at the same time.
*
* #### Example: Settings Groups
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:table columns="100, 150" cellheight="20">
* <!-- startcontent -->
* <a:label>Options</a:label>
* <a:label>Choices</a:label>
* <a:radiobutton group="g2">Option 1</a:radiobutton>
* <a:radiobutton group="g3">Choice 1</a:radiobutton>
* <a:radiobutton group="g2">Option 2</a:radiobutton>
* <a:radiobutton group="g3">Choice 2</a:radiobutton>
* <!-- endcontent -->
* </a:table>
* </a:application>
* ```
*
* @class apf.radiobutton
* @define radiobutton
* @allowchild {smartbinding}
*
* @form
* @inherits apf.Presentation
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*
* #### Example
*
* Sets the selection based on data loaded into this component.
*
* ```xml
* <a:radiobutton group="g2" bindings="bndExample" value="1">Choice 1</a:radiobutton>
* <a:radiobutton group="g2" value="2">Choice 2</a:radiobutton>
*
* <a:bindings id="bndExample">
* <a:value match="[@value]" />
* </a:bindings>
* ```
*
* A shorter way to write this is:
*
* ```xml
* <a:radiobutton group="g2" value="[@value]" value="1">Choice 1</a:radiobutton>
* <a:radiobutton group="g2" value="2">Choice 2</a:radiobutton>
* ```
*
*/
/**
* @event click Fires when the user presses a mousebutton while over this element and then lets the mousebutton go.
* @see apf.AmlNode@afterchange
*/
apf.radiobutton = function(struct, tagName) {
this.$init(tagName || "radiobutton", apf.NODE_VISIBLE, struct);
/*this.$constructor = apf.radiobutton;
var fEl = apf.aml.setElement("radiobutton", function(){
this.$init(tagName || "radiobutton", apf.NODE_VISIBLE, struct);
});
fEl.prototype = apf.radiobutton.prototype;
apf.radiobutton = fEl;*/
};
(function(){
this.implement(apf.ChildValue);
this.$childProperty = "label";
this.$focussable = apf.KEYBOARD; // This object can get the focus
//1 = force no bind rule, 2 = force bind rule
/*this.$attrExcludePropBind = apf.extend({
selected: 1
}, this.$attrExcludePropBind);*/
// *** Properties and Attributes *** //
this.$booleanProperties["selected"] = true;
this.$supportedProperties.push("value", "background", "group",
"label", "selected", "tooltip", "icon");
/**
* @attribute {String} group Sets or gets the name of the group to which this radio
* button belongs. Only one item in the group can be selected at the same
* time.
* When no group is specified the parent container functions as the
* group; only one radiobutton within that parent can be selected.
*/
this.$propHandlers["group"] = function(value) {
if (!this.$ext)
return;
if (this.$group && this.$group.$removeRadio)
this.$group.$removeRadio(this);
if (!value) {
this.$group = null;
return;
}
var group = typeof value == "string"
?
apf.nameserver.get("group", value)
: value;
if (!group) {
group = apf.nameserver.register("group", value,
new apf.$group());
group.setAttribute("id", value);
group.dispatchEvent("DOMNodeInsertedIntoDocument");
group.parentNode = this;
}
this.$group = group;
if (this.oInput)
this.oInput.setAttribute("name", value);
this.$group.$addRadio(this);
};
/**
* @attribute {String} tooltip Sets or gets the tooltip of this radio button.
*/
this.$propHandlers["tooltip"] = function(value) {
this.$ext.setAttribute("title", value);
};
/**
* @attribute {String} icon Sets or gets the icon for this radiobutton
*/
this.$propHandlers["icon"] = function(value) {
if (!this.oIcon) return;
if (value)
this.$setStyleClass(this.$ext, this.$baseCSSname + "Icon");
else
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Icon"]);
apf.skins.setIcon(this.oIcon, value, this.iconPath);
};
/**
* @attribute {String} label Sets or gets the label for this radiobutton
*/
this.$propHandlers["label"] = function(value) {
if (value)
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Empty"]);
else
this.$setStyleClass(this.$ext, this.$baseCSSname + "Empty");
if (this.oLabel)
this.oLabel.innerHTML = value;
};
/**
* @attribute {Boolean} selected Sets or gets whether this radiobutton is the selected one in the group it belongs to.
*/
this.$propHandlers["selected"] = function(value) {
if (!this.$group)
return;
if (value)
this.$group.setProperty("value", this.value);
//else if (this.$group.value == this.value)
//this.$group.setProperty("value", "");
};
this.addEventListener("prop.model", function(e) {
if (this.$group)
this.$group.setProperty("model", e.value);
});
/**
* @attribute {String} background Sets a multistate background. The arguments
* are seperated by pipes (`'|'`) and are in the order of: `'imagefilename|mapdirection|nrofstates|imagesize'`
*
* {:multiStateDoc}
*
*
* #### Example
*
* Here's a three state picture where each state is 16px high, vertically spaced:
*
* ```xml
* background="threestates.gif|vertical|3|16"
* ```
* @see apf.BaseButton
*/
this.$propHandlers["background"] = function(value) {
var oNode = this.$getLayoutNode("main", "background", this.$ext);
if (value) {
var b = value.split("|");
this.$background = b.concat(["vertical", 2, 16].slice(b.length - 1));
oNode.style.backgroundImage = "url(" + this.mediaPath + b[0] + ")";
oNode.style.backgroundRepeat = "no-repeat";
}
else {
oNode.style.backgroundImage = "";
oNode.style.backgroundRepeat = "";
this.$background = null;
}
};
// *** Public methods *** //
/**
* Sets the value of this element. This should be one of the values
* specified in the `values` attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current value
*/
this.getValue = function(){
return this.value;
};
this.select = function(){
this.setProperty("selected", true, false, true);
};
/*this.uncheck = function(){
this.setProperty("selected", false, false, true);
}*/
this.getGroup = function(){
return this.$group;
};
/*
* Sets the selected state and related value
*/
this.$check = function(visually) {
this.$setStyleClass(this.$ext, this.$baseCSSname + "Selected");
this.selected = true;
if (this.oInput)
this.oInput.selected = true;
this.doBgSwitch(2);
};
this.$uncheck = function(){
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Selected"]);
this.selected = false;
if (this.oInput)
this.oInput.selected = false;
this.doBgSwitch(1);
};
// *** Private methods *** //
this.$enable = function(){
if (this.oInput)
this.oInput.disabled = false;
var _self = this;
this.$ext.onclick = function(e) {
if (!e) e = event;
if ((e.srcElement || e.target) == this)
return;
_self.dispatchEvent("click", {
htmlEvent: e
});
_self.$group.change(_self.value);
}
this.$ext.onmousedown = function(e) {
if (!e) e = event;
if ((e.srcElement || e.target) == this)
return;
apf.setStyleClass(this, _self.$baseCSSname + "Down");
}
this.$ext.onmouseover = function(e) {
if (!e) e = event;
if ((e.srcElement || e.target) == this)
return;
apf.setStyleClass(this, _self.$baseCSSname + "Over");
}
this.$ext.onmouseout =
this.$ext.onmouseup = function(){
apf.setStyleClass(this, "", [_self.$baseCSSname + "Down", _self.$baseCSSname + "Over"]);
}
};
this.$disable = function(){
if (this.oInput)
this.oInput.disabled = true;
this.$ext.onclick =
this.$ext.onmousedown =
this.$ext.onmouseover =
this.$ext.onmouseout =
this.$ext.onmouseup = null;
};
/**
* @private
*/
this.doBgSwitch = function(nr) {
if (this.bgswitch && (this.bgoptions[1] >= nr || nr == 4)) {
if (nr == 4)
nr = this.bgoptions[1] + 1;
var strBG = this.bgoptions[0] == "vertical"
? "0 -" + (parseInt(this.bgoptions[2]) * (nr - 1)) + "px"
: "-" + (parseInt(this.bgoptions[2]) * (nr - 1)) + "px 0";
this.$getLayoutNode("main", "background", this.$ext)
.style.backgroundPosition = strBG;
}
};
this.$focus = function(){
if (!this.$ext)
return;
if (this.oInput && this.oInput.disabled)
return false;
this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
};
this.$blur = function(){
if (!this.$ext)
return;
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
};
// *** Keyboard support *** //
this.addEventListener("keydown", function(e) {
var key = e.keyCode;
if (key == 13 || key == 32) {
//this.check();
//this.$group.current = this;
this.$group.change(this.value);
return false;
}
//Up
else if (key == 38) {
var node = this;
while (node && node.previousSibling) {
node = node.previousSibling;
if (node.localName == "radiobutton" && !node.disabled
&& node.$group == this.$group) {
node.check();
node.focus();
return;
}
}
}
//Down
else if (key == 40) {
var node = this;
while (node && node.nextSibling) {
node = node.nextSibling;
if (node.localName == "radiobutton" && !node.disabled
&& node.$group == this.$group) {
node.check();
node.focus();
return;
}
}
}
}, true);
// *** Init *** //
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.oInput = this.$getLayoutNode("main", "input", this.$ext);
this.oLabel = this.$getLayoutNode("main", "label", this.$ext);
this.oIcon = this.$getLayoutNode("main", "icon", this.$ext);
if (this.oLabel && this.oLabel.nodeType != 1)
this.oLabel = this.oLabel.parentNode;
//Set events
this.$enable();
};
this.$childProperty = "label";
this.$loadAml = function(x) {
if (this.group)
this.$propHandlers["group"].call(this, this.group);
else if (this.parentNode.localName == "group")
this.$propHandlers["group"].call(this, this.parentNode);
if (!this.$group) {
this.$propHandlers["group"].call(this,
"group" + this.parentNode.$uniqueId);
}
};
this.$destroy = function(){
if (this.$group)
this.$group.$removeRadio(this);
};
}).call(apf.radiobutton.prototype = new apf.Presentation());
apf.aml.setElement("radiobutton", apf.radiobutton);
/**
* An element that defines groups for radio buttons.
*
* #### Example
*
* This example shows radio buttons with an explicit group set:
*
* ```xml
* <a:label>Options</a:label>
* <a:radiobutton group="g1">Option 1</a:radiobutton>
* <a:radiobutton group="g1">Option 2</a:radiobutton>
*
* <a:label>Choices</a:label>
* <a:group id="g2" value="[mdlForm::choice]">
* <a:radiobutton value="c1">Choice 1</a:radiobutton>
* <a:radiobutton value="c2">Choice 2</a:radiobutton>
* </a:group>
* ```
*
* @class apf.group
* @define group
*/
apf.$group = apf.group = function(struct, tagName) {
this.$init(tagName || "group", apf.NODE_VISIBLE, struct);
this.implement(
apf.StandardBinding,
apf.DataAction
);
var radiobuttons = [];
this.$supportedProperties.push("value", "selectedItem");
this.$propHandlers["value"] = function(value) {
for (var i = 0; i < radiobuttons.length; i++) {
if (radiobuttons[i].value == value) {
return this.setProperty("selectedItem", radiobuttons[i]);
}
}
return this.setProperty("selectedItem", null);
};
var lastSelected;
this.$propHandlers["selectedItem"] = function(rb) {
if (lastSelected)
lastSelected.$uncheck();
if (!rb)
return;
rb.$check();
lastSelected = rb;
for (var i = 0; i < radiobuttons.length; i++)
radiobuttons[i].setProperty("selectedItem", rb);
};
this.$addRadio = function(rb) {
var id = radiobuttons.push(rb) - 1;
if (!rb.value)
rb.setProperty("value", id);
var _self = this;
rb.addEventListener("prop.value", function(e) {
if (this.selected)
_self.setProperty("value", e.value);
else if (_self.value == e.value)
this.select();
});
if (this.value && rb.value == this.value)
this.setProperty("selectedItem", rb);
else if (rb.selected)
this.setProperty("value", rb.value);
};
this.$removeRadio = function(rb) {
radiobuttons.remove(rb);
if (rb.value === rb.id)
rb.setProperty("value", "");
if (rb.selectedItem == rb)
this.setProperty("value", null);
};
/**
* Sets the current value of this element.
*/
this.setValue = function(value) {
this.setProperty("value", value);
};
/**
* Returns the current value of this element.
* @return {String} The current value.
*/
this.getValue = function(){
return this.value;
};
this.$draw = function(){
this.$ext = this.$int = this.$pHtmlNode;
};
};
apf.$group.prototype = new apf.GuiElement();
apf.aml.setElement("group", apf.$group);
/**
* This element loads JavaScript into the application.
*
* #### Example
*
* ```xml
* <a:script src="code.js" />
* ```
*
* #### Example
*
* ```xml
* <a:script>//<!--
* for (var i = 0; i < 2; i++) {
* alert(i);
* }
* //--></a:script>
* ```
*
* @class apf.script
* @inherits apf.AmlElement
* @define script
* @logic
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
*/
/**
* @attribute {String} src the location of the script file.
*
*
*/
apf.script = function(){
this.$init("script", apf.NODE_HIDDEN);
};
(function(){
this.$propHandlers["src"] = function(value) {
if (!this.type)
this.type = this.getAttribute("type");
if (!this.type || this.type == "text/javascript") {
if (apf.isOpera) {
$setTimeout(function(){
apf.window.loadCodeFile(apf.hostPath
+ value);
}, 1000);
}
else {
apf.window.loadCodeFile(apf.getAbsolutePath(apf.hostPath,
value));
}
}
else {
var _self = this;
apf.ajax(value, {callback: function(data, state, extra) {
if (state != apf.SUCCESS) {
return apf.console.warn("Could not load script " + value);
}
_self.$execute(data);
}});
}
}
this.$execute = function(code, e) {
if (!this.type || this.type == "text/javascript") {
apf.jsexec(code);
}
else if (this.type.indexOf("livemarkup") > -1
|| this.type.indexOf("lm") > -1) { //@todo this is wrong, it should start in code mode
var func = apf.lm.compile(code, {event: true, parsecode: true, funcglobal: true, nostring: true});
func(e || {});
}
}
this.addEventListener("DOMNodeInserted", function(e) {
if (e.currentTarget.nodeType == 3 || e.currentTarget.nodeType == 4) {
this.$execute(e.currentTarget.nodeValue, apf.isIE && window.event);
}
});
//@todo this should use text node insertion
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var nodes = this.childNodes, s = [];
for (var i = 0, l = nodes.length; i < l; i++) {
s[s.length] = nodes[i].nodeValue;
}
var code = s.join("\n");
this.$execute(code);
});
}).call(apf.script.prototype = new apf.AmlElement());
apf.aml.setElement("script", apf.script);
//@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
* <a:skin src="perspex.xml"
* name = "perspex"
* media-path = "http://example.com/images"
* icon-path = "http://icons.example.com" />
* ```
*
* @class apf.skin
* @inherits apf.AmlElement
* @define skin
* @layout
* @allowchild style, presentation
*
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
/**
* @attribute {String} name Sets or gets the name of the skinset.
*/
/**
* @attribute {String} src Sets or gets the location of the skin definition.
*/
/**
* @attribute {String} media-path Sets or gets the basepath for the images of the skin.
*/
/**
* @attribute {String} icon-path Sets or gets the basepath for the icons used in the elements using this skinset.
*/
apf.skin = function(struct, tagName) {
this.$init(tagName || "skin", apf.NODE_HIDDEN, struct);
};
apf.aml.setElement("skin", apf.skin);
(function(){
this.$parsePrio = "002";
this.$includesRemaining = 0;
this.$propHandlers["src"] = function(value) {
if (value.trim().charAt(0) == "<") {
apf.skins.Init(apf.getXml(value), this, this.$path);
return;
}
this.$path = apf.getAbsolutePath(apf.hostPath, value)
getSkin.call(this, this.$path);
}
this.$propHandlers["name"] = function(value) {
if (!this.src && !this.attributes.getNamedItem("src")) {
this.$path = apf.getAbsolutePath(apf.hostPath, value) + "/index.xml";
getSkin.call(this, this.$path);
}
};
/**
* @private
*/
function checkForAmlNamespace(xmlNode) {
if (!xmlNode.ownerDocument.documentElement)
return false;
var nodes = xmlNode.ownerDocument.documentElement.attributes;
for (var found = false, i=0; i<nodes.length; i++) {
if (nodes[i].nodeValue == apf.ns.aml) {
found = true;
break;
}
}
return found;
}
function getSkin(path) {
var domParser = this.ownerDocument.$domParser;
if (!apf.skins.$first)
apf.skins.$first = this;
var defer = this.attributes.getNamedItem("defer");
if (!defer || !apf.isTrue(defer.nodeValue)) {
domParser.$pauseParsing.apply(domParser,
this.$parseContext = domParser.$parseContext || [this.ownerDocument.documentElement]);
}
//var basePath = apf.hostPath;//only for recursion: apf.getDirname(xmlNode.getAttribute("filename")) ||
loadSkinFile.call(this, path);
}
function finish(xmlNode) {
if (xmlNode)
apf.skins.Init(xmlNode, this, this.$path);
if (!this.defer) {// && this.$parseContext
var domParser = this.ownerDocument.$domParser;
domParser.$continueParsing(this.$parseContext[0]);
}
}
function loadSkinFile(path) {
var _self = this;
apf.getData(
path, {
callback: function(xmlString, state, extra) {
if (state != apf.SUCCESS) {
var oError = new Error(apf.formatErrorString(1007,
_self, "Loading skin file", "Could not load skin file '"
+ (path || _self.src)
+ "'\nReason: " + extra.message));
if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
return true;
throw oError;
}
//if (!apf.supportNamespaces)
xmlString = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "")
.replace(/^[\r\n\s]*/, "") //.replace(/&nbsp;/g, " ")
.replace(/xmlns\=\"[^"]*\"/g, "");
if (!xmlString) {
throw new Error(apf.formatErrorString(0, _self,
"Loading skin",
"Empty skin file. Maybe the file does not exist?", _self));
}
var xmlNode = apf.getXml(xmlString);//apf.getAmlDocFromString(xmlString);
if (!xmlNode) {
throw new Error(apf.formatErrorString(0, _self,
"Loading skin",
"Could not parse skin. Maybe the file does not exist?", _self));
}
xmlNode.setAttribute("filename", extra.url);
{
finish.call(_self, xmlNode);
}
},
async: true,
ignoreOffline: true
});
}
//@todo use mutation events to update
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
if (this.src || this.name)
return;
apf.skins.Init(this.$aml || this);
//@todo implied skin
/*if (this.parentNode && this.parentNode.parentNode) {
var name = "skin" + Math.round(Math.random() * 100000);
q.parentNode.setAttribute("skin", name);
apf.skins.skins[name] = {name: name, templates: {}};
apf.skins.skins[name].templates[q.parentNode[apf.TAGNAME]] = q;
}*/
});
}).call(apf.skin.prototype = new apf.AmlElement());
/**
* An element containing information on how databound elements process data.
*
* The {@link term.smartbinding smartbinding} element specifies how data is transformed and rendered
* in databound elements. It also specifies how changes on the bound data are
* send to their original data source ({@link apf.actions actions}) and
* which {@link term.datanode data nodes} can be dragged and dropped ({@link apf.DragDrop dragdrop}).
*
* #### Remarks
*
* Each element has its own set of binding rules it uses to render the data
* elements. The same goes for it's actions. To give an example, a slider has
* one action called 'change'. This action is called when then value of the
* slider changes. A tree element has several actions - among others: 'add',
* 'remove', 'move', 'copy' and 'rename'.
*
* Smartbindings enable many other features in a Ajax.org Platform
* application. Actions done by the user can be undone by calling
* {@link apf.actiontracker.undo} of the element. The
* Remote Databinding element can send changes on data to other clients.
*
* This element is created especially for reuse. Multiple elements can reference
* a single smartbinding element by setting the value of the 'smartbinding'
* attribute to the ID of this smartbinding element. If an element is only used
* for a single other element it can be set as it's child. In fact, each of the
* children of the smartbinding element can exist outside the smartbinding
* element and referenced indepently.
*
* #### Example
*
* A simple example of a smartbinding transforming data into representation
*
* ```xml
* <a:smartbinding id="sbProducts">
* <a:bindings>
* <a:caption match="[text()]" />
* <a:value match="[text()]" />
* <a:each match="[product]" />
* </a:bindings>
* </a:smartbinding>
*
* <a:list smartbinding="sbProducts" width="200">
* <a:model>
* <data>
* <product>LCD Panel</product>
* </data>
* </a:model>
* </a:list>
* ```
*
* #### Example
*
* This is an elaborate example showing how to create a filesystem tree with
* files and folders in a tree. The smartbinding element describes how the
* files and folders are transformed to tree elements and how actions within
* the tree are sent to the data source; in this case, WebDAV.
* is used. The drag and drop rules specify which elements can be dragged and
* where they can be dropped.
*
* ```xml
* <a:smartbinding id="sbFilesystem" model="{myWebdav.getRoot()}">
* <a:bindings>
* <a:insert match="[folder]" get="{myWebdav.readdir([@path])}" />
* <a:each match="[file|folder]" sort="[@name]" sort-method="filesort" />
* <a:caption match="[@name]" />
* <a:icon match="[folder]" value="icoFolder.png" />
* <a:icon match="[file]" method="getIcon" />
* <a:drag match="[folder|file]" copy="event.ctrlKey" />
* <a:drop
* match = "[folder|file]"
* target = "[folder]"
* action = "tree-append" />
* </a:bindings>
* <a:add type="folder" get="{myWebdav.mkdir([@id], 'New Folder')}" />
* <a:add type="file" get="{myWebdav.create([@id], 'New File', '')}" />
* <a:rename set="{myWebdav.move(oldValue, [@name], [@id])}"/>
* <a:copy match="[.]" set="{myWebdav.copy([@id], [../@id])}"/>
* <a:move match="[.]" set="{myWebdav.move()}"/>
* <a:remove match="[.]" set="{myWebdav.remove([@path])}"/>
* </a:smartbinding>
*
* <a:tree
* dragcopy = "true"
* model = "mdlFilesystem"
* smartbinding = "sbFilesystem" />
*
* <a:script>
* function filesort(value, args, xmlNode) {
* return (xmlNode.tagName == "folder" ? 0 : 1) + value;
* }
*
* function getIcon(xmlNode) {
* xmlNode.getAttribute('name').match(/\.([^\.]*)$/);
*
* var ext = RegExp.$1;
* return (SupportedIcons[ext.toUpperCase()]
* ? SupportedIcons[ext.toUpperCase()] + ".png"
* : "unknown.png");
* }
* </a:script>
* ```
*
* #### Example
*
* This example shows a smartbinding element which references to its children as
* stand alone elements.
*
* ```xml
* <a:bindings id="bndExample">
* ...
* </a:bindings>
* <a:actions id="actExample">
* ...
* </a:actions>
* <a:dragdrop id="ddExample">
* ...
* </a:dragdrop>
* <a:model id="mdlExample" />
*
* <a:smartbinding id="sbExample"
* actions = "actExample"
* bindings = "bndExample"
* dragdrop = "ddExample"
* model = "mdlExample" />
*
* <a:list smartbinding="sbExample" />
* <a:tree binding="bndExample" action="actExample" model="example.php" />
* ```
*
*
* #### Example
*
* The shortest method to add binding rules to an element is as follows:
*
* ```xml
* <a:tree each="[file|folder]" caption="[@name]" icon="[@icon]" />
* ```
*
* @see baseclass.databinding
* @see baseclass.databinding.attribute.smartbinding
* @see term.smartbinding
* @see term.binding
* @see term.action
*
* @define smartbinding
* @allowchild bindings, actions, ref, action, dragdrop, model
*
*
* @class apf.smartbinding
* @apfclass
* @inherits apf.AmlElement
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*
* @default_private
*/
apf.smartbinding = function(struct, tagName) {
this.$init(tagName || "smartbinding", apf.NODE_HIDDEN, struct);
this.$bindNodes = {};
};
(function(){
this.$supportedProperties = ["bindings", "actions", "model"];
this.$handlePropSet = function(prop, value, force) {
switch(prop) {
//@todo apf3 change this to use apf.setModel();
case "model":
if (typeof value == "string")
value = apf.nameserver.get("model", value);
this.model = apf.nameserver.register("model", this.name, value);
//this.modelBaseXpath = xpath;
var amlNode;
for (var uniqueId in this.$bindNodes) {
amlNode = this.$bindNodes[uniqueId];
this.model.unregister(amlNode);
this.model.register(amlNode, this.$modelXpath[amlNode.getHost
? amlNode.getHost().$uniqueId
//this is a hack.. by making Models with links to other
//models possible, this should not be needed
: amlNode.$uniqueId] || this.modelBaseXpath);
//this.$bindNodes[uniqueId].load(this.model);
}
break;
case "bindings":
if (this.$bindings)
this.remove(this.$bindings);
this.$bindings = typeof value == "object"
? value
:
apf.nameserver.lookup("bindings", value);
this.add(this.$bindings);
break;
case "actions":
if (this.$actions)
this.remove(this.$actions);
this.$actions = typeof value == "object"
? value
:
apf.nameserver.lookup("actions", value);
this.add(this.$actions);
break;
}
this[prop] = value;
};
this.add = function(node) {
for (var uId in this.$bindNodes)
node.register(this.$bindNodes[uId]);
this["$" + node.localName] = node;
};
this.remove = function(node) {
for (var uId in this.$bindNodes)
node.unregister(this.$bindNodes[uId]);
};
this.register = function(amlNode) {
this.$bindNodes[amlNode.$uniqueId] = amlNode;
if (this.$bindings)
this.$bindings.register(amlNode);
if (this.$actions)
this.$actions.register(amlNode);
if (this.$model)
this.$model.register(amlNode);
};
this.unregister = function(amlNode) {
//unregister element
this.$bindNodes[amlNode.$uniqueId] = null;
delete this.$bindNodes[amlNode.$uniqueId];
if (this.$bindings)
this.$bindings.unregister(amlNode);
if (this.$actions)
this.$actions.unregister(amlNode);
if (this.$model)
this.$model.unregister(amlNode);
};
/**
* Loads XML data in the model of this smartbinding element.
*
* @param {Mixed} xmlNode the {@link term.datanode data node} loaded into
* the model of this smartbinding element. This can be an XMLElement, a
* string or null.
* @private
*/
this.load = function(xmlNode) {
//@todo fix this
new apf.model().register(this).load(xmlNode);
};
this.clear = function(state) {
//for all elements do clear(state);
};
/*
* @private
*
* @attribute {String} bindings the id of the bindings element that contains
* the {@link term.binding binding rules} for all elements connected to
* this smartbinding element
*
* #### Example
*
* ```xml
* <a:smartbinding id="sbExample" bindings="bndExample" />
* ```
* @see element.bindings
* @see term.binding
* @see term.smartbinding
*
* @attribute {String} actions the id of the actions element that provides
* the {@link term.action action rules} for all elements connected to
* this smartbinding element
*
* #### Example
*
* ```xml
* <a:smartbinding id="sbExample" actions="actExample" />
* ```
* @see apf.actions
* @see term.action
* @see term.smartbinding
*
* @attribute {String} dragdrop the id of the dragdrop element that provides
* the drag and drop rules for all elements connected to this smartbinding
* element
*
* #### Example
*
* ```xml
* <a:smartbinding id="sbExample" bindings="bndExample" />
* ```
* @see apf.DragDrop
* @see term.smartbinding
*
* @attribute {String} model the id of the model element that provides
* the data for all elements connected to this smartbinding element.
*
* #### Example
*
* ```xml
* <a:smartbinding id="sbExample" model="mdlExample" />
* ```
* @see element.model
* @see term.smartbinding
*
* @define bindings element containing all the binding rules for the data
* bound elements referencing this element.
*
* #### Example
*
* ```xml
* <a:bindings id="bndFolders" >
* <a:caption match="[@name]" />
* <a:icon match="[@icon]" />
* <a:each match="[folder]" sort="[@name]" />
* </a:bindings>
*
* <a:tree bindings="bndFolders" />
* ```
* @see apf.smartbinding
* @allowchild {bindings}
*
*
*/
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
if (this.parentNode.hasFeature(apf.__DATABINDING__))
this.register(this.parentNode);
});
}).call(apf.smartbinding.prototype = new apf.AmlElement());
apf.aml.setElement("smartbinding", apf.smartbinding);
/**
* 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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:spinner value="6" min="-6" max="12" width="200"></a:spinner>
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Example: Loading Data
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:model id="mdlSpinner">
* <data value="56"></data>
* </a:model>
* <a:spinner value="[@value]" model="mdlSpinner" />
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Example: Connecting to a Textbox
*
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:model id="mdlTest">
* <overview page="1" pages="10" />
* </a:model>
* <a:spinner
* id = "spinner"
* min = "0"
* max = "[@pages]"
* model = "mdlTest"
* value = "[@page]"
* caption = "[@page] of [@pages]">
* </a:spinner>
* <a:textbox value="{spinner.caption}"></a:textbox>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.spinner
* @define spinner
* @form
* @version %I%, %G%
*
* @inherits apf.StandardBinding
* @inherits apf.DataAction
* @inheritsElsewhere apf.XForms
*
*/
/**
* @attribute {Number} [max=64000] Sets or gets the maximum allowed value
*/
/**
* @attribute {Number} [min=-64000] Sets or gets the minimal allowed value
*/
/**
* @attribute {Number} value Sets or gets the actual value displayed in component
*
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*/
apf.spinner = function(struct, tagName) {
this.$init(tagName || "spinner", apf.NODE_VISIBLE, struct);
this.max = 64000;
this.min = -64000;
this.focused = false;
this.value = 0;
this.realtime = false;
};
(function() {
this.implement(
apf.DataAction
);
this.$supportedProperties.push("width", "value", "max", "min", "caption", "realtime");
this.$booleanProperties["realtime"] = true;
this.$propHandlers["value"] = function(value) {
value = parseInt(value) || 0;
this.value = this.oInput.value = (value > this.max
? this.max
: (value < this.min
? this.min
: value));
};
this.$propHandlers["min"] = function(value) {
if (!(value = parseInt(value))) return;
this.min = value;
if (value > this.value)
this.change(value);
};
this.$propHandlers["max"] = function(value) {
if (!(value = parseInt(value))) return;
this.max = value;
if (value < this.value)
this.change(value);
};
/* ********************************************************************
PUBLIC METHODS
*********************************************************************/
/**
* Sets the value of this element. This should be one of the values
* specified in the `values` attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current element value
*/
this.getValue = function() {
return this.value;
};
/**
* Increments the spinner by one.
*/
this.increment = function() {
this.change(parseInt(this.oInput.value) + 1);
};
/**
* Decrements the spinner by one.
*/
this.decrement = function() {
this.change(parseInt(this.oInput.value) - 1);
};
this.$enable = function() {
this.oInput.disabled = false;
this.$setStyleClass(this.oInput, "", ["inputDisabled"]);
};
this.$disable = function() {
this.oInput.disabled = true;
this.$setStyleClass(this.oInput, "inputDisabled");
};
this.$focus = function(e) {
if (!this.$ext || this.focused) //this.disabled ||
return;
this.focused = true;
this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
if (this.oLeft)
this.$setStyleClass(this.oLeft, "leftFocus");
};
this.$blur = function(e) {
if (!this.$ext && !this.focused)
return;
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
if (this.oLeft)
this.$setStyleClass(this.oLeft, "" ["leftFocus"]);
this.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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:window
* visible = "true"
* width = "400"
* height = "150"
* title = "Simple Tab" >
* <!-- startcontent -->
* <a:splitbutton id="btnTestRun" caption = "Run tests"/>
* <!-- endcontent -->
* </a:window>
* </a:application>
* ```
*
* #### Remarks
*
* This component is used in the accordian element to create its sections. In
* the statusbar, the panel element is an alias of `bar`.
*
* @class apf.splitbutton
* @define splitbutton
* @container
* @allowchild button
* @allowchild {elements}, {anyaml}
*
* @inherits apf.GuiElement
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
/**
* @attribute {String} icon Sets or gets the url pointing to the icon image.
*/
/**
* @attribute {Boolean} [collapsed=false] Sets or gets collapse panel on load.
*/
/**
* @attribute {String} title Describes the content in a panel
*/
apf.splitbutton = function(struct, tagName) {
this.$init(tagName || "splitbutton", apf.NODE_VISIBLE, struct);
};
(function(){
this.$focussable = false;
this.$booleanProperties["disabled-split"] = 1;
//this.$supportedProperties.push("disabled-split", "button-skin");
this.$propHandlers["caption"] = function(value) {
this.$button1.setProperty("caption", value);
};
this.$propHandlers["icon"] = function(value) {
this.$button1.setProperty("icon", value);
};
this.$propHandlers["tooltip"] = function(value) {
this.$button1.setProperty("tooltip", value);
};
this.$propHandlers["hotkey"] = function(value) {
this.$button1.setProperty("hotkey", value);
};
this.$propHandlers["disabled"] = function(value) {
this.$button1.setProperty("disabled", value);
this.$button2.setProperty("disabled", value);
};
this.$propHandlers["disabled-split"]= function(value) {
this.$button2.setProperty("disabled", value);
};
this.$propHandlers["button-skin"] = function(value) {
this.$button1.setProperty("skin", value);
this.$button2.setProperty("skin", value);
};
this.$propHandlers["class"] = function(value) {
apf.setStyleClass(this.$ext, value, this.$lastClassValue ? [this.$lastClassValue] : null);
this.$lastClassValue = value;
};
this.$propHandlers["submenu"] = function(value) {
this.$button2.setProperty("submenu", value);
var _self = this;
this.$button2.addEventListener("mousedown", function() {
var menu = self[value] || value;
if (!menu.$splitInited) {
_self.dispatchEvent("submenu.init");
menu.addEventListener("display", function(){
var split = this.opener.parentNode;
var diff = apf.getAbsolutePosition(split.$button2.$ext)[0]
- apf.getAbsolutePosition(split.$button1.$ext)[0];
this.$ext.style.marginLeft = ~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
* <a:state-group
* loginMsg.visible = "false"
* winLogin.disabled = "false">
* <a:state id="stFail"
* loginMsg.value = "Username or password incorrect"
* loginMsg.visible = "true" />
* <a:state id="stError"
* loginMsg.value = "An error has occurred. Please check your network."
* loginMsg.visible = "true" />
* <a:state id="stLoggingIn"
* loginMsg.value = "Please wait while logging in..."
* loginMsg.visible = "true"
* winLogin.disabled = "true" />
* <a:state id="stIdle" />
* </a:state-group>
* ```
*
* @class apf.stateGroup
* @define state-group
* @logic
* @inherits apf.AmlElement
*
* @see apf.state
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
apf.stateGroup = function(){
this.$init("state-group", apf.NODE_HIDDEN);
};
apf.aml.setElement("state-group", apf.stateGroup);
(function(){
this.$handlePropSet = function(prop, value, force) {
if (prop == "id")
return;
var node, nodes = this.childNodes;
for (var i = 0, l = nodes.length; i < l; i++){
node = nodes[i];
if (node.nodeType != 1 || node.localName != "state")
continue;
if (!node[prop] || node.$inheritProperties[prop] == 2) {
node.$inheritProperties[prop] = 2;
node.setProperty(prop, value);
}
}
};
//@todo this should use text node insertion
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
if (!this.id)
this.id = "stategroup" + this.$uniqueId;
//apf.StateServer.addGroup(this.id, null, this.parentNode); //@todo rearch this
var nodes = this.childNodes;
for (var i = 0, l = nodes.length; i < l; i++){
nodes[i].setProperty("group", this.id);
}
});
}).call(apf.stateGroup.prototype = new apf.AmlElement());
/**
* @private
*/
apf.StateServer = {
states: {},
groups: {},
locs: {},
removeGroup: function(name, elState) {
this.groups[name].remove(elState);
if (!this.groups[name].length) {
if (self[name]) {
self[name].destroy();
self[name] = null;
}
delete this.groups[name];
}
},
addGroup: function(name, elState, pNode) {
if (!this.groups[name]) {
this.groups[name] = [];
var pState = new apf.state({
id: name
});
pState.parentNode = pNode;
//pState.implement(apf.AmlNode);
//pState.name = name;
pState.toggle = function(){
for (var next = 0, i = 0; i < apf.StateServer.groups[name].length; i++) {
if (apf.StateServer.groups[name][i].active) {
next = i + 1;
break;
}
}
apf.StateServer.groups[name][
(next == apf.StateServer.groups[name].length) ? 0 : next
].activate();
}
this.groups[name].pState = self[name] = pState;
}
if (elState)
this.groups[name].push(elState);
return this.groups[name].pState;
},
removeState: function(elState) {
delete this.states[elState.name];
},
addState: function(elState) {
this.states[elState.name] = elState;
}
}
/**
* This element specifies a certain state of (a part of) the application. By
* "state", we mean a collection of properties on objects that have a certain
* value at one time.
*
* This element allows you to specify which properties on
* which elements should be set when a state is activated. This element can
* belong to a state-group containing multiple elements with a default state.
*
* #### Example
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:state
* group = "stRole"
* id = "stUser"
* caption = "You are a user"
* active = "true" />
* <a:state
* group = "stRole"
* id = "stAdmin"
* caption = "You have super powers" />
* <a:label caption="{stRole.caption}" />
* <a:hbox height="34" width="200" margin="10 0 0 0">
* <a:button
* width = "100"
* onclick = "stUser.activate()">State - User</a:button>
* <a:button
* width = "100"
* onclick = "stAdmin.activate()">State - Admin</a:button>
* </a:hbox>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.state
* @define state
*
* @logic
* @inherits apf.AmlElement
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.9
*/
/**
* @event change Fires when the active property of this element changes.
*
*/
apf.state = function(struct, tagName) {
this.$init(tagName || "state", apf.NODE_HIDDEN, struct);
this.$signalElements = [];
this.$groupAdded = {};
this.$locationAdded = '';
};
(function(){
// *** Properties and Attributes *** //
this.$supportedProperties.push("active");
this.$booleanProperties["active"] = true;
/**
* @attribute {Boolean} active whether this state is the active state
*/
this.$propHandlers["active"] = function(value) {
//Activate State
if (apf.isTrue(value)) {
if (this.group) {
var nodes = apf.StateServer.groups[this.group];
if (!nodes) {
apf.StateServer.addGroup(this.group, this);
nodes = apf.StateServer.groups[this.group];
}
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] != this && nodes[i].active !== false)
nodes[i].deactivate();
}
}
var q = this.$signalElements;
for (var i = 0; i < q.length; i++) {
if (!self[q[i][0]] || !self[q[i][0]].setProperty) {
continue;
}
self[q[i][0]].setProperty(q[i][1], this[q[i].join(".")]);
}
if (this.group) {
var attr = this.attributes;
for (var i = 0; i < attr.length; i++) {
if (attr[i].nodeName.match(/^on|^(?:group|id)$|^.*\..*$/))
continue;
self[this.group].setProperty(attr[i].nodeName,
attr[i].nodeValue);
}
apf.StateServer.groups[this.group].pState.dispatchEvent("change");
}
this.dispatchEvent("activate");
}
//Deactivate State
else {
this.setProperty("active", false);
this.dispatchEvent("deactivate");
}
};
// *** Public methods *** //
/**
* Sets the value of this element. This should be one of the values
* specified in the values attribute.
* @param {String} value Sets the new value of this element
*/
this.setValue = function(value) {
this.active = 9999;
this.setProperty("active", value, false, true);
};
/**
* Actives this state, setting all the properties on the elements that
* were specified.
*/
this.activate = function(){
this.active = 9999;
this.setProperty("active", true, false, true);
};
/**
* Deactivates the state of this element. This is mostly a way to let all
* elements that have property bound to this state know it is no longer
* active.
*/
this.deactivate = function(){
this.setProperty("active", false, false, true);
};
// *** Init *** //
this.$propHandlers["group"] = function(value) {
if (value) {
apf.StateServer.addGroup(value, this);
this.$groupAdded = {'value' : value, elState : this};
}
else {
apf.StateServer.removeGroup(this.$groupAdded.value, this.$groupAdded.elState);
this.$groupAdded = {};
}
}
this.$propHandlers["location"] = function(value) {
if (value) {
apf.StateServer.locs[value] = this;
this.$locationAdded = value;
}
else {
delete apf.StateServer.locs[this.$locationAdded];
this.$locationAdded = '';
}
}
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
apf.StateServer.addState(this);
//Properties initialization
var attr = this.attributes;
for (var s, i = 0; i < attr.length; i++) {
s = attr[i].nodeName.split(".");
if (s.length == 2)
this.$signalElements.push(s);
}
});
this.addEventListener("DOMNodeRemovedFromDocument", function(){
this.$signalElements = null;
apf.StateServer.removeState(this);
if (this.group)
apf.StateServer.removeGroup(this.group, this);
});
}).call(apf.state.prototype = new apf.AmlElement());
apf.aml.setElement("state", apf.state);
/**
* Any child element of this element is placed in a table. The size of the
* columns and rows of the table can be set by attributes. Child elements can
* span multiple columns. Using `'*' `as a size indicator will use the remaining
* size for that column or row, when the table's size is set.
*
* #### Example
*
* This example shows a window with a table and two buttons that change the
* orientation of the table runtime. The textarea and its label have a span set
* to `'*'`. This means they will span the entire width of all columns, no matter
* how many columns there are.
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:window visible="true" width="500" height="260" title="Form">
* <!-- startcontent -->
* <a:table id="tableTest"
* columns = "80, *"
* edge = "10 10 10 10"
* padding = "5"
* bottom = "35"
* top = "0">
* <a:label>Name</a:label>
* <a:textbox />
* <a:label>Address</a:label>
* <a:textarea height="50" />
* <a:label>Country</a:label>
* <a:dropdown>
* <a:item>America</a:item>
* <a:item>Armenia</a:item>
* <a:item>The Netherlands</a:item>
* </a:dropdown>
*
* <a:label span="*">Message</a:label>
* <a:textarea id="txtMessage"
* height = "*"
* span = "*" />
* </a:table>
*
* <a:button
* caption = "Two Columns"
* bottom = "10"
* left = "10"
* onclick = "tableTest.setAttribute('columns', '80, *');"/>
*
* <a:button
* bottom = "10"
* left = "125"
* caption = "Four Columns"
* onclick = "tableTest.setAttribute('columns', '60, 120, 60, *');"/>
* <!-- endcontent -->
* </a:window>
* </a:application>
* ```
*
* @class apf.table
* @define table
* @allowchild {elements}, {anyaml}
*
* @container
* @inherits apf.GuiElement
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 1.0
*/
apf.table = function(struct, tagName) {
this.$init(tagName || "table", apf.NODE_VISIBLE, struct);
};
(function(){
// *** Properties and Attributes *** //
this.$focussable = false;
this.$useLateDom = true;
this.$layout = true;
this.columns = null;//"150,200";
this.padding = 2;
this.$edge = [5, 5, 5, 5];
this.cellheight = 19;
/**
* @attribute {Number | String} columns Sets or gets a comma seperated list of column sizes.
* A column size can be specified in a number (size in pixels), or using a number and a `"%"` sign to indicate a percentage.
* A `'*'` indicates the column spans the rest space. There can be only one `'*'` in the column string.
*
* #### Example
*
* ```xml
* <a:table columns="150, *, 20%" />
* ```
*
*/
/**
* @attribute {String} [padding=2] Sets or gets the space between each element.
*/
/**
* @attribute {String} [edge="5 5 5 5"] Sets or gets the space between the container and the elements, space seperated in pixels for each side. Similar to CSS in the sequence of `top right bottom left`.
*
* #### Example
*
* ```xml
* <a:table edge="10 10 40 10" />
* ````
*/
this.$supportedProperties.push("columns", "padding", "edge",
"cellheight", "span");
this.$propHandlers["columns"] = function(value) {
if (!value.match(/^((?:\d+\%?|\*)\s*(?:,\s*|\s*$))+$/)) {
return;
}
var col, colsize = this.$columns = value.splitSafe(",");
var total = 0, cols = this.$table.getElementsByTagName("col");
if (cols.length) {
for (var sz, i = 0, l = Math.min(cols.length, colsize.length); i < l; i++) {
cols[i].style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px";
total += parseInt(sz);
}
}
var start = cols.length - colsize.length;
if (start > 0) {
for (var i = cols.length - start; i < cols.length; i++) {
cols[i].parentNode.removeChild(cols[i]);
}
}
else if (start < 0) {
for (var i = colsize.length + start; i < colsize.length; i++) {
col = this.$table.appendChild(document.createElement("col"));
col.style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px";
col.setAttribute("valign", "top");
total += parseInt(sz);
}
}
this.$table.style.width = String(value).indexOf("%") > -1
? "auto"
: (total + ((colsize.length - 1) * this.padding)
+ this.$edge[0] + this.$edge[2]) + "px";
var cells = this.$tbody.firstChild.getElementsByTagName("td");
for (var i = cells.length - 1; i >= 0; i--)
cells[i].parentNode.removeChild(cells[i]);
for (var sz, c, i = 0; i < colsize.length; i++) {
c = this.$tbody.firstChild.appendChild(document.createElement("td"));
c.style.width = (sz = colsize[i]).indexOf("%") > -1 ? sz : sz + "px";
/*if (colsize[i].indexOf("%") > -1)
c.appendChild(document.createElement("div")).style.width = "50px";*/
}
if (start && this.$amlLoaded)
visibleHandler({sync: true, parentNode: this});
this.$resize();
}
this.$propHandlers["padding"] = function(value) {
if (!this.$columns) return;
var cells = this.$table.getElementsByTagName("td");
var lastCol, lastRow, cell, lRow = this.$tbody.lastChild;
for (var i = this.$columns.length, l = cells.length; i < l; i++) {
lastCol = (cell = cells[i]).parentNode.lastChild == cell;
lastRow = cell.parentNode == lRow;
cell.style.padding = "0px " + (lastCol ? 0 : value) + "px " + (lastRow ? 0 : value) + "px 0px";
}
this.$resize();
}
this.$propHandlers["edge"] = function(value) {
this.$table.style.padding = (this.$edge = apf.getBox(value)).join("px ") + "px";
this.$resize();
}
function visibleHandler(e) {
var table = e.parentNode || this.parentNode;
if (e.sync || e.value && !this.$altExt || !e.value && this.$altExt) {
var nodes = table.childNodes;
var cells = apf.getArrayFromNodelist(table.$tbody.getElementsByTagName("td"));
var rows = table.$tbody.getElementsByTagName("tr");
var empty = [], row = 1, cs, rs, collen = table.$columns.length;
var z = table.$columns.length, lastCol;
for (var node, td, last, l = nodes.length, i = 0; i < l; i++) {
if ((node = nodes[i]).visible === false)
continue;
td = node.$altExt = last = cells[z++];
if (!td) break;
//td = node.$altExt = last = document.createElement("td");
if (!rows[row])
table.$tbody.appendChild(document.createElement("tr"));
rows[row].appendChild(td);
td.appendChild(node.$ext);
td.setAttribute("colspan", cs = Math.min(collen - (empty[0] || 0), parseInt(node.colspan || node.span || 1)));
td.setAttribute("rowspan", rs = parseInt(node.rowspan || 1));
//@todo this is wrong it should be cs * rs
if (!empty[0])
empty[0] = 0;
empty[0] += cs;
if (rs > 1) {
for (var k = 1; k < rs; k++) {
if (!empty[k])
empty[k] = 0;
empty[k] += cs;
}
}
if (empty[0] >= collen) {
lastCol = true;
empty.shift();
row++;
}
else lastCol = false;
td.style.padding = "0px " + (lastCol ? 0 : table.padding)
+ "px " + (i == l - 1 ? 0 : table.padding) + "px 0px";
}
//Fix padding of last row
var lastCells = rows[rows.length - 1].getElementsByTagName("td");
for (i = 0, il = lastCells.length; i < il; i++) {
lastCells[i].style.padding = "0 "
+ (i == il - 1 ? 0 : table.padding) + "px 0 0"
}
for (;z < cells.length; z++)
cells[z].parentNode.removeChild(cells[z]);
if (e.sync) return;
if (e.value)
table.$addTd(nodes[l - 1]); //what if it's not visible
else {
//last.parentNode.removeChild(last);
this.$altExt = null;
}
}
}
this.$addTd = function(amlNode) {
var cells = this.$table.getElementsByTagName("td");
var total = 0, collen = this.$columns.length;
for (var cell, i = 0; i < cells.length; i++) {
total += Math.min(collen,
(parseInt((cell = cells[i]).getAttribute("colspan") || 1)
* parseInt(cell.getAttribute("rowspan") || 1)));
}
if (total % collen == 0) { //New Row
var row = this.$tbody.appendChild(document.createElement("tr"));
}
else
row = cells[cells.length - 1].parentNode;
//Add a new cell in the last row
var cel = row.appendChild(document.createElement("td"));
cel.style.position = "relative";
if (amlNode.colspan || amlNode.span)
cel.setAttribute("colspan", amlNode.colspan || amlNode.span);
if (amlNode.rowspan)
cel.setAttribute("rowspan", amlNode.rowspan);
cel.appendChild(amlNode.$ext);
amlNode.$altExt = cel;
}
var propHandlers = {
"width" : function(value) {
this.$ext.style.width = "";/*value
? Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"
: "";*/
},
"height" : function(value) {
this.$ext.style.height = value
? Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"
: "";
this.parentNode.$resize();
},
"margin" : function(value) {
this.$ext.style.margin = apf.getBox(value).join("px ") + "px";
this.parentNode.$resize();
},
"colspan" : function(value) {
if (!value)
this.$altExt.removeAttribute("colspan");
else
this.$altExt.setAttribute("colspan", value);
visibleHandler.call(this, {sync: true});
this.parentNode.$resize();
},
"rowspan" : function(value) {
if (!value)
this.$altExt.removeAttribute("rowspan");
else
this.$altExt.setAttribute("rowspan", value);
visibleHandler.call(this, {sync: true});
this.parentNode.$resize();
},
"valign" : function(value) {
this.$altExt.valign = value;
},
"align" : function(value) {
if ("left|right".indexOf(value) == -1)
return;
this.$altExt.align = value;
}
}
propHandlers.span = propHandlers.colspan;
//@todo move this to enableTable, disableTable
this.register = function(amlNode) {
if (amlNode.$altExt) //@todo hack, need to rearch layouting
return;
amlNode.$propHandlers["left"] =
amlNode.$propHandlers["top"] =
amlNode.$propHandlers["right"] =
amlNode.$propHandlers["bottom"] = apf.K;
for (var prop in propHandlers) {
amlNode.$propHandlers[prop] = propHandlers[prop];
}
amlNode.addEventListener("prop.visible", visibleHandler);
this.$addTd(amlNode);
this.$noResize = true;
if (amlNode.margin)
propHandlers.margin.call(amlNode, amlNode.margin);
//Why was this commented out?
if (amlNode.$ext.tagName == "INPUT") {
//amlNode.$ext.style.width = "100%";
}
else
amlNode.$ext.style.width = "auto";
if (this.lastChild == amlNode)
this.$propHandlers["padding"].call(this, this.padding);
delete this.$noResize;
}
this.unregister = function(amlNode) {
amlNode.$propHandlers["left"] =
amlNode.$propHandlers["top"] =
amlNode.$propHandlers["right"] =
amlNode.$propHandlers["bottom"] = null;
for (var prop in propHandlers) {
delete amlNode.$propHandlers[prop];
}
amlNode.removeEventListener("prop.visible", visibleHandler);
visibleHandler.call(amlNode, {value: false}); //maybe parent is already reset here?
if (amlNode.margin)
amlNode.$ext.style.margin = "";
if (amlNode.width)
amlNode.$ext.style.width = "";
}
/*
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
this.register(this.parentNode);
});
*/
// *** DOM Hooks *** //
this.addEventListener("DOMNodeRemoved", function(e) {
if (e.$doOnlyAdmin || e.currentTarget == this)
return;
if (e.relatedNode == this) {
this.unregister(e.currentTarget);
//e.currentTarget.$setLayout();
}
});
this.addEventListener("DOMNodeInserted", function(e) {
if (e.currentTarget == this || e.currentTarget.nodeType != 1)
return;
if (e.relatedNode == this) {
if (e.$isMoveWithinParent) {
visibleHandler.call(e.currentTarget, {sync: true});
}
else {
e.currentTarget.$setLayout("table");
if (e.currentTarget.nextSibling)
visibleHandler.call(e.currentTarget, {sync: true});
}
}
});
this.$draw = function(){
this.$ext = apf.insertHtmlNode(null, this.$pHtmlNode, null,
"<div><table cellSpacing='0' cellPadding='0'><tbody><tr class='first'></tr></tbody></table></div>");
this.$table = this.$ext.firstChild;
this.$tbody = this.$table.firstChild;
this.$ext.className = "table " + (this.getAttribute("class") || "");
//this.$ext.style.overflow = "hidden";
this.$int = this.$ext;
this.$ext.host = this;
if (this.getAttribute("class"))
apf.setStyleClass(this.$ext, this.getAttribute("class"));
this.addEventListener("resize", this.$resize);
this.$originalMin = [this.minwidth || 0, this.minheight || 0];
};
//@todo implement percentage by using fixed and add functionality here
this.$resize = function(){
if (!this.$amlLoaded || this.$noResize)
return;
if (this.$table.offsetWidth >= this.$ext.offsetWidth)
this.$ext.style.minWidth = (this.minwidth = Math.max(0, this.$table.offsetWidth
- apf.getWidthDiff(this.$ext))) + "px";
else {
this.$ext.style.minWidth = "";
this.minwidth = this.$originalMin[0];
}
if (this.$table.offsetHeight >= this.$ext.offsetHeight)
this.$ext.style.minHeight = (this.minheight = Math.max(0, this.$table.offsetHeight
- apf.getHeightDiff(this.$ext))) + "px";
else {
this.$ext.style.minHeight = "";
this.minheight = this.$originalMin[1];
}
}
this.$loadAml = function(x) {
this.$amlLoaded = false; //@todo hack
if (!this.$columns)
this.$propHandlers.columns.call(this, this.columns = "150, 200");
this.$amlLoaded = true; //@todo hack
};
}).call(apf.table.prototype = new apf.GuiElement());
apf.aml.setElement("table", apf.table);
/**
* This element displays a rectangle containing arbitrary (X)HTML.
*
* This element can be databound and use databounding rules to
* convert data into (X)HTML using--for instance--XSLT or JSLT.
*
* #### Example: Some simple text
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:text width="300">
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur congue, nunc sed convallis gravida, justo nunc egestas nisi, eu iaculis nunc ipsum vel orci. Morbi mauris urna, rutrum at imperdiet at, molestie eu risus. Curabitur eu tincidunt eros. Donec in massa ut dolor vulputate commodo. Cras pulvinar urna ut ipsum pulvinar mollis sit amet in dui. Nam lobortis ligula sed tortor dapibus eget tincidunt dui pretium.
* </a:text>
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Example: Using Scrolldown
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:text id="txtExample"
* width = "300"
* height = "100"
* scrolldown = "true">
* Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur congue, nunc sed convallis gravida, justo nunc egestas nisi, eu iaculis nunc ipsum vel orci. Morbi mauris urna, rutrum at imperdiet at, molestie eu risus. Curabitur eu tincidunt eros. Donec in massa ut dolor vulputate commodo. Cras pulvinar urna ut ipsum pulvinar mollis sit amet in dui. Nam lobortis ligula sed tortor dapibus eget tincidunt dui pretium. Quisque semper sem dignissim quam ullamcorper et lobortis arcu eleifend. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce ac commodo mi. Pellentesque sit amet magna sed velit volutpat volutpat. Nam lobortis sem sed tortor accumsan dictum. Donec scelerisque rhoncus cursus. Mauris dui dolor, vehicula quis lacinia quis, facilisis et eros. Nulla facilisi. Donec urna velit, adipiscing non sollicitudin at, sodales id lorem. Fusce fringilla, magna id pellentesque egestas, neque risus luctus mauris, vel porttitor libero tortor et augue. Integer euismod porttitor mi, at viverra nulla pharetra vel. Etiam odio elit, volutpat a porttitor eu, posuere nec augue. Phasellus placerat lacus ut augue tempor consectetur.
* </a:text>
* <a:button
* onclick="txtExample.setValue(txtExample.getValue() + '&lt;br />A new line!')">
* Add a line
* </a:button>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.text
* @define text
*
* @form
* @inherits apf.Cache
* @inherits apf.StandardBinding
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.1
*
*/
// @todo Please refactor this object
apf.text = function(struct, tagName) {
this.$init(tagName || "text", apf.NODE_VISIBLE, struct);
this.$nodes = [];
};
(function(){
this.implement(
apf.Cache,
apf.ChildValue
);
this.$focussable = true; // This object can't get the focus
this.focussable = false;
this.textselect = true;
this.$hasStateMessages = true;
this.$textTimer = this.$lastMsg = this.$lastClass = this.$changedHeight = null;
// *** Properties and Attributes *** //
/**
* @attribute {Boolean} scrolldown Sets or gets whether this element's viewport is always
* scrolled down. This is especially useful
* when this element is used to displayed
* streaming content such as a chat conversation.
*
*/
/**
* @attribute {Boolean} secure Sets or gets whether the content loaded in this element
* should be filtered in order for it to not
* be able to execute JavaScript. This is
* especially useful when the content does
* not come from a trusted source, like a
* web service or xmpp feed.
*/
this.$booleanProperties["scrolldown"] = true;
this.$booleanProperties["secure"] = true;
this.$booleanProperties["textselect"] = true;
this.$supportedProperties.push("behavior", "scrolldown", "secure", "value");
this.$isTextInput = function(){
return this.textselect;
}
this.$propHandlers["scrolldown"] = function(value) {
var _self = this;
if (value) {
//this.addEventListener("resize", this.$resize);
this.$scrolldown = true;
apf.addListener(this.$scrollArea, "scroll", this.$scrollFunc = function(){
_self.$scrolldown = this.scrollTop >= this.scrollHeight
- this.offsetHeight + apf.getVerBorders(this);
});
this.addEventListener("scroll", this.$scroll);
this.addEventListener("afterload", this.$scroll);
this.addEventListener("resize", function(){
if (_self.$scrollArea && _self.$scrolldown && _self.scrolldown)
_self.$scrollArea.scrollTop = _self.$scrollArea.scrollHeight;
});
clearInterval(this.$textTimer);
this.$textTimer = setInterval(function(){
if (_self.$scrollArea && _self.$scrolldown && _self.scrolldown)
_self.$scrollArea.scrollTop = _self.$scrollArea.scrollHeight;
}, 200);
}
else {
//this.removeEventListener("resize", this.$resize);
this.removeEventListener("scroll", this.$scroll);
this.removeEventListener("afterload", this.$scroll);
clearInterval(this.$textTimer);
if (this.$scrollArea)
apf.removeListener(this.$scrollArea, "scoll", this.$scrollFunc);
}
}
this.$scroll = function(e) {
var html = this.$scrollArea;
if (e.name == "afterload") {
this.$scrolldown = true;
html.scrollTop = html.scrollHeight;
return;
}
this.$scrolldown = html.scrollTop >= html.scrollHeight
- html.offsetHeight + apf.getVerBorders(html);
};
/*this.$resize = function(){
if (this.scrolldown && this.$scrolldown)
this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight;
}*/
/**
* @attribute {String} value Sets or gets the contents of this element. This can be text, html, xhtml.
*/
this.$propHandlers["value"] = function(value, prop, force, forceAdd) {
if (this.each)
return;
if (typeof value != "string") {
if (value.nodeType)
value = value.nodeType > 1 && value.nodeType < 5
? value.nodeValue
: value.firstChild && value.firstChild.nodeValue || "";
else
value = value ? value.toString() : "";
}
if (this.secure) {
value = value.replace(/<a /gi, "<a target='_blank' ")
.replace(/<object.*?\/object>/g, "")
.replace(/<script.*?\/script>/g, "")
.replace(new RegExp("ondblclick|onclick|onmouseover|onmouseout"
+ "|onmousedown|onmousemove|onkeypress|onkeydown|onkeyup|onchange"
+ "|onpropertychange", "g"), "ona");
}
value = value.replace(/\<\?xml version="1\.0" encoding="UTF-16"\?\>/, "");
if (forceAdd) {
apf.insertHtmlNodes(null, this.$container, null, value);
if (!this.value) this.value = "";
this.value += value;
}
else
this.$container.innerHTML = value;//.replace(/<img[.\r\n]*?>/ig, "")
if (this.scrolldown && this.$scrolldown)
this.$scrollArea.scrollTop = this.$scrollArea.scrollHeight;
};
this.$eachHandler = function(value) {
this.$attrExcludePropBind = apf.extend({}, this.$attrExcludePropBind);
this.$attrExcludePropBind.value = value ? 2 : 0;
};
this.addEventListener("prop.each", this.$eachHandler);
this.addEventListener("$clear", function(){
this.$container.innerHTML = "";
this.value = "";
this.dispatchEvent("prop.value", {value: ""});
});
// @todo replace this stub with something that does something
this.$moveNode = function() {};
// *** Public methods *** //
this.addValue = function(value) {
this.$propHandlers["value"].call(this, value, null, null, true);
this.dispatchEvent("prop.value", {value: this.value});
};
/**
* Sets the value of this element. This should be one of the values
* specified in the `values` attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
this.setProperty("value", value, false, true);
};
/**
* Returns the current value of this element.
* @return {String} The current value.
*/
this.getValue = function(){
return this.$container.innerHTML;
};
// *** Keyboard Support *** //
this.addEventListener("keydown", function(e) {
var key = e.keyCode;
switch (key) {
case 33:
//PGUP
this.$container.scrollTop -= this.$container.offsetHeight;
break;
case 34:
//PGDN
this.$container.scrollTop += this.$container.offsetHeight;
break;
case 35:
//END
this.$container.scrollTop = this.$container.scrollHeight;
break;
case 36:
//HOME
this.$container.scrollTop = 0;
break;
case 38:
this.$container.scrollTop -= 10;
break;
case 40:
this.$container.scrollTop += 10;
break;
default:
return;
}
return false;
}, true);
// *** Private methods *** //
this.$canLoadData = function(){
return this.$attrBindings.value ? true : false;
}
this.$add = function(xmlNode, Lid, xmlParentNode, htmlParentNode, beforeNode) {
var f = this.$attrBindings.value.cvalue;
var html = f(xmlNode);
html = "<div id='" + Lid + "'>" + html + "</div>";
if (htmlParentNode) {
if (beforeNode)
beforeNode.insertAdjacentHTML("beforebegin", html);
else
this.$container.insertAdjacentHTML("beforeend", html);
//apf.insertHtmlNode(oItem, htmlParentNode, beforeNode);
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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:table columns="150">
* <!-- startcontent -->
* <a:textbox value="Text"></a:textbox>
* <a:textbox value="Text" disabled="true" initial-message="I'm disabled!"></a:textbox>
* <!-- endcontent -->
* </a:table>
* </a:application>
* ```
*
* #### Example: Validation
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:label for="lbl2">Please enter a minimum of three characters</a:label>
* <a:textbox
* id = "lbl2"
* minlength = "3"
* maxlength = "5"
* invalidmsg = "Invalid! Please enter a minimum of three characters" />
* <a:label for="lbl3">Enter your email address</a:label>
* <a:textbox
* id = "lbl3"
* datatype = "a:email"
* invalidmsg = "Invalid! Please enter a proper email address" />
* <a:label
* caption = "A US Phone Number"
* for = "txt71">
* </a:label>
* <a:textbox
* mask = "(000)0000-0000;;_"
* id = "txt71" />
* <a:label
* caption = "A Date"
* for = "txt73">
* </a:label>
* <a:textbox
* mask = "00-00-0000;;_"
* datatype = "xsd:date"
* invalidmsg = "Invalid date; Please enter a correct date"
* id = "txt73" />
* <a:label
* caption = "A MAC Address"
* for = "txt75" ></a:label>
* <a:textbox
* mask = "XX-XX-XX-XX-XX-XX;;_"
* id = "txt75" />
* <!-- endcontent -->
* </a:application>
* ```
*
* #### Example: A Regular Box
*
* ```xml, demo
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <!-- startcontent -->
* <a:bar id="winGoToFile"
* width = "500"
* skin = "winGoToFile"
* minheight = "35"
* maxheight = "400">
* <a:vbox id="vboxGoToFile" edge="5 5 5 5" padding="5" anchors2="0 0 0 0">
* <a:textbox id="txtGoToFile" realtime="true" skin="searchbox_textbox" focusselect="true" />
* <a:list id="dgGoToFile"
* class = "searchresults noscrollbar"
* skin = "lineselect"
* maxheight = "350"
* scrollbar = "sbShared 32 7 7"
* viewport = "virtual"
* multiselect = "true"
* empty-message = "A filelist would go here.">
* </a:list>
* </a:vbox>
* </a:bar>
* <!-- endcontent -->
* </a:application>
* ```
*
* @class apf.textbox
* @define textbox
* @allowchild autocomplete, {smartbinding}
*
* @form
* @inherits apf.StandardBinding
* @inheritsElsewhere apf.XForms
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.1
*
*
*/
/**
* @binding value Determines the way the value for the element is retrieved
* from the bound data.
*
* #### Example
*
* Sets the value based on data loaded into this component.
*
* ```xml
* <a:model id="mdlTextbox">
* <data name="Lukasz"></data>
* </a:model>
* <a:textbox model="mdlTextbox" value="[@name]" />
* ```
*
* A shorter way to write this is:
* ```xml
* <a:model id="mdlTextbox">
* <data name="Lukasz"></data>
* </a:model>
* <a:textbox value="[mdlTextbox::@name]" />
* ```
*
*/
/**
* @event click Fires when the user presses a mousebutton while over this element and then let's the mousebutton go.
*/
/**
* @event mouseup Fires when the user lets go of a mousebutton while over this element.
*/
/**
* @event mousedown Fires when the user presses a mousebutton while over this element.
*/
/**
* @event keyup Fires when the user lets go of a keyboard button while this element is focussed.
* @param {Object} e The standard event object. It contains the following property:
* - keyCode ([[Number]]): which key was pressed. This is an ascii number.
*/
/**
* @event clear Fires when the content of this element is cleared.
*/
apf.input = function(struct, tagName) {
this.$init(tagName || "input", apf.NODE_VISIBLE, struct);
};
apf.secret = function(struct, tagName) {
this.$init(tagName || "secret", apf.NODE_VISIBLE, struct);
};
apf.password = function(struct, tagName) {
this.$init(tagName || "password", apf.NODE_VISIBLE, struct);
};
apf.textarea = function(struct, tagName) {
this.$init(tagName || "textarea", apf.NODE_VISIBLE, struct);
this.multiline = true;
};
// HTML5 email element
apf.email = function(struct, tagName) {
this.$init(tagName || "email", apf.NODE_VISIBLE, struct);
};
apf.textbox = function(struct, tagName) {
this.$init(tagName || "textbox", apf.NODE_VISIBLE, struct);
};
(function(){
this.implement(
apf.DataAction
);
this.$focussable = true; // This object can get the focus
this.$masking = false;
this.$autoComplete = false;
this.$childProperty = "value";
//this.realtime = false;
this.value = "";
this.readonly = false;
this.$isTextInput = true;
this.multiline = false;
/**
* @attribute {Boolean} realtime Defines whether the value of the bound data is
* updated as the user types it, or only when this element loses focus or
* the user presses enter.
*/
this.$booleanProperties["readonly"] = true;
this.$booleanProperties["focusselect"] = true;
this.$booleanProperties["realtime"] = true;
this.$booleanProperties["kbclear"] = true;
this.$supportedProperties.push("value", "mask", "initial-message",
"focusselect", "realtime", "type", "rows", "cols", "kbclear");
/**
* @attribute {String} value Sets or gets the text of this element
*
*/
this.$propHandlers["value"] = function(value, prop, force, initial) {
// @todo apf3.0 check use of this.$propHandlers["value"].call
if (!this.$input || !initial && this.getValue() == value)
return;
// Set Value
if (!initial && !value && !this.hasFocus()) //@todo apf3.x research the use of clear
return this.$clear();
else if (this.isHTMLBox) {
if (this.$input.innerHTML != value)
this.$input.innerHTML = value;
}
else if (this.$input.value != value)
this.$input.value = value;
if (!initial)
apf.setStyleClass(this.$ext, "", [this.$baseCSSname + "Initial"]);
if (this.$button)
this.$button.style.display = value && !initial ? "block" : "none";
};
//See validation
//var oldPropHandler = this.$propHandlers["maxlength"];
this.addEventListener("prop.maxlength", function(e) {
//Special validation support using nativate max-length browser support
if (this.$input.tagName.toLowerCase().match(/input|textarea/))
this.$input.maxLength = parseInt(e.value) || null;
});
this.addEventListener("prop.editable", function(e) {
if (apf.isIE)
this.$input.unselectable = e.value ? "On" : "Off";
else {
if (e.value)
apf.addListener(this.$input, "mousedown", apf.preventDefault);
else
apf.removeListener(this.$input, "mousedown", apf.preventDefault);
}
});
/**
* @attribute {String} mask Sets or gets a complex input pattern that the user should
* adhere to.
*
* This is a string which is a combination of special and normal
* characters. It is comma seperated, and thus has two options. The first option
* specifies whether the non-input characters (the chars not typed by the
* user) are in the value of this element. The second option specifies the
* character that is displayed when the user hasn't yet filled in a
* character.
*
* The following characters are possible:
*
* - `0`: any digit
* - `1`: the number 1 or 2.
* - `9`: any digit or a space.
* - `#`: user can enter a digit, space, plus or minus sign.
* - `L`: any alpha character, case insensitive.
* - `?`: any alpha character, case insensitive or space.
* - `A`: any alphanumeric character.
* - `a`: any alphanumeric character or space.
* - `X`: hexadecimal character, case insensitive.
* - `x`: hexadecimal character, case insensitive or space.
* - `&`: any whitespace.
* - `C`: any character.
* - `!`: causes the input mask to fill from left to right instead of from right to left.
* - `'`: the start or end of a literal part.
* - `"`: the start or end of a literal part.
* - `>`: converts all characters that follow to uppercase.
* - `<`: converts all characters that follow to lowercase.
* - `\`: cancel the special meaning of a character.
*
* #### Example
*
* An American phone number:
*
* ```xml
* <a:textbox mask="(000)0000-0000;;_" />
* ```
*
* #### Example
*
* A Dutch postal code:
*
* ```xml
* <a:textbox mask="0000 AA;;_" />
* ```
*
* #### Example
*
* A date
*
* ```xml
* <a:textbox mask="00-00-0000;;_" datatype="xsd:date" />
* ```
*
* #### Example
*
* A serial number
*
* ```xml
* <a:textbox mask="'WCS74'0000-00000;1;_" />
* ```
*
* #### Example
*
* A MAC address
*
* ```xml
* <a:textbox mask="XX-XX-XX-XX-XX-XX;;_" />
* ```
*/
this.$propHandlers["mask"] = function(value) {
if (this.mask.toLowerCase() == "password")// || !apf.hasMsRangeObject)
return;
if (!value) {
throw new Error("Not Implemented");
}
if (!this.$masking) {
this.$masking = true;
this.implement(apf.textbox.masking);
this.focusselect = false;
//this.realtime = false;
}
this.setMask(this.mask);
};
//this.$propHandlers["ref"] = function(value) {
// this.$input.setAttribute("name", value.split("/").pop().split("::").pop()
// .replace(/[\@\.\(\)]*/g, ""));
//};
/**
* @attribute {String} initial-message Sets or gets the message displayed by this element
* when it doesn't have a value set. This property is inherited from parent
* nodes. When none is found, it is looked for on the appsettings element.
*/
this.$propHandlers["initial-message"] = function(value) {
if (value) {
//this.$propHandlers["value"].call(this, value, null, true);
}
if (!this.value)
this.$clear(true);
if (this.type == "password" && this.$inputInitFix) {
this.$inputInitFix.innerHTML = value;
apf.setStyleClass(this.$inputInitFix, "initFxEnabled");
}
};
/**
* @attribute {Number} rows Sets or gets the row length for a text area.
*/
this.$propHandlers["rows"] = function(value) {
if (this.$input.tagName.toLowerCase() == "textarea" && value) {
this.setProperty("rows", value);
if (this.$ext) {
this.$ext.rows = value;
}
}
};
/**
* @attribute {Number} cols Sets or gets the column height for a text area.
*/
this.$propHandlers["cols"] = function(value) {
if (this.$input.tagName.toLowerCase() == "textarea" && value) {
this.setProperty("cols", value);
if (this.$ext) {
this.$ext.cols = value;
}
}
};
/**
* @attribute {Boolean} focusselect Sets or gets whether the text in this element is
* selected when this element receives focus.
*/
this.$propHandlers["focusselect"] = function(value) {
var _self = this;
this.$input.onmousedown = function(){
_self.focusselect = false;
};
this.$input.onmouseup =
this.$input.onmouseout = function(){
_self.focusselect = value;
};
};
/**
* @attribute {String} type Sets or gets the type or function this element represents.
* This can be any arbitrary name, although there are some special values:
*
* - `"username"`: this element is used to type in the name part of login credentials.
* - `"password"`: this element is used to type in the password part of login credentials.
*/
this.$propHandlers["type"] = function(value) {
if (value && "password|username".indexOf(value) > -1
&& typeof this.focusselect == "undefined") {
this.focusselect = true;
this.$propHandlers["focusselect"].call(this, true);
}
};
this.$isTextInput = function(e) {
return true;
};
// *** Public Methods *** //
/**
* Sets the value of this element. This should be one of the values
* specified in the `values` attribute.
* @param {String} value The new value of this element
*/
this.setValue = function(value) {
return this.setProperty("value", value, false, true);
};
/**
* Clears an element's value.
*/
this.clear = function(){
this.setProperty("value", "");
};
//@todo cleanup and put initial-message behaviour in one location
this.$clear = function(noEvent) {
if (this["initial-message"]) {
apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial");
this.$propHandlers["value"].call(this, this["initial-message"], null, null, true);
}
else {
this.$propHandlers["value"].call(this, "", null, null, true);
}
if (!noEvent)
this.dispatchEvent("clear");//@todo this should work via value change
};
/**
* Returns the current value of this element.
* @return {String} The current value.
*/
this.getValue = function(){
var v;
if (this.isHTMLBox) {
if (this.$input.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(/<br\/?\>/g, "\n")
.replace(/<[^>]*>/g, ""));
}
if (v.charAt(v.length - 1) == "\n")
v = v.substr(0, v.length - 1); //Remove the trailing new line
}
else
v = this.$input.value;
return v == this["initial-message"] ? "" : v.replace(/\r/g, "");
};
/**
* Selects the text in this element.
*/
this.select = function(){
this.$input.select();
};
/**
* Deselects the text in this element.
*/
this.deselect = function(){this.$input.deselect();};
/**** Private Methods *****/
this.$enable = function(){this.$input.disabled = false;};
this.$disable = function(){this.$input.disabled = true;};
this.$insertData = function(str) {
return this.setValue(str);
};
/**
* @private
*/
this.insert = function(text) {
if (apf.hasMsRangeObject) {
try {
this.$input.focus();
}
catch (e) {}
var range = document.selection.createRange();
if (this.oninsert)
text = this.oninsert(text);
range.pasteHTML(text);
range.collapse(true);
range.select();
}
else {
this.$input.value += text;
}
};
this.addEventListener("$clear", function(){
this.value = "";//@todo what about property binding?
if (this["initial-message"] && apf.document.activeElement != this) {
this.$propHandlers["value"].call(this, this["initial-message"], null, null, true);
apf.setStyleClass(this.$ext, this.$baseCSSname + "Initial");
}
else {
this.$propHandlers["value"].call(this, "");
}
if (!this.$input.tagName.toLowerCase().match(/input|textarea/i)) {
if (apf.hasMsRangeObject) {
try {
var range = document.selection.createRange();
range.moveStart("sentence", -1);
//range.text = "";
range.select();
}
catch (e) {}
}
}
this.dispatchEvent("clear"); //@todo apf3.0
});
this.$keyHandler = function(key, ctrlKey, shiftKey, altKey, e) {
if (this.$button && key == 27 && this.kbclear) {
//this.$clear();
if (this.value) {
this.change("");
this.dispatchEvent("keydown", {
keyCode: key,
ctrlKey: ctrlKey,
shiftKey: shiftKey,
altKey: altKey,
htmlEvent: e});
e.stopPropagation();
}
//this.focus({mouse:true});
}
// Disabled this because it was being fired via the document as well
// Tested using the gotoline plugin
// if (this.dispatchEvent("keydown", {
// keyCode : key,
// ctrlKey : ctrlKey,
// shiftKey : shiftKey,
// altKey : altKey,
// htmlEvent : e}) === false)
// return false;
};
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
* <a:application xmlns:a="http://ajax.org/2005/aml">
* <a:window
* id = "winMail"
* contextmenu = "fileMenu"
* width = "300"
* height = "200"
* visible = "true"
* resizable = "true"
* title = "An App">
* <!-- startcontent -->
* <a:toolbar>
* <a:menubar>
* <a:button submenu="fileMenu">File</a:button>
* <a:button submenu="editMenu">Edit</a:button>
* </a:menubar>
* </a:toolbar>
*
* <a:menu id="editMenu">
* <a:item>About us</a:item>
* <a:item>Help</a:item>
* </a:menu>
* <a:menu id="fileMenu">
* <a:item icon="email.png">Tutorials</a:item>
* <a:item>Live Helps</a:item>
* <a:divider></a:divider>
* <a:item>Visit Ajax.org</a:item>
* <a:item>Exit</a:item>
* </a:menu>
* <!-- endcontent -->
* </a:window>
* </a:application>
* ```
*
* @class apf.toolbar
* @define toolbar
* @container
*
* @allowchild bar, menubar
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
* @inherits apf.Presentation
*/
apf.toolbar = function(struct, tagName) {
this.$init(tagName || "toolbar", apf.NODE_VISIBLE, struct);
};
(function(){
this.$focussable = false;
// *** DOM Hooks *** //
// *** Init *** //
this.$draw = function(){
//Build Main Skin
this.$ext = this.$getExternal();
this.$int = this.$getLayoutNode("main", "container", this.$ext);
};
}).call(apf.toolbar.prototype = new apf.Presentation());
apf.aml.setElement("toolbar", apf.toolbar);
apf.aml.setElement("checked", apf.BindingRule);
/**
* @constructor
* @private
*/
apf.textbox.masking = function(){
/*
Special Masking Values:
- PASSWORD
<a:Textbox name="custref" mask="CS20999999" maskmsg="" validation="/CS200[3-5]\d{4}/" invalidmsg="" bind="custref/text()" />
*/
var _FALSE_ = 9128748732;
var _REF = {
"0" : "\\d",
"1" : "[12]",
"9" : "[\\d ]",
"#" : "[\\d +-]",
"L" : "[A-Za-z]",
"?" : "[A-Za-z ]",
"A" : "[A-Za-z0-9]",
"a" : "[A-Za-z0-9 ]",
"X" : "[0-9A-Fa-f]",
"x" : "[0-9A-Fa-f ]",
"&" : "[^\s]",
"C" : "."
};
var lastPos = -1;
var masking = false;
var oInput = this.$input;
var pos = [];
var initial, myvalue, format, fcase, replaceChar;
this.setPosition = function(setpos) {
setPosition(setpos || lastPos || 0);
};
this.addEventListener("$clear", function(){
this.value = "";
if (this.mask)
return this.setValue("");
});
this.$propHandlers["value"] = function(value) {
var data = "";
if (this.includeNonTypedChars) {
for (var i = 0; i < initial.length; i++) {
if (initial.substr(i, 1) != value.substr(i, 1))
data += value.substr(i, 1);//initial.substr(i,1) == replaceChar
}
}
this.$insertData(data || value);
setPosition(myvalue.length);
};
//Char conversion
var numpadKeys = {
"96": "0",
"97": "1",
"98": "2",
"99": "3",
"100": "4",
"101": "5",
"102": "6",
"103": "7",
"104": "8",
"105": "9",
"106": "*",
"107": "+",
"109": "-",
"110": ".",
"110": "/"
};
this.addEventListener("keydown", function(e) {
var key = e.keyCode,
stop = false;
switch (key) {
case 39:
//RIGHT
setPosition(lastPos + 1);
stop = true;
break;
case 37:
//LEFT
setPosition(lastPos - 1);
stop = true;
break;
case 35:
case 34:
setPosition(myvalue.length);
stop = true;
break;
case 33:
case 36:
setPosition(0);
stop = true;
break;
case 8:
//BACKSPACE
deletePosition(lastPos - 1);
setPosition(lastPos - 1);
stop = true;
break;
case 46:
//DEL
deletePosition(lastPos);
setPosition(lastPos);
stop = true;
break;
default:
if (key == 67 && e.ctrlKey) {
window.clipboardData.setData("Text", this.getValue());
stop = true;
}
break;
}
//@todo why isnt the conversion not always good? Check backtick.
var chr = numpadKeys[key] || String.fromCharCode(key);
if (setCharacter(chr))
setPosition(lastPos + 1);
var value, pos = lastPos;
if (this.realtime && (value = this.getValue()) != this.value) {
this.change(value);
setPosition(pos);
}
if (apf.isCharacter(e.keyCode) || stop)
return false;
}, true);
/* ***********************
Init
************************/
this.$initMasking = function(){
///this.keyHandler = this._keyHandler;
this.$keyHandler = null; //temp solution
masking = true;
this.$input[apf.isIphone ? "onclick" : "onmouseup"] = function(e) {
var pos = Math.min(calcPosFromCursor(), myvalue.length);
setPosition(pos);
return false;
};
this.$input.onpaste = function(e) {
e = e || window.event;
e.returnValue = false;
this.host.setValue(window.clipboardData.getData("Text") || "");
//setPosition(lastPos);
$setTimeout(function(){
setPosition(lastPos);
}, 1); //HACK good enough for now...
};
this.getValue = function(){
if (this.includeNonTypedChars)
return initial == this.$input.value
? ""
: this.$input.value.replace(new RegExp(replaceChar, "g"), "");
else
return myvalue.join("");
};
this.setValue = function(value) {
this.$propHandlers["value"].call(this, value);
};
};
this.setMask = function(m) {
if (!masking)
this.$initMasking();
m = m.split(";");
replaceChar = m.pop();
this.includeNonTypedChars = parseInt(m.pop(), 10) !== 0;
var mask = m.join(""); //why a join here???
var validation = "";
var visual = "";
var mode_case = "-";
var strmode = false;
var startRight = false;
var chr;
pos = [];
format = "";
fcase = "";
for (var looppos = -1, i = 0; i < mask.length; i++) {
chr = mask.substr(i, 1);
if (!chr.match(/[\!\'\"\>\<\\]/)) {
looppos++;
}
else {
if (chr == "!")
startRight = true;
else if (chr == "<" || chr == ">")
mode_case = chr;
else if (chr == "'" || chr == "\"")
strmode = !strmode;
continue;
}
if (!strmode && _REF[chr]) {
pos.push(looppos);
visual += replaceChar;
format += chr;
fcase += mode_case;
validation += _REF[chr];
}
else
visual += chr;
}
this.$input.value = visual;
initial = visual;
//pos = pos;
myvalue = [];
//format = format;
//fcase = fcase;
replaceChar = replaceChar;
//setPosition(0);//startRight ? pos.length-1 : 0);
//validation..
//forgot \ escaping...
};
function checkChar(chr, p) {
var f = format.substr(p, 1);
var c = fcase.substr(p, 1);
if (chr.match(new RegExp(_REF[f])) == null)
return _FALSE_;
if (c == ">")
return chr.toUpperCase();
if (c == "<")
return chr.toLowerCase();
return chr;
}
function setPosition(p) {
if (p < 0)
p = 0;
if (apf.hasMsRangeObject) {
var range = oInput.createTextRange();
range.expand("textedit");
range.select();
if (pos[p] == null) {
range.collapse(false);
range.select();
lastPos = pos.length;
return false;
}
range.collapse();
range.moveStart("character", pos[p]);
range.moveEnd("character", 1);
range.select();
}
else {
if (typeof pos[p] == "undefined") {
oInput.selectionStart = oInput.selectionEnd = pos[pos.length - 1] + 1;
lastPos = pos.length;
return false;
}
oInput.selectionStart = pos[p];
oInput.selectionEnd = pos[p] + 1;
}
lastPos = p;
}
function setCharacter(chr) {
if (pos.length && pos[lastPos] == null)
return false;
chr = checkChar(chr, lastPos);
if (chr == _FALSE_)
return false;
if (apf.hasMsRangeObject) {
var range = oInput.createTextRange();
range.expand("textedit");
range.collapse();
range.moveStart("character", pos[lastPos]);
range.moveEnd("character", 1);
range.text = chr;
if (apf.document.activeElement == this)
range.select();
}
else {
var val = oInput.value;
var start = oInput.selectionStart;
var end = oInput.selectionEnd;
oInput.value = val.substr(0, start) + chr + val.substr(end);
oInput.selectionStart = start;
oInput.selectionEnd = end;
}
myvalue[lastPos] = chr;
return true;
}
function deletePosition(p) {
if (pos[p] == null)
return false;
if (apf.hasMsRangeObject) {
var range = oInput.createTextRange();
range.expand("textedit");
range.collapse();
range.moveStart("character", pos[p]);
range.moveEnd("character", 1);
range.text = replaceChar;
range.select();
}
else {
var val = oInput.value;
var start = pos[p];
var end = pos[p] + 1;
oInput.value = val.substr(0, start) + replaceChar + val.substr(end);
oInput.selectionStart = start;
oInput.selectionEnd = end;
}
//ipv lastPos
myvalue[p] = " ";
}
this.$insertData = function(str) {
if (str == this.getValue())
return;
var i, j;
try {
if (!apf.hasMsRangeObject && oInput.selectionStart == oInput.selectionEnd)
setPosition(0); // is this always correct? practice will show...
}
catch (ex) {
// in FF (as we know it), we cannot access the selectStart property
// when the control/ input doesn't have the focus or is not visible.
// A workaround is provided here...
if (!str)
return;
var chr, val;
for (i = 0, j = str.length; i < j; i++) {
lastPos = i;
if (pos[lastPos] == null)
continue;
chr = checkChar(str.substr(i, 1), i);
if (chr == _FALSE_)
continue;
val = oInput.value;
oInput.value = val.substr(0, pos[i]) + chr + val.substr(pos[i] + 1);
}
if (str.length)
lastPos++;
return; // job done, bail out
}
str = this.dispatchEvent("insert", { data : str }) || str;
if (!str) {
if (!this.getValue()) return; //maybe not so good fix... might still flicker when content is cleared
for (i = this.getValue().length - 1; i >= 0; i--)
deletePosition(i);
setPosition(0);
return;
}
for (i = 0, j = str.length; i < j; i++) {
lastPos = i;
setCharacter(str.substr(i, 1));
if (!apf.hasMsRangeObject)
setPosition(i + 1);
}
if (str.length)
lastPos++;
};
function calcPosFromCursor(){
var range, lt = 0;
if (!apf.hasMsRangeObject) {
lt = oInput.selectionStart;
}
else {
range = document.selection.createRange();
var r2 = range.duplicate();
r2.expand("textedit");
r2.setEndPoint("EndToStart", range);
lt = r2.text.length;
}
for (var i = 0; i < pos.length; i++) {
if (pos[i] > lt)
return (i == 0) ? 0 : i - 1;
}
return myvalue.length; // always return -a- value...
}
};
/**
* Live Markup processor for a processing instruction
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.9
*/
apf.LiveMarkupPi = function(){
//this.$data;
this.$init();
};
/*
@todo optimize the pi, possible with this code:
var div, doc = this.ownerDocument, domParser = doc.$domParser,
docFrag = doc.createDocumentFragment(),
sStart = "<a:application xmlns:a='" + apf.ns.aml + "'>",
sEnd = "</a:application>";
docFrag.$int = div;
domParser.parseFromXml(apf.getXml(sStart + this.$bindingQueue[i] + sEnd), { //@todo might be optimized by doing it only once
doc: doc,
amlNode: docFrag,
beforeNode: null,
include: true
});
*/
(function(){
this.mainBind = "data";
this.implement(apf.StandardBinding);
this.getDocument = function(){
return this.$data && this.$data.ownerDocument;
};
this.clear = function(msg) {
if (msg == "loading" && apf.getInheritedAttribute(this, "loading-message")) {
this.$propHandlers["calcdata"].call(this, "<span class='loading'>Loading...</span>");
this.calcdata = "";
}
};
this.$propHandlers["calcdata"] = function(data) {
if (this.$skipChange) //Used by liveedit.js
return;
if (this.$data) {
var nodes = this.$data.childNodes;
for (var i = nodes.length - 1; i >= 0; i--)
nodes[i].destroy(true);
}
//var dt = new Date().getTime();
//if (!this.xmlRoot)
//return this.$ext.innerHTML = "loading...";
if (typeof data == "string" && data.indexOf("<a:") > -1) { //@todo use the .hasAml attribute
this.$ext.innerHTML = "";//data;
var doc = this.ownerDocument.$domParser.parseFromString("<a:application xmlns:a='"
+ apf.ns.apf + "'>" + data + "</a:application>", "text/xml", {
htmlNode: this.$ext,
host: this
//nodelay : true
})
this.$data = doc.documentElement;
//apf.queue.empty();
//alert(new Date().getTime() - dt);
}
else {
if (this.$data) {
var nodes = this.$data.childNodes;
for (var i = 0; i < nodes.length; i++)
nodes[i].destroy(true);
}
if (this.$ext)
this.$ext.innerHTML = data || "";
}
};
}).call(apf.LiveMarkupPi.prototype = new apf.AmlProcessingInstruction(true));
apf.aml.setProcessingInstruction("lm", apf.LiveMarkupPi);
apf.aml.setProcessingInstruction("lm-debug", apf.LiveMarkupPi);
apf.aml.setProcessingInstruction("livemarkup", apf.LiveMarkupPi);
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});
}
});