glow/ 40755 0 0 0 11222123307 7051 5 ustar 0 0 glow/1.5.0-rc2/ 40755 0 0 0 11222123266 10202 5 ustar 0 0 glow/1.5.0-rc2/core/ 40755 0 0 0 11222123266 11132 5 ustar 0 0 glow/1.5.0-rc2/widgets/ 40755 0 0 0 11222123311 11637 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/ 40755 0 0 0 11222123312 13105 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/carousel/ 40755 0 0 0 11222123312 14722 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/darkpanel/ 40755 0 0 0 11222123312 15046 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/editor/ 40755 0 0 0 11222123312 14373 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/lightpanel/ 40755 0 0 0 11222123312 15234 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/slider/ 40755 0 0 0 11222123312 14367 5 ustar 0 0 glow/1.5.0-rc2/widgets/images/timetable/ 40755 0 0 0 11222123312 15053 5 ustar 0 0 CHANGES 100644 0 0 1253 11222123312 7166 0 ustar 0 0 ================================= GLOW JAVASCRIPT LIBRARY CHANGELOG ================================= Changes with Glow 1.5 r1072 glow.dom - Fixing some bugs in the child selector r1073 glow.widgets.Overlay - Fixing an unscoped variable r1080 glow.widgets.Carousel - Cloning events on duplicate items r1093 glow.anim - Improvements to shortcut functions r1101 glow.widgets.Overlay - aliasing "roll" transition to "slide" for compatibility with glow.anim shortcuts r1107 glow.events - Deprecated addKeyListener, will return 'new and improved' in Glow 2.0 r1107 glow.widgets.Mask - Deprecated disableScroll r1118 glow.embed - Updated "no flash" message to include links to webwise. CONTRIBUTORS 100644 0 0 0 11222123312 7760 0 ustar 0 0 LICENCE 100644 0 0 23676 11222123312 7215 0 ustar 0 0 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS README 100644 0 0 0 11222123312 6760 0 ustar 0 0 glow/1.5.0-rc2/core/core.debug.js 100644 0 0 1146554 11222123266 13702 0 ustar 0 0 /* Copyright 2009 British Broadcasting Corporation Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** @name glow @namespace @version 1.5.0-rc2 (2008-09-22) @description The glow namespace and core library. Includes common methods for running scripts onDomReady and user agent sniffing. */ (function() { /* PrivateVar: moduleRegister Holds info on which modules are registered {name:true} */ var moduleRegister = {glow: true}, /* PrivateVar: regexEscape For escaping strings to go in regex */ regexEscape = /([$^\\\/()|?+*\[\]{}.-])/g, /* PrivateVar: ua A lowercase representation of the user's useragent string */ ua = navigator.userAgent.toLowerCase(), //glow version version = "1.5.0-rc2", // number of blockers blocking (when it's zero, we're ready) blockersActive = 0, /* PrivateMethod: domReadyQueue array of functions to call when dom is ready */ domReadyQueue = [], domReadyQueueLen = 0, /* PrivateMethod: readyQueue array of functions to call when all ready blockers are unblocked */ //we want to set isReady to true when this is first run readyQueue = [], readyQueueLen = 0, // stops two instances of 'runReadyQueue' fighting on the call stack processingReadyQueue = false, glow = { /** @name glow.VERSION @description Version of glow This is in the format 1.2.3 @type String @see Glow's versioning scheme */ VERSION: version, /** @name glow.UID @description A unique ID for this instance of Glow This will be used in glow-specific property names that need to be unique to this instance of glow. @type String */ UID: "glow" + Math.floor(Math.random() * (1<<30)), /** @name glow.isDomReady @description Is the DOM ready? If glow is loaded after the page has loaded (by means other than Gloader) this value should be set manually. @type Boolean */ //check gloader to see if dom is already ready isDomReady: window.gloader && gloader.isReady, /** @name glow.isReady @description Is Glow ready? Set to true when Glow is ready. This includes DOM ready, a supported browser and any additional requirements. For example Glow widgets will add the loading of their CSS file as a requirement. @type Boolean */ //check gloader to see if dom is already ready isReady: window.gloader && gloader.isReady, /** @name glow.env @description Information about the browser / platform @type Object @example if (glow.env.ie < 7) { //this only runs in IE 6 and below } if (glow.env.gecko < 1.9) { //this only runs in Gecko versions less than 1.9 //Wikipedia can be used to link engine versions to browser versions } */ /** @name glow.env.gecko @description Gecko version number to one decimal place (eg 1.9) or NaN @type Number */ /** @name glow.env.ie @description IE version number or NaN @type Number */ /** @name glow.env.opera @description Opera version (eg 8.02) or NaN @type Number */ /** @name glow.env.webkit @description Webkit version number to one decimal place (eg 419.3) or NaN @type Number */ /** @name glow.env.khtml @description KHTML version number to one decimal place or NaN @type Number */ /** @name glow.env.standardsMode @description True if the browser reports itself to be in 'standards mode' @type Boolean */ /** @name glow.env.version @description Browser version as a string. Includes non-numerical data, eg "1.8.1" or "7b" @type String */ env: function(){ var nanArray = [0, NaN], opera = (/opera[\s\/]([\w\.]+)/.exec(ua) || nanArray)[1], ie = opera ? NaN : (/msie ([\w\.]+)/.exec(ua) || nanArray)[1], gecko = (/rv:([\w\.]+).*gecko\//.exec(ua) || nanArray)[1], webkit = (/applewebkit\/([\w\.]+)/.exec(ua) || nanArray)[1], khtml = (/khtml\/([\w\.]+)/.exec(ua) || nanArray)[1], toNum = parseFloat; return { gecko : toNum(gecko), ie : toNum(ie), opera : toNum(opera), webkit : toNum(webkit), khtml : toNum(khtml), version : ie || gecko || webkit || opera || khtml, standardsMode : document.compatMode != "BackCompat" && (!ie || ie >= 6) } }(), /** @name glow.module @private @function @description Registers a new module with the library, checking version numbers & dependencies. @param {Object} meta Object containing all of the following. This object is compatible with gloader.module, hence some properties which seem unnecessary here. This is all simplified by glow's module pattern. @param {String} meta.name Name of the module. Eg. "glow.dom" or "glow.widgets.Panel" @param {String[]} meta.library Information about Glow. This must be ["glow", "1.5.0-rc2"]. 1.5.0-rc2 should be the version number of glow expected. @param {String[]} meta.depends The module's dependencies. This must start ["glow", "1.5.0-rc2"], followed by modules such as "glow.dom". 1.5.0-rc2 should be the version number of glow expected. @param {Function} meta.builder The module's implementation. A reference to glow will be passed in as the first parameter to this function. Add to that object to create publicly accessabile properties. Anything else in this function will be private. @returns {Object} Glow @example glow.module({ name: "glow.anim", library: ["glow", "1.0.0"], depends: ["glow", "1.0.0", "glow.dom"], builder: function(glow) { glow.anim = { //... }; } }); */ module: function(meta) { var i = 2, depends = meta.depends[0] || [], dependsLen = depends.length, name = meta.name, objRef = window.glow; //holds the parent object for the new module //check version number match core version if (meta.library[1] != glow.VERSION) { throw new Error("Cannot register " + name + ": Version mismatch"); } //check dependencies loaded if (depends[2]) { for (; i < dependsLen; i++) { //check exists if (!moduleRegister[depends[i]]) { //check again ondomready to detect if modules are being included in wrong order throw new Error("Module " + depends[i] + " required before " + name); } } } //create module meta.builder(glow); //register it as built moduleRegister[name] = true; return glow; }, /** @name glow.ready @function @description Calls a function when the DOM had loaded and the browser is supported "ready" also waits for glow's CSS file to load if it has been requested. @param {Function} callback Function to call @returns {glow} @example glow.ready(function() { alert("DOM Ready!"); }); */ ready: function(f) { //just run function if already ready if (this.isReady) { f(); } else { readyQueue[readyQueueLen++] = f; } return this; }, /** @name glow._readyBlockers @private @object @description A hash (by name) of blockers. True if they are blocking, false if they've since unblocked */ _readyBlockers: {}, /** @name glow._addReadyBlock @private @function @description Adds a blocker to anything added via glow.ready. Uncalled callbacks added via glow.ready will not fire until this block is removed @param {String} name Name of blocker @returns {glow} @example glow._addReadyBlock("widgetsCss"); // when CSS is ready... glow._removeReadyBlock("widgetsCss"); */ _addReadyBlock: function(name) { if (name in glow._readyBlockers) { throw new Error("Blocker '" + name +"' already exists"); } glow._readyBlockers[name] = true; glow.isReady = false; blockersActive++; return glow; }, /** @name glow._removeReadyBlock @private @function @description Removes a ready blocker added via glow._addReadyBlock @param {String} name Name of blocker @returns {glow} @example glow._addReadyBlock("widgetsCss"); // when CSS is ready... glow._removeReadyBlock("widgetsCss"); */ _removeReadyBlock: function(name) { if (glow._readyBlockers[name]) { glow._readyBlockers[name] = false; blockersActive--; // if we're out of blockers if (!blockersActive) { // call our queue glow.isReady = true; runReadyQueue(); } } return glow; }, /** @name glow.onDomReady @function @description Calls a function when / if the DOM is ready. This function does not wait for glow's CSS to load, nor does it block unsupported browsers. If you want these features, use {@link glow.ready} @param {Function} callback Function to call @returns {glow} @exmaple glow.onDomReady(function() { alert("DOM Ready!"); }); */ onDomReady: function(f) { //just run function if already ready if (this.isDomReady) { f(); } else { domReadyQueue[domReadyQueueLen++] = f; } }, /** @name glow.lang @namespace @description Useful language functions. @see Using glow.lang.clone */ lang: { /** @name glow.lang.trim @function @description Removes leading and trailing whitespace from a string @param {String} str String to trim @returns {String} String without leading and trailing whitespace @example glow.lang.trim(" Hello World "); // "Hello World" */ trim: function(sStr) { //this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript return sStr.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1'); }, /** @name glow.lang.toArray @function @description Converts an array-like object to a real array @param {Object} arrayLike Any array-like object @returns {Array} @example var a = glow.lang.toArray(glow.dom.get("a")); */ toArray: function(aArrayLike) { if (aArrayLike.constructor == Array) { return aArrayLike; } //use array.slice if not IE? Could be faster var r = [], i=0, len = aArrayLike.length; for (; i < len; i++) { r[i] = aArrayLike[i]; } return r; }, /** @name glow.lang.apply @function @description Copies properties from one object to another @param {Object} destination Destination object @param {Object} source Properties of this object will be copied onto a clone of destination @returns {Object} @example var obj = glow.lang.apply({foo: "hello", bar: "world"}, {bar: "everyone"}); //results in {foo: "hello", bar: "everyone"} */ apply: function(destination, source) { for (var i in source) { destination[i] = source[i]; } return destination; }, /** @name glow.lang.map @function @description Runs a function for each element of an array and returns an array of the results @param {Array} array Array to loop over @param {Function} callback The function to run on each element. This function is passed three params, the array item, its index and the source array. @param {Object} [context] The context for the callback function (the array is used if not specified) @returns {Array} Array containing one element for each value returned from the callback @example var weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] var weekdaysAbbr = glow.lang.map(weekdays, function (day) { return day.slice(0, 3).toLowerCase(); }); // returns ["mon", "tue", "wed", "thu", "fri"] */ map: function (arr, callback, context) { if (Array.prototype.map) { return Array.prototype.map.call(arr, callback, context || arr); } if (! callback.call) { throw new TypeError(); } var len = arr.length, res = [], thisp = context || arr, i = 0; for (; i < len; i++) { if (i in arr) { res[i] = callback.call(thisp, arr[i], i, arr); } } return res; }, /** @name glow.lang.replace @function @description Makes a replacement in a string. Has the same interface as the builtin String.prototype.replace method, but takes the input string as the first parameter. In general the native string method should be used unless you need to pass a function as the second parameter, as this method will work accross our supported browsers. @param {String} str Input string @param {String | RegExp} pattern String or regular expression to match against @param {String | Function} replacement String to make replacements with, or a function to generate the replacements @returns {String} A new string with the replacement(s) made @example var myDays = '1 3 6'; var dayNames = glow.lang.replace(myDays, /(\d)/, function (day) { return " MTWTFSS".charAt(day - 1); }); // dayNames now contains "M W S" */ replace: (function () { var replaceBroken = "g".replace(/g/, function () { return 'l'; }) != 'l', def = String.prototype.replace; return function (inputString, re, replaceWith) { var pos, match, last, buf; if (! replaceBroken || typeof(replaceWith) != 'function') { return def.call(inputString, re, replaceWith); } if (! (re instanceof RegExp)) { pos = inputString.indexOf(re); return pos == -1 ? inputString : def.call(inputString, re, replaceWith.call(null, re, pos, inputString)); } buf = []; last = re.lastIndex = 0; while ((match = re.exec(inputString)) != null) { pos = match.index; buf[buf.length] = inputString.slice(last, pos); buf[buf.length] = replaceWith.apply(null, match); if (re.global) { last = re.lastIndex; } else { last = pos + match[0].length; break; } } buf[buf.length] = inputString.slice(last); return buf.join(""); }; })(), /** @name glow.lang.interpolate @function @description Replaces placeholders in a string with data from an object @param {String} template The string containing {placeholders} @param {Object} data Object containing the data to be merged in to the template
The object can contain nested data objects and arrays, with nested object properties and array elements are accessed using dot notation. eg foo.bar or foo.0.
The data labels in the object cannot contain characters used in the template delimiters, so if the data must be allowed to contain the default { and } delimiters, the delimters must be changed using the option below.
@param {Object} opts Options object @param {String} [opts.delimiter="{}"] Alternative label delimiter(s) for the template The first character supplied will be the opening delimiter, and the second the closing. If only one character is supplied, it will be used for both ends. @returns {String} @example var data = {name: "Domino", colours: ["black", "white"], family:{mum: "Spot", dad: "Patch", siblings: []}}; var template = "My cat's name is {name}. His colours are {colours.0} & {colours.1}. His mum is {family.mum}, his dad is {family.dad} and he has {family.siblings.length} brothers or sisters."; var result = glow.lang.interpolate(template, data); //result == "My cat's name is Domino. His colours are black & white. His mum is Spot, his dad is Patch and he has 0 brothers or sisters." */ interpolate : function (template, data, opts) { var rx, l, r; opts = opts || {}; if(opts.delimiter == undefined) { rx = /\{[^{}]+\}/g; } else { l = opts.delimiter.substr(0, 1).replace(regexEscape, "\\$1"); r = (opts.delimiter.length == 1)?l:opts.delimiter.substr(1, 1).replace(regexEscape, "\\$1"); rx = new RegExp(l + "[^" + l + r + "]+" + r, "g"); } return template.replace(rx, function (found) { var t = found.substring(1, found.length - 1), exp = t.split("."), val = data; if(data[t]) { return data[t]; // need to be backwards compatible with "flattened" data. } for(var i = 0, l = exp.length; i < l; i++) { if(val[exp[i]] != undefined) { val = val[exp[i]]; } else { return found; } } return val; }); }, /** @name glow.lang.hasOwnProperty @function @description Cross-browser implementation Safari 1.3 doesn't support Object.hasOwnProperty , use this method instead. @param {Object} obj The object to check @param {String} property Property name @returns {Boolean} Returns false if a property doesn't exist in an object, or it was inherited from the object's prototype. Otherwise, returns true */ hasOwnProperty: {}.hasOwnProperty ? //not supported in Safari 1.3 function(obj, prop) { return obj.hasOwnProperty(prop); } : function(obj, prop) { var propVal = obj[prop], //value of the property objProto = obj.__proto__, //prototype of obj protoVal = objProto ? objProto[prop] : {}; //prototype val if (propVal !== protoVal) { return true; } //try changing prototype and see if obj reacts var restoreProtoVal = glow.lang.hasOwnProperty(objProto, prop), tempObjProtoVal = objProto[prop] = {}, hasOwn = (obj[prop] !== tempObjProtoVal); delete objProto[prop]; if (restoreProtoVal) { objProto[name] = tempObjProtoVal; } return hasOwn; }, /** @name glow.lang.extend @function @description Copies the prototype of one object to another. The 'subclass' can also access the 'base class' via subclass.base @param {Function} sub Class which inherits properties. @param {Function} base Class to inherit from. @param {Object} additionalProperties An object of properties and methods to add to the subclass. @example function MyClass(arg) { this.prop = arg; } MyClass.prototype = { showProp: function() { alert(this.prop); } }; function MyOtherClass(arg) { //call the base class's constructor arguments.callee.base.apply(this, arguments); } glow.lang.extend(MyOtherClass, MyClass, { setProp: function(newProp) { this.prop = newProp; } }); var test = new MyOtherClass("hello"); test.showProp(); // alerts "hello" test.setProp("world"); test.showProp(); // alerts "world" * */ extend: function(sub, base, additionalProperties) { var f = function () {}, p; f.prototype = base.prototype; p = new f(); sub.prototype = p; p.constructor = sub; sub.base = base; if (additionalProperties) { glow.lang.apply(sub.prototype, additionalProperties); } }, /** @name glow.lang.clone @function @description Deep clones an object / array @param {Object} Data Object to clone @returns {Object} @example var firstObj = { name: "Bob", secondNames: ["is","your","uncle"] }; var clonedObj = glow.lang.clone( firstObj ); */ clone: function( obj ) { var index, _index, tmp; obj = obj.valueOf(); if ( typeof obj !== 'object' ) { return obj; } else { if ( obj[0] || obj.concat ) { tmp = [ ]; index = obj.length; while(index--) { tmp[index] = arguments.callee( obj[index] ); } } else { tmp = { }; for ( index in obj ) { tmp[index] = arguments.callee( obj[index] ); } } return tmp; } } } }, env = glow.env, d = document; //dom ready stuff //run queued ready functions when DOM is ready function runDomReadyQueue() { glow.isDomReady = true; // run all functions in the array for (var i = 0; i < domReadyQueueLen; i++) { domReadyQueue[i](); } } function runReadyQueue() { // if we're already processing the queue, just exit, the other instance will take care of it if (processingReadyQueue) return; processingReadyQueue = true; for (var i = 0; i < readyQueueLen;) { readyQueue[i](); i++; // check if the previous function has created a blocker if (blockersActive) { break; } } // take items off the ready queue that have processed readyQueue = readyQueue.slice(i); // update len readyQueueLen = readyQueueLen - i; processingReadyQueue = false; } (function(){ //don't do this stuff if the dom is already ready if (glow.isDomReady) { return; } glow._addReadyBlock("glow_domReady"); if (env.ie) { if (typeof window.frameElement != 'undefined') { // we can't use doScroll if we're in an iframe... d.attachEvent("onreadystatechange", function(){ if (d.readyState == "complete") { d.detachEvent("onreadystatechange", arguments.callee); runDomReadyQueue(); glow._removeReadyBlock("glow_domReady"); } }); } else { // polling for no errors (function () { try { // throws errors until after ondocumentready d.documentElement.doScroll('left'); } catch (e) { setTimeout(arguments.callee, 0); return; } // no errors, fire runDomReadyQueue(); glow._removeReadyBlock("glow_domReady"); })(); } } else if (glow.env.webkit < 525.13 && typeof d.readyState != 'undefined') { var f = function(){ if ( /loaded|complete/.test(d.readyState) ) { runDomReadyQueue(); glow._removeReadyBlock("glow_domReady"); } else { setTimeout(f, 0); } }; f(); } else { var callback = function () { if (callback.fired) { return; } callback.fired = true; runDomReadyQueue(); glow._removeReadyBlock("glow_domReady"); }; d.addEventListener("DOMContentLoaded", callback, false); var oldOnload = window.onload; window.onload = function () { if (oldOnload) { oldOnload(); } callback(); }; } })(); /** @name glow.isSupported @description Set to true in supported user agents This will read false in 'level 2' browsers in BBC's Browser Support Guidelines @type Boolean @see BBC's Browser Support Guidelines */ // TODO: for v2 we should switch this to 'notSupported' as it's a blacklist glow.isSupported = !( //here are the browsers we don't support env.ie < 6 || (env.gecko < 1.9 && !/^1\.8\.1/.test(env.version)) || env.opera < 9 || env.webkit < 412 ); // block 'ready' if browser isn't supported if (!glow.isSupported) { glow._addReadyBlock("glow_browserSupport"); } if (window.gloader) { gloader.library({ name: "glow", version: "1.5.0-rc2", builder: function () { return glow; } }); } else if (window.glow) { throw new Error("Glow global object already exists"); } else { window.glow = glow; } // this helps IE cache background images if (glow.ie) { try { document.execCommand("BackgroundImageCache", false, true); } catch(e) {} } })(); /*@cc_on @*/ /*@if (@_jscript_version > 5.5)@*/ /** @name glow.dom @namespace @description Accessing and manipulating the DOM @see Creating NodeLists @see Working with NodeLists @see XML NodeLists */ (window.gloader || glow).module({ name: "glow.dom", library: ["glow", "1.5.0-rc2"], depends: [], builder: function(glow) { //private var env = glow.env, lang = glow.lang, /* PrivateVar: cssRegex For matching CSS selectors */ cssRegex = { tagName: /^(\w+|\*)/, combinator: /^\s*([>]?)\s*/, //safari 1.3 is a bit dim when it comes to unicode stuff, only dot matches them (not even \S), so some negative lookalheads are needed classNameOrId: (env.webkit < 417) ? new RegExp("^([\\.#])((?:(?![\\.#\\[:\\s\\\\]).|\\\\.)+)") : /^([\.#])((?:[^\.#\[:\\\s]+|\\.)+)/ }, //for escaping strings in regex regexEscape = /([$^\\\/()|?+*\[\]{}.-])/g, /* PrivateVar: cssCache Cache of arrays representing an execution path for css selectors */ cssCache = {}, /* PrivateVar: dom0PropertyMapping Mapping of HTML attribute names to DOM0 property names. */ dom0PropertyMapping = { checked : "checked", "class" : "className", "disabled" : "disabled", "for" : "htmlFor", maxlength : "maxLength" }, /* PrivateVar: dom0BooleanAttribute The HTML attributes names that should be converted from true/false to ATTRIBUTENAME/undefined (i.e. boolean attributes like checked="checked"). */ dom0BooleanAttribute = { checked : true, disabled : true }, /* PrivateVar: dom0AttributeMappings Functions that map dom0 values to sane ones. */ dom0AttributeMappings = { maxlength : function (val) { return val.toString() == "2147483647" ? undefined : val; } }, /* PrivateVar: ucheck Used by unique(), increased by 1 on each use */ ucheck = 1, /* PrivateVar: ucheckPropName This is the property name used by unique checks */ ucheckPropName = "_unique" + glow.UID, /* PrivateVar: htmlColorNames Mapping of colour names to hex values */ htmlColorNames = { black: 0, silver: 0xc0c0c0, gray: 0x808080, white: 0xffffff, maroon: 0x800000, red: 0xff0000, purple: 0x800080, fuchsia: 0xff00ff, green: 0x8000, lime: 0xff00, olive: 0x808000, yellow: 0xffff00, navy: 128, blue: 255, teal: 0x8080, aqua: 0xffff, orange: 0xffa500 }, /* PrivateVar: usesYAxis regex for detecting which css properties need to be calculated relative to the y axis */ usesYAxis = /height|top/, colorRegex = /^rgb\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)/i, cssPropRegex = /^(?:(width|height)|(border-(top|bottom|left|right)-width))$/, hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/, //append gets set to a function below append, //unique gets set to a function below unique, trbl = ["Top", "Right", "Bottom", "Left"], trblLen = 4, paddingStr = "padding", marginStr = "margin", borderStr = "border", widthStr = "Width", //we set this up at the end of the module placeholderElm, //getByTagName gets get to a function below getByTagName, win = window, doc = document, docBody, docElm, // true if properties of a dom node are cloned when the node is cloned (eg, true in IE) nodePropertiesCloned, // used to convert divs to strings tmpDiv = doc.createElement("div"); // clean up IE's mess if (env.ie) { window.attachEvent("onunload", function() { tmpDiv = null; }); } glow.ready(function() { docBody = doc.body; docElm = doc.documentElement; }); // test for nodePropertiesCloned (function() { var div = doc.createElement("div"); div.a = 1; nodePropertiesCloned = !!div.cloneNode(true).a; })(); /* PrivateMethod: removeClassRegex Get a regex that can be used to remove a class from a space separated list of classes. Arguments: name - (string) the name of the class. Returns: The regex. */ function removeClassRegex (name) { return new RegExp(["(^|\\s)", name.replace(regexEscape, "\\$1"), "($|\\s)"].join(""), "g"); } /* PrivateMethod: stringToNodes Creates an array of nodes from a string */ function stringToNodes(str) { // TODO: need to change container for certain elements. Make a lookup table for // {element: container}, for exceptions to div container var r = [], rLen = 0; //we add a text node to the start of the string here to fix an IE bug //when the string contains a link element tmpDiv.innerHTML = "a" + str; while (tmpDiv.childNodes[1]) { r[rLen++] = tmpDiv.removeChild(tmpDiv.childNodes[1]); } tmpDiv.innerHTML = ""; return r; } /* PrivateMethod: nodelistToArray Converts a w3 NodeList to an array */ function nodelistToArray(nodelist) { var r = [], i = 0; for (; nodelist[i]; i++) { r[i] = nodelist[i]; } return r; } /* PrivateMethod: setAttribute Sets the attribute in the nodelist using the supplied function. Arguments: value - (String|Function) the value/value generator. attributeSetter - (Function) a function that can be called to actually set the attribute. Returns: TheThis method is not applicable to XML NodeLists.
@param {String} name The name of the class to test for. @returns {Boolean} @example if (glow.dom.get("a").hasClass("termsLink")){ // ... } */ hasClass: function (name) { for (var i = 0, length = this.length; i < length; i++) { if ((" " + this[i].className + " ").indexOf(" " + name + " ") != -1) { return true; } } return false; }, /** @name glow.dom.NodeList#addClass @function @description Adds a class to each node.This method is not applicable to XML NodeLists.
@param {String} name The name of the class to add. @returns {glow.dom.NodeList} @example glow.dom.get("#login a").addClass("highlight"); */ addClass: function (name) { for (var i = 0, length = this.length; i < length; i++) { if ((" " + this[i].className + " ").indexOf(" " + name + " ") == -1) { this[i].className += " " + name; } } return this; }, /** @name glow.dom.NodeList#removeClass @function @description Removes a class from each node.This method is not applicable to XML NodeLists.
@param {String} name The name of the class to remove. @returns {glow.dom.NodeList} @example glow.dom.get("#footer #login a").removeClass("highlight"); */ removeClass: function (name) { var re = removeClassRegex(name), that = this, i = 0, length = that.length; for (; i < length; i++) { that[i].className = that[i].className.replace(re, " "); } return that; }, /** @name glow.dom.NodeList#toggleClass @function @description Toggles a class on each node.This method is not applicable to XML NodeLists.
@param {String} name The name of the class to toggle. @returns {glow.dom.NodeList} @example glow.dom.get(".onOffSwitch").toggleClass("on"); */ toggleClass: function (name) { var i = this.length, paddedClassName, paddedName = " " + name + " "; while (i--) { paddedClassName = " " + this[i].className + " "; if (paddedClassName.indexOf(paddedName) != -1) { this[i].className = paddedClassName.replace(paddedName, " "); } else { this[i].className += " " + name; } } return this; }, /** @name glow.dom.NodeList#val @function @description Gets or sets form values for the first node.This method is not applicable to XML NodeLists.
Getting values from form elements
The returned value depends on the type of element, see below:Getting values from a form
If the first element in the NodeList is a form, then an object is returned containing the form data. Each item property of the object is a value as above, apart from when multiple elements of the same name exist, in which case the it will contain an array of values.Setting values for form elements
If a value is passed and the first element of the NodeList is a form element, then the form element is given that value. For select elements, this means that the first option that matches the value will be selected. For selects that allow multiple selection, the options which have a value that exists in the array of values/match the value will be selected and others will be deselected. Currently checkboxes and radio buttons are not checked or unchecked, just their value is changed. This does mean that this does not do exactly the reverse of getting the value from the element (see above) and as such may be subject to changeSetting values for forms
If the first element in the NodeList is a form and the value is an object, then each element of the form has its value set to the corresponding property of the object, using the method described above. @param {String | Object} [value] The value to set the form element/elements to. @returns {glow.dom.NodeList | String | Object} When used to set a value it returns the NodeList, otherwise returns the value as described above. @example // get a value var username = glow.dom.get("input#username").val(); @example // get values from a form var userDetails = glow.dom.get("form").val(); @example // set a value glow.dom.get("input#username").val("example username"); @example // set values in a form glow.dom.get("form").val({ username : "another", name : "A N Other" }); */ val: function () { /* PrivateFunction: elementValue Get a value for a form element. */ function elementValue (el) { var elType = el.type, elChecked = el.checked, elValue = el.value, vals = [], i = 0; if (elType == "radio") { return elChecked ? elValue : ""; } else if (elType == "checkbox") { return elChecked ? elValue : ""; } else if (elType == "select-one") { return el.selectedIndex > -1 ? el.options[el.selectedIndex].value : ""; } else if (elType == "select-multiple") { for (var length = el.options.length; i < length; i++) { if (el.options[i].selected) { vals[vals.length] = el.options[i].value; } } return vals; } else { return elValue; } } /* PrivateMethod: formValues Get an object containing form data. */ function formValues (form) { var vals = {}, radios = {}, formElements = form.elements, i = 0, length = formElements.length, name, formElement, j, radio, nodeName; for (; i < length; i++) { formElement = formElements[i]; nodeName = formElement.nodeName.toLowerCase(); name = formElement.name; // fieldsets & objects come back as form elements, but we don't care about these // we don't bother with fields that don't have a name // switch to whitelist? if ( nodeName == "fieldset" || nodeName == "object" || !name ) { continue; } if (formElement.type == "checkbox" && ! formElement.checked) { if (! name in vals) { vals[name] = undefined; } } else if (formElement.type == "radio") { if (radios[name]) { radios[name][radios[name].length] = formElement; } else { radios[name] = [formElement]; } } else { var value = elementValue(formElement); if (name in vals) { if (vals[name].push) { vals[name][vals[name].length] = value; } else { vals[name] = [vals[name], value]; } } else { vals[name] = value; } } } for (i in radios) { j = 0; for (length = radios[i].length; j < length; j++) { radio = radios[i][j]; name = radio.name; if (radio.checked) { vals[radio.name] = radio.value; break; } } if (! name in vals) { vals[name] = undefined; } } return vals; } /* PrivateFunction: setFormValues Set values of a form to those in passed in object. */ function setFormValues (form, vals) { var prop, currentField, fields = {}, storeType, i = 0, n, len, foundOne, currentFieldType; for (prop in vals) { currentField = form[prop]; if (currentField && currentField[0]) { // is array of fields //normalise values to array of vals vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]]; //order the fields by types that matter fields.radios = []; fields.checkboxesSelects = []; fields.multiSelects = []; fields.other = []; for (i = 0; currentField[i]; i++) { currentFieldType = currentField[i].type; if (currentFieldType == "radio") { storeType = "radios"; } else if (currentFieldType == "select-one" || currentFieldType == "checkbox") { storeType = "checkboxesSelects"; } else if (currentFieldType == "select-multiple") { storeType = "multiSelects"; } else { storeType = "other"; } //add it to the correct array fields[storeType][fields[storeType].length] = currentField[i]; } for (i = 0; fields.multiSelects[i]; i++) { vals[prop] = setValue(fields.multiSelects[i], vals[prop]); } for (i = 0; fields.checkboxesSelects[i]; i++) { setValue(fields.checkboxesSelects[i], ""); for (n = 0, len = vals[prop].length; n < len; n++) { if (setValue(fields.checkboxesSelects[i], vals[prop][n])) { vals[prop].slice(n, 1); break; } } } for (i = 0; fields.radios[i]; i++) { fields.radios[i].checked = false; foundOne = false; for (n = 0, len = vals[prop].length; n < len; n++) { if (setValue(fields.radios[i], vals[prop][n])) { vals[prop].slice(n, 1); foundOne = true; break; } if (foundOne) { break; } } } for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) { setValue(fields.other[i], vals[prop][i]); } } else if (currentField && currentField.nodeName) { // is single field, easy setValue(currentField, vals[prop]); } } } /* PrivateFunction: setValue Set the value of a form element. Returns: values that weren't able to set if array of vals passed (for multi select). Otherwise true if val set, false if not */ function setValue (el, val) { var i = 0, length, n = 0, nlen, elOption, optionVal; if (el.type == "select-one") { for (length = el.options.length; i < length; i++) { if (el.options[i].value == val) { el.selectedIndex = i; return true; } } return false; } else if (el.type == "select-multiple") { var isArray = !!val.push; for (i = 0, length = el.options.length; i < length; i++) { elOption = el.options[i]; optionVal = elOption.value; if (isArray) { elOption.selected = false; for (nlen = val.length; n < nlen; n++) { if (optionVal == val[n]) { elOption.selected = true; val.splice(n, 1); break; } } } else { return elOption.selected = val == optionVal; } } return false; } else if (el.type == "radio" || el.type == "checkbox") { el.checked = val == el.value; return val == el.value; } else { el.value = val; return true; } } // toplevel implementation return function (/* [value] */) { var args = arguments, val = args[0], that = this, i = 0, length = that.length; if (args.length === 0) { return that[0].nodeName == 'FORM' ? formValues(that[0]) : elementValue(that[0]); } if (that[0].nodeName == 'FORM') { if (! typeof val == 'object') { throw 'value for FORM must be object'; } setFormValues(that[0], val); } else { for (; i < length; i++) { setValue(that[i], val); } } return that; }; }(), /** @name glow.dom.NodeList#slice @function @description Extracts nodes from a NodeList and returns them as a new NodeList. @param {Number} start The NodeList index at which to begin extraction. If negative, this param specifies a position measured from the end of the NodeList @param {Number} [end] The NodeList index immediately after the end of the extraction. If not specified the extraction includes all nodes from the start to the end of the NodeList. A Negative end specifies an position measured from the end of the NodeList. @returns {glow.dom.NodeList} Returns a new NodeList containing the extracted nodes @example var myNodeList = glow.dom.create(""); myNodeList = myNodeList.slice(1, 2); // just second div */ slice: function (/* start, end */) { return new r.NodeList().push(Array.prototype.slice.apply(this, arguments)); }, /** @name glow.dom.NodeList#sort @function @description Returns a new NodeList containing the same nodes in order. Sort order defaults to document order if no sort function is passed in. @param {Function} [func] Function to determine sort order This function will be passed 2 nodes (a, b). The function should return a number less than 0 to sort a lower than b and greater than 0 to sort a higher than b. @returns {glow.dom.NodeList} Returns a new NodeList containing the sorted nodes @example // heading elements in document order var headings = glow.dom.get("h1, h2, h3, h4, h5, h6").sort(); //get links in alphabetical (well, lexicographical) order var links = glow.dom.get("a").sort(function(a, b) { return ((a.textContent || a.innerText) < (b.textContent || b.innerText)) ? -1 : 1; }) */ sort: function(func) { var that = this, i=0, aNodes; if (!that.length) { return that; } if (!func) { if (typeof that[0].sourceIndex == "number") { // sourceIndex is IE proprietary (but Opera supports) func = function(a, b) { return a.sourceIndex - b.sourceIndex; }; } else if (that[0].compareDocumentPosition) { // DOM3 method func = function(a, b) { return 3 - (a.compareDocumentPosition(b) & 6); }; } else { // js emulation of sourceIndex aNodes = getByTagName("*", [doc]); for (; aNodes[i]; i++) { aNodes[i]._sourceIndex = i; } func = function(a, b) { return a._sourceIndex - b._sourceIndex; }; } } return r.get([].sort.call(that, func)); }, /** @name glow.dom.NodeList#filter @function @description Filter the NodeList using a function The supplied function will be called for each node in the NodeList. The index of the node will be provided as the first parameter, and 'this' will refer to the node. Return true to keep the node, or false to remove it. @param {Function} func Function to test each node @returns {glow.dom.NodeList} Returns a new NodeList containing the filtered nodes @example // return images with a width greater than 320 glow.dom.get("img").filter(function (i) { return this.width > 320; }); */ filter: function(callback) { var ret = [], //result ri = 0, i = 0, length = this.length; for (; i < length; i++) { if (callback.apply(this[i], [i])) { ret[ri++] = this[i]; } } return r.get(ret); }, /** @name glow.dom.NodeList#children @function @description Gets the child elements of each node as a new NodeList. @returns {glow.dom.NodeList} Returns a new NodeList containing all the child nodes @example // get all list items var items = glow.dom.get("ul, ol").children(); */ children: function() { var ret = [], ri = 0, i = 0, n = 0, length = this.length, childTmp; for (; i < length; i++) { ret = ret.concat( getChildElms(this[i]) ); } return r.get(ret); }, /** @name glow.dom.NodeList#parent @function @description Gets the unique parent nodes of each node as a new NodeList. @returns {glow.dom.NodeList} Returns a new NodeList containing the parent nodes, with duplicates removed @example // elements which contain links var parents = glow.dom.get("a").parent(); */ parent: function() { var ret = [], ri = 0, i = 0, length = this.length; for (; i < length; i++) { ret[ri++] = this[i].parentNode; } return r.get(unique(ret)); }, /** @name glow.dom.NodeList#next @function @description Gets the next sibling element for each node as a new NodeList. @returns {glow.dom.NodeList} A new NodeList containing the next sibling elements. @example // gets the element following #myLink (if there is one) var next = glow.dom.get("#myLink").next(); */ next: function() { return getNextOrPrev(this, "next"); }, /** @name glow.dom.NodeList#prev @function @description Gets the previous sibling element for each node as a new NodeList. @returns {glow.dom.NodeList} A new NodeList containing the previous sibling elements. @example // gets the elements before #myLink (if there is one) var previous = glow.dom.get("#myLink").previous(); */ prev: function() { return getNextOrPrev(this, "previous"); }, /** @name glow.dom.NodeList#is @function @description Tests if all the nodes match a CSS selector. Jake: I'm deprecating this until we have time to make it faster (probably when we change our CSS selector engine) @deprecated @param {String} selector A CSS selector string @returns {Boolean} @example var bigHeadings = glow.dom.get("h1, h2, h3"); // true if (bigHeadings.is("h1, h2, h3, h4, h5, h6")) ... // false if (bigHeadings.is("a")) ... */ is: function (selector) { // TODO - this implementation needs to be optimized var nodes = glow.dom.get(selector), i = 0, iLen = this.length, j, jLen; node: for (; i < iLen; i++) { for (j = 0, jLen = nodes.length; j < jLen; j++) { if (this[i] == nodes[j]) { continue node; } } return false; } return true; }, /** @name glow.dom.NodeList#text @function @description Gets the inner text of the first node, or set the inner text of all matched nodes. @param {String} [text] String to set as inner text of nodes @returns {glow.dom.NodeList | String} If the text argument is passed then the NodeList is returned, otherwise the text is returned. @example // set text var div = glow.dom.create("").text("Hello World!"); // get text var greeting = div.text(); */ text: function (/* text */) { var args = arguments, i = 0, that = this, length = that.length; if (args.length > 0) { for (; i < length; i++) { that[i].innerHTML = ""; that[i].appendChild(doc.createTextNode(args[0])); } return that; } //innerText (empty) and textContent (undefined) don't work in safari 2 for hidden elements return that[0].innerText || that[0].textContent == undefined ? getTextNodeConcat(that[0]) : that[0].textContent; }, /** @name glow.dom.NodeList#empty @function @description Removes the contents of all the nodes. @returns {glow.dom.NodeList} @example // remove the contents of all textareas glow.dom.get("textarea").empty(); */ empty: function () { /* NOTE: I changed this to destroy all nodes within the parent, but seemed backwards incompatible with our own timetable demo so we best hadn't do it until Glow 2 */ var i = 0, len = this.length; for (; i < len; i++) { while(this[i].firstChild) { this[i].removeChild(this[i].firstChild); } } return this; }, /** @name glow.dom.NodeList#remove @function @description Removes each node from its parent node. If you no longer need the nodes, consider using {@link glow.dom.NodeList#destroy destroy} @returns {glow.dom.NodeList} @example // take all the links out of a document glow.dom.get("a").remove(); */ remove: function () { for (var that = this, i = 0, length = that.length, parentNode; i < length; i++) { if (parentNode = that[i].parentNode) { parentNode.removeChild(that[i]); } } return that; }, /** @name glow.dom.NodeList#destroy @function @description Removes each node from the DOM The node will actually be destroyed to free up memory @returns {glow.dom.NodeList} An empty NodeList @example // destroy all links in the document glow.dom.get("a").destroy(); */ destroy: function () { this.appendTo(tmpDiv); // destroy nodes tmpDiv.innerHTML = ""; // empty the nodelist Array.prototype.splice.call(this, 0, this.length); return this; }, /** @name glow.dom.NodeList#clone @function @description Gets a new NodeList containing a clone of each node. @param {Boolean} [cloneListeners=false] Also clone any event listeners assigned using Glow @returns {glow.dom.NodeList} Returns a new NodeList containing clones of all the nodes in the NodeList @example // get a copy of all heading elements var myClones = glow.dom.get("h1, h2, h3, h4, h5, h6").clone(); @example // get a copy of all anchor elements with var myAnchors = glow.dom.get("a").clone(true); */ clone: function (cloneListeners) { var ret = [], i = this.length, allCloneElms, eventIdProp = '__eventId' + glow.UID; while (i--) { ret[i] = this[i].cloneNode(true); } // some browsers (ie) also clone node properties as attributes // we need to get rid of the eventId. if (nodePropertiesCloned && !isXml(ret[0])) { allCloneElms = r.get( ret ).get("*").push( ret ); i = allCloneElms.length; while(i--) { allCloneElms[i][eventIdProp] = null; } } // shall we clone events too? if (cloneListeners) { // check the stuff we need is hanging around, we don't want // glow.dom to be dependant on glow.events as it's a circular // dependency if ( !glow.events ) { throw "glow.events required to clone event listeners"; } glow.events._copyListeners( this.get("*").push( this ), allCloneElms || r.get( ret ).get("*").push( ret ) ); } return r.get(ret); }, /** @name glow.dom.NodeList#html @function @description Gets the HTML content of the first node, or set the HTML content of all nodes.This method is not applicable to XML NodeLists.
@param {String} [html] String to set as inner HTML of nodes @returns {glow.dom.NodeList | String} If the html argument is passed, then the NodeList is returned, otherwise the inner HTML of the first node is returned. @example // get the html in #footer var footerContents = glow.dom.get("#footer").html(); @example // set a new footer glow.dom.get("#footer").html("Hello World!"); */ html: function (newHtml) { var i = 0, length = this.length; if (newHtml != undefined) { for (; i < length; i++) { this[i].innerHTML = newHtml; } return this; } return this[0].innerHTML; }, /** @name glow.dom.NodeList#width @function @description Gets the width of the first node in pixels or sets the width of all nodesThis method is not applicable to XML NodeLists.
Return value does not include the padding or border of the element in browsers supporting the correct box model. You can use this to easily get the width of the document or window, see example below. @param {Number} [width] New width in pixels. @returns {glow.dom.NodeList | Number} Width of first element in pixels, or NodeList when setting widths @example // get the width of #myDiv glow.dom.get("#myDiv").width(); @example // set the width of list items in #myDiv to 200 pixels glow.dom.get("#myDiv li").width(200); @example // get the height of the document glow.dom.get(document).width() @example // get the height of the window glow.dom.get(window).width() */ width: function(width) { if (width == undefined) { return getElmSize(this[0]).width; } setElmsSize(this, width, "width"); return this; }, /** @name glow.dom.NodeList#height @function @description Gets the height of the first element in pixels or sets the height of all nodesThis method is not applicable to XML NodeLists.
Return value does not include the padding or border of the element in browsers supporting the correct box model. You can use this to easily get the height of the document or window, see example below. @param {Number} [height] New height in pixels. @returns {glow.dom.NodeList | Number} Height of first element in pixels, or NodeList when setting heights. @example // get the height of #myDiv glow.dom.get("#myDiv").height(); @example // set the height of list items in #myDiv to 200 pixels glow.dom.get("#myDiv li").height(200); @example // get the height of the document glow.dom.get(document).height() @example // get the height of the window glow.dom.get(window).height() */ height: function(height) { if (height == undefined) { return getElmSize(this[0]).height; } setElmsSize(this, height, "height"); return this; }, /** @name glow.dom.NodeList#show @function @description Shows all hidden items in the NodeList. @returns {glow.dom.NodeList} @example // Show element with ID myDiv glow.dom.get("#myDiv").show(); @example // Show all list items within #myDiv glow.dom.get("#myDiv li").show(); */ show: function() { var i = 0, len = this.length, currItem, itemStyle; for (; i < len; i++) { /* Create a NodeList for the current item */ currItem = r.get(this[i]); itemStyle = currItem[0].style; if (currItem.css("display") == "none") { itemStyle.display = ""; itemStyle.visibility = "visible"; /* If display is still none, set to block */ if (currItem.css("display") == "none") { itemStyle.display = "block"; } } } return this; }, /** @name glow.dom.NodeList#hide @function @description Hides all items in the NodeList. @returns {glow.dom.NodeList} @example // Hides all list items within #myDiv glow.dom.get("#myDiv li").hide(); */ hide: function() { var i = 0, len = this.length, currItem; for (; i < len; i++) { currItem = r.get(this[i]); currItem[i].style.display = "none"; currItem[i].style.visibility = "hidden"; } return this; }, /** @name glow.dom.NodeList#css @function @description Gets a CSS property for the first node or sets a CSS property on all nodes.This method is not applicable to XML NodeLists.
If a single property name is passed, the corresponding value from the first node will be returned. If a single property name is passed with a value, that value will be set on all nodes on the NodeList. If an array of properties name is passed with no value, the return value is the sum of those values from the first node in the NodeList. This can be useful for getting the total of left and right padding, for example. Return values are strings. For instance, "height" will return "25px" for an element 25 pixels high. You may want to use parseInt to convert these values. Here are the compatible properties you can get, if you use one which isn't on this list the return value may differ between browsers.String nodespecs cannot be used with XML NodeLists
@param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to append to each node. @returns {glow.dom.NodeList} @example // ends every paragraph with '...' glow.dom.get('p').append( '...' ); */ append: function (nodeSpec) { var that = this, j = 0, i = 1, length = that.length, nodes; if (length == 0) { return that; } nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) : nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec); for (; nodes[j]; j++) { that[0].appendChild(nodes[j]); } for (; i < length; i++) { for (j = 0; nodes[j]; j++) { that[i].appendChild(nodes[j].cloneNode(true)); } } return that; }, /** @name glow.dom.NodeList#prepend @function @description Prepends the given elements to each node. If there is more than one node in the NodeList, then the given elements are prepended to the first node and clones are prepended to the other nodes.String nodespecs cannot be used with XML NodeLists
@param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to prepend to each node. @returns {glow.dom.NodeList} @example // prepends every paragraph with 'Paragraph: ' glow.dom.get('p').prepend( 'Paragraph: ' ); */ prepend: function (nodeSpec) { var that = this, j = 0, i = 1, length = that.length, nodes, first; if (length == 0) { return that; } nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) : nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec); first = that[0].firstChild; for (; nodes[j]; j++) { that[0].insertBefore(nodes[j], first); } for (; i < length; i++) { first = that[i].firstChild; for (j = 0; nodes[j]; j++) { that[i].insertBefore(nodes[j].cloneNode(true), first); } } return that; }, /** @name glow.dom.NodeList#appendTo @function @description Appends the NodeList to elements. If more than one element is given (i.e. if the nodespec argument is an array of nodes or a NodeList) the NodeList will be appended to the first element and clones to each subsequent element. @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to append the NodeList to. @returns {glow.dom.NodeList} @example // appends '...' to every paragraph glow.dom.create('...').appendTo('p'); */ appendTo: function (nodes) { if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); } nodes.append(this); return this; }, /** @name glow.dom.NodeList#prependTo @function @description Prepends the NodeList to elements. If more than one element is given (i.e. if the nodespec argument is an array of nodes or a NodeList) the NodeList will be prepended to the first element and clones to each subsequent element. @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to prepend the NodeList to. @returns {glow.dom.NodeList} @example // prepends 'Paragraph: ' to every paragraph glow.dom.create('Paragraph: ').prependTo('p'); */ prependTo: function (nodes) { if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); } nodes.prepend(this); return this; }, /** @name glow.dom.NodeList#after @function @description Inserts elements after each node. If there is more than one node in the NodeList, the elements will be inserted after the first node and clones inserted after each subsequent node. @param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to insert after each node @returns {glow.dom.NodeList} @example // adds a paragraph after each heading glow.dom.get('h1, h2, h3').after('...
'); */ after: function (nodeSpec) { var that = this, length = that.length, nodes, nodesLen, j, i = 1, cloned; if (length == 0) { return that; } nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) : nodeSpec instanceof r.NodeList ? nodeSpec : r.get(nodeSpec); nodesLen = nodes.length; for (j = nodesLen - 1; j >= 0; j--) { that[0].parentNode.insertBefore(nodes[j], that[0].nextSibling); } for (; i < length; i++) { cloned = nodes.clone(); for (j = nodesLen - 1; j >= 0; j--) { that[i].parentNode.insertBefore(cloned[j], that[i].nextSibling); } } return that; }, /** @name glow.dom.NodeList#before @function @description Inserts elements before each node. If there is more than one node in the NodeList, the elements will be inserted before the first node and clones inserted before each subsequent node. @param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to insert before each node @returns {glow.dom.NodeList} @example // adds a heading before each glow.dom.get('p').before('HAI!
').insertAfter('h1, h2, h3'); */ insertAfter: function (nodes) { if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); } nodes.after(this); return this; }, /** @name glow.dom.NodeList#insertBefore @function @description Insert the NodeList before each of the given elements. If more than one element is passed in, the NodeList will be inserted before the first element and clones inserted before each subsequent element. @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to insert the NodeList before @returns {glow.dom.NodeList} @example // adds a heading before each paragraph glow.dom.create('Notes for Opera
It is currently impossible to differentiate certain key events in Opera (for example the RIGHT (the right arrow key) and the apostrope (') result in the same code). For this reason pressing either of these keys will result in key listeners specified as "RIGHT" and/or "'" to be fired.Key Identifiers
The key param uses the following strings to refer to special keys, i.e. non alpha-numeric keys.Notes for Opera
The information returned from Opera about key events does not allow certain keys to be differentiated. This mainly applies to special keys, such as the arrow keys, function keys, etc. which conflict with some printable characters. */ r.Event = function ( obj ) { if( obj ) { glow.lang.apply( this, obj ); } }; /** @name glow.events.Event#attachedTo @type Object | Element @description The object/element that the listener is attached to. See the description for 'source' for more details. */ /** @name glow.events.Event#source @type Element @description The actual object/element that the event originated from. For example, you could attach a listener to an 'ol' element to listen for clicks. If the user clicked on an 'li' the source property would be the 'li' element, and 'attachedTo' would be the 'ol'. */ /** @name glow.events.Event#pageX @type Number @description The horizontal position of the mouse pointer in the page in pixels.Only available for mouse events.
*/ /** @name glow.events.Event#pageY @type Number @description The vertical position of the mouse pointer in the page in pixels.Only available for mouse events.
*/ /** @name glow.events.Event#button @type Number @description A number representing which button was pressed.Only available for mouse events.
0 for the left button, 1 for the middle button or 2 for the right button. */ /** @name glow.events.Event#relatedTarget @type Element @description The element that the mouse has come from or is going to.Only available for mouse over/out events.
*/ /** @name glow.events.Event#wheelDelta @type Number @description The number of clicks up (positive) or down (negative) that the user moved the wheel.Only available for mouse wheel events.
*/ /** @name glow.events.Event#ctrlKey @type Boolean @description Whether the ctrl key was pressed during the key event.Only available for keyboard events.
*/ /** @name glow.events.Event#shiftKey @type Boolean @description Whether the shift key was pressed during the key event.Only available for keyboard events.
*/ /** @name glow.events.Event#altKey @type Boolean @description Whether the alt key was pressed during the key event.Only available for keyboard events.
*/ /** @name glow.events.Event#capsLock @type Boolean | Undefined @description Whether caps-lock was on during the key eventOnly available for keyboard events.
If the key is not alphabetic, this property will be undefined as it is not possible to tell if caps-lock is on in this scenario. */ /** @name glow.events.Event#keyCode @type Number @description An integer number represention of the keyboard key that was pressed.Only available for keyboard events.
*/ /** @name glow.events.Event#key @type String | Undefined @description A short identifier for the key for special keys.Only available for keyboard events.
If the key was not a special key this property will be undefined. See the list of key identifiers in {@link glow.events.addKeyListener} */ /** @name glow.events.Event#charCode @type Number | Undefined @description The unicode character code for a printable character.Only available for keyboard events.
This will be undefined if the key was not a printable character. */ /** @name glow.events.Event#chr @type String @description A printable character string.Only available for keyboard events.
The string of the key that was pressed, for example 'j' or 's'. This will be undefined if the key was not a printable character. */ /** @name glow.events.Event#preventDefault @function @description Prevent the default action for events. This can also be achieved by returning false from an event callback */ r.Event.prototype.preventDefault = function () { if (this[psuedoPreventDefaultKey]) { return; } this[psuedoPreventDefaultKey] = true; if (this.nativeEvent && this.nativeEvent.preventDefault) { this.nativeEvent.preventDefault(); this.nativeEvent.returnValue = false; } }; /** @name glow.events.Event#defaultPrevented @function @description Test if the default action has been prevented. @returns {Boolean} True if the default action has been prevented. */ r.Event.prototype.defaultPrevented = function () { return !! this[psuedoPreventDefaultKey]; }; /** @name glow.events.Event#stopPropagation @function @description Stops the event propagating. For DOM events, this stops the event bubbling up through event listeners added to parent elements. The event object is marked as having had propagation stopped (see {@link glow.events.Event#propagationStopped propagationStopped}). @example // catch all click events that are not links glow.events.addListener( document, 'click', function () { alert('document clicked'); } ); glow.events.addListener( 'a', 'click', function (e) { e.stopPropagation(); } ); */ r.Event.prototype.stopPropagation = function () { if (this[psuedoStopPropagationKey]) { return; } this[psuedoStopPropagationKey] = true; var e = this.nativeEvent; if (e) { e.cancelBubble = true; if (e.stopPropagation) { e.stopPropagation(); } } }; /** @name glow.events.Event#propagationStopped @function @description Tests if propagation has been stopped for this event. @returns {Boolean} True if event propagation has been prevented. */ r.Event.prototype.propagationStopped = function () { return !! this[psuedoStopPropagationKey]; }; //cleanup to avoid mem leaks in IE r.addListener(window, "unload", clearEvents); glow.events = r; } }); /** @name glow.data @namespace @description Serialising and de-serialising data @see Using glow.data */ (window.gloader || glow).module({ name: "glow.data", library: ["glow", "1.5.0-rc2"], depends: [["glow", "1.5.0-rc2", "glow.dom"]], builder: function(glow) { //private /* PrivateProperty: TYPES hash of strings representing data types */ var TYPES = { UNDEFINED : "undefined", OBJECT : "object", NUMBER : "number", BOOLEAN : "boolean", STRING : "string", ARRAY : "array", FUNCTION : "function", NULL : "null" } /* PrivateProperty: TEXT hash of strings used in encoding/decoding */ var TEXT = { AT : "@", EQ : "=", DOT : ".", EMPTY : "", AND : "&", OPEN : "(", CLOSE : ")" } /* PrivateProperty: JSON nested hash of strings and regular expressions used in encoding/decoding Json */ var JSON = { HASH : { START : "{", END : "}", SHOW_KEYS : true }, ARRAY : { START : "[", END : "]", SHOW_KEYS : false }, DATA_SEPARATOR : ",", KEY_SEPARATOR : ":", KEY_DELIMITER : "\"", STRING_DELIMITER : "\"", SAFE_PT1 : /^[\],:{}\s]*$/, SAFE_PT2 : /\\./g, SAFE_PT3 : /\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, SAFE_PT4 : /(?:^|:|,)(?:\s*\[)+/g } /* PrivateProperty: SLASHES hash of strings and regular expressions used in encoding strings */ var SLASHES = { TEST : /[\b\n\r\t\\\f\"]/g, B : {PLAIN : "\b", ESC : "\\b"}, N : {PLAIN : "\n", ESC : "\\n"}, R : {PLAIN : "\r", ESC : "\\r"}, T : {PLAIN : "\t", ESC : "\\t"}, F : {PLAIN : "\f", ESC : "\\f"}, SL : {PLAIN : "\\", ESC : "\\\\"}, QU : {PLAIN : "\"", ESC : "\\\""} } /* PrivateMethod: _getType Callback function for glow.lang.replace to escape appropriate characters Arguments: s - the regex match to be tested Returns: The escaped version of the input. */ function _replaceSlashes(s) { switch (s) { case SLASHES.B.PLAIN: return SLASHES.B.ESC; case SLASHES.N.PLAIN: return SLASHES.N.ESC; case SLASHES.R.PLAIN: return SLASHES.R.ESC; case SLASHES.T.PLAIN: return SLASHES.T.ESC; case SLASHES.F.PLAIN: return SLASHES.F.ESC; case SLASHES.SL.PLAIN: return SLASHES.SL.ESC; case SLASHES.QU.PLAIN: return SLASHES.QU.ESC; default: return s; } } /* PrivateMethod: _getType Returns the data type of the object Arguments: object - the object to be tested Returns: A one of the TYPES constant properties that represents the data type of the object. */ function _getType(object) { if((typeof object) == TYPES.OBJECT) { if (object == null) { return TYPES.NULL; } else { return (object instanceof Array)?TYPES.ARRAY:TYPES.OBJECT; } } else { return (typeof object); } } //public glow.data = { /** @name glow.data.encodeUrl @function @description Encodes an object for use as a query string. Returns a string representing the object suitable for use as a query string, with all values suitably escaped. It does not include the initial question mark. Where the input field was an array, the key is repeated in the output. @param {Object} object The object to be encoded. This must be a hash whose values can only be primitives or arrays of primitives. @returns {String} @example var getRef = glow.data.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]}); // will return "foo=Foo&bar=Bar%201&bar=Bar2" */ encodeUrl : function (object) { var objectType = _getType(object); var paramsList = []; var listLength = 0; if (objectType != TYPES.OBJECT) { throw new Error("glow.data.encodeUrl: cannot encode item"); } else { for (var key in object) { switch(_getType(object[key])) { case TYPES.FUNCTION: case TYPES.OBJECT: throw new Error("glow.data.encodeUrl: cannot encode item"); break; case TYPES.ARRAY: for(var i = 0, l = object[key].length; i < l; i++) { switch(_getType(object[key])[i]) { case TYPES.FUNCTION: case TYPES.OBJECT: case TYPES.ARRAY: throw new Error("glow.data.encodeUrl: cannot encode item"); break; default: paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key][i]); } } break; default: paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key]); } } return paramsList.join(TEXT.AND); } }, /** @name glow.data.decodeUrl @function @description Decodes a query string into an object. Returns an object representing the data given by the query string, with all values suitably unescaped. All keys in the query string are keys of the object. Repeated keys result in an array. @param {String} string The query string to be decoded. It should not include the initial question mark. @returns {Object} @example var getRef = glow.data.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2"); // will return the object {foo: "Foo", bar: ["Bar 1", "Bar2"]} */ decodeUrl : function (text) { if(_getType(text) != TYPES.STRING) { throw new Error("glow.data.decodeUrl: cannot decode item"); } else if (text === "") { return {}; } var result = {}; var keyValues = text.split(TEXT.AND); var thisPair, key, value; for(var i = 0, l = keyValues.length; i < l; i++) { thisPair = keyValues[i].split(TEXT.EQ); if(thisPair.length != 2) { throw new Error("glow.data.decodeUrl: cannot decode item"); } else { key = decodeURIComponent(thisPair[0]); value = decodeURIComponent(thisPair[1]); switch (_getType(result[key])) { case TYPES.ARRAY: result[key][result[key].length] = value; break; case TYPES.UNDEFINED: result[key] = value; break; default: result[key] = [result[key], value]; } } } return result; }, /** @name glow.data.encodeJson @function @description Encodes an object into a string JSON representation. Returns a string representing the object as JSON. @param {Object} object The object to be encoded. This can be arbitrarily nested, but must not contain functions or cyclical structures. @returns {Object} @example var myObj = {foo: "Foo", bar: ["Bar 1", "Bar2"]}; var getRef = glow.data.encodeJson(myObj); // will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}' */ encodeJson : function (object, options) { function _encode(object, options) { if(_getType(object) == TYPES.ARRAY) { var type = JSON.ARRAY; } else { var type = JSON.HASH; } var serial = [type.START]; var len = 1; var dataType; var notFirst = false; for(var key in object) { dataType = _getType(object[key]); if(dataType != TYPES.UNDEFINED) { /* ignore undefined data */ if(notFirst) { serial[len++] = JSON.DATA_SEPARATOR; } notFirst = true; if(type.SHOW_KEYS) { serial[len++] = JSON.KEY_DELIMITER; serial[len++] = key; serial[len++] = JSON.KEY_DELIMITER; serial[len++] = JSON.KEY_SEPARATOR; } switch(dataType) { case TYPES.FUNCTION: throw new Error("glow.data.encodeJson: cannot encode item"); break; case TYPES.STRING: default: serial[len++] = JSON.STRING_DELIMITER; serial[len++] = glow.lang.replace(object[key], SLASHES.TEST, _replaceSlashes); serial[len++] = JSON.STRING_DELIMITER; break; case TYPES.NUMBER: case TYPES.BOOLEAN: serial[len++] = object[key]; break; case TYPES.OBJECT: case TYPES.ARRAY: serial[len++] = _encode(object[key], options); break; case TYPES.NULL: serial[len++] = TYPES.NULL; break; } } } serial[len++] = type.END; return serial.join(TEXT.EMPTY); } options = options || {}; var type = _getType(object); if((type == TYPES.OBJECT) || (type == TYPES.ARRAY)) { return _encode(object, options); } else { throw new Error("glow.data.encodeJson: cannot encode item"); } }, /** @name glow.data.decodeJson @function @description Decodes a string JSON representation into an object. Returns a JavaScript object that mirrors the data given. @param {String} string The string to be decoded. Must be valid JSON. @param {Object} opts Zero or more of the following as properties of an object: @param {Boolean} [opts.safeMode=false] Whether the string should only be decoded if it is deemed "safe". The json.org regular expression checks are used. @returns {Object} @example var getRef = glow.data.decodeJson('{foo: "Foo", bar: ["Bar 1", "Bar2"]}'); // will return {foo: "Foo", bar: ["Bar 1", "Bar2"]} var getRef = glow.data.decodeJson('foobar', {safeMode: true}); // will throw an error */ decodeJson : function (text, options) { if(_getType(text) != TYPES.STRING) { throw new Error("glow.data.decodeJson: cannot decode item"); } options = options || {}; options.safeMode = options.safeMode || false; var canEval = true; if(options.safeMode) { canEval = (JSON.SAFE_PT1.test(text.replace(JSON.SAFE_PT2, TEXT.AT).replace(JSON.SAFE_PT3, JSON.ARRAY.END).replace(JSON.SAFE_PT4, TEXT.EMPTY))); } if(canEval) { try { return eval(TEXT.OPEN + text + TEXT.CLOSE); } catch(e) {/* continue to error */} } throw new Error("glow.data.decodeJson: cannot decode item"); }, /** @name glow.data.escapeHTML @function @description Escape HTML entities. Returns a string with HTML entities escaped. @param {String} string The string to be escaped. @returns {String} @example // useful for protecting against XSS attacks: var fieldName = '" onclick="alert(\'hacked\')" name="'; // but should be used in all cases like this: glow.dom.create(''); */ escapeHTML : function (html) { return glow.dom.create('').text(html).html(); } }; } }); /** @name glow.net @namespace @description Sending data to & from the server @see Using glow.net */ (window.gloader || glow).module({ name: "glow.net", library: ["glow", "1.5.0-rc2"], depends: [["glow", "1.5.0-rc2", "glow.data", "glow.events"]], builder: function(glow) { //private var STR = { XML_ERR:"Cannot get response as XML, check the mime type of the data", POST_DEFAULT_CONTENT_TYPE:'application/x-www-form-urlencoded;' }, /** * @name glow.net.scriptElements * @private * @description Script elements that have been added via {@link glow.net.loadScript loadScript} * @type Array */ scriptElements = [], /** * @name glow.net.callbackPrefix * @private * @description Callbacks in _jsonCbs will be named this + a number * @type String */ callbackPrefix = "c", /** * @name glow.net.globalObjectName * @private * @description Name of the global object used to store loadScript callbacks * @type String */ globalObjectName = "_" + glow.UID + "loadScriptCbs", events = glow.events, emptyFunc = function(){}; /** * @name glow.net.xmlHTTPRequest * @private * @function * @description Creates an xmlHTTPRequest transport * @returns Object */ function xmlHTTPRequest() { //try IE first. IE7's xmlhttprequest and XMLHTTP6 are broken. Avoid avoid avoid! if (window.ActiveXObject) { return (xmlHTTPRequest = function() { return new ActiveXObject("MSXML2.XMLHTTP"); })(); } else { return (xmlHTTPRequest = function() { return new XMLHttpRequest(); })(); } } /** * @name glow.net.populateOptions * @private * @function * @description Adds defaults to get / post option object * @param {Object} opts Object to add defaults to * @returns Object */ function populateOptions(opts) { return glow.lang.apply( { onLoad: emptyFunc, onError: emptyFunc, onAbort: emptyFunc, headers: {}, async: true, useCache: false, data: null, defer: false }, opts || {} ); } /* PrivateMethod: noCacheUrl Adds random numbers to the querystring of a url so the browser doesn't use a cached version */ function noCacheUrl(url) { return [url, (/\?/.test(url) ? "&" : "?"), "a", new Date().getTime(), parseInt(Math.random()*100000)].join(""); } /* PrivateMethod: makeXhrRequest Makes an http request */ /** * @name glow.net.makeXhrRequest * @private * @function * @description Makes an xhr http request * @param {String} method HTTP Method * @param {String} url URL of the request * @param {Object} Options, see options for {@link glow.net.get} * @returns Object */ function makeXhrRequest(method, url, opts) { var req = xmlHTTPRequest(), //request object data = opts.data && (typeof opts.data == "string" ? opts.data : glow.data.encodeUrl(opts.data)), i, request = new Request(req, opts); if (!opts.useCache) { url = noCacheUrl(url); } //open needs to go first to maintain cross-browser support for readystates req.open(method, url, opts.async); //add custom headers for (i in opts.headers) { req.setRequestHeader(i, opts.headers[i]); } function send() { request.send = emptyFunc; if (opts.async) { //sort out the timeout if there is one if (opts.timeout) { request._timeout = setTimeout(function() { abortRequest(request); var response = new Response(req, true); events.fire(request, "error", response); }, opts.timeout * 1000); } req.onreadystatechange = function() { if (req.readyState == 4) { //clear the timeout request._timeout && clearTimeout(request._timeout); //set as completed request.completed = true; var response = new Response(req); if (response.wasSuccessful) { events.fire(request, "load", response); } else { events.fire(request, "error", response); } } }; req.send(data); return request; } else { req.send(data); request.completed = true; var response = new Response(req); if (response.wasSuccessful) { events.fire(request, "load", response); } else { events.fire(request, "error", response); } return response; } } request.send = send; return opts.defer ? request : send(); } //public var r = {}; //the module /** @name glow.net.get @function @description Makes an HTTP GET request to a given url @param {String} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains, to do that you must put your data in a javascript file and use {@link glow.net.loadScript} to fetch it. @param {Object} opts Options Object of options. @param {Function} [opts.onLoad] Callback to execute when the request has sucessfully loaded The callback is passed a Response object as its first parameter. @param {Function} [opts.onError] Callback to execute if the request was unsucessful The callback is passed a Response object as its first parameter. This callback will also be run if a request times out. @param {Function} [opts.onAbort] Callback to execute if the request is aborted @param {Object} [opts.headers] A hash of headers to send along with the request Eg {"Accept-Language": "en-gb"} @param {Boolean} [opts.async=true] Should the request be performed asynchronously? @param {Boolean} [opts.useCache=false] Allow a cached response If false, a random number is added to the query string to ensure a fresh version of the file is being fetched @param {Number} [opts.timeout] Time to allow for the request in seconds No timeout is set by default. Only applies for async requests. Once the time is reached, the error event will fire with a "408" status code. @param {Boolean} [opts.defer=false] Do not send the request straight away Deferred requests need to be triggered later using myRequest.send() @returns {glow.net.Request|glow.net.Response} A response object for non-defered sync requests, otherwise a request object is returned @example var request = glow.net.get("myFile.html", { onLoad: function(response) { alert("Got file:\n\n" + response.text()); }, onError: function(response) { alert("Error getting file: " + response.statusText()); } }); */ r.get = function(url, o) { o = populateOptions(o); return makeXhrRequest('GET', url, o); }; /** @name glow.net.post @function @description Makes an HTTP POST request to a given url @param {String} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains, to do that you must put your data in a javascript file and use {@link glow.net.loadScript} to fetch it. @param {Object|String} data Data to post, either as a JSON-style object or a urlEncoded string @param {Object} opts Same options as {@link glow.net.get} @returns {Number|glow.net.Response} An integer identifying the async request, or the response object for sync requests @example var postRef = glow.net.post("myFile.html", {key:"value", otherkey:["value1", "value2"]}, { onLoad: function(response) { alert("Got file:\n\n" + response.text()); }, onError: function(response) { alert("Error getting file: " + response.statusText()); } } ); */ r.post = function(url, data, o) { o = populateOptions(o); o.data = data; if (!o.headers["Content-Type"]) { o.headers["Content-Type"] = STR.POST_DEFAULT_CONTENT_TYPE; } return makeXhrRequest('POST', url, o); }; /** @name glow.net.loadScript @function @description Loads data by adding a script element to the end of the page This can be used cross domain, but should only be used with trusted sources as any javascript included in the script will be executed. @param {String} url Url of the script. Use "{callback}" in the querystring as the callback name if the data source supports it, then you can use the options below @param {Object} [opts] An object of options to use if "{callback}" is specified in the url. @param {Function} [opts.onLoad] Called when loadScript succeeds. The parameters are passed in by the external data source @param {Function} [opts.onError] Called on timeout No parameters are passed @param {Function} [opts.onAbort] Called if the request is aborted @param {Boolean} [opts.useCache=false] Allow a cached response @param {Number} [opts.timeout] Time to allow for the request in seconds @param {String} [opts.charset] Charset attribute value for the script @returns {glow.net.Request} @example glow.net.loadScript("http://www.server.com/json/tvshows.php?jsoncallback={callback}", { onLoad: function(data) { alert("Data loaded"); } }); */ r.loadScript = function(url, opts) { //id of the request var newIndex = scriptElements.length, //script element that gets inserted on the page script, //generated name of the callback, may not be used callbackName = callbackPrefix + newIndex, opts = populateOptions(opts), request = new Request(newIndex, opts), url = opts.useCache ? url : noCacheUrl(url), //the global property used to hide callbacks globalObject = window[globalObjectName] || (window[globalObjectName] = {}); //assign onload if (opts.onLoad) { globalObject[callbackName] = function() { //clear the timeout request._timeout && clearTimeout(request._timeout); //set as completed request.completed = true; opts.onLoad.apply(this, arguments); // cleanup glow.dom.get(script).destroy(); // clean up references to prevent leaks script = scriptElements[newIndex] = globalObject[callbackName] = undefined; delete globalObject[callbackName]; delete scriptElements[newIndex]; } url = glow.lang.interpolate(url, {callback: globalObjectName + "." + callbackName}); } script = scriptElements[newIndex] = document.createElement("script"); if (opts.charset) { script.charset = opts.charset; } //add abort event events.addListener(request, "abort", opts.onAbort); glow.ready(function() { //sort out the timeout if (opts.timeout) { request._timeout = setTimeout(function() { abortRequest(request); opts.onError(); }, opts.timeout * 1000); } //using setTimeout to stop Opera 9.0 - 9.26 from running the loaded script before other code //in the current script block if (glow.env.opera) { setTimeout(function() { if (script) { //script may have been removed already script.src = url; } }, 0); } else { script.src = url; } //add script to page document.body.appendChild(script); }); return request; } /** * @name glow.net.abortRequest * @private * @function * @description Aborts the request * Doesn't trigger any events * * @param {glow.net.Request} req Request Object * @returns this */ function abortRequest(req) { var nativeReq = req.nativeRequest, callbackIndex = req._callbackIndex; //clear timeout req._timeout && clearTimeout(req._timeout); //different if request came from loadScript if (nativeReq) { //clear listeners nativeReq.onreadystatechange = emptyFunc; nativeReq.abort(); } else if (callbackIndex) { //clear callback window[globalObjectName][callbackPrefix + callbackIndex] = emptyFunc; //remove script element glow.dom.get(scriptElements[callbackIndex]).destroy(); } } /** * @name glow.net.Request * @class * @description Returned by {@link glow.net.post post}, {@link glow.net.get get} async requests and {@link glow.net.loadScript loadScript} * @glowPrivateConstructor There is no direct constructor, since {@link glow.net.post post} and {@link glow.net.get get} create the instances. */ /** * @name glow.net.Request#event:load * @event * @param {glow.events.Event} event Event Object * @description Fired when the request is sucessful * For a get / post request, this will be fired when request returns * with an HTTP code of 2xx. loadScript requests will fire 'load' only * if {callback} is used in the URL. */ /** * @name glow.net.Request#event:abort * @event * @param {glow.events.Event} event Event Object * @description Fired when the request is aborted * If you cancel the default (eg, by returning false) the request * will continue. * @description Returned by {@link glow.net.post glow.net.post}, {@link glow.net.get glow.net.get} async requests and {@link glow.net.loadScript glow.net.loadScript} * @see Using glow.net * @glowPrivateConstructor There is no direct constructor, since {@link glow.net.post glow.net.post} and {@link glow.net.get glow.net.get} create the instances. */ /** * @name glow.net.Request#event:error * @event * @param {glow.events.Event} event Event Object * @description Fired when the request is unsucessful * For a get/post request, this will be fired when request returns * with an HTTP code which isn't 2xx or the request times out. loadScript * calls will fire 'error' only if the request times out. */ /* We don't want users to create instances of this class, so the constructor is documented out of view of jsdoc @param {Object} requestObj Object which represents the request type. For XHR requests it should be an XmlHttpRequest object, for loadScript requests it should be a number, the Index of the callback in glow.net._jsonCbs @param {Object} opts Zero or more of the following as properties of an object: @param {Function} [opts.onLoad] Called when the request is sucessful @param {Function} [opts.onError] Called when a request is unsucessful @param {Function} [opts.onAbort] Called when a request is aborted */ function Request(requestObj, opts) { /** * @name glow.net.Request#_timeout * @private * @description timeout ID. This is set by makeXhrRequest or loadScript * @type Number */ this._timeout = null; /** * @name glow.net.Request#complete * @description Boolean indicating whether the request has completed * @example // request.complete with an asynchronous call var request = glow.net.get( "myFile.html", { async: true, onload: function(response) { alert(request.complete); // returns true } } ); alert(request.complete); // returns boolean depending on timing of asynchronous call // request.complete with a synchronous call var request = glow.net.get("myFile.html", {async: false;}); alert(request.complete); // returns true * @type Boolean */ this.complete = false; if (typeof requestObj == "number") { /** * @name glow.net.Request#_callbackIndex * @private * @description Index of the callback in glow.net._jsonCbs * This is only relavent for requests made via loadscript using the * {callback} placeholder * @type Number */ this._callbackIndex = requestObj; } else { /** * @name glow.net.Request#nativeRequest * @description The request object from the browser. * This may not have the same properties and methods across user agents. * Also, this will be undefined if the request originated from loadScript. * @example var request = glow.net.get( "myFile.html", { async: true, onload: function(response) { alert(request.NativeObject); // returns Object() } } ); * @type Object */ this.nativeRequest = requestObj; } //assign events var eventNames = ["Load", "Error", "Abort"], i=0; for (; i < 3; i++) { events.addListener(this, eventNames[i].toLowerCase(), opts["on" + eventNames[i]]); } } Request.prototype = { /** @name glow.net.Request#send @function @description Sends the request. This is done automatically unless the defer option is set @example var request = glow.net.get( "myFile.html", { onload : function(response) {alert("Loaded");}, defer: true } ); request.send(); // returns "Loaded" @returns {Object} This for async requests or a response object for sync requests */ //this function is assigned by makeXhrRequest send: function() {}, /** * @name glow.net.Request#abort * @function * @description Aborts an async request * The load & error events will not fire. If the request has been * made using {@link glow.net.loadScript loadScript}, the script * may still be loaded but the callback will not be fired. * @example var request = glow.net.get( "myFile.html", { async: true, defer: true, onabort: function() { alert("Something bad happened. The request was aborted."); } } ); request.abort(); // returns "Something bad happened. The request was aborted" * @returns this */ abort: function() { if (!this.completed && !events.fire(this, "abort").defaultPrevented()) { abortRequest(this); } return this; } }; /** @name glow.net.Response @class @description Provided in callbacks to {@link glow.net.post glow.net.post} and {@link glow.net.get glow.net.get} @see Using glow.net @glowPrivateConstructor There is no direct constructor, since {@link glow.net.post glow.net.post} and {@link glow.net.get glow.net.get} create the instances. */ /* These params are hidden as we don't want users to try and create instances of this... @param {XMLHttpRequest} nativeResponse @param {boolean} [timedOut=false] */ function Response(nativeResponse, timedOut) { //run Event constructor events.Event.call(this); /** @name glow.net.Response#nativeResponse @description The response object from the browser. This may not have the same properties and methods across user agents. @type Object */ this.nativeResponse = nativeResponse; /** @name glow.net.Response#status @description HTTP status code of the response @type Number */ //IE reports status as 1223 rather than 204, for laffs this.status = timedOut ? 408 : nativeResponse.status == 1223 ? 204 : nativeResponse.status; /** * @name glow.net.Response#timedOut * @description Boolean indicating if the requests time out was reached. * @type Boolean */ this.timedOut = !!timedOut; /** * @name glow.net.Response#wasSuccessful * @description Boolean indicating if the request returned successfully. * @type Boolean */ this.wasSuccessful = (this.status >= 200 && this.status < 300) || //from cache this.status == 304 || //watch our for requests from file:// (this.status == 0 && nativeResponse.responseText); } //don't want to document this inheritance, it'll just confuse the user glow.lang.extend(Response, events.Event, { /** @name glow.net.Response#text @function @description Gets the body of the response as plain text @returns {String} Response as text */ text: function() { return this.nativeResponse.responseText; }, /** @name glow.net.Response#xml @function @description Gets the body of the response as xml @returns {xml} Response as XML */ xml: function() { if (!this.nativeResponse.responseXML) { throw new Error(STR.XML_ERR); } return this.nativeResponse.responseXML; }, /** @name glow.net.Response#json @function @description Gets the body of the response as a json object @param {Boolean} [safeMode=false] If true, the response will be parsed using a string parser which will filter out non-JSON javascript, this will be slower but recommended if you do not trust the data source. @returns {Object} */ json: function(safe) { return glow.data.decodeJson(this.text(), {safeMode:safe}); }, /** @name glow.net.Response#header @function @description Gets a header from the response @param {String} name Header name @returns {String} Header value @example var contentType = myResponse.header("Content-Type"); */ header: function(name) { return this.nativeResponse.getResponseHeader(name); }, /** @name glow.net.Response#statusText @function @description Gets the meaning of {@link glow.net.Response#status myResponse.status} @returns {String} */ statusText: function() { return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText; } }) glow.net = r; } }); /** @name glow.tweens @namespace @description Functions for modifying animations @see What are tweens? */ (window.gloader || glow).module({ name: "glow.tweens", library: ["glow", "1.5.0-rc2"], depends: [], builder: function(glow) { /* PrivateMethod: _reverse Takes a tween function and returns a function which does the reverse */ function _reverse(tween) { return function(t) { return 1 - tween(1 - t); } } glow.tweens = { /** @name glow.tweens.linear @function @description Returns linear tween. Will transition values from start to finish with no acceleration or deceleration. @returns {Function} */ linear: function() { return function(t) { return t; }; }, /** @name glow.tweens.easeIn @function @description Creates a tween which starts off slowly and accelerates. @param {Number} [strength=2] How strong the easing is. A higher number means the animation starts off slower and ends quicker. @returns {Function} */ easeIn: function(strength) { strength = strength || 2; return function(t) { return Math.pow(1, strength - 1) * Math.pow(t, strength); } }, /** @name glow.tweens.easeOut @function @description Creates a tween which starts off fast and decelerates. @param {Number} [strength=2] How strong the easing is. A higher number means the animation starts off faster and ends slower @returns {Function} */ easeOut: function(strength) { return _reverse(this.easeIn(strength)); }, /** @name glow.tweens.easeBoth @function @description Creates a tween which starts off slowly, accelerates then decelerates after the half way point. This produces a smooth and natural looking transition. @param {Number} [strength=2] How strong the easing is. A higher number produces a greater difference between start / end speed and the mid speed. @returns {Function} */ easeBoth: function(strength) { return this.combine(this.easeIn(strength), this.easeOut(strength)); }, /** @name glow.tweens.overshootIn @function @description Returns the reverse of {@link glow.tweens.overshootOut overshootOut} @param {Number} [amount=1.70158] How much to overshoot. The default is 1.70158 which results in a 10% overshoot. @returns {Function} */ overshootIn: function(amount) { return _reverse(this.overshootOut(amount)); }, /** @name glow.tweens.overshootOut @function @description Creates a tween which overshoots its end point then returns to its end point. @param {Number} [amount=1.70158] How much to overshoot. The default is 1.70158 which results in a 10% overshoot. @returns {Function} */ overshootOut: function(amount) { amount = amount || 1.70158; return function(t) { if (t == 0 || t == 1) { return t; } return ((t -= 1)* t * ((amount + 1) * t + amount) + 1); } }, /** @name glow.tweens.overshootBoth @function @description Returns a combination of {@link glow.tweens.overshootIn overshootIn} and {@link glow.tweens.overshootOut overshootOut} @param {Number} [amount=1.70158] How much to overshoot. The default is 1.70158 which results in a 10% overshoot. @returns {Function} */ overshootBoth: function(amount) { return this.combine(this.overshootIn(amount), this.overshootOut(amount)); }, /** @name glow.tweens.bounceIn @function @description Returns the reverse of {@link glow.tweens.bounceOut bounceOut} @returns {Function} */ bounceIn: function() { return _reverse(this.bounceOut()); }, /** @name glow.tweens.bounceOut @function @description Returns a tween which bounces against the final value 3 times before stopping @returns {Function} */ bounceOut: function() { return function(t) { if (t < (1 / 2.75)) { return 7.5625 * t * t; } else if (t < (2 / 2.75)) { return (7.5625 * (t -= (1.5 / 2.75)) * t + .75); } else if (t < (2.5 / 2.75)) { return (7.5625 * (t -= (2.25 / 2.75)) * t + .9375); } else { return (7.5625 * (t -= (2.625 / 2.75)) * t + .984375); } }; }, /** @name glow.tweens.bounceBoth @function @description Returns a combination of {@link glow.tweens.bounceIn bounceIn} and {@link glow.tweens.bounceOut bounceOut} @returns {Function} */ bounceBoth: function() { return this.combine(this.bounceIn(), this.bounceOut()); }, /** @name glow.tweens.elasticIn @function @description Returns the reverse of {@link glow.tweens.elasticOut elasticOut} @param {Number} [amplitude=1] How strong the elasticity is. @param {Number} [period=0.3] The frequency period. @returns {Function} */ elasticIn: function(a, p) { return _reverse(this.elasticOut(a, p)); }, /** @name glow.tweens.elasticOut @function @description Creates a tween which has an elastic movement. You can tweak the tween using the parameters but you'll probably find the defaults sufficient. @param {Number} [amplitude=1] How strong the elasticity is. @param {Number} [period=0.3] The frequency period. @returns {Function} */ elasticOut: function(a, p) { return function (t) { if (t == 0 || t == 1) { return t; } if (!p) { p = 0.3; } if (!a || a < 1) { a = 1; var s = p / 4; } else { var s = p / (2 * Math.PI) * Math.asin(1 / a); } return a * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / p) + 1; } }, /** @name glow.tweens.elasticBoth @function @description Returns a combination of {@link glow.tweens.elasticIn elasticIn} and {@link glow.tweens.elasticOut elasticOut} @param {Number} [amplitude=1] How strong the elasticity is. @param {Number} [period=0.3] The frequency period. @returns {Function} */ elasticBoth: function(a, p) { p = p || 0.45; return this.combine(this.elasticIn(a, p), this.elasticOut(a, p)); }, /** @name glow.tweens.combine @function @description Create a tween from two tweens. This can be useful to make custom tweens which, for example, start with an easeIn and end with an overshootOut. To keep the motion natural, you should configure your tweens so the first ends and the same velocity that the second starts. @param {Function} tweenIn Tween to use for the first half @param {Function} tweenOut Tween to use for the second half @example // 4.5 has been chosen for the easeIn strength so it // ends at the same velocity as overshootOut starts. var myTween = glow.tweens.combine( glow.tweens.easeIn(4.5), glow.tweens.overshootOut() ); @returns {Function} */ combine: function(tweenIn, tweenOut) { return function (t) { if (t < 0.5) { return tweenIn(t * 2) / 2; } else { return tweenOut((t - 0.5) * 2) / 2 + 0.5; } } } }; } }); /** @name glow.anim @namespace @description Simple and powerful animations. @requires glow, glow.tweens, glow.events, glow.dom @see What are tweens? @see Guide to creating animations, with interactive examples */ (window.gloader || glow).module({ name: "glow.anim", library: ["glow", "1.5.0-rc2"], depends: [["glow", "1.5.0-rc2", "glow.tweens", "glow.events", "glow.dom"]], builder: function(glow) { //private var $ = glow.dom.get, manager, events = glow.events, dom = glow.dom, get = dom.get, hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/, noNegatives = /width|height|padding|opacity/, usesYAxis = /height|top/, getUnit = /(\D+)$/, testElement = dom.create(''); (function() { var queue = [], //running animations queueLen = 0, intervalTime = 1, //ms between intervals interval; //holds the number for the interval manager = { /** @name glow.anim-manager.addToQueue @private @function @description Adds an animation to the queue. */ addToQueue: function(anim) { //add the item to the queue queue[queueLen++] = anim; anim._playing = true; anim._timeAnchor = anim._timeAnchor || new Date().valueOf(); if (!interval) { this.startInterval(); } }, /** @name glow.anim-manager.removeFromQueue @private @function @description Removes an animation from the queue. */ removeFromQueue: function(anim) { for (var i = 0; i < queueLen; i++) { if (queue[i] == anim) { queue.splice(i, 1); anim._timeAnchor = null; anim._playing = false; //stop the queue if there's nothing in it anymore if (--queueLen == 0) { this.stopInterval(); } return; } } }, /** @name glow.anim-manager.startInterval @private @function @description Start processing the queue every interval. */ startInterval: function() { interval = window.setInterval(this.processQueue, intervalTime); }, /** @name glow.anim-manager.stopInterval @private @function @description Stop processing the queue. */ stopInterval: function() { window.clearInterval(interval); interval = null; }, /** @name glow.anim-manager.processQueue @private @function @description Animate each animation in the queue. */ processQueue: function() { var anim, i, now = new Date().valueOf(); for (i = 0; i < queueLen; i++) { anim = queue[i]; if (anim.position == anim.duration) { manager.removeFromQueue(anim); //need to decrement the index because we've just removed an item from the queue i--; events.fire(anim, "complete"); if (anim._opts.destroyOnComplete) { anim.destroy(); } continue; } if (anim.useSeconds) { anim.position = (now - anim._timeAnchor) / 1000; if (anim.position > anim.duration) { anim.position = anim.duration; } } else { anim.position++; } anim.value = anim.tween(anim.position / anim.duration); events.fire(anim, "frame"); } } }; })(); /** @name glow.anim.convertCssUnit @private @function @param {nodelist} element @param {string|number} fromValue Assumed pixels. @param {string} toUnit (em|%|pt...) @param {string} axis (x|y) @description Converts a css unit. We need to know the axis for calculating relative values, since they're relative to the width / height of the parent element depending on the situation. */ function convertCssUnit(element, fromValue, toUnit, axis) { var elmStyle = testElement[0].style, axisProp = (axis == "x") ? "width" : "height", startPixelValue, toUnitPixelValue; //reset stuff that may affect the width / height elmStyle.margin = elmStyle.padding = elmStyle.border = "0"; startPixelValue = testElement.css(axisProp, fromValue).insertAfter(element)[axisProp](); //using 10 of the unit then dividing by 10 to increase accuracy toUnitPixelValue = testElement.css(axisProp, 10 + toUnit)[axisProp]() / 10; testElement.remove(); return startPixelValue / toUnitPixelValue; } /** @name glow.anim.keepWithinRange @private @function @param num @param [start] @param [end] @description Takes a number then an (optional) lower range and an (optional) upper range. If the number is outside the range the nearest range boundary is returned, else the number is returned. */ function keepWithinRange(num, start, end) { if (start !== undefined && num < start) { return start; } if (end !== undefined && num > end) { return end; } return num; } /** @name glow.anim.buildAnimFunction @private @function @param element @param spec @description Builds a function for an animation. */ function buildAnimFunction(element, spec) { var cssProp, r = ["a=(function(){"], rLen = 1, fromUnit, unitDefault = [0,"px"], to, from, unit, a; for (cssProp in spec) { r[rLen++] = 'element.css("' + cssProp + '", '; //fill in the blanks if (typeof spec[cssProp] != "object") { to = spec[cssProp]; } else { to = spec[cssProp].to; } if ((from = spec[cssProp].from) === undefined) { if (cssProp == "font-size" || cssProp == "background-position") { throw new Error("From value must be set for " + cssProp); } from = element.css(cssProp); } //TODO help some multi value things? if (hasUnits.test(cssProp)) { //normalise the units for unit-ed values unit = (getUnit.exec(to) || unitDefault)[1]; fromUnit = (getUnit.exec(from) || unitDefault)[1]; //make them numbers, we have the units seperate from = parseFloat(from) || 0; to = parseFloat(to) || 0; //if the units don't match, we need to have a play if (from && unit != fromUnit) { if (cssProp == "font-size") { throw new Error("Units must be the same for font-size"); } from = convertCssUnit(element, from + fromUnit, unit, usesYAxis.test(cssProp) ? "y" : "x"); } if (noNegatives.test(cssProp)) { r[rLen++] = 'keepWithinRange((' + (to - from) + ' * this.value) + ' + from + ', 0) + "' + unit + '"'; } else { r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from + ' + "' + unit + '"'; } } else if (! (isNaN(from) || isNaN(to))) { //both pure numbers from = Number(from); to = Number(to); r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from; } else if (cssProp.indexOf("color") != -1) { to = dom.parseCssColor(to); if (! glow.lang.hasOwnProperty(from, "r")) { from = dom.parseCssColor(from); } r[rLen++] = '"rgb(" + keepWithinRange(Math.round(' + (to.r - from.r) + ' * this.value + ' + from.r + '), 0, 255) + "," + keepWithinRange(Math.round(' + (to.g - from.g) + ' * this.value + ' + from.g + '), 0, 255) + "," + keepWithinRange(Math.round(' + (to.b - from.b) + ' * this.value + ' + from.b + '), 0, 255) + ")"'; } else if (cssProp == "background-position") { var vals = {}, fromTo = ["from", "to"], unit = (getUnit.exec(from) || unitDefault)[1]; vals.fromOrig = from.toString().split(/\s/); vals.toOrig = to.toString().split(/\s/); if (vals.fromOrig[1] === undefined) { vals.fromOrig[1] = "50%"; } if (vals.toOrig[1] === undefined) { vals.toOrig[1] = "50%"; } for (var i = 0; i < 2; i++) { vals[fromTo[i] + "X"] = parseFloat(vals[fromTo[i] + "Orig"][0]); vals[fromTo[i] + "Y"] = parseFloat(vals[fromTo[i] + "Orig"][1]); vals[fromTo[i] + "XUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][0]) || unitDefault)[1]; vals[fromTo[i] + "YUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][1]) || unitDefault)[1]; } if ((vals.fromXUnit !== vals.toXUnit) || (vals.fromYUnit !== vals.toYUnit)) { throw new Error("Mismatched axis units cannot be used for " + cssProp); } r[rLen++] = '(' + (vals.toX - vals.fromX) + ' * this.value + ' + vals.fromX + ') + "' + vals.fromXUnit + ' " + (' + (vals.toY - vals.fromY) + ' * this.value + ' + vals.fromY + ') + "' + vals.fromYUnit + '"'; } r[rLen++] = ');'; } r[rLen++] = "})"; return eval(r.join("")); } //public var r = {}; //return object /** @name glow.anim.css @function @description Animates CSS properties of an element. @param {String | glow.dom.NodeList | Element} element Element to animate. This can be a CSS selector (first match will be used), {@link glow.dom.NodeList} (first node will be used), or a DOM element. @param {Number} duration Animation duration, in seconds by default. @param {Object} spec An object describing the properties to animate. This object should consist of property names corresponding to the CSS properties you wish to animate, and values which are objects with 'from' and 'to' properties with the values to animate between or a number/string representing the value to animate to. If the 'from' property is absent, the elements current CSS value will be used instead. See the spec example below for more information. @param {Object} opts Optional options object. @param {Boolean} [opts.useSeconds=true] Specifies whether duration should be in seconds rather than frames. @param {Function} [opts.tween=linear tween] The way the value moves through time. See {@link glow.tweens}. @example // an example of an spec object { "height": {from: "10px", to: "100px"}, "width": "100px", "font-size": {from: "0.5em", to: "1.3em"} } @example // animate an elements height and opacity to 0 from current values over 1 second glow.anim.css("#myElement", 1, { "height" : 0, "opacity" : 0 }).start(); @returns {glow.anim.Animation} */ r.css = function(element, duration, spec, opts) { element = get(element); // Fix for trac 156 - glow.anim.css should fail better if the element doesn't exist if (!element[0]) { throw new Error("Invalid element passed into glow.anim.css"); } var anim = new r.Animation(duration, opts), cssProp; events.addListener(anim, "frame", buildAnimFunction(element, spec)); return anim; }; /** @name glow.anim-slideElement @private @function @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} duration Animation duration in seconds. @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @description Builds a function for an animation. */ slideElement = function slideElement(element, duration, action, opts) { duration = duration || 0.5; // normalise 'element' to NodeList element = $(element); opts = glow.lang.apply({ tween: glow.tweens.easeBoth(), onStart: function(){}, onComplete: function(){} }, opts); var i = 0, thatlength = element.length, completeHeight, fromHeight, channels = [], timeline; for(; i < thatlength; i++) { if (action == "up" || (action == "toggle" && element.slice(i, i+1).height() > 0)) { element[i].style.overflow = 'hidden'; // give the element layout in IE if (glow.env.ie < 8) { element[i].style.zoom = 1; } completeHeight = 0; fromHeight = element.slice(i, i+1).height(); } else if (action == "down" || (action = "toggle" && element.slice(i, i+1).height() == 0)) { fromHeight = element.slice(i, i+1).height(); element[i].style.height = "auto"; completeHeight = element.slice(i, i+1).height(); element[i].style.height = fromHeight + "px"; } channels[i] = [ glow.anim.css(element[i], duration, { 'height': {from: fromHeight, to: completeHeight} }, { tween: opts.tween }) ]; } timeline = new glow.anim.Timeline(channels); events.addListener(timeline, "complete", function() { // return heights to "auto" for slide down element.each(function() { if (this.style.height != "0px") { this.style.height = "auto"; } }) }); events.addListener(timeline, "start", opts.onStart); events.addListener(timeline, "complete", opts.onComplete); // return & start our new timeline return timeline.start(); }; /** @name glow.anim.slideDown @function @description Slide a NodeList down from a height of 0 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.slideDown("#menu", 1); **/ r.slideDown = function(element, duration, opts) { return slideElement(element, duration, 'down', opts); }; /** @name glow.anim.slideUp @function @description Slide a NodeList up to a height of 0 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.slideUp("#menu", 1); **/ r.slideUp = function(element, duration, opts) { return slideElement(element, duration, 'up', opts); }; /** @name glow.anim.slideToggle @function @description Toggle a NodeList Up or Down depending on it's present state. @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.slideToggle("#menu", 1); **/ r.slideToggle = function(element, duration, opts) { return slideElement(element, duration, 'toggle', opts); }; /** @name glow.anim.fadeOut @function @description Fade out a set of elements @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.fadeOut("#menu", 1); **/ r.fadeOut = function(element, duration, opts) { return r.fadeTo(element, 0, duration, opts) }; /** @name glow.anim.fadeIn @function @description Fade in a set of elements @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.fadeIn("#menu", 1); **/ r.fadeIn = function(element, duration, opts){ r.fadeTo(element, 1, duration, opts); }; /** @name glow.anim.fadeTo @function @description Fade a set of elements to a given opacity @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {Number} opacity fade to opacity level between 0 & 1. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.fadeTo("#menu", 0.5, 1); **/ r.fadeTo = function(element, opacity, duration, opts){ duration = duration || 0.5; // normalise 'element' to NodeList element = $(element); opts = glow.lang.apply({ tween: glow.tweens.easeBoth(), onStart: function(){}, onComplete: function(){} }, opts); var i = 0, thatlength = element.length, channels = [], timeline; for(; i < thatlength; i++) { channels[i] = [ glow.anim.css(element[i], duration, { 'opacity': opacity }, { tween: opts.tween }) ]; } timeline = new glow.anim.Timeline(channels); events.addListener(timeline, "start", opts.onStart); events.addListener(timeline, "complete", opts.onComplete); // return & start our new timeline return timeline.start(); }; /** @name glow.anim.highlight @function @description Highlight an element by fading the background colour @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used. @param {String} highlightColour highlight colour in hex, "rgb(r, g, b)" or css colour name. @param {Number} duration Animation duration in seconds. @param {Function} opts Object @param {Function} [opts.completeColour] The background colour of the element once the highlight is complete. If none supplied Glow assumes the element's existing background color (e.g. #336699), if the element has no background color specified (e.g. Transparent) the highlight will transition to white. @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}. @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation. @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation. @returns {glow.anim.Timeline} A started timeline @example glow.anim.highlight("#textInput", "#ff0", 1); **/ r.highlight = function(element, highlightColour, duration, opts){ // normalise element element = $(element); duration = duration || 1; highlightColour = highlightColour || '#ffff99'; opts = glow.lang.apply({ tween: glow.tweens.easeBoth(), onStart: function(){}, onComplete: function(){} }, opts); var i = 0, transArray = [], elmsLength = element.length, completeColour, channels = [], timeline; for(; i < elmsLength; i++) { completeColour = opts.completeColour || element.slice(i, i+1).css("background-color"); if (completeColour == "transparent" || completeColour == "") { completeColour = "#fff"; } channels[i] = [ r.css(element[i], duration, { "background-color" : {from:highlightColour, to:completeColour} }, {tween: opts.tween}) ]; } timeline = new glow.anim.Timeline(channels); events.addListener(timeline, "start", opts.onStart); events.addListener(timeline, "complete", opts.onComplete); return timeline.start(); }; /** @name glow.anim.Animation @class @description Controls modifying values over time. You can create an animtion instance using the constructor, or use one of the helper methods in {@link glow.anim}. Once you have created your animation instance, you can use events such as "frame" to change values over time. @param {Number} duration Length of the animation in seconds / frames. Animations which are given a duration in seconds may drop frames to finish in the given time. @param {Object} opts Object of options. @param {Boolean} [opts.useSeconds=true] Specifies whether duration should be in seconds rather than frames. @param {Function} [opts.tween=linear tween] The way the value moves through time. See {@link glow.tweens}. @param {Boolean} [opts.destroyOnComplete=false] Destroy the animation once it completes? This will free any DOM references the animation may have created. Once the animation completes, you won't be able to start it again. @example var myAnim = new glow.anim.Animation(5, { tween:glow.tweens.easeBoth() }); */ /** @name glow.anim.Animation#event:start @event @description Fired when the animation is started from the beginning. @param {glow.events.Event} event Event Object @example var myAnim = new glow.anim.Animation(5, { tween:glow.tweens.easeBoth() }); glow.events.addListener(myAnim, "start", function() { alert("Started animation which lasts " + this.duration + " seconds"); }); myAnim.start(); */ /** @name glow.anim.Animation#event:frame @event @description Fired in each frame of the animation. This is where you'll specify what your animation does. @param {glow.events.Event} event Event Object @example var myAnim = new glow.anim.Animation(5, { tween:glow.tweens.easeBoth() }); var myDiv = glow.dom.get("#myDiv"), divStartHeight = myDiv.height(), divEndHeight = 500, divHeightChange = divEndHeight - divStartHeight; glow.events.addListener(myAnim, "frame", function() { myDiv.height(divStartHeight + (divHeightChange * this.value)); }); myAnim.start(); */ /** @name glow.anim.Animation#event:stop @event @description Fired when the animation is stopped before its end. If your listener prevents the default action (for instance, by returning false) the animtion will not be stopped. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Animation#event:complete @event @description Fired when the animation ends. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Animation#event:resume @event @description Fired when the animation resumes after being stopped. If your listener prevents the default action (for instance, by returning false) the animation will not be resumed. @param {glow.events.Event} event Event Object */ r.Animation = function(duration, opts) { this._opts = opts = glow.lang.apply({ useSeconds: true, tween: glow.tweens.linear(), destroyOnComplete: false }, opts); /** @name glow.anim.Animation#_playing @type Boolean @private @default false @description Indicates whether the animation is playing. */ this._playing = false; /** @name glow.anim.Animation#_timeAnchor @type Number @private @default null @description A timestamp used to keep the animation in the right position. */ this._timeAnchor = null; /** @name glow.anim.Animation#duration @type Number @description Length of the animation in seconds / frames. */ this.duration = duration; /** @name glow.anim.Animation#useSeconds @type Boolean @description Indicates whether duration is in seconds rather than frames. */ this.useSeconds = opts.useSeconds; /** @name glow.anim.Animation#tween @type Function @description The tween used by the animation. */ this.tween = opts.tween; /** @name glow.anim.Animation#position @type Number @default 0 @description Seconds since starting, or current frame. */ this.position = 0; /** @name glow.anim.Animation#value @type Number @default 0 @description Current tweened value of the animtion, usually between 0 & 1. The value may become greater than 1 or less than 0 depending on the tween used. {@link glow.tweens.elasticOut} for instance will result in values higher than 1, but will still end at 1. */ this.value = 0; }; r.Animation.prototype = { /** @name glow.anim.Animation#start @function @description Starts playing the animation from the beginning. @example var myAnim = new glow.anim.Animation(5, { tween:glow.tweens.easeBoth() }); //attach events here myAnim.start(); @returns {glow.anim.Animation} */ start: function() { if (this._playing) { this.stop(); } var e = events.fire(this, "start"); if (e.defaultPrevented()) { return this; } this._timeAnchor = null; this.position = 0; manager.addToQueue(this); return this; }, /** @name glow.anim.Animation#stop @function @description Stops the animation playing. @returns {glow.anim.Animation} */ stop: function() { if (this._playing) { var e = events.fire(this, "stop"); if (e.defaultPrevented()) { return this; } manager.removeFromQueue(this); } return this; }, /** @name glow.anim.Animation#destroy @function @description Destroys the animation & detatches references to DOM nodes Call this on animations you no longer need to free memory. @returns {glow.anim.Animation} */ destroy: function() { // stop the animation in case it's still playing this.stop(); events.removeAllListeners(this); return this; }, /** @name glow.anim.Animation#resume @function @description Resumes the animation from where it was stopped. @returns {glow.anim.Animation} */ resume: function() { if (! this._playing) { var e = events.fire(this, "resume"); if (e.defaultPrevented()) { return this; } //set the start time to cater for the pause this._timeAnchor = new Date().valueOf() - (this.position * 1000); manager.addToQueue(this); } return this; }, /** @name glow.anim.Animation#isPlaying @function @description Returns true if the animation is playing. @returns {Boolean} */ isPlaying: function() { return this._playing; }, /** @name glow.anim.Animation#goTo @function @description Goes to a specific point in the animation. @param {Number} pos Position in the animation to go to. This should be in the same units as the duration of your animation (seconds or frames). @example var myAnim = new glow.anim.Animation(5, { tween:glow.tweens.easeBoth() }); //attach events here //start the animation from half way through myAnim.goTo(2.5).resume(); @returns this */ goTo: function(pos) { this._timeAnchor = new Date().valueOf() - ((this.position = pos) * 1000); this.value = this.tween(this.duration && this.position / this.duration); events.fire(this, "frame"); return this; } }; /** @name glow.anim.Timeline @class @description Synchronises and chains animations. @param {Array | Array[]} channels An array of channels or a single channel. A channel is defined as an array containing numbers, animations and functions. Numbers indicate a number of seconds to wait before proceeding to the next item. Animations will be played, when the animation is complete the next item is processed. Functions will be called, then the next item is processed. @param {Object} opts An object of options. @param {Boolean} [opts.loop=false] Specifies whether the timeline loops. The "complete" event does not fire for looping animations. @param {Boolean} [opts.destroyOnComplete=false] Destroy the animation once it completes? This will free any DOM references the animation may have created. Once the animation completes, you won't be able to start it again. @example // in the simplest form, a timeline can be used to // string multiple animations together: // make our animations var moveUp = glow.anim.css(myDiv, { "top": {to:"0"} }); var moveDown = glow.anim.css(myDiv, { "top": {to:"100px"} }); // string them together new glow.anim.Timeline([moveUp, moveDown]).start(); @example // if you wanted a one second gap between the animations, the last line would be: new glow.anim.Timeline([moveUp, 1, moveDown]).start(); @example // you can run animations simutainiously with multiple channels. new glow.anim.Timeline([ [moveDivUp, 1, moveDivDown], [moveListDown, 1, moveListUp] ]).start(); @see Creating a mexican wave with an animation timeline */ /** @name glow.anim.Timeline#event:start @event @description Fired when the timeline is started from the beginning. This event will also trigger during each loop of a looping animation. If your listener prevents the default action (for instance, by returning false) the timeline will not start. @param {glow.events.Event} event Event Object @example var myTimeline = new glow.anim.Timeline([anim1, anim2]); glow.events.addListener(myTimeline, "start", function() { alert("Started timeline"); }); myTimeline.start(); */ /** @name glow.anim.Timeline#event:stop @event @description Fired when the timeline is stopped before its end. If your listener prevents the default action (for instance, by returning false) the timeline will not stop. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Timeline#event:complete @event @description Fired when the timeline ends. This event does not fire on looping timelines. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Timeline#event:resume @event @description Fired when the timeline resumes after being stopped. If your listener prevents the default action (for instance, by returning false) the timeline will not resume. @param {glow.events.Event} event Event Object */ r.Timeline = function(channels, opts) { this._opts = opts = glow.lang.apply({ loop: false, destroyOnComplete: false }, opts); /* PrivateProperty: _channels Array of channels */ //normalise channels so it's always an array of array(s) this._channels = (channels[0] && channels[0].push) ? channels : [channels]; /* PrivateProperty: _channelPos index of each currently playing animation */ this._channelPos = []; /* PrivateProperty: _playing Is the timeline playing? */ this._playing = false; /** @name glow.anim.Timeline#loop @type Boolean @description Inidcates whether the timeline loops. The "complete" event does not fire for looping animations. This can be set while a timeline is playing. */ this.loop = opts.loop; var i, j, iLen, jLen, channel, allChannels = this._channels, totalDuration = 0, channelDuration; //process channels for (i = 0, iLen = allChannels.length; i < iLen; i++) { channel = allChannels[i]; channelDuration = 0; for (j = 0, jLen = channel.length; j < jLen; j++) { //create a blank animation for time waiting if (typeof channel[j] == "number") { channel[j] = new r.Animation(channel[j]); } if (channel[j] instanceof r.Animation) { if (! channel[j].useSeconds) { throw new Error("Timelined animations must be timed in seconds"); } channel[j]._timelineOffset = channelDuration * 1000; channelDuration += channel[j].duration; channel[j]._channelIndex = i; } } /** @name glow.anim.Timeline#duration @type Number @description Length of the animation in seconds */ this.duration = totalDuration = Math.max(channelDuration, totalDuration); } /* PrivateProperty: _controlAnim This is used to keep the animation in time */ this._controlAnim = new r.Animation(totalDuration); events.addListener(this._controlAnim, "frame", this._processFrame, this); events.addListener(this._controlAnim, "complete", this._complete, this); }; r.Timeline.prototype = { /* PrivateMethod: _advanceChannel Move to the next position in a particular channel */ _advanceChannel: function(i) { //is there a next animation in the channel? var currentAnim = this._channels[i][this._channelPos[i]], nextAnim = this._channels[i][++this._channelPos[i]]; if (currentAnim && currentAnim._playing) { currentAnim._playing = false; events.fire(currentAnim, "complete"); if (currentAnim._opts.destroyOnComplete) { currentAnim.destroy(); } } if ((nextAnim) !== undefined) { if (typeof nextAnim == "function") { nextAnim(); this._advanceChannel(i); } else { nextAnim.position = 0; nextAnim._channelIndex = i; events.fire(nextAnim, "start"); nextAnim._playing = true; } } }, _complete: function() { if (this.loop) { this.start(); return; } this._playing = false; events.fire(this, "complete"); if (this._opts.destroyOnComplete) { this.destroy(); } }, _processFrame: function() { var i, len, anim, controlAnim = this._controlAnim, msFromStart = (new Date().valueOf()) - controlAnim._timeAnchor; for (i = 0, len = this._channels.length; i < len; i++) { if (! (anim = this._channels[i][this._channelPos[i]])) { continue; } anim.position = (msFromStart - anim._timelineOffset) / 1000; if (anim.position > anim.duration) { anim.position = anim.duration; } anim.value = anim.tween(anim.position / anim.duration); events.fire(anim, "frame"); if (anim.position == anim.duration) { this._advanceChannel(i); } } }, /** @name glow.anim.Timeline#start @function @description Starts playing the timeline from the beginning. @returns this */ start: function() { var e = events.fire(this, "start"); if (e.defaultPrevented()) { return this; } var i, iLen, j, jLen, anim; this._playing = true; for (i = 0, iLen = this._channels.length; i < iLen; i++) { this._channelPos[i] = -1; this._advanceChannel(i); for (j = this._channels[i].length; j; j--) { anim = this._channels[i][j]; if (anim instanceof r.Animation) { anim.goTo(0); } } } this._controlAnim.start(); return this; }, /** @name glow.anim.Timeline#stop @function @description Stops the timeline. @returns this */ stop: function() { if (this._playing) { var e = events.fire(this, "stop"); if (e.defaultPrevented()) { return this; } this._playing = false; var anim; for (var i = 0, len = this._channels.length; iYou do not generally need to call these functions directly, their names are passed to the Form {@link glow.forms.Form#addTests addTests} method.
If you want to create your own custom functions, or to see what the parameters these function take, you should refer to the Creating Custom Tests section of the Validating Forms user guide.
*/ glow.forms.tests = { /** @name glow.forms.tests.required @function @description The value must contain at least one non-whitespace character. @example myForm.addTests( "fieldName", ["required"] ); */ required: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.required()"); var message = opts.message || "Value is required"; for (var i = 0, len = values.length; i < len; i++) { if (/^\s*$/.test(values[i])) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.isNumber @function @description The value must be a valid number. @example myForm.addTests( "fieldName", ["isNumber"] ); */ isNumber: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.isNumber()"); var message = opts.message || "Must be a number."; for (var i = 0, len = values.length; i < len; i++) { if (values[i] == "" || isNaN(values[i])) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.min @function @description The numeric value must be at least the given value. @example myForm.addTests( "fieldName", ["min", { arg: "1" }] ); */ min: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.min()"); var message = opts.message || "The value must be at least "+opts.arg+"."; for (var i = 0, len = values.length; i < len; i++) { if (Number(values[i]) < Number(opts.arg)) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.max @function @description The numeric value must be no more than the given value. @example myForm.addTests( "fieldName", ["max", { arg: "100" }] ); */ max: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.max()"); var message = opts.message || "The value must be less than "+opts.arg+"."; for (var i = 0, len = values.length; i < len; i++) { if (Number(values[i]) > Number(opts.arg)) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.range @function @description The numeric value must be between x..y. @example myForm.addTests( "fieldName", ["range", { arg: "18..118" }] ); */ range: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.range()"); var minmax = opts.arg.split(".."); // like "0..10" if (typeof minmax[0] == "undefined" || typeof minmax[1] == "undefined") { throw "Range test requires a parameter like 0..10." } var message = opts.message || "The value must be "+minmax[0]+" or greater, and less than "+minmax[1]+"."; // cast to numbers to avoid stringy comparisons minmax[0] *= 1; minmax[1] *= 1; // reverse if the order is hi..lo if (minmax[0] > minmax[1]) { var temp = minmax[0]; minmax[0] = minmax[1]; minmax[1] = temp; } for (var i = 0, len = values.length; i < len; i++) { if (values[i] < minmax[0] || values[i] > minmax[1]) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.minCount @function @description There must be at least the given number of values submitted with this name. This is useful for multiple selects and checkboxes that have the same name. @example myForm.addTests( "fieldName", ["minCount", { arg: "1" }] ); */ minCount: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.minCount()"); var message = opts.message || "Must be have at least "+opts.arg+" values."; var count = 0; for (var i = 0; i < values.length; i++ ) { if (values[i] != "") count++; } if (count < opts.arg) { callback(glow.forms.FAIL, message); return; } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.maxCount @function @description There must be no more than the given number of values submitted with this name. This is useful for multiple selects and checkboxes that have the same name. @example myForm.addTests( "fieldName", ["maxCount", { arg: "10" }] ); */ maxCount: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.maxCount()"); var message = opts.message || "Must be have at most "+opts.arg+" values."; var count = 0; for (var i = 0; i < values.length; i++ ) { if (values[i] != "") count++; } if (count > opts.arg) { callback(glow.forms.FAIL, message); return; } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.count @function @description There must be exactly the given number of values submitted with this name. This is useful for multiple selects and checkboxes that have the same name. @example myForm.addTests( "fieldName", ["count", { arg: "2" }] ); */ count: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.count()"); var message = opts.message || "Must have "+opts.arg+" values."; var count = 0; for (var i = 0; i < values.length; i++ ) { if (values[i] != "") count++; } if (count != opts.arg) { callback(glow.forms.FAIL, message); return; } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.regex @function @description The value must match the given regular expression. @example myForm.addTests( "fieldName", ["regex", { arg: /^[A-Z0-9]*$/ }] ); */ regex: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.regex()"); var message = opts.message || "Must be in the correct format."; var regex = (typeof opts.arg == "string")? new RegExp(opts.arg) : opts.arg; // if its not a string assume its a regex literal for (var i = 0, len = values.length; i < len; i++) { if (!regex.test(values[i])) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.minLen @function @description The value must be at least the given number of characters long. @example myForm.addTests( "fieldName", ["minLen", { arg: "3" }] ); */ minLen: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.minLen()"); var message = opts.message || "Must be at least "+opts.arg+" characters."; for (var i = 0, len = values.length; i < len; i++) { if (values[i].length < opts.arg) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.maxLen @function @description The value must be at most the given number of characters long. @example myForm.addTests( "fieldName", ["maxLen", { arg: "24" }] ); */ maxLen: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.maxLen()"); var message = opts.message || "Must be at most "+opts.arg+" characters."; for (var i = 0, len = values.length; i < len; i++) { if (values[i].length > opts.arg) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.isEmail @function @description The value must be a valid email address. This checks the formatting of the address, not whether the address exists. @example myForm.addTests( "fieldName", ["isEmail"] ); */ isEmail: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.isEmail()"); var message = opts.message || "Must be a valid email address."; for (var i = 0, len = values.length; i < len; i++) { if (!/^[A-Za-z0-9](([_\.\-]*[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\.\-]?[a-zA-Z0-9]+)*)\.([A-Za-z]{2,})$/.test(values[i])) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.sameAs @function @description The value must be the same as the value in the given field. @example myForm.addTests( "email_confirm", ["sameAs", { arg: "email" }] ); */ sameAs: function(values, opts, callback, formValues) { /*debug*///console.log("glow.forms.tests.sameAs()"); var message = opts.message || "Must be the same as: "+opts.arg; var compareTo = formValues[opts.arg]; for (var i = 0, len = values.length; i < len; i++) { if (values[i] != compareTo) { callback(glow.forms.FAIL, message); return; } } callback(glow.forms.PASS, message); } , /** @name glow.forms.tests.ajax @function @description Send the data to the server for testing. A request to the given URL will be made and the response will be passed to the given callback. 'arg' is the function to handle the response from the server. 'url' is the url to call. You can use placeholders in here for form values (see example). @example function handleResponseText(response) { if (response.text() == "OK") { return glow.forms.PASS; } else { return glow.forms.FAIL; } } myForm.addTests( "username", ["ajax", { arg: handleResponseText, url: "/cgi/checkname.cgi?name={username}" }] ); */ ajax: function(values, opts, callback, formValues) { /*debug*///console.log("glow.forms.tests.ajax() - "+opts.url); var queryValues = {}; for (var p in formValues) { if (typeof formValues[p] == "string") { queryValues[p] = escape(formValues[p]); } else if (typeof formValues[p].push != "undefined") { queryValues[p] = glow.lang.map(formValues[p], function(i) { return escape(i); }).join(","); } } var url = glow.lang.interpolate(opts.url, queryValues); var request = glow.net.get(url, { onLoad: function(response) { /*debug*///console.log("glow.forms.tests.ajax - onLoad()"); callback(opts.arg(response), "server responded"); }, onError: function(response) { alert("Error getting file: "+url); } }); } , /** @name glow.forms.tests.custom @function @description Create a custom test. 'arg' is a function which tests the form value. The function is given the following parameters:These functions should be used as handlers for {@link glow.forms.Form}'s validate event. At the moment there is only one provided handler, more may be added in the future.
Of course, you don't have to use any of the methods here, you can provide your own.
@see Using the default form feedback */ var feedback = glow.forms.feedback = {}; /** @name glow.forms.feedback.defaultFeedback @function @description Default handler used by {@link glow.forms.Form}.This method outputs messages to the user informing them which fields contain invalid data. The output is unstyled and flexible.
@param {glow.forms.ValidateResult} result Object provided by the validate event @see Using the default form feedback */ feedback.defaultFeedback = (function() { //a hidden form element used to update a screenreader's buffer var screenReaderBufferUpdater; //attempts to update the buffer of the screen reader function updateScreenReaderBuffer() { if (!screenReaderBufferUpdater) { screenReaderBufferUpdater = glow.dom.create('').appendTo(document.body); } screenReaderBufferUpdater[0].value++; } //write out the messages which appear next to (or near) the fields function inlineErrors(response) { var fields = response.fields, //field test results fieldElm, //holder for the field element(s) being processed msgContainer, //holder for contextual message holder labelError, //error holder within label element i, len; for (i = 0, len = fields.length; i < len; i++) { fieldElm = glow.dom.get(response.form.formNode[0].elements[fields[i].name]); //here's where we get the error container, which is the label by default //also we need to escape invalid css chars CSS msgContainer = glow.dom.get("." + fields[i].name.replace(/(\W)/g, "\\$1") + "-msgContainer"); if (!msgContainer[0] && fieldElm.length == 1) { //none found, try and get the label msgContainer = response.form.formNode.get("label").filter(function() { return this.htmlFor == fieldElm[0].id }) } labelError = msgContainer.get("span.glow-errorMsg"); if (fields[i].result) { //clear error messages & classes labelError.remove(); fieldElm.removeClass("glow-invalid"); } else { if (msgContainer.length) { //add the error span to the label if it isn't already there if (!labelError[0]) { msgContainer.append( (labelError = glow.dom.create('')) ); } labelError.text(fields[i].message); fieldElm.addClass("glow-invalid"); } } } } //write out a list of errors to appear at the top of the form function summaryError(response) { var fields = response.fields, //field test results fieldElm, //holder for the field element(s) being processed errorSummary, //div containing error summary errorList, //list of errors inside the error summary promptContainer, //holds the 'question' of the field prompt, //text to prefix each error line with i, len; //remove existing summary response.form.formNode.get("div.glow-errorSummary").remove(); //create a summary div errorSummary = glow.dom.create('If a CSS selector is provided then the first matching element is used as the container.
If the parameter is a {@link glow.dom.NodeList}, then the first element of the list is used as the container.
@param {String|Object} minVersion The minimum required version of the Flash plugin.The Flash plugin has a version numbering scheme comprising of major, minor and release numbers.
This param can be a string with the major number only, major plus minor numbers, or full three-part version number, e.g. "9", "9.1" , "6.0.55" are all valid values.
If minVersion is set as an object, it must use a similar structure to the object
returned by the {@link glow.embed.Flash#version} method, e.g: {major: 9, minor:0,
release:0}.
{x:2, y:4} to set different steps to each
axis.
@param {Function} [opts.onDrag] An event listener that fires when the draggable starts being dragged.
@param {Function} [opts.onEnter] An event listener that fires when the draggable is dragged over a drop target.
@param {Function} [opts.onLeave] An event listener that fires when the draggable is dragged out of a drop target.
@param {Function} [opts.onDrop] An event listener that fires when the draggable is dropped.
@param {Function} [opts.onAfterDrop] An event listener that fires after the element has dropped, including any animations
The default action is to animate the draggable back to it's start
position. This can be cancelled by returning false from the listener
or calling {@link glow.events.Event.preventDefault} on the
{@link glow.events.Event} param.
@example
// create a draggable element with a corresponding DropTarget,
// container and two event listeners
var myDraggable = new glow.dragdrop.Draggable('#draggable', {
dropTargets : [ myDropTarget ],
container : '#container',
onDrag : function () {
this.element.css('opacity', '0.7');
},
onDrop : function () {
this.element.css('opacity', '1');
}
});
*/
/**
@name glow.dragdrop.Draggable#event:drag
@event
@description Fired when the draggable starts being dragged.
Concelling this event results in the user being unable to pick up
the draggable.
@param {glow.events.Event} event Event Object
*/
/**
@name glow.dragdrop.Draggable#event:enter
@event
@description Fired when the draggable is dragged over a drop target.
@param {glow.events.Event} event Event Object
*/
/**
@name glow.dragdrop.Draggable#event:leave
@event
@description Fired when the draggable is dragged out of a drop target.
@param {glow.events.Event} event Event Object
*/
/**
@name glow.dragdrop.Draggable#event:drop
@event
@description Fired when the draggable is dropped.
@param {glow.events.Event} event Event Object
*/
/**
@name glow.dragdrop.Draggable#event:afterDrop
@event
@description Fired after the element has dropped, including any animations
@param {glow.events.Event} event Event Object
*/
r.Draggable = function (el, opts) {
/**
@name glow.dragdrop.Draggable#element
@type glow.dom.NodeList
@description glow.dom.NodeList containing the draggable element
*/
this.element = $(el);
this._opts = opts = glow.lang.apply({
dragPrevention : ['input', 'textarea', 'button', 'select', 'option', 'a'],
placeholder : 'spacer',
placeholderClass : 'glow-dragdrop-placeholder',
step : {x:1, y:1}
}, opts || {});
//normalise the step param to an object
if (typeof opts.step == "number") {
opts.step = {x: opts.step, y: opts.step};
} else {
opts.step.x = opts.step.x || 1;
opts.step.y = opts.step.y || 1;
}
this._preventDrag = [];
for (var i = 0, l = opts.dragPrevention.length; i < l; i++) {
this._preventDrag[i] = opts.dragPrevention[i].toLowerCase();
}
if (opts.container) { this.container = $(opts.container); }
this._handle = opts.handle && this.element.get(opts.handle) || this.element;
if (opts.dropTargets) this.dropTargets = $(opts.dropTargets);
//used for IE literal edge case bug fix
//this._mouseUp = true;
//bug fix to get document.body.scrollTop to return true value (not 0) if using transitional 4.01 doctype
//get('body')[0].style.overflow = 'auto';
//this._opts = o, this._targetCoords = [], this.isOverTarget = false;
var listeners = this._listeners = [],
i = 0;
if (opts.onDrag) listeners[i++] = addListener(this, 'drag', this._opts.onDrag, this);
if (opts.onEnter) listeners[i++] = addListener(this, 'enter', this._opts.onEnter, this);
if (opts.onLeave) listeners[i++] = addListener(this, 'leave', this._opts.onLeave, this);
if (opts.onDrop) listeners[i++] = addListener(this, 'drop', this._opts.onDrop, this);
this._dragListener = addListener(this._handle, 'mousedown', this._startDragMouse, this);
return;
};
/*
Group: Methods
*/
//var applyFloatBugfix = glow.env.ie;
r.Draggable.prototype = {
/*
PrivateMethod: _createPlaceholder
Create an element that occupies the space where the draggable has been dragged from.
*/
_createPlaceholder: function () {
var el = this.element,
placeholder,
box = this._box;
if (this._opts.placeholder == 'clone') {
placeholder = el.clone();
}
else { // placeholder == 'spacer'
placeholder = placeholderElement(el);
}
if (this._opts.placeholderClass) {
placeholder.addClass(this._opts.placeholderClass);
}
box.sizePlaceholder(placeholder, null, this._startLeft, this._startTop);
el.after(placeholder);
this._placeholder = placeholder;
},
/*
PrivateMethod: _removePlaceholder
Removes the placeholder (see above) from the document.
*/
_removePlaceholder: function () {
this._placeholder.remove();
},
/*
PrivateMethod: _resetPosition
Sets the position CSS property to what it started as without moving the draggable. If the
original position was 'static' and making it 'static' again would mean moving the draggable,
then the position is set to 'relative'.
*/
_resetPosition: function () {
var origPos = this._preDragPosition,
el = this.element,
box = this._box,
startOffset = this._startOffset,
pos = el.css('position'),
newLeft,
newTop;
box.resetPosition();
var offset = {
x: box.offsetLeft(),
y: box.offsetTop()
};
if (this._placeholder || this._dropIndicator) {
el.remove();
}
if (origPos == 'static' && offset.y == startOffset.y && offset.x == startOffset.x) {
el.css('position', 'static');
el.css('left', '');
el.css('top', '');
}
else {
el.css('z-index', this._preDragZIndex);
el.css('position', origPos == 'static' ? 'relative' : origPos);
if (origPos == 'static') {
newLeft = offset.x - startOffset.x;
newTop = offset.y - startOffset.y;
}
else if (origPos == 'relative' && pos != 'relative') {
newLeft = this._startLeft + (offset.x - startOffset.x);
newTop = this._startTop + (offset.y - startOffset.y);
}
if (pos != origPos) {
el.css('left', newLeft ? newLeft + 'px' : '');
el.css('top', newTop ? newTop + 'px' : '');
}
}
if (this._dropIndicator) {
var parent = this._dropIndicator.parent()[0];
if (parent) parent.replaceChild(el[0], this._dropIndicator[0]);
delete this._dropIndicator;
if (this._placeholder) {
this._placeholder.remove();
delete this._placeholder;
}
// this is canceling out some of the stuff done in the if statement above, could be done better
el.css('position', origPos);
if (origPos == 'relative' && pos != 'relative') {
el.css('left', this._startLeft);
el.css('top', this._startTop);
}
}
else if (this._placeholder) {
var parent = this._placeholder.parent()[0];
if (parent) parent.replaceChild(el[0], this._placeholder[0]);
delete this._placeholder;
}
},
/*
PrivateFunction: _startDragMouse
Start the draggable dragging when the mousedown event is fired.
Arguments:
*e* (glow.events.Event)
The mousedown event that caused the listener to be fired.
*/
_startDragMouse: function (e) {
var preventDrag = this._preventDrag,
source = e.source,
tag = source.tagName.toLowerCase();
for (var i = 0, l = preventDrag.length; i < l; i++) {
if (preventDrag[i] == tag) {
return;
}
}
//fire the drag event
if (fire(this, 'drag').defaultPrevented()) {
//the default action was prevented, don't do any dragging
return;
}
if (this._dragging == 1)
return this.endDrag();
else if (this._dragging)
return;
// _dragging set to 1 during drag, 2 while ending drag and back to 0 when ready for new drag
this._dragging = 1;
var el = this.element,
container = this.container,
opts = this._opts,
box = this._box = new Box(el),
step = opts.step;
this._preDragPosition = el.css('position');
var startOffset = this._startOffset = {
x: box.offsetLeft(),
y: box.offsetTop()
};
if (container) {
this._containerBox = new Box(container);
this._bounds = this._containerBox.boundsFor(box);
//we may need to decrease the bounds to keep the element in step (if it's using stepped dragging)
//basically makes the bounding box smaller to fit in with the stepping
if (step.x != 1) {
this._bounds[3] -= (this._bounds[3] - startOffset.x) % step.x;
this._bounds[1] -= (this._bounds[1] - startOffset.x) % step.x;
}
if (step.y != 1) {
this._bounds[0] -= (this._bounds[0] - startOffset.y) % step.y;
this._bounds[2] -= (this._bounds[2] - startOffset.y) % step.y;
}
}
else {
delete this._bounds;
}
this._mouseStart = {
x: e.pageX,
y: e.pageY
};
this._preDragStyle = el.attr('style');
this._preDragZIndex = el.css('z-index');
el.css('z-index', _zIndex++);
this._startLeft = el[0].style.left ? parseInt(el[0].style.left) : 0;
this._startTop = el[0].style.top ? parseInt(el[0].style.top) : 0;
if (opts.placeholder && opts.placeholder != 'none') {
this._createPlaceholder();
}
el.css('position', 'absolute');
el.css('left', startOffset.x + 'px');
el.css('top', startOffset.y + 'px');
if(_ieStrict) {
this._scrollY = document.documentElement.scrollTop;
this._innerHeight = document.documentElement.clientHeight;
}
else if(_ieTrans){
this._scrollY = document.body.scrollTop;
this._innerHeight = document.body.clientHeight;
}
else {
this._scrollY = window.scrollY;
this._innerHeight = window.innerHeight;
}
var cancelFunc = function () { return false },
doc = document.documentElement;
if (this.dropTargets) {
var event = new events.Event();
event.draggable = this;
for (var i = 0, l = this.dropTargets.length; i < l; i++) {
fire(this.dropTargets[i], 'active', event);
}
this._mousePos = {
x: e.pageX,
y: e.pageY
};
this._testForDropTargets();
}
this._dragListeners = [
addListener(doc, 'selectstart', cancelFunc),
addListener(doc, 'dragstart', cancelFunc),
addListener(doc, 'mousedown', cancelFunc),
addListener(doc, 'mousemove', this._dragMouse, this),
addListener(doc, 'mouseup', this._releaseElement, this)
];
return false;
},
/*
PrivateFunction: _dragMouse
Move the draggable when a mousemove event is received.
Arguments:
*e* (glow.events.Event)
The mousedown event that caused the listener to be fired.
*/
_dragMouse: function (e) {
var element = this.element,
axis = this._opts.axis,
//do we need to do axis here, or just not apply the newX/Y if axis is used? May be faster
newX = axis == 'y' ?
this._startOffset.x:
(this._startOffset.x + e.pageX - this._mouseStart.x),
newY = axis == 'x' ?
this._startOffset.y:
(this._startOffset.y + e.pageY - this._mouseStart.y),
bounds = this._bounds,
step = this._opts.step;
//round position to the nearest step
if (step.x != 1) {
newX = Math.round((newX - this._startOffset.x) / step.x) * step.x + this._startOffset.x;
}
if (step.y != 1) {
newY = Math.round((newY - this._startOffset.y) / step.y) * step.y + this._startOffset.y;
}
// only pay for the function call if we have a container or an axis
if (bounds) {
// only apply bounds on the axis we're using
if (axis != 'y') {
newX = newX < bounds[3] ? bounds[3] : newX > bounds[1] ? bounds[1] : newX;
}
if (axis != 'x') {
newY = newY < bounds[0] ? bounds[0] : newY > bounds[2] ? bounds[2] : newY;
}
}
// set the new position
element[0].style.left = newX + 'px';
element[0].style.top = newY + 'px';
//if there are dragTargets check if the draggable is over the target
if (this.dropTargets) {
this._mousePos = { x: e.pageX, y: e.pageY };
}
// check for IE mouseup outside of page boundary
if(_ie && e.nativeEvent.button == 0) {
this._releaseElement(e);
return false;
};
return false;
},
/*
PrivateFunction: _testForDropTarget
Check if the draggable is over a drop target. Sets the activeTarget property of the draggable
to the drop target that the draggable is over, if any.
Arguments:
*mousePos* (object)
The position of the mouse pointer relative to the document. The object has x and y integer
pixel properties.
*/
_testForDropTargets: function (fromTimeout) {
if (! this._lock) this._lock = 0;
if (fromTimeout) this._lock--;
else if (this.lock) return;
if (this._dragging != 1) return;
var previousTarget = this.activeTarget,
activeTarget,
targets = this.dropTargets,
target,
targetBox,
box = this._box,
mousePos = this._mousePos;
box.resetPosition();
var maxIntersectSize = 0;
for (var i = 0, l = targets.length; i < l; i++) {
target = targets[i];
targetBox = target._box;
if (target._opts.tolerance == 'contained') {
if (targetBox.contains(box)) {
activeTarget = target;
break;
}
}
else if (target._opts.tolerance == 'cursor') {
if (targetBox.containsPoint(mousePos)) {
activeTarget = target;
break;
}
}
else {
var intersectSize = targetBox.intersectSize(box, true);
if (intersectSize > maxIntersectSize) {
maxIntersectSize = intersectSize;
activeTarget = target;
}
}
}
this.activeTarget = activeTarget;
// enter events
if (activeTarget !== previousTarget) {
if (activeTarget) {
// enter on the target
var draggableEnterEvent = new events.Event();
draggableEnterEvent.draggable = this;
fire(activeTarget, 'enter', draggableEnterEvent);
// enter on this (the draggable)
var enterTargetEvent = new events.Event();
enterTargetEvent.dropTarget = activeTarget;
fire(this, 'enter', enterTargetEvent);
}
if (previousTarget) {
// leave on target
var draggableLeaveEvent = new events.Event();
draggableLeaveEvent.draggable = this;
fire(previousTarget, 'leave', draggableLeaveEvent);
// leave on this (draggable)
var leaveTargetEvent = new events.Event();
leaveTargetEvent.dropTarget = previousTarget;
fire(this, 'leave', leaveTargetEvent);
}
}
// place the drop indicator in the drop target (not in the drop target class for speed)
if (activeTarget && activeTarget._opts.dropIndicator != 'none') {
var childBox,
childBoxes = activeTarget._childBoxes,
children = activeTarget._children;
box.resetPosition();
var totalHeight = activeTarget._box.innerTopPos();
var draggablePosition = mousePos.y - box.offsetParentPageTop();
var placed = 0;
for (var i = 0, l = childBoxes.length; i < l; i++) {
if (children[i] == this.element[0]) continue;
childBox = childBoxes[i];
totalHeight += childBox.outerHeight();
if (draggablePosition <= totalHeight) {
if (activeTarget._dropIndicatorAt != i) {
$(childBox.el).before(activeTarget._dropIndicator);
activeTarget._dropIndicatorAt = i;
}
placed = 1;
break;
}
}
if (! placed) {
if (childBox) {
$(childBox.el).after(activeTarget._dropIndicator);
activeTarget._dropIndicatorAt = i + 1;
}
else {
activeTarget.element.append(activeTarget._dropIndicator);
activeTarget._dropIndicatorAt = 0;
}
}
}
this._lock++;
var this_ = this;
setTimeout(function () { this_._testForDropTargets(1) }, 100);
},
/*
PrivateMethod: releaseElement
Finish the drag when a mouseup event is recieved.
Arguments:
*e* (glow.events.Event)
The mouseup event that caused the listener to be fired.
*/
_releaseElement: function () {
if (this._dragging != 1) return;
this._dragging = 2;
var i, l;
//call the onInactive function on all the dropTargets for this draggable
var dropTargets = this.dropTargets,
activeTarget = this.activeTarget;
if (dropTargets) {
for (i = 0, l = dropTargets.length; i < l; i++) {
var event = new events.Event();
event.draggable = this;
event.droppedOnThis = activeTarget && activeTarget == dropTargets[i];
fire(dropTargets[i], 'inactive', event);
}
}
if (activeTarget) {
var event = new events.Event();
event.draggable = this;
fire(activeTarget, 'drop', event);
}
var dragListeners = this._dragListeners;
for (i = 0, l = dragListeners.length; i < l; i++) {
events.removeListener(dragListeners[i]);
}
var dropEvent = fire(this, "drop");
if (! dropEvent.defaultPrevented() && this.dropTargets) {
this.returnHome();
}
else {
this.endDrag();
}
},
/*
Method: endDrag
Finishes dragging the draggable. Removes the placeholder (if any) and resets the position CSS property
of the draggable.
TODO - revist this code example
N.B. This is called by default but if you overwrite the onDrop function then you will have to call it youoriginal
(code)
// empty NodeList
var myDraggable = new glow.dragdrop.Draggable('#draggable', {
onDrop = function(e){
do some stuff that takes a while.....
this.endDrag();
false;
}
});
(end)
*/
endDrag: function(){
if (this._dragging != 2) return;
this._dragging = 0;
//remove any helpers/placeholders
if (this._reset) {
this._reset();
delete this._reset;
}
if (this.placeholder) {
this.placeholder.remove();
}
this._resetPosition();
delete this.activeTarget;
fire(this, "afterDrop");
},
/*
Event: returnHome
Animates the Draggable back to it's start position and calls endDrag() at the end of the
transition. This is called by default when the Draggable, that has a DragTarget, is dropped.
However if you override the default onDrop function you may want to call this function your
original
Arguments
tween (function)
The animation you wish to used for easing the Draggable. See