nextapp.echo2.webrender.resource.ClientEngine.js Maven / Gradle / Ivy
/* * This file is part of the Echo Web Application Framework (hereinafter "Echo"). * Copyright (C) 2002-2009 NextApp, Inc. * * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. */ /** * Namespace for core functionality. * @namespace */ Core = { /** * Creates a duplicate copy of a function by wrapping the original in a closure. * * @param f the function * @return an effectively identical copy */ _copyFunction: function(f) { return function() { f.apply(this, arguments); }; }, /** * Creates an empty function. */ _createFunction: function() { return function() { }; }, /** * Creates a new class, optionally extending an existing class. * This method may be called with one or two parameters as follows: *
property of the event is returned. * On clients which support only the Internet Explorer event model, * the*
Core.extend(definition)
*Core.extend(baseClass, definition)
** Each property of the definition object will be added to the prototype of the returned defined class. * Properties that begin with a dollar-sign (
$
) will be processed specially: **
*
*- The
*$construct
property, which must be a function, will be used as the constructor. * The$load
property, which must be a function, f provided, will be used as a static initializer, * executed once when the class is *defined*. The this pointer will be set to the class when * this method is executed.- The
*$static
property, an object, if provided, will have its properties installed as class variables.- The
*$abstract
property, an object ortrue
, if provided, will define methods that * must be implemented by derivative classes. If the value is simplytrue
, the object will be marked as * abstract (such that it does not necessarily need to provide implementations of abstract methods defined in its * base class.)- The
*$virtual
property, an object, if provided, defines methods that will be placed into the prototype * that may be overridden by subclasses. Attempting to override a property/method of the superclass that * is not defined in the virtual block will result in an exception. Having the default behavior NOT allow * for overriding ensures that namespacing between super- and sub-types if all internal variables are instance * duringCore.extend()
.* Use of this method enables a class to be derived WITHOUT executing the constructor of the base class * in order to create a prototype for the derived class. This method uses a "shared prototype" architecture, * where two objects are created, a "prototype class" and a "constructor class". These two objects share * the same prototype, but the "prototype class" has an empty constructor. When a class created with * this method is derived, the "prototype class" is used to create a prototype for the derivative. *
* This method will return the constructor class, which contains an internal reference to the * prototype class that will be used if the returned class is later derived by this method. * * @param {Function} baseClass the base class * @param {Object} definition an associative array containing methods and properties of the class * @return the constructor class */ extend: function() { // Configure baseClass/definition arguments. var baseClass = arguments.length == 1 ? null : arguments[0]; var definition = arguments.length == 1 ? arguments[0] : arguments[1]; var x, name; // Perform argument error checking. if (arguments.length == 2) { if (typeof(baseClass) != "function") { throw new Error("Base class is not a function, cannot derive."); } } if (!definition) { throw new Error("Object definition not provided."); } // Create the constructor class. var constructorClass; if (definition.$construct) { // Definition provides constructor, provided constructor function will be used as object. constructorClass = definition.$construct; // Remove property such that it will not later be added to the object prototype. delete definition.$construct; } else { // Definition does not provide constructor. if (baseClass) { // Base class available: copy constructor function from base class. // Note: should function copying not be supported by a future client, // it is possible to simply create a new constructor which invokes the base // class constructor (using closures and Function.apply()) to achieve the // same effect (with a slight performance penalty). constructorClass = Core._copyFunction(baseClass); } else { // No base class: constructor is an empty function. constructorClass = Core._createFunction(); } } // Create virtual property storage. constructorClass.$virtual = {}; // Store reference to base class in constructor class. constructorClass.$super = baseClass; if (baseClass) { // Create class with empty constructor that shares prototype of base class. var prototypeClass = Core._createFunction(); prototypeClass.prototype = baseClass.prototype; // Create new instance of constructor-less prototype for use as prototype of new class. constructorClass.prototype = new prototypeClass(); } // Assign constructor correctly. constructorClass.prototype.constructor = constructorClass; // Add abstract properties. if (definition.$abstract) { constructorClass.$abstract = {}; if (baseClass && baseClass.$abstract) { // Copy abstract properties from base class. for (x in baseClass.$abstract) { constructorClass.$abstract[x] = baseClass.$abstract[x]; } } if (definition.$abstract instanceof Object) { // Add abstract properties from definition. for (x in definition.$abstract) { constructorClass.$abstract[x] = true; constructorClass.$virtual[x] = true; } } // Remove property such that it will not later be added to the object prototype. delete definition.$abstract; } // Copy virtual property flags from base class to shared prototype. if (baseClass) { for (name in baseClass.$virtual) { constructorClass.$virtual[name] = baseClass.$virtual[name]; } } // Add virtual instance properties from definition to shared prototype. if (definition.$virtual) { Core._inherit(constructorClass.prototype, definition.$virtual, constructorClass.$virtual); for (name in definition.$virtual) { constructorClass.$virtual[name] = true; } // Remove property such that it will not later be added to the object prototype. delete definition.$virtual; } // Add toString and valueOf manually, as they will not be iterated // by for-in iteration in Internet Explorer. if (definition.hasOwnProperty("toString")) { constructorClass.prototype.toString = definition.toString; } if (definition.hasOwnProperty("valueOf")) { constructorClass.prototype.valueOf = definition.valueOf; } // Remove properties such that they will not later be added to the object prototype. delete definition.toString; delete definition.valueOf; // Add Mixins. if (definition.$include) { // Reverse order of mixins, such that later-defined mixins will override earlier ones. // (Mixins will only be added if they will NOT override an existing method.) var mixins = definition.$include.reverse(); Core._processMixins(constructorClass, mixins); // Remove property such that it will not later be added to the object prototype. delete definition.$include; } // Store $load static initializer and remove from definition so it is not inherited in static processing. var loadMethod = null; if (definition.$load) { loadMethod = definition.$load; // Remove property such that it will not later be added to the object prototype. delete definition.$load; } // Process static properties and methods defined in the '$static' object. if (definition.$static) { Core._inherit(constructorClass, definition.$static); // Remove property such that it will not later be added to the object prototype. delete definition.$static; } // Process instance properties and methods. Core._inherit(constructorClass.prototype, definition, constructorClass.$virtual); // If class is concrete, verify all abstract methods are provided. if (!constructorClass.$abstract) { this._verifyAbstractImpl(constructorClass); } // Invoke static constructors. if (loadMethod) { // Invoke $load() function with "this" pointer set to class. loadMethod.call(constructorClass); } return constructorClass; }, /** * Retrieves a value from an object hierarchy. * * Examples: * Given the following object 'o':
{ a: { b: 4, c: 2 }}
**
* * @param object an arbitrary object from which the value should be retrieved * @param {Array} path an array of object property names describing the path to retrieve * @return the value, if found, or null if it does not exist */ get: function(object, path) { for (var i = 0; i < path.length; ++i) { object = object[path[i]]; if (!object) { return null; } } return object; }, /** * Determines if the specified propertyName of the specified object is a virtual * property, i.e., that it can be overridden by subclasses. */ _isVirtual: function(virtualProperties, propertyName) { switch (propertyName) { case "toString": case "valueOf": return true; } return virtualProperties[propertyName]; }, /** * Installs properties from source object into destination object. *- *
Core.get(o, ["a", "b"]) will return
4
.- *
Core.get(o, ["a", "c"]) will return
2
.- *
Core.get(o, ["a", "d"]) will return
null
.- *
Core.get(o, ["a"]) will return
{ b: 4, c: 2 }
.- *
Core.get(o, ["b"]) will return
null
.- *
Core.get(o, ["d"]) will return
null
.* In the case where the destination object already has a property defined * and the "virtualProperties" argument is provided, the "virtualProperties" * collection will be checked to ensure that property is allowed to be * overridden. If "virtualProperties" is omitted, any property may be * overridden. * * @param destination the destination object * @param soruce the source object * @param virtualProperties (optional) collection of virtual properties from base class. */ _inherit: function(destination, source, virtualProperties) { for (var name in source) { if (virtualProperties && destination[name] !== undefined && !this._isVirtual(virtualProperties, name)) { // Property exists in destination as is not marked as virtual. throw new Error("Cannot override non-virtual property \"" + name + "\"."); } else { destination[name] = source[name]; } } }, /** * Creates a new function which executes a specific method of an object instance. * Any arguments passed to the returned function will be passed to the method. * The return value of the method will be returned by the function. * * CAUTION: When adding and removing methods as listeners, note that two separately * constructed methods will not be treated as equal, even if their instance and method * properties are the same. Failing to heed this warning can result in a memory leak, * as listeners would never be removed. * * @param instance the object instance * @param {Function} method the method to be invoked on the instance * @return the return value provided by the method */ method: function(instance, method) { return function() { return method.apply(instance, arguments); }; }, /** * Add properties of mixin objects to destination object. * Mixins will be added in order, and any property which is already * present in the destination object will not be overridden. * * @param destination the destination object * @param {Array} mixins the mixin objects to add */ _processMixins: function(destination, mixins) { for (var i = 0; i < mixins.length; ++i) { for (var mixinProperty in mixins[i]) { if (destination.prototype[mixinProperty]) { // Ignore mixin properties that already exist. continue; } destination.prototype[mixinProperty] = mixins[i][mixinProperty]; } } }, /** * Sets a value in an object hierarchy. * * Examples: * Given the following object 'o':
{ a: { b: 4, c: 2 } }
**
* * @param object an arbitrary object from which the value should be retrieved * @param {Array} path an array of object property names describing the path to retrieve * @return the value, if found, or null if it does not exist */ set: function(object, path, value) { var parentObject = null; // Find or create container object. for (var i = 0; i < path.length - 1; ++i) { parentObject = object; object = object[path[i]]; if (!object) { object = {}; parentObject[path[i]] = object; } } // Assign value. object[path[path.length - 1]] = value; }, /** * Verifies that a concrete derivative of an abstract class implements * abstract properties present in the base class. * * @param constructorClass the class to verify */ _verifyAbstractImpl: function(constructorClass) { var baseClass = constructorClass.$super; if (!baseClass || !baseClass.$abstract || baseClass.$abstract === true) { return; } for (var x in baseClass.$abstract) { if (constructorClass.prototype[x] == null) { throw new Error("Concrete class does not provide implementation of abstract method \"" + x + "\"."); } } } }; // _______________________ // Object EchoAsyncMonitor /** * Static object/namespace for polling server to monitor for asynchronous * server-initiated updates to the client ("server push"). */ EchoAsyncMonitor = { /** * The time, in milleseconds, between polling requests. */ timeInterval: 500, /** * Server query for polling service. */ pollServiceRequest: "?serviceId=Echo.AsyncMonitor", /** * Timeout identifier for the delayed invocation of the next poll request * (used to cancel the timeout if required). */ timeoutId: null, /** * Initiates an HTTP request to the asynchronous monitor poll service to * determine if the server has the need to update the client. */ connect: function() { var conn = new EchoHttpConnection(EchoClientEngine.baseServerUri + EchoAsyncMonitor.pollServiceRequest, "GET"); conn.responseHandler = EchoAsyncMonitor.responseHandler; conn.invalidResponseHandler = EchoAsyncMonitor.invalidResponseHandler; conn.connect(); }, /** * Processes an invalid response to the poll request. */ invalidResponseHandler: function() { alert("Invalid response from server to asynchronous polling connection."); }, /** * Starts the countdown to the next poll request. */ start: function() { if (!EchoServerTransaction.active) { EchoAsyncMonitor.timeoutId = window.setTimeout("EchoAsyncMonitor.connect();", EchoAsyncMonitor.timeInterval); } }, /** * Cancels the countdown to the next poll request, if necessary. */ stop: function() { if (EchoAsyncMonitor.timeoutId) { window.clearTimeout(EchoAsyncMonitor.timeoutId); EchoAsyncMonitor.timeoutId = null; } }, /** * Processes a response from the HTTP request made to the polling * service. * * @param conn the EchoHttpConnection containing the response information. */ responseHandler: function(conn) { if ("true" == conn.getResponseXml().documentElement.getAttribute("request-sync")) { // Server is requesting synchronization: Initiate server transaction. EchoServerTransaction.connect(); } else { // Server does not require synchronization: restart countdown to next poll request. EchoAsyncMonitor.start(); } } }; // _________________________ // Object EchoClientAnalyzer /** * Static object/namespace to perform analysis of client browser's * version information, capabilities, and quirks and store the information * in the outgoing ClientMessage. */ EchoClientAnalyzer = { /** * Analyzes properties of client browser and stores them in the outgoing * ClientMessage. */ analyze: function() { var messagePartElement = EchoClientMessage.getMessagePart("EchoClientAnalyzer"); EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorAppName", window.navigator.appName); EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorAppVersion", window.navigator.appVersion); EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorAppCodeName", window.navigator.appCodeName); EchoClientAnalyzer.setBooleanProperty(messagePartElement, "navigatorCookieEnabled", window.navigator.cookieEnabled); EchoClientAnalyzer.setBooleanProperty(messagePartElement, "navigatorJavaEnabled", window.navigator.javaEnabled()); EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorLanguage", window.navigator.language ? window.navigator.language : window.navigator.userLanguage); EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorPlatform", window.navigator.platform); EchoClientAnalyzer.setTextProperty(messagePartElement, "navigatorUserAgent", window.navigator.userAgent); if (window.screen) { // Capture Display Information EchoClientAnalyzer.setIntegerProperty(messagePartElement, "screenWidth", window.screen.width); EchoClientAnalyzer.setIntegerProperty(messagePartElement, "screenHeight", window.screen.height); EchoClientAnalyzer.setIntegerProperty(messagePartElement, "screenColorDepth", window.screen.colorDepth); } EchoClientAnalyzer.setIntegerProperty(messagePartElement, "utcOffset", 0 - parseInt((new Date()).getTimezoneOffset(), 10)); EchoClientAnalyzer.setTextProperty(messagePartElement, "unescapedXhrTest", "&"); }, /** * Stores a boolean client property in the ClientMessage. * * @param messagePartElement the- *
Core.set(o, ["a", "b"], 5)
will update the value of 'o' to be:{ a: { b: 5, c: 2 } }
- *
Core.set(o, ["a", "d"], 7)
will update the value of 'o' to be: *{ a: { b: 4, c: 2, d: 7 } }
- *
Core.set(o, ["e"], 9)
will update the value of 'o' to be:{ a: { b: 4, c: 2 }, e: 9 }
- *
Core.set(o, ["f", "g"], 8)
will update the value of 'o' to be: *{ a: { b: 4, c: 2 }, f: { g: 8 } }
- *
Core.set(o, ["a"], 10)
will update the value of 'o' to be:{ a: 10 }
message-part
element * in which generated property XML elements should be stored * @param propertyName the property name * @param propertyValue the property value */ setBooleanProperty: function(messagePartElement, propertyName, propertyValue) { var propertyElement = messagePartElement.ownerDocument.createElement("property"); propertyElement.setAttribute("type", "boolean"); propertyElement.setAttribute("name", propertyName); propertyElement.setAttribute("value", propertyValue ? "true" : "false"); messagePartElement.appendChild(propertyElement); }, /** * Stores a integer client property in the ClientMessage. * * @param messagePartElement themessage-part
element * in which generated property XML elements should be stored * @param propertyName the property name * @param propertyValue the property value */ setIntegerProperty: function(messagePartElement, propertyName, propertyValue) { var intValue = parseInt(propertyValue, 10); if (isNaN(intValue)) { return; } var propertyElement = messagePartElement.ownerDocument.createElement("property"); propertyElement.setAttribute("type", "integer"); propertyElement.setAttribute("name", propertyName); propertyElement.setAttribute("value", intValue); messagePartElement.appendChild(propertyElement); }, /** * Stores a text client property in the ClientMessage. * * @param messagePartElement themessage-part
element * in which generated property XML elements should be stored * @param propertyName the property name * @param propertyValue the property value */ setTextProperty: function(messagePartElement, propertyName, propertyValue) { var propertyElement = messagePartElement.ownerDocument.createElement("property"); propertyElement.setAttribute("type", "text"); propertyElement.setAttribute("name", propertyName); propertyElement.setAttribute("value", propertyValue); messagePartElement.appendChild(propertyElement); } }; // ______________________________ // Object EchoClientConfiguration /** * Static/object namespace which provides general-purpose Client Engine * configuration settings. */ EchoClientConfiguration = { /** * MessagePartProcessor implementation for EchoClientConfiguration. */ MessageProcessor: { /** * MessagePartProcessor process() method implementation. */ process: function(messagePartElement) { for (var i = 0; i < messagePartElement.childNodes.length; ++i) { if (messagePartElement.childNodes[i].nodeType == 1) { if (messagePartElement.childNodes[i].tagName == "store") { EchoClientConfiguration.MessageProcessor.processStore(messagePartElement.childNodes[i]); } } } }, /** * Proceses astore
message to store ClientConfiguration information * received from the server. * * @param storeElement thestore
element to process */ processStore: function(storeElement) { var propertyElements = storeElement.getElementsByTagName("property"); for (var i = 0; i < propertyElements.length; ++i) { var name = propertyElements[i].getAttribute("name"); var value = propertyElements[i].getAttribute("value"); EchoClientConfiguration.propertyMap[name] = value; } } }, /** * Client property map. */ propertyMap: { defaultOutOfSyncErrorMessage: "A synchronization error has occurred. Click \"Ok\" to re-synchronize.", defaultServerErrorMessage: "An application error has occurred. Your session has been reset.", defaultSessionExpirationMessage : "Your session has been reset due to inactivity." } }; // _______________________ // Object EchoClientEngine /** * Static object/namespace providing core client engine functionality. */ EchoClientEngine = { /** * The base URI of the Echo application server. */ baseServerUri: null, /** * Flag indicating whether debugging options (e.g., Debug Pane) are enabled. */ debugEnabled: true, /** * Initialization loading status (progress bar length). */ loadStatus: 2, /** * Current transaction id, retrieved from ServerMessage. */ transactionId: "", /** * Configures browser-specific settings based on ClientProperties results. * Invoked when ClientProperties are stored. */ configure: function() { if (EchoClientProperties.get("quirkCssPositioningOneSideOnly")) { // Initialize virtual positioning system used to emulate CSS positioning in MSIE. EchoVirtualPosition.init(); // Set body.style.overflow to hidden in order to hide root scrollbar in IE. // This is a non-standard CSS property. document.documentElement.style.overflow = "hidden"; } }, /** * Disposes the Echo2 Client Engine, unregistering event listeners * and releasing resources. */ dispose: function() { EchoEventProcessor.dispose(); EchoDomPropertyStore.disposeAll(); }, /** * Initializes the Echo2 Client Engine. * * @param baseServerUri the base URI of the Echo application server */ init: function(baseServerUri, debugEnabled) { EchoClientEngine.baseServerUri = baseServerUri; EchoClientEngine.debugEnabled = debugEnabled; // Launch debug window if requested in URI. if (EchoClientEngine.debugEnabled && window.location.search && window.location.search.toLowerCase().indexOf("debug") != -1) { EchoDebugManager.launch(); } // Confiugre initial client message. EchoClientMessage.setInitialize(); // Analyze client information. EchoClientAnalyzer.analyze(); // Synchronize initial state from server. EchoServerTransaction.connect(); // Add disposal listener. EchoDomUtil.addEventListener(window, "unload", EchoClientEngine.dispose); // Increment progress bar showing loading status. EchoClientEngine.incrementLoadStatus(); }, /** * Handles a client-side error. */ processClientError: function(message, ex) { var errorText; if (ex instanceof Error) { errorText = "Error Name: " + ex.name + "\n" + "Error Message: " + ex.message; } else { errorText = "Error: " + ex; } alert("The following client application error has occurred:\n\n" + "---------------------------------\n" + message + "\n\n" + errorText + "\n" + "---------------------------------\n\n" + "Please contact your server administrator.\n\n"); }, /** * Handles the expiration of server-side session information. */ processSessionExpiration: function() { var message = EchoClientConfiguration.propertyMap["sessionExpirationMessage"]; var uri = EchoClientConfiguration.propertyMap["sessionExpirationUri"]; if (message || uri) { if (message) { alert(message); } if (uri) { window.location.href = uri; } else { window.location.reload(); } } else { message = EchoClientConfiguration.propertyMap["defaultSessionExpirationMessage"]; alert(message); window.location.reload(); } }, /** * Handles a server-side error. */ processServerError: function() { var message = EchoClientConfiguration.propertyMap["serverErrorMessage"]; var uri = EchoClientConfiguration.propertyMap["serverErrorUri"]; if (message || uri) { if (message) { alert(message); } if (uri) { window.location.href = uri; } else { if (EchoServerMessage.transactionCount > 0) { // Only reload if not on first transaction to avoid infinite crash loop. window.location.reload(); } } } else { message = EchoClientConfiguration.propertyMap["defaultServerErrorMessage"]; alert(message); if (EchoServerMessage.transactionCount > 0) { // Only reload if not on first transaction to avoid infinite crash loop. window.location.reload(); } } }, /** * Disable (remove) loading status progress bar. * Performs no operation if application is not starting up, i.e., * if first synchronization has been completed. */ disableLoadStatus: function() { if (EchoClientEngine.loadStatus != -1) { EchoClientEngine.loadStatus = -1; EchoClientEngine.renderLoadStatus(); } }, /** * Increment loading status progress bar and re-render. * Performs no operation if application is not starting up, i.e., * if first synchronization has been completed. */ incrementLoadStatus: function() { if (EchoClientEngine.loadStatus != -1) { ++EchoClientEngine.loadStatus; EchoClientEngine.renderLoadStatus(); } }, /** * Render current loading status. * Performs no operation if element with id "loadstatus" * does not exist. */ renderLoadStatus: function() { var loadStatusDivElement = document.getElementById("loadstatus"); if (!loadStatusDivElement) { return; } if (EchoClientEngine.loadStatus == -1) { loadStatusDivElement.parentNode.removeChild(loadStatusDivElement); } else { var text = ""; for (var i = 0; i < EchoClientEngine.loadStatus; ++i) { text += "|"; } loadStatusDivElement.removeChild(loadStatusDivElement.firstChild); loadStatusDivElement.appendChild(document.createTextNode(text)); } }, /** * Update current transaction id, storing new value in client engine and outgoing client message. * * @param newValue the new transaction id */ updateTransactionId: function(newValue) { EchoClientEngine.transactionId = newValue; EchoClientMessage.messageDocument.documentElement.setAttribute("trans-id", EchoClientEngine.transactionId); }, /** * Verifies that the given element is eligible to receive input based on the * current condition of the user interface. * This method should be invoked by UI listeners before procsesing any user input. * This method will ensure that no server transaction is active and the element * is within the current modal context. * The method will additionally verify that the element does not have the *EchoDomPropertyStore
property *EchoClientEngine.inputDisabled
set to true. * * The 'allowInputDuringTransaction' flag (default value = false) may be used to * allow input while client/server transactions are being processed. This flag * should be used with extreme caution, as you'll then need to handle scenarios * such as when the user makes an input during a server transaction, and, during * that server transaction, the server manipulates the state of the component to * be incompatible with the new client state. This flag is used for components * such as text-entry fields where blocking user input during server * transactions has significant negative consequences for the user experience. * For example, were it not for this capability, if a server-pushed * synchronization occurred while the user were typing text into a field, some * of the his/her entered characters would be silently dropped. * * @param element the elment or id of the element * @param allowInputDuringTransaction flag indicating that input should be * allowed during client/server transactions (Use of this flag is * recommended against in MOST situations, see above) */ verifyInput: function(element, allowInputDuringTransaction) { if (!allowInputDuringTransaction && EchoServerTransaction.active) { return false; } if (!EchoModalManager.isElementInModalContext(element)) { return false; } if (EchoDomPropertyStore.getPropertyValue(element, "EchoClientEngine.inputDisabled")) { return false; } return true; } }; // ________________________ // Object EchoClientMessage /** * Static object/namespace representing the outgoing ClientMessage. */ EchoClientMessage = { /** * The current outgoing ClientMessage XML document. */ messageDocument: null, /** * Creates a property element in the EchoClientMessage. * * @param componentId the id of the component * @param propertyName the name of the property * @return the created 'property' element */ createPropertyElement: function(componentId, propertyName) { var messagePartElement = EchoClientMessage.getMessagePart("EchoPropertyUpdate"); var propertyElements = messagePartElement.getElementsByTagName("property"); var propertyElement = null; for (var i = 0; i < propertyElements.length; ++i) { if (componentId == propertyElements[i].getAttribute("component-id") && propertyName == propertyElements[i].getAttribute("name")) { propertyElement = propertyElements[i]; break; } } if (!propertyElement) { propertyElement = messagePartElement.ownerDocument.createElement("property"); propertyElement.setAttribute("component-id", componentId); propertyElement.setAttribute("name", propertyName); messagePartElement.appendChild(propertyElement); } return propertyElement; }, /** * Creates or retrieves the "message-part" DOM element from the * EchoClientMessage with the specified processor attribute. * * @param processor a string representing the processor type of * the message-part element to return * @return the "message-part" DOM element */ getMessagePart: function(processor) { // Return existingif available. var messagePartElements = EchoClientMessage.messageDocument.getElementsByTagName("message-part"); for (var i = 0; i < messagePartElements.length; ++i) { if (messagePartElements[i].getAttribute("processor") == processor) { return messagePartElements[i]; } } // Create new if none exists. var messagePartElement = EchoClientMessage.messageDocument.createElement("message-part"); messagePartElement.setAttribute("processor", processor); EchoClientMessage.messageDocument.documentElement.appendChild(messagePartElement); return messagePartElement; }, /** * Retrieves the value of a string property in the EchoClientMessage. * * @param componentId the id of the component. * @param propertyName the name of the property. * @return the value of the property */ getPropertyValue: function(componentId, propertyName) { var messagePartElement = EchoClientMessage.getMessagePart("EchoPropertyUpdate"); var propertyElements = messagePartElement.getElementsByTagName("property"); for (var i = 0; i < propertyElements.length; ++i) { if (componentId == propertyElements[i].getAttribute("component-id") && propertyName == propertyElements[i].getAttribute("name")) { return propertyElements[i].getAttribute("value"); } } return null; }, /** * Removes a property element (if present) from the EchoClientMessage. * * @param componentId the id of the component * @param propertyName the name of the property */ removePropertyElement: function(componentId, propertyName) { var messagePartElement = EchoClientMessage.getMessagePart("EchoPropertyUpdate"); var propertyElements = messagePartElement.getElementsByTagName("property"); var propertyElement = null; for (var i = 0; i < propertyElements.length; ++i) { if (componentId == propertyElements[i].getAttribute("component-id") && propertyName == propertyElements[i].getAttribute("name")) { propertyElement = propertyElements[i]; break; } } if (propertyElement) { messagePartElement.removeChild(propertyElement); } }, /** * Resets the state of the ClientMessage. * This method is invoked after a ClientMessage has been sent to the server * and is thus no longer relevant. */ reset: function() { // Clear existing message document. EchoClientMessage.messageDocument = null; // Create new message document. EchoClientMessage.messageDocument = EchoDomUtil.createDocument("http://www.nextapp.com/products/echo2/climsg", "client-message"); }, /** * Sets the action of the EchoClientMessage. This method should only be * called one time, and the EchoClientMessage should be submitted to the * server soon thereafter. * * @param componentId the id of the component which initiated the action * @param actionName the name of the action * @param actionValue the value of the action (optional) * @return the created 'action' element */ setActionValue: function(componentId, actionName, actionValue) { var messagePartElement = EchoClientMessage.getMessagePart("EchoAction"); var actionElement = messagePartElement.ownerDocument.createElement("action"); actionElement.setAttribute("component-id", componentId); actionElement.setAttribute("name", actionName); if (actionValue !== undefined) { // Only set value if argument is provided. actionElement.setAttribute("value", actionValue); } messagePartElement.appendChild(actionElement); EchoDebugManager.updateClientMessage(); return actionElement; }, /** * Stores the value of a string property in in the EchoClientMessage. * * @param componentId the id of the component * @param propertyName the name of the property * @param newValue the new value of the property */ setPropertyValue: function(componentId, propertyName, newValue) { var propertyElement = EchoClientMessage.createPropertyElement(componentId, propertyName); propertyElement.setAttribute("value", newValue); EchoDebugManager.updateClientMessage(); }, /** * Sets the "type" attribute of the client-message
element to * indicate that this is the initial client message. */ setInitialize: function() { EchoClientMessage.messageDocument.documentElement.setAttribute("type", "initialize"); } }; // ___________________________ // Object EchoClientProperties /** * Static object/namespace providing information about the make/model/version, * capabilities, quirks, and limitations of the browser client. */ EchoClientProperties = { /** * Associative array mapping client property names to values. */ propertyMap: { }, /** * MessagePartProcessor implementation for EchoClientProperties. */ MessageProcessor: { /** * MessagePartProcessor process() method implementation. */ process: function(messagePartElement) { for (var i = 0; i < messagePartElement.childNodes.length; ++i) { if (messagePartElement.childNodes[i].nodeType == 1) { if (messagePartElement.childNodes[i].tagName == "store") { EchoClientProperties.MessageProcessor.processStore(messagePartElement.childNodes[i]); } } } }, /** * Proceses astore
message to store ClientProperties information * received from the server. * * @param storeElement thestore
element to process */ processStore: function(storeElement) { var propertyElements = storeElement.getElementsByTagName("property"); for (var i = 0; i < propertyElements.length; ++i) { var name = propertyElements[i].getAttribute("name"); var value = propertyElements[i].getAttribute("value"); if (value == "true") { value = true; } else if (value == "false") { value = false; } EchoClientProperties.propertyMap[name] = value; } EchoClientEngine.configure(); } }, /** * Returns the specified value from the ClientProperties store. * * @param name the name of the property * @param value the value of the property */ get: function(name) { return EchoClientProperties.propertyMap[name]; } }; // _________________________ // Object EchoCollectionsMap /** * Class which provides associative mapping between keys and values. * Implmentation is based on an associative array. * Array is periodically recreated after a significant number of * element removals to eliminate side effects from memory management * flaws with Internet Explorer. * Null values are not permitted as keys. Setting a key to a null value * will result in the key being removed. */ EchoCollectionsMap = Core.extend({ /** * Creates a new map. */ $construct: function() { this.removeCount = 0; this.garbageCollectionInterval = 250; this.associations = { }; }, /** * Retrieves the value referenced by the spcefied key. * * @param key the key * @return the value, or null if no value is referenced */ get: function(key) { return this.associations[key]; }, /** * Stores a value referenced by the specified key. * * @param key the key * @param value the value */ put: function(key, value) { if (value === null) { this.remove(key); return; } this.associations[key] = value; }, /** * Performs 'garbage-collection' operations, recreating the array. * This operation is necessary due to Internet Explorer memory leak * issues. It will be called automatically--there is no reason to * directly invoke this method. */ garbageCollect: function() { this.removeCount = 0; var newAssociations = { }; var i = 0; for (var key in this.associations) { newAssociations[key] = this.associations[key]; ++i; } this.associations = newAssociations; }, /** * Removes the value referenced by the specified key. * * @param key the key */ remove: function(key) { delete this.associations[key]; ++this.removeCount; if (this.removeCount >= this.garbageCollectionInterval) { this.garbageCollect(); } } }); // __________________ // Object EchoCssUtil /** * Static object/namespace to provide cascading style-sheet (CSS) manipulation * utilities. */ EchoCssUtil = { /** * Object used to determine the actual on-screen pixel bounds * (top/left/width/height) of an element. * Craetes a newBounds
instance. * * @param element the element to analyze */ Bounds: Core.extend({ /** * Creates a newBounds
instance. * * @param element the element to measure */ $construct: function(element) { this.left = 0; this.top = 0; this.width = element.offsetWidth; this.height = element.offsetHeight; while (element != null) { if (element.scrollTop) { this.top -= element.scrollTop; } if (element.scrollLeft) { this.left -= element.scrollLeft; } this.left += element.offsetLeft; this.top += element.offsetTop; element = element.offsetParent; } }, /** * Renders aBounds
object to a human-readable debug string. */ toString: function() { return "(" + this.left + ", " + this.top + ") [" + this.width + "x" + this.height + "]"; } }), /** * Adds a rule to the document stylesheet. * This method does not function in Safari/KHTML. * * @param selectorText the selector * @param style the style information */ addRule: function(selectorText, style) { var ss = document.styleSheets[0]; if (ss.insertRule) { // W3C DOM Browsers. ss.insertRule(selectorText + " {" + style + "}", ss.cssRules.length); } else if (ss.addRule) { // IE6. ss.addRule(selectorText, style); } }, /** * Updates CSS information about a specific element. * * @param element the DOM element to update * @param cssText the CSS style text describing the updates to perform to * the existing CSS information of the element */ applyStyle: function(element, cssText) { var styleProperties = cssText.split(";"); for (var i = 0; i < styleProperties.length; ++i) { var separatorIndex = styleProperties[i].indexOf(":"); if (separatorIndex == -1) { continue; } var attributeName = styleProperties[i].substring(0, separatorIndex); var propertyName = EchoDomUtil.cssAttributeNameToPropertyName(attributeName); var propertyValue = styleProperties[i].substring(separatorIndex + 1); element.style[propertyName] = propertyValue; } }, /** * Temporarily adjusts the CSS style of an element. The 'original' style will * be stored in a DomPropertyStore attribute of the element (if it has not been * already) before the new style is applied. This method is useful for applying * selection/rollover states temporarily to elements. Note that there may * only be one 'original style' set on a particular element. * * @param element the DOM element to update * @param cssStyleText the CSS style text to temporarily apply to the element * (viaapplyStyle
) */ applyTemporaryStyle: function(element, cssStyleText) { // Set original style if not already set. if (!EchoDomPropertyStore.getPropertyValue(element, "EchoCssUtil.originalStyle")) { if (EchoDomUtil.getCssText(element)) { EchoDomPropertyStore.setPropertyValue(element, "EchoCssUtil.originalStyle", EchoDomUtil.getCssText(element)); } else { // Flag that no original CSS text existed. EchoDomPropertyStore.setPropertyValue(element, "EchoCssUtil.originalStyle", "-"); } } // Apply new style. EchoCssUtil.applyStyle(element, cssStyleText); }, /** * Removes a rule from the document stylesheet. * This method does not function in Safari/KHTML. * * @param selectorText the selector */ removeRule: function(selectorText) { selectorText = selectorText.toLowerCase(); var ss = document.styleSheets[0]; // Retrieve rules object for W3C DOM : IE6. var rules = ss.cssRules ? ss.cssRules : ss.rules; for (var i = 0; i < rules.length; ++i) { if (rules[i].type == 1 && rules[i].selectorText.toLowerCase() == selectorText) { if (ss.deleteRule) { // Delete rule: W3C DOM. ss.deleteRule(i); break; } else if (ss.removeRule) { // Delete rule: IE6. ss.deleteRule(i); break; } else { alert("rem fail"); } } } }, /** * Restores the original style to an element whose style was updated via *applyTemporaryStyle
. Removes the 'original' style from the * DomPropertyStore attribute of the element. * * @param element the DOM element to restore to its original state */ restoreOriginalStyle: function(element) { // Obtain original style. var originalStyle = EchoDomPropertyStore.getPropertyValue(element, "EchoCssUtil.originalStyle"); // Do nothing if no original style exists. if (!originalStyle) { return; } // Reset original style on element. EchoDomUtil.setCssText(element, originalStyle == "-" ? "" : originalStyle); // Clear original style from EchoDomPropertyStore. EchoDomPropertyStore.setPropertyValue(element, "EchoCssUtil.originalStyle", null); } }; // _______________________ // Object EchoDebugManager /** * Static object/namespace for managing the Debug Window. */ EchoDebugManager = { /** * Reference to the active debug window. */ debugWindow: null, /** * Writes a message to the console. * * @param message the message to write */ consoleWrite: function(message) { if (!EchoDebugManager.debugWindow || EchoDebugManager.debugWindow.closed) { return; } EchoDebugManager.debugWindow.EchoDebug.Console.write(message); }, /** * Opens the debug window. */ launch: function() { EchoDebugManager.debugWindow = window.open(EchoClientEngine.baseServerUri + "?serviceId=Echo.Debug", "EchoDebug", "width=400,height=650,resizable=yes", true); }, /** * Updates the current state of the ClientMessage in the synchronization tab. */ updateClientMessage: function() { if (!EchoDebugManager.debugWindow || EchoDebugManager.debugWindow.closed) { return; } EchoDebugManager.debugWindow.EchoDebug.Sync.displayClientMessage(EchoClientMessage.messageDocument); }, /** * Updates the current state of the ServerMessage in the synchronization tab. */ updateServerMessage: function() { if (!EchoDebugManager.debugWindow || EchoDebugManager.debugWindow.closed) { return; } EchoDebugManager.debugWindow.EchoDebug.Sync.displayServerMessage(EchoServerMessage.messageDocument); } }; // ___________________________ // Object EchoDomPropertyStore /** * Static object/namespace which provides storage of non-rendered property values * relevant to specific DOM elements within the DOM elmenets themselves. * Values are stored within associative arrays that are stored in a * 'echoDomPropertyStore' property of a DOM element. */ EchoDomPropertyStore = { /** * Static object/namespace for EchoDomPropertyStore MessageProcessor * implementation. Provides capability to set EchoDomPropertyStore * properties from ServerMessage directives. */ MessageProcessor: { /** * MessageProcessor process() implementation * (invoked by ServerMessage processor). * * @param messagePartElement themessage-part
element to process */ process: function(messagePartElement) { for (var i = 0; i < messagePartElement.childNodes.length; ++i) { if (messagePartElement.childNodes[i].nodeType == 1) { if (messagePartElement.childNodes[i].tagName == "store-property") { EchoDomPropertyStore.MessageProcessor.processStoreProperty(messagePartElement.childNodes[i]); } } } }, /** * Processes astore-property
message to set the state of an *EchoDomPropertyStore
property. * * @param storePropertyElement thestore-property
element to process */ processStoreProperty: function(storePropertyElement) { var propertyName = storePropertyElement.getAttribute("name"); var propertyValue = storePropertyElement.getAttribute("value"); var items = storePropertyElement.getElementsByTagName("item"); for (var i = 0; i < items.length; ++i) { var elementId = items[i].getAttribute("eid"); EchoDomPropertyStore.setPropertyValue(elementId, propertyName, propertyValue); } } }, /** * Disposes of anyEchoDomPropertyStore
properties stored for a * specific element. * * @param element the element or element id on which all properties should * be removed */ dispose: function(element) { if (typeof element == "string") { element = document.getElementById(element); } if (!element) { throw new Error("Element not found."); } if (element.echoDomPropertyStore) { for (var elementProperty in element.echoDomPropertyStore) { delete element.echoDomPropertyStore[elementProperty]; } } element.echoDomPropertyStore = undefined; }, /** * Destorys EchoDomPropertyStore instances for all elements in document. */ disposeAll: function() { EchoDomPropertyStore.disposeAllRecurse(document.documentElement); }, /** * Recursive implementation of disposeAll(). * Disposes of an element's property store (if applicable) and * recursively invokes on child elements. * * @param element the elemnt to dispose */ disposeAllRecurse: function(element) { if (element.echoDomPropertyStore) { for (var elementProperty in element.echoDomPropertyStore) { delete element.echoDomPropertyStore[elementProperty]; } } element.echoDomPropertyStore = undefined; for (var childElement = element.firstChild; childElement; childElement = childElement.nextSibling) { if (childElement.nodeType == 1) { EchoDomPropertyStore.disposeAllRecurse(childElement); } } }, /** * Retrieves the value of a specificEchoDomPropertyStore
property. * * @param element the element or element id on which to set the property * @param propertyName the name of the property * @return the property value, or null if it is not set */ getPropertyValue: function(element, propertyName) { if (typeof element == "string") { element = document.getElementById(element); } if (!element) { return null; } if (element.echoDomPropertyStore) { return element.echoDomPropertyStore[propertyName]; } else { return null; } }, /** * Sets the value of a specificEchoDomPropertyStore
property. * * @param element the element or element id on which to set the property * @param propertyName the name of the property * @param propertyValue the new value of the property */ setPropertyValue: function(element, propertyName, propertyValue) { if (typeof element == "string") { element = document.getElementById(element); } if (!element) { return; } if (!element.echoDomPropertyStore) { element.echoDomPropertyStore = { }; } element.echoDomPropertyStore[propertyName] = propertyValue; } }; // ____________________ // Object EchoDomUpdate /** * Static object/namespace for performing server-directed updates to the * client DOM. */ EchoDomUpdate = { /** * Static object/namespace for EchoDomUpdate MessageProcessor * implementation. */ MessageProcessor: { /** * MessageProcessor process() implementation * (invoked by ServerMessage processor). * * @param messagePartElement themessage-part
element to process */ process: function(messagePartElement) { for (var i = 0; i < messagePartElement.childNodes.length; ++i) { if (messagePartElement.childNodes[i].nodeType == 1) { switch (messagePartElement.childNodes[i].tagName) { case "dom-add": this.processAdd(messagePartElement.childNodes[i]); break; case "dom-remove": this.processRemove(messagePartElement.childNodes[i]); break; case "dom-remove-children": this.processRemoveChildren(messagePartElement.childNodes[i]); break; case "attribute-update": this.processAttributeUpdate(messagePartElement.childNodes[i]); break; case "style-update": this.processStyleUpdate(messagePartElement.childNodes[i]); break; case "stylesheet-add-rule": this.processStyleSheetAddRule(messagePartElement.childNodes[i]); break; case "stylesheet-remove-rule": this.processStyleSheetRemoveRule(messagePartElement.childNodes[i]); break; } } } }, /** * Processes adom-add
directive to append or insert new nodes into * the DOM. * * @param domAddElement thedom-add
element to process */ processAdd: function(domAddElement) { var contentElements = domAddElement.getElementsByTagName("content"); for (var i = 0; i < contentElements.length; ++i) { var parentId = contentElements[i].getAttribute("parent-id"); var siblingId = contentElements[i].getAttribute("sibling-id"); var parentElement = document.getElementById(parentId); var siblingElement = null; if (siblingId) { siblingElement = document.getElementById(siblingId); if (!siblingElement) { throw new EchoDomUpdate.TargetNotFoundException("Add", "sibling", siblingId); } } if (!parentElement) { throw new EchoDomUpdate.TargetNotFoundException("Add", "parent", parentId); } for (var j = 0; j < contentElements[i].childNodes.length; ++j) { var importedNode = EchoDomUtil.importNode(parentElement.ownerDocument, contentElements[i].childNodes[j], true); var newElementId = importedNode.getAttribute("id"); if (!newElementId) { throw "Cannot add element without id attribute."; } if (document.getElementById(newElementId)) { throw "Element " + newElementId + " already exists in document; cannot add."; } if (siblingElement) { // If sibling specified, perform insert operation. parentElement.insertBefore(importedNode, siblingElement); } else { // Perform append operation. parentElement.appendChild(importedNode); } } } }, /** * Processes anattribute-update
directive to update an attribute * of a DOM element. * * @param attributeUpdateElement theattribute-update
element to * process */ processAttributeUpdate: function(attributeUpdateElement) { var targetId = attributeUpdateElement.getAttribute("target-id"); var targetElement = document.getElementById(targetId); if (!targetElement) { throw new EchoDomUpdate.TargetNotFoundException("AttributeUpdate", "target", targetId); } targetElement[attributeUpdateElement.getAttribute("name")] = attributeUpdateElement.getAttribute("value"); }, /** * Processes adom-remove
directive to delete nodes from the DOM. * * @param domRemoveElement thedom-remove
element to process */ processRemove: function(domRemoveElement) { var targetId = domRemoveElement.getAttribute("target-id"); var targetElement = document.getElementById(targetId); if (!targetElement) { return; } targetElement.parentNode.removeChild(targetElement); }, /** * Processes adom-remove-children
directive to delete all child * nodes from a specific DOM element. * * @param domRemoveChildrenElement thedom-remove-children
element * to process */ processRemoveChildren: function(domRemoveElement) { var targetId = domRemoveElement.getAttribute("target-id"); var targetElement = document.getElementById(targetId); if (!targetElement) { return; } var childNode = targetElement.firstChild; while (childNode) { var nextChildNode = childNode.nextSibling; targetElement.removeChild(childNode); childNode = nextChildNode; } }, /** * Processes astylesheet-add-rule
directive to add a new CSS style sheet rule. * * @param addRuleElement thestylesheet-add-rule
element to process */ processStyleSheetAddRule: function(addRuleElement) { var selectorText = addRuleElement.getAttribute("selector"); var style = addRuleElement.getAttribute("style"); EchoCssUtil.addRule(selectorText, style); }, /** * Processes astylesheet-add-remove
directive to remove a new CSS style sheet rule. * * @param removeRuleElement thestylesheet-remove-rule
element to process */ processStyleSheetRemoveRule: function(removeRuleElement) { var selectorText = removeRuleElement.getAttribute("selector"); EchoCssUtil.removeRule(selectorText); }, /** * Processes astyle-update
directive to update an the CSS style * of a DOM element. * * @param styleUpdateElement thestyle-update
element to * process */ processStyleUpdate: function(styleUpdateElement) { var targetId = styleUpdateElement.getAttribute("target-id"); var targetElement = document.getElementById(targetId); if (!targetElement) { throw new EchoDomUpdate.TargetNotFoundException("StyleUpdate", "target", targetId); } targetElement.style[styleUpdateElement.getAttribute("name")] = styleUpdateElement.getAttribute("value"); } }, /** * An exception describing a failure of an EchoDomUpdate operation due to a * target node not existing. */ TargetNotFoundException: Core.extend({ /** * Creates a newTargetNotFoundException
. * * @param updateType the type of update, e.g., DomRemove or StyleUpdate * @param targetType the type of target that caused the failure * @param targetId the id of the target */ $construct: function(updateType, targetType, targetId) { this.targetId = targetId; this.targetType = targetType; this.updateType = updateType; }, /** * Renders a String representation of the exception. * * @return the String representation */ toString: function() { return "Failed to perform \"" + this.updateType + "\": " + this.targetType + " element \"" + this.targetId + "\" not found."; } }) }; // __________________ // Object EchoDomUtil /** * Static object/namespace for performing cross-platform DOM-related * operations. Most methods in this object/namespace are provided due * to nonstandard behavior in clients. */ EchoDomUtil = { /** * An associative array which maps between HTML attributes and HTMLElement * property names. These values generally match, but multiword attribute * names tend to have "camelCase" property names, e.g., the * "cellspacing" attribute corresponds to the "cellSpacing" property. * This map is required by the Internet Explorer-specific importNode() * implementation */ attributeToPropertyMap: { accesskey: "accessKey", cellpadding: "cellPadding", cellspacing: "cellSpacing", "class": "className", codebase: "codeBase", codetype: "codeType", colspan: "colSpan", datetime: "dateTime", frameborder: "frameBorder", longdesc: "longDesc", marginheight: "marginHeight", marginwidth: "marginWidth", maxlength: "maxLength", noresize: "noResize", noshade: "noShade", nowrap: "noWrap", readonly: "readOnly", rowspan: "rowSpan", tabindex: "tabIndex", usemap: "useMap", valign: "vAlign", valueType: "valueType" }, /** * Adds an event listener to an object, using the client's supported event * model. * * @param eventSource the event source * @param eventType the type of event (the 'on' prefix should NOT be included * in the event type, i.e., for mouse rollover events, "mouseover" would * be specified instead of "onmouseover") * @param eventListener the event listener to be invoked when the event occurs * @param useCapture a flag indicating whether the event listener should capture * events in the final phase of propagation (only supported by * DOM Level 2 event model) */ addEventListener: function(eventSource, eventType, eventListener, useCapture) { if (eventSource.addEventListener) { eventSource.addEventListener(eventType, eventListener, useCapture); } else if (eventSource.attachEvent) { eventSource.attachEvent("on" + eventType, eventListener); } }, /** * Creates a new XML DOM. * * @param namespaceUri the unique URI of the namespace of the root element in * the created document (not supported for * Internet Explorer 6 clients, null may be specified for all clients) * @param qualifiedName the name of the root element of the new document (this * element will be created automatically) * @return the created DOM */ createDocument: function(namespaceUri, qualifiedName) { if (document.implementation && document.implementation.createDocument) { // DOM Level 2 Browsers var dom; if (EchoClientProperties.get("browserMozillaFirefox") && EchoClientProperties.get("browserVersionMajor") == 3 && EchoClientProperties.get("browserVersionMinor") === 0) { // https://bugzilla.mozilla.org/show_bug.cgi?id=431701 dom = new DOMParser().parseFromString("<" + qualifiedName + "/>", "application/xml"); } else { dom = document.implementation.createDocument(namespaceUri, qualifiedName, null); } return dom; } else if (window.ActiveXObject) { // Internet Explorer var createdDocument = new ActiveXObject("Microsoft.XMLDOM"); var documentElement = createdDocument.createElement(qualifiedName); createdDocument.appendChild(documentElement); return createdDocument; } else { throw "Unable to create new Document."; } }, /** * Converts a hyphen-separated CSS attribute name into a camelCase * property name. * * @param attribute the CSS attribute name, e.g., border-color * @return the style property name, e.g., borderColor */ cssAttributeNameToPropertyName: function(attribute) { var segments = attribute.split("-"); var out = segments[0]; for (var i = 1; i < segments.length; ++i) { out += segments[i].substring(0, 1).toUpperCase(); out += segments[i].substring(1); } return out; }, /** * Escapes the text if this browser has a quirk where it does not properly escape * the out going XML. */ escapeText: function(text) { text = text.replace(/&/g, "&"); text = text.replace(/targetsrcElement
property of the event is returned. * * @param e the event * @return the target */ getEventTarget: function(e) { return e.target ? e.target : e.srcElement; }, /** * Imports a node into a document. This method is a * cross-browser replacement for DOMImplementation.importNode, as Internet * Explorer 6 clients do not provide such a method. * This method will directly invoke targetDocument.importNode() in the event * that a client provides such a method. * * @param targetDocument the document into which the node/hierarchy is to be * imported * @param sourceNode the node to import * @param importChildren a boolean flag indicating whether child nodes should * be recursively imported */ importNode: function(targetDocument, sourceNode, importChildren) { if (targetDocument.importNode) { // DOM Level 2 Browsers return targetDocument.importNode(sourceNode, importChildren); } else { // Internet Explorer Browsers return EchoDomUtil.importNodeImpl(targetDocument, sourceNode, importChildren); } }, /** * Manual implementation of DOMImplementation.importNode() for clients that do * not provide their own (i.e., Internet Explorer 6). * * @param targetDocument the document into which the node/hierarchy is to be * imported * @param sourceNode the node to import * @param importChildren a boolean flag indicating whether child nodes should * be recursively imported */ importNodeImpl: function(targetDocument, sourceNode, importChildren) { var targetNode; if (importChildren || !sourceNode.hasChildNodes()) { // Attempt to use innerHTML to import node. // This approach cannot be used if importing children is not desired and // the element has children. targetNode = EchoDomUtil.importNodeByInnerHtml(targetDocument, sourceNode, importChildren); if (targetNode != null) { // Return created node if successfully imported, otherwise continue. return targetNode; } } // Import single node (but not its children). targetNode = EchoDomUtil.importNodeByDom(targetDocument, sourceNode); if (importChildren && sourceNode.hasChildNodes()) { // Recurse to import children. for (var sourceChildNode = sourceNode.firstChild; sourceChildNode; sourceChildNode = sourceChildNode.nextSibling) { var targetChildNode = EchoDomUtil.importNodeImpl(targetDocument, sourceChildNode, true); if (targetChildNode) { targetNode.appendChild(targetChildNode); } } } return targetNode; }, /** * Import a node by re-creating it using DOM methods. * This method will not import child elements. * * @param targetDocument the document into which the node/hierarchy is to be * imported * @param sourceNode the node to import */ importNodeByDom: function(targetDocument, sourceNode) { var targetNode, i; switch (sourceNode.nodeType) { case 1: targetNode = targetDocument.createElement(sourceNode.nodeName); for (i = 0; i < sourceNode.attributes.length; ++i) { var attribute = sourceNode.attributes[i]; if ("style" == attribute.name) { targetNode.style.cssText = attribute.value; } else { var propertyName = EchoDomUtil.attributeToPropertyMap[attribute.name]; if (propertyName) { targetNode[propertyName] = attribute.value; } else { targetNode[attribute.name] = attribute.value; } } } break; case 3: targetNode = targetDocument.createTextNode(sourceNode.nodeValue); break; } return targetNode; }, /** * Try importing a node using innerHTML, on IE6 this is much faster than manually * traversing the DOM-tree. * * @param targetDocument the document into which the node/hierarchy is to be * imported * @param sourceNode the node to import */ importNodeByInnerHtml: function(targetDocument, sourceNode) { var targetNode; if (sourceNode.nodeName.match(/tr|tbody|table|thead|tfoot|colgroup/)) { // The innerHTML-property is read-only for the following objects: // COL, COLGROUP, FRAMESET, HTML, STYLE, TABLE, TBODY, TFOOT, THEAD, TITLE, TR // so we have to import nodes of such types using DOM-methods. // see: http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/innerhtml.asp return null; } //Get the html of the sourcenode as a string. var sourceHTML = ""; for (var sourceChildNode = sourceNode.firstChild; sourceChildNode; sourceChildNode = sourceChildNode.nextSibling) { //We are importing an xml-node. var childHTML = sourceChildNode.xml; if (childHTML == null) { return null; } sourceHTML += childHTML; } // Import the topnode targetNode = EchoDomUtil.importNodeByDom(targetDocument, sourceNode); // IE fails to recognizeas a closed tag when using innerHTML, so replace it with // except for br, img, and hr elements. var expr = new RegExp("<(?:(?!br|img|hr)([a-zA-Z]+))([^>]*)/>", "ig"); sourceHTML = sourceHTML.replace(expr, "<$1$2>$1>"); if (EchoStringUtil.trim(sourceHTML).length > 0) { // Adding an empty string throws an 'Unknown error'. targetNode.innerHTML = sourceHTML; } return targetNode; }, /** * Determines if ancestorNode
is or is an ancestor of *descendantNode
. * * @param ancestorNode the potential ancestor node * @param descendantNode the potential descendant node * @return true ifancestorNode
is or is an ancestor of *descendantNode
*/ isAncestorOf: function(ancestorNode, descendantNode) { var testNode = descendantNode; while (testNode !== null) { if (testNode == ancestorNode) { return true; } testNode = testNode.parentNode; } return false; }, /** * Prevents the default action of an event from occurring, using the * client's supported event model. * On clients which support the W3C DOM Level 2 event specification, * the preventDefault() method of the event is invoked. * On clients which support only the Internet Explorer event model, * the 'returnValue' property of the event is set to false. * * @param e the event */ preventEventDefault: function(e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } }, /** * Removes an event listener from an object, using the client's supported * event model. * * @param eventSource the event source * @param eventType the type of event (the 'on' prefix should NOT be included * in the event type, i.e., for mouse rollover events, "mouseover" would * be specified instead of "onmouseover") * @param eventListener the event listener to be invoked when the event occurs * @param useCapture a flag indicating whether the event listener should capture * events in the final phase of propagation (only supported by * DOM Level 2 event model) */ removeEventListener: function(eventSource, eventType, eventListener, useCapture) { if (eventSource.removeEventListener) { eventSource.removeEventListener(eventType, eventListener, useCapture); } else if (eventSource.detachEvent) { eventSource.detachEvent("on" + eventType, eventListener); } }, /** * Cross-platform method to set the CSS text of a DOM element. * * @param element the element * @param cssText the new CSS text */ setCssText: function(element, cssText) { if (EchoClientProperties.get("quirkOperaNoCssText")) { if (cssText) { element.setAttribute("style", cssText); } else { element.removeAttribute("style"); } } else { element.style.cssText = cssText; } }, /** * Stops an event from propagating ("bubbling") to parent nodes in the DOM, * using the client's supported event model. * On clients which support the W3C DOM Level 2 event specification, * the stopPropagation() method of the event is invoked. * On clients which support only the Internet Explorer event model, * the 'cancelBubble' property of the event is set to true. * * @param e the event */ stopPropagation: function(e) { if (e.stopPropagation) { e.stopPropagation(); } else { e.cancelBubble = true; } } }; // _________________________ // Object EchoEventProcessor /** * Static object/namespace providing higher-level event management. * Echo2 client-side components may optionally register, unregister, * and receive events using the EchoEventProcessor. * * Event handlers registered with the EchoEventProcessor will result * in additional information being sent when events are fired. * This information includes the DOM element on which the * event was actually registered (the "registeredTarget" property) as * well as the target that fired the event (the "target" property). * * Event handlers should return true if they wish for the event to continue * propagating. Returning false (or nothing at all) will result in the * event not being fired to additional handlers. * * Events may be registered with "capture" mode enabled. Capturing event * handlers will receive notification of an event on a child control before * the child (whereas non-capturing handlers will receive notification * after the child element does). This behavior works as described here even * on Internet Explorer browsers which lack support for the W3C Event Model and * its similar capability of event capturing. * * Additionally, use of the EchoEventProcessor minimizes "DHTML memory * leaks" and provides a mechanism for inspecting registered event handlers * using the Debug Window. */ EchoEventProcessor = { /** * Static object/namespace for EchoEventProcessor MessageProcessor * implementation. */ MessageProcessor: { /** * MessageProcessor process() implementation * (invoked by ServerMessage processor). * * @param messagePartElement themessage-part
element to process */ process: function(messagePartElement) { var i, j, k, eventTypes, handlers, addItems, removeItems, elementId; var adds = messagePartElement.getElementsByTagName("event-add"); var removes = messagePartElement.getElementsByTagName("event-remove"); for (i = 0; i < adds.length; ++i) { eventTypes = adds[i].getAttribute("type").split(","); handlers = adds[i].getAttribute("handler").split(","); if (handlers.length != eventTypes.length) { throw "Handler count and event type count do not match."; } addItems = adds[i].getElementsByTagName("item"); for (j = 0; j < addItems.length; ++j) { elementId = addItems[j].getAttribute("eid"); for (k = 0; k < eventTypes.length; ++k) { EchoEventProcessor.addHandler(elementId, eventTypes[k], handlers[k]); } } } for (i = 0; i < removes.length; ++i) { eventTypes = removes[i].getAttribute("type").split(","); removeItems = removes[i].getElementsByTagName("item"); for (j = 0; j < removeItems.length; ++j) { elementId = removeItems[j].getAttribute("eid"); for (k = 0; k < eventTypes.length; ++k) { EchoEventProcessor.removeHandler(elementId, eventTypes[k]); } } } } }, /** * An associative array mapping event types (e.g. "mouseover") to * associate arrays that map DOM element ids to the String names * of event handlers. */ eventTypeToHandlersMap: new EchoCollectionsMap(), /** * Registers an event handler. * * @param element the target element or the id of the target element on which * to register the event * @param eventType the type of the event (should be specified using * DOM level 2 event names, e.g., "mouseover" or "click", without * the "on" prefix) * @param handler the name of the handler, as a String * @param capture flag indicating whether listener should be registered as a * capturing event handler (NOTE: this flag operates as expected even * on IE browsers) */ addHandler: function(element, eventType, handler, capture) { var elementId; if (typeof element == "string") { elementId = element; element = document.getElementById(element); } else { elementId = element.id; } EchoDomUtil.addEventListener(element, eventType, EchoEventProcessor.processEvent, false); var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType); if (!elementIdToHandlerMap) { elementIdToHandlerMap = new EchoCollectionsMap(); EchoEventProcessor.eventTypeToHandlersMap.put(eventType, elementIdToHandlerMap); } var handlerData = new EchoEventProcessor.EventHandlerData(handler, capture); elementIdToHandlerMap.put(elementId, handlerData); }, /** * Disposes the EchoEventProcessor, releasing all resources. * This method is invoked when the client engine is disposed. */ dispose: function() { for (var eventType in EchoEventProcessor.eventTypeToHandlersMap.associations) { var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.associations[eventType]; for (var elementId in elementIdToHandlerMap.associations) { var element = document.getElementById(elementId); if (element) { EchoDomUtil.removeEventListener(element, eventType, EchoEventProcessor.processEvent, false); } elementIdToHandlerMap.remove(elementId); } EchoEventProcessor.eventTypeToHandlersMap.remove(eventType); } }, /** * Retrieves the event handler of a specific type for a specific element. * * @param eventType the type of the event (should be specified using * DOM level 2 event names, e.g., "mouseover" or "click", without * the "on" prefix) * @param elementId the elementId the id of the DOM element * @return the event handler name */ getHandler: function(eventType, elementId) { var handlerData = EchoEventProcessor.getHandlerData(eventType, elementId); return handlerData ? handlerData.handlerName : null; }, /** * Retrieves the event handler data object of a specific type for a specific element. * * @param eventType the type of the event (should be specified using * DOM level 2 event names, e.g., "mouseover" or "click", without * the "on" prefix) * @param elementId the elementId the id of the DOM element */ getHandlerData: function(eventType, elementId) { var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType); if (!elementIdToHandlerMap) { return null; } return elementIdToHandlerMap.get(elementId); }, /** * Returns the element ids of all event handlers of the specified type. */ getHandlerElementIds: function(eventType) { var elementIds = []; var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType); if (elementIdToHandlerMap) { for (var elementId in elementIdToHandlerMap.associations) { elementIds.push(elementId); } } return elementIds; }, /** * Returns the types of all registered event handlers. * * @return an array of Strings containing the type names of all registered * event handlers */ getHandlerEventTypes: function() { var handlerTypes = []; for (var eventType in EchoEventProcessor.eventTypeToHandlersMap.associations) { handlerTypes.push(eventType); } return handlerTypes; }, /** * Master event handler for all DOM events. * This method is registered as the event handler for all * EchoEventProcessor event registrations. Events are processed, * modified, and then dispatched to the appropriate * Echo event handlers by this method. * * @param e the event fired by the DOM */ processEvent: function(e) { var i, handler; e = e ? e : window.event; var eventType = e.type; if (!e.target && e.srcElement) { // The Internet Explorer event model stores the target element in the 'srcElement' property of an event. // Modify the event such the target is retrievable using the W3C DOM Level 2 specified property 'target'. e.target = e.srcElement; } var targetElement = e.target; var handlerDatas = []; var targetElements = []; var hasCapturingHandlers = false; while (targetElement) { if (targetElement.nodeType == 1) { // Element Node var handlerData = EchoEventProcessor.getHandlerData(eventType, targetElement.id); if (handlerData) { hasCapturingHandlers |= handlerData.capture; handlerDatas.push(handlerData); targetElements.push(targetElement); } } targetElement = targetElement.parentNode; } if (handlerDatas.length === 0) { return; } var propagate = true; if (hasCapturingHandlers) { // Process capturing event handlers. for (i = handlerDatas.length - 1; i >=0; --i) { if (!handlerDatas[i].capture) { // Ignore non-capturing handlers. continue; } // Load handler. try { handler = eval(handlerDatas[i].handlerName); } catch (ex1) { throw "Invalid handler: " + handlerDatas[i].handlerName + " (" + ex1 + ")"; } // Set registered target on event. e.registeredTarget = targetElements[i]; // Invoke handler. propagate = handler(e); // Stop propagation if required. if (!propagate) { break; } } } if (propagate) { // Process non-capturing event handlers. for (i = 0; i < handlerDatas.length; ++i) { if (handlerDatas[i].capture) { // Ignore capturing handlers. continue; } // Load handler. try { handler = eval(handlerDatas[i].handlerName); } catch (ex2) { throw "Invalid handler: " + handlerDatas[i].handlerName + " (" + ex2 + ")"; } // Set registered target on event. e.registeredTarget = targetElements[i]; // Invoke handler. propagate = handler(e); // Stop propagation if required. if (!propagate) { break; } } } if (!propagate) { // Stop propagation of event. EchoDomUtil.stopPropagation(e); } }, /** * Unregisters an event handler. * * @param element the target element or the id of the target element on which * to register the event * @param eventType the type of the event (should be specified using * DOM level 2 event names, e.g., "mouseover" or "click", without * the "on" prefix) */ removeHandler: function(element, eventType) { var elementId; if (typeof element == "string") { elementId = element; element = document.getElementById(element); } else { elementId = element.id; } if (element) { EchoDomUtil.removeEventListener(element, eventType, EchoEventProcessor.processEvent, false); } var elementIdToHandlerMap = EchoEventProcessor.eventTypeToHandlersMap.get(eventType); if (!elementIdToHandlerMap) { return; } elementIdToHandlerMap.remove(elementId); } }; /** * Data object describing an event handler. * * @param handlerName the name of the event handler * @param capture flag indicating whether the handler is a capturing listener */ EchoEventProcessor.EventHandlerData = function(handlerName, capture) { this.handlerName = handlerName; this.capture = capture; }; // _______________________ // Object EchoFocusManager /** * Static object/namespace to manage component focus. */ EchoFocusManager = { /** * Sets the focused state of a component. * * @param componentId the identifier of the component * @param focusState a boolean property describing whether the component is * focused */ setFocusedState: function(componentId, focusState) { if (focusState) { EchoClientMessage.messageDocument.documentElement.setAttribute("focus", componentId); } else { var focusedComponentId = EchoClientMessage.messageDocument.documentElement.getAttribute("focus"); if (!focusedComponentId || componentId == focusedComponentId) { // Set focus state to indeterminate if either no focused componentId is set // or if focused componentId matches specified componentId. EchoClientMessage.messageDocument.documentElement.setAttribute("focus", ""); } } EchoDebugManager.updateClientMessage(); } }; // _________________________ // Object EchoHttpConnection EchoHttpConnection = Core.extend({ $static: { nullMethod: function() { } }, /** * Creates a newEchoHttpConnection
. * This method simply configures the connection, the connection * will not be opened untilconnect()
is invoked. * The responseHandler method property of the connection must be set * before the connection is executed. * An invalidResponseHandler method property may optionally be set to * be invoked if an invalid response is received. * * @param url the target URL * @param method the connection method, i.e., GET or POST * @param messageObject the message to send (may be a String or XML DOM) * @param contentType the request content-type */ $construct: function(url, method, messageObject, contentType) { this.url = url; this.contentType = contentType; this.method = method; this.messageObject = messageObject; this.responseHandler = null; this.invalidResponseHandler = null; this.disposed = false; }, /** * Executes the HTTP connection. * The responseHandler property of the connection must be set * before the connection is executed. * This method will return asynchronously; theresponseHandler
* callback property will be invoked when a valid response is received. */ connect: function() { if (!this.responseHandler) { throw "EchoHttpConnection response handler not set."; } var usingActiveXObject = false; if (window.XMLHttpRequest) { this.xmlHttpRequest = new XMLHttpRequest(); } else if (window.ActiveXObject) { usingActiveXObject = true; this.xmlHttpRequest = new ActiveXObject("Microsoft.XMLHTTP"); } else { throw "Connect failed: Cannot create XMLHttpRequest."; } var instance = this; this.xmlHttpRequest.onreadystatechange = function() { if (!instance) { return; } try { instance.processReadyStateChange(); } finally { if (instance.disposed) { // Release instance reference to allow garbage collection. instance = null; } } }; this.xmlHttpRequest.open(this.method, this.url, true); if (this.contentType && (usingActiveXObject || this.xmlHttpRequest.setRequestHeader)) { this.xmlHttpRequest.setRequestHeader("Content-Type", this.contentType); } this.xmlHttpRequest.send(this.messageObject ? this.messageObject : null); }, dispose: function() { this.messageObject = null; this.responseHandler = null; this.invalidResponseHandler = null; this.xmlHttpRequest = null; this.disposed = true; }, /** * Returns the response as text. * This method may only be invoked from a response handler. * * @return the response, as text */ getResponseText: function() { return this.xmlHttpRequest ? this.xmlHttpRequest.responseText : null; }, /** * Returns the response as an XML DOM. * This method may only be invoked from a response handler. * * @return the response, as an XML DOM */ getResponseXml: function() { return this.xmlHttpRequest ? this.xmlHttpRequest.responseXML : null; }, /** * Event listener forreadystatechange
events received from * theXMLHttpRequest
. */ processReadyStateChange: function() { if (this.disposed) { return; } if (this.xmlHttpRequest.readyState == 4) { if (this.xmlHttpRequest.status == 200) { if (!this.responseHandler) { this.dispose(); throw "EchoHttpConnection response handler not set."; } this.responseHandler(this); this.dispose(); } else { if (this.invalidResponseHandler) { this.invalidResponseHandler(this); this.dispose(); } else { var statusValue = this.xmlHttpRequest.status; this.dispose(); throw "Invalid HTTP Response code (" + statusValue + ") and no handler set."; } } } } }); // _______________________ // Object EchoModalManager /** * Manages the modal state of the application. */ EchoModalManager = { /** * Element id of current modal object, or null if no object is currently * modal. */ modalElementId: null, /** * Determines if a particular element lies within the modal context. * * @param element the element or id of the element to analyze * @return true if the specifiedelement
is within the modal * context */ isElementInModalContext: function(element) { if (typeof element == "string") { element = document.getElementById(element); } if (EchoModalManager.modalElementId) { var modalElement = document.getElementById(EchoModalManager.modalElementId); return EchoDomUtil.isAncestorOf(modalElement, element); } else { return true; } } }; // _______________________________ // Object EchoScriptLibraryManager /** * Static object/namespace to manage dynamic loading of client libraries. */ EchoScriptLibraryManager = { /** * Library load-state constant indicating a library has been requested from * the server. */ STATE_REQUESTED: 1, /** * Library load-state constant indicating a library has been successfully * retrieved from the server. */ STATE_LOADED: 2, /** * Library load-state constant indicating a library has been successfully * installed. */ STATE_INSTALLED: 3, /** * Associative array mapping library service ids to library source code. */ librarySourceMap: { }, /** * Associative array mapping library service ids to load-states. */ libraryLoadStateMap: { }, /** * Queries the state of the specified libary. * * @param serviceId the server service identifier of the library * @return the load-state of the library, either *STATE_REQUESTED
orSTATE_LOADED
*/ getState: function(serviceId) { return EchoScriptLibraryManager.libraryLoadStateMap[serviceId]; }, /** * Installs a previously loaded JavaScript library. * * @param serviceId the server service identifier of the library */ installLibrary: function(serviceId) { if (EchoScriptLibraryManager.getState(serviceId) == EchoScriptLibraryManager.STATE_INSTALLED) { // Library already installed. return; } try { // Obtain source. var source = EchoScriptLibraryManager.librarySourceMap[serviceId]; // Execute library code. eval(source); // Clear source. EchoScriptLibraryManager.librarySourceMap[serviceId] = null; // Mark state as installed. EchoScriptLibraryManager.libraryLoadStateMap[serviceId] = EchoScriptLibraryManager.STATE_INSTALLED; } catch (ex) { EchoClientEngine.processClientError("Cannot load script module \"" + serviceId + "\"", ex); throw ex; } }, /** * Loads a JavaScript library and stores it for execution. * * @param serviceId the server service identifier of the library */ loadLibrary: function(serviceId) { if (EchoScriptLibraryManager.getState(serviceId)) { // Library already present. return; } var conn = new EchoHttpConnection(EchoClientEngine.baseServerUri + "?serviceId=" + serviceId, "GET"); conn.serviceId = serviceId; conn.responseHandler = EchoScriptLibraryManager.responseHandler; conn.connect(); // Mark state as "requested" so that application will wait for library to load. EchoScriptLibraryManager.libraryLoadStateMap[serviceId] = EchoScriptLibraryManager.STATE_REQUESTED; }, /** * Processes a valid response from the server. * * @param conn the EchoHttpConnection containing the response information. */ responseHandler: function(conn) { EchoScriptLibraryManager.librarySourceMap[conn.serviceId] = conn.getResponseText(); EchoScriptLibraryManager.libraryLoadStateMap[conn.serviceId] = EchoScriptLibraryManager.STATE_LOADED; EchoClientEngine.incrementLoadStatus(); } }; // _____________________________ // Object EchoServerDelayMessage /** * Static object/namespace to manage configuration and activation of the * server-delay message. * The server-delay message serves the following purposes: *
-
*
- Provides an additional barrier to user input (this should not be * be relied upon in any fashion, as components should themselves * invoke EchoClientEngine.verifyInput() before accepting input. *
- Displays a "wait" (hourglass) mouse cursor. *
- Displays a "please wait" message after a time interval has elapsed * during a longer-than-normal server transaction. *
-
*
EchoServerMessage.STATUS_INITIALIZED
* EchoServerMessage.STATUS_PROCESSING
* EchoServerMessage.STATUS_PROCESSING_COMPLETE
* EchoServerMessage.STATUS_PROCESSING_FAILED
*
-
*
- Modal context *
- Layout direction (LTR/RTL) *
-
*
- Margins and paddings, if set, must be set in pixel units. *
- Top, bottom, left, and right coordinates, if set, must be set in pixel * units. *
sort()
.
*
* @param element the current element of the hierarchy being analyzed.
* @param sortedList an array to which element ids will be appended in the
* orde rthe appear in the hierarchy
*/
sortImpl: function(element, sortedList) {
// If element has id and element is in set of virtually positioned elements
if (element.id && EchoVirtualPosition.elementIdSet.get(element.id)) {
sortedList.push(element.id);
}
for (var child = element.firstChild; child; child = child.nextSibling) {
if (child.nodeType == 1) {
EchoVirtualPosition.sortImpl(child, sortedList);
}
}
},
/**
* Parses the specified value as an integer, returning 0 in the event the
* specified value cannot be expressed as a number.
*
* @param value the value to parse, e.g., "20px"
* @return the value as a integer, e.g., '20'
*/
toInteger: function(value) {
value = parseInt(value, 10);
return isNaN(value) ? 0 : value;
},
/**
* Determines if the specified value contains a pixel dimension, e.g., "20px"
* Returns false if the value is null/whitespace/undefined.
*
* @param value the value to evaluate
* @return true if the value is a pixel dimension, false if it is not
*/
verifyPixelValue: function(value) {
if (value === null || value === "" || value === undefined) {
return false;
}
var valueString = value.toString();
return valueString == "0" || valueString.indexOf("px") != -1;
},
/**
* Determines if the specified value contains a pixel dimension, e.g., "20px"
* Returns true if the value is null/whitespace/undefined.
*
* @param value the value to evaluate
* @return true if the value is null or a pixel dimension, false if it is not
*/
verifyPixelOrUndefinedValue: function(value) {
if (value === null || value === "" || value === undefined) {
return true;
}
var valueString = value.toString();
return valueString == "0" || valueString.indexOf("px") != -1;
},
/**
* Static object/namespace for EchoVirtualPosition MessageProcessor
* implementation. Provides capability to register elements with
* the Virtual Positioning System.
*/
MessageProcessor: {
/**
* MessageProcessor process() implementation
* (invoked by ServerMessage processor).
*
* @param messagePartElement the message-part
element to process
*/
process: function(messagePartElement) {
for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
if (messagePartElement.childNodes[i].nodeType == 1) {
if (messagePartElement.childNodes[i].tagName == "register") {
EchoVirtualPosition.MessageProcessor.processRegister(messagePartElement.childNodes[i]);
}
}
}
},
/**
* Processes a register
message register an elmeent with the
* EchoVirtualPosition
system.
*
* @param registerElement the register
element to process
*/
processRegister: function(registerElement) {
var items = registerElement.getElementsByTagName("item");
for (var i = 0; i < items.length; ++i) {
var elementId = items[i].getAttribute("eid");
EchoVirtualPosition.register(elementId);
}
}
}
};
// _______________________
// Object EchoWindowUpdate
/**
* MessageProcessor implementation to perform browser window-related updates,
* such as setting the window title.
*/
EchoWindowUpdate = {
/**
* The DOM element to be focused (used by delayed focusing which is required on some browsers).
*/
focusPendingElement: null,
delayFocus: function() {
if (EchoWindowUpdate.focusPendingElement && EchoWindowUpdate.focusPendingElement.focus) {
try {
EchoWindowUpdate.focusPendingElement.focus();
} catch (ex) { }
EchoWindowUpdate.focusPendingElement = null;
}
},
/**
* MessageProcessor process() implementation.
*/
process: function(messagePartElement) {
for (var i = 0; i < messagePartElement.childNodes.length; ++i) {
if (messagePartElement.childNodes[i].nodeType == 1) {
switch (messagePartElement.childNodes[i].tagName) {
case "reload":
EchoWindowUpdate.processReload(messagePartElement.childNodes[i]);
break;
case "set-focus":
EchoWindowUpdate.processSetFocus(messagePartElement.childNodes[i]);
break;
case "set-title":
EchoWindowUpdate.processSetTitle(messagePartElement.childNodes[i]);
break;
}
}
}
},
/**
* Processes a server directive to reload the entire window
* (due to an out-of-sync application state).
*
* @param reloadElement the "reload" directive element
*/
processReload: function(reloadElement) {
var message = EchoClientConfiguration.propertyMap["defaultOutOfSyncErrorMessage"];
if (message) {
alert(message);
}
window.location.reload();
},
/**
* Processes a server directive to set the focused component.
*
* @param setFocusElement the "set-focus" directive element
*/
processSetFocus: function(setFocusElement) {
EchoVirtualPosition.redraw();
var elementId = setFocusElement.getAttribute("element-id");
var element = document.getElementById(elementId);
if (EchoClientProperties.get("quirkDelayedFocusRequired")) {
this.focusPendingElement = element;
window.setTimeout(EchoWindowUpdate.delayFocus, 0);
} else {
if (element && element.focus) {
element.focus();
}
}
},
/**
* Processes a server directive to set the window title.
*
* @param setTitleElement the "set-title" directive element
*/
processSetTitle: function(setTitleElement) {
document.title = setTitleElement.getAttribute("title");
}
};
// _____________________
// Static Initialization
EchoClientMessage.reset();