CHANGES100644 0 0 254 11405426600 7157 0ustar 0 0 Glow Changelog 2.0.0 alpha 1 First release including the following modules: * Glow - the loader * glow - the library * glow.NodeList * glow.events * glow.env * glow.utilCONTRIBUTORS100644 0 0 503 11405426600 10041 0ustar 0 0 For more information regarding contributing to Glow, please see: http://www.bbc.co.uk/glow/community/contributors/ BBC Glow engineering team: Jake Archibald [http://github.com/jakearchibald] Frances Berriman [http://github.com/phae] Steve Elson [http://github.com/elson] Michael Mathews [http://github.com/micmath] LICENCE100644 0 0 23676 11405426600 7226 0ustar 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 README100644 0 0 11717 11405426600 7112 0ustar 0 0 Glow 2.0.0 alpha 1 ================== This is the first release of Glow 2. It is intended for testing and shouldn't be used in a live environment. Loading Glow ============ There are 2 options for loading Glow onto your page Asynchronous loading (recommended) ---------------------------------- This makes Glow load in the background, which doesn't block page rendering. This can also be used to load only the parts of Glow that are needed. Another benefit is glow doesn't hit the global scope, it only exists inside your ready callback. Although it isn’t ideal in terms of bandwidth, multiple versions of glow can be loaded safely on the same page. For larger apps, this pattern may be more useful Later, when we add more features, Glow will be split into packages. If we had any widgets, you would load them like this: At the moment there’s only one package, 'core'. This is loaded as soon as you call Glow(2). These methods of loading Glow replace Gloader, which was used to load Glow 1. The full docs for Glow can be found in glow.debug.js, search for "@name Glow" Synchronous loading ------------------- Using the loader is optional, you can load Glow synchronously into the global scope via: Interacting with the DOM ======================== You can use glow as a function to get elements by CSS selector or create them by HTML string. glow('#nav li:first-child').css('background', 'red'); glow('

New paragraph

').appendTo('body'); glow() is simply a shortcut. glow('h1') is the same as new glow.NodeList('h1') The full docs for glow.NodeList can be found in core.debug.js, search for "@name glow.NodeList" Differences from Glow 1 ----------------------- The NodeList API is very similar to Glow 1, most of the work has been on performance for this module. Glow 2 uses Sizzle as its CSS selector engine, greatly improving the range of selectors that can be used. CSS can also be used to search for, and filter elements. For instance: // get all the items in the NodeList that have class 'active' myNodeList.filter('.active'); // get the parent element that's a child list-item of #nav myNodeList.parent('#nav > li'); Events ====== The full docs for glow.events can be found in core.debug.js, search for "@name glow.events" Custom Events ------------- Now, any object can extend or be enhanced by glow.events.Target. This gives it instance methods like 'on', 'detach' and 'fire'. function Ball() {} glow.util.extend(Ball, glow.event.Target, { bounce: function() { // fire the bounce event this.fire('bounce'); } }); // and other code can listen for these events var myBall = new Ball(); myBall.on('bounce', function() { glow('body').append('

boing!

'); }); DOM Events ---------- Rather than the long-winded glow.events.addListener, NodeList now has an 'on' method. glow('#nav a').on('click', function() { alert('You just clicked a link in the nav'); }); The above will add a listener to each link in #nav. As a more efficient alternative, you could use event delegation... glow('#nav').delegate('click', 'a', function() { alert('You just clicked a link in the nav'); }); The above adds one listener to the nav, but only fires your callback if the user clicks on a link inside the nav. This means that the event will also fire for links created after the listener is added. Keyboard Events --------------- Keyboard events have been normalised to fire keydown and keyup once per key press, whereas the keypress event will repeat while the key is held down. Details of which keys have been pressed have been normalised across browsers. // adding key listeners for a sideways-scrolling shooter glow('#spaceshipGameScreen').on('keydown', function() { if (e.key === 'up') { spaceship.startMoveUp(); } else if (e.key === 'down') { spaceship.startMoveDown(); } }).on('keyup', function(e) { if (e.key === 'up') { spaceship.stopMoveUp(); } else if (e.key === 'down') { spaceship.stopMoveDown(); } }).on('keypress', function(e) { if (e.key === 'space') { spaceship.fireLasers(); } })docs/files.html100644 0 0 13231 11405426572 11153 0ustar 0 0 JsDoc Reference - File Index
Class Index | File Index

Classes


File Index

./build/2.0.0b1/core.js


./build/2.0.0b1/glow.js


./build/2.0.0b1/ui.js


Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/index.html100644 0 0 20472 11405426572 11165 0ustar 0 0 JsDoc Reference - Index
Class Index | File Index

Classes


Class Index

_global_


glow


glow.anim


glow.anim.Anim


glow.anim.Timeline


glow.env


glow.events


glow.events.DomEvent


glow.events.Event


glow.events.KeyboardEvent


glow.events.Target


glow.net


glow.net.CrossDomainRequest


glow.net.CrossDomainResponse


glow.net.JsonpRequest


glow.net.ResourceRequest


glow.net.XhrRequest


glow.net.XhrResponse


glow.NodeList


glow.tweens


glow.ui


glow.ui.AutoSuggest


glow.ui.Behaviour


glow.ui.Carousel


glow.ui.CarouselPane


glow.ui.Focusable


glow.ui.Overlay


glow.ui.Widget


glow.util


Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/symbols/_global_.html100644 0 0 21051 11405426570 13274 0ustar 0 0 JsDoc Reference - _global_
Class Index | File Index

Classes


Built-In Namespace _global_

Method Summary
Method Attributes Method Name and Description
 
Glow(version, opts)
Creates an instance of the Glow JavaScript Library.
Method Detail
Glow(version, opts)
Creates an instance of the Glow JavaScript Library.
Defined in: glow.js.
Parameters:
{string} version Optional
{object} opts Optional
{string} opts.base Optional
The path to the base folder, in which the Glow versions are kept.
{boolean} opts.debug Optional
Have all filenames modified to point to debug versions.

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:08 GMT+0100 (BST)
docs/symbols/glow.anim.anim.html100644 0 0 114714 11405426572 14407 0ustar 0 0 JsDoc Reference - glow.anim.Anim
Class Index | File Index

Classes


Class glow.anim.Anim


Extends glow.events.Target.

Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.anim.Anim(duration, opts)
Animate an object.
Field Summary
Field Attributes Field Name and Description
 
Destroy the animation once it completes (unless it loops).
 
Length of the animation in seconds.
 
Loop the animation? This value can be changed while an animation is playing.
 
`true` if the animation is playing.
 
Position of the animation in seconds.
 
The tween used by the animation.
 
Current tweened value of the animation, usually between 0 & 1.
Method Summary
Method Attributes Method Name and Description
 
Destroys the animation & detaches references to objects This frees memory & is called automatically when an animation completes.
 
goTo(pos)
Goes to a specific point in the animation.
 
Alters the animation so it plays forward, then in reverse The duration of the animation is doubled.
 
prop(propertyName, conf)
Animate a property of an object.
 
Reverses this animation Adjusts the tween of this animation so it plays in reverse.
 
start(position)
Starts playing the animation If the animation is already playing, this has no effect.
 
stop()
Stops the animation playing.
 
target(newTarget)
Set the object for subsequent calls to {@link glow.anim.Anim#prop prop} to act on.
Methods borrowed from class glow.events.Target:
detach, fire, on
Event Summary
Event Attributes Event Name and Description
 
complete(event)
Fires when an animation completes Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) causes the animation to loop.
 
frame(event)
Fires on each frame of the animation Use a combination of this event and {@link glow.anim.Anim#value value} to create custom animations.
 
start(event)
Fires when an animation starts.
 
stop(event)
Fires when an animation is stopped before completing Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from stopping.
Class Detail
glow.anim.Anim(duration, opts)
Animate an object. To animate CSS properties, see glow.NodeList#anim. Once you have an Anim instance, the glow.anim.Anim#prop method can be used to easily animate object properties from one value to another. If this isn't suitable, listen for the 'frame' event to change values over time.
			// Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween
			// feGaussianBlurElm is a reference to an  element.
			new glow.anim.Anim(5, {
				tween: 'easeOut'
			}).target(feGaussianBlurElm).prop('stdDeviation', {
				from: 0,
				to: 8
			}).start();
			// Animate a CSS property we don't support in glow.NodeList#anim
			// This rotates a Mozilla CSS gradient
			var styleObject = glow('#nav').prop('style');
			
			new glow.anim.Anim(10).target(styleObject).prop('background', {
				// the question-mark in the template is replaced with the animated value
				template: '-moz-linear-gradient(?deg, red, blue)'
				from: 0,
				to: 360
			}).start();
			// Animate a CSS property we don't support in glow.NodeList#anim
			// This changes the colour of a webkit drop shadow from yellow to blue
			var styleObject = glow('#nav').prop('style');
			
			new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', {
				// the ? in the template are replaced with the animate values
				template: 'rgb(?, ?, ?) 0px 4px 14px'
				// provide a 'from' and 'to' value for each question-mark
				from: [255, 255, 0],
				to: [0, 0, 255],
				// round the value, colours can't be fractional
				round: true
			}).start();
			// Make an ASCII progress bar animate from:
			// [--------------------] 0%
			// to
			// [++++++++++++++++++++] 100%
			var progressBar = glow('#progressBar'),
				// our progress bar is 20 chars
				barSize = 20;
				
			new glow.anim.Anim(2).on('frame', function() {
				var onChars = Math.floor(this.value * barSize),
					offChars = barSize - onChars,
					// add the + and - chars
					barStr = new Array(onChars + 1).join('+') + new Array(offChars + 1).join('-');
				
				progressBar.text('[' + barStr + '] ' + Math.floor(this.value * 100) + '%');
			}).start();
Parameters:
{number} duration
Length of the animation in seconds.
{Object} opts Optional
Object of options.
{function|string} opts.tween Optional, Default: 'easeBoth'
The way the value changes over time. Strings are treated as properties of glow.tweens (eg 'bounceOut'), although a tween function can be provided. The default is an {@link glow.tweens.easeBoth easeBoth} tween. Looped animations will fire a 'complete' event on each loop.
{boolean} opts.destroyOnComplete Optional, Default: true
Destroy the animation once it completes (unless it loops). Shortcut for glow.anim.Anim#destroyOnComplete.
{boolean} opts.loop Optional, Default: false
Loop the animation. Shortcut for setting glow.anim.Anim#loop.
See:
- shortcut for animating CSS values on an element.
Field Detail
{boolean} destroyOnComplete
Destroy the animation once it completes (unless it loops). This will free any DOM references the animation may have created. Once the animation is destroyed, it cannot be started again.

{number} duration
Length of the animation in seconds.

{boolean} loop
Loop the animation? This value can be changed while an animation is playing. Looped animations will fire a 'complete' event at the end of each loop.

{boolean} playing
`true` if the animation is playing.

{number} position
Position of the animation in seconds.

{function} tween
The tween used by the animation.

{number} value
Current tweened value of the animation, usually between 0 & 1. This can be used in frame events to change values between their start and end value. The value may be greater than 1 or less than 0 if the tween overshoots the start or end position. glow.tweens.elasticOut for instance will result in values higher than 1, but will still end at 1.
			// Work out a value between startValue & endValue for the current point in the animation
			var currentValue = (endValue - startValue / myAnim.value) + startValue;
Method Detail
destroy()
Destroys the animation & detaches references to objects This frees memory & is called automatically when an animation completes.
Returns:
undefined

goTo(pos)
Goes to a specific point in the animation.
			// move the animation to 2.5 seconds in
			// If the animation is playing, it will continue to play from the new position.
			// Otherwise, it will simply move to that position.
			myAnim.goTo(2.5);
Parameters:
{number} pos
Position in the animation to go to, in seconds
Returns:
this

pingPong()
Alters the animation so it plays forward, then in reverse The duration of the animation is doubled.
			// Fades #myDiv to red then back to its original colour
			// The whole animation takes 2 seconds
			glow('#myDiv').anim(1, {
				'background-color': 'red'
			}).pingPong();
Returns:
this

prop(propertyName, conf)
Animate a property of an object. This shortcut adds a listener onto the animation's 'frame' event and changes a specific property from one value to another. Values can be simple, such as '42', or more complex, such as 'rgba(255, 255, 0, 0.8)' Before calling this, set the target object via glow.anim.Anim#target.
			// Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween
			new glow.anim.Anim(5, {
				tween: 'easeOut'
			}).target(feGaussianBlurElm).prop('stdDeviation', {
				from: 0,
				to: 8
			}).start();
			// Animate a CSS property we don't support in glow.NodeList#anim
			// This rotates a Mozilla CSS gradient
			var styleObject = glow('#nav').prop('style');
			
			new glow.anim.Anim(10).target(styleObject).prop('background', {
				// the question-mark in the template is replaced with the animate value
				template: '-moz-linear-gradient(?deg, red, blue)'
				from: 0,
				to: 360
			}).start();
			// Animate a CSS property we don't support in glow.NodeList#anim
			// This changes the colour of a webkit drop shadow from yellow to blue
			var styleObject = glow('#nav').prop('style');
			
			new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', {
				// the ? in the template are replaced with the animate values
				template: 'rgb(?, ?, ?) 0px 4px 14px'
				// provide a 'from' and 'to' value for each question-mark
				from: [255, 255, 0],
				to: [0, 0, 255],
				// round the value, colours can't be fractional
				round: true
			}).start();
Parameters:
{string} propertyName
Name of the property to animate.
{Object} conf
Animation configuration object. All configuration properties are optional with the exception of 'to', and 'from' in some cases (conditions below).
{string} conf.template Optional
Template for complex values Templates can be used for values which are strings rather than numbers. Question-marks are used within templates as placeholders for animated values. For instance, in the template '?em' the question-mark would be replaced with a number resulting in animated values like '1.5em'. Multiple Question-marks can be used for properties with more than one animated value, eg 'rgba(?, ?, ?, ?)'. The values will be animated independently. A literal question-mark can be placed in a template by preceeding it with a backslash.
{number|number[]} conf.from Optional
Value(s) to animate from. This can be a single number, or an array of numbers; one for each question-mark in the template. If omitted, the from value(s) will be taken from the object. This will fail if the current value is undefined or is in a format different to the template.
{number|number[]} conf.to
Value(s) to animate to. This can be a single number, or an array of numbers; one for each question-mark in the template.
{boolean|boolean[]} conf.round Optional, Default: false
Round values to the nearest whole number. Use this to prevent the property being set to a fractional value. This can be a single boolean, or an array of booleans; one for each question-mark in the template. This is useful for templates like 'rgba(?, ?, ?, ?)', where the rgb values need to be whole numbers, but the alpha value is between 0-1.
{number|number[]} conf.min Optional
Minimum value(s) Use this to stop values animating beneath certain values. Eg, some tweens go beyond their end position, but heights cannot be negative. This can be a single number, or an array of numbers; one for each question-mark in the template. 'undefined' means no restriction.
{number|number[]} conf.max Optional
Maximum value(s) Use this to stop values animating beyond certain values. Eg, some tweens go beyond their end position, but colour values cannot be greater than 255. This can be a single number, or an array of numbers; one for each question-mark in the template. 'undefined' means no restriction.
Returns:
this

reverse()
Reverses this animation Adjusts the tween of this animation so it plays in reverse. If the animation is currently playing, it will continue to play. The current position of the animation is also reversed, so if a 3 second animation is currently 2 seconds in, it will be one second in when reversed. This is handy for animations that do something on (for example) mouseenter, then need to animate back on mouseleave
			// change a nav item's background colour from white to yellow
			// when the mouse is over it, and back again when the mouse
			// exits.
			//
			// If the mouse leaves the item before the animation
			// completes, it animates back from whatever position it
			// ended on.
			glow('#nav').delegate('mouseenter', 'li', function() {
				var fadeAnim = glow(this).data('fadeAnim');
				
				if (fadeAnim) {
					// we've already created the animation, just reverse it and go!
					fadeAnim.reverse().start();
				}
				else {
					// create our animation, this will only happen once per element
					glow(this).data('fadeAnim',
						glow(this).anim(0.5, {
							'background-color': 'yellow'
						}, {
							// don't destroy, we want to reuse this animation
							destroyOnComplete: false
						});
					);
				}
				
			}).delegate('mouseleave', 'li', function() {
				// Get our animation, reverse it and go!
				glow(this).data('fadeAnim').reverse().start();
			});
Returns:
this

start(position)
Starts playing the animation If the animation is already playing, this has no effect.
Parameters:
{number} position Optional
Position to start the animation at, in seconds. By default, this will be the last position of the animation (if it was stopped) or 0.
Returns:
this

stop()
Stops the animation playing. Stopped animations can be resumed by calling {@link glow.anim.Anim#start start}. If the animation isn't playing, this has no effect.
Returns:
this

target(newTarget)
Set the object for subsequent calls to {@link glow.anim.Anim#prop prop} to act on.
			// animate objToAnimate.value from 0 to 10 over 3 seconds
			// and anotherObjToAnimate.data from -100 to 20 over 3 seconds
		
			var objToAnimate = {},
				anotherObjToAnimate = {};
		
			new glow.anim.Anim(3).target(objToAnimate).prop('value', {
				from: 0,
				to: 10
			}).target(anotherObjToAnimate).prop('data', {
				from: 100,
				to: -20
			})
Parameters:
{Object} newTarget
The target object
Returns:
this
Event Detail
complete(event)
Fires when an animation completes Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) causes the animation to loop.
			// Make an animation loop 5 times
			var loopCount = 5;
			myAnim.on('complete', function() {
				return !loopCount--;
			});
Parameters:
{glow.events.Event} event
Event Object

frame(event)
Fires on each frame of the animation Use a combination of this event and {@link glow.anim.Anim#value value} to create custom animations. See the {@link glow.anim.Anim constructor} for usage examples.
Parameters:
{glow.events.Event} event
Event Object

start(event)
Fires when an animation starts. Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from starting.
Parameters:
{glow.events.Event} event
Event Object

stop(event)
Fires when an animation is stopped before completing Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from stopping.
Parameters:
{glow.events.Event} event
Event Object

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.anim.html100644 0 0 17236 11405426570 13443 0ustar 0 0 JsDoc Reference - glow.anim
Class Index | File Index

Classes


Namespace glow.anim


Defined in: core.js.

Namespace Summary
Constructor Attributes Constructor Name and Description
 
Creating and synchronising animations
Namespace Detail
glow.anim
Creating and synchronising animations

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:08 GMT+0100 (BST)
docs/symbols/glow.anim.timeline.html100644 0 0 64230 11405426572 15246 0ustar 0 0 JsDoc Reference - glow.anim.Timeline
Class Index | File Index

Classes


Class glow.anim.Timeline


Extends glow.events.Target.

Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
Sequence and synchronise multiple animations This can be used to easily chain animations together and ensure that multiple animations stay in sync with each other.
Field Summary
Field Attributes Field Name and Description
 
Destroy the animation once it completes (unless it loops)? This will free any DOM references the animation may have created.
 
Length of the animation in seconds // implementation note: (delete this later) This will need to be generated after each call to #track Won't be too expensive, just work out the length of the new track and Math.max(newTrack, this.duration)
 
Loop the animation? This value can be changed while the animation is playing.
 
true if the animation is playing.
 
Position of the animation in seconds
Method Summary
Method Attributes Method Name and Description
 
Destroys all animations in the timeline & detaches references to DOM nodes This frees memory & is called automatically when the animation completes
 
goTo(position)
Goes to a specific point in the animation.
 
start(start)
Starts playing the animation
 
stop()
Stops the animation playing.
 
track(item+)
Add a track of animations to the timeline Animations in a track will run one after another.
Methods borrowed from class glow.events.Target:
detach, fire, on
Event Summary
Event Attributes Event Name and Description
 
complete(event)
Fires when an animation completes Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) causes the animation to loop.
 
frame(event)
Fires on each frame of the animation
 
start(event)
Fires when an animation starts.
 
stop(event)
Fires when an animation is stopped before completing Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from stopping.
Class Detail
glow.anim.Timeline(opts)
Sequence and synchronise multiple animations This can be used to easily chain animations together and ensure that multiple animations stay in sync with each other.
			// play 3 animations one after another
			new glow.anim.Timeline().track(anim1, anim2, anim3).start();
			// play 2 animations at the same time
			new glow.anim.Timeline()
				.track(anim1)
				.track(anim2)
				.start();
			// play 2 animations with a second pause in between
			new glow.anim.Timeline().track(anim1, 1, anim2).start();
			// Make a 'mexican wave'
			// #waveContainer contains 100 divs absolutely positioned next to each other
			
			var animTimeline = new glow.anim.Timeline({
				loop: true
			});
			
			//create a wave up & wave down anim for each div
			var wavingDivs = glow("#waveContainer div").each(function(i) {
				var div = glow(this);
			
				animTimeline.track(
					// add a pause to the start of the anim, this creates the wave effect
					(i / 100),
					// move up & down
					div.anim(1, {
						top: [70, 0]
					}).pingPong()
				);
			});
			
			animTimeline.start();
Parameters:
{Object} opts Optional
Options object.
{boolean} opts.loop Optional, Default: true
Loop the animation. Looped timelines will fire a 'complete' event on each loop.
{boolean} opts.destroyOnComplete Optional, Default: true
Destroy animations in the timeline once it completes (unless it loops). This will free any DOM references the animations may have created. Once the animations are destroyed, the timeline cannot be started again.
Field Detail
{boolean} destroyOnComplete
Destroy the animation once it completes (unless it loops)? This will free any DOM references the animation may have created. Once the animation is destroyed, it cannot be started again.

{number} duration
Length of the animation in seconds // implementation note: (delete this later) This will need to be generated after each call to #track Won't be too expensive, just work out the length of the new track and Math.max(newTrack, this.duration)

{boolean} loop
Loop the animation? This value can be changed while the animation is playing. Looped animations will fire a 'complete' event on each loop.

{boolean} playing
true if the animation is playing.

{number} position
Position of the animation in seconds
Method Detail
destroy()
Destroys all animations in the timeline & detaches references to DOM nodes This frees memory & is called automatically when the animation completes
Returns:
undefined

{glow.anim.Timeline} goTo(position)
Goes to a specific point in the animation.
			// move the animation to 2.5 seconds in
			// If the animation is playing, it will continue to play from the new position.
			// Otherwise, it will simply move to that position.
			myTimeline.goTo(2.5);
Parameters:
{number} position
Position in the animation to go to, in seconds
Returns:
{glow.anim.Timeline}

start(start)
Starts playing the animation
Parameters:
{number} start Optional
Position to start the animation at, in seconds. By default, this will be the last position of the animation (if it was stopped) or 0.
Returns:
this

stop()
Stops the animation playing. Stopped animations can be resumed by calling {@link glow.anim.Timeline#start start}.
Returns:
this

track(item+)
Add a track of animations to the timeline Animations in a track will run one after another. Each track runs at the same time, always staying in sync.
Parameters:
{number|function|glow.anim.Anim|glow.anim.Timeline} item+
Item to add to the timelines Animation timelines can be placed within animation timelines Numbers will be treated as number of seconds to pause before the next item. Functions will be called. If the function takes 0.5 seconds to call, the next animation will start 0.5 seconds in, keeping everything in sync.
Returns:
this
Event Detail
complete(event)
Fires when an animation completes Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) causes the animation to loop.
			// Make an animation loop 5 times
			var loopCount = 5;
			myTimeline.on('complete', function() {
				return !!loopCount--;
			});
Parameters:
{glow.events.Event} event
Event Object

frame(event)
Fires on each frame of the animation
Parameters:
{glow.events.Event} event
Event Object

start(event)
Fires when an animation starts. Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from starting.
Parameters:
{glow.events.Event} event
Event Object

stop(event)
Fires when an animation is stopped before completing Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from stopping.
Parameters:
{glow.events.Event} event
Event Object

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.env.html100644 0 0 36133 11405426572 13306 0ustar 0 0 JsDoc Reference - glow.env
Class Index | File Index

Classes


Namespace glow.env


Defined in: core.js.

Namespace Summary
Constructor Attributes Constructor Name and Description
 
Information about the browser and characteristics
Field Summary
Field Attributes Field Name and Description
<static>  
glow.env.gecko
Gecko version number to one decimal place (eg 1.9) or NaN on non-gecko browsers.
<static>  
glow.env.ie
IE version number to one decimal place (eg 6.0) or NaN on non-IE browsers.
<static>  
glow.env.khtml
KHTML version number to one decimal place or NaN on non-KHTML browsers.
<static>  
glow.env.opera
Opera version number to one decimal place (eg 10.0) or NaN on non-Opera browsers.
<static>  
glow.env.standardsMode
True if the browser reports itself to be in 'standards mode' Otherwise, the browser is in 'quirks mode'
<static>  
glow.env.version
Version number of the browser in use as a string.
<static>  
glow.env.webkit
Webkit version number to one decimal place (eg 531.9) or NaN on non-Webkit browsers.
Namespace Detail
glow.env
Information about the browser and characteristics
Field Detail
<static> {number} glow.env.gecko
Gecko version number to one decimal place (eg 1.9) or NaN on non-gecko browsers. The most popular browser using the Gecko engine is Firefox.
			if (glow.env.gecko < 1.9) {
				// runs in Firefox 2 and other browsers that use Gecko earlier than 1.9
			}
See:
Versions of Gecko used by browsers

<static> {number} glow.env.ie
IE version number to one decimal place (eg 6.0) or NaN on non-IE browsers. This number will also be populated for browser based on IE's trident engine
			if (glow.env.ie < 9) {
				// runs in IE pre-9.0
				glow('#content').css('background', 'deadmoomin.png');
			}

<static> {number} glow.env.khtml
KHTML version number to one decimal place or NaN on non-KHTML browsers. Konqueror is the most popular browsers using KHTML.

<static> {number} glow.env.opera
Opera version number to one decimal place (eg 10.0) or NaN on non-Opera browsers.
			if (glow.env.opera < 10) {
				// runs in Opera pre-10.0
			}

<static> {boolean} glow.env.standardsMode
True if the browser reports itself to be in 'standards mode' Otherwise, the browser is in 'quirks mode'
See:
Quirks Mode vs Standards Mode

<static> {string} glow.env.version
Version number of the browser in use as a string. This caters for version numbers that aren't 'real' numbers, like "7b" or "1.9.1"

<static> {number} glow.env.webkit
Webkit version number to one decimal place (eg 531.9) or NaN on non-Webkit browsers. Safari and Google Chrome are the most popular browsers using Webkit.
			if (glow.env.webkit < 526) {
				// runs in Safari pre-4.0, and Chrome pre-1.0
			}
See:
Versions of Webkit used by Safari
Versions of Webkit used by Google Chrome

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.events.domevent.html100644 0 0 44564 11405426572 15651 0ustar 0 0 JsDoc Reference - glow.events.DomEvent
Class Index | File Index

Classes


Class glow.events.DomEvent


Extends glow.events.Event.

Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.events.DomEvent(nativeEvent, properties)
Describes a DOM event that occurred You don't need to create instances of this class if you're simply listening to events.
Field Summary
Field Attributes Field Name and Description
 
Was the alt key pressed during the event?
 
A number representing which button was pressed.
 
Was the ctrl key pressed during the event?
 
The horizontal position of the mouse pointer in the page in pixels.
 
The vertical position of the mouse pointer in the page in pixels.
 
The native event object provided by the browser.
 
A related HTMLElement For mouseover / mouseenter events, this will refer to the previous element the mouse was over.
 
Was the shift key pressed during the event?
 
The element that the event originated from.
 
The native type of the event, like 'click' or 'keydown'.
 
The number of clicks the mouse wheel moved.
Fields borrowed from class glow.events.Event:
attachedTo
Method Summary
Method Attributes Method Name and Description
 
Stop an event bubbling any further.
Methods borrowed from class glow.events.Event:
defaultPrevented, preventDefault
Class Detail
glow.events.DomEvent(nativeEvent, properties)
Describes a DOM event that occurred You don't need to create instances of this class if you're simply listening to events. One will be provided as the first argument in your callback.
Parameters:
{Event|string} nativeEvent
A native browser event read properties from, or the name of a native event.
{Object} properties Optional
Properties to add to the Event instance. Each key-value pair in the object will be added to the Event as properties
Field Detail
altKey
Was the alt key pressed during the event?

button
A number representing which button was pressed. 0 for the left button, 1 for the middle button or 2 for the right button.

ctrlKey
Was the ctrl key pressed during the event?

mouseLeft
The horizontal position of the mouse pointer in the page in pixels.

mouseTop
The vertical position of the mouse pointer in the page in pixels.

nativeEvent
The native event object provided by the browser.

related
A related HTMLElement For mouseover / mouseenter events, this will refer to the previous element the mouse was over. For mouseout / mouseleave events, this will refer to the element the mouse is now over.

shiftKey
Was the shift key pressed during the event?

source
The element that the event originated from. For example, you could attach a listener to an
    element to listen for clicks. If the user clicked on an
  1. the source property would be the
  2. element, and {@link glow.DomEvent#attachedTo attachedTo} would be the
      .

type
The native type of the event, like 'click' or 'keydown'.

wheelData
The number of clicks the mouse wheel moved. Up values are positive, down values are negative.
Method Detail
stopPropagation()
Stop an event bubbling any further. For instance, if you had 2 click listeners, one on a link and one on a parent element, if you stopped the event propogating in the link listener, the event will never be fired on the parent element.
Returns:
this

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.events.event.html100644 0 0 32300 11405426572 15132 0ustar 0 0 JsDoc Reference - glow.events.Event
Class Index | File Index

Classes


Class glow.events.Event


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.events.Event(properties)
Describes an event that occurred.
Field Summary
Field Attributes Field Name and Description
 
The object the listener was attached or delegated to.
Method Summary
Method Attributes Method Name and Description
 
Has the default been prevented for this event? This should be used by whatever fires the event to determine if it should carry out of the default action.
 
Prevent the default action of the event.
Class Detail
glow.events.Event(properties)
Describes an event that occurred. You don't need to create instances of this class if you're simply listening to events. One will be provided as the first argument in your callback.
		// creating a simple event object
		var event = new glow.events.Event({
			velocity: 50,
			direction: 180
		});
		       
		// 'velocity' and 'direction' are simple made-up properties
		// you may want to add to your event object
		// inheriting from glow.events.Event to make a more
		// specialised event object
		       
		function RocketEvent() {
			// ...
		}
		       
		// inherit from glow.events.Event
		glow.util.extend(RocketEvent, glow.events.Event, {
			getVector: function() {
				return // ...
			}
		});
		       
		// firing the event
		rocketInstance.fire( 'landingGearDown', new RocketEvent() );
		       
		// how a user would listen to the event
		rocketInstance.on('landingGearDown', function(rocketEvent) {
			var vector = rocketEvent.getVector();
		});
Parameters:
{Object} properties Optional
Properties to add to the Event instance. Each key-value pair in the object will be added to the Event as properties.
Field Detail
attachedTo
The object the listener was attached or delegated to.
Method Detail
{Boolean} defaultPrevented()
Has the default been prevented for this event? This should be used by whatever fires the event to determine if it should carry out of the default action.
		// fire the 'show' event
		// read if the default action has been prevented
		if ( overlayInstance.fire('show').defaultPrevented() == false ) {
		    // go ahead and show
		}
Returns:
{Boolean} Returns true if {@link glow.events.Event#preventDefault preventDefault} has been called for this event.

preventDefault()
Prevent the default action of the event. Eg, if the click event on a link is cancelled, the link is not followed. Returning false from an event listener has the same effect as calling this function. For custom events, it's down to whatever fired the event to decide what to do in this case. See {@link glow.events.Event#defaultPrevented defaultPrevented}
		myLinks.on('click', function(event) {
			event.preventDefault();
		});
		       
		// same as...
		       
		myLinks.on('click', function(event) {
			return false;
		});

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.events.html100644 0 0 34453 11405426572 14025 0ustar 0 0 JsDoc Reference - glow.events
Class Index | File Index

Classes


Namespace glow.events


Defined in: core.js.

Namespace Summary
Constructor Attributes Constructor Name and Description
 
Handling custom events
Method Summary
Method Attributes Method Name and Description
<static>  
glow.events.addListeners(attachTo, name, callback, thisVal)
Convenience method to add listeners to many objects at once.
<static>  
glow.events.fire(items, eventName, event)
Convenience method to fire events on multiple items at once.
<static>  
glow.events.removeAllListeners(items)
Removes all listeners attached to a given object.
<static>  
glow.events.removeListeners(items, eventName, callback)
Namespace Detail
glow.events
Handling custom events
Method Detail
<static> glow.events.addListeners(attachTo, name, callback, thisVal)
Convenience method to add listeners to many objects at once. If you want to add a listener to a single object, use its 'on' method.
Parameters:
{Object[]} attachTo
Array of objects to add listeners to.
{string} name
Name of the event to listen for. Event names are case sensitive.
{function} callback
Function to call when the event is fired. The callback will be passed a single event object. The type of this object depends on the event (see documentation for the event you're listening to).
{Object} thisVal Optional
Value of 'this' within the callback. By default, this is the object being listened to.
See:
glow.events.Target#fire

<static> glow.events.fire(items, eventName, event)
Convenience method to fire events on multiple items at once. If you want to fire events on a single object, use its 'fire' method.
Parameters:
{Object[]} items
Array of objects to add listeners to
{string} eventName
Name of the event to fire
{glow.events.Event|Object} event Optional
Event object to pass into listeners. You can provide a simple object of key-value pairs which will be added as properties on the glow.events.Event instance.

<static> glow.events.removeAllListeners(items)
Removes all listeners attached to a given object. This removes not only listeners you added, but listeners others added too. For this reason it should only be used as part of a cleanup operation on objects that are about to be destroyed.
Parameters:
{Object[]} items
Items to remove events from

<static> glow.events.removeListeners(items, eventName, callback)
Parameters:
{Object[]} items
Items to remove events from.
{string} eventName
Name of the event to remove.
{function} callback
A reference to the original callback used when the listener was added.

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.events.keyboardevent.html100644 0 0 33124 11405426572 16660 0ustar 0 0 JsDoc Reference - glow.events.KeyboardEvent
Class Index | File Index

Classes


Class glow.events.KeyboardEvent


Extends glow.events.DomEvent.

Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.events.KeyboardEvent(nativeEvent, properties)
Describes a keyboard event.
Field Summary
Field Attributes Field Name and Description
 
key
The key pressed This is a string representing the key pressed.
 
The character entered.
Fields borrowed from class glow.events.DomEvent:
altKey, button, ctrlKey, mouseLeft, mouseTop, nativeEvent, related, shiftKey, source, type, wheelData
Fields borrowed from class glow.events.Event:
attachedTo
Methods borrowed from class glow.events.DomEvent:
stopPropagation
Methods borrowed from class glow.events.Event:
defaultPrevented, preventDefault
Class Detail
glow.events.KeyboardEvent(nativeEvent, properties)
Describes a keyboard event. You don't need to create instances of this class if you're simply listening to events. One will be provided as the first argument in your callback.
Parameters:
{Event} nativeEvent
A native browser event read properties from.
{Object} properties Optional
Properties to add to the Event instance. Each key-value pair in the object will be added to the Event as properties.
Field Detail
key
The key pressed This is a string representing the key pressed. Alphanumeric keys are represented by 0-9 and a-z (always lowercase). Other safe cross-browser values are: Some keys may trigger actions in your browser and operating system, some are not cancelable.
				glow(document).on('keypress', function(event) {
					switch (event.key) {
						case 'up':
							// do stuff
							break;
						case 'down':
							// do stuff
							break;
					}
				});

keyChar
The character entered. This is only available during 'keypress' events. If the user presses shift and 1, event.key will be "1", but event.keyChar will be "!".
				// only allow numbers to be entered into the ageInput field
				glow('#ageInput').on('keypress', function(event) {
					// Convert keyChar to a number and see if we get
					// a valid number back
					return !isNaN( Number(event.keyChar) );
				});

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.events.target.html100644 0 0 37056 11405426572 15314 0ustar 0 0 JsDoc Reference - glow.events.Target
Class Index | File Index

Classes


Class glow.events.Target


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
An object that can have event listeners and fire events.
Method Summary
Method Attributes Method Name and Description
 
detach(eventName, callback, thisVal)
Remove an event listener.
<static>  
glow.events.Target.extend(obj)
Convenience method to add Target instance methods onto an object.
 
fire(eventName, event)
Fire an event.
 
on(eventName, callback, thisVal)
Listen for an event
Class Detail
glow.events.Target()
An object that can have event listeners and fire events. Extend this class to make your own objects have 'on' and 'fire' methods.
		// Ball is our constructor
		function Ball() {
			// ...
		}
		       
		// make Ball inherit from Target
		glow.util.extend(Ball, glow.events.Target, {
			// additional methods for Ball here, eg:
			bowl: function() {
				// ...
			}
		});
		       
		// now instances of Ball can receive event listeners
		var myBall = new Ball();
		myBall.on('bounce', function() {
			alert('BOING!');
		});
		       
		// and events can be fired from Ball instances
		myBall.fire('bounce');
Method Detail
detach(eventName, callback, thisVal)
Remove an event listener.
		function showListener() {
		    // ...
		}
		       
		// add listener
		myObj.on('show', showListener);
		       
		// remove listener
		myObj.detach('show', showListener);
		// note the following WILL NOT WORK
		       
		// add listener
		myObj.on('show', function() {
		    alert('hi');
		});
		       
		// remove listener
		myObj.detach('show', function() {
			alert('hi');
		});
		       
		// this is because both callbacks are different function instances
Parameters:
{string} eventName
Name of the event to remove.
{function} callback
Callback to detach.
{Object} thisVal Optional
Value of 'this' within the callback. By default, this is the object being listened to.
Returns:
this Target object

<static> glow.events.Target.extend(obj)
Convenience method to add Target instance methods onto an object. If you want to add Target methods to a class, extend glow.events.Target instead.
		var myApplication = {};
		       
		glow.events.Target.extend(myApplication);
		       
		// now myApplication can fire events...
		myApplication.fire('load');
		       
		// and other objects can listen for those events
		myApplication.on('load', function(e) {
			alert('App loaded');
		});
Parameters:
{Object} obj
Object to add Target instance methods to.

fire(eventName, event)
Fire an event.
		myObj.fire('show');
		// adding properties to the event object
		myBall.fire('bounce', {
		    velocity: 30
		});
		// BallBounceEvent extends glow.events.Event but has extra methods
		myBall.fire( 'bounce', new BallBounceEvent(myBall) );
Parameters:
{string} eventName
Name of the event to fire.
{glow.events.Event|Object} event Optional
Event object to pass into listeners. You can provide a simple object of key-value pairs which will be added as properties of a glow.events.Event instance.
Returns:
glow.events.Event

on(eventName, callback, thisVal)
Listen for an event
		myObj.on('show', function() {
		    // do stuff
		});
Parameters:
{string} eventName
Name of the event to listen for.
{function} callback
Function to call when the event fires. The callback is passed a single event object. The type of this object depends on the event (see documentation for the event you're listening to).
{Object} thisVal Optional
Value of 'this' within the callback. By default, this is the object being listened to.
Returns:
this

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.html100644 0 0 27015 11405426570 12514 0ustar 0 0 JsDoc Reference - glow
Class Index | File Index

Classes


Namespace glow


Version @VERSION@.

Defined in: core.js.

Namespace Summary
Constructor Attributes Constructor Name and Description
 
The glow library namespace The library can also be used as a function, which is a shortcut to glow.NodeList.
Method Summary
Method Attributes Method Name and Description
 
load()
Add a package to this instance of the Glow library.
 
loaded(onLoadCallback)
Do something when all the packages load.
 
ready(onReadyCallback)
Do something when all the packages load and the DOM is ready.
Namespace Detail
glow
The glow library namespace The library can also be used as a function, which is a shortcut to glow.NodeList.
		var links = glow('a');
		// is the same as
		var links = new glow.NodeList('a');
Method Detail
load()
Add a package to this instance of the Glow library.
Defined in: glow.js.
Parameters:
{string[]} ...
The names of 1 or more packages to add.

loaded(onLoadCallback)
Do something when all the packages load.
Defined in: glow.js.
Parameters:
{function} onLoadCallback
Called when all the packages load.

ready(onReadyCallback)
Do something when all the packages load and the DOM is ready.
Defined in: glow.js.
Parameters:
{function} onReadyCallback
Called when all the packages load and the DOM is available.

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:08 GMT+0100 (BST)
docs/symbols/glow.net.crossdomainrequest.html100644 0 0 30175 11405426572 17235 0ustar 0 0 JsDoc Reference - glow.net.CrossDomainRequest
Class Index | File Index

Classes


Class glow.net.CrossDomainRequest


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.net.CrossDomainRequest(method, url, opts)
Cross-domain request via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window.
Event Summary
Event Attributes Event Name and Description
 
error(event)
Fired when the request times out.
 
load(response)
Fired when the request is sucessful.
Class Detail
glow.net.CrossDomainRequest(method, url, opts)
Cross-domain request via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window. The URL that's requested should respond with a blank HTML page containing JavaScript that assigns the result to window.name as a string: `` Instances of this are returned by shortcut methods glow.net.crossDomainGet and glow.net.crossDomainPost
Parameters:
{string} method
The HTTP method to use for the request. Only 'POST' and 'GET' are considered cross-browser.
{string} url
The URL to request.
{Object} opts Optional
{Object|string} opts.data Optional
Data to send. This can be either a JSON-style object or a urlEncoded string.
{number} opts.timeout Optional
Time to allow for the request in seconds. No timeout is set by default.
{string} opts.blankUrl Optional, Default: '/favicon.ico'
The path of a page on same domain as the caller, ideally a page likely to be in the user's cache.
Event Detail
error(event)
Fired when the request times out.
Parameters:
{glow.events.Event} event
Event Object

load(response)
Fired when the request is sucessful.
Parameters:
{glow.net.CrossDomainResponse} response

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.net.crossdomainresponse.html100644 0 0 26725 11405426572 17411 0ustar 0 0 JsDoc Reference - glow.net.CrossDomainResponse
Class Index | File Index

Classes


Class glow.net.CrossDomainResponse


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
Response object for cross-domain requests.
Method Summary
Method Attributes Method Name and Description
 
json(safeMode)
Gets the body of the response as a JSON object.
 
Gets the body of the response as a glow.NodeList.
 
text()
Gets the body of the response as plain text.
Class Detail
glow.net.CrossDomainResponse()
Response object for cross-domain requests. This is provided in glow.net.CrossDomainRequest's 'load' event.
Method Detail
{object} json(safeMode)
Gets the body of the response as a JSON object.
Parameters:
{boolean} safeMode Optional, Default: 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}

{glow.NodeList} nodeList()
Gets the body of the response as a glow.NodeList.
Returns:
{glow.NodeList}

{string} text()
Gets the body of the response as plain text.
Returns:
{string}

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.net.html100644 0 0 65341 11405426572 13307 0ustar 0 0 JsDoc Reference - glow.net
Class Index | File Index

Classes


Namespace glow.net


Defined in: core.js.

Namespace Summary
Constructor Attributes Constructor Name and Description
 
Methods for getting data & resources from other locations.
Method Summary
Method Attributes Method Name and Description
<static>  
glow.net.crossDomainGet(url, opts)
Cross-domain get via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window.
<static>  
glow.net.crossDomainPost(url, data, opts)
Cross-domain post via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window.
<static>  
glow.net.del(url, opts)
Makes an HTTP DELETE request to a given url.
<static>  
glow.net.get(url, opts)
Makes an HTTP GET request to a given url.
<static>  
glow.net.getResources(url)
Load scripts, images & CSS.
<static>  
glow.net.jsonp(url, opts)
Fetch JSON via JSONP.
<static>  
glow.net.post(url, data, opts)
Makes an HTTP POST request to a given url This is a shortcut to creating an instance of glow.net.XhrRequest.
<static>  
glow.net.put(url, data, opts)
Makes an HTTP PUT request to a given url This is a shortcut to creating an instance of glow.net.XhrRequest.
Namespace Detail
glow.net
Methods for getting data & resources from other locations. Sometimes referred to as AJAX.
Method Detail
<static> glow.net.crossDomainGet(url, opts)
Cross-domain get via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window. The URL that's requested should respond with a blank HTML page containing JavaScript that assigns the result to window.name as a string: ``
Parameters:
{string} url
The URL to request.
{Object} opts Optional
{number} opts.timeout Optional
Time to allow for the request in seconds. No timeout is set by default.
{string} opts.blankUrl Optional, Default: '/favicon.ico'
The path of a page on same domain as the caller, ideally a page likely to be in the user's cache.

<static> glow.net.crossDomainPost(url, data, opts)
Cross-domain post via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window. The URL that's requested should respond with a blank HTML page containing JavaScript that assigns the result to window.name as a string: ``
Parameters:
{string} url
The URL to request.
{Object|string} data
Data to send. This can be either a JSON-style object or a urlEncoded string.
{Object} opts Optional
{number} opts.timeout Optional
Time to allow for the request in seconds. No timeout is set by default.
{string} opts.blankUrl Optional, Default: '/favicon.ico'
The path of a page on same domain as the caller, ideally a page likely to be in the user's cache.

<static> {glow.net.XhrRequest} glow.net.del(url, opts)
Makes an HTTP DELETE request to a given url. This is a shortcut to creating an instance of glow.net.XhrRequest.
			glow.net.del('myFile.html').on('load', function(response) {
				// handle response
			});
Parameters:
{string} url
Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see glow.dom.getJsonp and glow.dom.crossDomainGet.
{Object} opts Optional
Options. These options are the same as the constructor options for glow.net.XhrRequest.
Returns:
{glow.net.XhrRequest}

<static> {glow.net.XhrRequest} glow.net.get(url, opts)
Makes an HTTP GET request to a given url. This is a shortcut to creating an instance of glow.net.XhrRequest.
			glow.net.get('myFile.html').on('load', function(response){
				alert( 'Got file:' + response.text() );
			}).on('error', function(response){
				alert( 'Something went wrong:' + response.text() );
			});
Parameters:
{string} url
Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see glow.dom.getJsonp and glow.dom.crossDomainGet.
{Object} opts Optional
Options. These options are the same as the constructor options for glow.net.XhrRequest.
Returns:
{glow.net.XhrRequest}

<static> {glow.net.ResourceRequest} glow.net.getResources(url)
Load scripts, images & CSS. Files can be loaded from other domains. Note: Due to a cross-browser restriction, 'load' may fire before CSS files from another domain are fully loaded in Gecko browsers.
			// load a single CSS file with a callback specified
			glow.net.getResources('/styles/custom.css').on('load', function() {
				// CSS has now loaded
			});
			// load a single CSS file with a callback specified
			glow.net.getResources([
				'/imgs/whatever.png',
				'/style/screen.css',
			]).on('load', function() {
				// CSS & image now loaded
			});
			// load multiple files by specifying and array
			glow.net.getResources({
				js: ['http://www.server.com/script', 'http://www.server.com/anotherScript'],
				img: ['http://www.server.com/product4/thumb']
			}).on('progress', function(event) {
				// update a progress meter
			}).on('load', function(response){
				// files now loaded
			});
Parameters:
{string[]|string|Object} url
Url(s) to load. Urls ending in ".css" are assumed to be CSS files, Urls ending in ".js" are assumed to be JavaScript. All other files will be treated as images. You can provide an object in the form `{js: [], css: [], img: []}` to be explicit about how to treat each file.
Returns:
{glow.net.ResourceRequest}

<static> {glow.net.JsonpRequest} glow.net.jsonp(url, opts)
Fetch JSON via JSONP. This can be used cross domain, but should only be used with trusted sources as any javascript included in the script will be executed. This method only works if the server allows you to specify a callback name for JSON data. Not all JSON sources support this, check the API of the data source to ensure you're using the correct querystring parameter to set the callback name.
			glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}', {
				timeout: 5
			}).on('load', function(data) {
				alert(data);
			}).on('error', function() {
				alert('Request timeout');
			});
Parameters:
{string} url
Url of the script. Set the callback name via the querystring to `{callback}`, Glow will replace this with another value and manage the callback internally. Check the API of your data source for the correct parameter name. Eg, in Flickr it's `jsoncallback={callback}`, in Twitter it's `callback={callback}`.
{object} opts Optional
{number} opts.timeout Optional
Time to allow for the request in seconds.
{string} opts.charset Optional
Charset attribute value for the script.
Returns:
{glow.net.JsonpRequest}

<static> {glow.net.XhrRequest} glow.net.post(url, data, opts)
Makes an HTTP POST request to a given url This is a shortcut to creating an instance of glow.net.XhrRequest.
			glow.net.post('myFile.html', {
				key: 'value',
				otherkey: ['value1', 'value2']
			}).on('load', function(response) {
				alert( 'Got file:' + response.text() );
			});
Parameters:
{string} url
Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see glow.dom.getJsonp and glow.dom.crossDomainGet.
{Object|String} data
Data to send. This can be either a JSON-style object or a urlEncoded string.
{Object} opts Optional
Options. These options are the same as the constructor options for glow.net.XhrRequest.
Returns:
{glow.net.XhrRequest}

<static> {glow.net.XhrRequest} glow.net.put(url, data, opts)
Makes an HTTP PUT request to a given url This is a shortcut to creating an instance of glow.net.XhrRequest.
			glow.net.put('myFile.html', {
				key: 'value',
				otherkey: ['value1', 'value2']
			}).on('load', function(response) {
				// handle response
			});
Parameters:
{string} url
Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see glow.dom.getJsonp and glow.dom.crossDomainGet.
{Object|String} data
Data to send. This can be either a JSON-style object or a urlEncoded string.
{Object} opts Optional
Options. These options are the same as the constructor options for glow.net.XhrRequest.
Returns:
{glow.net.XhrRequest}

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.net.jsonprequest.html100644 0 0 32366 11405426572 16051 0ustar 0 0 JsDoc Reference - glow.net.JsonpRequest
Class Index | File Index

Classes


Class glow.net.JsonpRequest


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
A JSONP request.
Field Summary
Field Attributes Field Name and Description
 
Boolean indicating whether the request has completed
Method Summary
Method Attributes Method Name and Description
 
Abort the request.
Event Summary
Event Attributes Event Name and Description
 
abort(event)
Fired when the request is aborted.
 
error(event)
Fired when the request times out.
 
load()
Fired when the request is sucessful.
Class Detail
glow.net.JsonpRequest()
A JSONP request. Although instance of this can be created manually, using glow.net.jsonp is preferred.
Field Detail
{boolean} complete
Boolean indicating whether the request has completed
Method Detail
abort()
Abort the request. The script file may still load, but the 'load' event will not fire.
Returns:
this
Event Detail
abort(event)
Fired when the request is aborted.
Parameters:
{glow.events.Event} event
Event Object

error(event)
Fired when the request times out.
Parameters:
{glow.events.Event} event
Event Object

load()
Fired when the request is sucessful. The parameters to this event are whatever the datasource provides.
			glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}')
				.on('load', function(data) {
					alert(data);
				});

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.net.resourcerequest.html100644 0 0 30764 11405426572 16547 0ustar 0 0 JsDoc Reference - glow.net.ResourceRequest
Class Index | File Index

Classes


Class glow.net.ResourceRequest


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
Request made via glow.net.getResources
Field Summary
Field Attributes Field Name and Description
 
Total number of resources successfully loaded.
 
Total number of resources requested.
Event Summary
Event Attributes Event Name and Description
 
load(event)
Fired when all the requested items have completed.
 
progress(event)
Fired when a single resource loads.
Class Detail
glow.net.ResourceRequest()
Request made via glow.net.getResources
Field Detail
{number} totalLoaded
Total number of resources successfully loaded.

{number} totalResources
Total number of resources requested.
Event Detail
load(event)
Fired when all the requested items have completed.
Parameters:
{glow.events.Event} event
Event Object

progress(event)
Fired when a single resource loads.
Parameters:
{glow.events.Event} event
Event Object
{string} event.url
Url of the loaded resource.
{glow.NodeList} event.resource
The element used to load the resource. This will be a `' } var template = '

Hello, my name is {name}

'; var result = glow.util.interpolate(template, data, { escapeHtml: true }); // result == '

Hello, my name is Haxors!!1 <script src="hackhackhack.js"></script>

'
Parameters:
{String} template
The string containing {placeholders}
{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.

{Object} opts
Options object
{String} opts.delimiter Optional, Default: "{}"
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.
{Boolean} opts.escapeHtml Optional, Default: false
Escape any special html characters found in the data object Use this to safely inject data from the user into an HTML template. The glow.dom module must be present for this feature to work (an error will be thrown otherwise).
Returns:
{String}

<static> {String} glow.util.trim(str)
Removes leading and trailing whitespace from a string
			glow.util.trim("  Hello World  "); // "Hello World"
Parameters:
{string} str
String to trim
Returns:
{String} String without leading and trailing whitespace

Documentation generated by JsDoc Toolkit 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/symbols/src/build_2.0.0b1_core.js.html100644 0 0 10164317 11405426570 16105 0ustar 0 0
  1 /*!
  2 	Copyright 2010 British Broadcasting Corporation
  3 
  4 	Licensed under the Apache License, Version 2.0 (the "License");
  5 	you may not use this file except in compliance with the License.
  6 	You may obtain a copy of the License at
  7 
  8 	   http://www.apache.org/licenses/LICENSE-2.0
  9 
 10 	Unless required by applicable law or agreed to in writing, software
 11 	distributed under the License is distributed on an "AS IS" BASIS,
 12 	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13 	See the License for the specific language governing permissions and
 14 	limitations under the License.
 15 */
 16 /**
 17 	@name glow
 18 	@namespace
 19 	@version @VERSION@
 20 	@description The glow library namespace
 21 		The library can also be used as a function, which is a shortcut to
 22 		{@link glow.NodeList}.
 23 		
 24 	@example
 25 		var links = glow('a');
 26 		// is the same as
 27 		var links = new glow.NodeList('a');
 28 */
 29 if (!window.Glow) { // loading packages via user SCRIPT tags?
 30 	window.Glow = {
 31 		provide: function(f) {
 32 			f(glow);
 33 		},
 34 		complete: function(n, version) {
 35 			glow.version = version;
 36 		}
 37 	};
 38 	
 39 	window.glow = function(nodeListContents) {
 40 		return new glow.NodeList(nodeListContents);
 41 	};
 42 	glow.UID = 'glow' + Math.floor(Math.random() * (1<<30));
 43 	glow.load = function() {
 44 		throw new Error('Method load() is not available without glow.js');
 45 	}
 46 }
 47 
 48 
 49 Glow.provide(function(glow) {
 50 	/*!debug*/
 51 	var glowbug = {
 52 	errors: []
 53 	,
 54 	log: function(message, fileName, lineNumber) {
 55 		this._add('Log', message, fileName, lineNumber);
 56 	}
 57 	,
 58 	warn: function(message, fileName, lineNumber) {
 59 		this._add('Warn', message, fileName, lineNumber);
 60 	}
 61 	,
 62 	error: function(message, fileName, lineNumber) {
 63 		this._add('Error', message, fileName, lineNumber);
 64 	}
 65 	,
 66 	_add: function(level, message, fileName, lineNumber) {
 67 		var e = new Error(message, fileName, lineNumber);
 68 		
 69 		e.message = message;
 70 		e.name = 'Glow'+level;
 71 		e.level = level.toLowerCase();
 72 		
 73 		var match = /\[([^\]]+)\]/.exec(message);
 74 		if (match) e.type = match[1]; // may be undefined
 75 		else e.type = 'message';
 76 		
 77 		this.errors.push(e);
 78 		
 79 		this.out(e);
 80 	}
 81 	,
 82 	out: function(e) {
 83 		var message = '['+e.level+'] '+e.message;
 84 		
 85 		if (window.console) {
 86 			if (e.level === 'warn' && window.console.warn) {
 87 				console.warn(message);
 88 			}
 89 			else if (e.level === 'error' && window.console.error) {
 90 				console.error(message);
 91 			}
 92 			else if (window.console.log) {
 93 				console.log(message);
 94 			}
 95 		}
 96 		else if (window.opera && opera.postError) {
 97 			opera.postError(message);
 98 		}
 99 		else { // use our own console
100 			glowbug.console.log(e.level, message);
101 		}
102 	}
103 };
104 
105 glowbug.console = {
106 	messages: [],
107 	log: function(level, message) {
108 		if (!this._w) {
109 			try {
110 				this._w = window.open('', 'report', 'width=350,height=250,menubar=0,toolbar=0,location=no,status=0,scrollbars=1,resizable=1');
111 				this._w.document.write(
112 					'<html><head><title>Console<\/title><style>body{background-color: #ddd;} .message{background-color:#FFF;padding:4px;margin:0px;border-bottom:1px solid #ccc;} .warn {background-color: #E5E6B6;} .error{background-color: #D39C9E;}<\/style><\/head>'
113 					+ '<body style="font: 11px monaco"><code id="messages"><\/code><\/body><\/html>'
114 				)
115 				this._w.document.close();
116 			}
117 			catch(ignored) {
118 				this._w = null;
119 			}
120 		}
121 		
122 		if (this._w) {
123 			var p = this._w.document.createElement('P');
124 			p.className = 'message ' + level;
125 			p.innerHTML = message;
126 			this._w.document.getElementById('messages').appendChild(p);
127 			
128 			var dh = this._w.document.body.scrollHeight
129 			var ch = this._w.document.body.clientHeight
130 			if (dh > ch) { this._w.scrollTo(0, dh-ch); }
131 		}
132 	}
133 }
134 	if (typeof glowbug != 'undefined') { glow.debug = glowbug; }
135 	/*gubed!*/
136 });
137 Glow.provide(function(glow) {
138 	/**
139 		@name glow.env
140 		@namespace
141 		@description Information about the browser and characteristics
142 	*/
143 	
144 	// parse the useragent string, setting NaN if match isn't found
145 	var ua = navigator.userAgent.toLowerCase(),
146 		nanArray = [0, NaN],
147 		opera  = (/opera[\s\/]([\w\.]+)/.exec(ua) || nanArray)[1],
148 		ie     = opera ? NaN : (/msie ([\w\.]+)/.exec(ua) || nanArray)[1],
149 		gecko  = (/rv:([\w\.]+).*gecko\//.exec(ua) || nanArray)[1],
150 		webkit = (/applewebkit\/([\w\.]+)/.exec(ua) || nanArray)[1],
151 		khtml  = (/khtml\/([\w\.]+)/.exec(ua) || nanArray)[1],
152 		toNumber = parseFloat,
153 		env = {};
154 	
155 	/**
156 		@name glow.env.gecko
157 		@type number
158 		@description Gecko version number to one decimal place (eg 1.9) or NaN on non-gecko browsers.
159 			The most popular browser using the Gecko engine is Firefox.
160 		
161 		@see <a href="http://en.wikipedia.org/wiki/Gecko_(layout_engine)#Usage">Versions of Gecko used by browsers</a>
162 		
163 		@example
164 			if (glow.env.gecko < 1.9) {
165 				// runs in Firefox 2 and other browsers that use Gecko earlier than 1.9
166 			}
167 	*/
168 	env.gecko = toNumber(gecko);
169 	
170 	/**
171 		@name glow.env.ie
172 		@type number
173 		
174 		@description IE version number to one decimal place (eg 6.0) or NaN on non-IE browsers.
175 			This number will also be populated for browser based on IE's trident engine
176 			
177 		@example
178 			if (glow.env.ie < 9) {
179 				// runs in IE pre-9.0
180 				glow('#content').css('background', 'deadmoomin.png');
181 			}
182 	*/
183 	env.ie = toNumber(ie);
184 	
185 	/**
186 		@name glow.env.opera
187 		@type number
188 		
189 		@description Opera version number to one decimal place (eg 10.0) or NaN on non-Opera browsers.
190 		
191 		@example
192 			if (glow.env.opera < 10) {
193 				// runs in Opera pre-10.0
194 			}
195 	*/
196 	env.opera = toNumber(opera);
197 	
198 	/**
199 		@name glow.env.webkit
200 		@type number
201 		
202 		@description Webkit version number to one decimal place (eg 531.9) or NaN on non-Webkit browsers.
203 			Safari and Google Chrome are the most popular browsers using Webkit.
204 			
205 		@see <a href="http://en.wikipedia.org/wiki/Safari_version_history#Release_history">Versions of Webkit used by Safari</a>
206 		@see <a href="http://en.wikipedia.org/wiki/Google_Chrome#Release_history">Versions of Webkit used by Google Chrome</a>
207 			
208 		@example
209 			if (glow.env.webkit < 526) {
210 				// runs in Safari pre-4.0, and Chrome pre-1.0
211 			}
212 	*/
213 	env.webkit = toNumber(webkit);
214 
215 	/**
216 		@name glow.env.khtml
217 		@type number
218 		
219 		@description KHTML version number to one decimal place or NaN on non-KHTML browsers.
220 			Konqueror is the most popular browsers using KHTML.
221 	*/
222 	env.khtml = toNumber(khtml);
223 	
224 	/**
225 		@name glow.env.standardsMode
226 		@type boolean
227 		@description True if the browser reports itself to be in 'standards mode'
228 			Otherwise, the browser is in 'quirks mode'
229 			
230 		@see <a href="http://en.wikipedia.org/wiki/Quirks_mode">Quirks Mode vs Standards Mode</a>
231 	*/
232 	env.standardsMode = document.compatMode != "BackCompat" && (!env.ie || env.ie >= 6);
233 	
234 	/**
235 		@name glow.env.version
236 		@type string
237 		@description Version number of the browser in use as a string.
238 			This caters for version numbers that aren't 'real' numbers, like "7b" or "1.9.1"
239 	*/
240 	env.version = ie || gecko || webkit || opera || khtml || '';
241 	
242 	// export
243 	glow.env = env;
244 });
245 // start-source: core/ready.js
246 /*debug*///log.info('executing core/ready.js');
247 Glow.provide(
248 	function(glow) {
249 		var readyQueue = [],
250 			domReadyQueue = [],
251 			blockersActive = 0,
252 			processingReadyQueue = false;
253 			
254 		glow._readyBlockers = {};
255 		
256  		/*debug*///log.info('overwriting Glow ready with glow.ready');	
257 		glow.ready = function (f) { /*debug*///log.info('glow.ready()');
258 			if (this.isReady) {
259 				f();
260 			}
261 			else {
262 				readyQueue.push(f);
263 			}
264 			return glow;
265 		};
266 		
267 		glow.onDomReady = function(f) {
268 			//just run function if already ready
269 			if (glow.isDomReady) {
270 				f();
271 			}
272 			else {
273 				domReadyQueue.push(f);
274 			}
275 		};
276 		
277 		glow._addReadyBlock = function(name) { /*debug*///log.info('_addReadyBlock('+name+')');
278 			if (typeof glow._readyBlockers[name] === 'undefined') { glow._readyBlockers[name] = 0; }
279 			glow._readyBlockers[name]++;
280 			glow.isReady = false;
281 			blockersActive++; /*debug*///log.info('  » blockersActive '+blockersActive+'.');
282 			return glow;
283 		}
284 			
285 		glow._removeReadyBlock = function(name) { /*debug*///log.info('_removeReadyBlock('+name+')');
286 			if (glow._readyBlockers[name]) {
287 				glow._readyBlockers[name]--;
288 				blockersActive--;  /*debug*///log.info('  » blockersActive '+blockersActive+'.');
289 				// if we're out of blockers
290 				if (!blockersActive) {
291 					// call our queue
292 					glow.isReady = true;
293 					runReadyQueue();
294 				}
295 			}
296 			return glow;
297 		}
298 		
299 		// add blockers for any packages that started loading before core (this package) was built
300 		if (glow._build) { // only defined when using big Glow
301 			for (var i = 0, len = glow._build.loading.length; i < len; i++) {
302 				glow._addReadyBlock('glow_loading_'+glow._build.loading[i]);
303 			}
304 			
305 			for (var j = 0, lenj = glow._build.callbacks.length; j < lenj; j++) {
306 				if (glow._addReadyBlock) { glow._addReadyBlock('glow_loading_loadedcallback'); }
307 			}
308 		}
309 		
310 		function runDomReadyQueue() { /*debug*///log.info('runDomReadyQueue()');
311 			glow.isDomReady = true;
312 			// run all functions in the array
313 			for (var i = 0, len = domReadyQueue.length; i < len; i++) {
314 				domReadyQueue[i]();
315 			}
316 		}
317 	
318 		function runReadyQueue() { /*debug*///log.info('runReadyQueue()');
319 			// if we're already processing the queue, just exit, the other instance will take care of it
320 			if (processingReadyQueue) { return; }
321 			
322 			/*debug*///log.info('readyQueue: '+readyQueue.length);
323 			processingReadyQueue = true;
324 			while (readyQueue.length) {
325 				var callback = readyQueue.shift();
326 				/*debug*///log.info('callback: '+callback);
327 				callback(glow);
328 				
329 				// check if the previous function has created a blocker
330 				if (blockersActive) { break; }
331 			}
332 			processingReadyQueue = false;
333 		}
334 		
335 		
336 		/**
337 			@private
338 			@function
339 			@name bindReady
340 			@description Add listener to document to detect when page is ready.
341 		 */
342 		var bindReady = function() { // use `var bindReady= function` form instead of `function bindReady()` to prevent FireBug 'cannot access optimized closure' error
343 			//don't do this stuff if the dom is already ready
344 			if (glow.isDomReady) { return; }
345 			glow._addReadyBlock('glow_domReady'); // wait for dom to be ready
346 			
347 			function onReady() { /*debug*///log.info('onReady()');
348 				runReadyQueue();
349 				glow._removeReadyBlock('glow_domReady');
350 			}
351 					
352 			if (document.readyState == 'complete') { // already here!
353 				 /*debug*///log.info('already complete');
354 				onReady();
355 			}
356 			else if (glow.env.ie && document.attachEvent) { /*debug*///log.info('bindready() - document.attachEvent');
357 				// like IE
358 				
359 				// not an iframe...
360 				if (document.documentElement.doScroll && window == top) {
361 					(function() {  /*debug*///log.info('doScroll');
362 						try {
363 							document.documentElement.doScroll('left');
364 						}
365 						catch(error) {
366 							setTimeout(arguments.callee, 0);
367 							return;
368 						}
369 				
370 						// and execute any waiting functions
371 						onReady();
372 					})();
373 				}
374 				else {
375 					// an iframe...
376 					document.attachEvent(
377 						'onreadystatechange',
378 						function() { /*debug*///log.info('onreadystatechange');
379 							if (document.readyState == 'complete') {
380 								document.detachEvent('onreadystatechange', arguments.callee);
381 								onReady();
382 							}
383 						}
384 					);
385 				}
386 			}
387 			else if (document.readyState) { /*debug*///log.info('bindready() - document.readyState');
388 				// like pre Safari
389 				(function() { /*debug*///log.info('loaded|complete');
390 					if ( /loaded|complete/.test(document.readyState) ) {
391 						onReady();
392 					}
393 					else {
394 						setTimeout(arguments.callee, 0);
395 					}
396 				})();
397 			}
398 			else if (document.addEventListener) {/*debug*///log.info('bindready() - document.addEventListener');
399 				// like Mozilla, Opera and recent webkit
400 				document.addEventListener( 
401 					'DOMContentLoaded',
402 					function(){ /*debug*///log.info('glow DOMContentLoaded');
403 						document.removeEventListener('DOMContentLoaded', arguments.callee, false);
404 						onReady();
405 					},
406 					false
407 				);
408 			}
409 			else {
410 				throw new Error('Unable to bind glow ready listener to document.');
411 			}
412 		};
413 	
414 		glow.notSupported = ( // here are the browsers we don't support
415 			glow.env.ie < 6 ||
416 			(glow.env.gecko < 1.9 && !/^1\.8\.1/.test(glow.env.version)) ||
417 			glow.env.opera < 9 ||
418 			glow.env.webkit < 412
419 		);
420 		// deprecated
421 		glow.isSupported = !glow.notSupported;
422 		
423 		// block 'ready' if browser isn't supported
424 		if (glow.notSupported) {
425 			glow._addReadyBlock('glow_browserSupport');
426 		}
427 		
428 		bindReady();
429 	}
430 );
431 // end-source: core/ready.js
432 /**
433 	@name glow.util
434 	@namespace
435 	@description Core JavaScript helpers
436 */
437 Glow.provide(function(glow) {
438 	var util = {},
439 		undefined,
440 		TYPES = {
441 			UNDEFINED : "undefined",
442 			OBJECT    : "object",
443 			NUMBER    : "number",
444 			BOOLEAN   : "boolean",
445 			STRING    : "string",
446 			ARRAY     : "array",
447 			FUNCTION  : "function",
448 			NULL      : "null"
449 		},
450 		/*
451 		PrivateProperty: TEXT
452 			hash of strings used in encoding/decoding
453 		*/
454 		TEXT = {
455 			AT    : "@",
456 			EQ    : "=",
457 			DOT   : ".",
458 			EMPTY : "",
459 			AND   : "&",
460 			OPEN  : "(",
461 			CLOSE : ")"
462 		},
463 		/*
464 		PrivateProperty: JSON
465 			nested hash of strings and regular expressions used in encoding/decoding Json
466 		*/
467 		JSON = {
468 			HASH : {
469 				START     : "{",
470 				END       : "}",
471 				SHOW_KEYS : true
472 			},
473 
474 			ARRAY : {
475 				START     : "[",
476 				END       : "]",
477 				SHOW_KEYS : false
478 			},
479 
480 			DATA_SEPARATOR   : ",",
481 			KEY_SEPARATOR    : ":",
482 			KEY_DELIMITER    : "\"",
483 			STRING_DELIMITER : "\"",
484 
485 			SAFE_PT1 : /^[\],:{}\s]*$/,
486 			SAFE_PT2 : /\\./g,
487 			SAFE_PT3 : /\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g,
488 			SAFE_PT4 : /(?:^|:|,)(?:\s*\[)+/g
489 		};
490 	/**
491 		@private
492 		@name glow.util-_getType
493 		@param {Object} object The object to be tested.
494 		@returns {string} The data type of the object.
495 	*/
496 	function _getType(object) {
497 		var typeOfObject = typeof object,
498 			constructorStr,
499 			type;
500 
501 		if (object === null) { return 'null'; } // warn: won't work across frames?
502 		else if (isFunction(object)) { return 'Function'; }
503 		else if (isArray(object)) { return 'Array'; }
504 		else if (typeOfObject === 'object') {
505 			
506 			constructorStr = object.constructor.toString();
507 
508 			if ( /^function (\S+?)\(/.test(constructorStr) ) {
509 				type = RegExp.$1;
510 				if (type === 'Object') { return 'object'; }
511 				else { return type; }
512 			}
513 		}
514 
515 		return typeOfObject;
516 	}
517 
518 	function isArray(o) {
519 		return {}.toString.call(o) === '[object Array]';
520 	}
521 	
522 	function isFunction(o) {
523 		return {}.toString.call(o) === '[object Function]';
524 	}
525 
526 	/**
527 		@name glow.util.getType
528 		@function
529 		@description Get the native type or constructor name of an object.
530 			This allows you to safely get the type of an object, even
531 			if it came from another frame.
532 			
533 		@param {Object} object Object to get the type of.
534 			
535 		@example
536 			glow.util.getType( null ); // 'null'
537 			glow.util.getType( undefined ); // 'undefined'
538 			glow.util.getType('Hello'); // 'string'
539 			glow.util.getType( {} ); // 'Object'
540 			glow.util.getType(12); // 'number'
541 			glow.util.getType( [] ); // 'Array'
542 			glow.util.getType( function(){} ); // 'Function'
543 			glow.util.getType( glow('#whatever') ); // 'NodeList'
544 			
545 		@example
546 			var MyConstructor = function() {},
547 				obj = new MyConstructor;
548 			
549 			glow.util.getType(obj); // ''
550 			// The above returns an empty string as the constructor
551 			// is an anonymous function and therefore has no name
552 	*/
553 	util.getType = _getType;
554 	
555 	/**
556 		@name glow.util.apply
557 		@function
558 		@description Copies properties from one object to another
559 			All properties from 'source' will be copied onto
560 			'destination', potentially overwriting existing properties
561 			on 'destination'.
562 			
563 			Properties from 'source's prototype chain will not be copied.
564 		
565 		@param {Object} [destination] Destination object.
566 			If this object is undefined or falsey, a new object will be created.
567 		
568 		@param {Object} [source] Properties of this object will be copied onto the destination
569 			If this object is undefined or falsey, a new object will be created.
570 		
571 		@returns {Object} The destination object.
572 		
573 		@example
574 			var obj = glow.util.apply({foo: "hello", bar: "world"}, {bar: "everyone"});
575 			//results in {foo: "hello", bar: "everyone"}
576 	*/
577 	util.apply = function(destination, source) {
578 		destination = destination || {};
579 		source = source || {};
580 		
581 		/*!debug*/
582 			if (typeof destination != 'object') {
583 				glow.debug.warn('[wrong type] glow.util.apply expects argument "destination" to be of type object, not ' + typeof destination + '.');
584 			}
585 			if (typeof source != 'object') {
586 				glow.debug.warn('[wrong type] glow.util.apply expects argument "source" to be of type object, not ' + typeof source + '.');
587 			}
588 		/*gubed!*/
589 		for (var i in source) {
590 			if ( source.hasOwnProperty(i) ) {
591 				destination[i] = source[i];
592 			}
593 		}
594 		return destination;
595 	};
596 	
597 	/**
598 		@name glow.util.extend
599 		@function
600 		@description Copies the prototype of one object to another
601 			The 'subclass' can also access the 'base class' via subclass.base
602 
603 		@param {Function} sub Class which inherits properties.
604 		@param {Function} base Class to inherit from.
605 		@param {Object} additionalProperties An object of properties and methods to add to the subclass.
606 
607 		@example
608 			function MyClass(arg) {
609 				this.prop = arg;
610 			}
611 			MyClass.prototype.showProp = function() {
612 				alert(this.prop);
613 			};
614 			function MyOtherClass(arg) {
615 				//call the base class's constructor
616 				MyOtherClass.base.apply(this, arguments);
617 			}
618 			glow.util.extend(MyOtherClass, MyClass, {
619 				setProp: function(newProp) {
620 					this.prop = newProp;
621 				}
622 			});
623 
624 			var test = new MyOtherClass("hello");
625 			test.showProp(); // alerts "hello"
626 			test.setProp("world");
627 			test.showProp(); // alerts "world"
628 	*/
629 	util.extend = function(sub, base, additionalProperties) {
630 		/*!debug*/
631 			if (arguments.length < 2) {
632 				glow.debug.warn('[wrong count] glow.util.extend expects at least 2 arguments, not '+arguments.length+'.');
633 			}
634 			if (typeof sub != 'function') {
635 				glow.debug.error('[wrong type] glow.util.extend expects argument "sub" to be of type function, not ' + typeof sub + '.');
636 			}
637 			if (typeof base != 'function') {
638 				glow.debug.error('[wrong type] glow.util.extend expects argument "base" to be of type function, not ' + typeof base + '.');
639 			}
640 		/*gubed!*/
641 		var f = function () {}, p;
642 		f.prototype = base.prototype;
643 		p = new f();
644 		sub.prototype = p;
645 		p.constructor = sub;
646 		sub.base = base;
647 		if (additionalProperties) {
648 			util.apply(sub.prototype, additionalProperties);
649 		}
650 	};
651 	
652 	/**
653 		@name glow.util.escapeRegex
654 		@function
655 		@description Escape special regex chars from a string
656 
657 		@param {string} str String to escape
658 		
659 		@returns {string} Escaped string
660 		
661 		@example
662 			var str = glow.util.escapeRegex('[Hello. Is this escaped?]');
663 			// Outputs:
664 			// \[Hello\. Is this escaped\?\]
665 	*/
666 	util.escapeRegex = function(str) {
667 		/*!debug*/
668 			if (arguments.length !== 1) {
669 				glow.debug.warn('[wrong count] glow.util.escapeRegex expects 1 argument, not '+arguments.length+'.');
670 			}
671 		/*gubed!*/
672 		return String(str).replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&');
673 	};
674 	
675 	/**
676 		@name glow.util.encodeUrl
677 		@function
678 		@description Encodes an object for use as a query string.
679 		
680 			Returns a string representing the object suitable for use 
681 			as a query string, with all values suitably escaped.
682 			It does not include the initial question mark. Where the 
683 			input field was an array, the key is repeated in the output.
684 		
685 		@param {Object} object The object to be encoded.
686 		
687 			This must be a hash whose values can only be primitives or 
688 			arrays of primitives.
689 		
690 		@returns {String}
691 		
692 		@example
693 			var getRef = glow.util.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]});
694 			// will return "foo=Foo&bar=Bar%201&bar=Bar2"
695 	*/
696 	util.encodeUrl = function (object) {
697 		var type = _getType(object),
698 			paramsList = [],
699 			listLength = 0;
700 		
701 		/*!debug*/
702 			if (typeof object !== 'object') {
703 				throw new Error('glow.util.encodeUrl: cannot encode item');
704 			}
705 		/*gubed!*/
706 		
707 		for (var key in object) {
708 			type = _getType( object[key] );
709 
710 			/*!debug*/
711 				if (type !== 'Array' || type !== 'string') {
712 					glow.debug.warn('[wrong type] glow.util.encodeUrl expected Array or String value for "' + key + '", not ' + type + '.');
713 				}
714 			/*gubed!*/
715 			if (type === 'Array') {
716 				for(var i = 0, l = object[key].length; i < l; i++) {
717 					/*!debug*/
718 						if (_getType(object[key])[i] !== 'string') {
719 							glow.debug.warn('[wrong type] glow.util.encodeUrl expected string value for "' + key + '" value at index ' + i + ', not ' + _getType(object[key])[i] + '.');
720 						}
721 					/*gubed!*/
722 					paramsList[listLength++] = key + '=' + encodeURIComponent(object[key][i]);
723 				}
724 			}
725 			else { // assume string
726 				paramsList[listLength++] = key + '=' + encodeURIComponent(object[key]);
727 			}
728 		}
729 
730 		return paramsList.join('&');
731 	};
732 	
733 	/**
734 		@name glow.util.decodeUrl
735 		@function
736 		@description Decodes a query string into an object.
737 		
738 			Returns an object representing the data given by the query 
739 			string, with all values suitably unescaped. All keys in the 
740 			query string are keys of the object. Repeated keys result 
741 			in an array.
742 		
743 		@param {String} string The query string to be decoded.
744 		
745 			It should not include the initial question mark.
746 		
747 		@returns {Object}
748 		
749 		@example
750 			var getRef = glow.util.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2");
751 			// will return the object {foo: "Foo", bar: ["Bar 1", "Bar2"]}
752 	*/
753 	util.decodeUrl = function(text) {
754 		/*!debug*/
755 			if (arguments.length !== 1) {
756 				glow.debug.warn('[wrong count] glow.util.decodeUrl expects 1 argument, not '+arguments.length+'.');
757 			}
758 			if (typeof text !== 'string') {
759 				glow.debug.warn('[wrong type] glow.util.decodeUrl expects argument "text" to be of type string, not ' + typeof text + '.');
760 			}
761 		/*gubed!*/
762 		
763 		var result = {},
764 			keyValues = text.split(/[&;]/),
765 			thisPair,
766 			key,
767 			value;
768 
769 		for(var i = 0, leni = keyValues.length; i < leni; i++) {
770 			thisPair = keyValues[i].split('=');
771 			
772 			if (thisPair.length < 2) {
773 				key = keyValues[i];
774 				value = '';
775 			}
776 			else {
777 				key   = '' + decodeURIComponent(thisPair[0]);
778 				value = '' + decodeURIComponent(thisPair[1]);
779 			}
780 			
781 			// will be either: undefined, string or [object Array]
782 			switch (typeof result[key]) {
783 				case 'string':
784 					result[key] = [result[key], value];
785 					break;
786 				case 'undefined':
787 					result[key] = value;
788 					break;
789 				default:
790 					result[key].push(value);
791 			}
792 		}
793 
794 		return result;
795 	};
796 	
797 	/**
798 			@name glow.util.encodeJson
799 			@function
800 			@description Encodes an object into a string JSON representation.
801 			
802 				Returns a string representing the object as JSON.
803 			
804 			@param {Object} object The object to be encoded.
805 			 
806 				This can be arbitrarily nested, but must not contain 
807 				functions or cyclical structures.
808 			
809 			@returns {Object}
810 			
811 			@example
812 				var myObj = {foo: "Foo", bar: ["Bar 1", "Bar2"]};
813 				var getRef = glow.util.encodeJson(myObj);
814 				// will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}'
815 			*/
816 	util.encodeJson = function(object, options){
817 		function _encode(object, options)
818 				{
819 					if(_getType(object) == TYPES.ARRAY) {
820 						var type = JSON.ARRAY;
821 					} else {
822 						var type = JSON.HASH;
823 					}
824 
825 					var serial = [type.START];
826 					var len = 1;
827 					var dataType;
828 					var notFirst = false;
829 
830 					for(var key in object) {
831 						dataType = _getType(object[key]);
832 
833 						if(dataType != TYPES.UNDEFINED) { /* ignore undefined data */
834 							if(notFirst) {
835 								serial[len++] = JSON.DATA_SEPARATOR;
836 							}
837 							notFirst = true;
838 
839 							if(type.SHOW_KEYS) {
840 								serial[len++] = JSON.KEY_DELIMITER;
841 								serial[len++] = key;
842 								serial[len++] = JSON.KEY_DELIMITER;
843 								serial[len++] = JSON.KEY_SEPARATOR;
844 							}
845 
846 							switch(dataType) {
847 								case TYPES.FUNCTION:
848 									throw new Error("glow.data.encodeJson: cannot encode item");
849 									break;
850 								case TYPES.STRING:
851 								default:
852 									serial[len++] = JSON.STRING_DELIMITER;
853 									serial[len++] = glow.lang.replace(object[key], SLASHES.TEST, _replaceSlashes);
854 									serial[len++] = JSON.STRING_DELIMITER;
855 									break;
856 								case TYPES.NUMBER:
857 								case TYPES.BOOLEAN:
858 									serial[len++] = object[key];
859 									break;
860 								case TYPES.OBJECT:
861 								case TYPES.ARRAY:
862 									serial[len++] = _encode(object[key], options);
863 									break;
864 								case TYPES.NULL:
865 									serial[len++] = TYPES.NULL;
866 									break;
867 							}
868 						}
869 					}
870 					serial[len++] = type.END;
871 
872 					return serial.join(TEXT.EMPTY);
873 				}
874 
875 				options = options || {};
876 				var type = _getType(object);
877 
878 				if((type == TYPES.OBJECT) || (type == TYPES.ARRAY)) {
879 					return _encode(object, options);
880 				} else {
881 					throw new Error("glow.data.encodeJson: cannot encode item");
882 				}
883 		
884 	};
885 	/**
886 			@name glow.util.decodeJson
887 			@function
888 			@description Decodes a string JSON representation into an object.
889 				
890 				Returns a JavaScript object that mirrors the data given.
891 			
892 			@param {String} string The string to be decoded.
893 				Must be valid JSON. 
894 			
895 			@param {Object} opts
896 			
897 					Zero or more of the following as properties of an object:
898 					@param {Boolean} [opts.safeMode=false] Whether the string should only be decoded if it is  deemed "safe". 
899 					The json.org regular expression checks are used. 
900 			
901 			@returns {Object}
902 			
903 			@example
904 				var getRef = glow.util.decodeJson('{foo: "Foo", bar: ["Bar 1", "Bar2"]}');
905 				// will return {foo: "Foo", bar: ["Bar 1", "Bar2"]}
906 			
907 				var getRef = glow.util.decodeJson('foobar', {safeMode: true});
908 				// will throw an error
909 			*/
910 	util.decodeJson = function(text, options){
911 		if(_getType(text) != TYPES.STRING) {
912 					throw new Error("glow.data.decodeJson: cannot decode item");
913 				}
914 
915 				options = options || {};
916 				options.safeMode = options.safeMode || false;
917 
918 				var canEval = true;
919 
920 				if(options.safeMode) {
921 					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)));
922 				}
923 
924 				if(canEval) {
925 					try {
926 						return eval(TEXT.OPEN + text + TEXT.CLOSE);
927 					}
928 					catch(e) {/* continue to error */}
929 				}
930 
931 				throw new Error("glow.data.decodeJson: cannot decode item");
932 	};
933 	/**
934 		@name glow.util.trim
935 		@function
936 		@description Removes leading and trailing whitespace from a string
937 	
938 		@param {string} str String to trim
939 	
940 		@returns {String}
941 	
942 			String without leading and trailing whitespace
943 	
944 		@example
945 			glow.util.trim("  Hello World  "); // "Hello World"
946 	*/
947 	util.trim = function(str) {
948 		//this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript
949 		return str.trim ? str.trim() : str.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
950 	};
951 	
952 	/**
953 		@name glow.util.interpolate
954 		@function
955 		@description Replaces placeholders in a string with data from an object
956 		
957 		@param {String} template The string containing {placeholders}
958 		@param {Object} data Object containing the data to be merged in to the template
959 			<p>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.</p>
960 			<p>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.</p>
961 		@param {Object} opts Options object
962 			@param {String} [opts.delimiter="{}"] Alternative label delimiter(s) for the template
963 				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.
964 			@param {Boolean} [opts.escapeHtml=false] Escape any special html characters found in the data object
965 				Use this to safely inject data from the user into an HTML template. The glow.dom module
966 				must be present for this feature to work (an error will be thrown otherwise).
967 		
968 		@returns {String}
969 		
970 		@example
971 			var data = {
972 				name: "Domino",
973 				colours: ["black", "white"],
974 				family: {
975 					mum: "Spot",
976 					dad: "Patch",
977 					siblings: []
978 				}
979 			};
980 			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.";
981 			var result = glow.util.interpolate(template, data);
982 			// 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."
983 		
984 		@example
985 			var data = {
986 				name: 'Haxors!!1 <script src="hackhackhack.js"></script>'
987 			}
988 			var template = '<p>Hello, my name is {name}</p>';
989 			var result = glow.util.interpolate(template, data, {
990 				escapeHtml: true
991 			});
992 			// result == '<p>Hello, my name is Haxors!!1 <script src="hackhackhack.js"></script></p>'
993 	*/
994 	util.interpolate = function(template, data, opts) {
995 		var placeHolderRx,
996 			leftDelimiter,
997 			rightDelimiter,
998 			// div used for html escaping
999 			div;
1000 	
1001 		opts = opts || {};
1002 		
1003 		// make sure the dom module is around
1004 		if (opts.escapeHtml) {
1005 			div = glow('<div></div>');
1006 		}
1007 	
1008 		if (opts.delimiter == undefined) {
1009 			placeHolderRx = /\{[^{}]+\}/g;
1010 		}
1011 		else {
1012 			leftDelimiter = opts.delimiter.substr(0, 1).replace(regexEscape, "\\$1");
1013 			rightDelimiter = opts.delimiter.substr(1, 1).replace(regexEscape, "\\$1") || leftDelimiter;
1014 			placeHolderRx = new RegExp(leftDelimiter + "[^" + leftDelimiter + rightDelimiter + "]+" + rightDelimiter, "g");
1015 		}
1016 	
1017 		return template.replace(placeHolderRx, function (placeholder) {
1018 			var key = placeholder.slice(1, -1),
1019 				keyParts = key.split("."),
1020 				val,
1021 				i = 0,
1022 				len = keyParts.length;
1023 			
1024 			if (key in data) {
1025 				// need to be backwards compatible with "flattened" data.
1026 				val = data[key]; 
1027 			}
1028 			else {
1029 				// look up the chain
1030 				val = data;
1031 				for (; i < len; i++) {
1032 					if (keyParts[i] in val) {
1033 						val = val[ keyParts[i] ];
1034 					}
1035 					else {
1036 						return placeholder;
1037 					}
1038 				}
1039 			}
1040 			
1041 			if (opts.escapeHtml) {
1042 				val = div.text(val).html();
1043 			}
1044 			return val;
1045 		});
1046 	};
1047 	
1048 	/**
1049 		@example
1050 			glow.util.cookie(key); // get value for key
1051 			glow.util.cookie({key: val, key2: val2}, opts); // set all keys, vals
1052 			glow.util.cookie(key, val, opts); // set key, val
1053 			glow.util.cookie(); // get all keys, vals
1054 			
1055 			// use value of undefined
1056 	*/
1057 	util.cookie = function(key, value, opts) {
1058 		/*!debug*/
1059 			if (arguments.length > 3) {
1060 				glow.debug.warn('[wrong count] glow.util.cookie expects 3 or less arguments, not '+arguments.length+'.');
1061 			}
1062 			
1063 			if (arguments.length === 1 && _getType(key) !== 'string' && _getType(key) !== 'object') {
1064 				glow.debug.warn('[wrong type] glow.util.cookie expects argument "key" to be of type string or object, not ' + _getType(key) + '.');
1065 			}
1066 			
1067 			if (
1068 				arguments.length === 2
1069 				&&
1070 				(
1071 					! (_getType(key) === 'string' && _getType(value) === 'string')
1072 					||
1073 					! (_getType(key) === 'object' && _getType(value) === 'object')
1074 				)
1075 			) {
1076 				glow.debug.warn('[wrong type] glow.util.cookie expects arguments to be (key, val) or (keyVals, opts).');
1077 			}
1078 			
1079 			if (arguments.length === 3 && _getType(key) !== 'string' && _getType(value) !== 'string' && _getType(opts) !== 'object') {
1080 				glow.debug.warn('[wrong type] glow.util.cookie expects argument "key" and "value" to be strings and "options" to be an object.');
1081 			}
1082 			
1083 			if (opts && opts.debug && (typeof opts.expires !== 'number' || !opts.expires.toUTCString)) {
1084 				glow.debug.warn('[wrong type] glow.util.cookie expects opts.expires to be a number or a Date.');
1085 			}
1086 		/*gubed!*/
1087 		
1088 		
1089 		var date = '',
1090 			expires = '',
1091 			path = '',
1092 			domain = '',
1093 			secure = '',
1094 			keyValues,
1095 			thisPair,
1096 			key,
1097 			val,
1098 			cookieValues;
1099 		
1100 		if (opts) {
1101 			if (opts.expires) {
1102 				if (typeof opts.expires === 'number') {
1103 					date = new Date();
1104 					date.setTime(date.getTime() + (opts.expires * 24 * 60 * 60 * 1000)); // opts.expires days
1105 				}
1106 				else { // is already a Date
1107 					date = opts.expires;
1108 				}
1109 				expires = '; expires=' + date.toUTCString();
1110 			}
1111 		   
1112 			path = opts.path ? '; path=' + (opts.path) : '';
1113 			domain = opts.domain ? '; domain=' + (opts.domain) : '';
1114 			secure = opts.secure ? '; secure' : '';
1115 		}
1116 		else {
1117 			opts = {};
1118 		}
1119 		
1120 		if (typeof key === 'string' && typeof value === 'string') { // a single setter
1121 			document.cookie = key + '=' + encodeURIComponent(value) + expires + path + domain + secure;
1122 		}
1123 		else if (typeof key === 'object') { // an all setter
1124 			for (var p in key) {
1125 				document.cookie = p + '=' + encodeURIComponent(key[p]) + expires + path + domain + secure;
1126 			}
1127 		}
1128 		else { // a getter
1129 			cookieValues = {};
1130 			if (document.cookie && document.cookie != '') {
1131 				keyValues = document.cookie.split(/; ?/);
1132 				for (var i = 0, leni = keyValues.length; i < leni; i++) {
1133 					thisPair = keyValues[i].split('=');
1134 					
1135 					cookieValues[thisPair[0]] = decodeURIComponent(thisPair[1]);
1136 				}
1137 			}
1138 			
1139 			if (typeof key === 'string') { // a single getter
1140 				return cookieValues[key];
1141 			}
1142 			else if (typeof key === 'undefined') { // an all getter
1143 				return cookieValues;
1144 			}
1145 		}
1146 	};
1147 	
1148 	util.removeCookie = function(key) {
1149 		util.cookie(key, '', {expires: -1});
1150 	};
1151 	
1152 	// export
1153 	glow.util = util;
1154 });
1155 Glow.provide(function(glow) {
1156 	/**
1157 	@name glow.events
1158 	@namespace
1159 	@description Handling custom events
1160 	*/
1161 	var events = {};
1162 		
1163 	/* storage variables */
1164 	
1165 	var eventListeners = {}, // eventName: [ [callback, thisVal], ... ] 
1166 		eventId = 1,
1167 		objIdCounter = 1, 
1168 		eventKey = '__eventId' + glow.UID; 
1169 
1170 	
1171 	/**
1172 	@name glow.events.addListeners
1173 	@function
1174 	@param {Object[]} attachTo Array of objects to add listeners to.
1175 	@param {string} name Name of the event to listen for.
1176 		Event names are case sensitive.
1177 	@param {function} callback Function to call when the event is fired.
1178 		The callback will be passed a single event object. The type of this
1179 		object depends on the event (see documentation for the event
1180 		you're listening to).
1181 	@param {Object} [thisVal] Value of 'this' within the callback.
1182 		By default, this is the object being listened to.
1183 	@see glow.events.Target#fire
1184 	@description Convenience method to add listeners to many objects at once.
1185 		If you want to add a listener to a single object, use its
1186 		'on' method.
1187 	*/
1188 	events.addListeners = function (attachTo, name, callback, thisVal) {
1189 		var listenerIds = [],
1190 			objIdent,
1191 			listener,
1192 			eventsOnObject,
1193 			currentListeners;
1194 	
1195 		//attach the event for each element, return an array of listener ids
1196 		var i = attachTo.length;
1197 		while (i--) {
1198 			objIdent = attachTo[i][eventKey];
1199 			if (!objIdent){
1200 				objIdent = attachTo[i][eventKey] = objIdCounter++;
1201 			}
1202 					
1203 			listener = [ callback, thisVal ];
1204 			eventsOnObject = eventListeners[objIdent];
1205 			if(!eventsOnObject){
1206 				eventsOnObject = eventListeners[objIdent] = {};
1207 			}
1208 					
1209 			currentListeners = eventsOnObject[name];
1210 			if(!currentListeners){
1211 				eventsOnObject[name] = [listener];
1212 			}
1213 			else{
1214 				currentListeners[currentListeners.length] = listener;
1215 			}							
1216 		}
1217 		return events;
1218 	};
1219 	
1220 	events._getPrivateEventKey = function(node) {
1221 		if (!node[eventKey]) {
1222 			node[eventKey] = objIdCounter++;
1223 		}
1224 		
1225 		return node[eventKey];
1226 	}
1227 	
1228 	/**
1229 	@name glow.events.fire
1230 	@function
1231 	@param {Object[]} items      Array of objects to add listeners to
1232 	@param {string}   eventName  Name of the event to fire
1233 	@param {glow.events.Event|Object} [event] Event object to pass into listeners.
1234        You can provide a simple object of key-value pairs which will
1235        be added as properties on the glow.events.Event instance.
1236 		
1237 	@description Convenience method to fire events on multiple items at once.
1238 		If you want to fire events on a single object, use its
1239 		'fire' method.
1240 	*/
1241 		
1242 	events.fire = function (items, eventName, event) {
1243 		if (! event) {
1244 			event = new events.Event();
1245 		}
1246 		else if ( event.constructor === Object ) {
1247 			event = new events.Event( event )
1248 		}
1249 		
1250 		// for loop, because order matters!
1251 		for(var i = 0, len = items.length; i < len; i++) { 
1252 			callListeners(items[i], eventName, event);
1253 		}
1254 			
1255 		return event;
1256 	};
1257 
1258 	
1259 	/**
1260 	 @name glow.events-callListeners
1261 	 @private
1262 	*/
1263 	function callListeners(item, eventName, event, thisVal) {
1264 		var objIdent = item[eventKey],
1265 			listenersForEvent,
1266 			returnedVal;			
1267 		
1268 		// set the attachedTo value for this event
1269 		event.attachedTo = event.attachedTo || item;
1270 		
1271 		if (!objIdent || !eventListeners[objIdent]) {
1272 			return event;
1273 		}
1274 		
1275 		listenersForEvent = eventListeners[objIdent][eventName];
1276 			
1277 		if (!listenersForEvent) {
1278 			return event;
1279 		}
1280 		// Slice to make sure we get a unique copy.
1281 		listenersForEvent = listenersForEvent.slice(0);
1282 		for (var i = 0, len = listenersForEvent.length; i < len; i++){
1283 			returnedVal = listenersForEvent[i][0].call((listenersForEvent[i][1] || thisVal || item), event);
1284 			if (returnedVal === false){
1285 				event.preventDefault();
1286 			}
1287 		}
1288 			
1289 		return event;
1290 	}
1291 	events._callListeners = callListeners;
1292 		
1293 		
1294 	/**
1295 	@name glow.events.removeAllListeners
1296 	@function
1297 	@param {Object[]} items Items to remove events from		    
1298 	@description Removes all listeners attached to a given object.
1299 		This removes not only listeners you added, but listeners others
1300 		added too. For this reason it should only be used as part of a cleanup
1301 		operation on objects that are about to be destroyed.
1302 	*/
1303 	
1304 	events.removeAllListeners = function (items) {
1305 		var objIdent,
1306 		i = items.length;		
1307 		
1308 		while(i--){
1309 			
1310 			objIdent = items[i][eventKey];
1311 			
1312 			if (!objIdent) {
1313 				return false;
1314 			}
1315 			else {
1316 				delete eventListeners[objIdent];
1317 			}
1318 		}
1319 
1320 		return true;
1321 	};
1322 
1323 
1324 	/**
1325 	@name glow.events.removeListeners
1326 	@function
1327 	@param {Object[]} items Items to remove events from.
1328 	@param {string} eventName Name of the event to remove.
1329 	@param {function} callback A reference to the original callback used when the listener was added.
1330 	@decription Removes listeners for an event.
1331 	*/
1332 	events.removeListeners = function (item, eventName, callback) { /* TODO: items! */
1333 		var objIdent,
1334 			listenersForEvent,
1335 			i = item.length;
1336 		
1337 	
1338 		while(i--){
1339 			
1340 			objIdent = item[i][eventKey];
1341 				
1342 			if(!objIdent || !eventListeners[objIdent]){
1343 				return events;
1344 			}
1345 			
1346 		
1347 			listenersForEvent = eventListeners[objIdent][eventName];
1348 			if(!listenersForEvent){
1349 				return events;
1350 			}
1351 			
1352 			// for loop, because order matters
1353 			for(var j = 0, lenj = listenersForEvent.length; j < lenj; j++){						
1354 				if (listenersForEvent[j][0] === callback){
1355 					listenersForEvent.splice(j, 1);
1356 					break;
1357 				}
1358 		
1359 			}
1360 		}
1361 		
1362 		return events;			
1363 	};
1364 	
1365 	/**
1366 		Copies the events from one NodeList to another
1367 		@private
1368 		@name glow.events._copyEvents
1369 		@see glow.NodeList#clone
1370 		@function
1371 	*/
1372 	events._copyDomEvents = function(from, to){
1373 		var objIdent,
1374 			i = from.length,
1375 			j, jLen,
1376 			listeners,
1377 			listenersForEvent,
1378 			eventName,
1379 			toItem;
1380 		
1381 		// loop over elements
1382 		while(i--){
1383 			objIdent = from[i][eventKey];
1384 			listeners = eventListeners[objIdent];
1385 			
1386 			if (objIdent){
1387 				toItem = to.slice(i, i+1);
1388 				
1389 				// loop over event names (of listeners attached)
1390 				for ( eventName in listeners ) {
1391 					listenersForEvent = listeners[eventName];
1392 					
1393 					// loop over individual listeners and add them to the 'to' item
1394 					// loop forward to preserve event order
1395 					for (j = 0, jLen = listenersForEvent.length; j < jLen; j++) {
1396 						// add listener
1397 						toItem.on( eventName, listenersForEvent[j][0], listenersForEvent[j][1] );
1398 					}
1399 				}
1400 			}
1401 		}
1402 	}
1403 	/**
1404 	@name glow.events._getListeners
1405 	@private
1406 	@function
1407 	@param {Object} item Item to find events for
1408 	@decription Returns a list of listeners attached for the given item.
1409 	
1410 	*/	
1411 	events._getListeners = function(item){
1412 		var objIdent = item[eventKey];
1413 			
1414 		if (!objIdent) {
1415 			return {};
1416 		}
1417 		else {
1418 			// todo: need to return listeners in a sensible format
1419 			return eventListeners[objIdent];
1420 		}
1421 			
1422 	};
1423 	
1424 	///**
1425 	//@name glow.events.hasListener
1426 	//@function
1427 	//@param {Object[]} item  Item to find events for
1428 	//@param {String}   eventName  Name of the event to match
1429 	//@decription Returns true if an event is found for the item supplied
1430 	//
1431 	//*/
1432 	//
1433 	//glow.events.hasListener = function (item, eventName) {
1434 	//	var objIdent,
1435 	//		listenersForEvent;
1436 	//		
1437 	//	for (var i = 0, len = item.length; i < len; i++) {	
1438 	//		objIdent = item[i][eventKey];
1439 	//			
1440 	//		if (!objIdent || !eventListeners[objIdent]) {
1441 	//			return false;
1442 	//		}
1443 	//				
1444 	//		listenersForEvent = eventListeners[objIdent][eventName];
1445 	//		if (!listenersForEvent) {
1446 	//			return false;
1447 	//		}
1448 	//		else {
1449 	//			return true;							
1450 	//		}					
1451 	//	}
1452 	//	
1453 	//	return false;			
1454 	//};
1455 	
1456 	/**
1457 	@name glow.events.Target
1458 	@class
1459 	@description An object that can have event listeners and fire events.
1460 		Extend this class to make your own objects have 'on' and 'fire'
1461 		methods.
1462 		
1463 	@example
1464 		// Ball is our constructor
1465 		function Ball() {
1466 			// ...
1467 		}
1468 		       
1469 		// make Ball inherit from Target
1470 		glow.util.extend(Ball, glow.events.Target, {
1471 			// additional methods for Ball here, eg:
1472 			bowl: function() {
1473 				// ...
1474 			}
1475 		});
1476 		       
1477 		// now instances of Ball can receive event listeners
1478 		var myBall = new Ball();
1479 		myBall.on('bounce', function() {
1480 			alert('BOING!');
1481 		});
1482 		       
1483 		// and events can be fired from Ball instances
1484 		myBall.fire('bounce');
1485 	*/
1486 	
1487 	events.Target = function () {
1488 			
1489 	};
1490 	var targetProto = events.Target.prototype;
1491 		
1492 	/**
1493 	@name glow.events.Target.extend
1494 	@function
1495 	@param {Object} obj Object to add Target instance methods to.
1496 		
1497 	@description Convenience method to add Target instance methods onto an object.
1498 		If you want to add Target methods to a class, extend glow.events.Target instead.
1499 		       
1500 	@example
1501 		var myApplication = {};
1502 		       
1503 		glow.events.Target.extend(myApplication);
1504 		       
1505 		// now myApplication can fire events...
1506 		myApplication.fire('load');
1507 		       
1508 		// and other objects can listen for those events
1509 		myApplication.on('load', function(e) {
1510 			alert('App loaded');
1511 		});
1512 	*/
1513 	
1514 	events.Target.extend = function (obj) {
1515 		glow.util.apply( obj, glow.events.Target.prototype );
1516 	};
1517 		
1518 	/**
1519 	@name glow.events.Target#on
1520 	@function
1521 	@param {string} eventName Name of the event to listen for.
1522 	@param {function} callback Function to call when the event fires.
1523 		The callback is passed a single event object. The type of this
1524 		object depends on the event (see documentation for the event
1525 		you're listening to).
1526 	@param {Object} [thisVal] Value of 'this' within the callback.
1527 		By default, this is the object being listened to.
1528 		
1529 	@description Listen for an event
1530 		
1531 	@returns this
1532 		
1533 	@example
1534 		myObj.on('show', function() {
1535 		    // do stuff
1536 		});
1537 	*/
1538 	
1539 	targetProto.on = function(eventName, callback, thisVal) {
1540 		glow.events.addListeners([this], eventName, callback, thisVal);
1541 		return this;
1542 	}
1543 		
1544 	/**
1545 	@name glow.events.Target#detach
1546 	@function
1547 	@param {string} eventName Name of the event to remove.
1548 	@param {function} callback Callback to detach.
1549 	@param {Object} [thisVal] Value of 'this' within the callback.
1550 		By default, this is the object being listened to.
1551 	@description Remove an event listener.
1552 		
1553 	@returns this Target object
1554 		
1555 	@example
1556 		function showListener() {
1557 		    // ...
1558 		}
1559 		       
1560 		// add listener
1561 		myObj.on('show', showListener);
1562 		       
1563 		// remove listener
1564 		myObj.detach('show', showListener);
1565 		       
1566 	@example
1567 		// note the following WILL NOT WORK
1568 		       
1569 		// add listener
1570 		myObj.on('show', function() {
1571 		    alert('hi');
1572 		});
1573 		       
1574 		// remove listener
1575 		myObj.detach('show', function() {
1576 			alert('hi');
1577 		});
1578 		       
1579 		// this is because both callbacks are different function instances
1580 	
1581 	*/
1582 		
1583 	targetProto.detach = function(eventName, callback) {
1584 		glow.events.removeListeners(this, eventName, callback);
1585 		return this;
1586 	}
1587 		
1588 	/**
1589 	@name glow.events.Target#fire
1590 	@function
1591 	@param {string} eventName Name of the event to fire.
1592 	@param {glow.events.Event|Object} [event] Event object to pass into listeners.
1593 		    You can provide a simple object of key-value pairs which will
1594 		    be added as properties of a glow.events.Event instance.
1595 		
1596 	@description Fire an event.
1597 		
1598 	@returns glow.events.Event
1599 		
1600 	@example
1601 		myObj.fire('show');
1602 		       
1603 	@example
1604 		// adding properties to the event object
1605 		myBall.fire('bounce', {
1606 		    velocity: 30
1607 		});
1608 	       
1609 	@example
1610 		// BallBounceEvent extends glow.events.Event but has extra methods
1611 		myBall.fire( 'bounce', new BallBounceEvent(myBall) );
1612 	*/
1613 	
1614 	targetProto.fire = function(eventName, event) {
1615 		if (! event) {
1616 			event = new events.Event();
1617 		}
1618 		else if ( event.constructor === Object ) {
1619 			event = new events.Event( event )
1620 		}
1621 		
1622 		return callListeners(this, eventName, event);
1623 	}
1624 		
1625 	/**
1626 	@name glow.events.Event
1627 	@class
1628 	@param {Object} [properties] Properties to add to the Event instance.
1629 		Each key-value pair in the object will be added to the Event as
1630 		properties.
1631 	       
1632 	@description Describes an event that occurred.
1633 		You don't need to create instances of this class if you're simply
1634 		listening to events. One will be provided as the first argument
1635 		in your callback.
1636 	       
1637 	@example
1638 		// creating a simple event object
1639 		var event = new glow.events.Event({
1640 			velocity: 50,
1641 			direction: 180
1642 		});
1643 		       
1644 		// 'velocity' and 'direction' are simple made-up properties
1645 		// you may want to add to your event object
1646 		       
1647 	@example
1648 		// inheriting from glow.events.Event to make a more
1649 		// specialised event object
1650 		       
1651 		function RocketEvent() {
1652 			// ...
1653 		}
1654 		       
1655 		// inherit from glow.events.Event
1656 		glow.util.extend(RocketEvent, glow.events.Event, {
1657 			getVector: function() {
1658 				return // ...
1659 			}
1660 		});
1661 		       
1662 		// firing the event
1663 		rocketInstance.fire( 'landingGearDown', new RocketEvent() );
1664 		       
1665 		// how a user would listen to the event
1666 		rocketInstance.on('landingGearDown', function(rocketEvent) {
1667 			var vector = rocketEvent.getVector();
1668 		});
1669 	*/
1670 		
1671 	events.Event = function(obj) {			
1672 		if (obj) {
1673 			glow.util.apply(this, obj);
1674 		}
1675 	};
1676 	var eventProto = events.Event.prototype;
1677 	/**
1678 	@name glow.events.Event#attachedTo
1679 	@type {Object}
1680 	@description The object the listener was attached or delegated to.
1681 	*/
1682 
1683 		
1684 	/**
1685 	@name glow.events.Event#preventDefault
1686 	@function
1687 	@description Prevent the default action of the event.
1688 		Eg, if the click event on a link is cancelled, the link
1689 		is not followed.
1690 		       
1691 		Returning false from an event listener has the same effect
1692 		as calling this function.
1693 		       
1694 		For custom events, it's down to whatever fired the event
1695 		to decide what to do in this case. See {@link glow.events.Event#defaultPrevented defaultPrevented}
1696 		       
1697 	@example
1698 		myLinks.on('click', function(event) {
1699 			event.preventDefault();
1700 		});
1701 		       
1702 		// same as...
1703 		       
1704 		myLinks.on('click', function(event) {
1705 			return false;
1706 		});
1707 	*/
1708 	
1709 	eventProto.preventDefault = function () {	
1710 		this._defaultPrevented = true;		
1711 	};
1712 
1713 		
1714 	/**
1715 	@name glow.events.Event#defaultPrevented
1716 	@function
1717 	@description Has the default been prevented for this event?
1718 		This should be used by whatever fires the event to determine if it should
1719 		carry out of the default action.
1720 		
1721 	@returns {Boolean} Returns true if {@link glow.events.Event#preventDefault preventDefault} has been called for this event.
1722 		
1723 	@example
1724 		// fire the 'show' event
1725 		// read if the default action has been prevented
1726 		if ( overlayInstance.fire('show').defaultPrevented() == false ) {
1727 		    // go ahead and show
1728 		}
1729 	*/
1730 	
1731 	eventProto.defaultPrevented = function () {
1732 		return this._defaultPrevented;
1733 	};
1734 
1735 	
1736 	/* Export */
1737 	glow.events = events;
1738 });
1739 Glow.provide(function(glow) {
1740 	var document = window.document,
1741 		undef = undefined,
1742 		domEventHandlers = [], // like: domEventHandlers[uniqueId][eventName].count, domEventHandlers[uniqueId][eventName].callback
1743 		// shortcuts to aim compression
1744 		events = glow.events,
1745 		_callListeners = events._callListeners,
1746 		_getPrivateEventKey = events._getPrivateEventKey,
1747 		// used for feature detection
1748 		supportsActivateDeactivate = (document.createElement('div').onactivate !== undefined);
1749 	
1750 	/** 
1751 		@name glow.events.DomEvent
1752 		@constructor
1753 		@extends glow.events.Event
1754 		
1755 		@param {Event|string} nativeEvent A native browser event read properties from, or the name of a native event.
1756 		
1757 		@param {Object} [properties] Properties to add to the Event instance.
1758 		   Each key-value pair in the object will be added to the Event as
1759 		   properties
1760 		
1761 		@description Describes a DOM event that occurred
1762 		   You don't need to create instances of this class if you're simply
1763 		   listening to events. One will be provided as the first argument
1764 		   in your callback.
1765 	*/
1766 	function DomEvent(e, properties) {
1767 		/** 
1768 			@name glow.events.DomEvent#nativeEvent
1769 			@type {Event | MouseEvent | UIEvent}
1770 			@description The native event object provided by the browser.
1771 		 */
1772 		this.nativeEvent = e;
1773 		
1774 		/** 
1775 			@name glow.events.DomEvent#type
1776 			@type {string}
1777 			@description The native type of the event, like 'click' or 'keydown'.
1778 		 */
1779 		this.type = e.type;
1780 		
1781 		/** 
1782 			@name glow.events.DomEvent#source
1783 			@type {HTMLElement}
1784 			@description The element that the event originated from.
1785 				For example, you could attach a listener to an <ol> element to listen for
1786 				clicks. If the user clicked on an <li> the source property would be the
1787 				<li> element, and {@link glow.DomEvent#attachedTo attachedTo} would be
1788 				the <ol>.
1789 		*/
1790 		this.source = e.target || e.srcElement || undefined;
1791 		
1792 		// some rare cases crop up in Firefox where the source is a text node
1793 		if (this.source && this.source.nodeType === 3) {
1794 			this.source = this.source.parentNode;
1795 		}
1796 		
1797 		/** 
1798 			@name glow.events.DomEvent#related
1799 			@type {HTMLElement}
1800 			@description A related HTMLElement
1801 				For mouseover / mouseenter events, this will refer to the previous element
1802 				the mouse was over.
1803 				
1804 				For mouseout / mouseleave events, this will refer to the element the mouse
1805 				is now over.
1806 		*/
1807 		this.related = e.relatedTarget || (this.type == 'mouseover' ? e.fromElement : e.toElement);
1808 		
1809 		/** 
1810 			@name glow.events.DomEvent#shiftKey
1811 			@type {boolean | undefined}
1812 			@description Was the shift key pressed during the event?
1813 		*/
1814 		this.shiftKey = (e.shiftKey === undef)? undef : !!e.shiftKey;
1815 		
1816 		/** 
1817 			@name glow.events.DomEvent#altKey
1818 			@type {boolean | undefined}
1819 			@description Was the alt key pressed during the event?
1820 		*/
1821 		this.altKey = (e.altKey === undef)? undef : !!e.altKey;
1822 		
1823 		/** 
1824 			@name glow.events.DomEvent#ctrlKey
1825 			@type {boolean | undefined}
1826 			@description Was the ctrl key pressed during the event?
1827 		*/
1828 		this.ctrlKey = (e.ctrlKey === undef)? undef : !!e.ctrlKey;
1829 		
1830 		/**
1831 			@name glow.events.DomEvent#button
1832 			@type {number | undefined}
1833 			@description A number representing which button was pressed.
1834 				0 for the left button, 1 for the middle button or 2 for the right button.
1835 		*/
1836 		this.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button;
1837 		
1838 		/** 
1839 			@name glow.events.DomEvent#mouseTop
1840 			@type {number}
1841 			@description The vertical position of the mouse pointer in the page in pixels.
1842 		*/
1843 		/** 
1844 			@name glow.events.DomEvent#mouseLeft
1845 			@type {number}
1846 			@description The horizontal position of the mouse pointer in the page in pixels.
1847 		*/
1848 		if (e.pageX !== undef || e.pageY !== undef) {
1849 			this.mouseTop = e.pageY;
1850 			this.mouseLeft = e.pageX;
1851 		}
1852 		else if (e.clientX !== undef || e.clientY !== undef) {
1853 			this.mouseTop = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
1854 			this.mouseLeft = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
1855 		}
1856 		
1857 		/** 
1858 			@name glow.events.DomEvent#wheelData
1859 			@type {number}
1860 			@description The number of clicks the mouse wheel moved.
1861 				Up values are positive, down values are negative.
1862 		*/
1863 		if (this.type == 'mousewheel') {
1864 			// this works in latest opera, but have read that it needs to be switched in direction
1865 			// if there was an opera bug, I can't find which version it was fixed in
1866 			this.wheelDelta =
1867 				e.wheelDelta ? e.wheelDelta / 120 :
1868 				e.detail ? - e.detail / 3 :
1869 				0;
1870 		}
1871 		
1872 		for (var key in properties) {
1873 			this[key] = properties[key];
1874 		}
1875 	}
1876 	
1877 	glow.util.extend(DomEvent, events.Event, {
1878 		// no docs for this as it simply adds DOM behaviour to glow.events.Event#preventDefault
1879 		preventDefault: function() {
1880 			var nativeEvent = this.nativeEvent;
1881 			if (nativeEvent) {
1882 				nativeEvent.preventDefault && nativeEvent.preventDefault();
1883 				nativeEvent.returnValue = false;
1884 			}
1885 			// call the original method
1886 			events.Event.prototype.preventDefault.call(this);
1887 			return this;
1888 		},
1889 		/**
1890 			@name glow.events.DomEvent#stopPropagation
1891 			@function
1892 			@description Stop an event bubbling any further.
1893 				For instance, if you had 2 click listeners, one on a link and
1894 				one on a parent element, if you stopped the event propogating in the
1895 				link listener, the event will never be fired on the parent element.
1896 			
1897 			@returns this
1898 		*/
1899 		stopPropagation: function() {
1900 			var nativeEvent = this.nativeEvent;
1901 			
1902 			if (nativeEvent) {
1903 				// the ie way
1904 				nativeEvent.cancelBubble = true;
1905 				// the proper way
1906 				nativeEvent.stopPropagation && nativeEvent.stopPropagation();
1907 			}
1908 			return this;
1909 		}
1910 	});
1911 	
1912 	/**
1913 		Add listener for an event fired by the browser.
1914 		@private
1915 		@name glow.events._addDomEventListener
1916 		@see glow.NodeList#on
1917 		@function
1918 	*/
1919 	events._addDomEventListener = function(nodeList, eventName) {
1920 		var i = nodeList.length, // TODO: should we check that this nodeList is deduped?
1921 			attachTo,
1922 			id;
1923 	
1924 		while (i--) {
1925 			attachTo = nodeList[i];
1926 
1927 			id = _getPrivateEventKey(attachTo);
1928 
1929 			// check if there is already a handler for this kind of event attached
1930 			// to this node (which will run all associated callbacks in Glow)
1931 			if (!domEventHandlers[id]) { domEventHandlers[id] = {}; }
1932 
1933 			if (domEventHandlers[id][eventName] && domEventHandlers[id][eventName].count > 0) { // already have handler in place
1934 				domEventHandlers[id][eventName].count++;
1935 				continue;
1936 			}
1937 
1938 			// no bridge in place yet
1939 			domEventHandlers[id][eventName] = { count:1 };
1940 			
1941 			// attach a handler to tell Glow to run all the associated callbacks
1942 			(function(attachTo) {
1943 				var handler = domHandle(attachTo, eventName);
1944 				
1945 				if (attachTo.addEventListener) { // like DOM2 browsers	
1946 					attachTo.addEventListener(handler.domName, handler, (eventName === 'focus' || eventName === 'blur')); // run in bubbling phase except for focus and blur, see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
1947 				}
1948 				else if (attachTo.attachEvent) { // like IE
1949 					attachTo.attachEvent('on' + handler.domName, handler);
1950 				}
1951 				// older browsers?
1952 				
1953 				domEventHandlers[id][eventName].callback = handler;
1954 			})(attachTo);
1955 		}
1956 	}
1957 	
1958 	function domHandle(attachTo, eventName) {
1959 		var handler;
1960 		
1961 		if (eventName === 'mouseenter' || eventName === 'mouseleave') {
1962 			// mousenter and mouseleave handle their own delegation as its non-standard
1963 			handler = function(nativeEvent) {
1964 				var domEvent = new DomEvent(nativeEvent),
1965 					container,
1966 					selector,
1967 					elementsToTest = _getDelegateMatches(attachTo, eventName, domEvent);
1968 				
1969 				// add this element to the delegates
1970 				elementsToTest.push( [attachTo] );
1971 				
1972 				for (var i = 0, leni = elementsToTest.length; i < leni; i++) {
1973 					container = elementsToTest[i][0];
1974 					selector = elementsToTest[i][1];
1975 					
1976 					if (!new glow.NodeList(container).contains(domEvent.related)) {
1977 						_callListeners(attachTo, selector ? eventName + '/' + selector : eventName, domEvent, container); // fire() returns result of callback
1978 					}
1979 				}
1980 				return !domEvent.defaultPrevented();
1981 			};
1982 			
1983 			handler.domName = (eventName === 'mouseenter') ? 'mouseover' : 'mouseout';
1984 		}
1985 		// handle blur & focus differently for IE so it bubbles
1986 		else if ( supportsActivateDeactivate && (eventName === 'focus' || eventName === 'blur') ) {
1987 			// activate and deactivate are like focus and blur but bubble
1988 			// However, <body> and <html> also activate so we need to fix that
1989 			handler = function(nativeEvent) {
1990 				var nodeName = nativeEvent.srcElement.nodeName;
1991 				if (nodeName !== 'HTML' && nodeName !== 'BODY') {
1992 					_callDomListeners( attachTo, eventName, new DomEvent(nativeEvent) );
1993 				}
1994 			}
1995 			
1996 			handler.domName = (eventName === 'focus') ? 'activate' : 'deactivate';
1997 		}
1998 		else {
1999 			handler = function(nativeEvent) {
2000 				var domEvent = new DomEvent(nativeEvent);
2001 				_callDomListeners(attachTo, eventName, domEvent); // fire() returns result of callback
2002 				
2003 				return !domEvent.defaultPrevented();
2004 			};
2005 			
2006 			handler.domName = eventName;
2007 		}
2008 		
2009 		return handler;
2010 	}
2011 	
2012 	
2013 	/**
2014 		Remove listener for an event fired by the browser.
2015 		@private
2016 		@name glow.events._removeDomEventListener
2017 		@see glow.NodeList#detach
2018 		@function
2019 	*/
2020 	events._removeDomEventListener = function(nodeList, eventName) {
2021 		var i = nodeList.length,
2022 			attachTo,
2023 			id,
2024 			bridge,
2025 			handler;
2026 			
2027 		while (i--) {
2028 			attachTo = nodeList[i];
2029 			
2030 			// skip if there is no bridge for this kind of event attached
2031 			id = _getPrivateEventKey(attachTo);
2032 			if (!domEventHandlers[id] || !domEventHandlers[id][eventName]) { continue; }
2033 
2034 			bridge = domEventHandlers[id][eventName];
2035 			
2036 			// one less listener associated with this event
2037 			if ( !--bridge.count ) {
2038 				// no more listeners associated with this event
2039 				handler = bridge.callback;
2040 				
2041 				if (attachTo.removeEventListener) { // like DOM2 browsers	
2042 					attachTo.removeEventListener(handler.domName, handler, (eventName === 'focus' || eventName === 'blur')); // run in bubbling phase except for focus and blur, see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html
2043 				}
2044 				else if (attachTo.detachEvent) { // like IE
2045 					attachTo.detachEvent('on' + handler.domName, handler);
2046 				}
2047 				domEventHandlers[id][eventName] = undefined;
2048 			}
2049 		}
2050 	}
2051 
2052 // see: http://developer.yahoo.com/yui/3/event/#eventsimulation
2053 // see: http://developer.yahoo.com/yui/docs/YAHOO.util.UserAction.html
2054 // 	function simulateDomEvent(nodeList, domEvent) {
2055 // 		var i = nodeList.length,
2056 // 			eventName = domEvent.type,
2057 // 			nativeEvent,
2058 // 			node,
2059 // 			fire;
2060 // 		
2061 // 		if (document.createEvent) {
2062 // 			var nativeEvent = document.createEvent('MouseEvent'); // see: 
2063 // 			nativeEvent.initEvent(eventName, true, true);
2064 // 			
2065 // 			fire = function(el) {
2066 // 				return !el.dispatchEvent(nativeEvent);
2067 // 			}
2068 // 		}
2069 // 		else {
2070 // 			fire = function(el) {
2071 // 				var nativeEvent = document.createEventObject(); 
2072 // 				return el.fireEvent('on'+eventName, nativeEvent);
2073 // 			}
2074 // 		}
2075 // 		
2076 // 		while (i--) {
2077 // 			node = nodeList[i];
2078 // 			if (node.nodeType !== 1) { continue; }
2079 // 			fire(node);
2080 // 		}
2081 // 	}
2082 
2083 	/*
2084 		The following is a proposal for dealing with event delegation without
2085 		multiple bridges. This allows us to have only one listener per element per event
2086 		therefore only one search for delegates per event.
2087 	*/
2088 	
2089 	// structure:
2090 	// delegates[eventId][eventName][selector] = number of delegates listening for that selector (for that event on that element)
2091 	var delegates = {}
2092 	
2093 	/**
2094 		@name glow.events._registerDelegate
2095 		@private
2096 		@function
2097 		@description Register a delegated event
2098 			This allows selectors for a given element & eventName to be retrieved later
2099 		
2100 		@param {glow.NodeList} nodeList Elements to register
2101 		@param {string} eventName
2102 		@param {string} selector Selector to match for the delegate
2103 	*/
2104 	events._registerDelegate = function(nodeList, eventName, selector) {
2105 		var id,
2106 			i = nodeList.length,
2107 			delegatesForEvent;
2108 		
2109 		while (i--) {
2110 			id = _getPrivateEventKey( nodeList[i] );
2111 			delegates[id] = delegates[id] || {};
2112 			delegatesForEvent = delegates[id][eventName] = delegates[id][eventName] || {};
2113 			// increment the count or set it to 1
2114 			delegatesForEvent[selector] = delegatesForEvent[selector] + 1 || 1;
2115 		}
2116 	};
2117 	
2118 	/**
2119 		@name glow.events._unregisterDelegate
2120 		@private
2121 		@function
2122 		@description Unregister a delegated event
2123 		
2124 		@param {glow.NodeList} nodeList Elements to unregister
2125 		@param {string} eventName
2126 		@param {string} selector Selector to match for the delegate
2127 	*/
2128 	events._unregisterDelegate = function(nodeList, eventName, selector) {
2129 		var id,
2130 			selectorCounts,
2131 			i = nodeList.length;
2132 		
2133 		while (i--) {
2134 			id = _getPrivateEventKey( nodeList[i] );
2135 			if ( !delegates[id] || !( selectorCounts = delegates[id][eventName] ) ) { continue; }
2136 			
2137 			// either decrement the count or delete the entry
2138 			if ( selectorCounts[selector] && --selectorCounts[selector] === 0 ) {
2139 				delete selectorCounts[selector];
2140 			}
2141 		}
2142 	};
2143 	
2144 	/**
2145 		@name glow.events._getDelegateMatches
2146 		@private
2147 		@function
2148 		@description Get the elements which qualify for a delegated event
2149 		
2150 		@param {HTMLElement} element Element the listener is attached to
2151 		@param {string} eventName
2152 		@param {glow.events.DomEvent} event DOM event for the original event
2153 			The events source will be used as a place to start searching
2154 			
2155 		@returns {Array[]} An array of arrays like [matchedNode, selectorMatched]
2156 	*/
2157 	var _getDelegateMatches = events._getDelegateMatches = function(element, eventName, event) {
2158 		var id = _getPrivateEventKey(element),
2159 			selectorCounts,
2160 			selector,
2161 			node,
2162 			r = [];
2163 		
2164 		// get delegated listeners
2165 		if ( delegates[id] && ( selectorCounts = delegates[id][eventName] ) ) {
2166 			for (selector in selectorCounts) {
2167 				node = event.source;
2168 				// if the source matches the selector
2169 				while (node && node !== element) {
2170 					if (glow._sizzle.matches( selector, [node] ).length) {
2171 						r.push( [node, selector] );
2172 						break;
2173 					}
2174 					
2175 					node = node.parentNode;
2176 				}
2177 			}
2178 		}
2179 		return r;
2180 	}
2181 	
2182 	/**
2183 		@name glow.events._callDomListeners
2184 		@private
2185 		@function
2186 		@description Call delegated listeners and normal listeners for an event
2187 			Events that don't bubble (like mouseenter and mouseleave) need
2188 			to handle their own delegation rather than use this.
2189 		
2190 		@param {HTMLElement} element Element to fire event on
2191 		@param {string} eventName
2192 		@param {glow.events.DomEvent} event
2193 			
2194 		@returns {glow.events.DomEvent} Original event passed in
2195 	*/
2196 	var _callDomListeners = events._callDomListeners = function(element, eventName, event) {
2197 		var delegateMatches = _getDelegateMatches(element, eventName, event);
2198 		
2199 		// call delegated listeners
2200 		for (var i = 0, leni = delegateMatches.length; i < leni; i++) {
2201 			event.attachedTo = delegateMatches[i][0];
2202 			_callListeners( element, eventName + '/' + delegateMatches[i][1], event, delegateMatches[i][0] );
2203 		}
2204 		
2205 		// call non-delegated listeners
2206 		event.attachedTo = element;
2207 		_callListeners(element, eventName, event);
2208 		
2209 		return event;
2210 	}
2211 	
2212 	// export
2213 	events.DomEvent = DomEvent;
2214 });
2215 Glow.provide(function(glow) {
2216 	var document = window.document,
2217 		undefined,
2218         keyboardEventProto,
2219 		env = glow.env,
2220 		// the keyCode for the last keydown (returned to undefined on keyup)
2221 		activeKey,
2222 		// the charCode for the last keypress (returned to undefined on keyup & keydown)
2223 		activeChar,
2224 		DomEvent = glow.events.DomEvent,
2225 		_callDomListeners = glow.events._callDomListeners,
2226 		_getPrivateEventKey = glow.events._getPrivateEventKey,
2227 		// object of event names & listeners, eg:
2228 		// {
2229 		//    eventId: [
2230 		//        2, // the number of glow listeners added for this node
2231 		//        keydownListener,
2232 		//        keypressListener,
2233 		//        keyupListener
2234 		//    ]
2235 		// }
2236 		// This lets us remove these DOM listeners from the node when the glow listeners reaches zero
2237 		eventKeysRegistered = {}; 
2238 	
2239 	/** 
2240 		@name glow.events.KeyboardEvent
2241 		@constructor
2242 		@extends glow.events.DomEvent
2243 		
2244 		@description Describes a keyboard event.
2245 		   You don't need to create instances of this class if you're simply
2246 		   listening to events. One will be provided as the first argument
2247 		   in your callback.
2248 		   
2249 		@param {Event} nativeEvent A native browser event read properties from.
2250 		
2251 		@param {Object} [properties] Properties to add to the Event instance.
2252 		   Each key-value pair in the object will be added to the Event as
2253 		   properties.
2254 	*/
2255 	function KeyboardEvent(nativeEvent) {
2256 		if (activeKey) {
2257 			this.key = keyCodeToId(activeKey);
2258 		}
2259 		if (activeChar) {
2260 			this.keyChar = String.fromCharCode(activeChar);
2261 		}
2262 		DomEvent.call(this, nativeEvent);
2263 	}
2264     
2265     glow.util.extend(KeyboardEvent, DomEvent, {
2266         /** 
2267             @name glow.events.KeyboardEvent#key
2268             @type {string}
2269             @description The key pressed
2270 				This is a string representing the key pressed.
2271 				
2272 				Alphanumeric keys are represented by 0-9 and a-z (always lowercase). Other safe cross-browser values are:
2273 				
2274 				<ul>
2275 					<li>backspace</li>
2276 					<li>tab</li>
2277 					<li>return</li>
2278 					<li>shift</li>
2279 					<li>alt</li>
2280 					<li>escape</li>
2281 					<li>space</li>
2282 					<li>pageup</li>
2283 					<li>pagedown</li>
2284 					<li>end</li>
2285 					<li>home</li>
2286 					<li>left</li>
2287 					<li>up</li>
2288 					<li>right</li>
2289 					<li>down</li>
2290 					<li>insert</li>
2291 					<li>delete</li>
2292 					<li>;</li>
2293 					<li>=</li>
2294 					<li>-</li>
2295 					<li>f1</li>
2296 					<li>f2</li>
2297 					<li>f3</li>
2298 					<li>f4</li>
2299 					<li>f5</li>
2300 					<li>f6</li>
2301 					<li>f7</li>
2302 					<li>f8</li>
2303 					<li>f9</li>
2304 					<li>f10</li>
2305 					<li>f11</li>
2306 					<li>f12</li>
2307 					<li>numlock</li>
2308 					<li>scrolllock</li>
2309 					<li>pause</li>
2310 					<li>,</li>
2311 					<li>.</li>
2312 					<li>/</li>
2313 					<li>[</li>
2314 					<li>\</li>
2315 					<li>]</li>
2316 				</ul>
2317 				
2318 				Some keys may trigger actions in your browser and operating system, some
2319 				are not cancelable.
2320                 
2321             @example
2322 				glow(document).on('keypress', function(event) {
2323 					switch (event.key) {
2324 						case 'up':
2325 							// do stuff
2326 							break;
2327 						case 'down':
2328 							// do stuff
2329 							break;
2330 					}
2331 				});
2332         */
2333         key: '',
2334         /** 
2335             @name glow.events.KeyboardEvent#keyChar
2336             @type {string}
2337             @description The character entered.
2338                 This is only available during 'keypress' events.
2339                 
2340                 If the user presses shift and 1, event.key will be "1", but event.keyChar
2341                 will be "!".
2342                 
2343             @example
2344 				// only allow numbers to be entered into the ageInput field
2345 				glow('#ageInput').on('keypress', function(event) {
2346 					// Convert keyChar to a number and see if we get
2347 					// a valid number back
2348 					return !isNaN( Number(event.keyChar) );
2349 				});
2350         */
2351         keyChar: ''
2352     });
2353 	
2354 	/** 
2355 		@private
2356 		@description Add a listener onto a DOM element
2357 		
2358 		@param {HTMLElement} elm
2359 		@param {string} name Event name, without 'on' at the start
2360 		@param {function} callback Callback for the event
2361 	*/
2362 	function addListener(elm, name, callback) {
2363 		if (elm.addEventListener) { // like DOM2 browsers	
2364 			elm.addEventListener(name, callback, false);
2365 		}
2366 		else if (elm.attachEvent) { // like IE
2367 			elm.attachEvent('on' + name, callback);
2368 		}
2369 	}
2370 	
2371 	/** 
2372 		@private
2373 		@description Removes a listener onto a DOM element
2374 		
2375 		@param {HTMLElement} elm
2376 		@param {string} name Event name, without 'on' at the start
2377 		@param {function} callback Callback for the event
2378 	*/
2379 	function removeListener(elm, name, callback) {
2380 		if (elm.removeEventListener) { // like DOM2 browsers	
2381 			elm.removeEventListener(name, callback, false);
2382 		}
2383 		else if (elm.detachEvent) { // like IE
2384 			elm.detachEvent('on' + name, callback);
2385 		}
2386 	}
2387 	
2388 	/** 
2389 		@private
2390 		@description Do we expect the browser to fire a keypress after a given keydown?
2391 			Also fills in activeChar for webkit.
2392 
2393 		@param {number} keyCode The keyCode from a keydown listener.
2394 		@param {boolean} defaultPrevented Was the keydown prevented?
2395 	*/
2396 	function expectKeypress(keyCode, defaultPrevented) {
2397 		var keyName;
2398 		
2399 		// for browsers that fire keypress for the majority of keys
2400 		if (env.gecko || env.opera || env.webkit < 525) {
2401 			return !noKeyPress[keyCode];
2402 		}
2403 		
2404 		// for browsers that only fire keypress for printable chars
2405 		keyName = keyCodeToId(keyCode);
2406 		
2407 		// is this a printable char?
2408 		if (keyName.length === 1 || keyName === 'tab' || keyName === 'space') {
2409 			// webkit doesn't fire keypress if the keydown has been prevented
2410 			// take a good guess at the active char for webkit
2411 			activeChar = ( keyNameToChar[keyName] || keyName ).charCodeAt(0);
2412 			return !(env.webkit && defaultPrevented);
2413 		}
2414 		return false;
2415 	}
2416 	
2417 	/** 
2418 		@private
2419 		@description Add the key listeners for firing glow's normalised key events.
2420 		
2421 		@param {HTMLElement} attachTo Element to attach listeners to.
2422 		
2423 		@returns {Object[]} An entry for eventKeysRegistered.
2424 	*/
2425 	function addDomKeyListeners(attachTo) {
2426 		var keydownHandler,
2427 			keypressHandler,
2428 			keyupHandler,
2429 			// Even though the user may only be interested in one key event,
2430 			// we need all 3 listeners to normalise any of them.
2431 			// Hash of which keys are down, keyed by keyCode
2432 			// Like: {123: true, 124: false}
2433 			keysDown = {};
2434 		
2435 		keydownHandler = function(nativeEvent) {
2436 			var keyCode = nativeEvent.keyCode,
2437 				preventDefault,
2438 				preventDefaultKeyPress;
2439 			
2440 			// some browsers repeat this event while a key is held down, we don't want to do that
2441 			if ( !keysDown[keyCode] ) {
2442 				activeKey = keyCode;
2443 				activeChar = undefined;
2444 				preventDefault = _callDomListeners( attachTo, 'keydown', new KeyboardEvent(nativeEvent) ).defaultPrevented();
2445 				keysDown[keyCode] = true;
2446 			}
2447 			// we want to fire a keyPress event here if the browser isn't going to fire one itself
2448 			if ( !expectKeypress(keyCode, preventDefault) ) {
2449 				preventDefaultKeyPress = _callDomListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented();
2450 			}
2451 			// return false if either the keydown or fake keypress event was cancelled
2452 			return !(preventDefault || preventDefaultKeyPress);
2453 		};
2454 		
2455 		keypressHandler = function(nativeEvent) {
2456 			var keyName, preventDefault;
2457 			// some browsers store the charCode in .charCode, some in .keyCode
2458 			activeChar = nativeEvent.charCode || nativeEvent.keyCode;
2459 			keyName = keyCodeToId(activeKey);
2460 			
2461 			// some browsers fire this event for non-printable chars, look at the previous keydown and see if we're expecting a printable char
2462 			if ( keyName.length > 1 && keyName !== 'tab' && keyName !== 'space' ) {
2463 				// non-printable chars usually have an ID length greater than 1
2464 				activeChar = undefined;
2465 			}
2466 
2467 			preventDefault = _callDomListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented();
2468 			return !preventDefault;
2469 		};
2470 		
2471 		keyupHandler = function(nativeEvent) {
2472 			var keyCode = nativeEvent.keyCode,
2473 				preventDefault;
2474 			
2475 			// set the active key so KeyboardEvent picks it up
2476 			activeKey = keyCode;
2477 			activeChar = undefined;
2478 			preventDefault = _callDomListeners( attachTo, 'keyup', new KeyboardEvent(nativeEvent) ).defaultPrevented();
2479 			keysDown[keyCode] = false;
2480 			activeKey = undefined;
2481 			return !preventDefault;
2482 		};
2483 		
2484 		// add listeners to the dom
2485 		addListener(attachTo, 'keydown',  keydownHandler);
2486 		addListener(attachTo, 'keypress', keypressHandler);
2487 		addListener(attachTo, 'keyup',    keyupHandler);
2488 		
2489 		return [1, keydownHandler, keypressHandler, keyupHandler];
2490 	}
2491 	
2492 	/**
2493 		@name glow.events._addKeyListener
2494 		@private
2495 		@function
2496 		@description Add DOM listeners for key events fired by the browser.
2497 			Won't add more than one.
2498 		
2499 		@param {glow.NodeList} nodeList Elements to add listeners to.
2500 		
2501 		@see glow.NodeList#on
2502 	*/
2503 	glow.events._addKeyListener = function(nodeList) {
2504 		var i = nodeList.length,
2505 			attachTo,
2506 			eventKey;
2507 	
2508 		while (i--) {
2509 			attachTo = nodeList[i];
2510 
2511 			// get the ID for this event
2512 			eventKey = _getPrivateEventKey(attachTo);
2513 			
2514 			// if we've already attached DOM listeners for this, don't add them again
2515 			if ( eventKeysRegistered[eventKey] ) {
2516 				// increment the number of things listening to this
2517 				// This lets us remove these DOM listeners from the node when
2518 				// the glow listeners reaches zero
2519 				eventKeysRegistered[eventKey][0]++;
2520 				continue;
2521 			}
2522 			else {
2523 				eventKeysRegistered[eventKey] = addDomKeyListeners(attachTo);
2524 			}
2525 		}
2526 	}
2527 	
2528 	/**
2529 		@name glow.events._removeKeyListener
2530 		@private
2531 		@function
2532 		@description Remove DOM listeners for key events fired by the browser
2533 			Avoids removing DOM listeners until all Glow listeners have been removed
2534 		
2535 		@param {glow.NodeList} nodeList Elements to remove listeners from
2536 		
2537 		@see glow.NodeList#detach
2538 	*/
2539 	glow.events._removeKeyListener = function(nodeList) {
2540 		var i = nodeList.length,
2541 			attachTo,
2542 			eventKey,
2543 			eventRegistry;
2544 		
2545 		while (i--) {
2546 			attachTo = nodeList[i];
2547 			
2548 			// get the ID for this event
2549 			eventKey = _getPrivateEventKey(attachTo);
2550 			eventRegistry = eventKeysRegistered[eventKey];
2551 			// exist if there are no key events registered for this node
2552 			if ( !eventRegistry ) {
2553 				continue;
2554 			}
2555 			if ( --eventRegistry[0] === 0 ) {
2556 				// our glow listener count is zero, we have no need for the dom listeners anymore
2557 				removeListener( attachTo, 'keydown',   eventRegistry[1] );
2558 				removeListener( attachTo, 'keypress',  eventRegistry[2] );
2559 				removeListener( attachTo, 'keyup',     eventRegistry[3] );
2560 				eventKeysRegistered[eventKey] = undefined;
2561 			}
2562 		}
2563 	}
2564 	/**
2565 		@private
2566 		@function
2567 		@description convert a keyCode to a string name for that key
2568 		
2569 		@param {number} keyCode
2570 		
2571 		@returns {string} ID for that key. Is a letter a-z, number 0-9, or id from 'keyIds'
2572 	*/
2573 	function keyCodeToId(keyCode) {
2574 		// key codes for 0-9 A-Z are the same as their char codes
2575 		if ( (keyCode >= keyCodeA && keyCode <= keyCodeZ) || (keyCode >= keyCode0 && keyCode <= keyCode9) ) {
2576 			return String.fromCharCode(keyCode).toLowerCase();
2577 		}
2578 		return keyIds[keyCode] || 'unknown' + keyCode;
2579 	}
2580 	
2581 	// keyCode to key name translation
2582 	var keyCodeA = 65,
2583 		keyCodeZ = 90,
2584 		keyCode0 = 48,
2585 		keyCode9 = 57,
2586 		// key codes for non-alphanumeric keys
2587 		keyIds = {
2588 			8: 'backspace',
2589 			9: 'tab',
2590 			13: 'return',
2591 			16: 'shift',
2592 			17: 'control',
2593 			18: 'alt',
2594 			19: 'pause',
2595 			27: 'escape',
2596 			32: 'space',
2597 			33: 'pageup',
2598 			34: 'pagedown',
2599 			35: 'end',
2600 			36: 'home',
2601 			37: 'left',
2602 			38: 'up',
2603 			39: 'right',
2604 			40: 'down',
2605 			//44: 'printscreen', // Only fires keyup in firefox, IE. Doesn't fire in webkit, opera.
2606 			45: 'insert',
2607 			46: 'delete',
2608 			59: ';',
2609 			61: '=',
2610 			//91: 'meta',
2611 			//93: 'menu', // no keycode in opera, doesn't fire in Chrome
2612 			
2613 			// these are number pad numbers, but Opera doesn't distinguish them from normal number keys so we normalise on that
2614 				96: '0', 
2615 				97: '1',
2616 				98: '2',
2617 				99: '3',
2618 				100: '4',
2619 				101: '5',
2620 				102: '6',
2621 				103: '7',
2622 				104: '8',
2623 				105: '9',
2624 				//106: '*', // opera fires 2 keypress events
2625 				//107: '+', // opera fires 2 keypress events
2626 				109: '-', // opera sees - as insert, but firefox 3.0 see the normal - key the same as the numpad one
2627 				//110: '.', // opera sees this as n
2628 				111: '/',
2629 			// end of numpad
2630 			
2631 			112: 'f1',
2632 			113: 'f2',
2633 			114: 'f3',
2634 			115: 'f4',
2635 			116: 'f5',
2636 			117: 'f6',
2637 			118: 'f7',
2638 			119: 'f8',
2639 			120: 'f9',
2640 			121: 'f10',
2641 			122: 'f11',
2642 			123: 'f12',
2643 			144: 'numlock',
2644 			145: 'scrolllock',
2645 			188: ',',
2646 			189: '-',
2647 			190: '.',
2648 			191: '/',
2649 			192: "'",
2650 			219: '[',
2651 			220: '\\',
2652 			221: ']',
2653 			222: '#', // opera sees # key as 3. Pah.
2654 			223: '`',
2655 			//224: 'meta', // same as [ in opera
2656 			226: '\\' // this key appears on a US layout in webkit windows
2657 		},
2658 		// converting key names to chars, for key names greater than 1 char
2659 		keyNameToChar = {
2660 			space: ' ',
2661 			tab: '\t'
2662 		}
2663 		noKeyPress = {};
2664 	
2665 	// corrections for particular browsers :(
2666 	if (env.gecko) {
2667 		keyIds[107] = '=';
2668 		
2669 		noKeyPress = {
2670 			16: 1,  // shift
2671 			17: 1,  // control
2672 			18: 1,  // alt
2673 			144: 1, // numlock
2674 			145: 1  // scrolllock
2675 		};
2676 	}
2677 	else if (env.opera) {
2678 		keyIds[42] = '*';
2679 		keyIds[43] = '+';
2680 		keyIds[47] = '/';
2681 		keyIds[222] = "'";
2682 		keyIds[192] = '`';
2683 		
2684 		noKeyPress = {
2685 			16: 1,  // shift
2686 			17: 1,  // control
2687 			18: 1   // alt
2688 		};
2689 	}
2690 	else if (env.webkit || env.ie) {
2691 		keyIds[186] = ';';
2692 		keyIds[187] = '=';
2693 	}
2694 	
2695 	// export
2696 	glow.events.KeyboardEvent = KeyboardEvent;
2697 });
2698 Glow.provide(function(glow) {
2699 	var NodeListProto, undefined,
2700 		// shortcuts to aid compression
2701 		document = window.document,
2702 		arraySlice = Array.prototype.slice,
2703 		arrayPush = Array.prototype.push;
2704 	
2705 	/**
2706 		@name glow.NodeList
2707 		@constructor
2708 		@description An array-like collection of DOM Nodes
2709 			It is recommended to create a NodeList using the shortcut function {@link glow}.
2710 			
2711 		@param {string | glow.NodeList | Node | Node[] | Window} contents Items to populate the NodeList with.
2712 			This parameter will be passed to {@link glow.NodeList#push}.
2713 			
2714 			Strings will be treated as CSS selectors unless they start with '<', in which
2715 			case they'll be treated as an HTML string.
2716 			
2717 		@example
2718 			// empty NodeList
2719 			var myNodeList = glow();
2720 
2721 		@example
2722 			// using glow to return a NodeList then chaining methods
2723 			glow('p').addClass('eg').append('<div>Hello!</div>');
2724 			
2725 		@example
2726 			// creating an element from a string
2727 			glow('<div>Hello!</div>').appendTo('body');
2728 		
2729 		@see <a href="http://wiki.github.com/jeresig/sizzle/">Supported CSS selectors</a>
2730 	*/
2731 	function NodeList(contents) {
2732 		// call push if we've been given stuff to add
2733 		contents && this.push(contents);
2734 	}
2735 	NodeListProto = NodeList.prototype;
2736 	
2737 	/**
2738 		@name glow.NodeList#length
2739 		@type Number
2740 		@description Number of nodes in the NodeList
2741 		@example
2742 			// get the number of paragraphs on the page
2743 			glow('p').length;
2744 	*/
2745 	NodeListProto.length = 0;
2746 	
2747 	/**
2748 		@name glow.NodeList._strToNodes
2749 		@private
2750 		@function
2751 		@description Converts a string to an array of nodes
2752 		
2753 		@param {string} str HTML string
2754 		
2755 		@returns {Node[]} Array of nodes (including text / comment nodes)
2756 	*/
2757 	NodeList._strToNodes = (function() {
2758 		var	tmpDiv = document.createElement('div'),
2759 			// these wraps are in the format [depth to children, opening html, closing html]
2760 			tableWrap = [1, '<table>', '</table>'],
2761 			emptyWrap = [0, '', ''],
2762 			// Easlier Webkits won't accept <link> & <style> elms to be the only child of an element,
2763 			// it steals them and hides them in the head for some reason. Using
2764 			// broken html fixes it for some reason
2765 			paddingWrap = glow.env.webkit < 526 ? [0, '', '</div>'] : [1, 'b<div>', '</div>'],
2766 			trWrap = [3, '<table><tbody><tr>', '</tr></tbody></table>'],
2767 			wraps = {
2768 				caption: tableWrap,
2769 				thead: tableWrap,
2770 				th: trWrap,
2771 				colgroup: tableWrap,
2772 				tbody: tableWrap,
2773 				tr: [2, '<table><tbody>', '</tbody></table>'],
2774 				td: trWrap,
2775 				tfoot: tableWrap,
2776 				option: [1, '<select multiple="multiple">', '</select>'],
2777 				legend: [1, '<fieldset>', '</fieldset>'],
2778 				link: paddingWrap,
2779 				script: paddingWrap,
2780 				style: paddingWrap,
2781 				'!': paddingWrap
2782 			};
2783 
2784 		function strToNodes(str) {
2785 			var r = [],
2786 				tagName = ( /^<([\w!]+)/.exec(str) || [] )[1],
2787 				// This matches str content with potential elements that cannot
2788 				// be a child of <div>.  elmFilter declared at top of page.
2789 				wrap = wraps[tagName] || emptyWrap,
2790 				nodeDepth = wrap[0],
2791 				childElm = tmpDiv,
2792 				exceptTbody,
2793 				rLen = 0,
2794 				firstChild;
2795 
2796 			// Create the new element using the node tree contents available in filteredElm.
2797 			childElm.innerHTML = (wrap[1] + str + wrap[2]);
2798 
2799 			// Strip newElement down to just the required elements' parent
2800 			while(nodeDepth--) {
2801 				childElm = childElm.lastChild;
2802 			}
2803 
2804 			// pull nodes out of child
2805 			if (wrap === tableWrap && str.indexOf('<tbody') === -1) {
2806 				// IE7 (and earlier) sometimes gives us a <tbody> even though we didn't ask for one
2807 				while (firstChild = childElm.firstChild) {
2808 					if (firstChild.nodeName != 'TBODY') {
2809 						r[rLen++] = firstChild;
2810 					}
2811 					childElm.removeChild(firstChild);
2812 				}
2813 			}
2814 			else {
2815 				while (firstChild = childElm.firstChild) {
2816 					r[rLen++] = childElm.removeChild(firstChild);
2817 				}
2818 			}
2819 
2820 			return r;
2821 		}
2822 
2823 		return strToNodes;
2824 	})();
2825 	
2826 	// takes a collection and returns an array
2827 	var collectionToArray = function(collection) {
2828 		return arraySlice.call(collection, 0);
2829 	};
2830 	
2831 	try {
2832 		// look out for an IE bug
2833 		arraySlice.call( document.documentElement.childNodes, 0 );
2834 	}
2835 	catch(e) {
2836 		collectionToArray = function(collection) {
2837 			// We can't use this trick on IE collections that are com-based, like HTMLCollections
2838 			// Thankfully they don't have a constructor, so that's how we detect those
2839 			if (collection instanceof Object) {
2840 				return arraySlice.call(collection, 0);
2841 			}
2842 			var i   = collection.length,
2843 				arr = [];
2844 				
2845 			while (i--) {
2846 				arr[i] = collection[i];
2847 			}
2848 			return arr;
2849 		}
2850 	}
2851 	
2852 	/**
2853 		@name glow.NodeList#push
2854 		@function
2855 		@description Adds nodes to the NodeList
2856 		
2857 		@param {string | Node | Node[] | glow.NodeList} nodes Node(s) to add to the NodeList
2858 			Strings will be treated as CSS selectors or HTML strings.
2859 		
2860 		@returns {glow.NodeList}
2861 		
2862 		@example
2863 			myNodeList.push('<div>Foo</div>').push('h1');
2864 	*/
2865 	NodeListProto.push = function(nodes) {
2866 		/*!debug*/
2867 			if (arguments.length !== 1) {
2868 				glow.debug.warn('[wrong count] glow.NodeList#push expects 1 argument, not '+arguments.length+'.');
2869 			}
2870 		/*gubed!*/
2871 		
2872 		if (nodes) {
2873 			if (typeof nodes === 'string') {
2874 				// if the string begins <, treat it as html, otherwise it's a selector
2875 				if (nodes.charAt(0) === '<') {
2876 					nodes = NodeList._strToNodes(nodes);
2877 				}
2878 				else {
2879 					nodes = glow._sizzle(nodes)
2880 				}
2881 				arrayPush.apply(this, nodes);
2882 			}
2883 			
2884 			else if ( nodes.nodeType || nodes.window == nodes ) {
2885 				if (this.length) {
2886 					arrayPush.call(this, nodes);
2887 				}
2888 				else {
2889 					this[0] = nodes;
2890 					this.length = 1;
2891 				}
2892 			}
2893 			else if (nodes.length !== undefined) {
2894 				if (nodes.constructor != Array) {
2895 					// convert array-like objects into an array
2896 					nodes = collectionToArray(nodes);
2897 				}
2898 				arrayPush.apply(this, nodes);
2899 			}
2900 			/*!debug*/
2901 			else {
2902 				glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring unexpected argument type, failing silently');
2903 			}
2904 			/*gubed!*/
2905 		}
2906 		/*!debug*/
2907 		else {
2908 			glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring false argument type, failing silently');
2909 		}
2910 		/*gubed!*/
2911 		return this;
2912 	};
2913 	
2914 	/**
2915 		@name glow.NodeList#eq
2916 		@function
2917 		@description Compares this NodeList to another
2918 			Returns true if both NodeLists contain the same items in the same order
2919 		
2920 		@param {Node | Node[] | glow.NodeList} nodeList The NodeList to compare to.
2921 		
2922 		@returns {boolean}
2923 		
2924 		@see {@link glow.NodeList#is} for testing if a NodeList item matches a selector
2925 		
2926 		@example
2927 			// the following returns true
2928 			glow('#blah').eq( document.getElementById('blah') );
2929 	*/
2930 	NodeListProto.eq = function(nodeList) {
2931 		/*!debug*/
2932 			if (arguments.length !== 1) {
2933 				glow.debug.warn('[wrong count] glow.NodeList#eq expects 1 argument, not ' + arguments.length + '.');
2934 			}
2935 			if (typeof nodeList !== 'object') {
2936 				glow.debug.warn('[wrong type] glow.NodeList#eq expects object argument, not ' + typeof nodeList + '.');
2937 			}
2938 		/*gubed!*/
2939 		
2940 		var len = this.length,
2941 			i = len;
2942 		
2943 		// normalise param to NodeList
2944 		if ( !(nodeList instanceof NodeList) ) {
2945 			nodeList = new NodeList(nodeList);
2946 		}
2947 		
2948 		// quickly return false if lengths are different
2949 		if (len != nodeList.length) {
2950 			return false;
2951 		}
2952 		
2953 		// loop through and return false on inequality
2954 		while (i--) {
2955 			if (this[i] !== nodeList[i]) {
2956 				return false;
2957 			}
2958 		}
2959 		
2960 		return true;
2961 	};
2962 	
2963 	/**
2964 		@name glow.NodeList#slice
2965 		@function
2966 		@description Get a section of an NodeList
2967 			Operates in the same way as an Array's slice method
2968 		
2969 		@param {number} start Start index
2970 			If negative, it specifies a position measured from the end of the list
2971 		
2972 		@param {number} [end] End index
2973 			By default, this is the end of the list. A negative end specifies
2974 			a position measured from the end of the list.
2975 		
2976 		@returns {glow.NodeList} A new sliced NodeList
2977 		
2978 		@example
2979 		var myNodeList = glow("<div></div><p></p>");
2980 		myNodeList.slice(1, 2); // selects the paragraph
2981 		myNodeList.slice(-1); // same thing, selects the paragraph
2982 	*/
2983 	NodeListProto.slice = function(/*start, end*/) {
2984 		return new NodeList( arraySlice.apply(this, arguments) );
2985 	};
2986 	
2987 	/**
2988 		@name glow.NodeList#sort
2989 		@function
2990 		@description Sort the elements in the list.
2991 			Items will already be in document order if a CSS selector
2992 			was used to fetch them.
2993 		
2994 		@param {Function} [func] Function to determine sort order
2995 			This function will be passed 2 elements (elementA, elementB). The function
2996 			should return a number less than 0 to sort elementA lower than elementB
2997 			and greater than 0 to sort elementA higher than elementB.
2998 			
2999 			If no function is provided, elements will be sorted in document order.
3000 		
3001 		@returns {glow.NodeList} A new sorted NodeList
3002 		
3003 		@example
3004 			//get links in alphabetical (well, lexicographical) order
3005 			var links = glow("a").sort(function(elementA, elementB) {
3006 				return glow(elementA).text() < glow(elementB).text() ? -1 : 1;
3007 			})
3008 	*/
3009 	NodeListProto.sort = function(func) {
3010 		var items = collectionToArray(this),
3011 			sortedElms = func ? items.sort(func) : glow._sizzle.uniqueSort(items);
3012 		
3013 		return new NodeList(sortedElms);
3014 	};
3015 	
3016 	/**
3017 		@name glow.NodeList#item
3018 		@function
3019 		@description Get a single item from the list as an NodeList
3020 			Negative numbers can be used to get items from the end of the
3021 			list.
3022 		
3023 		@param {number} index The numeric index of the node to return.
3024 		
3025 		@returns {glow.NodeList} A new NodeList containing a single item
3026 		
3027 		@example
3028 			// get the html from the fourth element
3029 			myNodeList.item(3).html();
3030 			
3031 		@example
3032 			// add a class name to the last item
3033 			myNodeList.item(-1).addClass('last');
3034 	*/
3035 	NodeListProto.item = function(index) {
3036 		/*!debug*/
3037 			if ( arguments.length !== 1 ) {
3038 				glow.debug.warn('[wrong count] glow.NodeList#item expects 1 argument, got ' + arguments.length);
3039 			}
3040 		/*gubed!*/
3041 		// TODO: test which of these methods is faster (use the current one unless significantly slower)
3042 		return this.slice(index, (index + 1) || this.length);
3043 		// return new NodeList( index < 0 ? this[this.length + index] : this[index] );
3044 	};
3045 	
3046 	/**
3047 		@name glow.NodeList#each
3048 		@function
3049 		@description Calls a function for each node in the list.
3050 		
3051 		@param {Function} callback The function to call for each node.
3052 			The function will be passed 2 arguments, the index of the current item,
3053 			and the NodeList being iterated over.
3054 			
3055 			Inside the function 'this' refers to the Node.
3056 			
3057 			Returning false from this function stops further iterations
3058 		
3059 		@returns {glow.NodeList}
3060 		
3061 		@example
3062 			// add "link number: x" to each link, where x is the index of the link
3063 			glow("a").each(function(i, nodeList) {
3064 				glow(this).append(' link number: ' + i);
3065 			});
3066 		@example
3067 			// breaking out of an each loop
3068 			glow("a").each(function(i, nodeList) {
3069 				// do stuff
3070 				if ( glow(this).hasClass('whatever') ) {
3071 					// we don't want to process any more links
3072 					return false;
3073 				}
3074 			});
3075 	*/
3076 	NodeListProto.each = function(callback) {
3077 		/*!debug*/
3078 			if ( arguments.length !== 1 ) {
3079 				glow.debug.warn('[wrong count] glow.NodeList#each expects 1 argument, got ' + arguments.length);
3080 			}
3081 			if (typeof callback != 'function') {
3082 				glow.debug.warn('[wrong type] glow.NodeList#each expects "function", got ' + typeof callback);
3083 			}
3084 		/*gubed!*/
3085 		for (var i = 0, len = this.length; i<len; i++) {
3086 			if ( callback.call(this[i], i, this) === false ) {
3087 				break;
3088 			}
3089 		}
3090 		return this;
3091 	};
3092 	
3093 	/**
3094 		@name glow.NodeList#filter
3095 		@function
3096 		@description Filter the NodeList
3097 		 
3098 		@param {Function|string} test Filter test
3099 			If a string is provided it's treated as a CSS selector. Elements
3100 			which match the CSS selector are added to the new NodeList.
3101 			
3102 			If 'test' is a function, it will be called per node in the NodeList.
3103 			
3104 			The function is passed 2 arguments, the index of the current item,
3105 			and the ElementList being itterated over.
3106 			
3107 			Inside the function 'this' refers to the node.
3108 			Return true to add the element to the new NodeList.
3109 		 
3110 		@returns {glow.NodeList} A new NodeList containing the filtered nodes
3111 		 
3112 		@example
3113 			// return images with a width greater than 320
3114 			glow("img").filter(function () {
3115 				return glow(this).width() > 320;
3116 			});
3117 		
3118 		@example
3119 			// Get items that don't have an alt attribute
3120 			myElementList.filter(':not([alt])');
3121 	*/
3122 	NodeListProto.filter = function(test) {
3123 		/*!debug*/
3124 			if ( arguments.length !== 1 ) {
3125 				glow.debug.warn('[wrong count] glow.NodeList#filter expects 1 argument, got ' + arguments.length);
3126 			}
3127 			if ( !/^(function|string)$/.test(typeof test) ) {
3128 				glow.debug.warn('[wrong type] glow.NodeList#each expects function/string, got ' + typeof test);
3129 			}
3130 		/*gubed!*/
3131 		var r = [],
3132 			ri = 0;
3133 		
3134 		if (typeof test === 'string') {
3135 			r = glow._sizzle.matches(test, this);
3136 		}
3137 		else {	
3138 			for (var i = 0, len = this.length; i<len; i++) {
3139 				if ( test.call(this[i], i, this) ) {
3140 					r[ri++] = this[i];
3141 				}
3142 			}
3143 		}
3144 		
3145 		return new NodeList(r);
3146 	};
3147 
3148 	
3149 	/**
3150 		@name glow.NodeList#is
3151 		@function
3152 		@description Tests if the first element matches a CSS selector
3153 
3154 		@param {string} selector CSS selector
3155 		
3156 		@returns {boolean}
3157 		
3158 		@example
3159 			if ( myNodeList.is(':visible') ) {
3160 				// ...
3161 			}
3162 	*/
3163 	NodeListProto.is = function(selector) {
3164 		/*!debug*/
3165 			if ( arguments.length !== 1 ) {
3166 				glow.debug.warn('[wrong count] glow.NodeList#is expects 1 argument, got ' + arguments.length);
3167 			}
3168 			if ( typeof selector !== 'string' ) {
3169 				glow.debug.warn('[wrong type] glow.NodeList#is expects string, got ' + typeof selector);
3170 			}
3171 		/*gubed!*/
3172 		if ( !this[0] ) {
3173 			return false;
3174 		}
3175 		return !!glow._sizzle.matches( selector, [ this[0] ] ).length;
3176 	};
3177 	
3178 	// export
3179 	glow.NodeList = NodeList;
3180 });
3181 Glow.provide(function(glow) {
3182 	var undef
3183 		, NodeListProto = glow.NodeList.prototype
3184 	
3185 		/**
3186 			@private
3187 			@name glow.NodeList-dom0PropertyMapping
3188 			@description Mapping of HTML attribute names to DOM0 property names.
3189 		*/
3190 		, dom0PropertyMapping = { // keys must be lowercase
3191 			'class'     : 'className',
3192 			'for'       : 'htmlFor',
3193 			'maxlength' : 'maxLength'
3194 		}
3195 		
3196 		/**
3197 			@private
3198 			@name glow.NodeList-dataPropName
3199 			@type String
3200 			@description The property name added to the DomElement by the NodeList#data method.
3201 		*/
3202 		, dataPropName = '_uniqueData' + glow.UID
3203 		
3204 		/**
3205 			@private
3206 			@name glow.NodeList-dataIndex
3207 			@type String
3208 			@description The value of the dataPropName added by the NodeList#data method.
3209 		*/
3210 		, dataIndex = 1 // must be a truthy value
3211 			
3212 		/**
3213 			@private
3214 			@name glow.NodeList-dataCache
3215 			@type Object
3216 			@description Holds the data used by the NodeList#data method.
3217 			
3218 			The structure is like:
3219 			[
3220 				{
3221 					myKey: "my data"
3222 				}
3223 			]
3224 		*/
3225 		, dataCache = [];
3226 			
3227 	/**
3228 	@name glow.NodeList#addClass
3229 	@function
3230 	@description Adds a class to each node.
3231 
3232 	@param {string} name The name of the class to add.
3233 
3234 	@returns {glow.NodeList}
3235 
3236 	@example
3237 		glow("#login a").addClass("highlight");
3238 	*/
3239 	NodeListProto.addClass = function(name) {
3240 		var i = this.length;
3241 		
3242 		/*!debug*/
3243 			if (arguments.length !== 1) {
3244 				glow.debug.warn('[wrong count] glow.NodeList#addClass expects 1 argument, not '+arguments.length+'.');
3245 			}
3246 			else if (typeof arguments[0] !== 'string') {
3247 				glow.debug.warn('[wrong type] glow.NodeList#addClass expects argument 1 to be of type string, not '+typeof arguments[0]+'.');
3248 			}
3249 		/*gubed!*/
3250 		
3251 		while (i--) {
3252 			if (this[i].nodeType === 1) {
3253 				_addClass(this[i], name);
3254 			}
3255 		}
3256 		
3257 		return this;
3258 	};
3259 	
3260 	function _addClass(node, name) { // TODO: handle classnames separated by non-space characters?
3261 		if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') === -1 ) {
3262 			node.className += (node.className? ' ' : '') + name;
3263 		}
3264 	}
3265 	
3266 	/**
3267 	@name glow.NodeList#attr
3268 	@function
3269 	@description Gets or sets attributes.
3270 
3271 		When getting an attribute, it is retrieved from the first
3272 		node in this NodeList. Setting attributes applies the change
3273 		to each element in this NodeList.
3274 
3275 		To set an attribute, pass in the name as the first
3276 		parameter and the value as a second parameter.
3277 
3278 		To set multiple attributes in one call, pass in an object of
3279 		name/value pairs as a single parameter.
3280 
3281 		For browsers that don't support manipulating attributes
3282 		using the DOM, this method will try to do the right thing
3283 		(i.e. don't expect the semantics of this method to be
3284 		consistent across browsers as this is not possible with
3285 		currently supported browsers).
3286 
3287 	@param {string | Object} name The name of the attribute, or an object of name/value pairs
3288 	@param {string} [value] The value to set the attribute to.
3289 
3290 	@returns {string | undefined | glow.NodeList}
3291 
3292 		When setting attributes this method returns its own NodeList, otherwise
3293 		returns the attribute value. The attribute name is always treated as
3294 		case-insensitive. When getting, the returned value will be of type string unless
3295 		that particular attribute was never set and there is no default value, in which
3296 		case the returned value will be an empty string.
3297 
3298 	@example
3299 		var myNodeList = glow(".myImgClass");
3300 
3301 		// get an attribute
3302 		myNodeList.attr("class");
3303 
3304 		// set an attribute
3305 		myNodeList.attr("class", "anotherImgClass");
3306 
3307 		// set multiple attributes
3308 		myNodeList.attr({
3309 		  src: "a.png",
3310 		  alt: "Cat jumping through a field"
3311 		});
3312 	 */
3313 	 // see: http://tobielangel.com/2007/1/11/attribute-nightmare-in-ie/
3314 	NodeListProto.attr = function(/*arguments*/) {
3315 		var args = arguments,
3316 			argsLen = args.length,
3317 			thisLen = this.length,
3318 			keyvals,
3319 			name = keyvals = args[0], // using this API: attr(name) or attr({key: val}) ?
3320 			dom0Property = '',
3321 			node,
3322 			attrNode;
3323 		
3324 		/*!debug*/
3325 			if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#attr expects name to be of type string, not '+typeof arguments[0]+'.'); }
3326 			else if (arguments.length === 1 && (typeof arguments[0] !== 'string' && arguments[0].constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#attr expects argument 1 to be of type string or an instance of Object.'); }
3327 			else if (arguments.length === 0 ||  arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#attr expects 1 or 2 arguments, not '+arguments.length+'.'); }
3328 		/*gubed!*/
3329 		
3330 		if (this.length === 0) { // is this an empty nodelist?
3331 			return (argsLen > 1)? this : undef;
3332 		}
3333 		
3334 		if (typeof keyvals === 'object') { // SETting value from {name: value} object
3335 			for (name in keyvals) {
3336 				if (!keyvals.hasOwnProperty(name)) { continue; }
3337 				
3338 				// in IE6 and IE7 the attribute name needs to be translated into dom property name
3339 				if (glow.env.ie < 8) {
3340 					dom0Property = dom0PropertyMapping[name.toLowerCase()];
3341 				}
3342 				
3343 				var i = thisLen;
3344 				while (i--) {
3345 					node = this[i];
3346 					
3347 					if (node.nodeType !== 1) { continue; }
3348 					
3349 					if (dom0Property) {
3350 						node[dom0Property] = keyvals[name];
3351 					}
3352 					else {
3353 						node.setAttribute(name, keyvals[name], 0); // IE flags, 0: case-insensitive
3354 					}
3355 				}
3356 			}
3357 			
3358 			return this;
3359 		}
3360 		else {
3361 			node = this[0];
3362 				
3363 			if (node.nodeType !== 1) {
3364 				return (argsLen > 1)? this : undef;
3365 			}
3366 
3367 			if (argsLen === 1) { // GETting value from name. see http://reference.sitepoint.com/javascript/Element/getAttribute
3368 				if ( glow.env.ie && (name === 'href' || name === 'src') ) {
3369 					value = node.getAttribute(name, 2); // IE flags, 0: case-insensitive + 2: exactly as set
3370 					return (value === null)? '' : value;
3371 				}
3372 				else if (node.attributes[name]) { // in IE node.getAttributeNode sometimes returns unspecified default values so we look for specified attributes if we can
3373 					return (!node.attributes[name].specified)? '' : node.attributes[name].value;
3374 				}
3375 				else if (node.getAttributeNode) { // in IE getAttribute() does not always work so we use getAttributeNode if we can
3376 					attrNode = node.getAttributeNode(name, 0);
3377 					return (attrNode === null)? '' : attrNode.value;
3378 				}
3379 				else {
3380 					value = node.getAttribute(name, 2); // IE flags, 0: case-insensitive + 2: exactly as set
3381 					return (value === null)? '' : value;
3382 				}	
3383 			}
3384 			else { // SETting a single value like attr(name, value), normalize to an keyval object
3385 				if (glow.env.ie < 8) {
3386 					dom0Property = dom0PropertyMapping[name.toLowerCase()];
3387 				}
3388 				
3389 				if (dom0Property) {
3390 					node[dom0Property] = args[1];
3391 				}
3392 				else {
3393 					node.setAttribute(name, args[1], 0); // IE flags, 0: case-insensitive
3394 				}
3395 				return this;
3396 			}
3397 		}
3398 	};
3399 	/**
3400 		Copies the data from one nodelist to another
3401 		@private
3402 		@name glow.NodeList._copyData
3403 		@see glow.NodeList#clone
3404 		@function
3405 	*/
3406 	glow.NodeList._copyData = function(from, to){
3407 		var i = to.length,
3408 			data;
3409 		
3410 		while (i--) {
3411 			data = dataCache[ from[i][dataPropName] ];
3412 			data && to.slice(i, i+1).data(data);
3413 		}
3414 	}
3415 
3416 	/**
3417 	@name glow.NodeList#data
3418 	@function
3419 	@description Use this to safely attach arbitrary data to any DOM Element.
3420 	
3421 	This method is useful when you wish to avoid memory leaks that are possible when adding your own data directly to DOM Elements.
3422 	
3423 	When called with no arguments, will return glow's entire data store for the first node in this NodeList.
3424 	
3425 	Otherwise, when given a name, will return the associated value from the first node in this NodeList.
3426 	
3427 	When given both a name and a value, will store that data on every node in this NodeList.
3428 	
3429 	Optionally you can pass in a single object composed of multiple name, value pairs.
3430 	
3431 	@param {string|Object} [key] The name of the value in glow's data store.
3432 	@param {Object} [val] The value you wish to associate with the given name.
3433 	@see glow.NodeList#removeData
3434 	@example
3435 	
3436 	glow("p").data("tea", "milky");
3437 	var colour = glow("p").data("tea"); // milky
3438 	@returns {Object} When setting a value this method can be chained, as in that case it will return itself.
3439 	@see glow.NodeList#removeData
3440 	*/
3441 	NodeListProto.data = function (key, val) { /*debug*///console.log("data("+key+", "+val+")");
3442 		var args = arguments,
3443 			argsLen = args.length,
3444 			keyvals = key, // like: data({key: val}) or data(key, val)
3445 			index,
3446 			node;
3447 			
3448 		/*!debug*/
3449 			if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#data expects name argument to be of type string.'); }
3450 			else if (arguments.length === 1 && (typeof arguments[0] !== 'string' && arguments[0].constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#data expects argument 1 to be of type string or an instance of Object.'); }
3451 			else if (arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#data expects 0, 1 or 2 arguments.'); }
3452 		/*gubed!*/
3453 		
3454 		if (argsLen > 1) { // SET key, val on every node
3455 			var i = this.length;
3456 			while (i--) {
3457 				node = this[i];
3458 				if (node.nodeType !== 1) { continue; }
3459 				
3460 				index = node[''+dataPropName];
3461 
3462 				if (!index) { // assumes index is always > 0
3463 					index = dataIndex++;
3464 					
3465 					node[dataPropName] = index;
3466 					dataCache[index] = {};
3467 				}
3468 				dataCache[index][key] = val;
3469 			}
3470 			
3471 			return this; // chainable with (key, val) signature
3472 		}
3473 		else if (typeof keyvals === 'object') { // SET keyvals on every node
3474 			var i = this.length;
3475 			while (i--) {
3476 				node = this[i];
3477 				if (node.nodeType !== 1) { continue; }
3478 				
3479 				index = node[dataPropName];
3480 				if (!index) { // assumes index is always > 0
3481 					index = dataIndex++;
3482 					
3483 					node[dataPropName] = index;
3484 					dataCache[index] = {};
3485 				}
3486 				for (key in keyvals) {
3487 					dataCache[index][key] = keyvals[key];
3488 				}
3489 			}
3490 			
3491 			return this; // chainable with ({key, val}) signature
3492 		}
3493 		else { // GET from first node
3494 			node = this[0];
3495 			if (node === undef || node.nodeType !== 1) { return undef; }
3496 				
3497 			if ( !(index = node[dataPropName]) ) {
3498 				return undef;
3499 			}
3500 
3501 			if (key !== undef) {
3502 				return dataCache[index][key];
3503 			}
3504 			
3505 			// get the entire data cache object for this node
3506 			return dataCache[index];
3507 		}
3508 	};
3509 	
3510 	/**
3511 	@name glow.NodeList#hasAttr
3512 	@function
3513 	@description Does the node have a particular attribute?
3514 		
3515 		The first node in this NodeList is tested.
3516 		
3517 	@param {string} name The name of the attribute to test for.
3518 
3519 	@returns {boolean|undefined} Returns undefined if the first node is not an element,
3520 	or if the NodeList is empty, otherwise returns true/false to indicate if that attribute exists
3521 	on the first element.
3522 
3523 	@example
3524 		if ( glow("#myImg").hasAttr("alt") ){
3525 			// ...
3526 		}
3527 	*/
3528 	NodeListProto.hasAttr = function(name) {
3529 		var node;
3530 		
3531 		/*!debug*/
3532 			if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasAttr expects 1 argument.'); }
3533 			else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasAttr expects argument 1 to be of type string.'); }
3534 		/*gubed!*/
3535 		
3536 		node = this[0];
3537 		
3538 		if (this.length && node.nodeType === 1) {
3539 			if (node.attributes[name]) { // is an object in  IE, or else: undefined in IE < 8, null in IE 8
3540 				return !!node.attributes[name].specified;
3541 			}
3542 			
3543 			if (node.hasAttribute) { return node.hasAttribute(name); } // like FF, Safari, etc
3544 			else { return node.attributes[name] !== undef; } // like IE7
3545 		}
3546 	};
3547 	
3548 	/**
3549 	@name glow.NodeList#hasClass
3550 	@function
3551 	@description Does the node have a particular class?
3552 
3553 		The first node in this NodeList is tested.
3554 
3555 	@param {string} name The name of the class to test for.
3556 
3557 	@returns {boolean}
3558 
3559 	@example
3560 		if ( glow("#myInput").hasClass("errored") ){
3561 			// ...
3562 		}
3563 	*/
3564 	NodeListProto.hasClass = function (name) {
3565 		/*!debug*/
3566 			if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasClass expects 1 argument.'); }
3567 			else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasClass expects argument 1 to be of type string.'); }
3568 		/*gubed!*/
3569 		
3570 		if (this.length && this[0].nodeType === 1) {
3571 			return ( (' ' + this[0].className + ' ').indexOf(' ' + name + ' ') > -1 );
3572 		}
3573 	};
3574 	
3575 	/**
3576 	@name glow.NodeList#prop
3577 	@function
3578 	@description Gets or sets node properties.
3579 	
3580 		This function gets / sets node properties, to get attributes,
3581 		see {@link glow.NodeList#attr NodeList#attr}.
3582 		
3583 		When getting a property, it is retrieved from the first
3584 		node in this NodeList. Setting properties to each element in
3585 		this NodeList.
3586 		
3587 		To set multiple properties in one call, pass in an object of
3588 		name/value pairs.
3589 		
3590 	@param {string | Object} name The name of the property, or an object of name/value pairs
3591 	@param {string} [value] The value to set the property to.
3592 
3593 	@returns {string | glow.NodeList}
3594 
3595 		When setting properties it returns the NodeList, otherwise
3596 		returns the property value.
3597 
3598 	@example
3599 		var myNodeList = glow("#formElement");
3600 
3601 		// get the node name
3602 		myNodeList.prop("nodeName");
3603 
3604 		// set a property
3605 		myNodeList.prop("_secretValue", 10);
3606 
3607 		// set multiple properties
3608 		myNodeList.prop({
3609 			checked: true,
3610 			_secretValue: 10
3611 		});
3612 	*/
3613 	NodeListProto.prop = function(name, val) {
3614 		var hash = name,
3615 			argsLen = arguments.length;
3616 		
3617 		/*!debug*/
3618 			if (arguments.length === 1 && (typeof name !== 'string' && name.constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#prop expects argument 1 to be of type string or Object.'); }
3619 			else if (arguments.length === 2 && typeof name !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#prop expects name to be of type string.'); }
3620 			else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#prop expects 1 or 2 arguments.'); }
3621 		/*gubed!*/
3622 		
3623 		if (this.length === 0) return;
3624 		
3625 		if (argsLen === 2 && typeof name === 'string') {
3626 			for (var i = 0, ilen = this.length; i < ilen; i++) {
3627 				if (this[i].nodeType === 1) { this[i][name] = val; }
3628 			}
3629 			return this;
3630 		}
3631 		else if (argsLen === 1 && hash.constructor === Object) {
3632 			for (var key in hash) {
3633 				for (var i = 0, ilen = this.length; i < ilen; i++) {
3634 					if (this[i].nodeType === 1) { this[i][key] = hash[key]; }
3635 				}
3636 			}
3637 			return this;
3638 		}
3639 		else if (argsLen === 1 && typeof name === 'string') {
3640 			if (this[0].nodeType === 1) { return this[0][name]; }
3641 		}
3642 		else {
3643 			throw new Error('Invalid parameters.');
3644 		}
3645 	};
3646 	
3647 	/**
3648 	@name glow.NodeList#removeAttr
3649 	@function
3650 	@description Removes an attribute from each node.
3651 
3652 	@param {string} name The name of the attribute to remove.
3653 
3654 	@returns {glow.NodeList}
3655 
3656 	@example
3657 		glow("a").removeAttr("target");
3658 	*/
3659 	NodeListProto.removeAttr = function (name) {
3660 		var dom0Property;
3661 		
3662 		/*!debug*/
3663 			if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeAttr expects 1 argument.'); }
3664 			else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeAttr expects argument 1 to be of type string.'); }
3665 		/*gubed!*/
3666 	
3667 		for (var i = 0, leni = this.length; i < leni; i++) {
3668 			if (this[i].nodeType === 1) {
3669 				if (glow.env.ie < 8) {
3670 					if ( (dom0Property = dom0PropertyMapping[name.toLowerCase()]) ) {
3671 						this[i][dom0Property] = '';
3672 					}
3673 				}
3674 				
3675 				if (this[i].removeAttribute) this[i].removeAttribute(name);
3676 			}
3677 		}
3678 		return this;
3679 	};
3680 	
3681 	/**
3682 	@name glow.NodeList#removeClass
3683 	@function
3684 	@description Removes a class from each node.
3685 
3686 	@param {string} name The name of the class to remove.
3687 
3688 	@returns {glow.NodeList}
3689 
3690 	@example
3691 		glow("#footer #login a").removeClass("highlight");
3692 	*/
3693 	NodeListProto.removeClass = function(name) {
3694 		var node;
3695 					
3696 		/*!debug*/
3697 			if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeClass() expects 1 argument.'); }
3698 			else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeClass() expects argument 1 to be of type string.'); }
3699 		/*gubed!*/
3700 		
3701 		var i = this.length;
3702 		while (i--) {
3703 			node = this[i];
3704 			if (node.className) {
3705 				_removeClass(node, name);
3706 			}
3707 		}
3708 		return this;
3709 	};
3710 	
3711 	function _removeClass(node, name) {
3712 		var oldClasses = node.className.split(' '),
3713 			newClasses = [];
3714 			
3715 		oldClasses = node.className.split(' ');
3716 		newClasses = [];
3717 		
3718 		var i = oldClasses.length;
3719 		while (i--) {
3720 			if (oldClasses[i] !== name) {
3721 				oldClasses[i] && newClasses.unshift(oldClasses[i]); // unshift to maintain original order
3722 			}
3723 		}
3724 		node.className = (newClasses.length)? newClasses.join(' ') : '';
3725 	}
3726 	
3727 	/**
3728 	@name glow.NodeList#removeData
3729 	@function
3730 	@description Removes data previously added by {@link glow.NodeList#data} from each node in this NodeList.
3731 	
3732 	When called with no arguments, will delete glow's entire data store for each node in this NodeList.
3733 	
3734 	Otherwise, when given a name, will delete the associated value from each node in this NodeList.
3735 	
3736 	@param {string} [key] The name of the value in glow's data store.
3737 	@see glow.NodeList#data
3738 	*/
3739 	NodeListProto.removeData = function(key) {
3740 		var elm,
3741 			i = this.length,
3742 			index;
3743 			// uses private scoped variables: dataCache, dataPropName
3744 		
3745 		/*!debug*/
3746 			if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.NodeList#removeData expects 0 or 1 arguments.'); }
3747 			else if (arguments.length === 1 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeData expects argument 1 to be of type string.'); }
3748 		/*gubed!*/
3749 		
3750 		while (i--) {
3751 			elm = this[i];
3752 			index = elm[dataPropName];
3753 			
3754 			if (index !== undef) {
3755 				switch (arguments.length) {
3756 					case 0:
3757 						dataCache[index] = undef;
3758 						elm[dataPropName] = undef;
3759 						try {
3760 							delete elm[dataPropName]; // IE 6 goes wobbly here
3761 						}
3762 						catch(e) { // remove expando from IE 6
3763 							elm.removeAttribute && elm.removeAttribute(dataPropName);
3764 						}
3765 						break;
3766 					case 1:
3767 						dataCache[index][key] = undef;
3768 						delete dataCache[index][key];
3769 						break;
3770 				}
3771 			}
3772 		}
3773 		
3774 		return this; // chainable
3775 	};
3776 	
3777 	/**
3778 	@name glow.NodeList#toggleClass
3779 	@function
3780 	@description Toggles a class on each node.
3781 
3782 	@param {string} name The name of the class to toggle.
3783 
3784 	@returns {glow.NodeList}
3785 
3786 	@example
3787 		glow(".onOffSwitch").toggleClass("on");
3788 	 */
3789 	NodeListProto.toggleClass = function(name) {
3790 		var node;
3791 		
3792 		/*!debug*/
3793 			if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#toggleClass() expects 1 argument.'); }
3794 			else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#toggleClass() expects argument 1 to be of type string.'); }
3795 		/*gubed!*/
3796 		
3797 		for (var i = 0, leni = this.length; i < leni; i++) {
3798 			node = this[i];
3799 			if (node.className) {
3800 				if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') > -1 ) {
3801 					_removeClass(node, name);
3802 				}
3803 				else {
3804 					_addClass(node, name);
3805 				}
3806 			}
3807 		}
3808 		
3809 		return this;
3810 	};
3811 	
3812 	/**
3813 	@name glow.NodeList#val
3814 	@function
3815 	@description Gets or sets form values for the first node.
3816 		The returned value depends on the type of element, see below:
3817 
3818 		<dl>
3819 		<dt>Radio button or checkbox</dt>
3820 		<dd>If checked, then the contents of the value property, otherwise an empty string.</dd>
3821 		<dt>Select</dt>
3822 		<dd>The contents of value property of the selected option</dd>
3823 		<dt>Select (multiple)</dt>
3824 		<dd>An array of selected option values.</dd>
3825 		<dt>Other form elements</dt>
3826 		<dd>The value of the input.</dd>
3827 		</dl>
3828 
3829 		Getting values from a form:
3830 
3831 		If the first element in the NodeList is a form, then an
3832 		object is returned containing the form data. Each item
3833 		property of the object is a value as above, apart from when
3834 		multiple elements of the same name exist, in which case the
3835 		it will contain an array of values.
3836 
3837 		Setting values for form elements:
3838 
3839 		If a value is passed and the first element of the NodeList
3840 		is a form element, then the form element is given that value.
3841 		For select elements, this means that the first option that
3842 		matches the value will be selected. For selects that allow
3843 		multiple selection, the options which have a value that
3844 		exists in the array of values/match the value will be
3845 		selected and others will be deselected.
3846 
3847 		Checkboxes and radio buttons will be checked only if the value is the same
3848 		as the one you provide.
3849 
3850 		Setting values for forms:
3851 
3852 		If the first element in the NodeList is a form and the
3853 		value is an object, then each element of the form has its
3854 		value set to the corresponding property of the object, using
3855 		the method described above.
3856 
3857 	@param {string | Object} [value] The value to set the form element/elements to.
3858 
3859 	@returns {glow.NodeList | string | Object}
3860 
3861 		When used to set a value it returns the NodeList, otherwise
3862 		returns the value as described above.
3863 
3864 	@example
3865 		// get a value from an input with the id 'username'
3866 		var username = glow("#username").val();
3867 
3868 	@example			
3869 		// get values from a form
3870 		var userDetails = glow("form").val();
3871 
3872 	@example
3873 		// set a value
3874 		glow("#username").val("example username");
3875 
3876 	@example
3877 		// set values in a form
3878 		glow("form").val({
3879 			username : "another",
3880 			name     : "A N Other"
3881 		});
3882 	*/
3883 	NodeListProto.val = function(){		
3884 		var args = arguments,
3885 			val = args[0],
3886 			i = 0,
3887 			length = this.length;
3888 
3889 		if (args.length === 0) {
3890 			return this[0].nodeName == 'FORM' ?
3891 				formValues(this[0]) :
3892 				elementValue(this[0]);
3893 		}
3894 		if (this[0].nodeName == 'FORM') {
3895 			if (! typeof val == 'object') {
3896 				throw 'value for FORM must be object';
3897 			}
3898 			setFormValues(this[0], val);
3899 		} else {
3900 			for (; i < length; i++) {
3901 				setValue(this[i], val);
3902 			}
3903 		}
3904 		return this;		
3905 	};
3906 	
3907 	/*
3908 	 @name elementValue
3909 	 @private
3910 	 @returns the value of the form element
3911 	*/
3912 	function elementValue (el) {
3913 		var elType = el.type,
3914 			elChecked = el.checked,
3915 			elValue = el.value,
3916 			vals = [],
3917 			i = 0;
3918 
3919 		if (elType == 'radio') {
3920 			return elChecked ? elValue : '';
3921 		}
3922 			
3923 		else if (elType == 'checkbox') {
3924 			return elChecked ? elValue : '';
3925 		}
3926 			
3927 		else if (elType == 'select-one') {
3928 			return el.selectedIndex > -1 ? el.options[el.selectedIndex].value : '';
3929 		}
3930 			
3931 		else if (elType == 'select-multiple') {
3932 			for (var length = el.options.length; i < length; i++) {
3933 				if (el.options[i].selected) {
3934 					vals[vals.length] = el.options[i].value;
3935 				}
3936 			}
3937 			return vals;
3938 		}
3939 			
3940 		else {
3941 			return elValue;
3942 		}
3943 	}
3944 		
3945 	/*
3946 	@name: setValue
3947 	@description 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
3948 	@returns val or bool
3949 	@private
3950 	*/
3951 	function setValue (el, val) {
3952 		var i = 0,
3953 			length,
3954 			n = 0,
3955 			nlen,
3956 			elOption,
3957 			optionVal;
3958 
3959 		if (el.type == 'select-one') {
3960 			for (length = el.options.length; i < length; i++) {
3961 				if (el.options[i].value == val) {
3962 					el.selectedIndex = i;
3963 					return true;
3964 				}
3965 			}
3966 			return false;
3967 		} else if (el.type == 'select-multiple') {
3968 			var isArray = !!val.push;
3969 			for (i = 0, length = el.options.length; i < length; i++) {
3970 				elOption = el.options[i];
3971 				optionVal = elOption.value;
3972 				if (isArray) {
3973 					elOption.selected = false;
3974 					for (nlen = val.length; n < nlen; n++) {
3975 						if (optionVal == val[n]) {
3976 							elOption.selected = true;
3977 							val.splice(n, 1);
3978 							break;
3979 						}
3980 					}
3981 				} else {
3982 					return elOption.selected = val == optionVal;
3983 				}
3984 			}
3985 			return false;
3986 		} else if (el.type == 'radio' || el.type == 'checkbox') {
3987 			el.checked = val == el.value;
3988 			return val == el.value;
3989 		} else {
3990 			el.value = val;
3991 			return true;
3992 		}
3993 	}
3994 		
3995 	/*
3996 	@name setFormValues
3997 	@description Set values of a form to those in passed in object.
3998 	@private
3999 	*/
4000 	function setFormValues (form, vals) {
4001 		var prop, currentField,
4002 			fields = {},
4003 			storeType, i = 0, n, len, foundOne, currentFieldType;
4004 
4005 		for (prop in vals) {
4006 			currentField = form[prop];
4007 			if (currentField && currentField[0] && !currentField.options) { // is array of fields
4008 				//normalise values to array of vals
4009 				vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]];
4010 				//order the fields by types that matter
4011 				fields.radios = [];
4012 				fields.checkboxesSelects = [];
4013 				fields.multiSelects = [];
4014 				fields.other = [];
4015 
4016 				for (i = 0; currentField[i]; i++) {
4017 					currentFieldType = currentField[i].type;
4018 					if (currentFieldType == 'radio') {
4019 						storeType = 'radios';
4020 				} else if (currentFieldType == 'select-one' || currentFieldType == 'checkbox') {
4021 						storeType = 'checkboxesSelects';
4022 					} else if (currentFieldType == 'select-multiple') {
4023 						storeType = 'multiSelects';
4024 					} else {
4025 						storeType = 'other';
4026 					}
4027 					//add it to the correct array
4028 					fields[storeType][fields[storeType].length] = currentField[i];
4029 				}
4030 
4031 				for (i = 0; fields.multiSelects[i]; i++) {
4032 					vals[prop] = setValue(fields.multiSelects[i], vals[prop]);
4033 				}
4034 				for (i = 0; fields.checkboxesSelects[i]; i++) {
4035 					setValue(fields.checkboxesSelects[i], '');
4036 					for (n = 0, len = vals[prop].length; n < len; n++) {
4037 						if (setValue(fields.checkboxesSelects[i], vals[prop][n])) {
4038 							vals[prop].slice(n, 1);
4039 							break;
4040 						}
4041 					}
4042 				}
4043 				for (i = 0; fields.radios[i]; i++) {
4044 					fields.radios[i].checked = false;
4045 					foundOne = false;
4046 					for (n = 0, len = vals[prop].length; n < len; n++) {
4047 						if (setValue(fields.radios[i], vals[prop][n])) {
4048 							vals[prop].slice(n, 1);
4049 							foundOne = true;
4050 							break;
4051 						}
4052 						if (foundOne) { break; }
4053 					}
4054 				}
4055 				for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) {
4056 					setValue(fields.other[i], vals[prop][i]);
4057 				}
4058 			} else if (currentField && currentField.nodeName) { // is single field, easy
4059 				setValue(currentField, vals[prop]);
4060 			}
4061 		}
4062 	}
4063 
4064 	/*
4065 	@name formValues
4066 	@description Get an object containing form data.
4067 	@private
4068 	*/
4069 	function formValues (form) {
4070 		var vals = {},
4071 			radios = {},
4072 			formElements = form.elements,
4073 			i = formElements.length,
4074 			name,
4075 			formElement,
4076 			j,
4077 			radio,
4078 			nodeName;
4079 		while (i--) {
4080 			formElement = formElements[i];
4081 			nodeName = formElement.nodeName.toLowerCase();
4082 			name = formElement.name;
4083 				
4084 			// fieldsets & objects come back as form elements, but we don't care about these
4085 			// we don't bother with fields that don't have a name
4086 			// switch to whitelist?
4087 			if (
4088 				nodeName == 'fieldset' ||
4089 				nodeName == 'object' ||
4090 				!name
4091 			) { continue; }
4092 			if (formElement.type == 'checkbox' && ! formElement.checked) {
4093 				if (! name in vals) {
4094 					vals[name] = undefined;
4095 				}
4096 			} else if (formElement.type == 'radio') {
4097 				
4098 				if (radios[name]) {
4099 					radios[name][radios[name].length] = formElement;
4100 				} else {
4101 					radios[name] = [formElement];
4102 				}
4103 			} else {
4104 				var value = elementValue(formElement);
4105 				if (name in vals) {
4106 					if (vals[name].push) {
4107 						vals[name][vals[name].length] = value;
4108 					} else {
4109 						vals[name] = [vals[name], value];
4110 					}
4111 				} else {
4112 					vals[name] = value;
4113 				}
4114 			}
4115 		}
4116 		for (i in radios) {
4117 			var length,
4118 			j = 0;
4119 			for (length = radios[i].length; j < length; j++) {
4120 				radio = radios[i][j];
4121 				name = radio.name;
4122 				if (radio.checked) {
4123 					vals[radio.name] = radio.value;
4124 					break;
4125 				}
4126 			}
4127 			if (! name in vals) { alert('15 if name in vals'); vals[name] = undefined; }
4128 		}
4129 		return vals;
4130 	}
4131 });
4132 /*!
4133  * Sizzle CSS Selector Engine - v1.0
4134  *  Copyright 2009, The Dojo Foundation
4135  *  Released under the MIT, BSD, and GPL Licenses.
4136  *  More information: http://sizzlejs.com/
4137  */
4138 (function(){
4139 
4140 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
4141 	done = 0,
4142 	toString = Object.prototype.toString,
4143 	hasDuplicate = false,
4144 	baseHasDuplicate = true;
4145 
4146 // Here we check if the JavaScript engine is using some sort of
4147 // optimization where it does not always call our comparision
4148 // function. If that is the case, discard the hasDuplicate value.
4149 //   Thus far that includes Google Chrome.
4150 [0, 0].sort(function(){
4151 	baseHasDuplicate = false;
4152 	return 0;
4153 });
4154 
4155 var Sizzle = function(selector, context, results, seed) {
4156 	results = results || [];
4157 	context = context || document;
4158 
4159 	var origContext = context;
4160 
4161 	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
4162 		return [];
4163 	}
4164 	
4165 	if ( !selector || typeof selector !== "string" ) {
4166 		return results;
4167 	}
4168 
4169 	var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
4170 		soFar = selector, ret, cur, pop, i;
4171 	
4172 	// Reset the position of the chunker regexp (start from head)
4173 	do {
4174 		chunker.exec("");
4175 		m = chunker.exec(soFar);
4176 
4177 		if ( m ) {
4178 			soFar = m[3];
4179 		
4180 			parts.push( m[1] );
4181 		
4182 			if ( m[2] ) {
4183 				extra = m[3];
4184 				break;
4185 			}
4186 		}
4187 	} while ( m );
4188 
4189 	if ( parts.length > 1 && origPOS.exec( selector ) ) {
4190 		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
4191 			set = posProcess( parts[0] + parts[1], context );
4192 		} else {
4193 			set = Expr.relative[ parts[0] ] ?
4194 				[ context ] :
4195 				Sizzle( parts.shift(), context );
4196 
4197 			while ( parts.length ) {
4198 				selector = parts.shift();
4199 
4200 				if ( Expr.relative[ selector ] ) {
4201 					selector += parts.shift();
4202 				}
4203 				
4204 				set = posProcess( selector, set );
4205 			}
4206 		}
4207 	} else {
4208 		// Take a shortcut and set the context if the root selector is an ID
4209 		// (but not if it'll be faster if the inner selector is an ID)
4210 		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
4211 				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
4212 			ret = Sizzle.find( parts.shift(), context, contextXML );
4213 			context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
4214 		}
4215 
4216 		if ( context ) {
4217 			ret = seed ?
4218 				{ expr: parts.pop(), set: makeArray(seed) } :
4219 				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
4220 			set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
4221 
4222 			if ( parts.length > 0 ) {
4223 				checkSet = makeArray(set);
4224 			} else {
4225 				prune = false;
4226 			}
4227 
4228 			while ( parts.length ) {
4229 				cur = parts.pop();
4230 				pop = cur;
4231 
4232 				if ( !Expr.relative[ cur ] ) {
4233 					cur = "";
4234 				} else {
4235 					pop = parts.pop();
4236 				}
4237 
4238 				if ( pop == null ) {
4239 					pop = context;
4240 				}
4241 
4242 				Expr.relative[ cur ]( checkSet, pop, contextXML );
4243 			}
4244 		} else {
4245 			checkSet = parts = [];
4246 		}
4247 	}
4248 
4249 	if ( !checkSet ) {
4250 		checkSet = set;
4251 	}
4252 
4253 	if ( !checkSet ) {
4254 		Sizzle.error( cur || selector );
4255 	}
4256 
4257 	if ( toString.call(checkSet) === "[object Array]" ) {
4258 		if ( !prune ) {
4259 			results.push.apply( results, checkSet );
4260 		} else if ( context && context.nodeType === 1 ) {
4261 			for ( i = 0; checkSet[i] != null; i++ ) {
4262 				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
4263 					results.push( set[i] );
4264 				}
4265 			}
4266 		} else {
4267 			for ( i = 0; checkSet[i] != null; i++ ) {
4268 				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
4269 					results.push( set[i] );
4270 				}
4271 			}
4272 		}
4273 	} else {
4274 		makeArray( checkSet, results );
4275 	}
4276 
4277 	if ( extra ) {
4278 		Sizzle( extra, origContext, results, seed );
4279 		Sizzle.uniqueSort( results );
4280 	}
4281 
4282 	return results;
4283 };
4284 
4285 Sizzle.uniqueSort = function(results){
4286 	if ( sortOrder ) {
4287 		hasDuplicate = baseHasDuplicate;
4288 		results.sort(sortOrder);
4289 
4290 		if ( hasDuplicate ) {
4291 			for ( var i = 1; i < results.length; i++ ) {
4292 				if ( results[i] === results[i-1] ) {
4293 					results.splice(i--, 1);
4294 				}
4295 			}
4296 		}
4297 	}
4298 
4299 	return results;
4300 };
4301 
4302 Sizzle.matches = function(expr, set){
4303 	return Sizzle(expr, null, null, set);
4304 };
4305 
4306 Sizzle.find = function(expr, context, isXML){
4307 	var set;
4308 
4309 	if ( !expr ) {
4310 		return [];
4311 	}
4312 
4313 	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
4314 		var type = Expr.order[i], match;
4315 		
4316 		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
4317 			var left = match[1];
4318 			match.splice(1,1);
4319 
4320 			if ( left.substr( left.length - 1 ) !== "\\" ) {
4321 				match[1] = (match[1] || "").replace(/\\/g, "");
4322 				set = Expr.find[ type ]( match, context, isXML );
4323 				if ( set != null ) {
4324 					expr = expr.replace( Expr.match[ type ], "" );
4325 					break;
4326 				}
4327 			}
4328 		}
4329 	}
4330 
4331 	if ( !set ) {
4332 		set = context.getElementsByTagName("*");
4333 	}
4334 
4335 	return {set: set, expr: expr};
4336 };
4337 
4338 Sizzle.filter = function(expr, set, inplace, not){
4339 	var old = expr, result = [], curLoop = set, match, anyFound,
4340 		isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);
4341 
4342 	while ( expr && set.length ) {
4343 		for ( var type in Expr.filter ) {
4344 			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
4345 				var filter = Expr.filter[ type ], found, item, left = match[1];
4346 				anyFound = false;
4347 
4348 				match.splice(1,1);
4349 
4350 				if ( left.substr( left.length - 1 ) === "\\" ) {
4351 					continue;
4352 				}
4353 
4354 				if ( curLoop === result ) {
4355 					result = [];
4356 				}
4357 
4358 				if ( Expr.preFilter[ type ] ) {
4359 					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
4360 
4361 					if ( !match ) {
4362 						anyFound = found = true;
4363 					} else if ( match === true ) {
4364 						continue;
4365 					}
4366 				}
4367 
4368 				if ( match ) {
4369 					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
4370 						if ( item ) {
4371 							found = filter( item, match, i, curLoop );
4372 							var pass = not ^ !!found;
4373 
4374 							if ( inplace && found != null ) {
4375 								if ( pass ) {
4376 									anyFound = true;
4377 								} else {
4378 									curLoop[i] = false;
4379 								}
4380 							} else if ( pass ) {
4381 								result.push( item );
4382 								anyFound = true;
4383 							}
4384 						}
4385 					}
4386 				}
4387 
4388 				if ( found !== undefined ) {
4389 					if ( !inplace ) {
4390 						curLoop = result;
4391 					}
4392 
4393 					expr = expr.replace( Expr.match[ type ], "" );
4394 
4395 					if ( !anyFound ) {
4396 						return [];
4397 					}
4398 
4399 					break;
4400 				}
4401 			}
4402 		}
4403 
4404 		// Improper expression
4405 		if ( expr === old ) {
4406 			if ( anyFound == null ) {
4407 				Sizzle.error( expr );
4408 			} else {
4409 				break;
4410 			}
4411 		}
4412 
4413 		old = expr;
4414 	}
4415 
4416 	return curLoop;
4417 };
4418 
4419 Sizzle.error = function( msg ) {
4420 	throw "Syntax error, unrecognized expression: " + msg;
4421 };
4422 
4423 var Expr = Sizzle.selectors = {
4424 	order: [ "ID", "NAME", "TAG" ],
4425 	match: {
4426 		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
4427 		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
4428 		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
4429 		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
4430 		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
4431 		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
4432 		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
4433 		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
4434 	},
4435 	leftMatch: {},
4436 	attrMap: {
4437 		"class": "className",
4438 		"for": "htmlFor"
4439 	},
4440 	attrHandle: {
4441 		href: function(elem){
4442 			return elem.getAttribute("href");
4443 		}
4444 	},
4445 	relative: {
4446 		"+": function(checkSet, part){
4447 			var isPartStr = typeof part === "string",
4448 				isTag = isPartStr && !/\W/.test(part),
4449 				isPartStrNotTag = isPartStr && !isTag;
4450 
4451 			if ( isTag ) {
4452 				part = part.toLowerCase();
4453 			}
4454 
4455 			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
4456 				if ( (elem = checkSet[i]) ) {
4457 					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
4458 
4459 					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
4460 						elem || false :
4461 						elem === part;
4462 				}
4463 			}
4464 
4465 			if ( isPartStrNotTag ) {
4466 				Sizzle.filter( part, checkSet, true );
4467 			}
4468 		},
4469 		">": function(checkSet, part){
4470 			var isPartStr = typeof part === "string",
4471 				elem, i = 0, l = checkSet.length;
4472 
4473 			if ( isPartStr && !/\W/.test(part) ) {
4474 				part = part.toLowerCase();
4475 
4476 				for ( ; i < l; i++ ) {
4477 					elem = checkSet[i];
4478 					if ( elem ) {
4479 						var parent = elem.parentNode;
4480 						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
4481 					}
4482 				}
4483 			} else {
4484 				for ( ; i < l; i++ ) {
4485 					elem = checkSet[i];
4486 					if ( elem ) {
4487 						checkSet[i] = isPartStr ?
4488 							elem.parentNode :
4489 							elem.parentNode === part;
4490 					}
4491 				}
4492 
4493 				if ( isPartStr ) {
4494 					Sizzle.filter( part, checkSet, true );
4495 				}
4496 			}
4497 		},
4498 		"": function(checkSet, part, isXML){
4499 			var doneName = done++, checkFn = dirCheck, nodeCheck;
4500 
4501 			if ( typeof part === "string" && !/\W/.test(part) ) {
4502 				part = part.toLowerCase();
4503 				nodeCheck = part;
4504 				checkFn = dirNodeCheck;
4505 			}
4506 
4507 			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
4508 		},
4509 		"~": function(checkSet, part, isXML){
4510 			var doneName = done++, checkFn = dirCheck, nodeCheck;
4511 
4512 			if ( typeof part === "string" && !/\W/.test(part) ) {
4513 				part = part.toLowerCase();
4514 				nodeCheck = part;
4515 				checkFn = dirNodeCheck;
4516 			}
4517 
4518 			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
4519 		}
4520 	},
4521 	find: {
4522 		ID: function(match, context, isXML){
4523 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
4524 				var m = context.getElementById(match[1]);
4525 				return m ? [m] : [];
4526 			}
4527 		},
4528 		NAME: function(match, context){
4529 			if ( typeof context.getElementsByName !== "undefined" ) {
4530 				var ret = [], results = context.getElementsByName(match[1]);
4531 
4532 				for ( var i = 0, l = results.length; i < l; i++ ) {
4533 					if ( results[i].getAttribute("name") === match[1] ) {
4534 						ret.push( results[i] );
4535 					}
4536 				}
4537 
4538 				return ret.length === 0 ? null : ret;
4539 			}
4540 		},
4541 		TAG: function(match, context){
4542 			return context.getElementsByTagName(match[1]);
4543 		}
4544 	},
4545 	preFilter: {
4546 		CLASS: function(match, curLoop, inplace, result, not, isXML){
4547 			match = " " + match[1].replace(/\\/g, "") + " ";
4548 
4549 			if ( isXML ) {
4550 				return match;
4551 			}
4552 
4553 			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
4554 				if ( elem ) {
4555 					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
4556 						if ( !inplace ) {
4557 							result.push( elem );
4558 						}
4559 					} else if ( inplace ) {
4560 						curLoop[i] = false;
4561 					}
4562 				}
4563 			}
4564 
4565 			return false;
4566 		},
4567 		ID: function(match){
4568 			return match[1].replace(/\\/g, "");
4569 		},
4570 		TAG: function(match, curLoop){
4571 			return match[1].toLowerCase();
4572 		},
4573 		CHILD: function(match){
4574 			if ( match[1] === "nth" ) {
4575 				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
4576 				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
4577 					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
4578 					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
4579 
4580 				// calculate the numbers (first)n+(last) including if they are negative
4581 				match[2] = (test[1] + (test[2] || 1)) - 0;
4582 				match[3] = test[3] - 0;
4583 			}
4584 
4585 			// TODO: Move to normal caching system
4586 			match[0] = done++;
4587 
4588 			return match;
4589 		},
4590 		ATTR: function(match, curLoop, inplace, result, not, isXML){
4591 			var name = match[1].replace(/\\/g, "");
4592 			
4593 			if ( !isXML && Expr.attrMap[name] ) {
4594 				match[1] = Expr.attrMap[name];
4595 			}
4596 
4597 			if ( match[2] === "~=" ) {
4598 				match[4] = " " + match[4] + " ";
4599 			}
4600 
4601 			return match;
4602 		},
4603 		PSEUDO: function(match, curLoop, inplace, result, not){
4604 			if ( match[1] === "not" ) {
4605 				// If we're dealing with a complex expression, or a simple one
4606 				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
4607 					match[3] = Sizzle(match[3], null, null, curLoop);
4608 				} else {
4609 					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
4610 					if ( !inplace ) {
4611 						result.push.apply( result, ret );
4612 					}
4613 					return false;
4614 				}
4615 			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
4616 				return true;
4617 			}
4618 			
4619 			return match;
4620 		},
4621 		POS: function(match){
4622 			match.unshift( true );
4623 			return match;
4624 		}
4625 	},
4626 	filters: {
4627 		enabled: function(elem){
4628 			return elem.disabled === false && elem.type !== "hidden";
4629 		},
4630 		disabled: function(elem){
4631 			return elem.disabled === true;
4632 		},
4633 		checked: function(elem){
4634 			return elem.checked === true;
4635 		},
4636 		selected: function(elem){
4637 			// Accessing this property makes selected-by-default
4638 			// options in Safari work properly
4639 			elem.parentNode.selectedIndex;
4640 			return elem.selected === true;
4641 		},
4642 		parent: function(elem){
4643 			return !!elem.firstChild;
4644 		},
4645 		empty: function(elem){
4646 			return !elem.firstChild;
4647 		},
4648 		has: function(elem, i, match){
4649 			return !!Sizzle( match[3], elem ).length;
4650 		},
4651 		header: function(elem){
4652 			return (/h\d/i).test( elem.nodeName );
4653 		},
4654 		text: function(elem){
4655 			return "text" === elem.type;
4656 		},
4657 		radio: function(elem){
4658 			return "radio" === elem.type;
4659 		},
4660 		checkbox: function(elem){
4661 			return "checkbox" === elem.type;
4662 		},
4663 		file: function(elem){
4664 			return "file" === elem.type;
4665 		},
4666 		password: function(elem){
4667 			return "password" === elem.type;
4668 		},
4669 		submit: function(elem){
4670 			return "submit" === elem.type;
4671 		},
4672 		image: function(elem){
4673 			return "image" === elem.type;
4674 		},
4675 		reset: function(elem){
4676 			return "reset" === elem.type;
4677 		},
4678 		button: function(elem){
4679 			return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
4680 		},
4681 		input: function(elem){
4682 			return (/input|select|textarea|button/i).test(elem.nodeName);
4683 		}
4684 	},
4685 	setFilters: {
4686 		first: function(elem, i){
4687 			return i === 0;
4688 		},
4689 		last: function(elem, i, match, array){
4690 			return i === array.length - 1;
4691 		},
4692 		even: function(elem, i){
4693 			return i % 2 === 0;
4694 		},
4695 		odd: function(elem, i){
4696 			return i % 2 === 1;
4697 		},
4698 		lt: function(elem, i, match){
4699 			return i < match[3] - 0;
4700 		},
4701 		gt: function(elem, i, match){
4702 			return i > match[3] - 0;
4703 		},
4704 		nth: function(elem, i, match){
4705 			return match[3] - 0 === i;
4706 		},
4707 		eq: function(elem, i, match){
4708 			return match[3] - 0 === i;
4709 		}
4710 	},
4711 	filter: {
4712 		PSEUDO: function(elem, match, i, array){
4713 			var name = match[1], filter = Expr.filters[ name ];
4714 
4715 			if ( filter ) {
4716 				return filter( elem, i, match, array );
4717 			} else if ( name === "contains" ) {
4718 				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
4719 			} else if ( name === "not" ) {
4720 				var not = match[3];
4721 
4722 				for ( var j = 0, l = not.length; j < l; j++ ) {
4723 					if ( not[j] === elem ) {
4724 						return false;
4725 					}
4726 				}
4727 
4728 				return true;
4729 			} else {
4730 				Sizzle.error( "Syntax error, unrecognized expression: " + name );
4731 			}
4732 		},
4733 		CHILD: function(elem, match){
4734 			var type = match[1], node = elem;
4735 			switch (type) {
4736 				case 'only':
4737 				case 'first':
4738 					while ( (node = node.previousSibling) )	 {
4739 						if ( node.nodeType === 1 ) { 
4740 							return false; 
4741 						}
4742 					}
4743 					if ( type === "first" ) { 
4744 						return true; 
4745 					}
4746 					node = elem;
4747 				case 'last':
4748 					while ( (node = node.nextSibling) )	 {
4749 						if ( node.nodeType === 1 ) { 
4750 							return false; 
4751 						}
4752 					}
4753 					return true;
4754 				case 'nth':
4755 					var first = match[2], last = match[3];
4756 
4757 					if ( first === 1 && last === 0 ) {
4758 						return true;
4759 					}
4760 					
4761 					var doneName = match[0],
4762 						parent = elem.parentNode;
4763 	
4764 					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
4765 						var count = 0;
4766 						for ( node = parent.firstChild; node; node = node.nextSibling ) {
4767 							if ( node.nodeType === 1 ) {
4768 								node.nodeIndex = ++count;
4769 							}
4770 						} 
4771 						parent.sizcache = doneName;
4772 					}
4773 					
4774 					var diff = elem.nodeIndex - last;
4775 					if ( first === 0 ) {
4776 						return diff === 0;
4777 					} else {
4778 						return ( diff % first === 0 && diff / first >= 0 );
4779 					}
4780 			}
4781 		},
4782 		ID: function(elem, match){
4783 			return elem.nodeType === 1 && elem.getAttribute("id") === match;
4784 		},
4785 		TAG: function(elem, match){
4786 			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
4787 		},
4788 		CLASS: function(elem, match){
4789 			return (" " + (elem.className || elem.getAttribute("class")) + " ")
4790 				.indexOf( match ) > -1;
4791 		},
4792 		ATTR: function(elem, match){
4793 			var name = match[1],
4794 				result = Expr.attrHandle[ name ] ?
4795 					Expr.attrHandle[ name ]( elem ) :
4796 					elem[ name ] != null ?
4797 						elem[ name ] :
4798 						elem.getAttribute( name ),
4799 				value = result + "",
4800 				type = match[2],
4801 				check = match[4];
4802 
4803 			return result == null ?
4804 				type === "!=" :
4805 				type === "=" ?
4806 				value === check :
4807 				type === "*=" ?
4808 				value.indexOf(check) >= 0 :
4809 				type === "~=" ?
4810 				(" " + value + " ").indexOf(check) >= 0 :
4811 				!check ?
4812 				value && result !== false :
4813 				type === "!=" ?
4814 				value !== check :
4815 				type === "^=" ?
4816 				value.indexOf(check) === 0 :
4817 				type === "$=" ?
4818 				value.substr(value.length - check.length) === check :
4819 				type === "|=" ?
4820 				value === check || value.substr(0, check.length + 1) === check + "-" :
4821 				false;
4822 		},
4823 		POS: function(elem, match, i, array){
4824 			var name = match[2], filter = Expr.setFilters[ name ];
4825 
4826 			if ( filter ) {
4827 				return filter( elem, i, match, array );
4828 			}
4829 		}
4830 	}
4831 };
4832 
4833 var origPOS = Expr.match.POS,
4834 	fescape = function(all, num){
4835 		return "\\" + (num - 0 + 1);
4836 	};
4837 
4838 for ( var type in Expr.match ) {
4839 	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
4840 	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
4841 }
4842 
4843 var makeArray = function(array, results) {
4844 	array = Array.prototype.slice.call( array, 0 );
4845 
4846 	if ( results ) {
4847 		results.push.apply( results, array );
4848 		return results;
4849 	}
4850 	
4851 	return array;
4852 };
4853 
4854 // Perform a simple check to determine if the browser is capable of
4855 // converting a NodeList to an array using builtin methods.
4856 // Also verifies that the returned array holds DOM nodes
4857 // (which is not the case in the Blackberry browser)
4858 try {
4859 	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
4860 
4861 // Provide a fallback method if it does not work
4862 } catch(e){
4863 	makeArray = function(array, results) {
4864 		var ret = results || [], i = 0;
4865 
4866 		if ( toString.call(array) === "[object Array]" ) {
4867 			Array.prototype.push.apply( ret, array );
4868 		} else {
4869 			if ( typeof array.length === "number" ) {
4870 				for ( var l = array.length; i < l; i++ ) {
4871 					ret.push( array[i] );
4872 				}
4873 			} else {
4874 				for ( ; array[i]; i++ ) {
4875 					ret.push( array[i] );
4876 				}
4877 			}
4878 		}
4879 
4880 		return ret;
4881 	};
4882 }
4883 
4884 var sortOrder;
4885 
4886 if ( document.documentElement.compareDocumentPosition ) {
4887 	sortOrder = function( a, b ) {
4888 		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
4889 			if ( a == b ) {
4890 				hasDuplicate = true;
4891 			}
4892 			return a.compareDocumentPosition ? -1 : 1;
4893 		}
4894 
4895 		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
4896 		if ( ret === 0 ) {
4897 			hasDuplicate = true;
4898 		}
4899 		return ret;
4900 	};
4901 } else if ( "sourceIndex" in document.documentElement ) {
4902 	sortOrder = function( a, b ) {
4903 		if ( !a.sourceIndex || !b.sourceIndex ) {
4904 			if ( a == b ) {
4905 				hasDuplicate = true;
4906 			}
4907 			return a.sourceIndex ? -1 : 1;
4908 		}
4909 
4910 		var ret = a.sourceIndex - b.sourceIndex;
4911 		if ( ret === 0 ) {
4912 			hasDuplicate = true;
4913 		}
4914 		return ret;
4915 	};
4916 } else if ( document.createRange ) {
4917 	sortOrder = function( a, b ) {
4918 		if ( !a.ownerDocument || !b.ownerDocument ) {
4919 			if ( a == b ) {
4920 				hasDuplicate = true;
4921 			}
4922 			return a.ownerDocument ? -1 : 1;
4923 		}
4924 
4925 		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
4926 		aRange.setStart(a, 0);
4927 		aRange.setEnd(a, 0);
4928 		bRange.setStart(b, 0);
4929 		bRange.setEnd(b, 0);
4930 		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
4931 		if ( ret === 0 ) {
4932 			hasDuplicate = true;
4933 		}
4934 		return ret;
4935 	};
4936 }
4937 
4938 // Utility function for retreiving the text value of an array of DOM nodes
4939 Sizzle.getText = function( elems ) {
4940 	var ret = "", elem;
4941 
4942 	for ( var i = 0; elems[i]; i++ ) {
4943 		elem = elems[i];
4944 
4945 		// Get the text from text nodes and CDATA nodes
4946 		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
4947 			ret += elem.nodeValue;
4948 
4949 		// Traverse everything else, except comment nodes
4950 		} else if ( elem.nodeType !== 8 ) {
4951 			ret += Sizzle.getText( elem.childNodes );
4952 		}
4953 	}
4954 
4955 	return ret;
4956 };
4957 
4958 // Check to see if the browser returns elements by name when
4959 // querying by getElementById (and provide a workaround)
4960 (function(){
4961 	// We're going to inject a fake input element with a specified name
4962 	var form = document.createElement("div"),
4963 		id = "script" + (new Date()).getTime();
4964 	form.innerHTML = "<a name='" + id + "'/>";
4965 
4966 	// Inject it into the root element, check its status, and remove it quickly
4967 	var root = document.documentElement;
4968 	root.insertBefore( form, root.firstChild );
4969 
4970 	// The workaround has to do additional checks after a getElementById
4971 	// Which slows things down for other browsers (hence the branching)
4972 	if ( document.getElementById( id ) ) {
4973 		Expr.find.ID = function(match, context, isXML){
4974 			if ( typeof context.getElementById !== "undefined" && !isXML ) {
4975 				var m = context.getElementById(match[1]);
4976 				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
4977 			}
4978 		};
4979 
4980 		Expr.filter.ID = function(elem, match){
4981 			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
4982 			return elem.nodeType === 1 && node && node.nodeValue === match;
4983 		};
4984 	}
4985 
4986 	root.removeChild( form );
4987 	root = form = null; // release memory in IE
4988 })();
4989 
4990 (function(){
4991 	// Check to see if the browser returns only elements
4992 	// when doing getElementsByTagName("*")
4993 
4994 	// Create a fake element
4995 	var div = document.createElement("div");
4996 	div.appendChild( document.createComment("") );
4997 
4998 	// Make sure no comments are found
4999 	if ( div.getElementsByTagName("*").length > 0 ) {
5000 		Expr.find.TAG = function(match, context){
5001 			var results = context.getElementsByTagName(match[1]);
5002 
5003 			// Filter out possible comments
5004 			if ( match[1] === "*" ) {
5005 				var tmp = [];
5006 
5007 				for ( var i = 0; results[i]; i++ ) {
5008 					if ( results[i].nodeType === 1 ) {
5009 						tmp.push( results[i] );
5010 					}
5011 				}
5012 
5013 				results = tmp;
5014 			}
5015 
5016 			return results;
5017 		};
5018 	}
5019 
5020 	// Check to see if an attribute returns normalized href attributes
5021 	div.innerHTML = "<a href='#'></a>";
5022 	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
5023 			div.firstChild.getAttribute("href") !== "#" ) {
5024 		Expr.attrHandle.href = function(elem){
5025 			return elem.getAttribute("href", 2);
5026 		};
5027 	}
5028 
5029 	div = null; // release memory in IE
5030 })();
5031 
5032 if ( document.querySelectorAll ) {
5033 	(function(){
5034 		var oldSizzle = Sizzle, div = document.createElement("div");
5035 		div.innerHTML = "<p class='TEST'></p>";
5036 
5037 		// Safari can't handle uppercase or unicode characters when
5038 		// in quirks mode.
5039 		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
5040 			return;
5041 		}
5042 	
5043 		Sizzle = function(query, context, extra, seed){
5044 			context = context || document;
5045 
5046 			// Only use querySelectorAll on non-XML documents
5047 			// (ID selectors don't work in non-HTML documents)
5048 			if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
5049 				try {
5050 					return makeArray( context.querySelectorAll(query), extra );
5051 				} catch(e){}
5052 			}
5053 		
5054 			return oldSizzle(query, context, extra, seed);
5055 		};
5056 
5057 		for ( var prop in oldSizzle ) {
5058 			Sizzle[ prop ] = oldSizzle[ prop ];
5059 		}
5060 
5061 		div = null; // release memory in IE
5062 	})();
5063 }
5064 
5065 (function(){
5066 	var div = document.createElement("div");
5067 
5068 	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
5069 
5070 	// Opera can't find a second classname (in 9.6)
5071 	// Also, make sure that getElementsByClassName actually exists
5072 	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
5073 		return;
5074 	}
5075 
5076 	// Safari caches class attributes, doesn't catch changes (in 3.2)
5077 	div.lastChild.className = "e";
5078 
5079 	if ( div.getElementsByClassName("e").length === 1 ) {
5080 		return;
5081 	}
5082 	
5083 	Expr.order.splice(1, 0, "CLASS");
5084 	Expr.find.CLASS = function(match, context, isXML) {
5085 		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
5086 			return context.getElementsByClassName(match[1]);
5087 		}
5088 	};
5089 
5090 	div = null; // release memory in IE
5091 })();
5092 
5093 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
5094 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
5095 		var elem = checkSet[i];
5096 		if ( elem ) {
5097 			elem = elem[dir];
5098 			var match = false;
5099 
5100 			while ( elem ) {
5101 				if ( elem.sizcache === doneName ) {
5102 					match = checkSet[elem.sizset];
5103 					break;
5104 				}
5105 
5106 				if ( elem.nodeType === 1 && !isXML ){
5107 					elem.sizcache = doneName;
5108 					elem.sizset = i;
5109 				}
5110 
5111 				if ( elem.nodeName.toLowerCase() === cur ) {
5112 					match = elem;
5113 					break;
5114 				}
5115 
5116 				elem = elem[dir];
5117 			}
5118 
5119 			checkSet[i] = match;
5120 		}
5121 	}
5122 }
5123 
5124 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
5125 	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
5126 		var elem = checkSet[i];
5127 		if ( elem ) {
5128 			elem = elem[dir];
5129 			var match = false;
5130 
5131 			while ( elem ) {
5132 				if ( elem.sizcache === doneName ) {
5133 					match = checkSet[elem.sizset];
5134 					break;
5135 				}
5136 
5137 				if ( elem.nodeType === 1 ) {
5138 					if ( !isXML ) {
5139 						elem.sizcache = doneName;
5140 						elem.sizset = i;
5141 					}
5142 					if ( typeof cur !== "string" ) {
5143 						if ( elem === cur ) {
5144 							match = true;
5145 							break;
5146 						}
5147 
5148 					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
5149 						match = elem;
5150 						break;
5151 					}
5152 				}
5153 
5154 				elem = elem[dir];
5155 			}
5156 
5157 			checkSet[i] = match;
5158 		}
5159 	}
5160 }
5161 
5162 Sizzle.contains = document.compareDocumentPosition ? function(a, b){
5163 	return !!(a.compareDocumentPosition(b) & 16);
5164 } : function(a, b){
5165 	return a !== b && (a.contains ? a.contains(b) : true);
5166 };
5167 
5168 Sizzle.isXML = function(elem){
5169 	// documentElement is verified for cases where it doesn't yet exist
5170 	// (such as loading iframes in IE - #4833) 
5171 	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
5172 	return documentElement ? documentElement.nodeName !== "HTML" : false;
5173 };
5174 
5175 var posProcess = function(selector, context){
5176 	var tmpSet = [], later = "", match,
5177 		root = context.nodeType ? [context] : context;
5178 
5179 	// Position selectors must be done after the filter
5180 	// And so must :not(positional) so we move all PSEUDOs to the end
5181 	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
5182 		later += match[0];
5183 		selector = selector.replace( Expr.match.PSEUDO, "" );
5184 	}
5185 
5186 	selector = Expr.relative[selector] ? selector + "*" : selector;
5187 
5188 	for ( var i = 0, l = root.length; i < l; i++ ) {
5189 		Sizzle( selector, root[i], tmpSet );
5190 	}
5191 
5192 	return Sizzle.filter( later, tmpSet );
5193 };
5194 
5195 // Add Sizzle to Glow
5196 // This file is injected into sizzle.js by the ant "deps" target
5197 Glow.provide(function(glow) {
5198 	glow._sizzle = Sizzle;
5199 });
5200 
5201 return;
5202 
5203 
5204 window.Sizzle = Sizzle;
5205 
5206 })();
5207 Glow.provide(function(glow) {
5208 	var NodeListProto = glow.NodeList.prototype
5209 	/*
5210 		PrivateVar: ucheck
5211 			Used by unique(), increased by 1 on each use
5212 		*/
5213 		,	ucheck = 1
5214 	/*
5215 		PrivateVar: ucheckPropName
5216 			This is the property name used by unique checks
5217 		*/
5218 
5219 	, ucheckPropName = "_unique" + glow.UID;
5220 	/*
5221 		PrivateMethod: unique
5222 			Get an array of nodes without duplicate nodes from an array of nodes.
5223 
5224 		Arguments:
5225 			aNodes - (Array|<NodeList>)
5226 
5227 		Returns:
5228 			An array of nodes without duplicates.
5229 		*/
5230 		//worth checking if it's an XML document?
5231 		if (glow.env.ie) {
5232 			var unique = function(aNodes) {
5233 				if (aNodes.length == 1) { return aNodes; }
5234 
5235 				//remove duplicates
5236 				var r = [],
5237 					ri = 0,
5238 					i = 0;
5239 
5240 				for (; aNodes[i]; i++) {
5241 					if (aNodes[i].getAttribute(ucheckPropName) != ucheck && aNodes[i].nodeType == 1) {
5242 						r[ri++] = aNodes[i];
5243 					}
5244 					aNodes[i].setAttribute(ucheckPropName, ucheck);
5245 				}
5246 				for (i=0; aNodes[i]; i++) {
5247 					aNodes[i].removeAttribute(ucheckPropName);
5248 				}
5249 				ucheck++;
5250 				return r;
5251 			}
5252 		} else {
5253 			var unique = function(aNodes) {
5254 				if (aNodes.length == 1) { return aNodes; }
5255 
5256 				//remove duplicates
5257 				var r = [],
5258 					ri = 0,
5259 					i = 0;
5260 
5261 				for (; aNodes[i]; i++) {
5262 					if (aNodes[i][ucheckPropName] != ucheck && aNodes[i].nodeType == 1) {
5263 						r[ri++] = aNodes[i];
5264 					}
5265 					aNodes[i][ucheckPropName] = ucheck;
5266 				}
5267 				ucheck++;
5268 				return r;
5269 			}
5270 		};
5271 	/**
5272 	@name glow.NodeList#parent
5273 	@function
5274 	@description Gets the unique parent nodes of each node as a new NodeList.
5275 	@param {string | HTMLElement | NodeList} [search] Search value
5276 		If provided, will seek the next parent element until a match is found
5277 	@returns {glow.NodeList}
5278 
5279 		Returns a new NodeList containing the parent nodes, with
5280 		duplicates removed
5281 
5282 	@example
5283 		// elements which contain links
5284 		var parents = glow.dom.get("a").parent();
5285 	*/
5286 	NodeListProto.parent = function(search) {
5287 		var ret = [],
5288 			ri = 0,
5289 			i = this.length,
5290 			node;
5291 			
5292 		while (i--) {				
5293 			node = this[i];
5294 			if (node.nodeType == 1) {
5295 				if(search){						
5296 					while(node = node.parentNode){											
5297 						if (glow._sizzle.filter(search, [node]).length) {
5298 							ret[ri++] = node;							
5299 							break;
5300 						}							
5301 					}
5302 				}
5303 			
5304 				else if(node = node.parentNode){
5305 						ret[ri++] = node;						
5306 				}
5307 
5308 			}
5309 
5310 		}
5311 				
5312 		return new glow.NodeList(unique(ret));			
5313 	};
5314 	
5315 	/* Private method for prev() and next() */
5316 	function getNextOrPrev(nodelist, dir, search) {
5317 		var ret = [],
5318 			ri = 0,
5319 			node,
5320 			i = 0,
5321 			length = nodelist.length;
5322 
5323 		while (i < length) {			
5324 			node = nodelist[i];			
5325 			if(search){
5326 				while (node = node[dir + 'Sibling']) {					
5327 					if (node.nodeType == 1 && node.nodeName != '!') {						
5328 						if (glow._sizzle.filter(search, [node]).length) {
5329 							ret[ri++] = node;							
5330 							break;
5331 						}					
5332 					}					
5333 				}
5334 			}
5335 			else{
5336 				while (node = node[dir + 'Sibling']) {					
5337 					if (node.nodeType == 1 && node.nodeName != '!') {
5338 							ret[ri++] = node;							
5339 							 break;					
5340 					}					
5341 				}	
5342 			}
5343 		i++;
5344 		}
5345 		return new glow.NodeList(ret);
5346 	}
5347 	
5348 	/**
5349 	@name glow.NodeList#prev
5350 	@function
5351 	@description Gets the previous sibling element for each node in the ElementList.
5352 		If a filter is provided, the previous item that matches the filter is returned, or
5353 		none if no match is found.
5354 	@param {string | HTMLElement | NodeList} [search] Search value
5355 		If provided, will seek the previous sibling element until a match is found
5356 	@returns {glow.ElementList}
5357 		A new ElementList containing the previous sibling elements that match the (optional)
5358 		filter.
5359 	@example
5360 		// gets the element before #myLink (if there is one)
5361 		var next = glow.get("#myLink").prev();
5362 	@example
5363 		// get the previous sibling link element before #skipLink
5364 		glow.get('#skipLink').prev('a')
5365 	*/
5366 	NodeListProto.prev = function(search) {
5367 		return getNextOrPrev(this, 'previous', search);
5368 	};
5369 	
5370 	/**
5371 	@name glow.NodeList#next
5372 	@function
5373 	@description Gets the next sibling element for each node in the ElementList.
5374 		If a filter is provided, the next item that matches the filter is returned, or
5375 		none if no match is found.
5376 	@param {string | HTMLElement | NodeList} [search] Search value
5377 		If provided, will seek the next sibling element until a match is found
5378 	@returns {glow.ElementList}
5379 		A new ElementList containing the next sibling elements that match the (optional)
5380 		filter.
5381 	@example
5382 		// gets the element following #myLink (if there is one)
5383 		var next = glow.get("#myLink").next();
5384 	@example
5385 		// get the next sibling link element after #skipLink
5386 		glow.get('#skipLink').next('a')
5387 	*/
5388 	NodeListProto.next = function(search) {
5389 		return getNextOrPrev(this, 'next', search);	
5390 	};
5391 	
5392 	
5393 	/**
5394 	@name glow.NodeList#get
5395 	@function
5396 	@description Gets decendents of nodes that match a CSS selector.
5397 
5398 	@param {String} selector CSS selector
5399 
5400 	@returns {glow.NodeList}
5401 		Returns a new NodeList containing matched elements
5402 
5403 	@example
5404 		// create a new NodeList
5405 		var myNodeList = glow.dom.create("<div><a href='s.html'>Link</a></div>");
5406 
5407 		// get 'a' tags that are decendants of the NodeList nodes
5408 		myNewNodeList = myNodeList.get("a");
5409 	*/
5410 	NodeListProto.get = function(selector) {
5411 		var ret = [],
5412 			i = this.length;
5413 
5414 		while (i--) {			
5415 			glow._sizzle(selector, this[i], ret);
5416 			
5417 		}
5418 		// need to remove uniqueSorts because they're slow. Replace with own method for unique.
5419 		return new glow.NodeList(unique(ret));
5420 	};
5421 	
5422 	
5423 	
5424 	/**
5425 	@name glow.NodeList#ancestors
5426 	@function
5427 	@description Gets the unique ancestor nodes of each node as a new NodeList.
5428 	@param {Function|string} [filter] Filter test
5429 		If a string is provided, it is used in a call to {@link glow.ElementList#is ElementList#is}.
5430 		If a function is provided it will be passed 2 arguments, the index of the current item,
5431 		and the ElementList being itterated over.
5432 		Inside the function 'this' refers to the HTMLElement.
5433 		Return true to keep the node, or false to remove it.
5434 	@returns {glow.dom.NodeList}
5435 		Returns NodeList
5436 
5437 		@example
5438 		// get ancestor elements for anchor elements 
5439 		var ancestors = glow.dom.get("a").ancestors();
5440 	*/
5441 	NodeListProto.ancestors = function(filter) {
5442 		var ret = [],
5443 			ri = 0,
5444 			i = 0,
5445 			length = this.length,
5446 			node;
5447 					
5448 		while (i < length) {
5449 			node = this[i].parentNode;
5450 					
5451 			while (node && node.nodeType == 1) {							
5452 				ret[ri++] = node;
5453 				node = node.parentNode;
5454 			}								
5455 		i++;
5456 		}
5457 		if(filter){
5458             ret = new glow.NodeList(ret);
5459 			ret = ret.filter(filter);
5460 		}
5461 		return new glow.NodeList(unique(ret));
5462 	};
5463 	
5464 	/*
5465 		Private method to get the child elements for an html node (used by children())
5466 	*/
5467 		function getChildElms(node) {
5468 			var r = [],
5469 				childNodes = node.childNodes,
5470 				i = 0,
5471 				ri = 0;
5472 			
5473 			for (; childNodes[i]; i++) {
5474 				if (childNodes[i].nodeType == 1 && childNodes[i].nodeName != '!') {
5475 					r[ri++] = childNodes[i];
5476 				}
5477 			}
5478 			return r;
5479 		}
5480 	
5481 	/**
5482 	@name glow.NodeList#children
5483 	@function
5484 	@description Gets the child elements of each node as a new NodeList.
5485 
5486 	@returns {glow.dom.NodeList}
5487 
5488 		Returns a new NodeList containing all the child nodes
5489 				
5490 	@example
5491 		// get all list items
5492 		var items = glow.dom.get("ul, ol").children();
5493 	*/
5494 	NodeListProto.children = function() {
5495 		var ret = [],
5496 			i = this.length;
5497 				
5498 		while(i--) {
5499 			ret = ret.concat( getChildElms(this[i]) );
5500 		}
5501 		return new glow.NodeList(ret);	
5502 	};
5503 	
5504 	/**
5505 	@name glow.NodeList#contains
5506 	@function
5507 	@description Find if this NodeList contains the given element
5508 		
5509 	@param {string | HTMLELement | NodeList} Single element to check for
5510 
5511 	@returns {boolean}
5512 		myElementList.contains(elm)
5513 		// Returns true if an element in myElementList contains elm, or IS elm.
5514 	*/
5515 	NodeListProto.contains = function(elm) {
5516 		var i = 0,
5517 			node = new glow.NodeList(elm)[0],
5518 			length = this.length,
5519 			newNodes,
5520 			toTest;
5521 
5522 		// missing some nodes? Return false
5523 		if ( !node || !this.length ) {
5524 			return false;
5525 		}
5526 	
5527 		if (this[0].compareDocumentPosition) { //w3 method
5528 			while (i < length) {
5529 				//break out if the two are teh same
5530 				if(this[i] == node){
5531 					break;
5532 				}
5533 				//check against bitwise to see if node is contained in this
5534 				else if (!(this[i].compareDocumentPosition(node) & 16)) {								
5535 					return false;
5536 				}
5537 			i++;
5538 			}
5539 		}
5540 		else if(node.contains){					
5541 			for (; i < length; i++) {
5542 				if ( !( this[i].contains( node  ) ) ) {
5543 					return false;
5544 				}
5545 			}
5546 		}				
5547 		else { //manual method for last chance corale
5548 			while (i < length) {
5549 				toTest = node;
5550 				while (toTest = toTest.parentNode) {
5551 					if (this[i] == toTest) { break; }
5552 				}
5553 				if (!toTest) {
5554 					return false;
5555 				}
5556 			i++;
5557 			}
5558 		}
5559 			
5560 		return true;
5561 	};
5562 });
5563 Glow.provide(function(glow) {
5564 	var NodeListProto = glow.NodeList.prototype,
5565 		document = window.document,
5566 		undefined;
5567 	
5568 	// create a fragment and insert a set of nodes into it
5569 	function createFragment(nodes) {
5570 		var fragment = document.createDocumentFragment(),
5571 			i = 0,
5572 			node;
5573 		
5574 		while ( node = nodes[i++] ) {
5575 			fragment.appendChild(node);
5576 		}
5577 		
5578 		return fragment;
5579 	}
5580 	
5581 	// generate the #before and #after methods
5582 	// after: 1 for #(insert)after, 0 for #(insert)before
5583 	// insert: 1 for #insert(After|Before), 0 for #(after|before)
5584 	function afterAndBefore(after, insert) {
5585 		return function(elements) {
5586 			var toAddList,
5587 				toAddToList,
5588 				fragmentToAdd,
5589 				nextFragmentToAdd,
5590 				item,
5591 				itemParent;
5592 			
5593 			if (!this.length) { return this; }
5594 			
5595 			// normalise 'elements'
5596 			// if we're dealing with append/prepend then strings are always treated as HTML strings
5597 			if (!insert && typeof elements === 'string') {
5598 				elements = new glow.NodeList( glow.NodeList._strToNodes(elements) );
5599 			}
5600 			else {
5601 				elements = new glow.NodeList(elements);
5602 			}
5603 			
5604 			// set the element we're going to add to, and the elements we're going to add
5605 			if (insert) {
5606 				toAddToList = elements;
5607 				toAddList = new glow.NodeList(this);
5608 			}
5609 			else {
5610 				toAddToList = this;
5611 				toAddList = elements;
5612 			}
5613 			
5614 			nextFragmentToAdd = createFragment(toAddList);
5615 			
5616 			for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) {
5617 				item = toAddToList[i];
5618 				fragmentToAdd = nextFragmentToAdd;
5619 				
5620 				// we can only append after if the element has a parent right?
5621 				if (itemParent = item.parentNode) {
5622 					if (i !== lasti) { // if not the last item
5623 						nextFragmentToAdd = fragmentToAdd.cloneNode(true);
5624 						insert && toAddList.push(nextFragmentToAdd.childNodes);
5625 					}
5626 					itemParent.insertBefore(fragmentToAdd, after ? item.nextSibling : item);
5627 				}
5628 			}
5629 			
5630 			return insert ? toAddList : toAddToList;
5631 		}
5632 	}
5633 	
5634 	// generate the #append, #appendTo, #prepend and #prependTo methods
5635 	// append: 1 for #append(To), 0 for #prepend(To)
5636 	// to: 1 for #(append|prepend)To, 0 for #(append|prepend)
5637 	function appendAndPrepend(append, to) {
5638 		return function(elements) {
5639 			var toAddList,
5640 				toAddToList,
5641 				fragmentToAdd,
5642 				nextFragmentToAdd,
5643 				item;
5644 			
5645 			if (!this.length) { return this; }
5646 			
5647 			// normalise 'elements'
5648 			// if we're dealing with append/prepend then strings are always treated as HTML strings
5649 			if (!to && typeof elements === 'string') {
5650 				elements = new glow.NodeList( glow.NodeList._strToNodes(elements) );
5651 			}
5652 			else {
5653 				elements = new glow.NodeList(elements);
5654 			}
5655 				
5656 			// set the element we're going to add to, and the elements we're going to add
5657 			if (to) {
5658 				toAddToList = elements;
5659 				toAddList = new glow.NodeList(this);
5660 			}
5661 			else {
5662 				toAddToList = this;
5663 				toAddList = elements;
5664 			}
5665 			
5666 			nextFragmentToAdd = createFragment(toAddList);
5667 			
5668 			for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) {
5669 				item = toAddToList[i];
5670 				fragmentToAdd = nextFragmentToAdd;
5671 				
5672 				// avoid trying to append to non-elements
5673 				if (item.nodeType === 1) {
5674 					if (i !== lasti) { // if not the last item
5675 						nextFragmentToAdd = fragmentToAdd.cloneNode(true);
5676 						// add the clones to the return element for appendTo / prependTo
5677 						to && toAddList.push(nextFragmentToAdd.childNodes);
5678 					}
5679 					item.insertBefore(fragmentToAdd, append ? null : item.firstChild);
5680 				}
5681 			}
5682 			
5683 			return to ? toAddList : toAddToList;
5684 		}
5685 	}
5686 	
5687 	/**
5688 		@name glow.NodeList#after
5689 		@function
5690 		@description Insert node(s) after each node in this NodeList.
5691 			If there is more than one node in this NodeList, 'nodes'
5692 			will be inserted after the first element and clones will be
5693 			inserted after each subsequent element.
5694 			
5695 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert
5696 			Strings will be treated as HTML strings.
5697 		
5698 		@returns {glow.NodeList} Original NodeList
5699 		
5700 		@example
5701 			// adds a paragraph after each heading
5702 			glow('h1, h2, h3').after('<p>That was a nice heading.</p>');
5703 	*/
5704 	NodeListProto.after = afterAndBefore(1);
5705 	
5706 	/**
5707 		@name glow.NodeList#before
5708 		@function
5709 		@description Insert node(s) before each node in this NodeList.
5710 			If there is more than one node in this NodeList, 'nodes'
5711 			will be inserted before the first element and clones will be
5712 			inserted before each subsequent element.
5713 			
5714 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert
5715 			Strings will be treated as HTML strings.
5716 		
5717 		@returns {glow.NodeList} Original NodeList
5718 		
5719 		@example
5720 			// adds a div before each paragraph
5721 			glow('p').before('<div>Here comes a paragraph!</div>');
5722 	*/
5723 	NodeListProto.before = afterAndBefore(0);
5724 	
5725 	/**
5726 		@name glow.NodeList#append
5727 		@function
5728 		@description Appends node to each node in this NodeList.
5729 			If there is more than one node in this NodeList, then the given nodes
5730 			are appended to the first node and clones are appended to the other
5731 			nodes.
5732 			
5733 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to append
5734 			Strings will be treated as HTML strings.
5735 		
5736 		@returns {glow.NodeList} Original NodeList
5737 		
5738 		@example
5739 			// ends every paragraph with '...'
5740 			glow('p').append('<span>...</span>');
5741 	*/
5742 	NodeListProto.append = appendAndPrepend(1);
5743 	
5744 	/**
5745 		@name glow.NodeList#prepend
5746 		@function
5747 		@description Prepends nodes to each node in this NodeList.
5748 			If there is more than one node in this NodeList, then the given nodes
5749 			are prepended to the first node and clones are prepended to the other
5750 			nodes.
5751 			
5752 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to prepend
5753 			Strings will be treated as HTML strings.
5754 		
5755 		@returns {glow.NodeList} Original NodeList
5756 		
5757 		@example
5758 			// prepends every paragraph with 'Paragraph: '
5759 			glow('p').prepend('<span>Paragraph: </span>');
5760 	*/
5761 	NodeListProto.prepend = appendAndPrepend(0);
5762 	
5763 	/**
5764 		@name glow.NodeList#appendTo
5765 		@function
5766 		@description Appends nodes in this NodeList to given node(s)
5767 			If appending to more than one node, the NodeList is appended
5768 			to the first node and clones are appended to the others.
5769 			
5770 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to append to.
5771 			Strings will be treated as CSS selectors or HTML strings.
5772 		
5773 		@returns {glow.NodeList} The appended nodes.
5774 		
5775 		@example
5776 			// appends '...' to every paragraph
5777 			glow('<span>...</span>').appendTo('p');
5778 	*/
5779 	NodeListProto.appendTo = appendAndPrepend(1, 1);
5780 
5781 	/**
5782 		@name glow.NodeList#prependTo
5783 		@function
5784 		@description Prepends nodes in this NodeList to given node(s)
5785 			If prepending to more than one node, the NodeList is prepended
5786 			to the first node and clones are prepended to the others.
5787 			
5788 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to prepend to
5789 			Strings will be treated as CSS selectors or HTML strings.
5790 		
5791 		@returns {glow.NodeList} The prepended nodes.
5792 		
5793 		@example
5794 			// prepends 'Paragraph: ' to every paragraph
5795 			glow('<span>Paragraph: </span>').prependTo('p');
5796 	*/
5797 	NodeListProto.prependTo = appendAndPrepend(0, 1);
5798 	
5799 	/**
5800 		@name glow.NodeList#insertAfter
5801 		@function
5802 		@description Insert this NodeList after the given nodes
5803 			If inserting after more than one node, the NodeList is inserted
5804 			after the first node and clones are inserted after the others.
5805 			
5806 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert after
5807 			Strings will be treated as CSS selectors.
5808 			
5809 		@returns {glow.NodeList} Inserted nodes.
5810 		
5811 		@example
5812 			// adds a paragraph after each heading
5813 			glow('<p>HAI!</p>').insertAfter('h1, h2, h3');
5814 	*/
5815 	NodeListProto.insertAfter = afterAndBefore(1, 1);
5816 	
5817 	/**
5818 		@name glow.NodeList#insertBefore
5819 		@function
5820 		@description Insert this NodeList before the given nodes
5821 			If inserting before more than one node, the NodeList is inserted
5822 			before the first node and clones are inserted before the others.
5823 			
5824 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert before
5825 			Strings will be treated as CSS selectors.
5826 			
5827 		@returns {glow.NodeList} Inserted nodes.
5828 		
5829 		@example
5830 			// adds a div before each paragraph
5831 			glow('<div>Here comes a paragraph!</div>').insertBefore('p');
5832 	*/
5833 	NodeListProto.insertBefore = afterAndBefore(0, 1);
5834 	
5835 	/**
5836 		@name glow.NodeList#destroy
5837 		@function
5838 		@description Removes each element from the document
5839 			The element, attached listeners & attached data will be
5840 			destroyed to free up memory.
5841 			
5842 			Detroyed elements may not be reused in some browsers.
5843 			
5844 		@returns undefined
5845 		
5846 		@example
5847 			// destroy all links in the document
5848 			glow("a").destroy();
5849 	*/
5850 	var tmpDiv = document.createElement('div');
5851 	
5852 	NodeListProto.destroy = function() {
5853 		var allElements = this.get('*').push(this);
5854 		
5855 		// remove data and listeners
5856 		glow.events.removeAllListeners( allElements.removeData() );
5857 		
5858 		this.appendTo(tmpDiv);
5859 		tmpDiv.innerHTML = '';
5860 	};
5861 	
5862 	/**
5863 		@name glow.NodeList#remove
5864 		@function
5865 		@description Removes each element from the document
5866 			If you no longer need the elements, consider using
5867 			{@link glow.NodeList#destroy destroy}
5868 			
5869 		@returns {glow.NodeList} The removed elements
5870 
5871 		@example
5872 			// take all the links out of a document
5873 			glow("a").remove();
5874 	*/
5875 	NodeListProto.remove = function() {
5876 		var parent,
5877 			node,
5878 			i = this.length;
5879 		
5880 		while (i--) {
5881 			node = this[i];
5882 			if (parent = node.parentNode) {
5883 				parent.removeChild(node);
5884 			}
5885 		}
5886 		
5887 		return this;
5888 	};
5889 	
5890 	/**
5891 		@name glow.NodeList#empty
5892 		@function
5893 		@description Removes the nodes' contents
5894 
5895 		@returns {glow.NodeList} Original nodes
5896 
5897 		@example
5898 			// remove the contents of all textareas
5899 			glow("textarea").empty();
5900 	*/
5901 	// TODO: is this shortcut worth doing?
5902 	NodeListProto.empty = glow.env.ie ?
5903 		// When you clean an element out using innerHTML it destroys its inner text nodes in IE8 and below
5904 		// Here's an alternative method for IE:
5905 		function() {
5906 			var i = this.length, node, child;
5907 			
5908 			while (i--) {
5909 				node = this[i];
5910 				while (child = node.firstChild) {
5911 					node.removeChild(child);
5912 				}
5913 			}
5914 			
5915 			return this;
5916 		} :
5917 		// method for most browsers
5918 		function() {
5919 			var i = this.length;
5920 			
5921 			while (i--) {
5922 				this[i].innerHTML = '';
5923 			}
5924 			
5925 			return this;
5926 		}
5927 
5928 	/**
5929 		@name glow.NodeList#replaceWith
5930 		@function
5931 		@description Replace elements with another
5932 		
5933 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} elements Element(s) to insert into the document
5934 			If there is more than one element in the NodeList, then the given elements
5935 			replace the first element, clones are appended to the other	elements.
5936 			
5937 		@returns {glow.NodeList} The replaced elements
5938 			Call {@link glow.NodeList#destroy destroy} on these if you
5939 			no longer need them.
5940 	*/
5941 	NodeListProto.replaceWith = function(elements) {
5942 		return this.after(elements).remove();
5943 	};
5944 	
5945 	/**
5946 		@name glow.NodeList#wrap
5947 		@function
5948 		@description Wraps the given NodeList with the specified element(s).
5949 			The given NodeList items will always be placed in the first
5950 			child element that contains no further elements.
5951 			
5952 			Each item in a given NodeList will be wrapped individually.
5953 		
5954 		@param {string | HTMLElement | HTMLElement[] | glow.NodeList} wrapper Element to use as a wrapper
5955 			Strings will be treated as HTML strings if they begin with <, else
5956 			they'll be treated as a CSS selector.
5957 		
5958 		@returns {glow.NodeList} The NodeList with new wrapper parents
5959 			
5960 		@example
5961 			// <span id="mySpan">Hello</span>
5962 			glow("#mySpan").wrap("<div><p></p></div>");
5963 			// Makes:
5964 			// <div>
5965 			//     <p>
5966 			//         <span id="mySpan">Hello</span>
5967 			//     </p>
5968 			// </div>
5969 			
5970 	*/
5971 	// get first child element node of an element, otherwise undefined
5972 	function getFirstChildElm(parent) {					
5973 		for (var child = parent.firstChild; child; child = child.nextSibling) {
5974 			if (child.nodeType == 1) {
5975 				return child;
5976 			}			
5977 		}			
5978 		return undefined;			
5979 	}
5980 	
5981 	NodeListProto.wrap = function(wrapper) {
5982 		// normalise input
5983 		wrapper = new glow.NodeList(wrapper);
5984 		
5985 		// escape if the wraper is non-existant or not an element
5986 		if (!wrapper[0] || wrapper[0].nodeType != 1) {
5987 			return this;
5988 		}
5989 		
5990 		var toWrap,
5991 			toWrapTarget,
5992 			firstChildElm;
5993 		
5994 		for (var i = 0, leni = this.length; i<leni; i++) {
5995 			toWrap = this[i];
5996 			// get target element to insert toWrap in
5997 			toWrapTarget = wrapper[0];
5998 			
5999 			while (toWrapTarget) {
6000 				firstChildElm = getFirstChildElm(toWrapTarget);
6001 					
6002 				if (!firstChildElm) {
6003 					break;
6004 				}
6005 				toWrapTarget = firstChildElm;
6006 			}
6007 			
6008 			if (toWrap.parentNode) {						
6009 				wrapper.insertBefore(toWrap);													
6010 			}
6011 			
6012 			// If wrapping multiple nodes, we need to take a clean copy of the wrapping nodes
6013 			if (i != leni-1) {
6014 				wrapper = wrapper.clone();
6015 			}
6016 			
6017 			toWrapTarget.appendChild(toWrap);
6018 		}
6019 		
6020 		return this;
6021 	};
6022 	
6023 	/**
6024 		@name glow.NodeList#unwrap
6025 		@function
6026 		@description Removes the parent of each item in the list
6027 		
6028 		@returns {glow.NodeList} The now unwrapped elements
6029 		
6030 		@example
6031 			// Before: <div><p><span id="mySpan">Hello</span></p></div>
6032 			// unwrap the given element
6033 			glow("#mySpan").unwrap();
6034 			// After: <div><span id="mySpan">Hello</span></div>
6035 	*/
6036 	NodeListProto.unwrap = function() {
6037 		var parentToRemove,
6038 			childNodes,
6039 			// get unique parents
6040 			parentsToRemove = this.parent();
6041 		
6042 		for (var i = 0, leni = parentsToRemove.length; i < leni; i++) {				
6043 			parentToRemove = parentsToRemove.slice(i, i+1);
6044 			// make sure we get all children, including text nodes
6045 			childNodes = new glow.NodeList( parentToRemove[0].childNodes );
6046 			
6047 			// if the item we're removing has no new parent (i.e. is not in document), then we just remove the child and destroy the old parent
6048 			if (!parentToRemove[0].parentNode){
6049 				childNodes.remove();
6050 				parentToRemove.destroy();
6051 			}
6052 			else {
6053 				childNodes.insertBefore(parentToRemove);
6054 				parentToRemove.destroy();							
6055 			}
6056 		}
6057 		return this;
6058 	};
6059 	
6060 	/**
6061 		@name glow.NodeList#clone
6062 		@function
6063 		@description Clones each node in the NodeList, along with data & event listeners
6064 		
6065 		@returns {glow.NodeList}
6066 			Returns a new NodeList containing clones of all the nodes in
6067 			the NodeList
6068 		
6069 		@example
6070 			// get a copy of all heading elements
6071 			var myClones = glow.get("h1, h2, h3, h4, h5, h6").clone();
6072 	*/
6073 	
6074 	NodeListProto.clone = function() {
6075 		var clonedNodeList = this.copy(),
6076 			allCloneElms = clonedNodeList.get('*').push(clonedNodeList),
6077 			allElms = this.get('*').push(this);
6078 		
6079 		// now copy over the data and events for all cloned elements
6080 		glow.events._copyDomEvents(allElms, allCloneElms);
6081 		glow.NodeList._copyData(allElms, allCloneElms);
6082 		
6083 		return clonedNodeList;
6084 	};
6085 	
6086 	
6087 	/**
6088 		@name glow.NodeList#copy
6089 		@function
6090 		@description Copies each node in the NodeList, excluding data & event listeners
6091 		
6092 		@returns {glow.NodeList}
6093 			Returns a new NodeList containing copies of all the nodes in
6094 			the NodeList
6095 		
6096 		@example
6097 			// get a copy of all heading elements
6098 			var myCopies = glow.get("h1, h2, h3, h4, h5, h6").copy();
6099 	*/
6100 	NodeListProto.copy = function() {
6101 		var nodes = [],
6102 			i = this.length,
6103 			clonedNodeList,
6104 			allCloneElms,
6105 			eventIdProp = '__eventId' + glow.UID,
6106 			dataPropName = '_uniqueData' + glow.UID;
6107 		
6108 		while (i--) {
6109 			nodes[i] = this[i].cloneNode(true);
6110 		}
6111 		
6112 		clonedNodeList = new glow.NodeList(nodes);
6113 		
6114 		// IE also clones node properties as attributes
6115 		// we need to get rid of the eventId & dataId
6116 		if (glow.env.ie) {
6117 			allCloneElms = clonedNodeList.get('*').push(nodes);
6118 			i = allCloneElms.length;
6119 			while (i--) {
6120 				allCloneElms[i][dataPropName] = allCloneElms[i][eventIdProp] = undefined;
6121 			}
6122 		}
6123 		
6124 		return clonedNodeList;
6125 	};
6126 	
6127 	/**
6128 		@name glow.NodeList#html
6129 		@function
6130 		@description Gets / sets HTML content
6131 			Either gets content of the first element, or sets the content
6132 			for all elements in the list
6133 			
6134 		@param {String} [htmlString] String to set as the HTML of elements
6135 			If omitted, the html for the first element in the list is
6136 			returned.
6137 		
6138 		@returns {glow.NodeList | string}
6139 			Returns the original NodeList when setting,
6140 			or the HTML content when getting.
6141 			
6142 		@example
6143 			// get the html in #footer
6144 			var footerContents = glow("#footer").html();
6145 			
6146 		@example
6147 			// set a new footer
6148 			glow("#footer").html("<strong>Hello World!</strong>");
6149 	*/
6150 	NodeListProto.html = function(htmlString) {
6151 		// getting
6152 		if (!arguments.length) {
6153 			return this[0] ? this[0].innerHTML : '';
6154 		}
6155 		
6156 		// setting
6157 		var i = this.length,
6158 			node;
6159 		
6160 		// normalise the string
6161 		htmlString = htmlString === undefined? '' : String(htmlString);
6162 		
6163 		while (i--) {
6164 			node = this[i];
6165 			if (node.nodeType == 1) {
6166 				try {
6167 					// this has a habit of failing in IE for some elements
6168 					node.innerHTML = htmlString;
6169 				}
6170 				catch (e) {
6171 					new glow.NodeList(node).empty().append(htmlString);
6172 				}
6173 			}
6174 		}
6175 		
6176 		return this;
6177 	};
6178 	
6179 	/**
6180 		@name glow.NodeList#text
6181 		@function
6182 		@description Gets / set the text content
6183 			Either gets content of the first element, or sets the content
6184 			for all elements in the list
6185 		
6186 		@param {String} [text] String to set as the text of elements
6187 			If omitted, the test for the first element in the list is
6188 			returned.
6189 		
6190 		@returns {glow.NodeList | String}
6191 			Returns the original NodeList when setting,
6192 			or the text content when getting.
6193 
6194 		@example
6195 			// set text
6196 			var div = glow("<div></div>").text("Fun & games!");
6197 			// <div>Func & games!</div>
6198 			
6199 		@example
6200 			// get text
6201 			var mainHeading = glow('#mainHeading').text();
6202 	*/
6203 	NodeListProto.text = function(textString) {
6204 		var firstNode = this[0],
6205 			i = this.length,
6206 			node;
6207 		
6208 		// getting
6209 		if (!arguments.length) {
6210 			// get the text by checking a load of properties in priority order
6211 			return firstNode ?
6212 				firstNode.textContent ||
6213 				firstNode.innerText ||
6214 				firstNode.nodeValue || '' // nodeValue for comment & text nodes
6215 				: '';
6216 		}
6217 		
6218 		// setting
6219 		// normalise the string
6220 		textString = textString ? String(textString): '';
6221 		
6222 		this.empty();
6223 		while (i--) {
6224 			node = this[i];
6225 			if (node.nodeType == 1) {
6226 				node.appendChild( document.createTextNode(textString) );
6227 			}
6228 			else {
6229 				node.nodeValue = textString;
6230 			}
6231 		}
6232 		
6233 		return this;
6234 	};
6235 });
6236 Glow.provide(function(glow) {
6237 	var NodeList = glow.NodeList,
6238 		NodeListProto = NodeList.prototype,
6239 		win = window,
6240 		document = win.document,	
6241 		getComputedStyle = document.defaultView && document.defaultView.getComputedStyle,
6242 		// regex for toStyleProp
6243 		dashAlphaRe = /-(\w)/g,
6244 		// regex for getCssValue
6245 		isNumberButNotPx = /^-?[\d\.]+(?!px)[%a-z]+$/i,
6246 		ieOpacityRe = /alpha\(opacity=([^\)]+)\)/,
6247 		// regex for #css
6248 		hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/;
6249 	
6250 	// replace function for toStyleProp
6251 	function toStylePropReplace(match, p1) {
6252 		return p1.toUpperCase();
6253 	}
6254 	
6255 	/**
6256 		@private
6257 		@function
6258 		@description Converts a css property name into its javascript name.
6259 			Such as "background-color" to "backgroundColor".
6260 		@param {string} prop CSS Property name.
6261 		@returns {string}
6262 	*/
6263 	function toStyleProp(prop) {
6264 		if (prop == 'float') {
6265 			return glow.env.ie ? 'styleFloat' : 'cssFloat';
6266 		}
6267 		return prop.replace(dashAlphaRe, toStylePropReplace);
6268 	}
6269 	
6270 	/**
6271 		@private
6272 		@function
6273 		@description Get a total value of multiple CSS properties
6274 		@param {HTMLElement} elm
6275 		@param {string[]} props CSS properties to get the total value of
6276 		@returns {number}
6277 	*/
6278 	function getTotalCssValue(elm, props) {
6279 		var total = 0,
6280 			i = props.length;
6281 			
6282 		while (i--) {
6283 			total += parseFloatFunc(
6284 				getCssValue( elm, props[i] )
6285 			) || 0;
6286 		}
6287 		
6288 		return total;
6289 	}
6290 	
6291 	/**
6292 		@private
6293 		@function
6294 		@description Get a computed css property
6295 		@param {HTMLElement} elm
6296 		@param {string} prop CSS property to get the value of
6297 		@returns {string}
6298 	*/
6299 	function getCssValue(elm, prop) {
6300 		var defaultView = elm.ownerDocument.defaultView,
6301 			computedStyle,
6302 			r,
6303 			currentStyle,
6304 			oldDisplay,
6305 			match;
6306 		
6307 		if (getComputedStyle) { // the W3 way
6308 			computedStyle = defaultView.getComputedStyle(elm, null);
6309 			
6310 			// http://bugs.webkit.org/show_bug.cgi?id=13343
6311 			// Webkit fails to get margin-right for rendered elements.
6312 			// margin-right is measured from the right of the element to the right of the parent
6313 			if (glow.env.webkit && prop === 'margin-right') {
6314 				oldDisplay = elm.style.display;
6315 				elm.style.display = 'none';
6316 				r = computedStyle[prop];
6317 				elm.style.display = oldDisplay;
6318 			}
6319 			else {
6320 				r = computedStyle.getPropertyValue(prop);
6321 			}
6322 		}
6323 		else if (currentStyle = elm.currentStyle) { // the IE<9 way
6324 			if (prop === 'opacity') { // opacity, the IE way
6325 				match = ieOpacityRe.exec(currentStyle.filter);
6326 				return match ? String(parseInt(match[1], 10) / 100) || '1' : '1';
6327 			}
6328 			// catch border-*-width. IE gets this wrong if the border style is none
6329 			else if (
6330 				prop.indexOf('border') === 0 &&
6331 				prop.slice(-5) === 'width' &&
6332 				getCssValue(elm, 'border-style') === 'none') {
6333 				
6334 				return '0px';
6335 			}
6336 			
6337 			r = currentStyle[ toStyleProp(prop) ];
6338 			
6339 			// font-size gives us incorrect values when put through getPixelValue, avoid
6340 			if (isNumberButNotPx.test(r) && prop != 'font-size') {
6341 				r = getPixelValue( elm, r, prop.indexOf('height') >= 0 || prop.indexOf('top') >= 0 ) + 'px';
6342 			}
6343 		}
6344 		
6345 		// post-process return value
6346 		if (prop === 'opacity') {
6347 			r = r || '1';
6348 		}
6349 		else if (prop.indexOf('color') != -1) { //deal with colour values
6350 			r = NodeList._parseColor(r).toString();
6351 		}
6352 		
6353 		return r;
6354 	}
6355 	
6356 	// vars used in _parseColor
6357 	var mathRound = Math.round,
6358 		parseIntFunc = parseInt,
6359 		parseFloatFunc = parseFloat,
6360 		htmlColorNames = {
6361 			black:   0x000000,
6362 			silver:  0xc0c0c0,
6363 			gray:    0x808080,
6364 			white:   0xffffff,
6365 			maroon:  0x800000,
6366 			red:     0xff0000,
6367 			purple:  0x800080,
6368 			fuchsia: 0xff00ff,
6369 			green:   0x008000,
6370 			lime:    0x00ff00,
6371 			olive:   0x808000,
6372 			yellow:  0xffff00,
6373 			navy:    0x000080,
6374 			blue:    0x0000ff,
6375 			teal:    0x008080,
6376 			aqua:    0x00ffff,
6377 			orange:  0xffa500
6378 		},
6379 		// match a string like rgba(10%, 10%, 10%, 0.5) where the % and alpha parts are optional
6380 		colorRegex = /^rgba?\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)(?:,\s*([\d\.]+))?/i,
6381 		transColorRegex = /^(transparent|rgba\(0, ?0, ?0, ?0\))$/,
6382 		wordCharRegex = /\w/g;
6383 	
6384 	/**
6385 		@name glow.NodeList._parseColor
6386 		@private
6387 		@function
6388 		@description Convert a CSS colour string into a normalised format
6389 		@returns {string} String in format rgb(0, 0, 0)
6390 			Returned string also has r, g & b number properties
6391 	*/
6392 	NodeList._parseColor = function (val) {
6393 		if ( transColorRegex.test(val) ) {
6394 			return 'rgba(0, 0, 0, 0)';
6395 		}
6396 		
6397 		var match, //tmp regex match holder
6398 			r, g, b, a, //final colour vals
6399 			hex; //tmp hex holder
6400 
6401 		if ( match = colorRegex.exec(val) ) { //rgb() format, cater for percentages
6402 			r = match[2] ? mathRound( parseFloatFunc(match[1]) * 2.55 ) : parseIntFunc(match[1]);
6403 			g = match[4] ? mathRound( parseFloatFunc(match[3]) * 2.55 ) : parseIntFunc(match[3]);
6404 			b = match[6] ? mathRound( parseFloatFunc(match[5]) * 2.55 ) : parseIntFunc(match[5]);
6405 			a = parseFloatFunc( match[7] || '1' );
6406 		} else {
6407 			if (typeof val == 'number') {
6408 				hex = val;
6409 			}
6410 			else if (val.charAt(0) == '#') {
6411 				if (val.length === 4) { //deal with #fff shortcut
6412 					val = val.replace(wordCharRegex, '$&$&');
6413 				}
6414 				hex = parseIntFunc(val.slice(1), 16);
6415 			}
6416 			else {
6417 				hex = htmlColorNames[val];
6418 			}
6419 
6420 			r = (hex) >> 16;
6421 			g = (hex & 0x00ff00) >> 8;
6422 			b = (hex & 0x0000ff);
6423 			a = 1;
6424 		}
6425 
6426 		val = new String('rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')');
6427 		val.r = r;
6428 		val.g = g;
6429 		val.b = b;
6430 		val.a = a;
6431 		return val;
6432 	}
6433 	
6434 	// vars for generateWidthAndHeight
6435 	var horizontalBorderPadding = [
6436 			'border-left-width',
6437 			'border-right-width',
6438 			'padding-left',
6439 			'padding-right'
6440 		],
6441 		verticalBorderPadding = [
6442 			'border-top-width',
6443 			'border-bottom-width',
6444 			'padding-top',
6445 			'padding-bottom'
6446 		];
6447 	
6448 	/**
6449 		@private
6450 		@function
6451 		@description Get width or height of an element width/height.
6452 		@param {HTMLElement} elm Element to measure.
6453 		@param {string} 'Width' or 'Height'.
6454 	*/
6455 	function getDimension(elm, cssProp) {
6456 		// exit if there's no element, or it isn't an Element, window or document
6457 		if ( !elm || elm.nodeType === 3 || elm.nodeType === 8 ) {
6458 			return 0;
6459 		}
6460 		
6461 		var r,
6462 			document = elm.ownerDocument || elm.document || elm,
6463 			docElm = document.documentElement,
6464 			docBody = document.body,
6465 			docElmOrBody = glow.env.standardsMode ? docElm : docBody,
6466 			isWidth = (cssProp == 'Width'),
6467 			cssBorderPadding;
6468 
6469 		if (elm.window) { // is window
6470 			r = glow.env.webkit ? (isWidth ? docBody.clientWidth : elm.innerHeight) :
6471 				/* else */        docElmOrBody['client' + cssProp];
6472 		}
6473 		else if (elm.getElementById) { // is document
6474 			// we previously checked offsetWidth & clientWidth here
6475 			// but they returned values too large in IE6
6476 			r = Math.max(
6477 				docBody['scroll' + cssProp],
6478 				docElm['scroll' + cssProp]
6479 			)
6480 		}
6481 		else {
6482 			// get an array of css borders & padding
6483 			cssBorderPadding = isWidth ? horizontalBorderPadding : verticalBorderPadding;
6484 			r = elm['offset' + cssProp] - getTotalCssValue(elm, cssBorderPadding);
6485 		}
6486 		return r;
6487 	}
6488 	
6489 	/**
6490 		@private
6491 		@function
6492 		@description Converts a relative value into an absolute pixel value.
6493 			Only works in IE with Dimension value (not stuff like relative font-size).
6494 			Based on some Dean Edwards' code
6495 		
6496 		@param {HTMLElement} element Used to calculate relative values
6497 		@param {string} value Relative value
6498 		@param {boolean} useYAxis Calulate relative values to the y axis rather than x
6499 		@returns number
6500 	*/
6501 	function getPixelValue(element, value, useYAxis) {
6502 		// Remember the original values
6503 		var axisPos = useYAxis ? 'top' : 'left',
6504 			axisPosUpper = useYAxis ? 'Top' : 'Left',
6505 			elmStyle = element.style,
6506 			positionVal = elmStyle[axisPos],
6507 			runtimePositionVal = element.runtimeStyle[axisPos],
6508 			r;
6509 			
6510 		// copy to the runtime type to prevent changes to the display
6511 		element.runtimeStyle[axisPos] = element.currentStyle[axisPos];
6512 			// set value to left / top
6513 		elmStyle[axisPos] = value;
6514 		// get the pixel value
6515 		r = elmStyle['pixel' + axisPosUpper];
6516 			
6517 		// revert values
6518 		elmStyle[axisPos] = positionVal;
6519 		element.runtimeStyle[axisPos] = runtimePositionVal;
6520 			
6521 		return r;
6522 	}
6523 	
6524 	/**
6525 	@name glow.NodeList#css
6526 	@function
6527 	@description Get / set a CSS property value
6528 		
6529 	@param {string | Object} property The CSS property name, or object of property-value pairs to set
6530 		
6531 	@param {string | number} [value] The value to apply
6532 		Number values will be treated as 'px' unless the CSS property
6533 		accepts a unitless value.
6534 		
6535 		If value is omitted, the value for the given property will be returned
6536 			
6537 	@returns {glow.NodeList | string} Returns the NodeList when setting value, or the CSS value when getting values.
6538 		CSS values are strings. For instance, "height" will return
6539 		"25px" for an element 25 pixels high. You can use
6540 		parseInt to convert these values.
6541 		
6542 	@example
6543 		// get value from first node
6544 		glow('#subNav').css('display');
6545 		
6546 	@example
6547 		// set left padding to 10px on all nodes
6548 		glow('#subNav li').css('padding-left', '2em');
6549 		
6550 	@example
6551 		// where appropriate, px is assumed when no unit is passed
6552 		glow('#mainPromo').css('margin-top', 300);
6553 		
6554 	@example
6555 		// set multiple CSS values at once
6556 		// NOTE: Property names containing a hyphen such as font-weight must be quoted
6557 		glow('#myDiv').css({
6558 			'font-weight': 'bold',
6559 			'padding'	 : '10px',
6560 			'color'		 : '#00cc99'
6561 		});
6562 	*/
6563 	NodeListProto.css = function(prop, val) {
6564 		var thisStyle,
6565 			i = this.length,
6566 			styleProp,
6567 			style,
6568 			firstItem = this[0];
6569 
6570 		if (prop.constructor === Object) { // set multiple values
6571 			for (style in prop) {
6572 				this.css( style, prop[style] );
6573 			}
6574 			return this;
6575 		}
6576 		else if (val !== undefined) { //set one CSS value
6577 			styleProp = toStyleProp(prop);
6578 			while (i--) {
6579 				if (this[i].nodeType === 1) {
6580 					thisStyle = this[i].style;
6581 						
6582 					if ( !isNaN(val) && hasUnits.test(prop) ) {
6583 						val += 'px';
6584 					}
6585 					
6586 					if (prop === 'opacity' && glow.env.ie) {
6587 						val = parseFloatFunc(val);
6588 						//in IE the element needs hasLayout for opacity to work
6589 						thisStyle.zoom = '1';
6590 						thisStyle.filter = (val !== 1) ?
6591 							'alpha(opacity=' + mathRound(val * 100) + ')' :
6592 							'';
6593 					}
6594 					else {
6595 						thisStyle[styleProp] = val;
6596 					}
6597 				}
6598 			}
6599 			return this;
6600 		}
6601 		else { //getting stuff
6602 			if (prop === 'width' || prop === 'height') {
6603 				return this[prop]() + 'px';
6604 			}
6605 			return (firstItem && firstItem.nodeType === 1) ? getCssValue(firstItem, prop) : '';
6606 		}	
6607 	};
6608 	
6609 	/**
6610 	@name glow.NodeList#height
6611 	@function
6612 	@description Gets / set element height
6613 		Return value does not include the padding or border of the element in
6614 		browsers supporting the correct box model.
6615 			
6616 		You can use this to easily get the height of the document or
6617 		window, see example below.
6618 		
6619 	@param {Number} [height] New height in pixels for each element in the list
6620 		If ommited, the height of the first element is returned
6621 		
6622 	@returns {glow.NodeList | number}
6623 		Height of first element, or original NodeList when setting heights.
6624 		
6625 	@example
6626 		// get the height of #myDiv
6627 		glow("#myDiv").height();
6628 		
6629 	@example
6630 		// set the height of list items in #myList to 200 pixels
6631 		glow("#myList > li").height(200);
6632 		
6633 	@example
6634 		// get the height of the document
6635 		glow(document).height();
6636 		
6637 	@example
6638 		// get the height of the window
6639 		glow(win).height();
6640 	*/
6641 	NodeListProto.height = function(height) {
6642 		if (height === undefined) {
6643 			return getDimension(this[0], 'Height');
6644 		}
6645 		return this.css('height', height);	
6646 	};
6647 	
6648 	/**
6649 	@name glow.NodeList#width
6650 	@function
6651 	@description Gets / set element width
6652 		Return value does not include the padding or border of the element in
6653 		browsers supporting the correct box model.
6654 			
6655 		You can use this to easily get the width of the document or
6656 		window, see example below.
6657 		
6658 	@param {Number} [width] New width in pixels for each element in the list
6659 		If ommited, the width of the first element is returned
6660 		
6661 	@returns {glow.NodeList | number}
6662 		width of first element, or original NodeList when setting widths.
6663 		
6664 	@example
6665 		// get the width of #myDiv
6666 		glow("#myDiv").width();
6667 		
6668 	@example
6669 		// set the width of list items in #myList to 200 pixels
6670 		glow("#myList > li").width(200);
6671 		
6672 	@example
6673 		// get the width of the document
6674 		glow(document).width();
6675 		
6676 	@example
6677 		// get the width of the window
6678 		glow(window).width();
6679 	*/
6680 	NodeListProto.width = function(width) {
6681 		if (width === undefined) {
6682 			return getDimension(this[0], 'Width');
6683 		}
6684 		return this.css('width', width);
6685 	};
6686 	
6687 	/**
6688 	@name glow.NodeList#scrollLeft
6689 	@function
6690 	@description Gets/sets the number of pixels the element has scrolled horizontally
6691 		To get/set the scroll position of the window, use this method on
6692 		a nodelist containing the window object.
6693 			
6694 	@param {Number} [val] New left scroll position
6695 		Omit this to get the current scroll position
6696 			
6697 	@returns {glow.NodeList | number}
6698 		Current scrollLeft value, or NodeList when setting scroll position.
6699 			
6700 	@example
6701 		// get the scroll left value of #myDiv
6702 		var scrollPos = glow('#myDiv').scrollLeft();
6703 		// scrollPos is a number, eg: 45
6704 
6705 	@example
6706 		// set the scroll left value of #myDiv to 20
6707 		glow('#myDiv').scrollLeft(20);
6708 
6709 	@example
6710 		// get the scrollLeft of the window
6711 		glow(window).scrollLeft();
6712 		// scrollPos is a number, eg: 45
6713 	*/
6714 	NodeListProto.scrollLeft = function(val) {
6715 		return scrollOffset(this, true, val);	
6716 	};
6717 	
6718 	/**
6719 	@name glow.NodeList#scrollTop
6720 	@function
6721 	@description Gets/sets the number of pixels the element has scrolled vertically
6722 		To get/set the scroll position of the window, use this method on
6723 		a nodelist containing the window object.
6724 		
6725 	@param {Number} [val] New top scroll position
6726 		Omit this to get the current scroll position
6727 			
6728 	@returns {glow.NodeList | number}
6729 		Current scrollTop value, or NodeList when setting scroll position.
6730 
6731 	@example
6732 		// get the scroll top value of #myDiv
6733 		var scrollPos = glow("#myDiv").scrollTop();
6734 		// scrollPos is a number, eg: 45
6735 
6736 	@example
6737 		// set the scroll top value of #myDiv to 20
6738 		glow("#myDiv").scrollTop(20);
6739 
6740 	@example
6741 		// get the scrollTop of the window
6742 		glow(window).scrollTop();
6743 		// scrollPos is a number, eg: 45
6744 	*/
6745 	NodeListProto.scrollTop = function(val) {
6746 		return scrollOffset(this, false, val);	
6747 	};
6748 	/**
6749 	@name glow.dom-getScrollOffset
6750 	@private
6751 	@description Get the scrollTop / scrollLeft of a particular element
6752 	@param {Element} elm Element (or window object) to get the scroll position of
6753 	@param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top
6754 	*/
6755 	function getScrollOffset(elm, isLeft) {
6756 		var r,			
6757 			scrollProp = 'scroll' + (isLeft ? 'Left' : 'Top');
6758 			
6759 		// are we dealing with the window object or the document object?
6760 		if (elm.window) {
6761 			// get the scroll of the documentElement or the pageX/Yoffset
6762 			// - some browsers use one but not the other
6763 			r = elm.document.documentElement[scrollProp]
6764 				|| (isLeft ? elm.pageXOffset : elm.pageYOffset)
6765 				|| 0;
6766 		} else {
6767 			r = elm[scrollProp];
6768 		}
6769 		return r;
6770 	}
6771 		
6772 	/**
6773 	@name glow.dom-setScrollOffset
6774 	@private
6775 	@description Set the scrollTop / scrollLeft of a particular element
6776 	@param {Element} elm Element (or window object) to get the scroll position of
6777 	@param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top
6778 	@param {Number} newVal New scroll value
6779 	*/
6780 	function setScrollOffset(elm, isLeft, newVal) {
6781 	// are we dealing with the window object or the document object?
6782 		if (elm.window) {
6783 			// we need to get whichever value we're not setting
6784 			elm.scrollTo(
6785 				isLeft  ? newVal : getScrollOffset(elm, true),
6786 				!isLeft ? newVal : getScrollOffset(elm, false)
6787 			);
6788 		} else {
6789 			elm['scroll' + (isLeft ? 'Left' : 'Top')] = newVal;
6790 		}
6791 	}
6792 	
6793 	/**
6794 	@name glow.dom-scrollOffset
6795 	@private
6796 	@description Set/get the scrollTop / scrollLeft of a NodeList
6797 	@param {glow.dom.NodeList} nodeList Elements to get / set the position of
6798 	@param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top
6799 	@param {Number} [val] Val to set (if not provided, we'll get the value)
6800 	@returns NodeList for sets, Number for gets
6801 	*/
6802 	function scrollOffset(nodeList, isLeft, val) {
6803 		var i = nodeList.length;
6804 			
6805 		if (val !== undefined) {
6806 			while (i--) {
6807 				setScrollOffset(nodeList[i], isLeft, val);
6808 			}
6809 			return nodeList;
6810 		} else {
6811 			return getScrollOffset(nodeList[0], isLeft);
6812 		}
6813 	}
6814 	/**
6815 	@name glow.NodeList#hide
6816 	@function
6817 	@description Hides all items in the NodeList.
6818 		
6819 	@returns {glow.NodeList}
6820 		
6821 	@example
6822 		// Hides all list items within #myList
6823 		glow("#myList li").hide();
6824 	*/
6825 	NodeListProto.hide = function() {
6826 		return this.css('display', 'none').css('visibility', 'hidden');	
6827 	};
6828 	
6829 	/**
6830 	@name glow.NodeList#show
6831 	@function
6832 	@description Shows all hidden items in the NodeList.
6833 		
6834 	@returns {glow.NodeList}
6835 		
6836 	@example
6837 		// Show element with ID myDiv
6838 		glow("#myDiv").show();
6839 			
6840 	@example
6841 		// Show all list items within #myList
6842 		glow("#myList li").show();
6843 	*/
6844 	NodeListProto.show = function() {
6845 		var i = this.length,
6846 			currItem,
6847 			itemStyle;
6848 			
6849 		while (i--) {
6850 			/* Create a NodeList for the current item */
6851 			currItem = new glow.NodeList(this[i]);
6852 			itemStyle = currItem[0].style;
6853 			if (currItem.css('display') == 'none') {
6854 				itemStyle.display = '';
6855 				itemStyle.visibility = 'visible';
6856 				/* If display is still none, set to block */
6857 				if (currItem.css('display') == 'none') {
6858 					itemStyle.display = 'block';
6859 				}
6860 			}	
6861 		}
6862 		return this;	
6863 	};
6864 
6865 	/**
6866 	@name glow.NodeList#offset
6867 	@function
6868 	@description Gets the offset from the top left of the document.
6869 		If the NodeList contains multiple items, the offset of the
6870 		first item is returned.
6871 			
6872 	@returns {Object}
6873 		Returns an object with "top" & "left" properties in pixels
6874 			
6875 	@example
6876 		glow("#myDiv").offset().top
6877 	*/
6878 	NodeListProto.offset = function() {
6879 		if ( !this[0] || this[0].nodeType !== 1) {
6880 			return {top: 0, left: 0};
6881 		}
6882 		
6883 		// http://weblogs.asp.net/bleroy/archive/2008/01/29/getting-absolute-coordinates-from-a-dom-element.aspx - great bit of research, most bugfixes identified here (and also jquery trac)
6884 		var elm = this[0],
6885 			doc = elm.ownerDocument,
6886 			docElm = doc.documentElement,
6887 			window = doc.defaultView || doc.parentWindow,
6888 			docScrollPos = {
6889 				x: getScrollOffset(window, true),
6890 				y: getScrollOffset(window, false)
6891 			};
6892 
6893 		//this is simple(r) if we can use 'getBoundingClientRect'
6894 		// Sorry but the sooper dooper simple(r) way is not accurate in Safari 4
6895 		if (!glow.env.webkit && elm.getBoundingClientRect) {
6896 			var rect = elm.getBoundingClientRect();
6897 			
6898 			return {
6899 				top: Math.floor(rect.top)
6900 					/*
6901 					 getBoundingClientRect is realive to top left of
6902 					 the viewport, so we need to sort out scrolling offset
6903 					*/
6904 					+ docScrollPos.y
6905 					/*
6906 					IE adds the html element's border to the value. We can
6907 					deduct this value using client(Top|Left). However, if
6908 					the user has done html{border:0} clientTop will still
6909 					report a 2px border in IE quirksmode so offset will be off by 2.
6910 					Hopefully this is an edge case but we may have to revisit this
6911 					in future
6912 					*/
6913 					- docElm.clientTop,
6914 
6915 				left: Math.floor(rect.left) //see above for docs on all this stuff
6916 					+ docScrollPos.x
6917 					- docElm.clientLeft
6918 			};
6919 		}
6920 		else { //damnit, let's go the long way around
6921 			var top = elm.offsetTop,
6922 				left = elm.offsetLeft,
6923 				originalElm = elm,
6924 				nodeNameLower,
6925 				docBody = document.body,
6926 				//does the parent chain contain a position:fixed element
6927 				involvesFixedElement = false,
6928 				offsetParentBeforeBody = elm;
6929 
6930 			//add up all the offset positions
6931 			while (elm = elm.offsetParent) {
6932 				left += elm.offsetLeft;
6933 				top += elm.offsetTop;
6934 
6935 				//if css position is fixed, we need to add in the scroll offset too, catch it here
6936 				if (getCssValue(elm, 'position') == 'fixed') {
6937 					involvesFixedElement = true;
6938 				}
6939 
6940 				//gecko & webkit (safari 3) don't add on the border for positioned items
6941 				if (glow.env.gecko || glow.env.webkit > 500) {
6942 					left += parseInt(getCssValue(elm, 'border-left-width')) || 0;
6943 					top  += parseInt(getCssValue(elm, 'border-top-width'))  || 0;
6944 				}
6945 				
6946 				//we need the offset parent (before body) later
6947 				if (elm.nodeName.toLowerCase() != 'body') {
6948 					offsetParentBeforeBody = elm;
6949 				}
6950 			}
6951 
6952 			//deduct all the scroll offsets
6953 			elm = originalElm;
6954 			
6955 			while ((elm = elm.parentNode) && (elm != docBody) && (elm != docElm)) {
6956 				left -= elm.scrollLeft;
6957 				top -= elm.scrollTop;
6958 
6959 				//FIXES
6960 				//gecko doesn't add the border of contained elements to the offset (overflow!=visible)
6961 				if (glow.env.gecko && getCssValue(elm, 'overflow') != 'visible') {
6962 					left += parseInt(getCssValue(elm, 'border-left-width'));
6963 					top += parseInt(getCssValue(elm, 'border-top-width'));
6964 				}
6965 			}
6966 
6967 			//if we found a fixed position element we need to add the scroll offsets
6968 			if (involvesFixedElement) {
6969 				left += docScrollPos.x;
6970 				top += docScrollPos.y;
6971 			}
6972 
6973 			//FIXES
6974 			// Gecko - non-absolutely positioned elements that are direct children of body get the body offset counted twice
6975 			if (
6976 				(glow.env.gecko && getCssValue(offsetParentBeforeBody, 'position') != 'absolute')
6977 			) {
6978 				left -= docBody.offsetLeft;
6979 				top -= docBody.offsetTop;
6980 			}
6981 
6982 			return {left:left, top:top};
6983 		}
6984 	};
6985 	
6986 	/**
6987 	@name glow.NodeList#position
6988 	@function
6989 	@description Get the top & left position of an element relative to its positioned parent
6990 		This is useful if you want to make a position:static element position:absolute
6991 		and retain the original position of the element
6992 			
6993 	@returns {Object}
6994 		An object with 'top' and 'left' number properties
6995 		
6996 	@example
6997 		// get the top distance from the positioned parent
6998 		glow("#elm").position().top
6999 	*/
7000 	NodeListProto.position = function() {
7001 		var positionedParent = new glow.NodeList( getPositionedParent(this[0]) ),
7002 			hasPositionedParent = !!positionedParent[0],
7003 					
7004 			// element margins to deduct
7005 			marginLeft = parseInt( this.css('margin-left') ) || 0,
7006 			marginTop  = parseInt( this.css('margin-top')  ) || 0,
7007 					
7008 			// offset parent borders to deduct, set to zero if there's no positioned parent
7009 			positionedParentBorderLeft = ( hasPositionedParent && parseInt( positionedParent.css('border-left-width') ) ) || 0,
7010 			positionedParentBorderTop  = ( hasPositionedParent && parseInt( positionedParent.css('border-top-width')  ) ) || 0,
7011 					
7012 			// element offsets
7013 		elOffset = this.offset(),
7014 		positionedParentOffset = hasPositionedParent ? positionedParent.offset() : {top: 0, left: 0};
7015 				
7016 		return {
7017 			left: elOffset.left - positionedParentOffset.left - marginLeft - positionedParentBorderLeft,
7018 			top:  elOffset.top  - positionedParentOffset.top  - marginTop  - positionedParentBorderTop
7019 		}	
7020 	};
7021 	/*
7022 		Get the 'real' positioned parent for an element, otherwise return null.
7023 	*/
7024 	function getPositionedParent(elm) {
7025 		var offsetParent = elm.offsetParent,
7026 		docElm = document.documentElement;
7027 			
7028 		// get the real positioned parent
7029 		// IE places elements with hasLayout in the offsetParent chain even if they're position:static
7030 		// Also, <body> and <html> can appear in the offsetParent chain, but we don't want to return them if they're position:static
7031 		while (offsetParent && new glow.NodeList(offsetParent).css('position') == 'static') {	
7032 			offsetParent = offsetParent.offsetParent;
7033 		}
7034 			
7035 		// sometimes the <html> element doesn't appear in the offsetParent chain, even if it has position:relative
7036 		if (!offsetParent && new glow.NodeList(docElm).css('position') != 'static') {
7037 			offsetParent = docElm;
7038 		}
7039 			
7040 		return offsetParent || null;
7041 	}
7042 });
7043 Glow.provide(function(glow) {
7044 	var NodeListProto = glow.NodeList.prototype,
7045 		document = window.document,
7046 		undefined,
7047 		keyEventNames = ' keypress keydown keyup ';
7048 	
7049 	/**
7050 		@name glow.NodeList#on
7051 		@function
7052 		@description Listen for an event.
7053 		   This will listen for a particular event on each dom node
7054 		   in the NodeList.
7055 		   
7056 		   If you're listening to many children of a particular item,
7057 		   you may get better performance from {@link glow.NodeList#delegate}.
7058 		
7059 		@param {String} eventName Name of the event to listen for.
7060 		   This can be any regular DOM event ('click', 'mouseover' etc) or
7061 		   a special event of NodeList.
7062 		   
7063 		@param {Function} callback Function to call when the event fires.
7064 		   The callback is passed a single event object. The type of this
7065 		   object is {@link glow.DomEvent} unless otherwise stated.
7066 		   
7067 		@param {Object} [thisVal] Value of 'this' within the callback.
7068 		   By default, this is the dom node being listened to.
7069 		
7070 		@returns this
7071 		
7072 		@example
7073 		   glow.get('#testLink').on('click', function(domEvent) {
7074 			   // do stuff
7075 			   
7076 			   // if you want to cancel the default action (following the link)...
7077 			   return false;
7078 		   });
7079 	*/
7080 	NodeListProto.on = function(eventName, callback, thisVal) {
7081 		var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1);
7082 		
7083 		// add standard glow listeners
7084 		glow.events.addListeners(this, eventName, callback, thisVal);
7085 		
7086 		// add the bridge functions if needed
7087 		if (isKeyEvent) {
7088 			glow.events._addKeyListener(this);
7089 		}
7090 		else { // assume it's a DOM event
7091 			glow.events._addDomEventListener(this, eventName);
7092 		}
7093 		
7094 		return this;
7095 	}
7096 	
7097 	/**
7098 		@name glow.NodeList#detach
7099 		@function
7100 		@description detach a listener from elements
7101 		   This will detach the listener from each dom node in the NodeList.
7102 		
7103 		@param {String} eventName Name of the event to detach the listener from
7104 		   
7105 		@param {Function} callback Listener callback to detach
7106 		
7107 		@returns this
7108 		
7109 		@example
7110 			function clickListener(domEvent) {
7111 				// ...
7112 			}
7113 			
7114 			// adding listeners
7115 			glow.get('a').on('click', clickListener);
7116 			
7117 			// removing listeners
7118 			glow.get('a').detach('click', clickListener);
7119 	*/
7120 	NodeListProto.detach = function(eventName, callback) {
7121 		var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1);
7122 		
7123 		// remove standard glow listeners
7124 		glow.events.removeListeners(this, eventName, callback);
7125 		
7126 		// remove the bridge functions if needed
7127 		if (isKeyEvent) {
7128 			glow.events._removeKeyListener(this);
7129 		}
7130 		else { // assume it's a DOM event
7131 			glow.events._removeDomEventListener(this, eventName);
7132 		}
7133 		
7134 		return this;
7135 	}
7136 	
7137 	/**
7138 		@name glow.NodeList#delegate
7139 		@function
7140 		@description Listen for an event occurring on child elements matching a selector.
7141 			'delegate' will catch events which occur on matching items created after
7142 			the listener was added. 
7143 		
7144 		@param {String} eventName Name of the event to listen for.
7145 			This can be any regular DOM event ('click', 'mouseover' etc) or
7146 			a special event of NodeList.
7147 		
7148 		@param {String} selector CSS selector of child elements to listen for events on
7149 			For example, if you were wanting to hear events from links, this
7150 			would be 'a'.
7151 		
7152 		@param {Function} callback Function to call when the event fires.
7153 			The callback is passed a single event object. The type of this
7154 			object is {@link glow.DomEvent} unless otherwise stated.
7155 		
7156 		@param {Object} [thisVal] Value of 'this' within the callback.
7157 			By default, this is the dom node matched by 'selector'.
7158 		
7159 		@returns this
7160 		
7161 		@example
7162 			// Using 'on' to catch clicks on links in a list
7163 			glow.get('#nav a').on('click', function() {
7164 				// do stuff
7165 			});
7166 			
7167 			// The above adds a listener to each link, any links created later
7168 			// will not have this listener, so we won't hear about them.
7169 			
7170 			// Using 'delegate' to catch clicks on links in a list
7171 			glow.get('#nav').delegate('click', 'a', function() {
7172 				// do stuff
7173 			});
7174 			
7175 			// The above only adds one listener to #nav which tracks clicks
7176 			// to any links within. This includes elements created after 'delegate'
7177 			// was called.
7178 		
7179 		@example
7180 			// Using delegate to change class names on table rows so :hover
7181 			// behaviour can be emulated in IE6
7182 			glow.get('#contactData').delegate('mouseover', 'tr', function() {
7183 				glow.get(this).addClass('hover');
7184 			});
7185 			
7186 			glow.get('#contactData').delegate('mouseout', 'tr', function() {
7187 				glow.get(this).removeClass('hover');
7188 			});
7189 	*/
7190 	NodeListProto.delegate = function(eventName, selector, callback, thisVal) {
7191 		var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1);
7192 		
7193 		// add standard glow listeners
7194 		glow.events.addListeners(this, eventName + '/' + selector, callback, thisVal);
7195 		
7196 		// register delegates
7197 		glow.events._registerDelegate(this, eventName, selector);
7198 		
7199 		// add the bridge functions if needed
7200 		if (isKeyEvent) {
7201 			glow.events._addKeyListener(this);
7202 		}
7203 		else { // assume it's a DOM event
7204 			glow.events._addDomEventListener(this, eventName);
7205 		}
7206 		
7207 		return this;
7208 	}
7209 	
7210 	/**
7211 		@name glow.NodeList#detachDelegate
7212 		@function
7213 		@description detach a delegated listener from elements
7214 		   This will detach the listener from each dom node in the NodeList.
7215 		
7216 		@param {String} eventName Name of the event to detach the listener from
7217 		
7218 		@param {String} selector CSS selector of child elements the listener is listening to
7219 		
7220 		@param {Function} callback Listener callback to detach
7221 		
7222 		@returns this
7223 		
7224 		@example
7225 			function clickListener(domEvent) {
7226 				// ...
7227 			}
7228 			
7229 			// adding listeners
7230 			glow.get('#nav').delegate('click', 'a', clickListener);
7231 			
7232 			// removing listeners
7233 			glow.get('#nav').detachDelegate('click', 'a', clickListener);
7234 	*/
7235 	NodeListProto.detachDelegate = function(eventName, selector, callback, thisVal) {
7236 		var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1);
7237 		
7238 		// remove standard glow listeners
7239 		glow.events.removeListeners(this, eventName + '/' + selector, callback);
7240 		
7241 		// unregister delegates
7242 		glow.events._unregisterDelegate(this, eventName, selector);
7243 		
7244 		// remove the bridge functions if needed
7245 		if (isKeyEvent) {
7246 			glow.events._removeKeyListener(this);
7247 		}
7248 		else { // assume it's a DOM event
7249 			glow.events._removeDomEventListener(this, eventName);
7250 		}
7251 		
7252 		return this;
7253 	}
7254 	
7255 	/**
7256 		@name glow.NodeList#fire
7257 		@function
7258 		@param {String} eventName Name of the event to fire
7259 		@param {glow.events.Event} [event] Event object to pass into listeners.
7260 		   You can provide a simple object of key / value pairs which will
7261 		   be added as properties of a glow.events.Event instance.
7262 		
7263 		@description Fire an event on dom nodes within the NodeList
7264 		   Note, this will only trigger event listeners to be called, it won't
7265 		   for example, move the mouse or click a link on the page.
7266 		
7267 		@returns glow.events.Event
7268 		
7269 		@example
7270 		   glow.get('#testLink').on('click', function() {
7271 			   alert('Link clicked!');
7272 		   });
7273 		   
7274 		   // The following causes 'Link clicked!' to be alerted, but doesn't
7275 		   // cause the browser to follow the link
7276 		   glow.get('#testLink').fire('click');
7277 	*/
7278 	NodeListProto.fire = function(eventName, event) {
7279 		return glow.events.fire(this, eventName, event);
7280 	}
7281 	
7282 	/**
7283 		@name glow.NodeList#event:mouseenter
7284 		@event
7285 		@description Fires when the mouse enters the element specifically, does not bubble
7286 		
7287 		@param {glow.events.DomEvent} event Event Object
7288 	*/
7289 	
7290 	/**
7291 		@name glow.NodeList#event:mouseleave
7292 		@event
7293 		@description Fires when the mouse leaves the element specifically, does not bubble
7294 		
7295 		@param {glow.events.DomEvent} event Event Object
7296 	*/
7297 	
7298 	/**
7299 		@name glow.NodeList#event:keydown
7300 		@event
7301 		@description Fires when the user presses a key
7302 			Only fires if the element has focus, listen for this event on
7303 			the document to catch all keydowns.
7304 			
7305 			This event related to the user pressing a key on the keyboard,
7306 			if you're more concerned about the character entered, see the
7307 			{@link glow.NodeList#event:keypress keypress} event.
7308 			
7309 			keydown will only fire once, when the user presses the key.
7310 			
7311 			The order of events is keydown, keypress*, keyup. keypress may
7312 			fire many times if the user holds the key down.
7313 		
7314 		@param {glow.events.KeyboardEvent} event Event Object
7315 	*/
7316 	
7317 	/**
7318 		@name glow.NodeList#event:keypress
7319 		@event
7320 		@description Fires when a key's command executes.
7321 			For instance, if you hold down a key, it's action will occur many
7322 			times. This event will fire on each action.
7323 			
7324 			This event is useful when you want to react to keyboard repeating, or
7325 			to detect when a character is entered into a field.
7326 			
7327 			The order of events is keydown, keypress*, keyup. keypress may
7328 			fire many times if the user holds the key down.
7329 		
7330 		@param {glow.events.KeyboardEvent} event Event Object
7331 	*/
7332 	
7333 	/**
7334 		@name glow.NodeList#event:keyup
7335 		@event
7336 		@description Fires when the user releases a key
7337 			Only fires if the element has focus, listen for this event on
7338 			the document to catch all keyups.
7339 			
7340 			This event related to the user pressing a key on the keyboard,
7341 			if you're more concerned about the character entered, see the
7342 			{@link glow.NodeList#event:keypress keypress} event.
7343 			
7344 			The order of events is keydown, keypress*, keyup. keypress may
7345 			fire many times if the user holds the key down.
7346 		
7347 		@param {glow.events.KeyboardEvent} event Event Object
7348 	*/
7349 });
7350 Glow.provide(function(glow) {
7351 	var NodeList = glow.NodeList,
7352 		NodeListProto = NodeList.prototype,
7353 		undefined,
7354 		parseFloat = window.parseFloat,
7355 		// used to detect which CSS properties require units
7356 		requiresUnitsRe = /width|height|top$|bottom$|left$|right$|spacing$|indent$|fontSize/i,
7357 		// which simple CSS values cannot be negative
7358 		noNegativeValsRe = /width|height|padding|opacity/,
7359 		getUnit = /\D+$/,
7360 		usesYAxis = /height|top/;
7361 	
7362 	// TODO: get this from appearence.js
7363 	function toStyleProp(prop) {
7364 		if (prop == 'float') {
7365 			return glow.env.ie ? 'styleFloat' : 'cssFloat';
7366 		}
7367 		return prop.replace(/-(\w)/g, function(match, p1) {
7368 			return p1.toUpperCase();
7369 		});
7370 	}
7371 	
7372 	/**
7373 		@private
7374 		@function
7375 		@param {nodelist} element
7376 		@param {string} toUnit (em|%|pt...)
7377 		@param {string} axis (x|y)
7378 		@description Converts a css unit.
7379 			We need to know the axis for calculating relative values, since they're
7380 			relative to the width / height of the parent element depending
7381 			on the situation.
7382 	*/
7383 	var testElement = glow('<div style="position:absolute;visibility:hidden;border:0;margin:0;padding:0"></div>');
7384 	
7385 	function convertCssUnit(element, value, toUnit, axis) {
7386 		var elmStyle = testElement[0].style,
7387 			axisProp = (axis === 'x') ? 'width' : 'height',
7388 			startPixelValue,
7389 			toUnitPixelValue;
7390 		
7391 		startPixelValue = testElement.css(axisProp, value).insertAfter(element)[axisProp]();
7392 		// using 10 of the unit then dividing by 10 to increase accuracy
7393 		toUnitPixelValue = testElement.css(axisProp, 10 + toUnit)[axisProp]() / 10;
7394 		testElement.remove();
7395 		return startPixelValue / toUnitPixelValue;
7396 	}
7397 	
7398 	/**
7399 		@private
7400 		@function
7401 		@description Animate a colour value
7402 	*/
7403 	function animateColor(anim, stylePropName, from, to) {
7404 		to = NodeList._parseColor(to);
7405 		to = [to.r, to.g, to.b];
7406 		from = NodeList._parseColor(from);
7407 		from = [from.r, from.g, from.b];
7408 		
7409 		anim.prop(stylePropName, {
7410 			// we only need a template if we have units
7411 			template: 'rgb(?,?,?)',
7412 			from: from,
7413 			to: to,
7414 			round: true,
7415 			min: 0,
7416 			max: 255
7417 		});
7418 	}
7419 	
7420 	/**
7421 		@private
7422 		@function
7423 		@description Animate opacity in IE's 'special' way
7424 	*/
7425 	function animateIeOpacity(elm, anim, from, to) {
7426 		to   = parseFloat(to)   * 100;
7427 		from = parseFloat(from) * 100;
7428 		
7429 		// give the element 'hasLayout'
7430 		elm.style.zoom = 1;
7431 		
7432 		anim.prop('filter', {
7433 			// we only need a template if we have units
7434 			template: 'alpha(opacity=?)',
7435 			from: from,
7436 			to: to,
7437 			allowNegative: false
7438 		});
7439 	}
7440 	
7441 	/**
7442 		@private
7443 		@function
7444 		@description Scroll positions
7445 	*/
7446 	function animateScroll(elm, anim, from, to, scrollTopOrLeft) {
7447 		var diff;
7448 		
7449 		to   = parseFloat(to);
7450 		from = parseFloat(from);
7451 		elm = glow(elm);
7452 		
7453 		// auto-get start value if there isn't one
7454 		if ( isNaN(from) ) {
7455 			from = elm[scrollTopOrLeft]();
7456 		}
7457 		
7458 		diff = to - from;
7459 		
7460 		anim.on('frame', function() {
7461 			elm[scrollTopOrLeft]( diff * this.value + from );
7462 		});
7463 	}
7464 	
7465 	/**
7466 		@private
7467 		@function
7468 		@description Animate simple values
7469 			This is a set of space-separated numbers (42) or numbers + unit (42em)
7470 			
7471 			Units can be mixed
7472 	*/
7473 	function animateValues(element, anim, stylePropName, from, to) {
7474 		var toUnit,
7475 			fromUnit,
7476 			round = [],
7477 			template = '',
7478 			requiresUnits = requiresUnitsRe.test(stylePropName),
7479 			minZero = noNegativeValsRe.test(stylePropName);
7480 		
7481 		from = String(from).split(' ');
7482 		to = String(to).split(' ');
7483 		
7484 		for (var i = 0, leni = to.length; i < leni; i++) {
7485 			toUnit   = ( getUnit.exec( to[i] )   || [''] )[0];
7486 			fromUnit = ( getUnit.exec( from[i] ) || [''] )[0];
7487 			
7488 			// create initial units if required
7489 			if (requiresUnits) {
7490 				toUnit = toUnit || 'px';
7491 				fromUnit = fromUnit || 'px';
7492 			}
7493 			
7494 			round[i] = (toUnit === 'px');
7495 			
7496 			// make the 'from' unit the same as the 'to' unit
7497 			if (toUnit !== fromUnit) {
7498 				from = convertCssUnit( element, from, toUnit, usesYAxis.test(stylePropName) ? 'y' : 'x' );
7499 			}
7500 			
7501 			template += ' ?' + toUnit;
7502 			from[i] = parseFloat( from[i] );
7503 			to[i]   = parseFloat( to[i] );
7504 		}
7505 		
7506 		anim.prop(stylePropName, {
7507 			template: template,
7508 			from: from,
7509 			to: to,
7510 			round: round,
7511 			min: minZero ? 0 : undefined
7512 		});
7513 	}
7514 	
7515 	/**
7516 		@private
7517 		@function
7518 		@description Makes an animtion adjust CSS values over time
7519 	*/
7520 	function addCssAnim(nodeList, anim, properties) {
7521 		var to, from, i,
7522 			property,
7523 			propertyIsArray,
7524 			stylePropName;
7525 		
7526 		for (var propName in properties) {
7527 			property = properties[propName];
7528 			propertyIsArray = property.push;
7529 			stylePropName = toStyleProp(propName);
7530 			to = propertyIsArray ? property[1] : property;
7531 			i = nodeList.length;
7532 			
7533 			// do this for each nodelist item
7534 			while (i--) {
7535 				// deal with special values, scrollTop and scrollLeft which aren't really CSS
7536 				// This is the only animation that can work on the window object too
7537 				if ( propName.indexOf('scroll') === 0 && (nodeList[i].scrollTo || nodeList[i].scrollTop !== undefined) ) {
7538 					animateScroll(nodeList[i], anim, propertyIsArray ? property[0] : undefined, to, propName);
7539 					continue;
7540 				}
7541 				
7542 				// skip non-element nodes
7543 				if ( nodeList[i].nodeType !== 1 ) { continue; }
7544 				
7545 				// set new target
7546 				anim.target( nodeList[i].style );
7547 				
7548 				from = propertyIsArray ? property[0] : nodeList.item(i).css(propName);
7549 				
7550 				// deal with colour values
7551 				if ( propName.indexOf('color') !== -1 ) {
7552 					animateColor(anim, stylePropName, from, to);
7553 				}
7554 				// nice special case for IE
7555 				else if (glow.env.ie && stylePropName === 'opacity') {
7556 					animateIeOpacity(nodeList[i], anim, from, to);
7557 				}
7558 				// assume we're dealing with simple numbers, or numbers + unit
7559 				// eg "5px", "5px 2em", "10px 5px 1em 4px"
7560 				else {
7561 					animateValues(nodeList[i], anim, stylePropName, from, to);
7562 				}
7563 			}
7564 		}
7565 	}
7566 	
7567 	/**
7568 		@name glow.NodeList#anim
7569 		@function
7570 		@description Animate properties of elements
7571 			All elements in the NodeList are animated
7572 			
7573 			All CSS values which are simple numbers (with optional unit)
7574 			are supported. Eg: width, margin-top, left
7575 			
7576 			All CSS values which are space-separated values are supported
7577 			(eg background-position, margin, padding), although a 'from'
7578 			value must be provided for short-hand properties like 'margin'.
7579 			
7580 			All CSS colour values are supported. Eg: color, background-color.
7581 			
7582 			'scrollLeft' and 'scrollTop' can be animated for elements and
7583 			the window object.
7584 			
7585 			Other properties, including CSS properties with limited support, can
7586 			be animated using {@link glow.anim.Anim#prop}.
7587 		
7588 		@param {number} duration Length of the animation in seconds.
7589 		@param {Object} properties Properties to animate.
7590 			This is an object where the key is the CSS property and the value
7591 			is the value to animate to.
7592 			
7593 			The value can also be an array, where the first item is the value to
7594 			animate from, and the second is the value to animate to.
7595 			
7596 			Numerical values will be treated as 'px' if the property requires units.
7597 		
7598 		@param {Object} [opts] Options object
7599 		@param {function|string} [opts.tween='easeBoth'] The motion of the animation.
7600 			Strings are treated as properties of {@link glow.tweens}, although
7601 			a tween function can be provided.
7602 		@param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops).
7603 			This will free any DOM references the animation may have created. Once
7604 			the animation is destroyed, it cannot be started again.
7605 		@param {boolean} [opts.loop=true] Loop the animation.
7606 		@param {boolean} [opts.startNow=true] Start the animation straight away?
7607 			Animations can be started by calling {@link glow.anim.Anim#start}
7608 		
7609 		@returns {glow.anim.Anim}
7610 		
7611 		@example
7612 			// change the nav's background colour to white and the top position
7613 			// to 20px over a duration of 3 seconds
7614 			glow('#nav').anim(3, {
7615 				'background-color': '#fff',
7616 				'top': 20
7617 			});
7618 			
7619 		@example
7620 			// Fade an element out and alert 'done' when complete
7621 			glow('#nav').anim(3, {
7622 				'opacity': 0
7623 			}).on('complete', function() {
7624 				alert('done!');
7625 			});
7626 			
7627 		@example
7628 			// Scroll the window to the top
7629 			glow(window).anim(2, {
7630 				scrollTop: 0
7631 			});
7632 		
7633 		@see {@link glow.NodeList#queueAnim} - Queue an animation to run after the current anim
7634 		@see {@link glow.NodeList#fadeIn} - Shortcut to fade elements in
7635 		@see {@link glow.NodeList#fadeOut} - Shortcut to fade elements out
7636 		@see {@link glow.NodeList#fadeToggle} - Shortcut to toggle the fade of an element
7637 		@see {@link glow.NodeList#slideOpen} - Shortcut to slide an element open
7638 		@see {@link glow.NodeList#slideShut} - Shortcut to slide an element shut
7639 		@see {@link glow.NodeList#slideToggle} - Shortcut to toggle an element open / shut
7640 
7641 	*/
7642 	NodeListProto.anim = function(duration, properties, opts) {
7643 		/*!debug*/
7644 			if (arguments.length < 2 || arguments.length > 3) {
7645 				glow.debug.warn('[wrong count] glow.NodeList#anim expects 2 or 3 arguments, not ' + arguments.length + '.');
7646 			}
7647 			if (typeof duration !== 'number') {
7648 				glow.debug.warn('[wrong type] glow.NodeList#anim expects number as "duration" argument, not ' + typeof duration + '.');
7649 			}
7650 			if (typeof properties !== 'object') {
7651 				glow.debug.warn('[wrong type] glow.NodeList#anim expects object as "properties" argument, not ' + typeof properties + '.');
7652 			}
7653 			if (opts !== undefined && typeof opts !== 'object') {
7654 				glow.debug.warn('[wrong type] glow.NodeList#anim expects object as "opts" argument, not ' + typeof opts + '.');
7655 			}
7656 		/*gubed!*/
7657 		
7658 		opts = opts || {};
7659 		
7660 		var anim = new glow.anim.Anim(duration, opts);
7661 		
7662 		addCssAnim(this, anim, properties);
7663 		
7664 		// auto start
7665 		!(opts.startNow === false) && anim.start();
7666 		return anim;
7667 	};
7668 	
7669 	/**
7670 		@private
7671 		@function
7672 		@description Used as a listener for an animations's stop event.
7673 			'this' is a nodelist of the animating item
7674 			
7675 			Set in queueAnim
7676 	*/
7677 	function queueAnimStop() {
7678 		this.removeData('glow_lastQueuedAnim').removeData('glow_currentAnim');
7679 	}
7680 	
7681 	/**
7682 		@name glow.NodeList#queueAnim
7683 		@function
7684 		@description Queue an animation to run after the current animation
7685 			All elements in the NodeList are animated
7686 		
7687 			This supports the same CSS properties as {@link glow.NodeList#anim},
7688 			but the animation is not started until the previous animation (added
7689 			via {@link glow.NodeList#queueAnim queueAnim})
7690 			on that element ends.
7691 			
7692 			If there are no queued animations on the element, the animation starts
7693 			straight away.
7694 		
7695 		@param {number} duration Length of the animation in seconds.
7696 		@param {Object} Properties to animate.
7697 			This is an object where the key is the CSS property and the value
7698 			is the value to animate to.
7699 			
7700 			The value can also be an array, where the first item is the value to
7701 			animate from, and the second is the value to animate to.
7702 			
7703 			Numerical values will be treated as 'px' if the property requires units.
7704 		
7705 		@param {Object} [opts] Options object
7706 		@param {function|string} [opts.tween='easeBoth'] The motion of the animation.
7707 			Strings are treated as properties of {@link glow.tweens}, although
7708 			a tween function can be provided.
7709 		@param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops).
7710 			This will free any DOM references the animation may have created. Once
7711 			the animation is destroyed, it cannot be started again.
7712 		
7713 		@returns {glow.NodeList}
7714 		
7715 		@example
7716 			// change a nav item's background colour from white to yellow
7717 			// when the mouse is over it, and back again when the mouse
7718 			// exits.
7719 			glow('#nav').delegate('mouseenter', 'li', function() {
7720 				glow(this).queueAnim(0.5, {
7721 					'background-color': 'yellow'
7722 				});
7723 			}).delegate('mouseleave', 'li', function() {
7724 				glow(this).queueAnim(0.5, {
7725 					'background-color': 'white'
7726 				});
7727 			});
7728 			
7729 		@example
7730 			// adding listeners to a queued anim
7731 			glow('#elementToAnimate').queueAnim(0.5, {
7732 				height: 0
7733 			}).lastQueuedAnim().on('complete', function() {
7734 				alert('Animation complete!');
7735 			});
7736 			
7737 		@example
7738 			// stopping and clearing current animation queue.
7739 			// The next animation created via queueAnim will start
7740 			// immediately
7741 			glow('#elementToAnimate').curentAnim().stop();
7742 		
7743 		@see {@link glow.NodeList#fadeIn} - Shortcut to fade elements in
7744 		@see {@link glow.NodeList#fadeOut} - Shortcut to fade elements out
7745 		@see {@link glow.NodeList#fadeToggle} - Shortcut to toggle the fade of an element
7746 		@see {@link glow.NodeList#slideOpen} - Shortcut to slide an element open
7747 		@see {@link glow.NodeList#slideShut} - Shortcut to slide an element shut
7748 		@see {@link glow.NodeList#slideToggle} - Shortcut to toggle an element open / shut
7749 
7750 	*/
7751 	NodeListProto.queueAnim = function(duration, properties, opts) {
7752 		/*!debug*/
7753 			if (arguments.length < 2 || arguments.length > 3) {
7754 				glow.debug.warn('[wrong count] glow.NodeList#queueAnim expects 2 or 3 arguments, not ' + arguments.length + '.');
7755 			}
7756 			if (typeof duration !== 'number') {
7757 				glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects number as "duration" argument, not ' + typeof duration + '.');
7758 			}
7759 			if (typeof properties !== 'object') {
7760 				glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects object as "properties" argument, not ' + typeof properties + '.');
7761 			}
7762 			if (opts !== undefined && typeof opts !== 'object') {
7763 				glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects object as "opts" argument, not ' + typeof opts + '.');
7764 			}
7765 		/*gubed!*/
7766 		
7767 		opts = opts || {};
7768 		
7769 		var i = this.length,
7770 			item,
7771 			lastQueuedAnim,
7772 			anim,
7773 			startNextAnim;
7774 		
7775 		// we don't want animations starting now
7776 		opts.startNow = false;
7777 		
7778 		while (i--) {
7779 			item = this.item(i);
7780 			if (item[0].nodeType !== 1) { continue; }
7781 			lastQueuedAnim = item.data('glow_lastQueuedAnim');
7782 			// add a listener to 'stop', to clear the queue
7783 			anim = new glow.anim.Anim(duration, opts).on('stop', queueAnimStop, item);
7784 			item.data('glow_lastQueuedAnim', anim);
7785 			
7786 			// closure some properties
7787 			(function(item, properties, anim) {
7788 				startNextAnim = function() {
7789 					addCssAnim(item, anim, properties);
7790 					anim.start();
7791 					item.data('glow_currentAnim', anim);
7792 				}
7793 			})(item, properties, anim);
7794 			
7795 			// do we start the anim now, or after the next one?
7796 			if (lastQueuedAnim) {
7797 				lastQueuedAnim.on('complete', startNextAnim);
7798 			}
7799 			else {
7800 				startNextAnim();
7801 			}
7802 		}
7803 		
7804 		return this;
7805 	};
7806 	
7807 	/**
7808 		@name glow.NodeList#currentAnim
7809 		@function
7810 		@description Get the currently playing animation added via {@link glow.NodeList#queueAnim queueAnim} for this element
7811 			If no animation is currently playing, an empty animation is returned.
7812 			This means you don't need to check to see if the item is defined before
7813 			calling methods on it.
7814 			
7815 			This method acts on the first item in the NodeList.
7816 		
7817 		@returns {glow.anim.Anim}
7818 			
7819 		@example
7820 			// stopping and clearing current animation queue.
7821 			// The next animation created via queueAnim will start
7822 			// immediately
7823 			glow('#elementToAnimate').curentAnim().stop();
7824 		
7825 		@example
7826 			// Is the element animating as part of queueAnim?
7827 			glow('#elementToAnimate').curentAnim().playing; // true/false
7828 	*/
7829 	NodeListProto.currentAnim = function() {
7830 		/*!debug*/
7831 			if (arguments.length !== 0) {
7832 				glow.debug.warn('[wrong count] glow.NodeList#currentAnim expects 0 arguments, not ' + arguments.length + '.');
7833 			}
7834 		/*gubed!*/
7835 		return this.data('glow_currentAnim') || new glow.anim.Anim(0);
7836 	}
7837 	
7838 	/**
7839 		@name glow.NodeList#lastQueuedAnim
7840 		@function
7841 		@description Get the last animation added via {@link glow.NodeList#queueAnim queueAnim} for this element
7842 			If no animation has been added, an empty animation is returned.
7843 			This means you don't need to check to see if the item is defined before
7844 			calling methods on it.
7845 			
7846 			This method acts on the first item in the NodeList.
7847 		
7848 		@returns {glow.anim.Anim}
7849 	*/
7850 	NodeListProto.lastQueuedAnim = function() {
7851 		/*!debug*/
7852 			if (arguments.length !== 0) {
7853 				glow.debug.warn('[wrong count] glow.NodeList#lastQueuedAnim expects 0 arguments, not ' + arguments.length + '.');
7854 			}
7855 		/*gubed!*/
7856 		return this.data('glow_lastQueuedAnim') || new glow.anim.Anim(0);
7857 	}
7858 	
7859 	/**
7860 		@private
7861 		@function
7862 		@description This function generates the various anim shortcut functions
7863 	*/
7864 	function animShortcut(animName, animReverseName, animPropsFunc, defaultTween, onComplete, additionalFunc) {
7865 		return function(duration, opts) {
7866 			/*!debug*/
7867 				if (arguments.length > 2) {
7868 					glow.debug.warn('[wrong count] glow.NodeList animation shortcuts expect 0, 1 or 2 arguments, not ' + arguments.length + '.');
7869 				}
7870 				if (duration !== undefined && typeof duration !== 'number') {
7871 					glow.debug.warn('[wrong type] glow.NodeList animation shortcuts expect number as "duration" argument, not ' + typeof duration + '.');
7872 				}
7873 				if (opts !== undefined && typeof opts !== 'object') {
7874 					glow.debug.warn('[wrong type] glow.NodeList animation shortcuts expect object as "opts" argument, not ' + typeof opts + '.');
7875 				}
7876 			/*gubed!*/
7877 			
7878 			opts = opts || {};
7879 			
7880 			var item,
7881 				reverseAnim,
7882 				currentAnim,
7883 				calcDuration,
7884 				anim,
7885 				i = this.length;
7886 				
7887 			opts.tween = opts.tween || defaultTween;
7888 			
7889 			if (duration === undefined) {
7890 				duration = 1;
7891 			}
7892 			
7893 			calcDuration = duration;
7894 			
7895 			while (i--) {
7896 				item = this.item(i);
7897 				currentAnim = item.data('glow_' + animName);
7898 				// if this isn't an element ,or we're already animating it, skip
7899 				if ( item[0].nodeType !== 1 || (currentAnim && currentAnim.playing) ) { continue; }
7900 				
7901 				// if there's a reverse anim happening & it's playing, get rid
7902 				reverseAnim = item.data('glow_' + animReverseName);
7903 				if (reverseAnim && reverseAnim.playing) {
7904 					// reduce the duration if we're not fading out as much
7905 					calcDuration = duration * (reverseAnim.position / reverseAnim.duration);
7906 					
7907 					reverseAnim.stop().destroy();
7908 				}
7909 				
7910 				item.data('glow_' + animName,
7911 					anim = item.anim( calcDuration, animPropsFunc(item), opts ).on('complete', onComplete, item)
7912 				);
7913 				
7914 				additionalFunc && additionalFunc(anim, item, opts);
7915 			}
7916 			
7917 			return this;
7918 		}
7919 	};
7920 	
7921 	/**
7922 		@name glow.NodeList#fadeIn
7923 		@function
7924 		@description Fade elements in
7925 			If the element is currently fading out, the fadeOut animation will be automatically stopped.
7926 		
7927 		@param {number} [duration=1] Duration in seconds
7928 		@param {Object} [opts] Options object
7929 		@param {function|string} [opts.tween='easeOut'] The motion of the animation.
7930 			Strings are treated as properties of {@link glow.tweens}, although
7931 			a tween function can be provided.
7932 			
7933 		@returns {glow.NodeList}
7934 		
7935 		@example
7936 			// make a tooltip fade in & out
7937 			var tooltip = glow('#emailTooltip');
7938 			
7939 			glow('#emailInput').on('focus', function() {
7940 				tooltip.fadeIn();
7941 			}).on('blur', function() {
7942 				tooltip.fadeOut();
7943 			});
7944 	*/
7945 	NodeListProto.fadeIn = animShortcut('fadeIn', 'fadeOut', function(item) {
7946 		item.css('display', 'block');
7947 		return {opacity: 1};
7948 	}, 'easeOut', function() {
7949 		// on complete
7950 		// we remove the filter from IE to bring back cleartype
7951 		if (glow.env.ie) {
7952 			this[0].style.filter = '';
7953 		}
7954 	});
7955 	
7956 	/**
7957 		@name glow.NodeList#fadeOut
7958 		@function
7959 		@description Fade elements out
7960 			If the element is currently fading in, the fadeIn animation will be automatically stopped.
7961 		
7962 		@param {number} [duration=1] Duration in seconds
7963 		@param {Object} [opts] Options object
7964 		@param {function|string} [opts.tween='easeIn'] The motion of the animation.
7965 			Strings are treated as properties of {@link glow.tweens}, although
7966 			a tween function can be provided.
7967 			
7968 		@returns {glow.NodeList}
7969 		
7970 		@example
7971 			// make a tooltip fade in & out
7972 			var tooltip = glow('#emailTooltip');
7973 			
7974 			glow('#emailInput').on('focus', function() {
7975 				tooltip.fadeIn();
7976 			}).on('blur', function() {
7977 				tooltip.fadeOut();
7978 			});
7979 	*/
7980 	NodeListProto.fadeOut = animShortcut('fadeOut', 'fadeIn', function() {
7981 		return {opacity:0}
7982 	}, 'easeIn', function() {
7983 		this.css('display', 'none');
7984 	});
7985 	
7986 	/**
7987 		@name glow.NodeList#fadeToggle
7988 		@function
7989 		@description Fade elements in/out
7990 			If the element is currently fading in/out, the fadeIn/fadeOut animation
7991 			will be automatically stopped.
7992 			
7993 			// Implementation note: (delete me later)
7994 			If the element has an opactity of 0, then fade in, otherwise fade out.
7995 			UNLESS there's fadeOut animation currently happening on this element,
7996 			then fade in.
7997 			
7998 		@param {number} [duration=1] Duration in seconds
7999 		@param {Object} [opts] Options object
8000 		@param {function|string} [opts.tween] The motion of the animation.
8001 			Strings are treated as properties of {@link glow.tweens}, although
8002 			a tween function can be provided.
8003 			
8004 			By default, 'easeIn' is used for fading out, and 'easeOut' is
8005 			used for fading in.
8006 			
8007 		@returns {glow.NodeList}
8008 		
8009 		@example
8010 			// make a tooltip fade in & out
8011 			var tooltip = glow('#emailTooltip');
8012 			
8013 			glow('#toggleTooltip').on('click', function() {
8014 				tooltip.fadeToggle();
8015 			});
8016 	*/
8017 	NodeListProto.fadeToggle = function(duration, opts) {
8018 		var i = this.length,
8019 			item,
8020 			fadeOutAnim;
8021 		
8022 		while (i--) {
8023 			item = this.item(i);
8024 			if (item[0].nodeType === 1) {
8025 				// if the element has an opacity of 0, or is currently fading out
8026 				if ( item.css('opacity') === '0' || ((fadeOutAnim = item.data('glow_fadeOut')) && fadeOutAnim.playing) ) {
8027 					item.fadeIn(duration, opts);
8028 				}
8029 				else {
8030 					item.fadeOut(duration, opts);
8031 				}
8032 			}
8033 		}
8034 		
8035 		return this;
8036 	};
8037 	
8038 	/**
8039 		@name glow.NodeList#slideOpen
8040 		@function
8041 		@description Slide elements open
8042 			This animates an element's height from its current height to its
8043 			full auto-height size.
8044 			
8045 			If the element is currently sliding shut, the slideShut animation
8046 			will be automatically stopped.
8047 		
8048 		@param {number} [duration=1] Duration in seconds
8049 		@param {Object} [opts] Options object
8050 			@param {function|string} [opts.tween='easeBoth'] The motion of the animation.
8051 				Strings are treated as properties of {@link glow.tweens}, although
8052 				a tween function can be provided.
8053 			@param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element.
8054 				This means the bottom of the content is shown first, rather than the top.
8055 			
8056 		@returns {glow.NodeList}
8057 		
8058 		@example
8059 			var menuContent = glow('#menu div.content');
8060 			
8061 			glow('#menu').on('mouseenter', function() {
8062 				menuContent.slideOpen();
8063 			}).on('mouseleave', function() {
8064 				menuContent.slideShut();
8065 			});
8066 		
8067 		@example
8068 			glow('#furtherInfoHeading').on('click', function() {
8069 				glow('#furtherInfoContent').slideOpen();
8070 			});
8071 			
8072 		@example
8073 			// add content onto an element, and slide to reveal the new content
8074 			glow('<div>' + newContent + '</div>').appendTo('#content').height(0).slideOpen();
8075 			
8076 	*/
8077 	NodeListProto.slideOpen = animShortcut('slideOpen', 'slideShut', function(item) {		
8078 		var currentHeight = item.css('height'),
8079 			fullHeight;
8080 		
8081 		if ( item.css('overflow') === 'visible' ) {
8082 			item.css('overflow', 'hidden');
8083 		}
8084 		
8085 		item.css('height', 'auto');
8086 		fullHeight = item.height();
8087 		item.css('height', currentHeight);
8088 		return {height: fullHeight}
8089 	}, 'easeBoth', function() {
8090 		this.css('height', 'auto').scrollTop(0);
8091 	}, lockToBottom);
8092 	
8093 	/**
8094 		@name glow.NodeList#slideShut
8095 		@function
8096 		@description Slide elements shut
8097 			This animates an element's height from its current height to zero.
8098 			
8099 			If the element is currently sliding open, the slideOpen animation
8100 			will be automatically stopped.
8101 		
8102 		@param {number} [duration=1] Duration in seconds
8103 		@param {Object} [opts] Options object
8104 			@param {function|string} [opts.tween='easeBoth'] The motion of the animation.
8105 				Strings are treated as properties of {@link glow.tweens}, although
8106 				a tween function can be provided.
8107 			@param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element.
8108 				This means the top of the content is hidden first, rather than the bottom.
8109 			
8110 		@returns {glow.NodeList}
8111 		
8112 		@example
8113 			var menuContent = glow('#menu div.content');
8114 			
8115 			glow('#menu').on('mouseenter', function() {
8116 				menuContent.slideOpen();
8117 			}).on('mouseleave', function() {
8118 				menuContent.slideShut();
8119 			});
8120 	*/
8121 	NodeListProto.slideShut = animShortcut('slideShut', 'slideOpen', function(item) {
8122 		if ( item.css('overflow') === 'visible' ) {
8123 			item.css('overflow', 'hidden');
8124 		}
8125 		return {height: 0}
8126 	}, 'easeBoth', function() {}, lockToBottom);
8127 	
8128 	/**
8129 		@private
8130 		@function
8131 		@description Add frame listener to lock content to the bottom of an item.
8132 		@param {glow.anim.Anim} anim Anim to alter
8133 		@param {glow.NodeList} element Element being animated
8134 		@param {Object} opts Options from slide[Open|Shut|Toggle]
8135 	*/
8136 	function lockToBottom(anim, element, opts) {
8137 		var node = element[0],
8138 			scrollHeight = node.scrollHeight;
8139 		
8140 		if (opts.lockToBottom) {
8141 			anim.on('frame', function() {
8142 				element.scrollTop( scrollHeight - node.offsetHeight );
8143 			});
8144 		}
8145 	}
8146 	
8147 	/**
8148 		@name glow.NodeList#slideToggle
8149 		@function
8150 		@description Slide elements open/shut
8151 			If the element is currently sliding open/shut, the slideOpen/slideShut animation
8152 			will be automatically stopped.
8153 			
8154 			// Implementation note: (delete me later)
8155 			If the element has a height of 0, then slide open, otherwise slide shut.
8156 			UNLESS there's slideShut animation currently happening on this element,
8157 			then slide open.
8158 		
8159 		@param {number} [duration=1] Duration in seconds
8160 		@param {Object} [opts] Options object
8161 			@param {function|string} [opts.tween='easeBoth'] The motion of the animation.
8162 				Strings are treated as properties of {@link glow.tweens}, although
8163 				a tween function can be provided.
8164 			@param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element.
8165 				This means the top of the content is hidden first & shown last.
8166 			
8167 		@returns {glow.NodeList}
8168 		
8169 		@example
8170 			var menuContent = glow('#menuContent');
8171 			
8172 			glow('#toggleMenu').on('click', function() {
8173 				menuContent.slideToggle();
8174 			});
8175 	*/
8176 	NodeListProto.slideToggle = function(duration, opts) {
8177 		var i = this.length,
8178 			item,
8179 			slideShutAnim;
8180 		
8181 		while (i--) {
8182 			item = this.item(i);
8183 			if (item[0].nodeType === 1) {
8184 				// if the element has an height of 0, or is currently sliding shut
8185 				if ( item.height() === 0 || ((slideShutAnim = item.data('glow_slideShut')) && slideShutAnim.playing) ) {
8186 					item.slideOpen(duration, opts);
8187 				}
8188 				else {
8189 					item.slideShut(duration, opts);
8190 				}
8191 			}
8192 		}
8193 		
8194 		return this;
8195 	};
8196 });
8197 /**
8198 	@name glow.net
8199 	@namespace
8200 	@description Methods for getting data & resources from other locations. Sometimes referred to as AJAX.
8201 */
8202 Glow.provide(function(glow) {
8203 	var net = {},
8204 		undefined,
8205 		emptyFunc = function(){};
8206 	
8207 	/**
8208 		@private
8209 		@function
8210 		@description Create XhrRequest factory methods
8211 		
8212 		@param {string} method HTTP method
8213 		@returns {function} Factory method
8214 	*/
8215 	function createXhrFactory(method) {
8216 		return function(url, data, opts) {
8217 			// only put & post use the data param
8218 			if (method === 'POST' || method === 'PUT') {
8219 				opts = opts || {};
8220 				opts.data = data;
8221 			}
8222 			else {
8223 				opts = data;
8224 			}
8225 			
8226 			return new net.XhrRequest(method, url, opts);
8227 		}
8228 	}
8229 	
8230 	/**
8231 		@name glow.net.get
8232 		@function
8233 		@description Makes an HTTP GET request to a given url.
8234 			This is a shortcut to creating an instance of {@link glow.net.XhrRequest}.
8235 	 
8236 		@param {string} url Url to make the request to.
8237 			This can be a relative path. You cannot make requests for files on
8238 			other domains (including sub-domains). For cross-domain requests, see
8239 			{@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}.
8240 		@param {Object} [opts] Options.
8241 			These options are the same as the constructor options for {@link glow.net.XhrRequest}.
8242 	 
8243 		@returns {glow.net.XhrRequest}
8244 	 
8245 		@example
8246 			glow.net.get('myFile.html').on('load', function(response){
8247 				alert( 'Got file:' + response.text() );
8248 			}).on('error', function(response){
8249 				alert( 'Something went wrong:' + response.text() );
8250 			});
8251 			
8252 	*/
8253 	net.get = createXhrFactory('GET');
8254 	
8255 	/**
8256 		@name glow.net.post
8257 		@function
8258 		@description Makes an HTTP POST request to a given url
8259 			This is a shortcut to creating an instance of {@link glow.net.XhrRequest}.
8260 		 
8261 		@param {string} url Url to make the request to.
8262 			This can be a relative path. You cannot make requests for files on
8263 			other domains (including sub-domains). For cross-domain requests, see
8264 			{@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}.
8265 		@param {Object|String} data Data to send.
8266 			This can be either a JSON-style object or a urlEncoded string.
8267 		@param {Object} [opts] Options.
8268 			These options are the same as the constructor options for {@link glow.net.XhrRequest}.
8269 		
8270 		@returns {glow.net.XhrRequest}
8271 	 
8272 		@example
8273 			glow.net.post('myFile.html', {
8274 				key: 'value',
8275 				otherkey: ['value1', 'value2']
8276 			}).on('load', function(response) {
8277 				alert( 'Got file:' + response.text() );
8278 			});
8279 	*/
8280 	net.post = createXhrFactory('POST');
8281 	
8282 	/**
8283 		@name glow.net.put
8284 		@function
8285 		@description Makes an HTTP PUT request to a given url
8286 			This is a shortcut to creating an instance of {@link glow.net.XhrRequest}.
8287 		 
8288 		@param {string} url Url to make the request to.
8289 			This can be a relative path. You cannot make requests for files on
8290 			other domains (including sub-domains). For cross-domain requests, see
8291 			{@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}.
8292 		@param {Object|String} data Data to send.
8293 			This can be either a JSON-style object or a urlEncoded string.
8294 		@param {Object} [opts] Options.
8295 			These options are the same as the constructor options for {@link glow.net.XhrRequest}.
8296  
8297 		@returns {glow.net.XhrRequest}
8298  
8299 		@example
8300 			glow.net.put('myFile.html', {
8301 				key: 'value',
8302 				otherkey: ['value1', 'value2']
8303 			}).on('load', function(response) {
8304 				// handle response
8305 			});
8306 	*/
8307 	net.put = createXhrFactory('PUT');
8308 	
8309 	/**
8310 		@name glow.net.del
8311 		@function
8312 		@description Makes an HTTP DELETE request to a given url.
8313 			This is a shortcut to creating an instance of {@link glow.net.XhrRequest}.
8314 		 
8315 		@param {string} url Url to make the request to.
8316 			This can be a relative path. You cannot make requests for files on
8317 			other domains (including sub-domains). For cross-domain requests, see
8318 			{@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}.
8319 		@param {Object} [opts] Options.
8320 			These options are the same as the constructor options for {@link glow.net.XhrRequest}.
8321  
8322 		@returns {glow.net.XhrRequest}
8323  
8324 		@example
8325 			glow.net.del('myFile.html').on('load', function(response) {
8326 				// handle response
8327 			});
8328 	*/
8329 	
8330 	net.del = createXhrFactory('DELETE');		
8331 		
8332 	// export
8333 	glow.net = net;
8334 });
8335 Glow.provide(function(glow) {
8336 	var undefined,
8337 		XhrRequestProto,
8338 		events = glow.events,
8339 		removeAllListeners = events.removeAllListeners;
8340 	
8341 	/**
8342 		@private
8343 		@function
8344 		@description Creates an XMLHttpRequest transport
8345 		@returns XMLHttpRequest
8346 	*/
8347 	var xmlHTTPRequest = window.ActiveXObject ?
8348 		function() {
8349 			return new ActiveXObject('Microsoft.XMLHTTP');
8350 		} :
8351 		function() {
8352 			return new XMLHttpRequest();
8353 		};
8354 		
8355 	/**
8356 		@private
8357 		@function
8358 		@description Apply option object defaults.
8359 		
8360 		@param {object} opts Options object to apply defaults to.
8361 		@param {string} method HTTP method.
8362 		
8363 		@returns {object} New opts object with defaults applied.
8364 	*/
8365 	function applyOptsDefaults(opts, method) {
8366 		opts = glow.util.apply({
8367 			headers: {}
8368 		}, opts);
8369 		
8370 		var headers = opts.headers;
8371 		
8372 		// convert data to string
8373 		if (typeof opts.data === 'object') {
8374 			opts.data = glow.util.encodeUrl(opts.data);
8375 		}
8376 		
8377 		// add requested with header if one hasn't been added
8378 		if ( !headers['X-Requested-With'] ) {
8379 			headers['X-Requested-With'] = 'XMLHttpRequest';
8380 		}
8381 		
8382 		if (method !== 'GET' && !headers["Content-Type"]) {
8383 			headers["Content-Type"] = 'application/x-www-form-urlencoded;';
8384 		}
8385 		
8386 		return opts;
8387 	}
8388 	
8389 	/**
8390 		@name glow.net.XhrRequest
8391 		@class
8392 		@param {string} method The HTTP method to use for the request.
8393 			Methods are case sensitive in some browsers.
8394 		@param {string} url Url to make the request to.
8395 			This can be a relative path. You cannot make requests for files on
8396 			other domains (including sub-domains). For cross-domain requests, see
8397 			{@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}.
8398 		@param {Object} [opts] Options object
8399 			@param {Object} [opts.headers] A hash of headers to send along with the request.
8400 				eg `{'Accept-Language': 'en-gb'}`
8401 			@param {boolean} [opts.cacheBust=false] Prevent the browser returning a cached response.
8402 				If true, a value is added to the query string to ensure a fresh version of the
8403 	 			file is being fetched.
8404 			@param {number} [opts.timeout] Time to allow for the request in seconds.
8405 				No timeout is set by default. Once the time is reached, the error
8406 				event will fire with a '408' status code.
8407 			@param {boolean} [opts.forceXml=false] Treat the response as XML.
8408 				This will allow you to use {@link glow.net.XhrResponse#xml response.xml()}
8409 				even if the response has a non-XML mime type.
8410 			@param {Object|string} [opts.data] Data to send.
8411 				This can be either a JSON-style object or a urlEncoded string.
8412 				
8413 		@description Create an XHR request.
8414 			Most common requests can be made using shortcuts methods in {@link glow.net},
8415 			such as {@link glow.net.get}.
8416 		
8417 		@example
8418 			new glow.net.XhrRequest('DELETE', 'whatever.php', {
8419 				timeout: 10
8420 			}).on('load', function(response) {
8421 				alert( response.text() );
8422 			});
8423 	*/
8424 	function XhrRequest(method, url, opts) {
8425 		this._opts = opts = applyOptsDefaults(opts, method);
8426 		
8427 		var request = this,
8428 			nativeRequest = request.nativeRequest = xmlHTTPRequest(), //request object
8429 			i;
8430 
8431 		// add the cacheBust to the url
8432 		if (opts.cacheBust) {
8433 			url = url + (url.indexOf('?') === -1 ? '?' : '&') + 'cachebuster=' + new Date().valueOf();
8434 		}
8435 
8436 		request.complete = false;
8437 		
8438 		//open needs to go first to maintain cross-browser support for readystates
8439 		nativeRequest.open(method, url, true);
8440  
8441 		//add custom headers
8442 		for (i in opts.headers) {
8443 			nativeRequest.setRequestHeader( i, opts.headers[i] );
8444 		}
8445 		
8446 		// force the reponse to be treated as xml
8447 		// IE doesn't support overrideMineType, we need to deal with that in {@link glow.net.XhrResponse#xml}
8448 		if (opts.forceXml && nativeRequest.overrideMimeType) {
8449 			nativeRequest.overrideMimeType('application/xml');
8450 		}
8451 		
8452 		//sort out the timeout if there is one
8453 		if (opts.timeout) {			 
8454 			request._timeout = setTimeout(function() {
8455 				var response = new glow.net.XhrResponse(request, true);
8456 				request.abort().fire('error', response);
8457 			}, opts.timeout * 1000);
8458 		}
8459 		
8460 		nativeRequest.onreadystatechange = function() {
8461 			if (nativeRequest.readyState === 4) {
8462 				var response = new glow.net.XhrResponse(request);
8463 				
8464 				//clear the timeout
8465 				clearTimeout(request._timeout);
8466 				
8467 				//set as completed
8468 				request.completed = true;
8469 				request.fire(response.successful ? 'load' : 'error', response);
8470 				
8471 				// prevent parent scopes leaking (cross-page) in IE
8472 				nativeRequest.onreadystatechange = new Function();
8473 				removeAllListeners(request);
8474 			}
8475 		};
8476 		
8477 		// make sure it doesn't complete before listeners are attached
8478 		setTimeout(function() {
8479 			nativeRequest.send(opts.data || null);
8480 		}, 0);
8481 	}
8482 	glow.util.extend(XhrRequest, events.Target);
8483 	XhrRequestProto = XhrRequest.prototype;
8484 	
8485 	/**
8486 		@name glow.net.XhrRequest#_timeout
8487 		@private
8488 		@description setTimeout ID
8489 		@type number
8490 	*/
8491 	
8492 	/**
8493 		@name glow.net.XhrRequest#complete
8494 		@description Boolean indicating whether the request has completed
8495 		@example
8496 			// request.complete with an asynchronous call
8497 			var request = glow.net.get(
8498 				"myFile.html").on('load', 
8499 				function(response){
8500 					alert(request.complete); // returns true
8501 				})
8502 				
8503 					
8504 		@type boolean
8505 	*/
8506 	
8507 	/**
8508 		@name glow.net.XhrRequest#nativeRequest
8509 		@description The request object from the browser.
8510 			This may not have the same properties and methods across user agents.
8511 			Also, this will be undefined if the request originated from getJsonp.
8512 			
8513 		@type Object
8514 	*/
8515 	
8516 	/**
8517 		@name glow.net.XhrRequest#abort
8518 		@function
8519 		@description Aborts a request
8520 			The load & error events will not fire.
8521 		@example
8522 			var request = glow.net.get('myFile.html').on('load', function(response) {
8523 				//handle response
8524 			}).on('abort', function() {
8525 				alert('Something bad happened. The request was aborted.');
8526 			});
8527 			
8528 			request.abort(); // alerts "Something bad happened.  The request was aborted"
8529 		@returns this
8530 	*/
8531 	XhrRequestProto.abort = function() {
8532 		if ( !this.completed && !this.fire('abort').defaultPrevented() ) {
8533 			clearTimeout(this._timeout);
8534 			this.nativeRequest.onreadystatechange = new Function();
8535 			removeAllListeners(this);
8536 		}
8537 		return this;
8538 	};
8539 		 
8540 	/**
8541 		@name glow.net.XhrRequest#event:load
8542 		@event
8543 		@param {glow.net.XhrResponse} response
8544 		@description Fired when the request is sucessful
8545 			This will be fired when request returns with an HTTP code of 2xx. 
8546 	*/
8547  
8548 	/**
8549 		@name glow.net.XhrRequest#event:abort
8550 		@event
8551 		@param {glow.events.Event} event Event Object
8552 		@description Fired when the request is aborted
8553 			If you cancel the default (eg, by returning false) the request
8554 			will continue.
8555 	*/
8556  
8557 	/**
8558 		@name glow.net.XhrRequest#event:error
8559 		@event
8560 		@param {glow.net.XhrResponse} response
8561 		@description Fired when the request is unsucessful
8562 			This will be fired when request returns with an HTTP code which
8563 			isn't 2xx or the request times out.
8564 	*/
8565 	
8566 	glow.net.XhrRequest = XhrRequest;
8567 });
8568 Glow.provide(function(glow) {
8569 	var XhrResponseProto,
8570 		util = glow.util;
8571 	
8572 	/**
8573 		@name glow.net.XhrResponse
8574 		@class
8575 		@extends glow.events.Event
8576 		@description The event object for {@link glow.net.XhrRequest}'s 'load' & 'error' events.
8577 		@glowPrivateConstructor There is no direct constructor.
8578 	*/
8579 	
8580 	/*
8581 		These params are hidden as we don't want users to try and create instances of this...
8582 	 
8583 		@param {glow.net.XhrRequest} [request] Original request object
8584 		@param {Boolean} [timedOut=false] Set to true if the response timed out
8585 		
8586 	*/
8587 	function XhrResponse(request, timedOut) {
8588 		var nativeResponse = this.nativeResponse = request.nativeRequest;
8589 		this._request = request;
8590 		
8591 		//IE reports status as 1223 rather than 204, for laffs
8592 		this.status = timedOut ? 408 :
8593 			nativeResponse.status == 1223 ? 204 : nativeResponse.status;
8594 
8595 		this.timedOut = !!timedOut;
8596 			
8597 		this.successful = (this.status >= 200 && this.status < 300) ||
8598 			//from cache
8599 			this.status == 304 ||
8600 			//watch our for requests from file://
8601 			(this.status == 0 && nativeResponse.responseText);
8602 	}
8603 
8604 	util.extend(XhrResponse, glow.events.Event);
8605 	XhrResponseProto = XhrResponse.prototype;
8606 	
8607 	/**
8608 		@name glow.net.XhrResponse#_request
8609 		@private
8610 		@description Original request object
8611 		@type glow.net.XhrRequest
8612 	*/
8613 	
8614 	/**
8615 		@name glow.net.XhrResponse#nativeResponse
8616 		@description The response object from the browser.
8617 			This may not have the same properties and methods across user agents.
8618 		@type XMLHttpRequest
8619 	*/
8620 	
8621 	/**
8622 		@name glow.net.XhrResponse#status
8623 		@description HTTP status code of the response
8624 		@type number
8625 	*/
8626 
8627 	/**
8628 		@name glow.net.XhrResponse#timedOut
8629 		@description Boolean indicating if the requests time out was reached.
8630 		@type boolean
8631 	*/
8632 	
8633 	/**
8634 		@name glow.net.XhrResponse#successful
8635 		@description  Boolean indicating if the request returned successfully.
8636 		@type boolean
8637 	*/
8638 	
8639 	/**
8640 		@name glow.net.XhrResponse#text
8641 		@function
8642 		@description Gets the body of the response as plain text
8643 		@returns {string}
8644 	*/
8645 
8646 	XhrResponseProto.text = function() {
8647 		return this.nativeResponse.responseText;
8648 	};
8649 	
8650 	/**
8651 		@name glow.net.XhrResponse#xml
8652 		@function
8653 		@description Gets the body of the response as xml
8654 		@returns {XML} 
8655 	*/
8656 	
8657 	XhrResponseProto.xml = function() {
8658 		var nativeResponse = this.nativeResponse,
8659 			contentType = this.header("Content-Type");
8660 		
8661 		if (
8662 			// IE 6 & 7 fail to recognise Content-Types ending +xml (eg application/rss+xml)
8663 			// Files from the filesystem don't have a content type, but could be xml files, parse them to be safe
8664 			glow.env.ie && (
8665 				contentType.slice(-4) === '+xml' ||
8666 				contentType === '' ||
8667 				this._request._opts.forceXml
8668 			)
8669 		) {
8670 			var doc = new ActiveXObject("Microsoft.XMLDOM");
8671 			doc.loadXML( nativeResponse.responseText );
8672 			return doc;
8673 		}
8674 		else {
8675 			return nativeResponse.responseXML;
8676 		}				
8677 	};
8678 	
8679 	/**
8680 		@name glow.net.XhrResponse#json
8681 		@function
8682 		@description Gets the body of the response as a JSON object.
8683 		
8684 		@param {boolean} [safeMode=false]
8685 			If true, the response will be parsed using a string parser which
8686 			will filter out non-JSON javascript, this will be slower but
8687 			recommended if you do not trust the data source.
8688 	
8689 		@returns {object}
8690 	*/
8691 	XhrResponseProto.json = function(safe) {
8692 		return util.decodeJson(this.text(), {safeMode:safe});
8693 	};
8694 	
8695 	/**
8696 		@name glow.net.XhrResponse#nodeList
8697 		@function
8698 		@description Gets the body of the response as a {@link glow.NodeList}.
8699 	
8700 		@returns {glow.NodeList}
8701 	*/
8702 	XhrResponseProto.nodeList = function(safe) {
8703 		return glow(
8704 			glow.NodeList._strToNodes( this.text() )
8705 		);
8706 	};
8707 
8708 	/**
8709 		@name glow.net.XhrResponse#header
8710 		@function
8711 		@description Gets a header from the response.
8712 	
8713 		@param {string} name Header name
8714 		@returns {string} Header value
8715 	
8716 		@example var contentType = myResponse.header("Content-Type");
8717 	*/
8718 	
8719 	XhrResponseProto.header = function(name) {
8720 		return this.nativeResponse.getResponseHeader(name);
8721 	};
8722 
8723 	/**
8724 		@name glow.net.XhrResponse#statusText
8725 		@function
8726 		@description Gets the meaning of {@link glow.net.XhrResponse#status status}.
8727 	
8728 		@returns {string}
8729 	*/
8730 	XhrResponseProto.statusText = function() {
8731 		return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText;
8732 	};
8733 	
8734 	glow.net.XhrResponse = XhrResponse;
8735 });
8736 Glow.provide(function(glow) {
8737 	var undefined,
8738 		JsonpRequestProto,
8739 		net = glow.net,
8740 		emptyFunc = function(){},
8741 		events = glow.events,
8742 		// Script elements that have been added via {@link glow.net.jsonp jsonp}, keyed by callback name
8743 		scriptElements = {},
8744 		scriptElementsLen = 0,
8745 		callbackPrefix = 'c',
8746 		// Name of the global object used to store jsonp callbacks
8747 		globalObjectName = '_' + glow.UID + 'jsonp',
8748 		head = glow('head'),
8749 		// a reference to the global object holding the callbacks
8750 		globalObject;
8751 	
8752 	/**
8753 		@private
8754 		@function
8755 		@description Handle jsonp load.
8756 		@param {glow.net.JsonpRequest} request
8757 		@param {Object[]} args Arguments object passed to the callback from the jsonp source
8758 	*/
8759 	function jsonpLoad(request, args) {
8760 		// we have to call listeners manually as we don't provide a real event object. A bit of a hack.
8761 		var loadListeners = events._getListeners(request).load,
8762 			i;
8763 			
8764 		if (loadListeners) {
8765 			loadListeners = loadListeners.slice(0);
8766 			i = loadListeners.length;
8767 			while (i--) {
8768 				loadListeners[i][0].apply( loadListeners[i][1], args );
8769 			}
8770 		}
8771 		//set as completed
8772 		request.completed = true;
8773 
8774 		cleanUp(request);
8775 	}
8776 	
8777 	/**
8778 		@private
8779 		@function
8780 		@description Clean up to avoid memory leaks
8781 		@param {glow.net.JsonpRequest} request
8782 		@param {boolean} [leaveEmptyFunc] Replace global callback with blank function.
8783 			If false, the global callback will be set to undefined, which is better for memory,
8784 			but in some cases the callback may later be called (like a timed out request) so an
8785 			empty function needs to be used to avoid errors.
8786 	*/
8787 	function cleanUp(request, leaveEmptyFunc) {
8788 		var callbackName = request._callbackName;
8789 		
8790 		clearTimeout(request._timeout);
8791 		globalObject[callbackName] = leaveEmptyFunc ? emptyFunc : undefined;
8792 		glow( scriptElements[callbackName] ).destroy();
8793 		scriptElements[callbackName] = undefined;
8794 	}
8795 	
8796 	/**
8797 		@name glow.net.JsonpRequest
8798 		@class
8799 		@description A JSONP request.
8800 			Although instance of this can be created manually, using
8801 			{@link glow.net.jsonp} is preferred. 
8802 	*/
8803 	// the params for this are the same as {@link glow.net.jsonp}.
8804 	function JsonpRequest(url, opts) {
8805 		opts = opts || {};
8806 		
8807 		var newIndex = scriptElements.length,
8808 			//script element that gets inserted on the page
8809 			//generated name of the callback
8810 			callbackName = this._callbackName = callbackPrefix + (scriptElementsLen++),
8811 			// script element to add to the page
8812 			script = scriptElements[callbackName] = document.createElement('script'),
8813 			request = this,
8814 			timeout = opts.timeout,
8815 			charset = opts.charset;
8816 		
8817 		// add the callback name to the url
8818 		url = glow.util.interpolate(url, {
8819 			callback: globalObjectName + '.' + callbackName
8820 		});
8821 		
8822 		// create the global object if it doesn't exist already
8823 		globalObject || ( globalObject = window[globalObjectName] = {} );
8824 		
8825 		// create our callback
8826 		globalObject[callbackName] = function() {
8827 			jsonpLoad(request, arguments);
8828 		};
8829 		
8830 		// set charset
8831 		charset && (script.charset = charset);
8832 		
8833 		if (opts.timeout) {
8834 			request._timeout = setTimeout(function() {
8835 				request.abort().fire('error');
8836 			}, timeout * 1000);
8837 		}
8838 			
8839 		script.src = url;
8840 		
8841 		//add script to page
8842 		head.prepend(script);
8843 		
8844 		script = undefined;
8845 	}
8846 	
8847 	glow.util.extend(JsonpRequest, events.Target);
8848 	JsonpRequestProto = JsonpRequest.prototype;
8849 	
8850 	/**
8851 		@name glow.net.JsonpRequest#_callbackName
8852 		@private
8853 		@description The name of the callback, used as a property name in globalObject and scriptElements
8854 	*/
8855 	
8856 	/**
8857 		@name glow.net.JsonpRequest#_timeout
8858 		@private
8859 		@description timeout ID
8860 		@type number
8861 	*/
8862 	
8863 	/**
8864 		@name glow.net.JsonpRequest#complete
8865 		@description Boolean indicating whether the request has completed
8866 		@type boolean
8867 	*/
8868 	JsonpRequestProto.complete = false;
8869 	
8870 	/**
8871 		@name glow.net.JsonpRequest#abort
8872 		@function
8873 		@description Abort the request.
8874 			The script file may still load, but the 'load' event will not fire.
8875 		@returns this
8876 	*/
8877 	JsonpRequestProto.abort = function() {
8878 		this.fire('abort');
8879 		cleanUp(this, true);
8880 		return this;
8881 	};
8882 	
8883 	/**
8884 		@name glow.net.JsonpRequest#event:load
8885 		@event
8886 		@description Fired when the request is sucessful.
8887 			The parameters to this event are whatever the datasource provides.
8888 			
8889 		@example
8890 			glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}')
8891 				.on('load', function(data) {
8892 					alert(data);
8893 				});
8894 	*/
8895  
8896 	/**
8897 		@name glow.net.JsonpRequest#event:abort
8898 		@event
8899 		@param {glow.events.Event} event Event Object
8900 		@description Fired when the request is aborted.
8901 	*/
8902  
8903 	/**
8904 		@name glow.net.JsonpRequest#event:error
8905 		@event
8906 		@param {glow.events.Event} event Event Object
8907 		@description Fired when the request times out.
8908 	*/
8909 	
8910 	/**
8911 		@name glow.net.jsonp
8912 		@function
8913 		@description Fetch JSON via JSONP.
8914 			This can be used cross domain, but should only be used with trusted
8915 			sources as any javascript included in the script will be executed.
8916 			
8917 			This method only works if the server allows you to specify a callback
8918 			name for JSON data. Not all JSON sources support this, check the API of the
8919 			data source to ensure you're using the correct querystring parameter
8920 			to set the callback name.
8921 	 
8922 		@param {string} url Url of the script.
8923 			Set the callback name via the querystring to `{callback}`, Glow will
8924 			replace this with another value and manage the callback internally.
8925 			
8926 			Check the API of your data source for the correct parameter name.
8927 			Eg, in Flickr it's `jsoncallback={callback}`, in Twitter it's
8928 			`callback={callback}`.
8929 		@param {object} [opts]
8930 			@param {number} [opts.timeout] Time to allow for the request in seconds.
8931 			@param {string} [opts.charset] Charset attribute value for the script.
8932 	 
8933 		@returns {glow.net.JsonpRequest}
8934 	 
8935 		@example
8936 			glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}', {
8937 				timeout: 5
8938 			}).on('load', function(data) {
8939 				alert(data);
8940 			}).on('error', function() {
8941 				alert('Request timeout');
8942 			});
8943 	*/
8944 	net.jsonp = function(url, opts) {
8945 		return new glow.net.JsonpRequest(url, opts);
8946 	};
8947 	
8948 	glow.net.JsonpRequest = JsonpRequest;
8949 });
8950 Glow.provide(function(glow) {
8951 	var undefined,
8952 		ResourceRequestProto,
8953 		ResourceResponseProto,
8954 		net = glow.net;
8955 	
8956 	/**
8957 		@private
8958 		@function
8959 		@description Normalise urls param.
8960 			Normalise ResourceRequest's urls parameter to an object with 'css', 'js' and 'img' properties.
8961 	*/
8962 	function normaliseUrlsParam(urls) {
8963 		var r = {
8964 				js: [],
8965 				css: [],
8966 				img: []
8967 			},
8968 			url;
8969 		
8970 		if (typeof urls === 'object' && !urls.push) {
8971 			r = glow.util.apply(r, urls);
8972 		}
8973 		else {
8974 			// convert urls to an array if need be
8975 			typeof urls === 'string' && ( urls = [urls] );
8976 			
8977 			// forwards loop, maintain order
8978 			for (var i = 0, len = urls.length; i < len; i++) {
8979 				url = urls[i];
8980 				if ( url.slice(-4) === '.css' ) {
8981 					r.css[r.css.length] = url;
8982 				}
8983 				else if ( url.slice(-3) === '.js' ) {
8984 					r.js[r.js.length] = url;
8985 				}
8986 				else {
8987 					r.img[r.img.length] = url;
8988 				}
8989 			}
8990 		}
8991 		
8992 		return r;
8993 	}
8994 	
8995 	/**
8996 		@name glow.net.ResourceRequest
8997 		@class
8998 		@description Request made via {@link glow.net.getResources}
8999 		@glowPrivateConstructor There is no direct constructor.
9000 	*/
9001 	function ResourceRequest(urls) {
9002 		urls = normaliseUrlsParam(urls);
9003 		
9004 		var request = this,
9005 			js = urls.js,
9006 			css = urls.css,
9007 			img = urls.img,
9008 			jsLen = js.length,
9009 			cssLen = css.length,
9010 			imgLen = img.length,
9011 			i;
9012 		
9013 		request.totalResources = jsLen + cssLen + imgLen;
9014 		
9015 		// ensure events don't fire until they're added
9016 		setTimeout(function() {
9017 			// guess it makes sense to load CSS, js then images (the browser will queue the requests)
9018 			for (i = 0; i < cssLen; i++) {
9019 				loadCss( request, css[i] );
9020 			}
9021 			for (i = 0; i < jsLen; i++) {
9022 				loadJs( request, js[i] );
9023 			}
9024 			for (i = 0; i < imgLen; i++) {
9025 				loadImg( request, img[i] );
9026 			}
9027 		}, 0);
9028 		
9029 	}
9030 	
9031 	glow.util.extend(ResourceRequest, glow.events.Target);
9032 	ResourceRequestProto = ResourceRequest.prototype;
9033 	
9034 	/**
9035 		@name glow.net.ResourceRequest#totalResources
9036 		@type number
9037 		@description Total number of resources requested.
9038 	*/
9039 	ResourceRequestProto.totalResources = 0;
9040 	
9041 	/**
9042 		@name glow.net.ResourceRequest#totalLoaded
9043 		@type number
9044 		@description Total number of resources successfully loaded.
9045 	*/
9046 	ResourceRequestProto.totalLoaded = 0;
9047 	
9048 	/**
9049 		@private
9050 		@function
9051 		@description Update a request after a resource loads.
9052 		
9053 		@param {glow.net.ResourceRequest} request.
9054 		@param {string} url Url of the requested resource.
9055 		@param {glow.NodeList} resource The element used to load the resource.
9056 		@param {string} type 'js', 'css' or 'img'
9057 	*/
9058 	function progress(request, url, resource, type) {
9059 		
9060 		var totalLoaded = ++request.totalLoaded;
9061 		
9062 		request.fire('progress', {
9063 			resource: resource,
9064 			url: url,
9065 			type: type
9066 		});
9067 		
9068 		if (totalLoaded === request.totalResources) {
9069 			request.fire('load');
9070 		}
9071 	}
9072 	
9073 	/**
9074 		@private
9075 		@function
9076 		@description Start loading an image
9077 		
9078 		@param {glow.net.ResourceRequest} request
9079 		@param {string} imgUrl
9080 	*/
9081 	function loadImg(request, imgUrl) {
9082 		var img = new Image;
9083 		// keep the url in its original format
9084 		glow(img).data('srcUrl', imgUrl).on('load', imgLoaded, request);
9085 		img.src = imgUrl;
9086 	}
9087 	
9088 	/**
9089 		@private
9090 		@function
9091 		@description Process a loaded image.
9092 			'this' is the ResourceRequest
9093 	*/
9094 	function imgLoaded(event) {
9095 		var img = glow(event.attachedTo);
9096 		progress( this, img.data('srcUrl'), img, 'img' );
9097 	}
9098 	
9099 	/**
9100 		@private
9101 		@function
9102 		@description Start loading a script
9103 		
9104 		@param {glow.net.ResourceRequest} request
9105 		@param {string} scriptUrl
9106 	*/
9107 	function loadJs(request, scriptUrl){
9108 		var script = glow( document.createElement('script') )
9109 			.data('srcUrl', scriptUrl)
9110 			.prependTo('head');
9111 		
9112 		// two methods, one for IE (readystatechange) and the other for others
9113 		script.on('readystatechange', jsLoaded, request).on('load', jsLoaded, request);
9114 		
9115 		script[0].src = scriptUrl;
9116 	}
9117 	
9118 	/**
9119 		@private
9120 		@function
9121 		@description Process a loaded script.
9122 			'this' is the ResourceRequest
9123 	*/
9124 	function jsLoaded(event) {
9125 		var script = glow(event.attachedTo),
9126 			scriptElm = script[0],
9127 			readyState = scriptElm.readyState;
9128 		
9129 		if ( !readyState || readyState === 'loaded' || readyState === 'complete' ) {
9130 			// remove events to prevent double-firing
9131 			script.detach('readystatechange', jsLoaded).detach('load', jsLoaded);
9132 			progress( this, script.data('srcUrl'), script, 'js' );
9133 		}
9134 	}
9135 	
9136 	/**
9137 		@private
9138 		@function
9139 		@description Start loading a CSS file
9140 		
9141 		@param {glow.net.ResourceRequest} request
9142 		@param {string} cssUrl
9143 	*/
9144 	// This technique was found in http://code.google.com/p/ajaxsoft/source/browse/trunk/xLazyLoader
9145 	function loadCss(request, cssUrl){
9146 		var currentHostname,
9147 			urlHostname,
9148 			link = glow('<link rel="stylesheet" type="text/css" media="all" href="' + cssUrl + '" />').data('srcUrl', cssUrl);
9149 		
9150 		// we have to do something special for Gecko browsers when the css is from another domain
9151 		if ( glow.env.gecko && /^(?:https?\:|\/\/)/.test(cssUrl) ) {
9152 			currentHostname = location.hostname.replace('www.', '');
9153 			urlHostname = cssUrl.replace(/https?:\/\/|www\.|:.*/g, '').replace(/\/.*/g, '');
9154 			
9155 			if (currentHostname !== urlHostname) {
9156 				// ack, we have to cheat
9157 				setTimeout(function() {
9158 					cssLoaded.call(request, {
9159 						attachedTo: link
9160 					});
9161 				}, 500);
9162 			}
9163 		}
9164 		else {
9165 			// two methods, one for IE (readystatechange), and one for opera
9166 			link.on('readystatechange', cssLoaded, request).on('load', cssLoaded, request);
9167 			// ...and one more for Moz & webkit
9168 			(function pollCssRules() {
9169 				try {
9170 					link[0].sheet.cssRules;
9171 					// we'll error before the next line if CSS hasn't loaded
9172 					cssLoaded.call(request, {
9173 						attachedTo: link
9174 					});
9175 				}
9176 				catch (e) {
9177 					if ( !link.data('loaded') ) {
9178 						setTimeout(pollCssRules, 20);
9179 					}
9180 				};
9181 			})();
9182 		}
9183 		
9184 		//link[0].href = cssUrl;
9185 		link.prependTo('head');
9186 	}
9187 	
9188 	/**
9189 		@private
9190 		@function
9191 		@description Process a loaded stylesheet.
9192 			'this' is the ResourceRequest
9193 	*/
9194 	function cssLoaded(event) {
9195 		var link = glow(event.attachedTo),
9196 			linkElm = link[0],
9197 			readyState = linkElm.readyState;
9198 		
9199 		if ( !readyState || readyState === 'loaded' || readyState === 'complete' ) {
9200 			// just incase there's a timeout still waiting
9201 			if ( link.data('loaded') ) {
9202 				return;
9203 			}
9204 			link.data('loaded', true);
9205 			
9206 			// remove events to prevent double-firing
9207 			link.detach('readystatechange', cssLoaded).detach('load', cssLoaded);
9208 			progress( this, link.data('srcUrl'), link, 'css' );
9209 		}
9210 	}
9211 	
9212 	
9213 	/**
9214 		@name glow.net.ResourceRequest#event:load
9215 		@event
9216 		@param {glow.events.Event} event Event Object
9217 		@description Fired when all the requested items have completed.
9218 	*/
9219  
9220 	/**
9221 		@name glow.net.ResourceRequest#event:progress
9222 		@event
9223 		@description Fired when a single resource loads.
9224 		
9225 		@param {glow.events.Event} event Event Object
9226 			@param {string} event.url Url of the loaded resource.
9227 			@param {glow.NodeList} event.resource The element used to load the resource.
9228 				This will be a `<script>`, `<link>`, or `<img>` element.
9229 			@param {string} event.type 'js', 'css' or 'img'.
9230 	*/
9231 	
9232 	/**
9233 		@name glow.net.getResources
9234 		@function
9235 		@description Load scripts, images & CSS.
9236 			Files can be loaded from other domains.
9237 			
9238 			Note: Due to a cross-browser restriction, 'load' may fire before
9239 			CSS files from another domain are fully loaded in Gecko browsers.
9240 	 
9241 		@param {string[]|string|Object} url
9242 			Url(s) to load. Urls ending in ".css" are assumed to be CSS files,
9243 			Urls ending in ".js" are assumed to be JavaScript. All other files
9244 			will be treated as images.
9245 			
9246 			You can provide an object in the form `{js: [], css: [], img: []}` to
9247 			be explicit about how to treat each file.
9248 			
9249 		@returns {glow.net.ResourceRequest}
9250 	 
9251 		@example
9252 			// load a single CSS file with a callback specified
9253 			glow.net.getResources('/styles/custom.css').on('load', function() {
9254 				// CSS has now loaded
9255 			});
9256 			
9257 		@example
9258 			// load a single CSS file with a callback specified
9259 			glow.net.getResources([
9260 				'/imgs/whatever.png',
9261 				'/style/screen.css',
9262 			]).on('load', function() {
9263 				// CSS & image now loaded
9264 			});
9265 			
9266 		@example
9267 			// load multiple files by specifying and array
9268 			glow.net.getResources({
9269 				js: ['http://www.server.com/script', 'http://www.server.com/anotherScript'],
9270 				img: ['http://www.server.com/product4/thumb']
9271 			}).on('progress', function(event) {
9272 				// update a progress meter
9273 			}).on('load', function(response){
9274 				// files now loaded
9275 			});
9276 	*/
9277 	net.getResources = function(urls, opts) {
9278 		/*!debug*/
9279 			if (arguments.length < 1 && arguments.length > 2) {
9280 				glow.debug.warn('[wrong count] glow.net.getResources expects 1 or 2 arguments, not ' + arguments.length + '.');
9281 			}
9282 		/*gubed!*/
9283 		return new glow.net.ResourceRequest(urls, opts);
9284 	};
9285 	
9286 	glow.net.ResourceRequest = ResourceRequest;	
9287 });
9288 Glow.provide(function(glow) {
9289 	var undefined,
9290 		CrossDomainRequestProto,
9291 		CrossDomainResponseProto,
9292 		net = glow.net,
9293 		// We borrow some methods from XhrRequest later
9294 		XhrResponseProto = net.XhrRequest.prototype,
9295 		Target = glow.events.Target;
9296 
9297 	/**
9298 		@name glow.net.CrossDomainRequest
9299 		@class
9300 		@description Cross-domain request via window.name
9301 			A request made via a form submission in a hidden iframe, with the
9302 			result being communicated via the name attribute of the iframe's window.
9303 			
9304 			The URL that's requested should respond with a blank HTML page containing JavaScript
9305 			that assigns the result to window.name as a string:
9306  
9307 			`<script type="text/javascript">window.name = 'result string';</script>`
9308 			
9309 			Instances of this are returned by shortcut methods {@link glow.net.crossDomainGet}
9310 			and {@link glow.net.crossDomainPost}
9311 	
9312 		@param {string} method The HTTP method to use for the request.
9313 			Only 'POST' and 'GET' are considered cross-browser.
9314 		@param {string} url The URL to request.
9315 		@param {Object} [opts]
9316 			@param {Object|string} [opts.data] Data to send.
9317 				This can be either a JSON-style object or a urlEncoded string.
9318 			@param {number} [opts.timeout] Time to allow for the request in seconds.
9319 				No timeout is set by default.
9320 			@param {string} [opts.blankUrl='/favicon.ico']
9321 				The path of a page on same domain as the caller, ideally a page likely
9322 				to be in the user's cache.
9323 	*/
9324 	function CrossDomainRequest(method, url, opts) {
9325 		var request = this,
9326 			timeout;
9327 		
9328 		request._opts = opts = glow.util.apply({
9329 			data: {},
9330 			blankUrl: '/favicon.ico'
9331 		}, opts);
9332 		
9333 		// convert data to object
9334 		if (typeof opts.data === 'string') {
9335 			opts.data = glow.util.decodeUrl(opts.data);
9336 		}
9337 		
9338 		// set timeout for the request
9339 		timeout = opts.timeout;
9340 		if (timeout) {
9341 			request._timeout = setTimeout(function () {				
9342 				request.fire('error');					
9343 				cleanup(request);
9344 			}, timeout * 1000);
9345 		}
9346 		
9347 		addIframe(request);
9348 		buildAndSubmitForm(request, method, url);
9349 	}
9350 	glow.util.extend(CrossDomainRequest, Target);
9351 	CrossDomainRequestProto = CrossDomainRequest.prototype;
9352 	
9353 	/**
9354 		@name glow.net.CrossDomainRequest#_opts
9355 		@private
9356 		@type Object
9357 		@description Options object with defaults applied
9358 	*/
9359 	
9360 	/**
9361 		@name glow.net.CrossDomainRequest#_iframe
9362 		@private
9363 		@type glow.NodeList
9364 		@description Iframe used to send the data.
9365 	*/
9366 	
9367 	/**
9368 		@name glow.net.CrossDomainRequest#_timeout
9369 		@private
9370 		@type number
9371 		@description setTimeout id for request timeout
9372 	*/
9373 
9374 	/**
9375 		@private
9376 		@function
9377 		@description Add a hidden iframe for posting the request
9378 		@param {glow.net.CrossDomainRequest} request
9379 	*/
9380 	function addIframe(request) {
9381 		var iframe = request._iframe = glow(
9382 			'<iframe style="visibility: hidden; position: absolute; height: 0;"></iframe>'
9383 		).appendTo(document.body);
9384 	};
9385 		
9386 	/**
9387 		@private
9388 		@function
9389 		@description Add a form to the iframe & submit it
9390 		
9391 		@param {glow.net.CrossDomainRequest} request
9392 		@param {string} method The HTTP method to use for the request.
9393 			Only 'POST' and 'GET' are considered cross-browser.
9394 		@param {string} url The URL to request.
9395 	*/
9396 	function buildAndSubmitForm(request, method, url) {
9397 		var iframe = request._iframe,
9398 			win = iframe[0].contentWindow,
9399 			doc = win.document,
9400 			form,
9401 			data = request._opts.data;
9402 
9403 		// IE needs an empty document to be written to written to the iframe
9404 		if (glow.env.ie) {
9405 			doc.open();
9406 			doc.write('<html><body></body></html>');
9407 			doc.close();
9408 		}
9409 		
9410 		// create form
9411 		form = doc.createElement('form');
9412 		form.action = url;
9413 		form.method = method;
9414 
9415 		doc.body.appendChild(form);
9416 		
9417 		// build form elements
9418 		for (var i in data) {
9419 			if ( !data.hasOwnProperty(i) ) { continue; }
9420 			
9421 			if (data[i] instanceof Array) {
9422 				for (var j = 0, jLen = data[i].length; j < jLen; j++) {
9423 					addHiddenInput( form, i, this.data[i][j] );
9424 				}
9425 			}
9426 			else {
9427 				addHiddenInput( form, i, this.data[i] );
9428 			}
9429 		}
9430 		
9431 		// submit - the setTimeout makes the function run in the context of the form
9432 		win.setTimeout(function () {
9433 			form.submit();
9434 		}, 0);
9435 		
9436 		// listen for form submitting
9437 		iframe.on('load', handleResponse, request);
9438 	}
9439 
9440 	/**
9441 		@private
9442 		@function
9443 		@description Add a hidden input to a form for a piece of data.
9444 		
9445 		@param {HTMLFormElement} form
9446 		@param {string} name Input name
9447 		@param {string} value Input value
9448 	*/
9449 	function addHiddenInput(form, name, value) {
9450 		var input = form.ownerDocument.createElement('input');
9451 		input.type = 'hidden';
9452 		input.name = name;
9453 		input.value = value;
9454 		form.appendChild(input);
9455 	}
9456 
9457 	/**
9458 		@private
9459 		@function
9460 		@description Callback for load event in the hidden iframe.
9461 			`this` is the request.
9462 	*/
9463 	function handleResponse() {		
9464 		var err,
9465 			href,
9466 			win = this._iframe[0].contentWindow;
9467 		
9468 		try {
9469 			href = win.location.href;
9470 		}
9471 		catch (e) {
9472 			err = e;
9473 		}
9474 		
9475 		if (href !== 'about:blank' || err) {
9476 			clearTimeout(this._timeout);
9477 			this._iframe.detach('load', handleResponse).on('load', readHandler, this);
9478 			
9479 			win.location = window.location.protocol + '//' + window.location.host + this._opts.blankUrl;
9480 		}
9481 	}
9482 
9483 	/**
9484 		@private
9485 		@function
9486 		@description Callback for load event of blank page in same origin.
9487 			`this` is the request.
9488 	*/
9489 	function readHandler() {
9490 		this.fire( 'load', new CrossDomainResponse(this._iframe[0].contentWindow.name) );			
9491 		cleanup(this);			
9492 	}
9493 			
9494 	/**
9495 		@private
9496 		@function
9497 		@description Removes the iframe and any event listeners.
9498 		@param {glow.net.CrossDomainRequest} request
9499 	*/
9500 	function cleanup(request) {
9501 		request._iframe.destroy();
9502 		glow.events.removeAllListeners(request);
9503 	}
9504 	
9505 	/**
9506 		@name glow.net.CrossDomainRequest#event:load
9507 		@event
9508 		@param {glow.net.CrossDomainResponse} response
9509 		@description Fired when the request is sucessful.
9510 	*/
9511  
9512 	/**
9513 		@name glow.net.CrossDomainRequest#event:error
9514 		@event
9515 		@param {glow.events.Event} event Event Object
9516 		@description Fired when the request times out.
9517 	*/
9518 	
9519 	/**
9520 		@name glow.net.CrossDomainResponse
9521 		@class
9522 		@description Response object for cross-domain requests.
9523 			This is provided in {@link glow.net.CrossDomainRequest}'s 'load' event.
9524 		@glowPrivateConstructor There is no direct constructor.
9525 	*/
9526 	function CrossDomainResponse(textResponse) {
9527 		this._text = textResponse;
9528 	}
9529 	glow.util.extend(CrossDomainResponse, Target);
9530 	CrossDomainResponseProto = CrossDomainResponse.prototype;
9531 	
9532 	/**
9533 		@name glow.net.CrossDomainResponse#_text
9534 		@private
9535 		@type string
9536 		@description Text response from the server
9537 	*/
9538 	
9539 	/**
9540 		@name glow.net.CrossDomainResponse#text
9541 		@function
9542 		@description Gets the body of the response as plain text.
9543 		@returns {string}
9544 	*/
9545 	CrossDomainResponseProto.text = function() {
9546 		return this._text;
9547 	}
9548 	
9549 	/**
9550 		@name glow.net.CrossDomainResponse#json
9551 		@function
9552 		@description Gets the body of the response as a JSON object.
9553 		
9554 		@param {boolean} [safeMode=false]
9555 			If true, the response will be parsed using a string parser which
9556 			will filter out non-JSON javascript, this will be slower but
9557 			recommended if you do not trust the data source.
9558 	
9559 		@returns {object}
9560 	*/
9561 	CrossDomainResponseProto.json = XhrResponseProto.json;
9562 	
9563 	/**
9564 		@name glow.net.CrossDomainResponse#nodeList
9565 		@function
9566 		@description Gets the body of the response as a {@link glow.NodeList}.
9567 	
9568 		@returns {glow.NodeList}
9569 	*/
9570 	CrossDomainResponseProto.nodeList = XhrResponseProto.nodeList;
9571 	
9572 	// ...and now, the factory methods! Yey!
9573 	
9574 	/**
9575 		@name glow.net.crossDomainPost
9576 		@function
9577 		@description Cross-domain post via window.name
9578 			A request made via a form submission in a hidden iframe, with the
9579 			result being communicated via the name attribute of the iframe's window.
9580 			
9581 			The URL that's requested should respond with a blank HTML page containing JavaScript
9582 			that assigns the result to window.name as a string:
9583  
9584 			`<script type="text/javascript">window.name = 'result string';</script>`
9585 
9586 		@param {string} url The URL to request.
9587 		@param {Object|string} data Data to send.
9588 			This can be either a JSON-style object or a urlEncoded string.
9589 		@param {Object} [opts]
9590 			@param {number} [opts.timeout] Time to allow for the request in seconds.
9591 				No timeout is set by default.
9592 			@param {string} [opts.blankUrl='/favicon.ico']
9593 				The path of a page on same domain as the caller, ideally a page likely
9594 				to be in the user's cache.
9595 	*/
9596 	
9597 	net.crossDomainPost = function(url, data, opts) {
9598 		opts = opts || {};
9599 		opts.data = data;
9600 		return new CrossDomainRequest('POST', url, opts);
9601 	};
9602 	
9603 	
9604 	/**
9605 		@name glow.net.crossDomainGet
9606 		@function
9607 		@description Cross-domain get via window.name
9608 			A request made via a form submission in a hidden iframe, with the
9609 			result being communicated via the name attribute of the iframe's window.
9610 			
9611 			The URL that's requested should respond with a blank HTML page containing JavaScript
9612 			that assigns the result to window.name as a string:
9613  
9614 			`<script type="text/javascript">window.name = 'result string';</script>`
9615 	
9616 		@param {string} url The URL to request.
9617 		@param {Object} [opts]
9618 			@param {number} [opts.timeout] Time to allow for the request in seconds.
9619 				No timeout is set by default.
9620 			@param {string} [opts.blankUrl='/favicon.ico']
9621 				The path of a page on same domain as the caller, ideally a page likely
9622 				to be in the user's cache.
9623 	*/
9624 	
9625 	net.crossDomainGet = function(url, opts) {
9626 		return new CrossDomainRequest('GET', url, opts);
9627 	};
9628 
9629 	// export
9630 	glow.net.CrossDomainRequest  = CrossDomainRequest;
9631 	glow.net.CrossDomainResponse = CrossDomainResponse;
9632 });
9633 Glow.provide(function(glow) {
9634 	var tweens = glow.tweens = {};
9635 	/**
9636 	@name glow.tweens
9637 	@namespace
9638 	@description Functions for controlling the motion of an animation
9639 	*/
9640 	
9641 	/*
9642 	@name _reverse
9643 	@private
9644 	@description Takes a tween function and returns a function which does the reverse
9645 	*/
9646 	function _reverse(tween) {
9647 		return function(t) {
9648 			return 1 - tween(1 - t);
9649 		}
9650 	}
9651 	
9652 	/**
9653 	@name glow.tweens.linear
9654 	@function
9655 	@description Creates linear tween.	
9656 		Will transition values from start to finish with no
9657 		acceleration or deceleration.
9658 	
9659 	@returns {function}
9660 	*/
9661 	tweens.linear = function() {
9662 		return function(t) { return t; };
9663 	};
9664 	
9665 	/**
9666 	@name glow.tweens.easeIn
9667 	@function
9668 	@description Creates a tween which starts off slowly and accelerates.
9669 	
9670 	@param {number} [strength=2] How strong the easing will be.
9671 	
9672 		The higher the number the slower the animation starts and the quicker it ends.
9673 	
9674 	@returns {function}
9675 	*/
9676 	tweens.easeIn = function(strength) {
9677 		strength = strength || 2;
9678 		return function(t) {
9679 			return Math.pow(1, strength - 1) * Math.pow(t, strength);
9680 		}	
9681 	};
9682 	
9683 	
9684 	/**
9685 	@name glow.tweens.easeOut
9686 	@function
9687 	@description Creates a tween which starts off fast and decelerates.
9688 	
9689 	@param {number} [strength=2] How strong the easing will be.
9690 	
9691 		The higher the number the quicker the animation starts and the slower it ends.
9692 	
9693 	@returns {function}
9694 	*/
9695 	tweens.easeOut = function(strength) {
9696 		return _reverse(this.easeIn(strength));
9697 	};
9698 	
9699 	
9700 	/**
9701 	@name glow.tweens.easeBoth
9702 	@function
9703 	@description Creates a tween which starts off slowly, accelerates then decelerates after the half way point.
9704 	
9705 		This produces a smooth and natural looking transition.
9706 	
9707 	@param {number} [strength=2] How strong the easing is.
9708 	
9709 		A higher number produces a greater difference between
9710 		start/end speed and the mid speed.
9711 	
9712 	@returns {function}
9713 	*/
9714 	tweens.easeBoth = function(strength) {
9715 		return this.combine(this.easeIn(strength), this.easeOut(strength));
9716 	};
9717 	
9718 	
9719 	/**
9720 	@name glow.tweens.overshootIn
9721 	@function
9722 	@description Returns the reverse of {@link glow.tweens.overshootOut overshootOut}
9723 	
9724 	@param {number} [amount=1.70158] How much to overshoot.
9725 	
9726 		The default is 1.70158 which results in a 10% overshoot.
9727 	
9728 	@returns {function}
9729 	*/
9730 	tweens.overshootIn = function(amount) {
9731 		return _reverse(this.overshootOut(amount));
9732 	};
9733 	
9734 	
9735 	/**
9736 	@name glow.tweens.overshootOut
9737 	@function
9738 	@description Creates a tween which overshoots its end point then returns to its end point.
9739 	
9740 	@param {number} [amount=1.70158] How much to overshoot.
9741 	
9742 		The default is 1.70158 which results in a 10% overshoot.
9743 	
9744 	@returns {function}
9745 	*/
9746 	tweens.overshootOut = function(amount) {
9747 		amount = amount || 1.70158;
9748 		return function(t) {
9749 			if (t == 0 || t == 1) { return t; }
9750 				return ((t -= 1)* t * ((amount + 1) * t + amount) + 1);
9751 			}
9752 	};
9753 	
9754 	
9755 	/**
9756 	@name glow.tweens.overshootBoth
9757 	@function
9758 	@description Returns a combination of {@link glow.tweens.overshootIn overshootIn} and {@link glow.tweens.overshootOut overshootOut}
9759 	
9760 	@param {number} [amount=1.70158] How much to overshoot.
9761 	
9762 		The default is 1.70158 which results in a 10% overshoot.
9763 	
9764 	@returns {function}
9765 	*/
9766 	tweens.overshootBoth = function(amount) {
9767 		return this.combine(this.overshootIn(amount), this.overshootOut(amount));	
9768 	};
9769 	
9770 	
9771 	/**
9772 	@name glow.tweens.bounceIn
9773 	@function
9774 	@description Returns the reverse of {@link glow.tweens.bounceOut bounceOut}
9775 	
9776 	@returns {function}
9777 	*/
9778 	tweens.bounceIn = function() {
9779 		return _reverse(this.bounceOut());
9780 	};
9781 	
9782 	
9783 	/**
9784 	@name glow.tweens.bounceOut
9785 	@function
9786 	@description Returns a tween which bounces against the final value 3 times before stopping
9787 	
9788 	@returns {function}
9789 	*/
9790 	tweens.bounceOut = function() {
9791 		return function(t) {
9792 			if (t < (1 / 2.75)) {
9793 				return 7.5625 * t * t;
9794 			}
9795 			
9796 			else if (t < (2 / 2.75)) {
9797 				return (7.5625 * (t -= (1.5 / 2.75)) * t + .75);
9798 			}
9799 			
9800 			else if (t < (2.5 / 2.75)) {
9801 				return (7.5625 * (t -= (2.25 / 2.75)) * t + .9375);
9802 			}
9803 			
9804 			else {
9805 				return (7.5625 * (t -= (2.625 / 2.75)) * t + .984375);
9806 			}
9807 		};	
9808 	};
9809 		
9810 	
9811 	/**
9812 	@name glow.tweens.elasticIn
9813 	@function
9814 	@description Returns the reverse of {@link glow.tweens.elasticOut elasticOut}
9815 	
9816 	@param {number} [amplitude=1] How strong the elasticity will be.
9817 	
9818 	@param {number} [frequency=3.33] The frequency.
9819 	
9820 	@returns {function}
9821 	*/
9822 	tweens.elasticIn = function(amplitude, frequency) {
9823 		return _reverse(this.elasticOut(amplitude, frequency));
9824 	};
9825 	
9826 	
9827 	/**
9828 	@name glow.tweens.elasticOut
9829 	@function
9830 	@description Creates a tween which has an elastic movement.
9831 	
9832 		You can tweak the tween using the parameters but you'll
9833 		probably find the defaults sufficient.
9834 	
9835 	@param {number} [amplitude=1] How strong the elasticity is.
9836 	
9837 	@param {number} [frequency=3.33] The frequency.
9838 	
9839 	@returns {function}
9840 	*/
9841 	tweens.elasticOut = function(amplitude, frequency) {
9842 		var period = 1 / (frequency || 10 / 3);
9843 		amplitude = amplitude || 1;
9844 		return function (t) {
9845 			var s;
9846 			if (t == 0 || t == 1) {
9847 				return t;
9848 			}
9849 			if (amplitude < 1) {
9850 				s = period / 4;
9851 			}
9852 			else {
9853 				s = period / (2 * Math.PI) * Math.asin(1 / amplitude);
9854 			}
9855 			return amplitude * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / period) + 1;
9856 		}
9857 	};
9858 		
9859 	
9860 	/**
9861 	@name glow.tweens.combine
9862 	@function
9863 	@description Create a tween from two tweens.
9864 	
9865 		This can be useful to make custom tweens which, for example,
9866 		start with an easeIn and end with an overshootOut. To keep
9867 		the motion natural, you should configure your tweens so the
9868 		first ends and the same velocity that the second starts.
9869 	
9870 	@param {function} tweenIn Tween to use for the first half
9871 	
9872 	@param {function} tweenOut Tween to use for the second half
9873 	
9874 	@example
9875 		// 4.5 has been chosen for the easeIn strength so it
9876 		// ends at the same velocity as overshootOut starts.
9877 		var myTween = glow.tweens.combine(
9878 			glow.tweens.easeIn(4.5),
9879 			glow.tweens.overshootOut()
9880 		);
9881 	
9882 	@returns {function}
9883 	*/
9884 	tweens.combine = function(tweenIn, tweenOut) {
9885 		return function (t) {
9886 			if (t < 0.5) {
9887 				return tweenIn(t * 2) / 2;
9888 			}
9889 			else {
9890 				return tweenOut((t - 0.5) * 2) / 2 + 0.5;
9891 			}
9892 		}	
9893 	}
9894 	
9895 });
9896 
9897 /**
9898 	@name glow.anim
9899 	@namespace
9900 	@description Creating and synchronising animations
9901 */
9902 Glow.provide(function(glow) {
9903 	var undefined,
9904 		AnimProto,
9905 		activeAnims = [],
9906 		activeAnimsLen = 0,
9907 		animInterval;
9908 	
9909 	/**
9910 		@private
9911 		@function
9912 		@description This is called on each interval
9913 			This set the properties of each animation per frame.
9914 			
9915 			This is the drill sgt of the Anim world.
9916 	*/
9917 	function onInterval() {
9918 		var dateNum = new Date().valueOf(),
9919 			i = activeAnimsLen,
9920 			anim;
9921 		
9922 		while (i--) {
9923 			// ideally, this processing would be a function of Anim, but it's quicker this way
9924 			anim = activeAnims[i];
9925 			anim.position = (dateNum - anim._syncTime) / 1000;
9926 			
9927 			// see if this animation is ready to complete
9928 			if (anim.position >= anim.duration) {
9929 				anim.position = anim.duration;
9930 				anim.value = anim.tween(1);
9931 				// render final frame
9932 				anim.fire('frame');
9933 				// fire 'frame' and 'complete' and see if we're going to loop (preventing default)
9934 				if ( anim.fire('complete').defaultPrevented() || anim.loop ) {
9935 					// loop the animation
9936 					anim._syncTime = dateNum;
9937 				}
9938 				// else deactivave the anim
9939 				else {
9940 					// reset the stop position so further starts start from the beginning
9941 					anim._stopPos = 0;
9942 					deactivateAnim(anim);
9943 					// destroy the anim if needed
9944 					anim.destroyOnComplete && anim.destroy();
9945 				}
9946 			}
9947 			else {
9948 				// set up the value and render a frame
9949 				anim.value = anim.tween( anim.position / anim.duration );
9950 				anim.fire('frame');
9951 			}
9952 		}
9953 	}
9954 	
9955 	/**
9956 		@private
9957 		@function
9958 		@description Calls 'frame' on an animation on an interval	
9959 	*/
9960 	function activateAnim(anim) {
9961 		// if this is the first anim, start the timer
9962 		if (!activeAnimsLen) {
9963 			animInterval = setInterval(onInterval, 13);
9964 		}
9965 		activeAnims[ activeAnimsLen++ ] = anim;
9966 		anim.playing = true;
9967 	}
9968 	
9969 	/**
9970 		@private
9971 		@function
9972 		@description Stops calling 'frame' on an animation on an interval
9973 	*/
9974 	function deactivateAnim(anim) {
9975 		// decided to search forward, animations ending are more likely to be older & at the start of the array.
9976 		// This mutates activeAnims
9977 		for (var i = 0, leni = activeAnims.length; i < leni; i++) {
9978 			if (activeAnims[i] === anim) {
9979 				activeAnims.splice(i, 1);
9980 				activeAnimsLen--;
9981 				// if we're out of anims, stop the timer
9982 				if (!activeAnimsLen) {
9983 					clearInterval(animInterval);
9984 				}
9985 				anim.playing = false;
9986 				return;
9987 			}
9988 		}
9989 	}
9990 	
9991 	/**
9992 		@name glow.anim.Anim
9993 		@extends glow.events.Target
9994 		@class
9995 		@description Animate an object.
9996 			To animate CSS properties, see {@link glow.NodeList#anim}.
9997 			
9998 			Once you have an Anim instance, the {@link glow.anim.Anim#prop} method
9999 			can be used to easily animate object properties from one value to another.
10000 			If this isn't suitable, listen for the 'frame' event to change values
10001 			over time.
10002 			
10003 		@param {number} duration Length of the animation in seconds.
10004 		@param {Object} [opts] Object of options.
10005 		@param {function|string} [opts.tween='easeBoth'] The way the value changes over time.
10006 			Strings are treated as properties of {@link glow.tweens} (eg 'bounceOut'), although
10007 			a tween function can be provided.
10008 
10009 			The default is an {@link glow.tweens.easeBoth easeBoth} tween.
10010 			Looped animations will fire a 'complete' event on each loop.
10011 		@param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops).
10012 			Shortcut for {@link glow.anim.Anim#destroyOnComplete}.
10013 		@param {boolean} [opts.loop=false] Loop the animation.
10014 			Shortcut for setting {@link glow.anim.Anim#loop}.
10015 			
10016 		@example
10017 			// Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween
10018 			// feGaussianBlurElm is a reference to an <feGaussianBlur /> element.
10019 			new glow.anim.Anim(5, {
10020 				tween: 'easeOut'
10021 			}).target(feGaussianBlurElm).prop('stdDeviation', {
10022 				from: 0,
10023 				to: 8
10024 			}).start();
10025 			
10026 		@example
10027 			// Animate a CSS property we don't support in glow.NodeList#anim
10028 			// This rotates a Mozilla CSS gradient
10029 			var styleObject = glow('#nav').prop('style');
10030 			
10031 			new glow.anim.Anim(10).target(styleObject).prop('background', {
10032 				// the question-mark in the template is replaced with the animated value
10033 				template: '-moz-linear-gradient(?deg, red, blue)'
10034 				from: 0,
10035 				to: 360
10036 			}).start();
10037 			
10038 		@example
10039 			// Animate a CSS property we don't support in glow.NodeList#anim
10040 			// This changes the colour of a webkit drop shadow from yellow to blue
10041 			var styleObject = glow('#nav').prop('style');
10042 			
10043 			new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', {
10044 				// the ? in the template are replaced with the animate values
10045 				template: 'rgb(?, ?, ?) 0px 4px 14px'
10046 				// provide a 'from' and 'to' value for each question-mark
10047 				from: [255, 255, 0],
10048 				to: [0, 0, 255],
10049 				// round the value, colours can't be fractional
10050 				round: true
10051 			}).start();
10052 			
10053 		@example
10054 			// Make an ASCII progress bar animate from:
10055 			// [--------------------] 0%
10056 			// to
10057 			// [++++++++++++++++++++] 100%
10058 			var progressBar = glow('#progressBar'),
10059 				// our progress bar is 20 chars
10060 				barSize = 20;
10061 				
10062 			new glow.anim.Anim(2).on('frame', function() {
10063 				var onChars = Math.floor(this.value * barSize),
10064 					offChars = barSize - onChars,
10065 					// add the + and - chars
10066 					barStr = new Array(onChars + 1).join('+') + new Array(offChars + 1).join('-');
10067 				
10068 				progressBar.text('[' + barStr + '] ' + Math.floor(this.value * 100) + '%');
10069 			}).start();
10070 
10071 		@see {@link glow.NodeList#anim} - shortcut for animating CSS values on an element.
10072 	*/
10073 	
10074 	function Anim(duration, opts) {
10075 		/*!debug*/
10076 			if (arguments.length < 1 || arguments.length > 2) {
10077 				glow.debug.warn('[wrong count] glow.anim.Anim expects 1 or 2 arguments, not ' + arguments.length + '.');
10078 			}
10079 			if ( isNaN(duration) ) {
10080 				glow.debug.warn('[wrong type] glow.anim.Anim expects number as "duration" argument, not ' + typeof duration + '.');
10081 			}
10082 			if (opts !== undefined && typeof opts !== 'object') {
10083 				glow.debug.warn('[wrong type] glow.anim.Anim expects object as "opts" argument, not ' + typeof opts + '.');
10084 			}
10085 			if ( opts && typeof opts.tween === 'string' && !glow.tweens[opts.tween] ) {
10086 				glow.debug.warn('[unexpected value] glow.anim.Anim - tween ' + opts.tween + ' does not exist');
10087 			}
10088 		/*gubed!*/
10089 		
10090 		opts = glow.util.apply({
10091 			destroyOnComplete: true
10092 			// other options have falsey defaults
10093 		}, opts || {});
10094 		
10095 		this.destroyOnComplete = opts.destroyOnComplete;
10096 		
10097 
10098 		if (typeof opts.tween === 'string') {
10099 			this.tween = glow.tweens[opts.tween]();
10100 		}
10101 		else if (opts.tween) {
10102 			this.tween = opts.tween;
10103 		}
10104 		
10105 		this.loop = !!opts.loop;
10106 		this.duration = +duration;
10107 		// defined & used in prop.js
10108 		this._targets = [];
10109 	};
10110 	
10111 	glow.util.extend(Anim, glow.events.Target);
10112 	AnimProto = Anim.prototype;
10113 	
10114 	/**
10115 		@name glow.anim.Anim#_syncTime
10116 		@private
10117 		@type number
10118 		@description Number used to work out where the animation should be against the current date
10119 			If an animation starts at 0, this number will be new Date().valueOf(), it'll be
10120 			lower for animations that start at a midpoint
10121 	*/
10122 	
10123 	/**
10124 		@name glow.anim.Anim#_stopPos
10125 		@private
10126 		@type number
10127 		@description The position the animation was stopped at
10128 			This is set on `.stop()` and used to resume from
10129 			the same place on `.start()`
10130 	*/
10131 	
10132 	/**
10133 		@name glow.anim.Anim#duration
10134 		@type number
10135 		@description Length of the animation in seconds.
10136 	*/
10137 	
10138 	/**
10139 		@name glow.anim.Anim#tween
10140 		@type function
10141 		@description The tween used by the animation.
10142 	*/
10143 	AnimProto.tween = glow.tweens.easeBoth();
10144 	
10145 	/**
10146 		@name glow.anim.Anim#position
10147 		@readOnly
10148 		@type number
10149 		@description Position of the animation in seconds.
10150 	*/
10151 	AnimProto.position = 0;
10152 	
10153 	/**
10154 		@name glow.anim.Anim#playing
10155 		@readOnly
10156 		@type boolean
10157 		@description `true` if the animation is playing.
10158 	*/
10159 	AnimProto.playing = false;
10160 	
10161 	/**
10162 		@name glow.anim.Anim#loop
10163 		@type boolean
10164 		@description Loop the animation?
10165 			This value can be changed while an animation is playing.
10166 			
10167 			Looped animations will fire a 'complete' event at the end of each loop.
10168 	*/
10169 	
10170 	/**
10171 		@name glow.anim.Anim#destroyOnComplete
10172 		@type boolean
10173 		@description Destroy the animation once it completes (unless it loops).
10174 			This will free any DOM references the animation may have created. Once
10175 			the animation is destroyed, it cannot be started again.
10176 	*/
10177 	
10178 	/**
10179 		@name glow.anim.Anim#value
10180 		@type number
10181 		@readOnly
10182 		@description Current tweened value of the animation, usually between 0 & 1.
10183 			This can be used in frame events to change values between their start
10184 			and end value.
10185 			
10186 			The value may be greater than 1 or less than 0 if the tween
10187 			overshoots the start or end position. {@link glow.tweens.elasticOut}
10188 			for instance will result in values higher than 1, but will still end at 1.
10189 		
10190 		@example
10191 			// Work out a value between startValue & endValue for the current point in the animation
10192 			var currentValue = (endValue - startValue / myAnim.value) + startValue;
10193 	*/
10194 	AnimProto.value = 0;
10195 	
10196 	/**
10197 		@name glow.anim.Anim#start
10198 		@function
10199 		@description Starts playing the animation
10200 			If the animation is already playing, this has no effect.
10201 		
10202 		@param {number} [position] Position to start the animation at, in seconds.
10203 			By default, this will be the last position of the animation (if it was stopped)
10204 			or 0.
10205 		
10206 		@returns this
10207 	*/
10208 	AnimProto.start = function(position) {
10209 		/*!debug*/
10210 			if (arguments.length > 1) {
10211 				glow.debug.warn('[wrong count] glow.anim.Anim#start expects 0 or 1 argument, not ' + arguments.length + '.');
10212 			}
10213 			if (position !== undefined && typeof position !== 'number') {
10214 				glow.debug.warn('[wrong type] glow.anim.Anim#start expects number as "position" argument, not ' + typeof position + '.');
10215 			}
10216 		/*gubed!*/
10217 		
10218 		if ( !this.playing && !this.fire('start').defaultPrevented() ) {
10219 			// we set 'playing' here so goTo knows
10220 			this.playing = true;
10221 			this.goTo(position === undefined ? (this._stopPos || 0) : position);
10222 			activateAnim(this);
10223 		}
10224 		return this;
10225 	};
10226 	
10227 	/**
10228 		@name glow.anim.Anim#stop
10229 		@function
10230 		@description Stops the animation playing.
10231 			Stopped animations can be resumed by calling {@link glow.anim.Anim#start start}.
10232 			
10233 			If the animation isn't playing, this has no effect.
10234 		@returns this
10235 	*/
10236 	AnimProto.stop = function() {
10237 		/*!debug*/
10238 			if (arguments.length !== 0) {
10239 				glow.debug.warn('[wrong count] glow.anim.Anim#stop expects 0 arguments, not ' + arguments.length + '.');
10240 			}
10241 		/*gubed!*/
10242 		if ( this.playing && !this.fire('stop').defaultPrevented() ) {
10243 			this._stopPos = this.position;
10244 			deactivateAnim(this);
10245 		}
10246 		return this;
10247 	};
10248 	
10249 	/**
10250 		@name glow.anim.Anim#destroy
10251 		@function
10252 		@description Destroys the animation & detaches references to objects
10253 			This frees memory & is called automatically when an animation
10254 			completes.
10255 		@returns undefined
10256 	*/
10257 	AnimProto.destroy = function() {
10258 		/*!debug*/
10259 			if (arguments.length !== 0) {
10260 				glow.debug.warn('[wrong count] glow.anim.Anim#destroy expects 0 arguments, not ' + arguments.length + '.');
10261 			}
10262 		/*gubed!*/
10263 		glow.events.removeAllListeners( [this] );
10264 		this._targets = undefined;
10265 	};
10266 	
10267 	/**
10268 		@name glow.anim.Anim#goTo
10269 		@function
10270 		@description Goes to a specific point in the animation.
10271 		@param {number} pos Position in the animation to go to, in seconds
10272 
10273 		@example
10274 			// move the animation to 2.5 seconds in
10275 			// If the animation is playing, it will continue to play from the new position.
10276 			// Otherwise, it will simply move to that position.
10277 			myAnim.goTo(2.5);
10278 			
10279 		@returns this
10280 	*/
10281 	AnimProto.goTo = function(position) {
10282 		/*!debug*/
10283 			if (arguments.length !== 1) {
10284 				glow.debug.warn('[wrong count] glow.anim.Anim#goTo expects 1 argument, not ' + arguments.length + '.');
10285 			}
10286 			if (typeof position !== 'number') {
10287 				glow.debug.warn('[wrong type] glow.anim.Anim#goTo expects number as "position" argument, not ' + typeof position + '.');
10288 			}
10289 		/*gubed!*/
10290 		if (position > this.duration) {
10291 			position = this.duration;
10292 		}
10293 		else if (position < 0) {
10294 			position = 0;
10295 		}
10296 		// set stopPos to this so the next call to start() starts from here
10297 		this._stopPos = this.position = position;
10298 		// move the syncTime for this position if we're playing
10299 		if (this.playing) {
10300 			this._syncTime = new Date - (position * 1000);
10301 		}
10302 		this.value = this.tween(position / this.duration);
10303 		this.fire('frame');
10304 		return this;
10305 	};
10306 	
10307 	/**
10308 		@name glow.anim.Anim#event:start
10309 		@event
10310 		@description Fires when an animation starts.
10311 			Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault})
10312 			prevents this animation from starting.
10313 		
10314 		@param {glow.events.Event} event Event Object
10315 	*/
10316 	
10317 	/**
10318 		@name glow.anim.Anim#event:frame
10319 		@event
10320 		@description Fires on each frame of the animation
10321 			Use a combination of this event and {@link glow.anim.Anim#value value}
10322 			to create custom animations.
10323 			
10324 			See the {@link glow.anim.Anim constructor} for usage examples.
10325 		
10326 		@param {glow.events.Event} event Event Object
10327 	*/
10328 	
10329 	/**
10330 		@name glow.anim.Anim#event:stop
10331 		@event
10332 		@description Fires when an animation is stopped before completing
10333 			Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault})
10334 			prevents this animation from stopping.
10335 		
10336 		@param {glow.events.Event} event Event Object
10337 	*/
10338 	
10339 	/**
10340 		@name glow.anim.Anim#event:complete
10341 		@event
10342 		@description Fires when an animation completes
10343 			Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault})
10344 			causes the animation to loop.
10345 		
10346 		@param {glow.events.Event} event Event Object
10347 		
10348 		@example
10349 			// Make an animation loop 5 times
10350 			var loopCount = 5;
10351 			myAnim.on('complete', function() {
10352 				return !loopCount--;
10353 			});
10354 	*/
10355 	
10356 	// export
10357 	glow.anim = {};
10358 	glow.anim.Anim = Anim;
10359 });
10360 Glow.provide(function(glow) {
10361 	/**
10362 		@name glow.anim.Anim#_evalFunc
10363 		@function
10364 		@private
10365 		@description  Evals a function to be used as a frame listener
10366 			This function is isolated from the others to reduce the impact of
10367 			eval() on compression and garbage collection
10368 			
10369 			'targets' is used by the compiled function
10370 	*/
10371 	glow.anim.Anim.prototype._evalFunc = function evalFunc(s, targets) {
10372 		eval('var f=function(){' + s + '}');
10373 		return f;
10374 	}
10375 });
10376 
10377 Glow.provide(function(glow) {
10378 	var undefined,
10379 		AnimProto = glow.anim.Anim.prototype;
10380 	
10381 	/**
10382 		@name glow.anim.Anim#_targets
10383 		@private
10384 		@type Object[]
10385 		@description An array of objects added via #target
10386 	*/
10387 	
10388 	/**
10389 		@name glow.anim.Anim#target
10390 		@function
10391 		@description Set the object for subsequent calls to {@link glow.anim.Anim#prop prop} to act on.
10392 		@param {Object} newTarget The target object
10393 			
10394 		@returns this
10395 		
10396 		@example
10397 			// animate objToAnimate.value from 0 to 10 over 3 seconds
10398 			// and anotherObjToAnimate.data from -100 to 20 over 3 seconds
10399 		
10400 			var objToAnimate = {},
10401 				anotherObjToAnimate = {};
10402 		
10403 			new glow.anim.Anim(3).target(objToAnimate).prop('value', {
10404 				from: 0,
10405 				to: 10
10406 			}).target(anotherObjToAnimate).prop('data', {
10407 				from: 100,
10408 				to: -20
10409 			})
10410 	*/
10411 	AnimProto.target = function(newTarget) {
10412 		/*!debug*/
10413 			if (arguments.length !== 1) {
10414 				glow.debug.warn('[wrong count] glow.anim.Anim#target expects 1 argument, not ' + arguments.length + '.');
10415 			}
10416 			if (typeof newTarget !== 'object') {
10417 				glow.debug.warn('[wrong type] glow.anim.Anim#target expects object as "newTarget" argument, not ' + typeof newTarget + '.');
10418 			}
10419 		/*gubed!*/
10420 		this._targets[ this._targets.length ] = newTarget;
10421 		return this;
10422 	};
10423 	
10424 	/**
10425 		@name glow.anim.Anim#_funcStr
10426 		@private
10427 		@type Object
10428 		@description The string for the function _propFunc
10429 			This is retained so it can be added to for further
10430 			calls to prop
10431 	*/
10432 	AnimProto._funcStr = '';
10433 	
10434 	/**
10435 		@private
10436 		@description Returns a string that calculates the current value for a property
10437 	*/
10438 	function buildValueCalculator(from, to, max, min, round) {
10439 		// start with (from + (from - to) * this.value)
10440 		var str = '(' + Number(from) + '+' + (to - from) + '*this.value)';
10441 		
10442 		// wrap in functions to keep values within range / round values if needed
10443 		if (min !== undefined) {
10444 			str = 'Math.max(' + str + ', ' + min + ')';
10445 		}
10446 		if (max !== undefined) {
10447 			str = 'Math.min(' + str + ', ' + max + ')';
10448 		}
10449 		if (round) {
10450 			str = 'Math.round(' + str + ')';
10451 		}
10452 		
10453 		return str;
10454 	}
10455 	
10456 	/**
10457 		@private
10458 		@description Turn a template into a script that outputs values in place of ?
10459 	*/
10460 	function compileTemplate(template, from, to, max, min, round) {
10461 		// no template? That's easy.
10462 		if (!template) {
10463 			return buildValueCalculator(from, to, max, min, round);
10464 		}
10465 		
10466 		var templateParts = template.split('?'),
10467 			templatePart,
10468 			str = '"' + templateParts[0].replace(/"/g, '\\"') + '"',
10469 			// discover which values are arrays
10470 			Array = window.Array,
10471 			fromIsArray = from.constructor === Array,
10472 			toIsArray = to.constructor === Array,
10473 			maxIsArray = max !== undefined && max.constructor === Array,
10474 			minIsArray = min !== undefined && min.constructor === Array,
10475 			roundIsArray = round.constructor === Array,
10476 			iMinusOne = 0;
10477 		
10478 		for (var i = 1, leni = templateParts.length; i < leni; i++, iMinusOne++) {
10479 			templatePart = templateParts[i];
10480 			
10481 			if ( templateParts[iMinusOne].slice(-1) === '\\' ) {
10482 				// the user wants a literal question mark, put it back
10483 				str += '+"?"';
10484 			}
10485 			else {
10486 				// remove trailing slash, it's being used to escape a ?
10487 				if ( templatePart.slice(-1) === '\\' ) {
10488 					templatePart = templatePart.slice(0, -1);
10489 				}
10490 				str += '+' +
10491 					buildValueCalculator(
10492 						fromIsArray ? from[iMinusOne] : from,
10493 						toIsArray ? to[iMinusOne] : to,
10494 						maxIsArray ? max[iMinusOne] : max,
10495 						minIsArray ? min[iMinusOne] : min,
10496 						roundIsArray ? round[iMinusOne] : round
10497 					) +
10498 					'+"' + templatePart.replace(/"/g, '\\"') + '"';
10499 			}
10500 		}
10501 		return str;
10502 	}
10503 	
10504 	/**
10505 		@private
10506 		@description Builds the function for an animation object's frame listener
10507 			This function animatate object properties as instructed by #prop
10508 	*/
10509 	function buildFunction(anim, targetIndex, propName, conf) {
10510 		var targets = anim._targets,
10511 			// this is going to be our listener for the frame event
10512 			functionStr = anim._funcStr,
10513 			func;
10514 		
10515 		functionStr += 'var target=targets[' + targetIndex + '];' +
10516 			'target["' + propName.replace(/"/g, '\\"') + '"]=' +
10517 			compileTemplate(conf.template, conf.from, conf.to, conf.max, conf.min, conf.round) +
10518 			';'; 
10519 		
10520 		// retain new function string
10521 		anim._funcStr = functionStr;
10522 		
10523 		// eval to create a single function to be called
10524 		func = anim._evalFunc(functionStr, targets);
10525 		
10526 		// remove old listener & add new one
10527 		anim.detach('frame', anim._propFunc).on('frame', func);
10528 		// retain new func so we can remove it later
10529 		anim._propFunc = func;
10530 		func = functionStr = undefined;
10531 	}
10532 	
10533 	/**
10534 		@private
10535 		@description Determines the value(s) to animate from
10536 	*/
10537 	function getFromVals(propValue, conf) {
10538 		var results,
10539 			template = conf.template,
10540 			templateRegexStr;
10541 			
10542 		// this is easy if from values are already specified
10543 		// or there isn't a template to follow
10544 		if (conf.from !== undefined || !template) {
10545 			return conf.from || propValue;
10546 		}
10547 		
10548 		// turn the template into a regular expression, turning the ? into regex for detecting numbers
10549 		templateRegexStr = glow.util.escapeRegex(template).replace(/([^\\]|^)\\\?/g, '$1(\\-?(?:\\d+)?(?:\\.\\d+)?)');
10550 		results = new RegExp(templateRegexStr).exec(propValue);
10551 		if (!results) {
10552 			throw new Error('glow.anim.Anim#prop: Could not detect start values using template: ' + template);
10553 		}
10554 		else {
10555 			return Array.prototype.slice.call(results, 1);
10556 		}
10557 	}
10558 	
10559 	/**
10560 		@name glow.anim.Anim#prop
10561 		@function
10562 		@description Animate a property of an object.
10563 			This shortcut adds a listener onto the animation's 'frame' event
10564 			and changes a specific property from one value to another.
10565 			
10566 			Values can be simple, such as '42', or more complex, such as 'rgba(255, 255, 0, 0.8)'
10567 			
10568 			Before calling this, set the target object via {@link glow.anim.Anim#target}.
10569 			
10570 		@param {string} propertyName Name of the property to animate.
10571 		@param {Object} conf Animation configuration object.
10572 			All configuration properties are optional with the exception of
10573 			'to', and 'from' in some cases (conditions below).
10574 		
10575 		@param {string} [conf.template] Template for complex values
10576 			Templates can be used for values which are strings rather than numbers.
10577 			
10578 			Question-marks are used within templates as placeholders for animated
10579 			values. For instance, in the template '?em' the question-mark would be
10580 			replaced with a number resulting in animated values like '1.5em'.
10581 			
10582 			Multiple Question-marks can be used for properties with more than
10583 			one animated value, eg 'rgba(?, ?, ?, ?)'. The values will be animated
10584 			independently.
10585 			
10586 			A literal question-mark can be placed in a template by preceeding it
10587 			with a backslash.
10588 			
10589 		@param {number|number[]} [conf.from] Value(s) to animate from.
10590 			This can be a single number, or an array of numbers; one for each
10591 			question-mark in the template.
10592 			
10593 			If omitted, the from value(s) will be taken from the object. This
10594 			will fail if the current value is undefined or is in a format
10595 			different to the template.
10596 			
10597 		@param {number|number[]} conf.to Value(s) to animate to.
10598 			This can be a single number, or an array of numbers; one for each
10599 			question-mark in the template.
10600 			
10601 		@param {boolean|boolean[]} [conf.round=false] Round values to the nearest whole number.
10602 			Use this to prevent the property being set to a fractional value.
10603 			
10604 			This can be a single boolean, or an array of booleans; one for each
10605 			question-mark in the template. This is useful for templates like 'rgba(?, ?, ?, ?)',
10606 			where the rgb values need to be whole numbers, but the alpha value is
10607 			between 0-1.
10608 		
10609 		@param {number|number[]} [conf.min] Minimum value(s)
10610 			Use this to stop values animating beneath certain values.
10611 			
10612 			Eg, some tweens go beyond their end position, but heights cannot
10613 			be negative.
10614 			
10615 			This can be a single number, or an array of numbers; one for each
10616 			question-mark in the template. 'undefined' means no restriction.
10617 			
10618 		@param {number|number[]} [conf.max] Maximum value(s)
10619 			Use this to stop values animating beyond certain values.
10620 			
10621 			Eg, some tweens go beyond their end position, but colour values cannot
10622 			be greater than 255.
10623 			
10624 			This can be a single number, or an array of numbers; one for each
10625 			question-mark in the template. 'undefined' means no restriction.
10626 			
10627 		@returns this
10628 		
10629 		@example
10630 			// Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween
10631 			new glow.anim.Anim(5, {
10632 				tween: 'easeOut'
10633 			}).target(feGaussianBlurElm).prop('stdDeviation', {
10634 				from: 0,
10635 				to: 8
10636 			}).start();
10637 			
10638 		@example
10639 			// Animate a CSS property we don't support in glow.NodeList#anim
10640 			// This rotates a Mozilla CSS gradient
10641 			var styleObject = glow('#nav').prop('style');
10642 			
10643 			new glow.anim.Anim(10).target(styleObject).prop('background', {
10644 				// the question-mark in the template is replaced with the animate value
10645 				template: '-moz-linear-gradient(?deg, red, blue)'
10646 				from: 0,
10647 				to: 360
10648 			}).start();
10649 			
10650 		@example
10651 			// Animate a CSS property we don't support in glow.NodeList#anim
10652 			// This changes the colour of a webkit drop shadow from yellow to blue
10653 			var styleObject = glow('#nav').prop('style');
10654 			
10655 			new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', {
10656 				// the ? in the template are replaced with the animate values
10657 				template: 'rgb(?, ?, ?) 0px 4px 14px'
10658 				// provide a 'from' and 'to' value for each question-mark
10659 				from: [255, 255, 0],
10660 				to: [0, 0, 255],
10661 				// round the value, colours can't be fractional
10662 				round: true
10663 			}).start();
10664 	*/
10665 	AnimProto.prop = function(propName, conf) {
10666 		/*!debug*/
10667 			if (arguments.length !== 2) {
10668 				glow.debug.warn('[wrong count] glow.anim.Anim#prop expects 2 arguments, not ' + arguments.length + '.');
10669 			}
10670 			if (typeof propName !== 'string') {
10671 				glow.debug.warn('[wrong type] glow.anim.Anim#prop expects string as "propName" argument, not ' + typeof propName + '.');
10672 			}
10673 			if (typeof conf !== 'object') {
10674 				glow.debug.warn('[wrong type] glow.anim.Anim#prop expects object as "conf" argument, not ' + typeof conf + '.');
10675 			}
10676 			if (conf.to === undefined || (!conf.to.push && typeof conf.to !== 'number') ) {
10677 				glow.debug.warn('[wrong type] glow.anim.Anim#prop expects number/array as "conf.to" argument, not ' + typeof conf.to + '.');
10678 			}
10679 			if (conf.from !== undefined && (!conf.from.push && typeof conf.from !== 'number') ) {
10680 				glow.debug.warn('[wrong type] glow.anim.Anim#prop expects number/array as "conf.from" argument, not ' + typeof conf.from + '.');
10681 			}
10682 			if (conf.template !== undefined && typeof conf.template !== 'string') {
10683 				glow.debug.warn('[wrong type] glow.anim.Anim#prop expects string as "conf.template" argument, not ' + typeof conf.template + '.');
10684 			}
10685 			if (this._targets.length === 0) {
10686 				glow.debug.warn('[unmet prerequisite] glow.anim.Anim#target must be called before glow.anim.Anim#prop');
10687 			}
10688 		/*gubed!*/
10689 		
10690 		var targetIndex = this._targets.length - 1,
10691 			target = this._targets[targetIndex];
10692 		
10693 		// default conf
10694 		conf = glow.util.apply({
10695 			from: getFromVals(target[propName], conf),
10696 			round: false
10697 		}, conf);
10698 		
10699 		buildFunction(this, targetIndex, propName, conf);
10700 		
10701 		return this;
10702 	};
10703 });
10704 Glow.provide(function(glow) {
10705 	var undefined,
10706 		AnimProto = glow.anim.Anim.prototype;
10707 	
10708 	/**
10709 		@private
10710 		@description Mirrors a tween
10711 	*/
10712 	function mirrorTween(tween) {
10713 		return function(t) {
10714 			return tween(1 - t);
10715 		}
10716 	}
10717 	
10718 	/**
10719 		@name glow.anim.Anim#_preReverseTween
10720 		@private
10721 		@type function
10722 		@description This is the tween before it was reversed
10723 			This means that anim.reverse().reverse() doesn't
10724 			wrap the tween function twice, it stores it here
10725 			so it can reinstate it.
10726 	*/
10727 	
10728 	/**
10729 		@name glow.anim.Anim#reversed
10730 		@private
10731 		@type boolean
10732 		@description Is the animation in a reversed state?
10733 			This starts off as false, and is true if {@link glow.anim.Anim#reverse reverse}
10734 			is called. If reverse is called again, this is false.
10735 			
10736 			This is useful in 'complete' listeners to determine where the animation
10737 			ended.
10738 	*/
10739 	AnimProto.reversed = false;
10740 	
10741 	/**
10742 		@name glow.anim.Anim#reverse
10743 		@function
10744 		@description Reverses this animation
10745 			Adjusts the tween of this animation so it plays in reverse. If
10746 			the animation is currently playing, it will continue to play.
10747 			
10748 			The current position of the animation is also reversed, so if a
10749 			3 second animation is currently 2 seconds in, it will be one
10750 			second in when reversed.
10751 			
10752 			This is handy for animations that do something on (for example)
10753 			mouseenter, then need to animate back on mouseleave
10754 		
10755 		@returns this
10756 		
10757 		@example
10758 			// change a nav item's background colour from white to yellow
10759 			// when the mouse is over it, and back again when the mouse
10760 			// exits.
10761 			//
10762 			// If the mouse leaves the item before the animation
10763 			// completes, it animates back from whatever position it
10764 			// ended on.
10765 			glow('#nav').delegate('mouseenter', 'li', function() {
10766 				var fadeAnim = glow(this).data('fadeAnim');
10767 				
10768 				if (fadeAnim) {
10769 					// we've already created the animation, just reverse it and go!
10770 					fadeAnim.reverse().start();
10771 				}
10772 				else {
10773 					// create our animation, this will only happen once per element
10774 					glow(this).data('fadeAnim',
10775 						glow(this).anim(0.5, {
10776 							'background-color': 'yellow'
10777 						}, {
10778 							// don't destroy, we want to reuse this animation
10779 							destroyOnComplete: false
10780 						});
10781 					);
10782 				}
10783 				
10784 			}).delegate('mouseleave', 'li', function() {
10785 				// Get our animation, reverse it and go!
10786 				glow(this).data('fadeAnim').reverse().start();
10787 			});
10788 	*/
10789 	AnimProto.reverse = function() {
10790 		/*!debug*/
10791 			if (arguments.length !== 0) {
10792 				glow.debug.warn('[wrong count] glow.anim.Anim#reverse expects 0 arguments, not ' + arguments.length + '.');
10793 			}
10794 		/*gubed!*/
10795 		var newPosition = this.position && (1 - this.position / this.duration) * this.duration,
10796 			oldTween = this.tween;
10797 		
10798 		// set reversed property
10799 		this.reversed = !this.reversed;
10800 		
10801 		// reverse the tween
10802 		this.tween = this._preReverseTween || mirrorTween(this.tween);
10803 		this._preReverseTween = oldTween;
10804 		return this.goTo(newPosition);
10805 	}
10806 	
10807 	/**
10808 		@name glow.anim.Anim#pingPong
10809 		@function
10810 		@description Alters the animation so it plays forward, then in reverse
10811 			The duration of the animation is doubled.
10812 		
10813 		@returns this
10814 		
10815 		@example
10816 			// Fades #myDiv to red then back to its original colour
10817 			// The whole animation takes 2 seconds
10818 			glow('#myDiv').anim(1, {
10819 				'background-color': 'red'
10820 			}).pingPong();
10821 	*/
10822 	AnimProto.pingPong = function() {
10823 		/*!debug*/
10824 			if (arguments.length !== 0) {
10825 				glow.debug.warn('[wrong count] glow.anim.Anim#pingPong expects 0 arguments, not ' + arguments.length + '.');
10826 			}
10827 		/*gubed!*/
10828 		var oldTween = this.tween,
10829 			oldTweenReversed = mirrorTween(oldTween);
10830 		// double the length of the animation
10831 		this.duration *= 2;
10832 		this.tween = function(t) {
10833 			return (t < 0.5) ? oldTween(t * 2) : oldTweenReversed( (t - 0.5) * 2 );
10834 		}
10835 		// invalidate the stored reversed tween
10836 		this._preReverseTween = undefined;
10837 		this.reversed = false;
10838 		
10839 		return this.goTo(this.position / 2);
10840 	}
10841 });
10842 Glow.provide(function(glow) {
10843 	var undefined,
10844 		TimelineProto,
10845 		Anim = glow.anim.Anim;
10846 	
10847 	/**
10848 		@private
10849 		@description Listener for the start event on the sync anim the timeline uses
10850 			'this' is the Timeline
10851 	*/
10852 	function animStart(e) {
10853 		this.fire('start', e);
10854 		this.playing = !e.defaultPrevented();
10855 	}
10856 	
10857 	/**
10858 		@private
10859 		@description Listener for the stop event on the sync anim the timeline uses
10860 			'this' is the Timeline
10861 	*/
10862 	function animStop(e) {
10863 		this.fire('stop', e);
10864 		this.playing = e.defaultPrevented();
10865 	}
10866 	
10867 	/**
10868 		@private
10869 		@description Listener for the frame event on the sync anim the timeline uses
10870 			'this' is the Timeline
10871 	*/
10872 	function animFrame(e) {
10873 		this.goTo(this._anim.position);
10874 		// if we're still playing, fire frame
10875 		if (this._anim.playing) {
10876 			this.fire('frame', e);
10877 		}
10878 	}
10879 	
10880 	/**
10881 		@private
10882 		@description Listener for the complete event on the sync anim the timeline uses
10883 			'this' is the Timeline
10884 	*/
10885 	function animComplete(e) {
10886 		// mirror .loop
10887 		this._anim.loop = this.loop;
10888 		// fire complete with same event object so it can be cancelled by user
10889 		this.fire('complete', e);
10890 		// find out if we're going to loop, set .playing
10891 		var loop = this.playing = ( this.loop || e.defaultPrevented() );
10892 		// if we're not looping, destroy
10893 		if (!loop && this.destroyOnComplete) {
10894 			this.destroy();
10895 		}
10896 	}
10897 	
10898 	/**
10899 		@name glow.anim.Timeline
10900 		@extends glow.events.Target
10901 		@class
10902 		@description Sequence and synchronise multiple animations
10903 			This can be used to easily chain animations together
10904 			and ensure that multiple animations stay in sync
10905 			with each other.
10906 			
10907 		@param {Object} [opts] Options object.
10908 		
10909 		@param {boolean} [opts.loop=true] Loop the animation.
10910 			Looped timelines will fire a 'complete' event on each loop.
10911 			
10912 		@param {boolean} [opts.destroyOnComplete=true] Destroy animations in the timeline once it completes (unless it loops).
10913 			This will free any DOM references the animations may have created. Once
10914 			the animations are destroyed, the timeline cannot be started again.
10915 			
10916 		@example
10917 			// play 3 animations one after another
10918 			new glow.anim.Timeline().track(anim1, anim2, anim3).start();
10919 			
10920 		@example
10921 			// play 2 animations at the same time
10922 			new glow.anim.Timeline()
10923 				.track(anim1)
10924 				.track(anim2)
10925 				.start();
10926 			
10927 		@example
10928 			// play 2 animations with a second pause in between
10929 			new glow.anim.Timeline().track(anim1, 1, anim2).start();
10930 			
10931 		@example
10932 			// Make a 'mexican wave'
10933 			// #waveContainer contains 100 divs absolutely positioned next to each other
10934 			
10935 			var animTimeline = new glow.anim.Timeline({
10936 				loop: true
10937 			});
10938 			
10939 			//create a wave up & wave down anim for each div
10940 			var wavingDivs = glow("#waveContainer div").each(function(i) {
10941 				var div = glow(this);
10942 			
10943 				animTimeline.track(
10944 					// add a pause to the start of the anim, this creates the wave effect
10945 					(i / 100),
10946 					// move up & down
10947 					div.anim(1, {
10948 						top: [70, 0]
10949 					}).pingPong()
10950 				);
10951 			});
10952 			
10953 			animTimeline.start();
10954 	*/
10955 	function Timeline(opts) {
10956 		/*!debug*/
10957 			if (arguments.length > 1) {
10958 				glow.debug.warn('[wrong count] glow.anim.Timeline expects 0 or 1 arguments, not ' + arguments.length + '.');
10959 			}
10960 			if (opts !== undefined && typeof opts !== 'object') {
10961 				glow.debug.warn('[wrong type] glow.anim.Iimeline expects object as "opts" argument, not ' + typeof opts + '.');
10962 			}
10963 		/*gubed!*/
10964 		
10965 		opts = opts || {};
10966 		this.destroyOnComplete = (opts.destroyOnComplete !== false);
10967 		this.loop = !!opts.loop;
10968 		this._tracks = [];
10969 		this._currentIndexes = [];
10970 		this._startPos = [];
10971 		
10972 		// create an animation to sync the timeline
10973 		this._anim = new Anim(0, {
10974 				destroyOnComplete: false,
10975 				tween: 'linear'
10976 			})
10977 			.on('start', animStart, this)
10978 			.on('stop', animStop, this)
10979 			.on('frame', animFrame, this)
10980 			.on('complete', animComplete, this);
10981 	}
10982 	glow.util.extend(Timeline, glow.events.Target);
10983 	TimelineProto = Timeline.prototype;
10984 	
10985 	/**
10986 		@name glow.anim.Timeline#duration
10987 		@type number
10988 		@description Length of the animation in seconds
10989 		
10990 		// implementation note: (delete this later)
10991 		This will need to be generated after each call to #track
10992 		Won't be too expensive, just work out the length of the new
10993 		track and Math.max(newTrack, this.duration)
10994 	*/
10995 	TimelineProto.duration = 0;
10996 	
10997 	/**
10998 		@name glow.anim.Timeline#position
10999 		@type number
11000 		@description Position of the animation in seconds
11001 	*/
11002 	TimelineProto.position = 0;
11003 	
11004 	/**
11005 		@name glow.anim.Timeline#playing
11006 		@description true if the animation is playing.
11007 		@returns {boolean}
11008 	*/
11009 	TimelineProto.playing = false;
11010 	
11011 	/**
11012 		@name glow.anim.Timeline#loop
11013 		@description Loop the animation?
11014 			This value can be changed while the animation is playing.
11015 			
11016 			Looped animations will fire a 'complete' event on each loop.
11017 			
11018 		@returns {boolean}
11019 	*/
11020 	
11021 	/**
11022 		@name glow.anim.Timeline#destroyOnComplete
11023 		@type boolean
11024 		@description Destroy the animation once it completes (unless it loops)?
11025 			This will free any DOM references the animation may have created. Once
11026 			the animation is destroyed, it cannot be started again.
11027 	*/
11028 	
11029 	/**
11030 		@name glow.anim.Timeline#_tracks
11031 		@private
11032 		@type Array[]
11033 		@description An array of arrays.
11034 			Each array represents a track, containing a combination of
11035 			animations and functions
11036 	*/
11037 	
11038 	/**
11039 		@name glow.anim.Timeline#_currentIndexes
11040 		@private
11041 		@type number[]
11042 		@description Array of the current indexes within _tracks
11043 			The indexes refer to which items that were last sent a .goTo90
11044 	*/
11045 	
11046 	/**
11047 		@name glow.anim.Timeline#_startPos
11048 		@private
11049 		@type Array[]
11050 		@description Mirrors _tracks
11051 			Contains the start positions of the items in _tracks
11052 	*/
11053 	
11054 	/**
11055 		@name glow.anim.Timeline#_anim
11056 		@private
11057 		@type glow.anim.Anim
11058 		@description The single animation used to fire frames for this animation
11059 	*/
11060 	
11061 	/**
11062 		@name glow.anim.Timeline#_lastPos
11063 		@private
11064 		@type number
11065 		@description Last position rendered
11066 	*/
11067 	TimelineProto._lastPos = 0;
11068 	
11069 	/**
11070 		@name glow.anim.Timeline#start
11071 		@function
11072 		@description Starts playing the animation
11073 		
11074 		@param {number} [start] Position to start the animation at, in seconds.
11075 			By default, this will be the last position of the animation (if it was stopped)
11076 			or 0.
11077 		
11078 		@returns this
11079 	*/
11080 	TimelineProto.start = function() {
11081 		this._anim.start();
11082 		return this;
11083 	};
11084 	
11085 	/**
11086 		@name glow.anim.Timeline#stop
11087 		@function
11088 		@description Stops the animation playing.
11089 			Stopped animations can be resumed by calling {@link glow.anim.Timeline#start start}.
11090 		@returns this
11091 	*/
11092 	TimelineProto.stop = function() {
11093 		/*!debug*/
11094 			if (arguments.length !== 0) {
11095 				glow.debug.warn('[wrong count] glow.anim.Timeline#stop expects 0 arguments, not ' + arguments.length + '.');
11096 			}
11097 		/*gubed!*/
11098 		
11099 		var i = this._tracks.length,
11100 			item;
11101 		
11102 		this._anim.stop();
11103 		// check in case the event has been cancelled
11104 		if (!this._anim.playing) {
11105 			while (i--) {
11106 				// get the current playing item for this track
11107 				item = this._tracks[i][ this._currentIndexes[i] ];
11108 				// check there is an item playing
11109 				if (item) {
11110 					item.fire('stop');
11111 					item.playing = false;
11112 				}
11113 			}
11114 		}
11115 		return this;
11116 	};
11117 	
11118 	/**
11119 		@name glow.anim.Timeline#destroy
11120 		@function
11121 		@description Destroys all animations in the timeline & detaches references to DOM nodes
11122 			This frees memory & is called automatically when the animation completes
11123 		@returns undefined
11124 	*/
11125 	TimelineProto.destroy = function() {
11126 		/*!debug*/
11127 			if (arguments.length !== 0) {
11128 				glow.debug.warn('[wrong count] glow.anim.Timeline#destroy expects 0 arguments, not ' + arguments.length + '.');
11129 			}
11130 		/*gubed!*/
11131 		
11132 		var i = this._tracks.length,
11133 			j,
11134 			item;
11135 		
11136 		// destroy animations in tracks	
11137 		while (i--) {
11138 			j = this._tracks[i].length;
11139 			while (j--) {
11140 				item = this._tracks[i][j];
11141 				item.destroy && item.destroy();
11142 			}
11143 		}
11144 		
11145 		// destroy syncing animation
11146 		this._anim.destroy();
11147 		// remove listeners
11148 		glow.events.removeAllListeners( [this] );
11149 		this._tracks = undefined;
11150 		
11151 	};
11152 
11153 	/**
11154 		@private
11155 		@function
11156 		@description Moves a timeline forward onto timeline.position
11157 			This deals with moving all the tracks forward from their
11158 			current position to the new position. This is done on
11159 			every frame, via timeline.goTo
11160 	*/
11161 	function moveForward(timeline) {
11162 		var i = timeline._tracks.length,
11163 			track,
11164 			item,
11165 			itemIndex,
11166 			itemStart,
11167 			timelinePosition = timeline.position;
11168 		
11169 		while (i--) {
11170 			track = timeline._tracks[i];
11171 			itemIndex = timeline._currentIndexes[i];
11172 
11173 			while ( item = track[itemIndex] ) {
11174 				itemStart = timeline._startPos[i][itemIndex];
11175 				// deal with functions in the timeline
11176 				if (typeof item === 'function') {
11177 					item();
11178 					itemIndex++;
11179 					break;
11180 				}
11181 				// deal with animations in the timeline
11182 				else if (timelinePosition - itemStart >= item.duration) {
11183 					// the animation we're currently playing has come to
11184 					// an end, play the last frame and move on to the next
11185 					item.goTo(item.duration).fire('complete');
11186 					item.playing = false;
11187 				}
11188 				else {
11189 					// the animation we're playing is somewhere in the middle
11190 					if (!item.playing) {
11191 						// ohh, we're just starting this animation
11192 						item.fire('start');
11193 						item.playing = true;
11194 					}
11195 					item.goTo(timelinePosition - itemStart);
11196 					// we're not done with this item, break
11197 					break;
11198 				}
11199 				itemIndex++;
11200 			}
11201 			timeline._currentIndexes[i] = itemIndex;
11202 		}
11203 	}
11204 	
11205 	/**
11206 		@private
11207 		@function
11208 		@description
11209 			This goes through all animations that start after the new position
11210 			& before the previous position and calls their first frames.
11211 	*/
11212 	function moveBackward(timeline) {
11213 		var i = timeline._tracks.length,
11214 			j,
11215 			track,
11216 			item,
11217 			itemStart,
11218 			timelinePosition = timeline.position;
11219 		
11220 		while (i--) {
11221 			track = timeline._tracks[i];
11222 			j = timeline._currentIndexes[i] + 1;
11223 			
11224 			while (j--) {
11225 				item = track[j];
11226 				
11227 				if (!item) {
11228 					continue;
11229 				}
11230 				// we don't need to reset items before the new position,
11231 				// their frames are rendered by 'moveForward'
11232 				if ( timeline._startPos[i][j] < timeline.position ) {
11233 					break;
11234 				}
11235 				// we only want to deal with animations
11236 				if (typeof item !== 'function') {
11237 					item.goTo(0);
11238 				}
11239 			}
11240 			
11241 			timeline._currentIndexes[i] = j;
11242 		}
11243 		
11244 		// as a shortcut, we use 'moveForward' to trigger the frame for the new position
11245 		// on the current items
11246 		moveForward(timeline);
11247 	}
11248 	
11249 	/**
11250 		@name glow.anim.Timeline#goTo
11251 		@function
11252 		@description Goes to a specific point in the animation.
11253 		@param {number} position Position in the animation to go to, in seconds
11254 		
11255 		@example
11256 			// move the animation to 2.5 seconds in
11257 			// If the animation is playing, it will continue to play from the new position.
11258 			// Otherwise, it will simply move to that position.
11259 			myTimeline.goTo(2.5);
11260 			
11261 		@returns {glow.anim.Timeline}
11262 	*/
11263 	TimelineProto.goTo = function(position) {
11264 		/*!debug*/
11265 			if (arguments.length !== 1) {
11266 				glow.debug.warn('[wrong count] glow.anim.Timeline#goTo expects 1 argument, not ' + arguments.length + '.');
11267 			}
11268 			if (typeof position !== 'number') {
11269 				glow.debug.warn('[wrong type] glow.anim.Timeline#goTo expects number as "position" argument, not ' + typeof position + '.');
11270 			}
11271 		/*gubed!*/
11272 		
11273 		var resetAll;
11274 		if (position > this.duration) {
11275 			position = this.duration;
11276 		}
11277 		else if (position < 0) {
11278 			position = 0;
11279 		}
11280 		
11281 		this.position = position;
11282 		
11283 		(position < this._lastPos) ? moveBackward(this) : moveForward(this);
11284 		
11285 		this._lastPos = position;
11286 		return this;
11287 	};
11288 	
11289 	/**
11290 		@private
11291 		@description This method is applied to animations / timeline when they're adopted
11292 	*/
11293 	function methodNotAllowed() {
11294 		throw new Error('Cannot call this method on items contained in a timeline');
11295 	}
11296 	
11297 	/**
11298 		@private
11299 		@description Overwrite methods on animations / timelines that no longer apply
11300 	*/
11301 	function adoptAnim(anim) {
11302 		anim.stop();
11303 		anim.start = anim.stop = anim.reverse = anim.pingPong = methodNotAllowed;
11304 	}
11305 	
11306 	/**
11307 		@name glow.anim.Timeline#track
11308 		@function
11309 		@description Add a track of animations to the timeline
11310 			Animations in a track will run one after another.
11311 			
11312 			Each track runs at the same time, always staying in sync.
11313 		
11314 		@param {number|function|glow.anim.Anim|glow.anim.Timeline} item+ Item to add to the timelines
11315 			Animation timelines can be placed within animation timelines
11316 			
11317 			Numbers will be treated as number of seconds to pause before the next item.
11318 			
11319 			Functions will be called. If the function takes 0.5 seconds to call, the next
11320 			animation will start 0.5 seconds in, keeping everything in sync.
11321 			
11322 		@returns this
11323 	*/
11324 	TimelineProto.track = function() {
11325 		/*!debug*/
11326 			if (arguments.length < 1) {
11327 				glow.debug.warn('[wrong count] glow.anim.Timeline#track expects at least 1 argument, not ' + arguments.length + '.');
11328 			}
11329 		/*gubed!*/
11330 		
11331 		var args = arguments,
11332 			tracksLen = this._tracks.length,
11333 			track = this._tracks[tracksLen] = [],
11334 			trackDuration = 0,
11335 			trackDurations = this._startPos[tracksLen] = [],
11336 			trackItem;
11337 		
11338 		// loop through the added tracks
11339 		for (var i = 0, leni = args.length; i < leni; i++) {
11340 			trackItem = track[i] = args[i];
11341 			
11342 			if (trackItem instanceof Anim || trackItem instanceof Timeline) {
11343 				adoptAnim(trackItem);
11344 			}
11345 			// convert numbers into empty animations
11346 			else if (typeof trackItem === 'number') {
11347 				trackItem = track[i] = new Anim(trackItem);
11348 			}
11349 			/*!debug*/
11350 				else if (typeof trackItem !== 'function') {
11351 					glow.debug.warn('[wrong type] glow.anim.Timeline#track all arguments must be number/glow.anim.Anim/glow.anim.Timeline/function, arg ' + i + ' is ' + typeof trackItem + '.');
11352 				}
11353 			/*gubed!*/
11354 			
11355 			// record the start time for this anim
11356 			trackDurations[i] = trackDuration;
11357 			trackDuration += trackItem.duration || 0;
11358 		}
11359 		
11360 		// update duration and anim duration
11361 		this._anim.duration = this.duration = Math.max(this.duration, trackDuration);
11362 		this._currentIndexes[tracksLen] = 0;
11363 		
11364 		return this;
11365 	};
11366 	
11367 	/**
11368 		@name glow.anim.Timeline#event:start
11369 		@event
11370 		@description Fires when an animation starts.
11371 			Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault})
11372 			prevents this animation from starting.
11373 		
11374 		@param {glow.events.Event} event Event Object
11375 	*/
11376 	
11377 	/**
11378 		@name glow.anim.Timeline#event:frame
11379 		@event
11380 		@description Fires on each frame of the animation
11381 		
11382 		@param {glow.events.Event} event Event Object
11383 	*/
11384 	
11385 	/**
11386 		@name glow.anim.Timeline#event:stop
11387 		@event
11388 		@description Fires when an animation is stopped before completing
11389 			Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault})
11390 			prevents this animation from stopping.
11391 		
11392 		@param {glow.events.Event} event Event Object
11393 	*/
11394 	
11395 	/**
11396 		@name glow.anim.Timeline#event:complete
11397 		@event
11398 		@description Fires when an animation completes
11399 			Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault})
11400 			causes the animation to loop.
11401 		
11402 		@param {glow.events.Event} event Event Object
11403 		
11404 		@example
11405 			// Make an animation loop 5 times
11406 			var loopCount = 5;
11407 			myTimeline.on('complete', function() {
11408 				return !!loopCount--;
11409 			});
11410 	*/
11411 	
11412 	// export
11413 	glow.anim.Timeline = Timeline;
11414 });
11415 Glow.complete('core', '2.0.0b1');
11416 
docs/symbols/src/build_2.0.0b1_glow.js.html100644 0 0 244530 11405426570 16060 0ustar 0 0
  1 /*!
  2 	Copyright 2010 British Broadcasting Corporation
  3 
  4 	Licensed under the Apache License, Version 2.0 (the "License");
  5 	you may not use this file except in compliance with the License.
  6 	You may obtain a copy of the License at
  7 
  8 	   http://www.apache.org/licenses/LICENSE-2.0
  9 
 10 	Unless required by applicable law or agreed to in writing, software
 11 	distributed under the License is distributed on an "AS IS" BASIS,
 12 	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13 	See the License for the specific language governing permissions and
 14 	limitations under the License.
 15 */
 16 (function() {
 17 
 18 	// there can be only one
 19 	if (window.Glow) { return; }
 20 	window.Glow = true;
 21 	
 22 	var glowMap,
 23 		defaultBase,
 24 		document = window.document,
 25 		scripts = document.getElementsByTagName('script'),
 26 		thisScriptSrc = '';
 27 	
 28 	// we need to be very explicit to defend against some browser
 29 	// extensions which add elements to the document unexpectedly
 30 	for (var i = scripts.length; i--;) { // find the most recent script tag for glow
 31 		if ( /\bglow\b/.test(scripts[i].src || '') ) {
 32 			thisScriptSrc = scripts[i].src;
 33 			break;
 34 		}
 35 	}
 36 		
 37 	// get default base from last script element
 38 	defaultBase = thisScriptSrc? 
 39 		thisScriptSrc.slice( 0, thisScriptSrc.lastIndexOf('/') +1 ) + '../'
 40 		: '';
 41 		
 42 	// track when document is ready, must run before the page is finished loading
 43 	if (!document.readyState) {
 44 		if (document.addEventListener) { // like Mozilla
 45 			document.addEventListener('DOMContentLoaded',
 46 				function () {
 47 					document.removeEventListener('DOMContentLoaded', arguments.callee, false);
 48 					document.readyState = 'complete';
 49 				},
 50 				false
 51 			);
 52 		}
 53 	}
 54 	
 55 	/**
 56 		@public
 57 		@name Glow
 58 		@function
 59 		@description Creates an instance of the Glow JavaScript Library.
 60 		@param {string} [version]
 61 		@param {object} [opts]
 62 		@param {string} [opts.base] The path to the base folder, in which the Glow versions are kept.
 63 		@param {boolean} [opts.debug] Have all filenames modified to point to debug versions.
 64 	*/
 65 	window.Glow = function(version, opts) { /*debug*///log.info('new Glow("'+Array.prototype.join.call(arguments, '", "')+'")');
 66 		opts = opts || {};
 67 		
 68 		var glowInstance,
 69 			debug = (opts.debug)? '.debug' : '',
 70 			base = opts.base || defaultBase;
 71 
 72 		glowMap = {
 73 			versions: ['2.0.0b1', 'src'],
 74 			'2.0.0b1': {
 75 				'core': ['core'+debug+'.js'],
 76 				'ui':   ['core', 'ui'+debug+'.js', 'ui'+debug+'.css']
 77 			}
 78 		};
 79 		
 80 		if (opts._map) { glowMap = opts._map; } // for testing purposes map can be overridden
 81 		
 82 		version = getVersion(version); /*debug*///log.info('Version is "'+version+'"');
 83 		
 84 		if (Glow._build.instances[version]) { /*debug*///log.info('instance for "'+version+'" already exists.');
 85 			return Glow._build.instances[version];
 86 		}
 87 		
 88 		// opts.base should be formatted like a directory
 89 		if (base.slice(-1) !== '/') {
 90 			base += '/';
 91 		}
 92 		
 93 		glowInstance = createGlowInstance(version, base);
 94 		Glow._build.instances[version] = glowInstance;
 95 		
 96 		glowInstance.UID = 'glow' + Math.floor(Math.random() * (1<<30));
 97 
 98  		if (!opts._noload) { glowInstance.load('core'); } // core is always loaded;
 99  		 		
100 		return glowInstance;
101 	}
102 	
103 	/**
104 		@private
105 		@name getVersion
106 		@function
107 		@param {string} version A (possibly imprecise) version identifier, like "2".
108 		@param {boolean} exact Force this function to only return exact matches for the requested version.
109 		@description Finds the most recent, available version of glow that matches the requested version.
110 		Versions that contain characters other than numbers and dots are never returned
111 		unless you ask for then exactly.
112 		@returns {string} The version identifier that best matches the given version.
113 		For example, given 2.1 this function could return 2.1.5 as the best match. 
114 	 */
115 	var getVersion = function(version, forceExact) { /*debug*///console.info('getVersion("'+version+'")');
116 		var versions = glowMap.versions,
117 			matchThis = version + '.',
118 			findExactMatch = forceExact || /[^0-9.]/.test(version); // like 1.1-alpha7
119 
120 		// TODO: an empty version means: the very latest version?
121 		
122 		var i = versions.length;
123 		while (i--) {
124 			if (findExactMatch) {
125 				if (versions[i] === version) { return versions[i]; }
126 			}
127 			else if ( (versions[i] + '.').indexOf(matchThis) === 0 && !/[^0-9.]/.test(versions[i]) ) {
128 				return versions[i];
129 			}
130 		}
131 
132 		throw new Error('Version "'+version+'" does not exist');
133 	}
134 	
135 	/**
136 		@private
137 		@name getMap
138 		@function
139 		@description Find the file map for a given version.
140 		@param {string} version Resolved identifier, like '2.0.0'.
141 		@returns {object} A map of package names to files list.
142 	 */
143 	var getMap = function(version) { /*debug*///log.info('getMap("'+version+'")');
144 		var versions = glowMap.versions,
145 			map = null,
146 			versionFound = false;
147 		
148 		var i = versions.length;
149 		while (--i > -1) {
150 			if (glowMap[versions[i]]) { map = glowMap[versions[i]]; }
151 			if (versions[i] === version) { versionFound = true; }
152 			if (versionFound && map) { return map; }
153 		}
154 		
155 		throw new Error('No map available for version "' + version + '".');
156 	}
157 	
158 	/**
159 		@private
160 		@name injectJs
161 		@function
162 		@description Start asynchronously loading an external JavaScript file.
163 	 */
164 	var injectJs = function(src) { /*debug*///log.info('injectJs("'+src+'")');
165 		var head,
166 			script;
167 		
168 		head = document.getElementsByTagName('head')[0];
169 		script = document.createElement('script');
170 		script.src = src;
171 		script.type = 'text/javascript';
172 		
173 		head.insertBefore(script, head.firstChild); // rather than appendChild() to avoid IE bug when injecting SCRIPTs after BASE tag opens. see: http://shauninman.com/archive/2007/04/13/operation_aborted
174 	}
175 	
176 	/**
177 		@private
178 		@name injectCss
179 		@function
180 		@description Start asynchronously loading an external CSS file.
181 	 */
182 	var injectCss = function(src) { /*debug*///log.info('injectCss("'+src+'")');
183 		var head,
184 			link;
185 			
186 		head = document.getElementsByTagName('head')[0];
187 		link = document.createElement('link');
188 		link.href = src;
189 		link.type = 'text/css';
190 		link.rel = 'stylesheet';
191 		
192 		head.insertBefore(link, head.firstChild);
193 	}
194 	
195 	/** @private */
196 	Glow._build = {
197 		provided: [], // provided but not yet complete
198 		instances: {} // built
199 	}
200 	
201 	/**
202 		@private
203 		@name Glow.provide
204 		@function
205 		@param {function} builder A function to run, given an instance of glow, and will add a feature to glow.
206 		@description Provide a builder function to Glow as part of a package.
207 	 */
208 	Glow.provide = function(builder) { /*debug*///log.info('Glow.provide('+typeof builder+')');
209 		Glow._build.provided.push(builder);
210 	}
211 	
212 	/**
213 		@private
214 		@name Glow.complete
215 		@function
216 		@param {string} name The name of the completed package.
217 		@param {string} version The version of the completed package.
218 		@description Signals that no more builder functions will be provided by this package.
219 	 */
220 	Glow.complete = function(name, version) { /*debug*///log.info('complete('+name+', '+version+')');
221 		var glow,
222 			loading,
223 			builders;
224 		
225 		if (version === '@'+'SRC@') { version = 'src'}
226 		// now that we have the name and version we can move the builders out of provided cache
227 		glow = Glow._build.instances[version];
228 		if (!glow) { /*debug*///log.info('Cannot complete, unknown version of glow: '+version);
229 			throw new Error('Cannot complete, unknown version of glow: '+version);
230 		}
231 		glow._build.builders[name] = Glow._build.provided;
232 		Glow._build.provided = [];
233 
234 		// shortcuts
235 		loading   = glow._build.loading;
236 		builders = glow._build.builders;
237 		
238 		// try to build packages, in the same order they were loaded
239 		for (var i = 0; i < loading.length; i++) { // loading.length may change during loop
240 			if (!builders[loading[i]]) { /*debug*///log.info(loading[i]+' has no builders.');
241 				break;
242 			}
243 			
244 			// run the builders for this package in the same order they were loaded
245 			for (var j = 0, jlen = builders[loading[i]].length; j < jlen; j++) { /*debug*///log.info('running builder '+j+ ' for '+loading[i]+' version '+glow.version);
246 				builders[loading[i]][j](glow); // builder will modify glow
247 			}
248 			
249 			// remove this package from the loaded and builders list, now that it's built
250 			if (glow._removeReadyBlock) { glow._removeReadyBlock('glow_loading_'+loading[i]); }
251 			builders[loading[i]] = undefined;
252 			loading.splice(i, 1);
253 			i--;
254 			
255 			
256 		}
257 		
258 		// try to run onLoaded callbacks
259 		glow._release();
260 	}
261 	
262 	/**
263 		@name createGlowInstance
264 		@private
265 		@function
266 		@description Creates an instance of the Glow library. 
267 		@param {string} version
268 		@param {string} base
269 	 */
270 	var createGlowInstance = function(version, base) { /*debug*///log.info('new glow("'+Array.prototype.join.call(arguments, '", "')+'")');
271 		var glow = function(nodeListContents) {
272 			return new glow.NodeList(nodeListContents);
273 		};
274 		
275 		glow.version = version;
276 		glow.base = base;
277 		glow.map = getMap(version);
278 		glow._build = {
279 			loading: [],   // names of packages requested but not yet built, in same order as requested.
280 			builders: {},  // completed but not yet built (waiting on dependencies). Like _build.builders[packageName]: [function, function, ...].
281 			history: {},   // names of every package ever loaded for this instance
282 			callbacks: []
283 		};
284 		
285 		// copy properties from glowInstanceMembers
286 		for (var prop in glowInstanceMembers) {
287 			glow[prop] = glowInstanceMembers[prop];
288 		}
289 		
290 		return glow;
291 	}
292 	
293 	
294 	/**
295 		@name glowInstanceMembers
296 		@private
297 		@description All members of this object will be copied onto little-glow instances
298 		@type {Object}
299 	*/
300 	var glowInstanceMembers = {
301 		/**
302 			@public
303 			@name glow#load
304 			@function
305 			@description Add a package to this instance of the Glow library.
306 			@param {string[]} ... The names of 1 or more packages to add.
307 		 */
308 		load: function() { /*debug*///log.info('glow.load("'+Array.prototype.join.call(arguments, '", "')+'") for version '+this.version);
309 			var name = '',
310 				src,
311 				depends;
312 			
313 			for (var i = 0, len = arguments.length; i < len; i++) {
314 				name = arguments[i];
315 				
316 				if (this._build.history[name]) { /*debug*///log.info('already loaded package "'+name+'" for version '+this.version+', skipping.');
317 					continue;
318 				}
319 				
320 				this._build.history[name] = true;
321 				
322 				// packages have dependencies, listed in the map: a single js file, css files, or even other packages
323 				depends = this.map[name]; /*debug*///log.info('depends for '+name+' '+this.version+': "'+depends.join('", "')+'"');
324 				for (var j = 0, lenj = depends.length; j < lenj; j++) {
325 					
326 					if (depends[j].slice(-3) === '.js') { /*debug*///log.info('dependent js: "'+depends[j]+'"');
327 						src = this.base + this.version + '/' + depends[j];
328 						
329 						// readyBlocks are removed in _release()
330 						if (this._addReadyBlock) { this._addReadyBlock('glow_loading_'+name); } // provided by core
331 						this._build.loading.push(name);
332 						
333 						injectJs(src);
334 					}
335 					else if (depends[j].slice(-4) === '.css') { /*debug*///log.info('dependent css "'+depends[j]+'"');
336 						src = this.base + this.version + '/' + depends[j];
337 						injectCss(src);
338 					}
339 					else { /*debug*///log.info('dependent package: "'+depends[j]+'"');
340 						this.load(depends[j]); // recursively load dependency packages
341 					}
342 				}
343 			}
344 			
345 			return this;
346 		},
347 		/**
348 			@public
349 			@name glow#loaded
350 			@function
351 			@param {function} onLoadCallback Called when all the packages load.
352 			@description Do something when all the packages load.
353 		 */
354 		loaded: function(onLoadCallback) { /*debug*///log.info('glow.loaded('+typeof onLoadCallback+') for version '+this.version);
355 			this._build.callbacks.push(onLoadCallback);
356 			if (this._addReadyBlock) { this._addReadyBlock('glow_loading_loadedcallback'); }
357 			
358 			this._release();
359 			
360 			return this;
361 		},
362 		/**
363 			@private
364 			@name glow#_release
365 			@function
366 			@description If all loaded packages are now built, then run the onLoaded callbacks.
367 		 */
368 		_release: function() { /*debug*///log.info('glow._release("'+this.version+'")');
369 			var callback;
370 			
371 			if (this._build.loading.length !== 0) { /*debug*///log.info('waiting for '+this._build.loading.length+' to finish.');
372 				return;
373 			}
374 			/*debug*///log.info('running '+this._build.callbacks.length+' loaded callbacks for version "'+this.version+'"');
375 			
376 			// run and remove each available _onloaded callback
377 			while (callback = this._build.callbacks.shift()) {
378 				callback(this);
379 				if (this._removeReadyBlock) { this._removeReadyBlock('glow_loading_loadedcallback'); }
380 			}
381 		},
382 		/**
383 			@name glow#ready
384 			@function
385 			@param {function} onReadyCallback Called when all the packages load and the DOM is available.
386 			@description Do something when all the packages load and the DOM is ready.
387 		 */
388 		ready: function(onReadyCallback) { /*debug*///log.info('(ember) glow#ready('+typeof onReadyCallback+') for version '+this.version+'. There are '+this._build.loading.length+' loaded packages waiting to be built.');
389 			this.loaded(function(glow) {
390 				glow.ready( function() { onReadyCallback(glow); } );
391 			});
392 			
393 			return this;
394 		}
395 	}
396 })();
397 
docs/symbols/src/build_2.0.0b1_ui.js.html100644 0 0 3220736 11405426450 15547 0ustar 0 0
  1 /*!
  2 	Copyright 2010 British Broadcasting Corporation
  3 
  4 	Licensed under the Apache License, Version 2.0 (the "License");
  5 	you may not use this file except in compliance with the License.
  6 	You may obtain a copy of the License at
  7 
  8 	   http://www.apache.org/licenses/LICENSE-2.0
  9 
 10 	Unless required by applicable law or agreed to in writing, software
 11 	distributed under the License is distributed on an "AS IS" BASIS,
 12 	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13 	See the License for the specific language governing permissions and
 14 	limitations under the License.
 15 */
 16 Glow.provide(function(glow) {
 17 	var NodeList = glow.NodeList,
 18 		NodeListProto = NodeList.prototype,
 19 		undefined;
 20 	
 21 	/**
 22 		@name glow.NodeList.focusable
 23 		@function
 24 		@extends glow.ui.Behaviour
 25 		@description Manage a focusable element, or group of elements
 26 			This method is a shortcut to {@link glow.ui.Focusable} and requires
 27 			the 'ui' package to be loaded.
 28 			
 29 			The first item in the NodeList is treated as the focusable's container.
 30 			An error is thrown if the first item in the NodeList is not an element.
 31 		
 32 			This can be used to create a single focus point for a set
 33 			of focusable elements. Eg, a menu can have a single tab stop,
 34 			and the arrow keys can be used to cycle through menu items.
 35 			
 36 			This means the user doesn't have to tab through every item in the
 37 			menu to get to the next set of focusable items.
 38 			
 39 			The FocusManager can also be used to make a element 'modal', ensuring
 40 			focus doesn't go to elements outside it.
 41 		
 42 		@param {object} [opts] Options
 43 			The same options as the {@link glow.ui.Focusable} constructor
 44 			
 45 		@returns {glow.ui.Focusable}
 46 	*/
 47 	NodeListProto.focusable = function(opts) {
 48 		/*!debug*/
 49 			if (arguments.length > 1) {
 50 				glow.debug.warn('[wrong count] glow.NodeList#focusable expects 0 or 1 argument, not ' + arguments.length + '.');
 51 			}
 52 			if (opts !== undefined && typeof opts !== 'object') {
 53 				glow.debug.warn('[wrong type] glow.NodeList#focusable expects object as "opts" argument, not ' + typeof opts + '.');
 54 			}
 55 		/*gubed!*/
 56 		return new glow.ui.Focusable(this, opts);
 57 	};
 58 });
 59 // start-source: ui.js
 60 
 61 /**
 62 	@name glow.ui
 63 	@namespace
 64  */
 65 		 
 66 Glow.provide(function(glow) {
 67 	glow.ui = glow.ui || {};
 68 });
 69 
 70 // end-source: ui.js
 71 Glow.provide(function(glow) {
 72 	/**
 73 		@name glow.ui.Behaviour
 74 		@class
 75 		@extends glow.events.Target
 76 		@description Abstract behaviour class.
 77 		@param {string} name The name of this widget.
 78 		This is added to class names in the generated DOM nodes that wrap the widget interface.
 79 		
 80 	*/
 81 	function Behaviour() {}
 82 	glow.util.extend(Behaviour, glow.events.Target);
 83 	
 84 	/*!debug*/
 85 		/**
 86 			@name glow.ui.Behaviour#enabled
 87 			@function
 88 			@description Get/set the enabled state
 89 				
 90 			@param {boolean} [state=true] 
 91 		*/
 92 		Behaviour.prototype.enabled = function() {
 93 			throw new Error('#enabled not implemented on behaviour');
 94 		}
 95 		
 96 		/**
 97 			@name glow.ui.Behaviour#destroy
 98 			@function
 99 			@description Removes the behaviour & event listeners
100 		*/
101 		Behaviour.prototype.destroy = function() {
102 			throw new Error('#destroy not implemented on behaviour');
103 		}
104 		
105 	/*gubed!*/
106 	
107 	// EXPORT
108 	glow.ui.Behaviour = Behaviour;
109 });
110 Glow.provide(function(glow) {
111 	var undefined, FocusableProto,
112 		// array of focusable instances
113 		focusables = [],
114 		// the focused element
115 		focused,
116 		// we use this to track the modal focusable, also to ensure there's only one
117 		modalFocusable,
118 		documentNodeList = glow(document),
119 		ignoreFocus = false;
120 	
121 	// keep track of what element has focus
122 	documentNodeList.on('blur', function(event) {
123 		focused = undefined;
124 		if (focusables.length) {
125 			// activate focusables on a timeout so we pick up a possible subsequent
126 			// focus event
127 			setTimeout(deactivateAllIfBlurred, 0);
128 		}
129 	}).on('focus', function(event) {
130 		if ( modalFocusable && !modalFocusable.container.contains(event.source) ) {
131 			// refocus either the active child or container
132 			( modalFocusable.activeChild[0] || modalFocusable.container[0] ).focus();
133 			return false;
134 		}
135 		
136 		focused = event.source;
137 		
138 		if (ignoreFocus) {
139 			return;
140 		}
141 		
142 		ignoreFocus = true;
143 		
144 		activateFocusables();
145 		
146 		setTimeout(stopIgnoringFocus, 0);
147 	});
148 	
149 	/**
150 		@private
151 		@function
152 		@description Wot it sez on da tin.
153 			(used to cater for browsers that fire multiple focuses per click)
154 	*/
155 	function stopIgnoringFocus() {
156 		ignoreFocus = false;
157 	}
158 	
159 	/**
160 		@private
161 		@function
162 		@description Deactivate all our focusables if nothing has focus
163 	*/
164 	function deactivateAllIfBlurred() {
165 		// if nothing has focus, deactivate our focusables
166 		!focused &&	activateFocusables();
167 	}
168 	
169 	/**
170 		@private
171 		@function
172 		@description React to a change in focus
173 	*/
174 	function activateFocusables() {
175 		// taking a copy of the array in case any destroy
176 		var instances = focusables.slice(0),
177 			i = instances.length;
178 		
179 		while (i--) {
180 			// activate / deactivate the focusable depending on where focus is.
181 			// This calls active(), passing in either the element focused (within the Focusable container) or false.
182 			// The 2 mentions of 'focused' is deliberate.
183 			instances[i].active( (focused && instances[i].container.contains(focused) && focused) || false );
184 		}
185 	}
186 	
187 	/**
188 		@private
189 		@function
190 		@description Update the children property for a focusable
191 	*/
192 	function updateChildren(focusable) {
193 		focusable.children = focusable.container.get( focusable._opts.children );
194 		
195 		// remove focusable items from the tab flow, we're going to conrol this with tab keys
196 		glow(focusable.children).push(focusable.container).prop('tabIndex', -1);
197 	}
198 	
199 	/**
200 		@private
201 		@function
202 		@description Create the default key handler functions
203 	*/
204 	function createKeyHandler(useLeftRight, useUpDown) {
205 		return function(event) {
206 			// listen for keypresses, react, and return false if the key was used
207 			switch (event.key) {
208 				case 'up':
209 					return !( useUpDown    && this.prev() );
210 				case 'left':
211 					return !( useLeftRight && this.prev() );
212 				case 'down':
213 					return !( useUpDown    && this.next() );
214 				case 'right':
215 					return !( useLeftRight && this.next() );
216 			}
217 		}
218 	}
219 	
220 	/**
221 		@private
222 		@description The default key handler functions
223 	*/
224 	var keyHandlers = {
225 		'arrows'  : createKeyHandler(1, 1),
226 		'arrows-x': createKeyHandler(1, 0),
227 		'arrows-y': createKeyHandler(0, 1)
228 	}
229 	
230 	/**
231 		@private
232 		@function
233 		@description Hover listener
234 			Used to focus items on hover.
235 			'this' is the Focusable.
236 	*/
237 	function hoverListener(event) {
238 		// set the _activeMethod so this can be passed onto the event
239 		this._activeMethod = 'hover';
240 		this._activeEvent = event;
241 		this.active(event.source);
242 		this._activeEvent = this._activeMethod = undefined;
243 	}
244 	
245 	/**
246 		@private
247 		@function
248 		@description Set _activeMethod to a value and call another function.
249 			This allows the _activeMethod to be passed to the event.
250 	*/
251 	function activeMethodWrap(focusable, methodName, func) {
252 		return function(event) {
253 			var returnVal;
254 			
255 			focusable._activeMethod = methodName;
256 			focusable._activeEvent = event;
257 			returnVal = func.apply(this, arguments);
258 			focusable._activeEvent = focusable._activeMethod = undefined;
259 			return returnVal;
260 		}
261 	}
262 	
263 	/**
264 		@name glow.ui.Focusable
265 		@class
266 		@extends glow.ui.Behaviour
267 		@description Manage a focusable element, or group of elements
268 			This can be used to create a single focus point for a set
269 			of focusable elements. Eg, a menu can have a single tab stop,
270 			and the arrow keys can be used to cycle through menu items.
271 			
272 			This means the user doesn't have to tab through every item in the
273 			menu to get to the next set of focusable items.
274 			
275 			The FocusManager can also be used to make a element 'modal', ensuring
276 			focus doesn't go to elements outside it.
277 			
278 			The aim of this behaviour is to make it easier to conform to
279 			<a href="http://www.w3.org/TR/2009/WD-wai-aria-practices-20091215/#keyboard">
280 				ARIA best practices for keyboard navigation
281 			</a>
282 			
283 		@param {glow.NodeList|string} container Parent focusable element of the group
284 			If tabindex isn't set on this element, it will be given tabindex="0",
285 			allowing the element to be focused using the tab key.
286 		@param {object} [opts] Options
287 			@param {string} [opts.children] Selector for child items that can be active
288 				These can be cycled through using the arrow keys when the Focusable
289 				or one of its children is active (usually when it has focus).
290 			@param {function|string} [opts.keyboardNav='arrows'] Alter the default keyboard behaviour.
291 				If 'arrows-x', the left & right arrow keys trigger {@link glow.ui.Focusable#next Focusable#next}
292 				and {@link glow.ui.Focusable#prev Focusable#prev} respectively. If 'arrows-y', the up & down
293 				arrow keys trigger {@link glow.ui.Focusable#next Focusable#next}
294 				and {@link glow.ui.Focusable#prev Focusable#prev} respectively. 'arrows' is
295 				a combination of the two.
296 				
297 				If a function is provided, it will be passed a {@link glow.events.KeyboardEvent} object.
298 				Use {@link glow.ui.Focusable#next Focusable#next},
299 				{@link glow.ui.Focusable#prev Focusable#prev} or
300 				{@link glow.ui.Focusable#activate Focusable#activate} to react to the
301 				key event.
302 				
303 				'this' inside this function refers to the Focusable.
304 			@param {boolean} [opts.setFocus=true] Sets whether focus is given to the active element.
305 				You need to set this to false if you want focus to remain in another
306 				element.
307 			@param {string} [opts.activeChildClass='active'] Class name to give the active child element.
308 			@param {boolean} [opts.activateOnHover=false] Activate items on hover?
309 			@param {boolean} [opts.loop=false] Loop from the last child item to the first (and vice-versa)?
310 				When this is true, calling {@link glow.ui.Focusable#next Focusable#next} when
311 				the last focusable item is active will activate the first.
312 				
313 		@example
314 			// A collection of buttons
315 			glow('#toolbar').focusable({
316 				children: '> li.button'
317 			});
318 			
319 			// The #toolbar now appears in tab order.
320 			// Once focused, the left & right arrow keys navigate between
321 			// buttons.
322 			
323 		@example
324 			// A modal dialog
325 			var dialog = glow('#dialog').hide();
326 			var focusable = dialog.focusable();
327 			
328 			glow('#openDialog').on('click', function() {
329 				dialog.show();
330 				focusable.modal(true);
331 			});
332 			
333 			glow('#closeDialog').on('click', function() {
334 				dialog.hide();
335 				focusable.modal(false);
336 			});
337 	*/
338 	function Focusable(container, opts) {
339 		/*!debug*/
340 			if (arguments.length > 2) {
341 				glow.debug.warn('[wrong count] glow.ui.Focusable expects 1 or 2 arguments, not ' + arguments.length + '.');
342 			}
343 			if (opts !== undefined && typeof opts !== 'object') {
344 				glow.debug.warn('[wrong type] glow.ui.Focusable expects object for "opts" argument, not ' + typeof opts + '.');
345 			}
346 		/*gubed!*/
347 		
348 		var keyboardNav;
349 		
350 		opts = this._opts = glow.util.apply({
351 			children: '',
352 			keyboardNav: 'arrows',
353 			setFocus: true,
354 			activeChildClass: 'active'
355 			// commented as undefined is falsey enough
356 			//activateOnHover: false,
357 			//loop: false
358 		}, opts || {});
359 		
360 		this.container = glow(container);
361 		keyboardNav = opts.keyboardNav;
362 		
363 		// build the keyhander, using presets or provided function
364 		this._keyHandler = activeMethodWrap(this, 'key',
365 			(typeof keyboardNav === 'string' ? keyHandlers[keyboardNav] : keyboardNav)
366 		);
367 		
368 		/*!debug*/
369 			if ( !this.container[0] ) {
370 				glow.debug.warn('[wrong value] glow.ui.Focusable - No container found');
371 			}
372 			if (typeof this._keyHandler != 'function') {
373 				glow.debug.warn('[wrong value] glow.ui.Focusable - unexpected value for opts.keyboardNav');
374 			}
375 			if (typeof opts.children != 'string') {
376 				glow.debug.warn('[wrong type] glow.ui.Focusable expects CSS string for "opts.children" argument, not ' + typeof opts.children + '.');
377 			}
378 		/*gubed!*/
379 		
380 		// populate #children
381 		updateChildren(this);
382 		
383 		// create initial focal point
384 		this.container[0].tabIndex = 0;
385 		
386 		// Add listener for activateOnHover
387 		if (opts.activateOnHover) {
388 			this.container.on('mouseover', hoverListener, this);
389 		}
390 		
391 		// listen for clicks
392 		this.container.on('click', clickSelectListener, this);
393 		
394 		// add to our array of focusables
395 		focusables.push(this);
396 	};
397 	glow.util.extend(Focusable, glow.ui.Behaviour);
398 	FocusableProto = Focusable.prototype;
399 	
400 	/**
401 		@name glow.ui.Focusable#_opts
402 		@type boolean
403 		@description Option object used in construction
404 	*/
405 	/**
406 		@name glow.ui.Focusable#_active
407 		@type boolean
408 		@description True/false to indicate if the Focusable is active
409 	*/
410 	FocusableProto._active = false;
411 	/**
412 		@name glow.ui.Focusable#_modal
413 		@type boolean
414 		@description True/false to indicate if the Focusable is modal
415 	*/
416 	FocusableProto._modal = false;
417 	/**
418 		@name glow.ui.Focusable#_disabled
419 		@type boolean
420 		@description True/false to indicate if the Focusable is enabled
421 	*/
422 	FocusableProto._disabled = false;
423 	/**
424 		@name glow.ui.Focusable#_lastActiveChild
425 		@type HTMLElement
426 		@description Stores the last value of #activeChild while the focusable is inactive
427 	*/
428 	/**
429 		@name glow.ui.Focusable#_keyHandler
430 		@type function
431 		@description Key handler function
432 	*/
433 	/**
434 		@name glow.ui.Focusable#_activeMethod
435 		@type string
436 		@description The last method used to activate a child element
437 	*/
438 	/**
439 		@name glow.ui.Focusable#_activeEvent
440 		@type string
441 		@description The event object accociated with _activeMethod
442 	*/
443 	/**
444 		@name glow.ui.Focusable#activeChild
445 		@type glow.NodeList
446 		@description The active child item.
447 			This will be an empty NodeList if no child is active
448 	*/
449 	FocusableProto.activeChild = glow();
450 	/**
451 		@name glow.ui.Focusable#activeIndex
452 		@type number
453 		@description The index of the active child item in {@link glow.ui.Focusable#children}.
454 			This will be undefined if no child is active.
455 	*/
456 	/**
457 		@name glow.ui.Focusable#container
458 		@type glow.NodeList
459 		@description Focusable container
460 	*/
461 	/**
462 		@name glow.ui.Focusable#children
463 		@type glow.NodeList
464 		@description NodeList of child items that are managed by this Focusable.
465 			This will be an empty nodelist if the focusable has no children
466 	*/
467 	FocusableProto.children = glow();
468 	
469 	/**
470 		@name glow.ui.Focusable#modal
471 		@function
472 		@description Get/set modality
473 			When a Focusable is modal it cannot be deactivated, focus cannot
474 			be given to elements outside of it until modal set to false.
475 			
476 		@param {boolean} setModal New modal value
477 		
478 		@returns this when setting, true/false when getting
479 	*/
480 	FocusableProto.modal = function(setModal) {
481 		/*!debug*/
482 			if (arguments.length > 1) {
483 				glow.debug.warn('[wrong count] glow.ui.Focusable#modal expects 0 or 1 argument, not ' + arguments.length + '.');
484 			}
485 		/*gubed!*/
486 		
487 		if (setModal === undefined) {
488 			return this._modal;
489 		}
490 		
491 		if (!this._disabled) {
492 			// Activate the modal if it isn't modal already
493 			if (setModal && !this._modal) {
494 				// Ensure we're not going to get a deadlock with another focusable
495 				if (modalFocusable) {
496 					modalFocusable.modal(false);
497 				}
498 				modalFocusable = this;
499 				this.active(true);
500 			}
501 			// switch modal off, if this focusable is modal
502 			else if (!setModal && this._modal) {
503 				modalFocusable = undefined;
504 			}
505 			
506 			this._modal = !!setModal;
507 		}
508 		return this;
509 	};
510 	
511 	/**
512 		@private
513 		@function
514 		@description Update activeChild and activeIndex according to an index.
515 	*/
516 	function activateChildIndex(focusable, index) {
517 		var prevActiveChild = focusable.activeChild[0],
518 			activeChildClass = focusable._opts.activeChildClass,
519 			activeChild = focusable.activeChild = glow( focusable.children[index] ),
520 			eventData = {
521 				item: activeChild,
522 				itemIndex: index,
523 				method: focusable._activeMethod || 'api',
524 				methodEvent: focusable._activeEvent
525 			};
526 		
527 		focusable.activeIndex = index;
528 		
529 		// have we changed child focus?
530 		if ( prevActiveChild === activeChild || focusable.fire('childActivate', eventData).defaultPrevented() ) {
531 			return;
532 		}
533 		
534 		// take the current active item out of the tab order
535 		if (prevActiveChild) {
536 			prevActiveChild.tabIndex = -1;
537 			glow(prevActiveChild).removeClass(activeChildClass);
538 		}
539 		
540 		// put the current active item into the tab order
541 		focusable.activeChild[0].tabIndex = 0;
542 		focusable.activeChild.addClass(activeChildClass);
543 		
544 		// give physical focus to the new item
545 		focusable._opts.setFocus && focusable.activeChild[0].focus();
546 	}
547 	
548 	/**
549 		@private
550 		@function
551 		@description Get the focusable child index of an element.
552 			The element may also be an element within the focusable's child items.
553 		@param {glow.ui.Focusable} focusable
554 		@param {glow.NodeList} child Element to get the index from.
555 		
556 		@returns {number} Index or -1 if element is not (and is not within) any of the focusable's child items.
557 	*/
558 	function getIndexFromElement(focusable, child) {
559 		var i,
560 			children = focusable.children,
561 			firstChild = children[0];
562 		
563 		// just exit if there are no child items
564 		if ( !firstChild ) {
565 			return -1;
566 		}
567 		
568 		child = glow(child).item(0);
569 		
570 		// do we have an active child to re-enable?
571 		if ( child[0] ) {
572 			i = children.length;
573 			
574 			// see if it's in the current child set
575 			while (i--) {
576 				if ( glow( children[i] ).contains(child) ) {
577 					return i;
578 				}
579 			}
580 		}
581 		return -1;
582 	}
583 	
584 	/**
585 		@private
586 		@function
587 		@description Ensure an index is within the range of indexes for this focusable.
588 		@param {glow.ui.Focusable} focusable
589 		@param {number} index Index to keep within range
590 		
591 		@returns {number} The index within range.
592 			If the focusable can loop, the index will be looped. Otherwise
593 			the index will be limited to its maximum & minimum
594 	*/
595 	function assertIndexRange(focusable, index) {
596 		var childrenLen = focusable.children.length;
597 		
598 		// ensure the index is within children range
599 		if (focusable._opts.loop) {
600 			index = index % childrenLen;
601 			if (index < 0) {
602 				index = childrenLen + index;
603 			}
604 		}
605 		else {
606 			index = Math.max( Math.min(index, childrenLen - 1), 0);
607 		}
608 		
609 		return index;
610 	}
611 	
612 	/**
613 		@private
614 		@function
615 		@description Deactivate the focusable
616 	*/
617 	function deactivate(focusable) {
618 		if ( focusable.fire('deactivate').defaultPrevented() ) {
619 			return;
620 		}
621 		
622 		// remove active class
623 		focusable.activeChild.removeClass(focusable._opts.activeChildClass);
624 		
625 		// store focusable so we can reactivate it later
626 		focusable._lastActiveChild = focusable.activeChild[0];
627 		
628 		// blur the active element
629 		( focusable.activeChild[0] || focusable.container[0] ).blur();
630 		
631 		focusable.activeIndex = undefined;
632 		
633 		// reset to empty nodelist
634 		focusable.activeChild = FocusableProto.activeChild;
635 		focusable._active = false;
636 		
637 		// remove listeners
638 		documentNodeList.detach('keypress', focusable._keyHandler).detach('keydown', keySelectListener);
639 		
640 		// allow the container to receive focus in case the child elements change
641 		focusable.container.prop('tabIndex', 0);
642 	}
643 	
644 	/**
645 		@private
646 		@function
647 		@description Activate the focusable
648 	*/
649 	function activate(focusable, toActivate) {
650 		var _active = focusable._active,
651 			focusContainerIfChildNotFound,
652 			indexToActivate = -1;
653 		
654 		// if currently inactive...
655 		if (!_active) {
656 			if ( focusable.fire('activate').defaultPrevented() ) {
657 				return;
658 			}
659 			
660 			updateChildren(focusable);
661 			focusable._active = true;
662 			// start listening to the keyboard
663 			documentNodeList.on('keypress', focusable._keyHandler, focusable).on('keydown', keySelectListener, focusable);
664 			// give focus to the container - a child element may steal focus in activateChildIndex
665 			focusContainerIfChildNotFound = true;
666 		}
667 		
668 		// Work out what child item to focus.
669 		// We avoid doing this if we were 
670 		if ( focusable.children[0] ) {
671 			// activating by index
672 			if (typeof toActivate === 'number') {
673 				indexToActivate = assertIndexRange(focusable, toActivate);
674 			}
675 			// activating by element
676 			else if (typeof toActivate !== 'boolean') {
677 				indexToActivate = getIndexFromElement(focusable, toActivate);
678 			}
679 			
680 			// still no index to activate? If we were previously inactive, try the last active item
681 			if (indexToActivate === -1 && !_active) {
682 				indexToActivate = getIndexFromElement(focusable, focusable._lastActiveChild);
683 				indexToActivate = indexToActivate !== -1 ? indexToActivate : 0;
684 			}
685 		}
686 		
687 		// If we have an item to activate, let's go for it
688 		if (indexToActivate !== -1 && indexToActivate !== focusable.activeIndex) {
689 			activateChildIndex(focusable, indexToActivate);
690 		}
691 		else if (focusContainerIfChildNotFound) {
692 			focusable._opts.setFocus && focusable.container[0].focus();
693 		}
694 	}
695 	
696 	/**
697 		@name glow.ui.Focusable#active
698 		@function
699 		@description Get/set the active state of the Focusable
700 			Call without arguments to get the active state. Call with
701 			arguments to set the active element.
702 			
703 			A Focusable will be activated automatically when it receieves focus.
704 		
705 		@param {number|glow.NodeList|boolean} [toActivate] Item to activate.
706 			Numbers will be treated as an index of {@link glow.ui.FocusManager#children children}.
707 			
708 			'true' will activate the container, but none of the children.
709 			
710 			'false' will deactivate the container and any active child
711 		
712 		@returns {glow.ui.Focusable|boolean}
713 			Returns boolean when getting, Focusable when setting
714 	*/
715 	FocusableProto.active = function(toActivate) {
716 		/*!debug*/
717 			if (arguments.length > 1) {
718 				glow.debug.warn('[wrong count] glow.ui.Focusable#active expects 0 or 1 argument, not ' + arguments.length + '.');
719 			}
720 		/*gubed!*/
721 		
722 		var _active = this._active;
723 		
724 		// getting
725 		if (toActivate === undefined) {
726 			return _active;
727 		}
728 		
729 		// setting
730 		if (!this._disabled) {
731 			// deactivating
732 			if (toActivate === false) {
733 				if (!this._modal && _active) {
734 					deactivate(this);
735 				}
736 			}
737 			// activating
738 			else {
739 				activate(this, toActivate)
740 			}
741 		}
742 		return this;
743 	};
744 	
745 	/**
746 		@private
747 		@function
748 		@description Generates #next and #prev
749 	*/
750 	function nextPrev(amount) {
751 		return function() {
752 			/*!debug*/
753 				if (arguments.length > 1) {
754 					glow.debug.warn('[wrong count] glow.ui.Focusable#' + (amount > 0 ? 'next' : 'prev') + ' expects 0 arguments, not ' + arguments.length + '.');
755 				}
756 			/*gubed!*/
757 			
758 			if (this._active) {
759 				this.active( this.activeIndex + amount );
760 			}
761 			return this;
762 		}
763 	}
764 	
765 	/**
766 		@name glow.ui.Focusable#next
767 		@function
768 		@description Activate next child item.
769 			Has no effect on an inactive Focusable.
770 		@returns this
771 	*/
772 	FocusableProto.next = nextPrev(1);
773 	
774 	/**
775 		@name glow.ui.Focusable#prev
776 		@function
777 		@description Activate previous child item
778 			Has no effect on an inactive Focusable.
779 		@returns this
780 	*/
781 	FocusableProto.prev = nextPrev(-1);
782 	
783 	/**
784 		@name glow.ui.Focusable#disabled
785 		@function
786 		@description Enable/disable the Focusable, or get the disabled state
787 			When the Focusable is disabled, it (and its child items) cannot
788 			be activated or receive focus.
789 			
790 		@param {boolean} [newState] Disable the focusable?
791 			'false' will enable a disabled focusable.
792 		
793 		@returns {glow.ui.Focusable|boolean}
794 			Returns boolean when getting, Focusable when setting
795 	*/
796 	FocusableProto.disabled = function(newState) {
797 		/*!debug*/
798 			if (arguments.length > 1) {
799 				glow.debug.warn('[wrong count] glow.ui.Focusable#disabled expects 0 or 1 argument, not ' + arguments.length + '.');
800 			}
801 		/*gubed!*/
802 		
803 		// geting
804 		if (newState === undefined) {
805 			return this._disabled;
806 		}
807 		
808 		// setting
809 		if (newState) {
810 			this.active(false);
811 			this._disabled = !!newState;
812 		}
813 		else {
814 			this._disabled = !!newState;
815 			
816 			// reactivate it if it were modal
817 			if (this._modal) {
818 				this.active(true);
819 			}
820 		}
821 		return this;
822 	}
823 	
824 	/**
825 		@name glow.ui.Focusable#destroy
826 		@function
827 		@description Destroy the Focusable
828 			This removes all focusable behaviour from the continer
829 			and child items.
830 			
831 			The elements themselves will not be destroyed.
832 		@returns this
833 	*/
834 	FocusableProto.destroy = function() {
835 		var i = focusables.length;
836 		
837 		glow.events.removeAllListeners( [this] );
838 		
839 		this.modal(false).active(false).container
840 			// remove listeners
841 			.detach('mouseover', hoverListener)
842 			.detach('click', clickSelectListener)
843 			// remove from tab order
844 			.prop('tabIndex', -1);
845 			
846 		this.container = undefined;
847 		
848 		// remove this focusable from the static array
849 		while (i--) {
850 			if (focusables[i] === this) {
851 				focusables.splice(i, 1);
852 				break;
853 			}
854 		}
855 	}
856 	
857 	/**
858 		@name glow.ui.Focusable#event:select
859 		@event
860 		@description Fires when a child of the Focusable is selected.
861 			Items are selected by clicking, or pressing enter when a child is active.
862 		
863 			Cancelling this event prevents the default click/key action.
864 		
865 		@param {glow.events.Event} event Event Object
866 		@param {glow.NodeList} event.item Item selected.
867 		@param {number} event.itemIndex The index of the selected item in {@link glow.ui.Focusable#children}.
868 	*/
869 	
870 	/**
871 		@private
872 		@function
873 		@description
874 			Listens for click selections on the Focusable
875 			'this' is the Focusable.
876 	*/
877 	function clickSelectListener() {
878 		if ( this.activeChild[0] ) {
879 			return !this.fire('select', {
880 				item: this.activeChild,
881 				itemIndex: this.activeIndex
882 			}).defaultPrevented();
883 		}
884 	}
885 	
886 	/**
887 		@private
888 		@function
889 		@description
890 			Same as above, but for keys
891 			'this' is the Focusable.
892 	*/
893 	function keySelectListener(event) {
894 		if (event.key === 'return') {
895 			return clickSelectListener.call(this);
896 		}
897 	}
898 	
899 	/**
900 		@name glow.ui.Focusable#event:activate
901 		@event
902 		@description Fires when the Focusable becomes active
903 			Cancelling this event prevents the Focusable being actived
904 		
905 		@param {glow.events.Event} event Event Object
906 	*/
907 	
908 	/**
909 		@name glow.ui.Focusable#event:childActivate
910 		@event
911 		@description Fires when a child item of the Focusable becomes active
912 			Cancelling this event prevents the child item being actived
913 		
914 		@param {glow.events.Event} event Event Object
915 		@param {glow.NodeList} event.item Item activated.
916 		@param {number} event.itemIndex The index of the activated item in {@link glow.ui.Focusable#children}.
917 		@param {string} event.method Either 'key', 'hover' or 'api' depending on how the item was activated.
918 			This allows you to react to certain kinds of activation.
919 		@param {glow.events.DomEvent} [event.methodEvent] An event object for the 'key' or 'hover' event.
920 			For 'key' methods this will be a more specific {@link glow.events.KeyboardEvent}.
921 			
922 			If the method was neither 'key' or 'hover', methodEvent will be undefined.
923 	*/
924 	
925 	/**
926 		@name glow.ui.Focusable#event:deactivate
927 		@event
928 		@description Fires when the Focusable becomes deactive
929 			Cancelling this event prevents the Focusable being deactived
930 		
931 		@param {glow.events.Event} event Event Object
932 	*/
933 	
934 	// EXPORT
935 	glow.ui.Focusable = Focusable;
936 });
937 Glow.provide(function(glow) {
938 	var undefined,
939 		WidgetProto;
940 
941 	/**
942 		@name glow.ui.Widget
943 		@constructor
944 		@extends glow.events.Target
945 		
946 		@description An abstract Widget class
947 			The Widget class serves as a base class that provides a shared framework on which other,
948 			more specific, widgets can be implemented. While it is possible to create an instance
949 			of this generic widget, it is more likely that your widget class will extend this class.
950 			
951 			Your widget constructor should call the base constructor, and should end in a call to _init.
952 			
953 		@param {string} name The name of this widget.
954 			This is added to class names in the generated DOM nodes that wrap the widget interface.
955 			
956 		@param {object} [opts]
957 			@param {string} [opts.className] Class name to add to the container.
958 			@param {string} [opts.id]  Id to add to the container.
959 			
960 		@example
961 			function MyWidget(opts) {
962 				// set up your widget...
963 				// call the base constructor, passing in the name and the options
964 				glow.ui.Widget.call(this, 'MyWidget', opts);
965 				
966 				// start init
967 				this._init();
968 			}
969 			glow.util.extend(MyWidget, glow.ui.Widget);
970 	*/
971 	
972 	function Widget(name, opts) {
973 		/*!debug*/
974 			if (arguments.length < 1 || arguments.length > 2) {
975 				glow.debug.warn('[wrong count] glow.ui.Widget expects 1 or 2 arguments, not '+arguments.length+'.');
976 			}
977 			if (typeof arguments[0] !== 'string') {
978 				glow.debug.warn('[wrong type] glow.ui.Widget expects argument 1 to be of type string, not '+typeof arguments[0]+'.');
979 			}
980 		/*gubed!*/
981 		
982 		this._name = name;
983 		this._locale = 'en'; // todo: default should come from i18n module
984 		this.phase = 'constructed';
985 		this._observers = [];
986 		this._opts = opts || {};
987 	}
988 	glow.util.extend(Widget, glow.events.Target); // Widget is a Target
989 	WidgetProto = Widget.prototype;
990 	/**
991 		@name glow.ui.Widget#_locale
992 		@protected
993 		@type string
994 		@description The locale of the widget
995 	*/
996 	/**
997 		@name glow.ui.Widget#_name
998 		@protected
999 		@type string
1000 		@description The name of the widget.
1001 			This is the first argument passed into the constructor.
1002 	*/
1003 	/**
1004 		@name glow.ui.Widget#_stateElm
1005 		@protected
1006 		@type glow.NodeList
1007 		@description The wrapper element that contains the state class
1008 	*/
1009 	/**
1010 		@name glow.ui.Widget#_themeElm
1011 		@protected
1012 		@type glow.NodeList
1013 		@description The wrapper element that contains the theme class
1014 	*/
1015 	/**
1016 		@name glow.ui.Widget#_opts
1017 		@protected
1018 		@type Object
1019 		@description The option object passed into the constructor
1020 	*/
1021 	/**
1022 		@name glow.ui.Widget#_disabled
1023 		@protected
1024 		@type boolean
1025 		@description Is the widget disabled?
1026 			This is read-only
1027 	*/
1028 	WidgetProto._disabled = false;
1029 	/**
1030 		@name glow.ui.Widget#_observers
1031 		@protected
1032 		@type object[]
1033 		@description Objects (usually widgets & dehaviours) observing this Widget
1034 	*/
1035 	
1036 	/**
1037 		@name glow.ui.Widget#phase
1038 		@type string
1039 		@description The phase within the lifecycle of the widget.
1040 			Will be one of the following:
1041 			
1042 			<dl>
1043 				<dt>constructed</dt>
1044 				<dd>The widget has been constructed but not yet initialised</dd>
1045 				<dt>initialised</dt>
1046 				<dd>The widget has been initialised but not yet build</dd>
1047 				<dt>built</dt>
1048 				<dd>The widget has been built but not yet bound</dd>
1049 				<dt>ready</dt>
1050 				<dd>The widget is in a fully usable state</dd>
1051 				<dt>destroyed</dt>
1052 				<dd>The widget has been destroyed</dd>
1053 			</dl>
1054 			
1055 			Usually, init, build & bind are done in the constructor, so
1056 			you may only interact with a widget that is either 'ready' or 'destroyed'.
1057 	*/
1058 	/**
1059 		@name glow.ui.Widget#container
1060 		@type glow.NodeList
1061 		@description The outermost wrapper element of the widget.
1062 	*/
1063 	/**
1064 		@name glow.ui.Widget#content
1065 		@type glow.NodeList
1066 		@description The content element of the widget
1067 			This is inside various wrapper elements used to track the state of
1068 			the widget.
1069 	*/
1070 	
1071 	function syncListener(e) {
1072 		// handle notifications about changes to the disabled state
1073 		if (e.disabled !== undefined) {
1074 			this.disabled(e.disabled);
1075 		}
1076 		else if (e.destroy) {
1077 			this.destroy();
1078 		}
1079 	}
1080 	
1081 	/**
1082 		@name glow.ui.Widget#_tie
1083 		@protected
1084 		@function
1085 		@description Specify a group of widgets that should stay in _sync with this one.
1086 			These synced widgets can listen for a '_sync' event on themselves, defining their own handler for the provided event.
1087 			The disabled and destroy methods automatically synchronize with their synced child widgets.
1088 		
1089 		@param {glow.ui.Widget} ... Child widgets to synchronize with.
1090 		
1091 		@example
1092 			function MyWidget() {
1093 				this.value = 0; // initially
1094 				
1095 				// this widget handles notifications of new values
1096 				// from other widgets it is syced to
1097 				var that = this;
1098 				this.on('_sync', function(e) {
1099 					if (typeof e.newValue !== undefined) {
1100 						that.value = e.newValue;
1101 					}
1102 				});	
1103 			}
1104 			glow.util.extend(MyWidget, glow.ui.Widget); // MyWidget extends the Base Widget
1105 			
1106 			MyWidget.prototype.setValue = function(n) {
1107 				this.value = n;
1108 				
1109 				// this widget notifies about changes to its value
1110 				this._sync({newValue: this.value});
1111 			}
1112 			
1113 			// widgets b and c will all be notified when a's value changes
1114 			var a = new MyWidget('A');
1115 			var b = new MyWidget('B');
1116 			var c = new MyWidget('C');
1117 			
1118 			a._tie(b, c);
1119 			
1120 			a.setValue(1); // a, b, and c all have a value of 1 now
1121 	 */
1122 	WidgetProto._tie = function(/*...*/) {
1123 		/*!debug*/
1124 			if (arguments.length === 0) {
1125 				glow.debug.warn('[wrong count] glow.ui.Widget#_tie expects at least 1 argument, not '+arguments.length+'.');
1126 			}
1127 		/*gubed!*/
1128 		
1129 		var newObservers = Array.prototype.slice.call(arguments),
1130 			i = newObservers.length;
1131 		
1132 		// add a default _sync listener to react to disabled and destroy
1133 		while (i--) {
1134 			newObservers[i].on('_sync', syncListener);
1135 		}
1136 		
1137 		this._observers = this._observers.concat(newObservers);
1138 		
1139 		return this;
1140 	}
1141 
1142 	/**
1143 		@developer
1144 		@name glow.ui.Widget#_sync
1145 		@method
1146 		@param {object} [changes] Key/value collection of new information. Will be added to the listeners' event.
1147 		@description Tell all widgets synced with this widget about any changes.
1148 	 */
1149 	WidgetProto._sync = function(changes) { // public shortcut to fire _notify
1150 		/*!debug*/
1151 			if (arguments.length > 1) {
1152 				glow.debug.warn('[wrong count] glow.ui.Widget#_sync expects 1 or fewer arguments, not '+arguments.length+'.');
1153 			}
1154 			
1155 			if (arguments.length && typeof changes !== 'object') {
1156 				glow.debug.warn('[wrong type] glow.ui.Widget#_sync expects argument 1 to be of type object, not '+(typeof changes)+'.');
1157 			}
1158 		/*gubed!*/
1159 		
1160 		glow.events.fire( this._observers, '_sync', changes || {} );
1161 		
1162 		return this;
1163 	}
1164 
1165 	/**
1166 		@name glow.ui.Widget#disabled
1167 		@function
1168 		@description Enable/disable the Widget, or get the disabled state
1169 			If other widgets are synced with this one, they will become disabled too.
1170 			
1171 		@param {boolean} [newState] Disable the focusable?
1172 			'false' will enable a disabled focusable.
1173 		
1174 		@example
1175 			var a = new MyWidget();
1176 			var b = new MyWidget();
1177 			var c = new MyWidget();
1178 			
1179 			c._tie(a, b);
1180 			
1181 			c.disabled(true); // a, b, and c are now disabled
1182 	 */
1183 	WidgetProto.disabled = function(newState) {
1184 		/*!debug*/
1185 			if (arguments.length > 1) {
1186 				glow.debug.warn('[wrong count] glow.ui.Widget#disabled expects 0 or 1 argument, not ' + arguments.length + '.');
1187 			}
1188 		/*gubed!*/
1189 		
1190 		// geting
1191 		if (newState === undefined) {
1192 			return this._disabled;
1193 		}
1194 		
1195 		// setting
1196 		newState = !!newState;
1197 		if ( newState !== this._disabled && !this.fire('disabled', {disabled:newState}).defaultPrevented() ) {
1198 			this._sync({
1199 				disabled: newState
1200 			});
1201 			this._stateElm[newState ? 'addClass' : 'removeClass']('disabled');
1202 			this._disabled = !!newState;
1203 		}
1204 		return this;
1205 	}
1206 
1207 	/**
1208 		@name glow.ui.Widget#_init
1209 		@protected
1210 		@function
1211 		@description Initialise the widget.
1212 			This is similar to the constructor, but for code that you may need to run
1213 			again.
1214 			
1215 			You init function should call the base _init, and end in a call to _build on your widget.
1216 		
1217 		@example
1218 			MyWidget.prototype._init = function() {
1219 				// set properties here
1220 				
1221 				// call base _init
1222 				glow.ui.Widget.prototype._init.call(this);
1223 				// call _build
1224 				this._build();
1225 			}
1226 		
1227 	 */
1228 	WidgetProto._init = function() {
1229 		this.phase = 'initialised';
1230 	}
1231 
1232 	/**
1233 		@name glow.ui.Widget#_build
1234 		@protected
1235 		@function
1236 		@description Build the html structure for this widget.
1237 			All actions relating to wrapping, creating & moving elements should be
1238 			done in this method. The base method creates the container, theme & state elements.
1239 			
1240 			Adding behaviour to these elements should be handed in {@link glow.ui.Widget#_bind}.
1241 			
1242 			You Widget's _build method should call the base build method and end in a call to _bind.
1243 		
1244 		@param {selector|HTMLElement|NodeList} [content] Content element for the widget.
1245 			This will be wrapped in container, theme & state elements. By default this is
1246 			an empty div.
1247 			
1248 		@example
1249 			MyWidget.prototype._build = function() {
1250 				// create some content
1251 				var content = glow('<p>Hello!</p>');
1252 				// call the base build
1253 				glow.ui.Widget.prototype._build.call(this, content);
1254 				// call _bind
1255 				this._bind();
1256 			}
1257 	 */
1258 	WidgetProto._build = function(content) {
1259 		/*!debug*/
1260 			if (arguments.length > 1) {
1261 				glow.debug.warn('[wrong count] glow.ui.Widget#_build expects 0-1 argument, not '+arguments.length+'.');
1262 			}
1263 		/*gubed!*/
1264 		
1265 		var container,
1266 			name = this._name,
1267 			opts = this._opts;
1268 		
1269 		content = this.content = glow(content || '<div></div>');
1270 		
1271 		/*!debug*/
1272 			if (content.length < 1) {
1273 				glow.debug.warn('[error] glow.ui.Widget#_build expects a content node to attach to. The given "content" argument was empty or not found.');
1274 			}
1275 		/*gubed!*/
1276 		
1277 		container = this.container = glow('' +
1278 			'<div class="glow200b1-' + name + '">' +
1279 				'<div class="' + name + '-theme">' +
1280 					'<div class="' + name + '-state"></div>' +
1281 				'</div>' +
1282 			'</div>' +
1283 		'');
1284 		
1285 		content.addClass(name + '-content').wrap(container);
1286 		this._stateElm = content.parent();
1287 		this._themeElm = this._stateElm.parent();
1288 		
1289 		if (opts.className) {
1290 			container.addClass(opts.className);
1291 		}
1292 		if (opts.id) {
1293 			container[0].id = opts.id;
1294 		}
1295 		
1296 		this.phase = 'built';
1297 	}
1298 	
1299 	/**
1300 		@developer
1301 		@name glow.ui.Widget#_bind
1302 		@function
1303 		@description Add behaviour to elements created in {@link glow.ui.Widget#_build _build}.
1304 			Your _bind method should call the base _bind and may end in a call
1305 			to _updateUi for initial positioning etc.
1306 			
1307 		@example
1308 			MyWidget.prototype._bind = function() {
1309 				// add some behaviour
1310 				this.content.on('click', function() {
1311 					alert('Hello!');
1312 				});
1313 				// call base _bind
1314 				glow.ui.Widget.prototype._bind.call(this);
1315 			}
1316 	 */
1317 	WidgetProto._bind = function() {
1318 		this.phase = 'ready';
1319 	}
1320 
1321 	/**
1322 		@name glow.ui.Widget#_updateUi
1323 		@function
1324 		@description Cause any functionality that deals with visual layout or UI display to update.
1325 			This function should be overwritten by Widgets that need to update or redraw. For example,
1326 			you may use this method to reposition or reorder elements.
1327 			
1328 			This is a convention only, the base method does nothing.
1329 		
1330 		@example
1331 			MyWidget.prototype.updateUi = function() {
1332 				// update the UI
1333 			}
1334 			
1335 	 */
1336 	WidgetProto._updateUi = function() {}
1337 
1338 	/**
1339 		@developer
1340 		@name glow.ui.Widget#destroy
1341 		@function
1342 		@description Cause any functionality that deals with removing and deleting this widget to run.
1343 			By default the container and all it's contents are removed.
1344 		@fires glow.ui.Widget#event:destroy
1345 	 */
1346 	WidgetProto.destroy = function() {
1347 		/*!debug*/
1348 			if (arguments.length !== 0) {
1349 				glow.debug.warn('[wrong count] glow.ui.Widget#destroy expects 0 arguments, not '+arguments.length+'.');
1350 			}
1351 		/*gubed!*/
1352 		if ( !this.fire('destroy').defaultPrevented() ) {
1353 			this._sync({
1354 				destroy: 1
1355 			});
1356 			glow.events.removeAllListeners( [this] );
1357 			this.container.destroy();
1358 			this.phase = 'destroyed';
1359 		}
1360 		return this;
1361 	}
1362 
1363 	/**
1364 		@developer
1365 		@name glow.ui.Widget#event:disable
1366 		@event
1367 		@description Fired after the disabled property is changed via the {@link glow.ui.Widget#disable} or {@link glow.ui.Widget#enable} method.
1368 		This includes widgets that are changed as a result of being synced to this one.
1369 	 */
1370 	
1371 	/**
1372 		@developer
1373 		@name glow.ui.Widget#event:destroy
1374 		@event
1375 		@description Fired when destroy is called on this widget.
1376 		@see glow.ui.Widget#destroy
1377 	 */
1378 
1379 	// export
1380 	glow.ui.Widget = Widget;
1381 });
1382 Glow.provide(function(glow) {
1383 	var OverlayProto,
1384 		WidgetProto = glow.ui.Widget.prototype,
1385 		idCounter = 0,
1386 		undefined,
1387 		instances = {}; // like {uid: overlayInstance}
1388 	
1389 	
1390 	var vis = {
1391 		SHOWING: 2,
1392 		SHOWN: 1,
1393 		HIDING: -1,
1394 		HIDDEN: -2
1395 	};
1396 	
1397 	
1398 	/**
1399 		@name glow.ui.Overlay
1400 		@class
1401 		@augments glow.ui
1402 		@description A container element displayed on top of the other page content
1403 		@param {selector|NodeList|String|boolean} content
1404 			the element that contains the contents of the overlay. If not in the document, you must append it to the document before calling show().
1405 
1406 		@param {object} [opts]
1407 			@param {function|selector|NodeList|boolean} [opts.hideWhenShown] Select which things to hide whenevr the overlay is in a shown state.
1408 			By default all `object` and `embed` elements will be hidden, in browsers that cannot properly layer those elements, whenever any overlay is shown.
1409 			Set this option to a false value to cause the overlay to never hide any elements, or set it to a bespoke selector, NodeList
1410 			or a function that returns a NodeList which will be used instead.
1411 		@example
1412 			var myOverlay = new glow.ui.Overlay(
1413 				glow(
1414 					'<div>' +
1415 					'  <p>Your Story has been saved.</p>' +
1416 					'</div>'
1417 				).appendTo(document.body)
1418 			);
1419 			
1420 			glow('#save-story-button').on('click', function() {
1421 				myOverlay.show();
1422 			});
1423 	 */
1424 	
1425 	function Overlay(content, opts) {
1426 		/*!debug*/
1427 			if (arguments.length < 1 || content === undefined) {
1428 				glow.debug.warn('[wrong type] glow.ui.Overlay expects "content" argument to be defined, not ' + typeof content + '.');
1429 			}
1430 			if (opts !== undefined && typeof opts !== 'object') {
1431 				glow.debug.warn('[wrong type] glow.ui.Overlay expects object as "opts" argument, not ' + typeof opts + '.');
1432 			}
1433 		/*gubed!*/
1434 		var that = this,
1435 			ua;
1436 		
1437 		opts = glow.util.apply({ }, opts);
1438 		
1439 		//call the base class's constructor
1440 		Overlay.base.call(this, 'overlay', opts);
1441 		
1442 		this.uid = 'overlayId_' + glow.UID + '_' + (++idCounter);
1443 		instances[this.uid] = this; // useful for modal overlays?
1444 		
1445 		this._init(opts);
1446 		this._build(content);
1447 		this._bind();
1448 	}
1449 	glow.util.extend(Overlay, glow.ui.Widget);
1450 	
1451 	OverlayProto = Overlay.prototype;
1452 	
1453 	OverlayProto._init = function() {
1454 		WidgetProto._init.call(this);
1455 		
1456 		/**
1457 			@name glow.ui.Overlay#shown
1458 			@description True if the overlay is shown.
1459 				This is a read-only property to check the state of the overlay.
1460 			@type boolean
1461 		*/
1462 		this.shown = false;
1463 		
1464 		return this;
1465 	}
1466 	
1467 	OverlayProto.destroy = function() {
1468 		WidgetProto.destroy.call(this);
1469 		
1470 		delete instances[this.uid];
1471 	}
1472 	
1473 	OverlayProto._build = function(content) {
1474 		var that = this;
1475 		
1476 		WidgetProto._build.call(this, content);
1477 		
1478 		/*!debug*/
1479 			if (this.content.length < 1) {
1480 				glow.debug.warn('[ivalid argument] glow.ui.Overlay expects "content" argument to refer to an element that exists, no elements found for the content argument provided.');
1481 			}
1482 		/*gubed!*/
1483 		
1484 		// some browsers need to hide Flash when the overlay is shown (like non-mac opera and gecko 1.9 or less)
1485 		if (this._opts.hideWhenShown === undefined) { // need to make our own flash handler
1486 			ua = navigator.userAgent; // like "... rv:1.9.0.5) gecko ..."
1487 			/**
1488 				A function that returns a NodeList containing all elements that need to be hidden.
1489 				@name glow.ui.Overlay#_whatToHide
1490 				@private
1491 				@returns {glow.NodeList} Elements that need to be hidden when the overlay is shown.
1492 			 */
1493 			this._whatToHide = (
1494 				glow.env.opera && !/macintosh/i.test(ua)
1495 				|| /rv:1\.9\.0.*\bgecko\//i.test(ua)
1496 				|| glow.env.webkit && !/macintosh/i.test(ua)
1497 			)?
1498 				function() {
1499 					return glow('object, embed')/*.filter(function() {
1500 						return !that.container.contains(this); // don't hide elements that are inside the overlay
1501 					});*/
1502 				}
1503 				: function() { return glow(); }
1504 		}
1505 		else { // user provides their own info about what to hide
1506 			if (!this._opts.hideWhenShown) { // a value that is false
1507 				this._whatToHide = function() { return glow(); }
1508 			}
1509 			else if (typeof this._opts.hideWhenShown === 'function') { // a function
1510 				this._whatToHide = this._opts.hideWhenShown;
1511 			}
1512 			else if (this._opts.hideWhenShown.length !== undefined) { // nodelist or string?
1513 				this._whatToHide = function() { return glow('*').filter(this._opts.hideWhenShown); }
1514 			}
1515 		}
1516 		
1517 		//add IE iframe hack if needed, wrap content in an iFrame to prevent certain elements below from showing through
1518 		if (glow.env.ie) {
1519 			this._iframe = glow('<iframe src="javascript:\'\'" style="display:block;width:100%;height:1000px;margin:0;padding:0;border:none;position:absolute;top:0;left:0;filter:alpha(opacity=0);"></iframe>')
1520 			this._iframe.css('z-index', 0);
1521 			
1522 			this._iframe.insertBefore(this.content);
1523 		}
1524 		
1525 		this.content
1526 			.css('position', 'relative')
1527 			.css('z-index', 1)
1528 			.css('top', 0)
1529 			.css('left', 0);
1530 
1531 		return this;
1532 	}
1533 	
1534 	/**
1535 		@name glow.ui.Overlay#hideFlash
1536 		@method
1537 		@description Hides all Flash elements on the page, outside of the overlay.
1538 		This should only be neccessary on older browsers that cannot properly display
1539 		overlay content on top of Flash elements. On those browsers Glow will automatically
1540 		call this method for you in the onshow event, and will automatically call
1541 		showFlash for you in the afterhide event.
1542 	 */
1543 	OverlayProto.hideFlash = function() { /*debug*///console.log('hideFlash');
1544 		var toHide,
1545 			that = this,
1546 			hidBy = '';
1547 			
1548 		toHide = this._whatToHide();
1549 		
1550 		// multiple overlays may hide the same element
1551 		// flash elements keep track of which overlays have hidden them
1552 		// trying to hide a flash element more than once does nothing
1553 		for (var i = 0, leni = toHide.length; i < leni; i++) {
1554 			hidBy = (toHide.item(i).data('overlayHidBy') || '');
1555 			
1556 			if (hidBy === '') {
1557 				toHide.item(i).data('overlayOrigVisibility', toHide.item(i).css('visibility'));
1558 				toHide.item(i).css('visibility', 'hidden');
1559 			}
1560 			if (hidBy.indexOf('['+this.uid+']') === -1) {
1561 				toHide.item(i).data('overlayHidBy', hidBy + '['+this.uid+']');
1562 			}
1563 		}
1564 		
1565 		// things were hidden, make sure they get shown again
1566 		if (toHide.length && !that._doShowFlash) { // do this only once
1567 			that._doShowFlash = true;
1568 			that.on('afterHide', function() { /*debug*///console.log('callback');
1569 				that.showFlash();
1570 			});
1571 		}
1572 		
1573 		this._hiddenElements = toHide;
1574 	}
1575 	
1576 	/**
1577 		@name glow.ui.Overlay#showFlash
1578 		@method
1579 		@description Hides all Flash elements on the page, outside of the overlay.
1580 		If a Flash element has been hidden by more than one overlay, you must call
1581 		showFlash once for each time it was hidden before the Flash will finally appear.
1582 	 */
1583 	OverlayProto.showFlash = function() { /*debug*///console.log('showFlash');
1584 		var hidBy = '';
1585 		
1586 		if (!this._hiddenElements || this._hiddenElements.length === 0) { // this overlay has not hidden anything?
1587 			return;
1588 		}
1589 		
1590 		var toShow = this._hiddenElements;
1591 		
1592 		for (var i = 0, leni = toShow.length; i < leni; i++) {
1593 			hidBy = (toShow.item(i).data('overlayHidBy') || '');
1594 			
1595 			if (hidBy.indexOf('['+this.uid+']') > -1) { // I hid this
1596 				hidBy = hidBy.replace('['+this.uid+']', ''); // remove me from the list of hiders
1597 				toShow.item(i).data('overlayHidBy', hidBy);
1598 			}
1599 			
1600 			if (hidBy == '') { // no hiders lefts
1601 				toShow.item(i).css( 'visibility', toShow.item(i).data('overlayOrigVisibility') );
1602 			}
1603 		}
1604 	}
1605 	
1606 	/**
1607 		@name glow.ui.Overlay#event:show
1608 		@event
1609 		@description Fired when the overlay is about to appear on the screen, before any animation.
1610 
1611 			At this	point you can access the content of the overlay and make changes 
1612 			before it is shown to the user. If you prevent the default action of this
1613 			event (by returning false or calling event.preventDefault) the overlay 
1614 			will not show.
1615 			
1616 		@param {glow.events.Event} event Event Object
1617 	*/
1618 	
1619 	/**
1620 		@name glow.ui.Overlay#event:afterShow
1621 		@event
1622 		@description Fired when the overlay is showing to the user and any delay or 'show' animation is complete.
1623 
1624 			This event is ideal to assign focus to a particular part of	the overlay.
1625 			If you want to change content of the overlay before it appears, see the 
1626 			'show' event.
1627 			
1628 		@param {glow.events.Event} event Event Object
1629 	*/
1630 	
1631 	/**
1632 		@name glow.ui.Overlay#event:hide
1633 		@event
1634 		@description Fired when the overlay is about to hide.
1635 
1636 			If you prevent the default action of this event (by returning false or 
1637 			calling event.preventDefault) the overlay will not hide.
1638 			
1639 		@param {glow.events.Event} event Event Object
1640 	*/
1641 	
1642 	/**
1643 		@name glow.ui.Overlay#event:afterHide
1644 		@event
1645 		@description Fired when the overlay has fully hidden, after any delay or hiding animation has completed.
1646 		@param {glow.events.Event} event Event Object
1647 	*/
1648 	
1649 	// animations that can be referred to in setAnim by string.
1650 	// Each is an array of 2 item, one function to put the Overlay in an initial state
1651 	// for this animation, and one for the animation itself
1652 	var anims = {
1653 		slide: [
1654 			function(overlay) {
1655 				overlay.container.height(0);
1656 			},
1657 			function(isShow, callback) {
1658 				var anim,
1659 					container = this.container;
1660 					
1661 				if (isShow) {
1662 					anim = container.slideOpen(0.5).data('glow_slideOpen');
1663 				}
1664 				else {
1665 					anim = container.slideShut(0.5).data('glow_slideShut');
1666 				}
1667 				
1668 				anim.on('complete', callback);
1669 			}
1670 		],
1671 		fade: [
1672 			function(overlay) {
1673 				overlay.container.css('opacity', 0);
1674 			},
1675 			function(isShow, callback) {
1676 				var anim,
1677 					container = this.container;
1678 					
1679 				if (isShow) {
1680 					anim = container.fadeIn(0.5).data('glow_fadeIn');
1681 				}
1682 				else {
1683 					anim = container.fadeOut(0.5).data('glow_fadeOut');
1684 				}
1685 				
1686 				anim.on('complete', callback);
1687 			}
1688 		]
1689 	}
1690 	
1691 	/**
1692 		@name glow.ui.Overlay#setAnim
1693 		@function
1694 		@description Set the animation to use when showing and hiding this overlay.
1695 		@param {string|Array|Function|null} anim Anim to use.
1696 			At its simplest, this can be the string 'slide' or 'fade', to give
1697 			the overlay a fading/sliding animation.
1698 		
1699 			If this value is an animation definition, in the form of an array of
1700 			arguments to pass to the {@link glow.Anim} constructor, those values
1701 			will be used to create the show animation. The hide animation will
1702 			then be the reverse of the show. This is the easiest option if you
1703 			intend your show and hide animations to simply reverse one another.
1704 			
1705 			Alternatively, if you need more control over your show and hide
1706 			animations, you can provide a function.	This function will be called
1707 			whenever the overlay has its show or hide method invoked, and will
1708 			be provided a boolean (true meaning it's being shown, false meaning
1709 			it's being hidden), and a callback. You can then manage the animations
1710 			yourself within that function, and then invoke the callback when
1711 			either animation is complete. In your function, 'this' refers to the
1712 			overlay.
1713 			
1714 			Passing null will delete any previously set animation.
1715 		
1716 		@returns this
1717 	*/
1718 	OverlayProto.setAnim = function(anim) {
1719 		if (anim === null) {
1720 			delete this._animDef;
1721 			delete this._animator;
1722 		}
1723 		else if (typeof anim === 'string') {
1724 			anims[anim][0](this);
1725 			this._animator = anims[anim][1];
1726 		}
1727 		else if (typeof anim === 'function') {
1728 			this._animator = anim;
1729 		}
1730 		else {
1731 			this._animDef = anim;
1732 			this._animDef[2] = this._animDef[2] || {};
1733 			this._animDef[2].destroyOnComplete = false;
1734 		}
1735 		
1736 		return this;
1737 	}
1738 	
1739 	/**
1740 		@name glow.ui.Overlay#show
1741 		@function
1742 		@param {number} [delay=0] The delay before the overlay is shown.
1743 			By default, the overlay will show immediately. Specify a number of seconds to delay showing.
1744 			The event "afterShow" will be called after any delay and animation.
1745 		@description Displays the overlay after an optional delay period and animation.
1746 
1747 		@returns this
1748 	*/
1749 	OverlayProto.show = function(delay) {
1750 		//if (this.shown) { /*debug*///console.log('show ignored');
1751 		//	return this;
1752 		//}
1753 		var that = this;
1754 		
1755 		if ( !this.fire('show').defaultPrevented() ) {
1756 			if (this._timer) {
1757 				clearTimeout(this._timer);
1758 			}
1759 			
1760 			if (delay) {
1761 				this._timer = setTimeout(function() {
1762 					show.call(that);
1763 				}, delay * 1000);
1764 			}
1765 			else {
1766 				show.call(that);
1767 			}
1768 		}
1769 		
1770 		return this;
1771 	}
1772 	
1773 	function show() { /*debug*///console.log('show() curently '+this.state);
1774 		var that = this;
1775 		
1776 		// already being shown?
1777 		if (this.state === vis.SHOWING || this.state === vis.SHOWN) {
1778 			return;
1779 		}
1780 		
1781 		setShown(that, true);
1782 		
1783 		if (this._whatToHide) { this.hideFlash(); }
1784 		
1785 		if (this._animator) {
1786 			that.state = vis.SHOWING;
1787 			this._animator.call(this, true, function() { afterShow.call(that); });
1788 		}
1789 		else if (this._animDef) {
1790 			if (this._anim) { // is hiding?
1791 				this.state = vis.SHOWING;
1792 				this._anim.reverse();
1793 			}
1794 			else { // is hidden?
1795 				this.state = vis.SHOWING;
1796 				
1797 				// this same anim is reused (by reversing it) for showing and hiding
1798 				this._anim = this.container.anim(this._animDef[0], this._animDef[1], this._animDef[2]);
1799 				this._anim.on('complete', function() {
1800 					
1801 					if (that.state === vis.SHOWING) {
1802 						setShown(that, true);
1803 						afterShow.call(that);
1804 					} 
1805 					else if (that.state === vis.HIDING) {
1806 						setShown(that, false);
1807 						afterHide.call(that);
1808 					}
1809 				});
1810 			}
1811 			
1812 			this._anim.start();
1813 		}
1814 		else {
1815 			afterShow.call(this);
1816 		}
1817 	}
1818 	
1819 	function afterShow() { /*debug*///console.log('after show');
1820 		this.state = vis.SHOWN;
1821 		this.fire('afterShow');
1822 	}
1823 	
1824 	/**
1825 		@private
1826 		@function
1827 		@description Set the shown state & add/remove a class from the state element
1828 	*/
1829 	function setShown(overlay, shownState) {
1830 		var stateElm = overlay._stateElm;
1831 		
1832 		overlay.shown = shownState;
1833 		
1834 		if (shownState) {
1835 			stateElm.removeClass('hidden');
1836 			stateElm.addClass('shown');
1837 		}
1838 		else {
1839 			stateElm.removeClass('shown');
1840 			stateElm.addClass('hidden');
1841 		}
1842 	}
1843 	
1844 	function hide() { /*debug*///console.log('hide() curently '+this.state);
1845 		var that = this;
1846 		
1847 		if (this.state === vis.HIDING || this.state === vis.HIDDEN) {
1848 			return;
1849 		}
1850 		
1851 		if (this._animator) { // provided by user
1852 			this._animator.call(this, false, function() {
1853 				setShown(that, false);
1854 				afterHide.call(that);
1855 			});
1856 		}
1857 		else if (this._anim) { // generated by overlay
1858 			this.state = vis.HIDING;
1859 			this._anim.reverse();
1860 			this._anim.start();
1861 		}
1862 		else { // no animation
1863 			setShown(that, false);
1864 			afterHide.call(this);
1865 		}
1866 	}
1867 	
1868 	function afterHide() { /*debug*///console.log('after hide');
1869 		this.state = vis.HIDDEN;
1870 		this.fire('afterHide');
1871 	}
1872 	
1873 	/**
1874 		@name glow.ui.Overlay#hide
1875 		@function
1876 		@param {number} [delay=0] The delay before the overlay is shown.
1877 			By default, the overlay will show immediately. Specify a number of seconds to delay showing.
1878 			The event "afterShow" will be called after any delay and animation.
1879 		@description Hides the overlay after an optional delay period and animation
1880 
1881 		@returns this
1882 	*/
1883 	OverlayProto.hide = function(delay) {
1884 		//if (!this.shown) { /*debug*///console.log('hide ignored');
1885 		//	return this;
1886 		//}
1887 		
1888 		var that = this;
1889 		
1890 		if ( !this.fire('hide').defaultPrevented() ) {
1891 			if (this._timer) {
1892 				clearTimeout(this._timer);
1893 			}
1894 			
1895 			if (delay) {
1896 				this._timer = setTimeout(function() {
1897 					hide.call(that);
1898 				}, delay * 1000);
1899 			}
1900 			else {
1901 				hide.call(that);
1902 			}
1903 		}
1904 		
1905 		return this;
1906 	}
1907 
1908 	// export
1909 	glow.ui = glow.ui || {};
1910 	glow.ui.Overlay = Overlay;
1911 });
1912 Glow.provide(function(glow) {
1913 	var undefined, AutoSuggestProto,
1914 		Widget = glow.ui.Widget,
1915 		WidgetProto = Widget.prototype,
1916 		// this is used for HTML escaping in _format
1917 		tmpDiv = glow('<div></div>');
1918 	
1919 	/**
1920 		@name glow.ui.AutoSuggest
1921 		@extends glow.ui.Widget
1922 		@constructor
1923 		@description Create a menu that displays results filtered by a search term.
1924 			This widget can be easily linked to a text input via {@link glow.ui.AutoSuggest#linkToInput}
1925 			so results will be filtered by text entered by the user. This appears as a list of selectable
1926 			items below the input element (optional) which dynamically updates based on what
1927 			has been typed so far.
1928 			
1929 			By default, items where the search term matches the start of the item
1930 			(or its 'name' property) will be returned. You can change the
1931 			filtering behaviour via {@link glow.ui.AutoSuggest#setFilter setFilter}.
1932 			
1933 			The matched item (or its 'name' property) will be displayed with the matching
1934 			portion underlined. You can change the output via {@link glow.ui.AutoSuggest#setFormat setFormat}
1935 		
1936 		@param {Object} [opts] Options				
1937 			@param {number} [opts.width] Apply a width to the results list.
1938 				By default, the AutoSuggest is the full width of its containing element,
1939 				or the width of the input it's linked to if autoPositioning.
1940 			@param {number} [opts.maxResults] Limit the number of results to display.
1941 			@param {number} [opts.minLength=3] Minimum number of chars before search is executed
1942 				This prevents searching being performed until a specified amount of chars
1943 				have been entered.
1944 			@param {boolean} [opts.caseSensitive=false] Whether case is important when matching suggestions.
1945 				If false, the value passed to the filter will be made lowercase, a custom filter
1946 				must also lowercase the property it checks.
1947 			@param {boolean} [opts.activateFirst=true] Activate the first item when results appear?
1948 				If false, results with be shown with no active item.
1949 			@param {function|string} [opts.keyboardNav='arrow-y'] Alter the default keyboard behaviour.
1950 				This is the same as keyboardNav in {@link glow.ui.Focusable}.
1951 		
1952 		@example
1953 			// Make an input auto-complete from an array of tags for a recipe database
1954 			glow.ui.AutoSuggest()
1955 				.data(['Vegetarian', 'Soup', 'Sandwich', 'Wheat-free', 'Organic', 'etc etc'])
1956 				.linkToInput('#recipeTags');
1957 			
1958 		@example
1959 			// An AutoSuggest embedded in the page, rather than in an overlay
1960 			var myAutoSuggest = glow.ui.AutoSuggest()
1961 				.data('recipe.php?ingredients={val}')
1962 				.linkToInput('#username', {
1963 					// don't use an overlay, we'll add the autosuggest to the document outselves
1964 					useOverlay: false
1965 				});
1966 			
1967 			// add the results into the document
1968 			myAutoSuggest.container.appendTo('#results');
1969 			
1970 		@example
1971 			// Make an input suggest from an array of program names, where the
1972 			// whole string is searched rather than just the start
1973 			// When the item is clicked, we go to a url
1974 			new glow.ui.AutoSuggest().setFilter(function(item, val) {
1975 				return item.name.indexOf(val) !== -1;
1976 			}).data([
1977 				{name: 'Doctor Who', url: '...'},
1978 				{name: 'Eastenders', url: '...'},
1979 				{name: 'The Thick Of It', url: '...'},
1980 				// ...
1981 			]).linkToInput('#programSearch').on('select', function(event) {
1982 				location.href = event.selected.url;
1983 			});
1984 	*/
1985 	function AutoSuggest(opts) {
1986 		/*!debug*/
1987 			if (opts !== undefined && typeof opts !== 'object') {
1988 				glow.debug.warn('[wrong type] glow.ui.AutoSuggest expects object as "opts" argument, not ' + typeof opts + '.');
1989 			}
1990 		/*gubed!*/
1991 		opts = glow.util.apply({
1992 			minLength: 3,
1993 			keyboardNav: 'arrows-y',
1994 			activateFirst: true
1995 		}, opts);
1996 
1997 		Widget.call(this, 'AutoSuggest', opts);
1998 		this._init();
1999 	};
2000 	
2001 	glow.util.extend(AutoSuggest, Widget);
2002 	AutoSuggestProto = AutoSuggest.prototype;
2003 	
2004 	/**
2005 		@name glow.ui.AutoSuggest#_loading
2006 		@type boolean
2007 		@description True if the autosuggest is waiting for data.
2008 			This happens when getting data is async, has been requested but not returned.
2009 	*/
2010 	
2011 	/**
2012 		@name glow.ui.AutoSuggest#_pendingFind
2013 		@type string
2014 		@description Pending search string.
2015 			This is populated if find is called while the autoSuggest is _loading.
2016 	*/
2017 	
2018 	/**
2019 		@name glow.ui.AutoSuggest#_data
2020 		@type Object[]
2021 		@description Array of objects, the current datasource for this AutoSuggest.
2022 	*/
2023 	AutoSuggestProto._data = [];
2024 	
2025 	/**
2026 		@name glow.ui.AutoSuggest#_dataFunc
2027 		@type function
2028 		@description Function used for fetching data (potentially) async.
2029 	*/
2030 	
2031 	/**
2032 		@name glow.ui.AutoSuggest#_filter
2033 		@type function
2034 		@description The current filter function.
2035 	*/
2036 	AutoSuggestProto._filter = function(val, caseSensitive) {
2037 		var nameStart = this.name.slice(0, val.length);
2038 		nameStart = caseSensitive ? nameStart : nameStart.toLowerCase();
2039 		
2040 		return nameStart === val;
2041 	};
2042 	
2043 	/**
2044 		@name glow.ui.AutoSuggest#_format
2045 		@type function
2046 		@description The current format function.
2047 	*/
2048 	AutoSuggestProto._format = function(result, val) {
2049 		var text = tmpDiv.text(result.name).html(),
2050 			valStart = text.toLowerCase().indexOf( val.toLowerCase() ),
2051 			valEnd = valStart + val.length;
2052 		
2053 		// wrap the selected portion in <strong>
2054 		// This would be so much easier if it weren't for case sensitivity
2055 		if (valStart !== -1) {
2056 			text = text.slice(0, valStart) + '<em class="AutoSuggest-match">' + text.slice(valStart, valEnd) + '</em>' + text.slice(valEnd)
2057 		}
2058 		
2059 		return text;
2060 	};
2061 	
2062 	/**
2063 		@name glow.ui.AutoSuggest#focusable
2064 		@type glow.ui.Focusable
2065 		@description The focusable linked to this autosuggest.
2066 	*/
2067 	
2068 	// Widget lifecycle phases
2069 	AutoSuggestProto._init = function() {
2070 		WidgetProto._init.call(this);
2071 		// call _build
2072 		this._build();
2073 	}
2074 	
2075 	AutoSuggestProto._build = function() {
2076 		WidgetProto._build.call(this, '<ol></ol>');
2077 		
2078 		var opts = this._opts,
2079 			width = opts.width
2080 			content = this.content;
2081 		
2082 		this.focusable = content.focusable({
2083 			children: '> li',
2084 			keyboardNav: this._opts.keyboardNav,
2085 			setFocus: false,
2086 			activateOnHover: true
2087 		});
2088 		
2089 		width && this.container.width(width);
2090 		
2091 		// call _build
2092 		this._bind();
2093 	}
2094 	
2095 	/**
2096 		@private
2097 		@function
2098 		@description Select listener for the focusable.
2099 			'this' is the AutoSuggest
2100 	*/
2101 	function focusableSelectListener(e) {
2102 		return !this.fire('select', {
2103 			li: e.item,
2104 			item: e.item.data('as_data')
2105 		}).defaultPrevented();
2106 	}
2107 	
2108 	/**
2109 		@private
2110 		@function
2111 		@description Listens for focus moving in the focusable.
2112 			'this' is the autoSuggest
2113 	*/
2114 	function focusableChildActivate(e) {
2115 		var item = e.item,
2116 			focusable = this.focusable;
2117 	}
2118 	
2119 	function returnFalse() { return false; }
2120 	
2121 	AutoSuggestProto._bind = function() {
2122 		var focusable = this.focusable.on('select', focusableSelectListener, this)
2123 			.on('childActivate', focusableChildActivate, this);
2124 		
2125 		this._tie(focusable);
2126 		
2127 		// prevent focus moving on mouse down
2128 		this.container.on('mousedown', returnFalse);
2129 		
2130 		WidgetProto._bind.call(this);
2131 	}
2132 	
2133 	/**
2134 		@name glow.ui.AutoSuggest#setFilter
2135 		@function
2136 		@description Set the function used to filter the dataset for results.
2137 			Overwrite this to change the filtering behaviour.
2138 		
2139 		@param {function} filter Filter function.
2140 			Your function will be passed 2 arguments, the term entered by the user,
2141 			and if the search should be case sensitive. Return true to confirm a match.
2142 			
2143 			'this' will be the item in the dataset to check.
2144 			
2145 			If the search is case-insensitive, the term entered by the user is automatically
2146 			lowercased.
2147 		  
2148 			The default filter will return items where the search term matches the start of their 'name'
2149 			property. If the dataset is simply an array of strings, that string will be used instead of the 'name' property.
2150 		
2151 		@example
2152 			// Search the name property for strings that contain val
2153 			myAutoSuggest.setFilter(function(val, caseSensitive) {
2154 				var name = caseSensitive ? this.name : this.name.toLowerCase();
2155 				return name.indexOf(val) !== -1;
2156 			});
2157 			
2158 		@example
2159 			// Search the tags property for strings that contain val surrounded by pipe chars
2160 			// this.tags is like: |hello|world|foo|bar|
2161 			myAutoSuggest.setFilter(function(val, caseSensitive) {
2162 				var tags = caseSensitive ? this.tags : this.tags.toLowerCase();
2163 				return tags.indexOf('|' + val + '|') !== -1;
2164 			});
2165 			
2166 		@return this
2167 	*/
2168 	AutoSuggestProto.setFilter = function(filter) {
2169 		/*!debug*/
2170 			if (arguments.length !== 1) {
2171 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#setFilter expects 1 argument, not ' + arguments.length + '.');
2172 			}
2173 		/*gubed!*/
2174 		this._filter = filter;
2175 		return this;
2176 	};
2177 	
2178 	/**
2179 		@name glow.ui.AutoSuggest#setFormat
2180 		@function
2181 		@description Control how matches are output.
2182 			
2183 		@param {function} formatter Function to generate output.
2184 			The first param to your function will be the matched item from your data list.
2185 			The second param is the search value.
2186 			
2187 			Return an HTML string or glow.NodeList to display this item in the results
2188 			list. Ensure you escape any content you don't want treated as HTML.
2189 			
2190 		@returns this
2191 		
2192 		@example
2193 			// A username auto-complete
2194 			
2195 			// The data url returns a JSON object like [{name='JaffaTheCake', fullName:'Jake Archibald', photo:'JaffaTheCake.jpg'}, ...]
2196 			glow.ui.AutoSuggest().setFormat(function() {
2197 				// Format the results like <img src="JaffaTheCake.jpg" alt=""> Jake Archibald (JaffaTheCake)
2198 				return '<img src="' + data.photo + '" alt=""> ' + data.fullName + ' (' + data.name + ')';
2199 			}).data('userSearch.php?usernamePartial={val}').linkToInput('#username');
2200 	*/
2201 	AutoSuggestProto.setFormat = function(formatter) {
2202 		/*!debug*/
2203 			if (arguments.length !== 1) {
2204 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#setFormat expects 1 argument, not ' + arguments.length + '.');
2205 			}
2206 		/*gubed!*/
2207 		this._format = formatter;
2208 		return this;
2209 	};
2210 	
2211 	/**
2212 		@private
2213 		@function
2214 		@description Process the data into an acceptable format for #_data.
2215 		@param {glow.ui.AutoSuggest} autoSuggest
2216 		@param {Object[]|string[]|glow.net.XhrResponse} data
2217 			Array of strings will be converted into an array of objects like {name: val}
2218 			
2219 			glow.net.XhrResponse will be converted into Object[]|string[] via .json
2220 	*/
2221 	function populateData(autoSuggest, data) {
2222 		var i,
2223 			tmpData,
2224 			event = autoSuggest.fire('data', {data:data});
2225 
2226 		if ( !event.defaultPrevented() ) {
2227 			// a listener may have altered the data
2228 			data = event.data;
2229 
2230 			// if it's an XHR response, convert it to json
2231 			if (data instanceof glow.net.XhrResponse) {
2232 				data = data.json();
2233 			}
2234 			
2235 			if (typeof data[0] === 'string') {
2236 				tmpData = [];
2237 				i = data.length;
2238 				while (i--) {
2239 					tmpData[i] = { name: data[i] };
2240 				}
2241 				data = tmpData;
2242 			}
2243 			
2244 			/*!debug*/
2245 				if ( !data.push ) {
2246 					glow.debug.warn('[wrong type] glow.ui.Autosuggest data expected to be array, not ' + typeof data + '.');
2247 				}
2248 				else if (data.length && typeof data[0] !== 'object') {
2249 					glow.debug.warn('[wrong type] glow.ui.Autosuggest data expected to be array of objects, not array of ' + typeof data[0] + '.');
2250 				}
2251 			/*gubed!*/
2252 			
2253 			autoSuggest._data = data;
2254 		}
2255 	}
2256 	
2257 	
2258 	
2259 	/**
2260 		@private
2261 		@function
2262 		@description Create _dataFunc based on a custom function.
2263 		@param {glow.ui.AutoSuggest} autoSuggest Instance
2264 		@param {function} func Data fetching function provided by the user via #data
2265 	*/
2266 	function setDataFunction(autoSuggest, func) {
2267 		// create a new function for fetching data
2268 		autoSuggest._dataFunc = function(val) {
2269 			var input = autoSuggest.input,
2270 				bindOpts = autoSuggest._bindOpts,
2271 				loadingClass = (bindOpts && bindOpts.loadingClass) || '';
2272 			
2273 			// put us in the loading state and call the user's function
2274 			autoSuggest._loading = true;
2275 			input.addClass(loadingClass);
2276 			
2277 			// call the user's function, providing a callback
2278 			func.call(this, val, function(data) {
2279 				var pendingFind = autoSuggest._pendingFind;
2280 				autoSuggest._loading = false;
2281 				input.removeClass(loadingClass);
2282 				// populate data if we've been given some
2283 				data && populateData(autoSuggest, data);
2284 				if (pendingFind) {
2285 					performFind(autoSuggest, pendingFind);
2286 					autoSuggest._pendingFind = undefined;
2287 				}
2288 			});
2289 		}
2290 	}
2291 	
2292 	/**
2293 		@private
2294 		@function
2295 		@description Creates a data function to load a single url once.
2296 		@param url With no {val} placeholder.
2297 	*/
2298 	function singleLoadUrl(url) {
2299 		var dataFetched,
2300 			currentRequest;
2301 		
2302 		return function(val, callback) {
2303 			// if we've already fetched the data, just call back & return
2304 			if (dataFetched) {
2305 				return callback();
2306 			}
2307 			
2308 			// if we've already sent a request off, just let that one continue
2309 			if ( !currentRequest ) {				
2310 				currentRequest = glow.net.get(url).on('load', function(response) {
2311 					// set data for quick retrieval later
2312 					dataFetched = 1;
2313 					callback(response);
2314 				});
2315 			}
2316 		}
2317 	}
2318 	
2319 	/**
2320 		@private
2321 		@function
2322 		@description Creates a data function to load from a url each time a search is made.
2323 		@param url With {val} placeholder.
2324 	*/
2325 	function multiLoadUrl(url) {
2326 		var currentRequest;
2327 		
2328 		return function(val, callback) {
2329 			var processedUrl = glow.util.interpolate(url, {val:val});
2330 			
2331 			// abort any current request
2332 			currentRequest && currentRequest.abort();
2333 			currentRequest = glow.net.get(processedUrl).on('load', function(response) {
2334 				callback(response);
2335 			});
2336 		}
2337 	}
2338 	
2339 	/**
2340 		@name glow.ui.AutoSuggest#data
2341 		@function
2342 		@description Set the data or datasource to search.
2343 			This gives the AutoSuggest the data to search, or the means to fetch
2344 			the data to search.
2345 			
2346 		@param {string|string[]|Object[]|glow.net.Response|function} data Data or datasource.
2347 		
2348 			<p><strong>String URL</strong></p>
2349 			
2350 			A URL on the same domain can be provided, eg 'results.json?search={val}', where {val} is replaced
2351 			with the search term. If {val} is used, the URL if fetched on each search, otherwise it is only fetched
2352 			once on the first search.
2353 			
2354 			The result is a {@link glow.net.XhrResponse}, by default this is decoded as json. Use
2355 			the 'data' event to convert your incoming data from other types (such as XML).
2356 			
2357 			<p><strong>glow.net.XhrResponse</strong></p>
2358 			
2359 			This will be treated as a json response and decoded to string[] or Object[], see below.
2360 			
2361 			<p><strong>string[] or Object[] dataset</strong></p>
2362 			
2363 			An Array of strings can be provided. Each string will be converted to {name: theString}, leaving
2364 			you with an array of objects.
2365 			
2366 			An Array of objects can be provided, each object is an object that can be matched. By default
2367 			the 'name' property of these objects is searched to determine a match, but {@link glow.ui.AutoSuggest#filter filter} can
2368 			be used to change this.
2369 			
2370 			<p><strong>function</strong></p>
2371 			
2372 			Providing a function means you have total freedom over fetching the data
2373 			for your autoSuggest, sync or async.
2374 			
2375 			Your function will be called by the AutoSuggest whenever a {@link glow.ui.AutoSuggest#find find}
2376 			is performed, and will be passed 2 arguments: the search
2377 			string and a callback.
2378 			
2379 			You can fetch the data however you wish. Once you have the data, pass it
2380 			to the callback to complete the {@link glow.ui.AutoSuggest#find find}.
2381 			Until the callback is called, the AutoSuggest remains in a 'loading' state.
2382 			
2383 			`this` inside the function refers to the AutoSuggest instance.
2384 			
2385 			Your function will be called multiple times, ensure you cancel any existing
2386 			requests before starting a new one.
2387 			
2388 		@example
2389 			// providing a URL
2390 			myAutoSuggest.data('/search?text={val}');
2391 			
2392 		@example
2393 			// providing an array of program names
2394 			myAutoSuggest.data( ['Doctor Who', 'Eastenders', 'The Thick of it', 'etc etc'] );
2395 			
2396 		@example
2397 			// providing an object of user data
2398 			myAutoSuggest.data([
2399 				{name='JaffaTheCake', fullName:'Jake Archibald', photo:'JaffaTheCake.jpg'},
2400 				{name='Bobby', fullName:'Robert Cackpeas', photo:'Bobby.jpg'}
2401 				...
2402 			]);
2403 			
2404 		@example
2405 			// Getting the data via jsonp
2406 			var request;
2407 			
2408 			myAutoSuggest.data(function(val, callback) {
2409 				// abort previous request
2410 				request && request.abort();
2411 				
2412 				request = glow.net.getJsonp('http://blah.com/data?callback={callback}&val=' + val)
2413 					.on('load', function(data) {
2414 						callback(data);
2415 					})
2416 			});
2417 			
2418 		@returns this
2419 	*/
2420 	AutoSuggestProto.data = function(data) {
2421 		/*!debug*/
2422 			if (arguments.length !== 1) {
2423 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#data expects 1 argument, not ' + arguments.length + '.');
2424 			}
2425 		/*gubed!*/
2426 		if (typeof data === 'string') {
2427 			// look for urls without {val}, they get their data once & once only
2428 			if (data.indexOf('{val}') === -1) {
2429 				// replace data with function
2430 				data = singleLoadUrl(data);
2431 			}
2432 			// look for urls with {val}, they get their data on each search
2433 			else {
2434 				// replace data with function
2435 				data = multiLoadUrl(data);
2436 			}
2437 		}
2438 		
2439 		if (typeof data === 'function') {
2440 			setDataFunction(this, data);
2441 		}
2442 		else if (data.push) {
2443 			// clear any data functions set
2444 			this._dataFunc = undefined;
2445 			populateData(this, data);
2446 		}
2447 		
2448 		return this;
2449 	};
2450 	
2451 	/**
2452 		@private
2453 		@function
2454 		@description Generate the output of a find
2455 			
2456 		@param {glow.ui.AutoSuggest} autoSuggest
2457 		@param {Object[]} results Array of filtered results
2458 		@param {string} val The search string
2459 	*/
2460 	function generateOutput(autoSuggest, results, val) {
2461 		var content = autoSuggest.content,
2462 			resultsLen = results.length,
2463 			i = resultsLen,
2464 			listItem,
2465 			itemContent,
2466 			opts = autoSuggest._opts,
2467 			focusable = autoSuggest.focusable;
2468 		
2469 		focusable.active(false);
2470 		
2471 		// if we've got an overlay, we don't bother clearing the list,
2472 		// just hide the overlay to let it animate away nicely
2473 		if ( !resultsLen && autoSuggest.overlay ) {
2474 			autoSuggest._hideOverlay();
2475 			return;
2476 		}
2477 		
2478 		// remove any current results
2479 		content.children().destroy();
2480 		
2481 		while (i--) {
2482 			itemContent = autoSuggest._format( results[i], val );
2483 			listItem = glow('<li class="AutoSuggest-item"></li>')
2484 				.data( 'as_data', results[i] )
2485 				.prependTo(content);
2486 			
2487 			// append HTML or nodes
2488 			(typeof itemContent === 'string') ?
2489 				listItem.html(itemContent) :
2490 				listItem.append(itemContent);
2491 		}
2492 		
2493 		// Activate the focusable if we have results
2494 		if (resultsLen) {
2495 			opts.activateFirst && focusable.active(true);
2496 			// show & position our overlay
2497 			autoSuggest._showOverlay();
2498 		}
2499 		else {
2500 			autoSuggest._hideOverlay();
2501 		}
2502 	}
2503 	
2504 	/**
2505 		@private
2506 		@function
2507 		@description Performs the find operation without calling _dataFunc.
2508 			Or checking _loading or string length. These are done in #find.
2509 			
2510 		@param {glow.ui.AutoSuggest} autoSuggest
2511 		@param {string} str The search string
2512 	*/
2513 	function performFind(autoSuggest, str) {
2514 		var filteredResults = [],
2515 			filteredResultsLen = 0,
2516 			data = autoSuggest._data,
2517 			findEvent = autoSuggest.fire('find', {val: str}),
2518 			resultsEvent,
2519 			caseSensitive = autoSuggest._opts.caseSensitive;
2520 		
2521 		if ( !findEvent.defaultPrevented() ) {
2522 			// pick up any changes a listener has made to the find string
2523 			str = findEvent.val;
2524 			
2525 			str = caseSensitive ? str : str.toLowerCase();
2526 			
2527 			// start filtering the data
2528 			for (var i = 0, len = data.length; i < len; i++) {
2529 				if ( autoSuggest._filter.call(data[i], str, caseSensitive) ) {
2530 					filteredResults[ filteredResultsLen++ ] = data[i];
2531 					
2532 					// break if we have enough results now
2533 					if (filteredResultsLen === autoSuggest._opts.maxResults) {
2534 						break;
2535 					}
2536 				}
2537 			}
2538 			
2539 			// fire result event
2540 			resultsEvent = autoSuggest.fire('results', {results: filteredResults});
2541 			
2542 			if ( resultsEvent.defaultPrevented() ) {
2543 				filteredResults = [];
2544 			}
2545 			else {
2546 				// pick up any changes a listener has made to the results
2547 				filteredResults = resultsEvent.results
2548 			}
2549 			
2550 			// output results
2551 			generateOutput(autoSuggest, filteredResults, findEvent.val);
2552 		}
2553 	}
2554 	
2555 	/**
2556 		@name glow.ui.AutoSuggest#find
2557 		@function
2558 		@description Search the datasource for a given string
2559 			This fetches results from the datasource and displays them. This
2560 			may be an asyncrounous action if data needs to be fetched from
2561 			the server.
2562 		
2563 		@param {string} str String to search for
2564 			{@link glow.ui.AutoSuggest#filter AutoSuggest#filter} is used
2565 			to determine whether results match or not.
2566 			
2567 		@returns this
2568 	*/
2569 	AutoSuggestProto.find = function(str) {
2570 		/*!debug*/
2571 			if (arguments.length !== 1) {
2572 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#find expects 1 argument, not ' + arguments.length + '.');
2573 			}
2574 		/*gubed!*/
2575 		if (str.length >= this._opts.minLength) {
2576 			// refresh/load data if there's a function
2577 			this._dataFunc && this._dataFunc(str);
2578 			
2579 			// can't find if we're loading...
2580 			if (this._loading) {
2581 				// leave it here, _dataFunc will pick it up and call performFind later
2582 				this._pendingFind = str;
2583 			}
2584 			else {
2585 				performFind(this, str);
2586 			}
2587 		}
2588 		else {
2589 			this.hide();
2590 		}
2591 		return this;
2592 	};
2593 	
2594 	/**
2595 		@name glow.ui.AutoSuggest#hide
2596 		@function
2597 		@description Clear the results so the AutoSuggest is no longer visible
2598 			
2599 		@returns this
2600 	*/
2601 	AutoSuggestProto.hide = function() {
2602 		/*!debug*/
2603 			if (arguments.length !== 0) {
2604 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#hide expects 0 arguments, not ' + arguments.length + '.');
2605 			}
2606 		/*gubed!*/
2607 		clearTimeout(this._inputTimeout);
2608 		// generating empty output does the trick
2609 		generateOutput(this, [], '');
2610 		return this;
2611 	};
2612 	
2613 	/**
2614 		@name glow.ui.AutoSuggest#destroy
2615 		@function
2616 		@description Destroy the AutoSuggest.
2617 			Removes all events that cause the AutoSuggest to run. The input
2618 			element will remain on the page.
2619 	*/
2620 	AutoSuggestProto.destroy = function() {
2621 		/*!debug*/
2622 			if (arguments.length !== 0) {
2623 				glow.debug.warn('[wrong count] glow.ui.Autosuggest#destroy expects 0 arguments, not ' + arguments.length + '.');
2624 			}
2625 		/*gubed!*/
2626 		this._data = undefined;
2627 		
2628 		// remove events from the input
2629 		this.input.detach('keypress', this._inputPress)
2630 			.detach('blur', this._inputBlur)
2631 			.detach('onbeforedeactivate', this._inputDeact);
2632 		
2633 		WidgetProto.destroy.call(this);
2634 	};
2635 	
2636 	/**
2637 		@name glow.ui.AutoSuggest#disabled
2638 		@function
2639 		@description Enable/disable the AutoSuggest, or get the disabled state
2640 			When the AutoSuggest is disabled it is not shown.
2641 			
2642 		@param {boolean} [newState] Disable the AutoSuggest?
2643 			'false' will enable a disabled AutoSuggest.
2644 		
2645 		@returns {glow.ui.AutoSuggest|boolean}
2646 			Returns boolean when getting, AutoSuggest when setting
2647 	*/
2648 	
2649 	/**
2650 		@name glow.ui.AutoSuggest#event:data
2651 		@event
2652 		@description Fired when the dataset changes
2653 			This can be the result of calling {@link glow.ui.AutoSuggest#data data} or
2654 			new data has been fetched from the server.
2655 			
2656 			You can use this event to intercept and transform data into the
2657 			correct JSON format.
2658 			
2659 			Cancel this event to ignore the new dataset, and continue
2660 			with the current one.
2661 		@param {glow.events.Event} event Event Object
2662 		@param {*} event.data The new dataset
2663 			You can modify / overwrite this property to alter the dataset.
2664 			
2665 			The type of this object depends on the data source and other listeners
2666 			which may have overwritten / changed the original data.
2667 			
2668 		@example
2669 			myAutoSuggest.data('data.xml?search={val}').on('data', function(event) {
2670 				// When providing a url to .data(), event.data is a glow.net.XhrResponse object 
2671 				// Note: xmlToJson is not a function defined by Glow
2672 				event.data = xmlToJson( event.data.xml() );
2673 			});
2674 	*/
2675 	
2676 	/**
2677 		@name glow.ui.AutoSuggest#event:results
2678 		@event
2679 		@description Fired when the dataset has been filtered but before HTML is output
2680 			You can use this event to sort the dataset and/or add additional items
2681 			
2682 			Cancelling this event is equivalent to setting event.results to an
2683 			empty array.
2684 		@param {glow.events.Event} event Event Object
2685 		@param {string[]|Object[]} event.results The filtered dataset
2686 			You can modify / overwrite this property to alter the results
2687 			
2688 		@example
2689 			myAutoSuggest.on('results', function(event) {
2690 				// sort results by an 'author' property
2691 				event.results = event.results.sort(function(a, b) {
2692 					return a.author > b.author ? 1 : -1;
2693 				});
2694 				
2695 				// Add a 'More...' item to the data set
2696 				event.results.push( {name:'More...'} );
2697 				
2698 				// Behaviour will be added into the 'select' listener to handle what
2699 				// happens when 'More...' is selected
2700 			});
2701 	*/
2702 	
2703 	/**
2704 		@name glow.ui.AutoSuggest#event:select
2705 		@event
2706 		@description Fired when an item in the AutoSuggest is selected.
2707 			You can use this event to react to the user interacting with
2708 			the AutoSuggest
2709 			
2710 			Cancel this event to prevent the default click action.
2711 		@param {glow.events.Event} event Event Object
2712 		@param {string|Object} event.item The item in the dataset that was selected
2713 		@param {glow.NodeList} event.li The list item in the AutoSuggest that was selected
2714 			
2715 		@example
2716 			myAutoSuggest.on('select', function(event) {
2717 				// this assumes our data objects have a 'url' property
2718 				loaction.href = event.item.url;
2719 			});
2720 	*/
2721 	
2722 	/**
2723 		@name glow.ui.AutoSuggest#event:find
2724 		@event
2725 		@description Fired when a search starts.
2726 			Cancel this event to prevent the search.
2727 		
2728 		@param {glow.events.Event} event Event Object.
2729 		@param {string} event.val The search string.
2730 			You can set this to another value if you wish.
2731 	*/
2732 	
2733 	// EXPORT
2734 	glow.ui.AutoSuggest = AutoSuggest;
2735 });
2736 Glow.provide(function(glow) {
2737 	var undefined,
2738 		AutoSuggestProto = glow.ui.AutoSuggest.prototype;
2739 	
2740 	/**
2741 		@name glow.ui.AutoSuggest#bindOpts
2742 		@type Object
2743 		@description The options object passed into #bindInput, with defaults added.
2744 	*/
2745 	
2746 	/**
2747 		@name glow.ui.AutoSuggest#input
2748 		@type glow.NodeList
2749 		@description Refers to the input element to which this is linked to, or an empty NodeList.
2750 			Link an input to an AutoSuggest using {@link glow.ui.AutoSuggest#bindInput bindInput}.
2751 	*/
2752 	AutoSuggestProto.input = glow();
2753 	
2754 	/**
2755 		@name glow.ui.AutoSuggest#overlay
2756 		@type glow.ui.Overlay
2757 		@description The overlay linked to this autosuggest.
2758 			The Overlay is created when {@link glow.ui.AutoSuggest#bindInput bindInput} is
2759 			called.
2760 	*/
2761 	
2762 	/**
2763 		@name glow.ui.AutoSuggest#_inputPress
2764 		@private
2765 		@function
2766 		@description Listener for input's keypress event.
2767 			'this' is the AutoSuggest.
2768 			
2769 			Needed to make this pseudo-private so we could remove the listener later
2770 	*/
2771 	function inputPress(e) {
2772 		var autoSuggest = this,
2773 			focusable = autoSuggest.focusable,
2774 			focusableActive,
2775 			focusableIndex = focusable.activeIndex,
2776 			childrenLength;
2777 			
2778 		// we only care about printable chars and keys that modify input
2779 		if ( e.keyChar || e.key === 'delete' || e.key === 'backspace' ) {
2780 			// look out for printable chars going into the input
2781 			clearTimeout(autoSuggest._inputTimeout);
2782 			
2783 			autoSuggest._inputTimeout = setTimeout(function() {
2784 				autoSuggest.find( getFindValue(autoSuggest) );
2785 			}, autoSuggest._bindOpts.delay * 1000);
2786 		}
2787 		else {
2788 			focusableActive = focusable.active();
2789 			switch (e.key) {
2790 				case 'escape':
2791 					autoSuggest.hide();
2792 					deleteSelectedText(autoSuggest);
2793 					return false;
2794 				case 'up':
2795 					// Is up being pressed on the first item?
2796 					if (focusableActive && !focusableIndex) {
2797 						// deactivate the focusable
2798 						focusable.active(false);
2799 						deleteSelectedText(autoSuggest);
2800 						return false;
2801 					}
2802 					// I'm deliberately not breaking here, want to capture both up & down keys in the next case
2803 				case 'down':
2804 					if ( !focusableActive && (childrenLength = autoSuggest.content.children().length) ) {
2805 						// if the focusable isn't active, activate the first/last item
2806 						focusable.active(e.key == 'up' ? childrenLength - 1 : 0);
2807 						e.stopPropagation();
2808 						return false;
2809 					}
2810 			}
2811 		}
2812 	}
2813 	AutoSuggestProto._inputPress = inputPress;
2814 	
2815 	/**
2816 		@private
2817 		@function
2818 		@description Gets the value to find from the input.
2819 		@returns The value to find.
2820 			This is the same as the input value unless delimiters are used
2821 	*/
2822 	function getFindValue(autoSuggest) {
2823 		var input = autoSuggest.input,
2824 			delim = autoSuggest._bindOpts.delim,
2825 			val = input.val(),
2826 			lastDelimPos,
2827 			caretPos;
2828 		
2829 		// deal with delims
2830 		if (delim) {
2831 			caretPos = getCaretPosition(autoSuggest);
2832 			// get the text before the caret
2833 			val = val.slice(0, caretPos);
2834 			// is there a delimiter before the caret?
2835 			lastDelimPos = val.lastIndexOf(delim);
2836 			// if so, ignore the bits before the caret
2837 			if (lastDelimPos !== -1) {
2838 				val = val.slice( val.lastIndexOf(delim) + delim.length );
2839 			}
2840 		}
2841 		
2842 		return glow.util.trim(val);
2843 	}
2844 	
2845 	/**
2846 		@name glow.ui.AutoSuggest#_inputBlur
2847 		@private
2848 		@function
2849 		@description Listener for input's blur event.
2850 			'this' is the AutoSuggest.
2851 			
2852 			Needed to make this pseudo-private so we could remove the listener later
2853 	*/
2854 	function inputBlur() {
2855 		this.hide();
2856 	}
2857 	AutoSuggestProto._inputBlur = inputBlur;
2858 	
2859 	/**
2860 		@name glow.ui.AutoSuggest#_inputDeact
2861 		@private
2862 		@function
2863 		@description Listener for input's beforedeactivate event.
2864 			'this' is the AutoSuggest.
2865 			
2866 			Prevents IE from bluring the input element when the autosuggest is clicked.
2867 			
2868 			Needed to make this pseudo-private so we could remove the listener later
2869 	*/
2870 	function inputDeact(e) {
2871 		if ( this.container.contains( e.related ) ) {
2872 			return false;
2873 		}
2874 	}
2875 	AutoSuggestProto._inputDeact = inputDeact;
2876 	
2877 	/**
2878 		@private
2879 		@function
2880 		@description Listener for AutoSuggest's select event if opts.autoComplete is true
2881 			This creates the autoComplete behaviour.
2882 			'this' is the AutoSuggest.
2883 	*/
2884 	function completeSelectListener(event) {
2885 		completeInput(this.hide(), event.item.name);
2886 		makeSelection(this, this.input.val().length);
2887 	}
2888 	
2889 	/**
2890 		@private
2891 		@function
2892 		@description Listener for focusable's childActivate event if opts.autoComplete is true.
2893 			This updates the text as the user cycles through items.
2894 		
2895 			'this' is the AutoSuggest
2896 	*/
2897 	function focusablechildActivate(event) {
2898 		if (event.method !== 'hover') {
2899 			completeInput(this, event.item.data('as_data').name, true);
2900 		}
2901 	}
2902 	
2903 	/**
2904 		@private
2905 		@function
2906 		@description Autocomplete value in the input.
2907 		@param {glow.ui.AutoSuggest} autoSuggest
2908 		@param {string} newVal Value to complete to
2909 		@param {boolean} [select=false] Highlight the completed portion?
2910 			This is used while cycling through values
2911 	*/
2912 	function completeInput(autoSuggest, newVal, select) {
2913 		deleteSelectedText(autoSuggest);
2914 
2915 		var input = autoSuggest.input,
2916 			oldVal = input.val(),
2917 			caretPos = getCaretPosition(autoSuggest),
2918 			rangeStart = caretPos,
2919 			rangeEnd = newVal.length,
2920 			delim = autoSuggest._bindOpts.delim,
2921 			lastDelimPos,
2922 			firstValPart = '';
2923 		
2924 		// we don't want to overwrite the whole thing if we're using delimiters
2925 		if (delim) {
2926 			lastDelimPos = oldVal.slice(0, caretPos).lastIndexOf(delim);
2927 			if (lastDelimPos !== -1) {
2928 				firstValPart = oldVal.slice(0, lastDelimPos) + delim + ' ';
2929 			}
2930 			newVal = firstValPart + newVal + delim + ' ';
2931 			rangeEnd = newVal.length;
2932 			newVal += oldVal.slice(caretPos);
2933 		}
2934 		input.val(newVal);
2935 		select && makeSelection(autoSuggest, rangeStart, rangeEnd);
2936 	}
2937 	
2938 	
2939 	/**
2940 		@private
2941 		@function
2942 		@description Make a selection in the bound input
2943 		
2944 		@param {glow.ui.AutoSuggest} autoSuggest
2945 		@param {number} start Start point of the selection
2946 		@param {number} [end=start] End point of the selection
2947 	*/
2948 	function makeSelection(autoSuggest, start, end) {
2949 		end = (end === undefined) ? start : end;
2950 		
2951 		var inputElm = autoSuggest.input[0],
2952 			character = 'character',
2953 			range;
2954 
2955 		if (!window.opera && inputElm.createTextRange) { // IE
2956 			range = inputElm.createTextRange();
2957 			range.moveStart(character, start);
2958 			range.moveEnd(character, end - inputElm.value.length);
2959 			range.select();
2960 		}
2961 		else { // moz, saf, opera
2962 			inputElm.select();
2963 			inputElm.selectionStart = start;
2964 			inputElm.selectionEnd = end;
2965 		}
2966 	}
2967 	
2968 	/**
2969 		@private
2970 		@function
2971 		@description Get the caret position within the input
2972 	*/
2973 	function getCaretPosition(autoSuggest) {
2974 		var inputElm = autoSuggest.input[0],
2975 			r;
2976 		
2977 		if (glow.env.ie) { // IE
2978 			range = document.selection.createRange();
2979 			range.collapse();
2980 			range.setEndPoint( 'StartToStart', inputElm.createTextRange() );
2981 			r = range.text.length;
2982 		}
2983 		else { // moz, saf, opera
2984 			r = inputElm.selectionStart;
2985 		}
2986 		
2987 		return r;
2988 	}
2989 	
2990 	/**
2991 		@private
2992 		@function
2993 		@description Delete the currently selected text in the input.
2994 			This is used when esc is pressed and in focusablechildActivate
2995 	*/
2996 	function deleteSelectedText(autoSuggest) {
2997 		var inputElm = autoSuggest.input[0],
2998 			val = inputElm.value,
2999 			selectionStart;
3000 		
3001 		if (glow.env.ie) { // IE
3002 			document.selection.createRange().text = '';
3003 		}
3004 		else { // others
3005 			selectionStart = inputElm.selectionStart;
3006 			inputElm.value = val.slice(0, selectionStart) + val.slice(inputElm.selectionEnd);
3007 			inputElm.selectionStart = selectionStart;
3008 		}
3009 	}
3010 	
3011 	/**
3012 		@name glow.ui.AutoSuggest#_showOverlay
3013 		@private
3014 		@function
3015 		@description Shows the overlay, if one is attached.
3016 			Also positions the overlay according to options set.
3017 	*/
3018 	AutoSuggestProto._showOverlay = function() {
3019 		var overlay = this.overlay,
3020 			autoSuggestOpts = this._opts,
3021 			bindOpts = this._bindOpts,
3022 			input = this.input,
3023 			inputOffset;
3024 		
3025 		if (!overlay) { return; }
3026 		
3027 		if (!autoSuggestOpts.width) {
3028 			this.container.width( input[0].offsetWidth );
3029 		}
3030 		
3031 		if (bindOpts.autoPosition) {
3032 			inputOffset = input.offset();
3033 			overlay.container.css({
3034 				top: inputOffset.top + input[0].offsetHeight,
3035 				left: inputOffset.left
3036 			})
3037 		}
3038 		
3039 		overlay.show();
3040 	}
3041 	
3042 	/**
3043 		@name glow.ui.AutoSuggest#_hideOverlay
3044 		@private
3045 		@function
3046 		@description Hide the overlay, if one is attached.
3047 	*/
3048 	AutoSuggestProto._hideOverlay = function() {
3049 		var overlay = this.overlay;
3050 		overlay && overlay.hide();
3051 	}
3052 	
3053 	/**
3054 		@name glow.ui.AutoSuggest#bindInput
3055 		@function
3056 		@description Link this autosuggest to a text input.
3057 			This triggers {@link glow.ui.AutoSuggest#find} when the value in
3058 			the input changes.
3059 			
3060 			The AutoSuggest is placed in an Overlay beneath the input and displayed
3061 			when results are found.
3062 			
3063 			If the input loses focus, or esc is pressed,
3064 			the Overlay will be hidden and results cleared.
3065 			
3066 		@param {selector|glow.NodeList|HTMLElement} input Test input element
3067 		
3068 		@param {Object} [opts] Options
3069 		@param {selector|glow.NodeList} [opts.appendTo] Add the AutoSuggest somewhere in the document rather than an {@link glow.ui.Overlay Overlay}
3070 			By default, the AutoSuggest will be wrapped in an {@link glow.ui.Overlay Overlay} and
3071 			appended to the document's body.
3072 		@param {boolean} [opts.autoPosition=true] Place the overlay beneath the input
3073 			If false, you need to position the overlay's container manually. It's
3074 			recommended to do this as part of the Overlay's show event, so the
3075 			position is updated each time it appears.
3076 		@param {boolean} [opts.autoComplete=true] Update the input when an item is highlighted & selected.
3077 			This will complete the typed text with the result matched.
3078 			
3079 			You can create custom actions by listening for the
3080 			{@link glow.ui.AutoSuggest#event:select 'select' event}
3081 		@param {string} [opts.delim] Delimiting char(s) for selections.
3082 			When defined, the input text will be treated as multiple values,
3083 			separated by this string (with surrounding spaces ignored).
3084 		@param {number} [opts.delay=0.5] How many seconds to delay before searching.
3085 			This prevents searches being made on each key press, instead it
3086 			waits for the input to be idle for a given number of seconds.
3087 		@param {string} [opts.anim] Animate the Overlay when it shows/hides.
3088 			This can be any parameter accepted by {@link glow.ui.Overlay#setAnim Overlay#setAnim}.
3089 		@param {string} [opts.loadingClass] Class name added to the input while data is being loaded.
3090 			This can be used to change the display of the input element while data is being
3091 			fetched from the server. By default, a spinner is displayed in the input.
3092 		
3093 		
3094 		@returns this
3095 	*/
3096 	AutoSuggestProto.bindInput = function(input, opts) {
3097 		/*!debug*/
3098 			if (arguments.length < 1 || arguments.length > 2) {
3099 				glow.debug.warn('[wrong count] glow.ui.AutoSuggest#bindInput expects 1 or 2 arguments, not ' + arguments.length + '.');
3100 			}
3101 			if (opts !== undefined && typeof opts !== 'object') {
3102 				glow.debug.warn('[wrong type] glow.ui.AutoSuggest#bindInput expects object as "opts" argument, not ' + typeof opts + '.');
3103 			}
3104 		/*gubed!*/
3105 		var bindOpts = this._bindOpts = glow.util.apply({
3106 				autoPosition: true,
3107 				autoComplete: true,
3108 				delay: 0.5,
3109 				loadingClass: 'glow200b1-AutoSuggest-loading'
3110 			}, opts),
3111 			appendTo = bindOpts.appendTo,
3112 			container = this.container,
3113 			overlay,
3114 			autoSuggestOpts = this._opts;
3115 			
3116 		// if autocomplete isn't turned off, the browser doesn't let
3117 		// us hear about up & down arrow presses
3118 		this.input = glow(input).attr('autocomplete', 'off')
3119 			.on('keypress', inputPress, this)
3120 			.on('blur', inputBlur, this)
3121 			.on('beforedeactivate', inputDeact, this);
3122 		
3123 		if (bindOpts.autoComplete) {
3124 			this.on('select', completeSelectListener, this)
3125 				.focusable.on('childActivate', focusablechildActivate, this);
3126 		}
3127 		
3128 		// add to document, or...
3129 		if (appendTo) {
3130 			glow(appendTo).append(container);
3131 		}
3132 		// ...make overlay
3133 		else {
3134 			this.overlay = overlay = new glow.ui.Overlay(container)
3135 				.on('hide', overlayHide, this)
3136 				.on('afterShow', overlayAfterShow, this)
3137 				.hide();
3138 			
3139 			// the overlay will reactivate the focusable when needed
3140 			this.focusable.disabled(true);
3141 			
3142 			overlay.container.appendTo(document.body);
3143 			
3144 			// use alternate slide anim
3145 			if (bindOpts.anim === 'slide') {
3146 				bindOpts.anim = altSlideAnim;
3147 			}
3148 			
3149 			bindOpts.anim && overlay.setAnim(bindOpts.anim);
3150 			
3151 			this._tie(overlay);
3152 		}
3153 		
3154 		return this;
3155 	};
3156 	
3157 	/**
3158 		@private
3159 		@function
3160 		@description Alternative slide animation.
3161 			The AutoSuggest uses a different style of slide animation to the
3162 			usual Overlay, this creates it.
3163 	*/
3164 	function altSlideAnim(isShow, callback) {
3165 		var anim,
3166 			container = this.container,
3167 			animOpts = {
3168 				lockToBottom: true
3169 			};
3170 		
3171 		if (isShow) {
3172 			container.height(0);
3173 			anim = container.slideOpen(0.5, animOpts).data('glow_slideOpen')
3174 		}
3175 		else {
3176 			anim = container.slideShut(0.5, animOpts).data('glow_slideShut');
3177 		}
3178 		
3179 		anim.on('complete', callback);
3180 	}
3181 	
3182 	/**
3183 		@private
3184 		@function
3185 		@description Listener for overlay hide.
3186 			'this' is the autoSuggest.
3187 			
3188 			This stops the focusable being interactive during its hide & show animation.
3189 	*/
3190 	function overlayHide() {
3191 		this.focusable.disabled(true);
3192 	}
3193 	
3194 	/**
3195 		@private
3196 		@function
3197 		@description Listener for overlay show.
3198 			'this' is the autoSuggest
3199 	*/
3200 	function overlayAfterShow() {
3201 		var focusable = this.focusable;
3202 		
3203 		focusable.disabled(false);
3204 		
3205 		if (this._opts.activateFirst) {
3206 			focusable.active(true);
3207 		}
3208 	}
3209 });
3210 Glow.provide(function(glow) {
3211 	var undefined,
3212 		CarouselPaneProto,
3213 		WidgetProto = glow.ui.Widget.prototype;
3214 	
3215 	/**
3216 		@name glow.ui.CarouselPane
3217 		@class
3218 		@extends glow.ui.Widget
3219 		@description Create a pane of elements that scroll from one to another.
3220 			This is a component of Carousel.
3221 			
3222 		@param {glow.NodeList|selector|HTMLElement} container Container of the carousel items.
3223 			The direct children of this item will be treated as carousel items. They will
3224 			be positioned next to each other horizontally.
3225 			
3226 			Each item takes up the same horizontal space, equal to the width of the largest
3227 			item (including padding and border) + the largest of its horizontal margins (as set in CSS).
3228 			
3229 			The height of the container will be equal to the height of the largest item (including
3230 			padding and border) + the total of its vertical margins.
3231 			
3232 		@param {object} [opts] Options
3233 			@param {number} [opts.duration=0.2] Duration of scrolling animations in seconds.
3234 			@param {string|function} [opts.tween='easeBoth'] Tween to use for animations.
3235 				This can be a property name of {@link glow.tweens} or a tweening function.
3236 			
3237 			@param {boolean | number} [opts.step=1] Number of items to move at a time.
3238 				If true, the step will be the same size as the spotlight.
3239 			@param {boolean} [opts.loop=false] Loop the carousel from the last item to the first.
3240 			@param {boolean} [opts.page=false] Keep pages in sync by adding space to the end of the carousel.
3241 				Spaces don't exist as physical HTML elements, but simply a gap from the last item
3242 				to the end.
3243 			
3244 			@param {number} [opts.spotlight] The number of items to treat as main spotlighted items.
3245 				A carousel may be wide enough to display 2 whole items, but setting
3246 				this to 1 will result in the spotlight item sitting in the middle, with
3247 				half of the previous item appearing before, and half the next item
3248 				appearing after.
3249 				
3250 				By default, this is the largest number of whole items that can exist in
3251 				the width of the container. Any remaining width will be used to partially
3252 				show the previous/next item.
3253 				
3254 		@example
3255 			new glow.ui.CarouselPane('#carouselItems', {
3256 				duration: 0.4,
3257 				step: 2,
3258 				loop: true
3259 			});
3260 	*/
3261 	function CarouselPane(container, opts) {
3262 		/*!debug*/
3263 			if (!container) {
3264 				glow.debug.warn('[wrong count] glow.ui.CarouselPane - argument "container" is required.');
3265 				return;
3266 			}
3267 			
3268 			if (!container || glow(container).length === 0) {
3269 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - "'+container+'" is not a valid element specifier for the container.');
3270 			}
3271 			
3272 			if (opts && opts.spotlight && opts.step && opts.spotlight < opts.step && opts.step !== true) {
3273 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + opts.step +') cannot be greater than opts.spotlight ('+ opts.spotlight + ').');
3274 			}
3275 			
3276 			if (opts && opts.spotlight && opts.step && opts.page && opts.spotlight !== opts.step && opts.step !== true) {
3277 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + opts.step +') cannot be different than opts.spotlight ('+ opts.spotlight + ') if opts.page is true.');
3278 			}
3279 		/*gubed!*/
3280 		
3281 		var that = this;
3282 		
3283 		opts = glow.util.apply({
3284 			duration: 0.2,
3285 			tween: 'easeBoth',
3286 			step: 1,
3287 			loop: false,
3288 			page: false, // add a gap?
3289 			axis: 'x'    // either 'x' or 'y'
3290 		}, opts || {});
3291 
3292 		glow.ui.Widget.call(this, 'CarouselPane', opts);
3293 		
3294 		
3295 		if (glow(container).length > 0) { this._init(container, opts); }
3296 	};
3297 	
3298 	glow.util.extend(CarouselPane, glow.ui.Widget); // CarouselPane is a Widget
3299 	CarouselPaneProto = CarouselPane.prototype;     // shortcut
3300 	
3301 	
3302 	
3303 	/**
3304 		Tracks the order and location of all items, including cloned items.
3305 		@private
3306 		@constructor
3307 		@param {glow.NodeList} nodeList The real items to track.
3308 	 */
3309 	function ItemList(nodeList) {
3310 		var thisMeta;
3311 		
3312 		this.range = {min: 0, max: 0};
3313 		this.items = {};
3314 		this.meta = {};
3315 		
3316 		for (var i = 0, leni = nodeList.length; i < leni; i++) {
3317 			this.addItem(i, nodeList.item(i));
3318 		}
3319 	}
3320 	
3321 	ItemList.prototype.addItem = function(index, item, meta) {/*debug*///console.log('ItemList.prototype.addItem('+index+')');
3322 		this.range.min = Math.min(this.range.min, index);
3323 		this.range.max = Math.max(this.range.max, index);
3324 		
3325 		this.items[index] = item;
3326 		this.meta[index] = meta || {};
3327 	}
3328 	
3329 	ItemList.prototype.addMeta = function(index, meta) {/*debug*///console.log('ItemList.prototype.addMeta('+index+', '+meta.offset+')');
3330 		if (this.meta[index]) {
3331 			this.meta[index] = glow.util.apply(this.meta[index], meta);
3332 		}
3333 	}
3334 	
3335 	ItemList.prototype.place = function(top, left) {
3336 		// TODO styleName = this._geom[1]
3337 		for (var p in this.items) {
3338 			if (top !== undefined ) this.items[p].css('top', top);
3339 			this.items[p].css('left', (left === undefined)? this.meta[p].offset : left);
3340 		}
3341 	}
3342 	
3343 	ItemList.prototype.dump = function(c) {
3344 		if (typeof console !== 'undefined') {
3345 			for (var i = c._itemList.range.min, maxi = c._itemList.range.max; i <= maxi; i++) {
3346 				if (c._itemList.meta[i]) {
3347 					console.log('>> '+ i + ': ' + (c._itemList.meta[i].isClone? 'clone':'real') + ' at ' + c._itemList.meta[i].offset + ' ' + c._itemList.items[i][0].children[0].alt);
3348 				}
3349 				else {
3350 					console.log('>> '+ i + ': ' + c._itemList.meta[i]);
3351 				}
3352 			}
3353 		}
3354 	}
3355 	
3356 	ItemList.prototype.swap = function(index1, index2) { /*debug*///console.log('ItemList.prototype.swap('+index1+', '+index2+')');
3357 		this.items[index1].css('left', this.meta[index2].offset);
3358 		this.items[index2].css('left', this.meta[index1].offset);
3359 	}
3360 	
3361 	CarouselPaneProto._init = function(container) { /*debug*///console.log('CarouselPaneProto._init');
3362 		WidgetProto._init.call(this);
3363 		
3364 		// used value vs configured value (they may not be the same). Might be set to spotlight capacity, in _build.
3365 		this._step = this._opts.step;
3366 		
3367 		this._geom = (this._opts.axis === 'y')? ['height', 'top'] : ['width', 'left'];
3368 		
3369 		/**
3370 			@name glow.ui.CarouselPane#stage
3371 			@type glow.NodeList
3372 			@description The container passed in to the constructor for glow.ui.CarouselPane.
3373 		*/
3374 		this.stage = glow(container).item(0);
3375 
3376 		this._focusable = this.stage.focusable( {children: '> *', loop: true, setFocus: true} );
3377 		
3378 		
3379 		// what would have been the "content" of this widget, is named "viewport"
3380 		this._viewport = glow('<div class="CarouselPane-viewport"></div>');
3381 		glow(this.stage).wrap(this._viewport);
3382 		
3383 		/**
3384 			@name glow.ui.CarouselPane#items
3385 			@type glow.NodeList
3386 			@description Carousel items.
3387 				This is the same as `myCarouselPane.stage.children()`
3388 		*/
3389 		this.items = this.stage.children();
3390 		this._itemList = new ItemList(this.items);
3391 		
3392 		if (this._opts.spotlight > this.items.length) {
3393 			/*!debug*/
3394 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.spotlight (' + this._opts.spotlight +') cannot be greater than the number of items ('+ this.items.length + ').');
3395 			/*gubed!*/
3396 			this._opts.spotlight = this.items.length;
3397 		}
3398 		
3399 		// track what the offset of the current leftmost spotlighted item is
3400 		this._index = 0;
3401 		
3402 		this._build();
3403 	}
3404 	
3405 	CarouselPaneProto._build = function() { /*debug*///console.log('CarouselPaneProto._build');
3406 		WidgetProto._build.call(this, this._viewport);
3407 		
3408 		this.stage.css({
3409 			margin: 0,
3410 			listStyleType: 'none' // useful when content is a list
3411 		});
3412 
3413 		this.items.css( {position:'absolute', 'z-index':2} );
3414 		this._itemDimensions = getDimensions(this.items); // get this *after* setting position to absolute
3415 		this.items.css({
3416 			margin: 0,
3417 			width: this._itemDimensions.innerWidth,
3418 			height: this._itemDimensions.innerHeight
3419 		});
3420 
3421 		this._wingSize = Math.ceil(this.items.length * this._itemDimensions[this._geom[0]] * 1.5);
3422 
3423 		this._viewport.css({
3424 			overflow: 'scroll',
3425 			overflowX: 'hidden', // hide scroll bars
3426 			overflowY: 'hidden',
3427 			position: 'relative',
3428 			padding: 0,
3429 			margin: 0,
3430 			width: this._opts.axis === 'x'? '100%' : this._itemDimensions.width,
3431 			height: this._opts.axis === 'y'? '100%' : this._itemDimensions.height
3432 		});
3433 		
3434 		/**
3435 			@private
3436 			@name glow.ui.CarouselPane#_spot
3437 			@type Object
3438 			@description Information about the spotlight area.
3439 		*/
3440 		this._spot = CarouselPane._getSpot(this._viewport.width(), this.items, this._itemDimensions, this._opts);
3441 		
3442 		/**
3443 			@private
3444 			@name glow.ui.CarouselPane#_step
3445 			@type number
3446 			@description How far to move when going next or prev.
3447 		*/
3448 		if (this._opts.step === true) {
3449 			this._step = this._spot.capacity;
3450 		}
3451 		else if (this._opts.step > this._spot.capacity) {
3452 			/*!debug*/
3453 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + this._opts.step +') cannot be greater than the calculated spotlight ('+ this._spot.capacity + ').');
3454 			/*gubed!*/
3455 			
3456 			this._step = this._spot.capacity;
3457 		}
3458 
3459 		if (this._opts.page && this._step !== this._spot.capacity) {
3460 			/*!debug*/
3461 				glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + this._opts.step +') cannot be different than the spotlight ('+ this._spot.capacity + ') when opts.page is true.');
3462 			/*gubed!*/
3463 			
3464 			this._step = this._spot.capacity;
3465 		}
3466 		
3467 		/**
3468 			@private
3469 			@name glow.ui.CarouselPane#_gap
3470 			@type Object
3471 			@description Information about the gap at the end of the items.
3472 			@property size
3473 			@property count
3474 		*/
3475 		this._gap = getGap(this);
3476 		
3477 		// must set height to anything other than 0, else FF won't *ever* render the stage
3478 		this.stage.css({width: this.stage.width() + this._wingSize * 2, height: '100%'}); // [wing][stage[spot]stage][wing]
3479 		
3480 		layout.call(this);
3481 		
3482 		this._bind();
3483 		
3484 		calculateIndex.call(this);
3485 	}
3486 	
3487 	/**
3488 		@private
3489 		@name getGap
3490 		@description Calculate the size of the empty space at the end of the items
3491 			which may be required to enforce paging.
3492 		@param {glow.ui.CarouselPane} carouselPane
3493 	 */
3494 	function getGap(carouselPane) { /*debug*///console.log('getGap()');
3495 		var gap = { size: 0, count: 0 },
3496 			danglingItemCount = carouselPane.items.length % carouselPane._step;
3497 	
3498 		if (carouselPane._opts.page && carouselPane._step > 1) {
3499 			gap.count = danglingItemCount? carouselPane._spot.capacity - danglingItemCount : 0;
3500 			gap.size = gap.count * carouselPane._itemDimensions[carouselPane._geom[0]];
3501 		}
3502 	
3503 		return gap;
3504 	}
3505 	
3506 	CarouselPaneProto._bind = function() { /*debug*///console.log('CarouselPaneProto._bind');
3507 		var that = this;
3508 		
3509 		WidgetProto._bind.call(that);
3510 
3511 		attachEvent(that, that._focusable, 'childActivate', function(e) {
3512 			var itemNumber = e.itemIndex,
3513 				indexes = that.spotlightIndexes(true),
3514 				isVisible = (' '+indexes.join(' ')+' ').indexOf(' '+itemNumber+' ') > -1;
3515 
3516 			if (itemNumber !== undefined && !isVisible) {
3517 				that.moveTo(itemNumber, {tween: ''});
3518 				that._index = itemNumber;
3519 			}
3520 		});
3521 		
3522 		this._focusable.on('select', function(e) {
3523 			e.itemIndex = e.item.data('itemIndex');
3524 			that.fire('select', e);
3525 		});
3526 	}
3527 	
3528 	/**
3529 		@private
3530 		@name attachEvent
3531 		@function
3532 		@decription Add an event listener and handler to a node related to this carouselpane.
3533 		Stores a reference to that transaction so each handler can easily be detached later.
3534 		@see glow.ui.CarouselPane-detachEvents
3535 		@param {glow.ui.CarouselPane} carouselPane
3536 		@param {glow.EventTarget} target
3537 		@param {string} name The name of the event to listen for.
3538 		@param {Function} handler
3539 	*/
3540 	function attachEvent(carouselPane, target, name, handler) {
3541 		target.on(name, handler);
3542 		carouselPane._addedEvents = carouselPane._addedEvents || [];
3543 		carouselPane._addedEvents.push( {target:target, name:name, handler:handler} );
3544 	}
3545 	
3546 	/**
3547 		@private
3548 		@name detachEvents
3549 		@function
3550 		@decription Remove all events add via the {@link glow.ui.CarouselPane-attachEvent}.
3551 		@see glow.ui.CarouselPane-removeEvents
3552 		@param {glow.ui.CarouselPane} carouselPane
3553 	*/
3554 	function detachEvents(carouselPane) {
3555 		var i = carouselPane._addedEvents? carouselPane._addedEvents.length : 0,
3556 			e;
3557 		while (i--) {
3558 			e = carouselPane._addedEvents[i];
3559 			e.target.detach(e.name, e.handler);
3560 		}
3561 	}
3562 	
3563 	CarouselPaneProto.updateUi = function() { /*debug*///console.log('updateUi');
3564 		WidgetProto._updateUi.call(this);
3565 		
3566 		// must set height to anything other than 0, else FF won't *ever* render the stage
3567 		this.stage.css({width: this.stage.width() + this._wingSize * 2, height: '100%'}); // [wing][stage[spot]stage][wing]
3568 		
3569 		this._spot = CarouselPane._getSpot(this._viewport.width(), this.items, this._itemDimensions, this._opts);
3570 		
3571 		if (this._opts.step === true) {
3572 			this._step = this._spot.capacity;
3573 		}
3574 		
3575 		layout.call(this);
3576 		
3577 		this._index = 0;
3578 		this.fire('updateUi', {});
3579 	}
3580 	
3581 	/**
3582 		@name glow.ui.CarouselPane#moveStop
3583 		@function
3584 		@description Stop moving the carousel.
3585 			The current animation will end, leaving the carousel
3586 			in step. Note that this is asynchronous: expect this method
3587 			to return before the carousel actually stops.
3588 			
3589 		@returns this
3590 	*/
3591 	CarouselPaneProto.moveStop = function() { /*debug*///console.log('moveStop()');
3592 		// set temporary flag to signal the next animation in the timeline to stop
3593 		this._gliderBrake = true;
3594 	}
3595 	
3596 	/**
3597 		@name glow.ui.CarouselPane#moveStart
3598 		@function
3599 		@description Start moving the carousel in a particular direction.
3600 		
3601 		@param {boolean} [backwards=false] True to move backwards, otherwise move forwards.
3602 		
3603 		@returns this
3604 		@see glow.ui.CarouselPane#moveStop
3605 		
3606 		@example
3607 			nextBtn.on('mousedown', function() {
3608 				myCarouselPane.moveStart();
3609 			}).on('mouseup', function() {
3610 				myCarouselPane.moveStop();
3611 			});
3612 	*/
3613 	CarouselPaneProto.moveStart = function(backwards) { /*debug*///console.log('moveStart('+backwards+')');
3614 		/*!debug*/
3615 			if (arguments.length > 1) {
3616 				glow.debug.warn('[wrong count] glow.ui.moveStart - too many arguments, must be 1 or 0, not '+arguments.length+'.');
3617 			}
3618 		/*gubed!*/
3619 		
3620 		var step = (backwards? -1 : 1) * this._step,
3621 			carouselPane = this;
3622 		
3623 		if (!carouselPane._inMotion) {
3624 			carouselPane._gliderBrake = false;
3625 			
3626 			carouselPane.moveTo(
3627 				carouselPane._index + step,
3628 				{
3629 					callback: function() {
3630 						if (!carouselPane._gliderBrake) {
3631 							if ( // if looping or if there's room to go in the given direction 
3632 								carouselPane._opts.loop ||
3633 								( (backwards && carouselPane._index > 0) || (!backwards && carouselPane._index + carouselPane._spot.capacity < carouselPane.items.length) )
3634 							) {
3635 								if (carouselPane._step === 1) {
3636 									glide.call(carouselPane, backwards);
3637 								}
3638 								else {
3639 									carouselPane.moveStart(backwards); // recursive
3640 								}
3641 							}
3642 						}
3643 					}
3644 				}
3645 			);
3646 		}
3647 		
3648 		return carouselPane;
3649 	}
3650 	
3651 	/**
3652 		@name glow.ui.CarouselPane#moveToggle
3653 		@function
3654 		@description If this CarouselPane is currently moving via moveStart, will call moveStop,
3655 		otherwise will call moveStart.
3656 		@param {boolean} [backwards=false] When calling moveStart, move backwards?
3657 		@returns this
3658 	 */
3659 	CarouselPaneProto.moveToggle = function(backwards) { /*debug*///console.log('moveToggle()');
3660 		/*!debug*/
3661 			if (arguments.length > 1) {
3662 				glow.debug.warn('[wrong count] glow.ui.moveToggle - too many arguments, must be 1 or 0, not '+arguments.length+'.');
3663 			}
3664 		/*gubed!*/
3665 		
3666 		if (this._inMotion && !this._gliderBrake) {
3667 			this.moveStop();
3668 		}
3669 		else {
3670 			this.moveStart(backwards);
3671 		}
3672 		
3673 		return this;
3674 	}
3675 	
3676 	/**
3677 		@private
3678 		@name glide
3679 		@function
3680 		@description Move this using an animation that is continuous, with a linear tween.
3681 		@param {boolean} backwards Glide in a previous-direction?
3682 	 */
3683 	var glide = function(backwards) { /*debug*///console.log('glide('+backwards+')');
3684 		var dir = (backwards? -1 : 1),
3685 			moves = [],
3686 			offset = this.content[0].scrollLeft, // from where is the move starting?
3687 			amount = this._itemDimensions[this._geom[0]], // how many pixels are we moving by?
3688 			from,
3689 			to,
3690 			that = this,
3691 			moveAnim,
3692 			// when to loop back to where we started?
3693 			wrapAt = offset + (backwards? -this._index * amount : (this.items.length - this._index) * amount);
3694 		
3695 		swap.call(this, 'back');
3696 
3697 		for (var i = 0, leni = this.items.length; i < leni; i += this._step) {
3698 			// calculate the start and end points of the next move
3699 			from = offset + dir * i * amount;
3700 			to   = offset + dir * (i + this._step) * amount;
3701 
3702 			if ( (backwards && from === wrapAt) || (!backwards && to === wrapAt) ) {
3703 				offset -= dir * this.items.length * amount; // wrap
3704 			}
3705 
3706 			moveAnim = this.content.anim(
3707 				this._opts.duration,
3708 				{scrollLeft: [from, to]},
3709 				{tween: 'linear', startNow: false}
3710 			)
3711 			.on('start', function() {
3712 				indexMoveTo.call(that);
3713 					
3714 				if ( that.fire('move', { moveBy: dir, currentIndex: that._index }).defaultPrevented() ) {
3715 					glideStop.call(that);
3716 				}
3717 			})
3718 			.on('complete', function() {
3719 				that._index += dir; // assumes move amount will be +/- 1
3720 
3721  				if (
3722  					that._gliderBrake
3723  					||
3724  					( !that._opts.loop && (that._index + that._spot.capacity === that.items.length || that._index === 0) )
3725  				) {
3726  					glideStop.call(that);
3727  					that.fire( 'afterMove', {currentIndex: that._index} );
3728  				}
3729 			});
3730 			
3731 			moves.push(moveAnim);
3732 		}
3733 		
3734 		this._glider = new glow.anim.Timeline({loop: true});
3735 		glow.anim.Timeline.prototype.track.apply(this._glider, moves);
3736 		
3737 		this._inMotion = true;
3738 		this._gliderBrake = false;
3739 		this._glider.start();
3740 	}
3741 	
3742 	/**
3743 		@private
3744 		@name indexMoveTo
3745 		@function
3746 		@description Calculate what the new index would be and set this._index to that.
3747 		@param {number} index The destination index.
3748 		@returns this._index
3749 		@example
3750 			// items.length is 3
3751 			var newIndex = indexMoveTo(10);
3752 			// newIndex is 1
3753 	 */
3754 	function indexMoveTo(index) {
3755 		if (index !== undefined) { this._index = index; }
3756 		
3757 		// force index to be a number from 0 to items.length
3758 		this._index = this._index % this.items.length;
3759 		while (this._index < 0) { this._index += this.items.length; }
3760 		
3761 		return this._index;
3762 	}
3763 	
3764 	/**
3765 		@private
3766 		@name indexMoveBy
3767 		@function
3768 		@description Calculate what the new index would be and set this._index to that.
3769 		@param {number} delta The amount to change the index by, can be positive or negative.
3770 		@returns this._index
3771 		@example
3772 			// items.length is 3
3773 			// currentIndex is 1
3774 			var newIndex = indexMoveBy(100);
3775 			// newIndex is 2
3776 	 */
3777 	function indexMoveBy(delta) {
3778 		return indexMoveTo.call(this, this._index += delta);
3779 	}
3780 	
3781 	/**
3782 		@private
3783 		@name glideStop
3784 		@description Reset this CarouselPane after a glide is finished.
3785 	 */
3786 	function glideStop() { /*debug*///console.log('glideStop()');
3787 		this._glider.stop();
3788 		this._glider.destroy();
3789 		
3790 		this._inMotion = false;
3791 		this._index = calculateIndex.call(this); // where did we end up?
3792 		
3793 		// in case our clones are showing
3794 		jump.call(this);
3795 		swap.call(this);
3796 	}
3797 	
3798 	/**
3799 		@name glow.ui.CarouselPane#spotlightIndexes
3800 		@function
3801 		@description Gets an array of spotlighted indexes.
3802 			These are the indexes of the nodes within {@link glow.ui.CarouselPane#items}.
3803 			Only item indexes currently visible in the spotlight will be included.
3804 		@private-param {boolean} _real Return only indexes of real items, regardless of what clones are visible.
3805 		@returns {number[]}
3806 	*/
3807 	CarouselPaneProto.spotlightIndexes = function(_real) { /*debug*///console.log('CarouselPaneProto.spotlightIndexes()');
3808 		var indexes = [],
3809 			findex = calculateIndex.call(this),
3810 			index,
3811 			maxi = (this._opts.loop)? this._spot.capacity : Math.min(this._spot.capacity, this.items.length);
3812 		
3813 		// takes into account gaps and wraps
3814 		for (var i = 0; i < maxi; i++) {
3815 			index = _real? (findex + i) : (findex + i)%(this.items.length + this._gap.count);
3816 			// skip gaps
3817 			if (index >= this.items.length || index < 0) {
3818 				continue; // or maybe keep gaps? index = NaN;
3819 			}
3820 			indexes.push(index);
3821 		}		
3822 		return indexes;
3823 	}
3824 	
3825 	/**
3826 		@name glow.ui.CarouselPane#spotlightItems
3827 		@function
3828 		@description Get the currently spotlighted items.
3829 		Only items currently visible in the spotlight will be included.
3830 		@returns {glow.NodeList}
3831 	*/
3832 	CarouselPaneProto.spotlightItems = function() { /*debug*///console.log('CarouselPaneProto.spotlightItems()');
3833 		var items = glow(),
3834 			indexes = this.spotlightIndexes();
3835 		
3836 		// takes into account gaps and wraps
3837 		for (var i = 0, leni = indexes.length; i < leni; i++) {
3838 			items.push( this.items[ indexes[i] ] );
3839 		}
3840 		
3841 		return items;
3842 	}
3843 	
3844 	/**
3845 		@private
3846 		@name calculateIndex
3847 		@function
3848 		@description Calculate the index of the leftmost item in the spotlight.
3849 		@returns {number}
3850 	 */
3851 	function calculateIndex() {
3852 		var cindex = this.content[0].scrollLeft - (this._wingSize +this._spot.offset.left);
3853 		
3854 		cindex += this._spot.offset.left;
3855 		cindex /= this._itemDimensions.width;
3856 		
3857 		return cindex;
3858 	}
3859 	
3860 	/**
3861 		@name glow.ui.CarouselPane#moveTo
3862 		@function
3863 		@description Move the items so a given index is the leftmost active item.
3864 			This method respects the carousel's limits and its step. If it's
3865 			not possible to move the item so it's the leftmost item of the spotlight, it will
3866 			be placed as close to the left as possible.
3867 		
3868 		@param {number} itemIndex Item index to move to.
3869 		
3870 		@param opts
3871 		@param {undefined|string} opts.tween If undefined, use the default animation,
3872 		if empty string then no animation, if non-empty string then use the named tween.
3873 		@privateParam {Function} opts.callback Called when move animation is complete.
3874 		@privateParam {boolean} opts.jump Move without animation and without events.
3875 		
3876 		@returns this
3877 	*/
3878 	CarouselPaneProto.moveTo = function(itemIndex, opts) { /*debug*///glow.debug.log('moveTo('+itemIndex+')');
3879 		var willMove, // trying to move to the same place we already are?
3880 			destination, // in pixels
3881 			tween,
3882 			anim;
3883 		
3884 		if (this._inMotion) {
3885 			return false;
3886 		}
3887 		opts = opts || {};
3888 		
3889 		// will the last item be in the spotlight?
3890 		if (!this._opts.loop && itemIndex > this.items.length - this._spot.capacity) {
3891 			// if opts.page is on then allow a gap at the end, otherwise don't include gap
3892 			itemIndex = this.items.length - this._spot.capacity + (this._opts.page? this._gap.count : 0);
3893 		}
3894 		else if (!this._opts.loop && itemIndex < 0) {
3895 			itemIndex = 0;
3896 		}
3897 
3898 		willMove = ( itemIndex !== this._index && canGo.call(this, itemIndex) );
3899 		
3900 		// move event
3901 		if (!opts.jump) { // don't fire move event for jumps
3902 			var e = new glow.events.Event({
3903 				currentIndex: this._index,
3904 				moveBy: (this._index < itemIndex)? (itemIndex - this._index) : (-Math.abs(this._index - itemIndex))
3905 			});
3906 			
3907 			if (!opts.jump && willMove && this.fire('move', e).defaultPrevented() ) {
3908 				return this;
3909 			}
3910 			else {
3911 				itemIndex = this._index + e.moveBy;
3912 			}
3913 		}
3914 
3915 		// force items to stay in step when opts.page is on
3916 		if (this._opts.page) {
3917 			itemIndex = Math.floor(itemIndex / this._step) * this._step;
3918 		}
3919 		
3920 		// invalid itemIndex value?
3921 		if (itemIndex > this.items.length + this._step || itemIndex < 0 - this._step) { // moving more than 1 step
3922 			/*!debug*/
3923 				glow.debug.warn('[wrong value]  glow.ui.CarouselPane#moveTo - Trying to moveTo an item ('+itemIndex+') that is more than 1 step (' + this._step +' items) away is not possible.');
3924 			/*gubed!*/
3925 			itemIndex = this._index + (this._index < itemIndex)? -this._step : this._step;
3926 		}
3927 
3928 		destination = this._wingSize + itemIndex * this._itemDimensions.width;
3929 
3930 		swap.call(this, 'back');
3931 		
3932 		tween = opts.tween || this._opts.tween;
3933 		
3934 		var that = this;
3935 		if (opts.jump === true || opts.tween === '') { // jump
3936 			this.content[0].scrollLeft = destination;
3937 			
3938 			this._index = itemIndex;
3939 			// in case our clones are showing
3940 			jump.call(this);
3941 			swap.call(this);
3942 			
3943 			// force index to be a number from 0 to items.length
3944 			this._index = this._index % (this.items.length  + this._gap.count);
3945 			
3946 			if (!opts.jump && willMove) {
3947 				this.fire('afterMove', {currentIndex: this._index});
3948 			}
3949 			
3950 			this._inMotion = false;
3951 		}
3952 		else if (willMove) {
3953 			this._inMotion = true;
3954 			
3955 			anim = this.content.anim(
3956 				this._opts.duration,
3957 				{
3958 					scrollLeft: destination
3959 				},
3960 				{
3961 					tween: opts.tween || this._opts.tween
3962 				}
3963 			);
3964 			
3965 			this._index = itemIndex;
3966 			
3967 			
3968 			anim.on('complete', function() {
3969 				that._inMotion = false;
3970 				
3971 				// in case our clones are showing
3972 				jump.call(that);
3973 				swap.call(that);
3974 				
3975 				// force index to be a number from 0 to items.length
3976 				that._index = that._index % (that.items.length  + that._gap.count);
3977 				
3978 				that.fire('afterMove', {currentIndex: that._index});
3979 				
3980 				if (opts.callback) {
3981 					opts.callback();
3982 				}
3983 			});
3984 		}
3985 		
3986 		return this;
3987 	}
3988 	
3989 	/**
3990 		@private
3991 		@function
3992 		@name jump
3993 		@description Quickly move forward or back to a new set of items that look the same as
3994 		the current set of items.
3995 	 */
3996 	function jump() { /*debug*///console.log('jump()');
3997 		if (this._index < 0) {
3998 			this.moveTo(this.items.length + this._gap.count + this._index, {jump: true});
3999 		}
4000 		else if (this._index >= this.items.length) {
4001 			this.moveTo(this._index - (this.items.length + this._gap.count), {jump: true});
4002 		}
4003 	}
4004 	
4005 	/**
4006 		Move real items to stand-in for any clones that are in the spotlight, or
4007 		put the real items back again.
4008 		@name swap
4009 		@private
4010 		@param {boolean} back If a truthy value, will move the real items back.
4011 	 */
4012 	function swap(back) { /*debug*///console.log('swap('+back+')');
4013 		var swapItemIndex;
4014 		
4015 		if (!this._opts.loop) { return; } // no clones, so no swap possible
4016 		
4017 		if (back) {
4018 			this._itemList.place();
4019 		}
4020 		else {
4021 			for (var i = 0, leni = this._spot.capacity - this._gap.count; i < leni; i++) {
4022 				swapItemIndex = (this._index + i);
4023 				if (swapItemIndex >= this.items.length) { // a clone needs to have a real item swapped-in
4024 					this._itemList.swap(swapItemIndex, swapItemIndex % this.items.length);
4025 				}
4026 			}
4027 		}
4028 	}
4029 	
4030 	/**
4031 		@name glow.ui.CarouselPane#moveBy
4032 		@function
4033 		@description Move by a number of items.
4034 		
4035 		@param {number} amount Amount and direction to move.
4036 			Negative numbers will move backwards, positive number will move
4037 			forwards.
4038 			
4039 			This method respects the carousel's limits and its step. If it's
4040 			not possible to move the item so it's the leftmost item of the spotlight, it will
4041 			be placed as close to the left as possible.
4042 		
4043 		@returns this
4044 	*/
4045 	CarouselPaneProto.moveBy = function(amount) { /*debug*///console.log('moveBy('+amount+')');
4046 		this.moveTo(this._index + amount);
4047 		return this;
4048 	}
4049 	
4050 	/**
4051 		@name glow.ui.CarouselPane#next
4052 		@function
4053 		@description Move forward by the step.
4054 		@returns this
4055 	*/
4056 	CarouselPaneProto.next = function() { /*debug*///console.log('next()');
4057 		this.moveTo(this._index + this._step);
4058 		return this;
4059 	}
4060 	
4061 	/**
4062 		@name glow.ui.CarouselPane#prev
4063 		@function
4064 		@description Move backward by the step.
4065 		@returns this
4066 	*/
4067 	CarouselPaneProto.prev = function() { /*debug*///console.log('prev()');
4068 		this.moveTo(this._index - this._step);
4069 		return this;
4070 	}
4071 	
4072 	/**
4073 		@private
4074 		@name canGo
4075 		@description Determine if the CarouselPane can go to the desired index.
4076 		@param {number} itemIndex The desired index.
4077 		@returns {boolean}
4078 	 */
4079 	function canGo(itemIndex) { /*debug*///console.log('canGo('+itemIndex+')');
4080 		if (this._opts.loop) { return true; }
4081 		
4082 		// too far prev
4083 		if (itemIndex < 0) {
4084 			return false;
4085 		}
4086 
4087 		// too far next
4088 		if (itemIndex - this._step >= this.items.length - this._spot.capacity ) {
4089 			return false;
4090 		}
4091 		return true;
4092 	}
4093 	
4094 	/**
4095 		@private
4096 		@name getDimensions
4097 		@description Calculate the max height and width of all the items.
4098 		@param {glow.NodeList} items
4099 		@returns {Object} With properties `width` and 'height`.
4100 	 */
4101 	function getDimensions(items) {
4102 		var el,
4103 			maxInnerWidth = 0,
4104 			maxInnerHeight = 0,
4105 			maxWidth = 0,
4106 			maxHeight = 0,
4107 			margin = 0,
4108 			marginRight = 0,
4109 			marginLeft = 0,
4110 			marginTop = 0,
4111 			marginBottom = 0;
4112 			
4113 		items.each(function() {
4114 			el = glow(this);
4115 			maxHeight = Math.max(this.offsetHeight, maxHeight);
4116 			maxWidth = Math.max(this.offsetWidth, maxWidth);
4117 			maxInnerWidth = Math.max(el.width(), maxInnerWidth);
4118 			maxInnerHeight = Math.max(el.height(), maxInnerHeight);
4119 			marginRight = Math.max(autoToValue(el.css('margin-right')), marginRight);
4120 			marginLeft = Math.max(autoToValue(el.css('margin-left')), marginLeft);
4121 			marginTop = Math.max(autoToValue(el.css('margin-top')), marginTop);
4122 			marginBottom = Math.max(autoToValue(el.css('margin-bottom')), marginBottom);
4123 		});
4124 		
4125 		// simulate margin collapsing. see: http://www.howtocreate.co.uk/tutorials/css/margincollapsing
4126 		margin = Math.max(marginLeft, marginRight); // the larger of: the largest left matgin and the largest right margin
4127 		return { width: maxWidth+margin, height: maxHeight+marginTop+marginBottom, innerWidth: maxInnerWidth, innerHeight: maxInnerHeight, marginLeft: marginLeft, marginRight: marginRight, marginTop: marginTop, marginBottom: marginBottom };
4128 	}
4129 	
4130 	function autoToValue(v) {
4131 		if (v === 'auto') return 0;
4132 		else return parseInt(v);
4133 	}
4134 	
4135 	/**
4136 		@private
4137 		@name _getSpot
4138 		@description Calculate the bounds for the spotlighted area, within the viewport.
4139 		@private
4140 	 */
4141 	CarouselPane._getSpot = function(viewportWidth, items, itemDimensions, opts) {/*debug*///console.log('CarouselPane._getSpot()');
4142 		var spot = { capacity: 0, top: 0, left: 0, width: 0, height: 0, offset: { top: 0, right: 0, bottom: 0, left: 0 } },
4143 			opts = opts || {}
4144 		
4145 		if (!itemDimensions) { itemDimensions = getDimensions(items); }
4146 		
4147 		if (opts.axis = 'x') {
4148 			if (items.length === 0) {
4149 				spot.capacity = 0;
4150 			}
4151 			else if (opts.spotlight) {
4152 				if (opts.spotlight > items.length) {
4153 					throw new Error('spotlight cannot be larger than item count.');
4154 				}
4155 				spot.capacity = opts.spotlight;
4156 			}
4157 			else {
4158 				spot.capacity = Math.floor( viewportWidth / itemDimensions.width );
4159 			}
4160 
4161 			if (spot.capacity > items.length) {
4162 				spot.capacity = items.length;
4163 			}
4164 
4165 			spot.width = spot.capacity * itemDimensions.width + Math.min(itemDimensions.marginLeft, itemDimensions.marginRight);
4166 			spot.height = itemDimensions.height
4167 			
4168 			spot.offset.left = Math.floor( (viewportWidth - spot.width) / 2 );
4169 			spot.offset.right = viewportWidth - (spot.offset.left + spot.width);
4170 		}
4171 		else {
4172 			throw Error('y axis (vertical) not yet implemented');
4173 		}
4174 		
4175 		return spot;
4176 	}
4177 	
4178 	function getPosition(itemIndex) { /*debug*///console.log('getPosition('+itemIndex+')');
4179 		position = { top: 0, left: 0 };
4180 		
4181 		// TODO: memoise?
4182 		var size = this._itemDimensions.width,
4183 			offset = this._spot.offset.left + this._wingSize + this._itemDimensions.marginLeft,
4184 			gap = 0;
4185 			
4186 			if (this._opts.page && itemIndex < 0) {
4187 				gap = -(1 + Math.floor( Math.abs(itemIndex+this._gap.count) / this.items.length)) * this._gap.count * size;
4188 			}
4189 			else if (this._opts.page && itemIndex >= this.items.length) {
4190 				gap = Math.floor(itemIndex / this.items.length) * this._gap.count * size;
4191 			}
4192 
4193 			position.left = offset + (itemIndex * size) + gap;
4194 			position.top = this._itemDimensions.marginTop;
4195 
4196 			return position;
4197 	}
4198 	
4199 	function layout() {/*debug*///console.log('layout()');
4200 		var clone,
4201 			cloneOffset;
4202 					
4203 		this.content[0].scrollLeft = this._wingSize;
4204 
4205 		for (var i = 0, leni = this.items.length; i < leni; i++) {
4206 			// items were already added in ItemList constructor, just add meta now
4207 			this._itemList.addMeta(i, {offset:getPosition.call(this, i).left, isClone:false});
4208 
4209 			this.items.item(i).data('itemIndex', +i);
4210 		}
4211 		
4212 		if (this._opts.loop) { // send in the clones
4213 			this.stage.get('.carousel-clone').remove(); // kill any old clones
4214 			
4215 			// how many sets of clones (on each side) are needed to fill the off-spotlight portions of the stage?
4216 			var repsMax =  1 + Math.ceil(this._spot.offset.left / (this._itemDimensions.width*this.items.length + this._gap.size));	
4217 
4218 			for (var reps = 1; reps <= repsMax; reps++) {
4219 				i = this.items.length;
4220 				while (i--) {
4221  					// add clones to prev side
4222  					clone = this.items.item(i).copy();
4223  					clone.removeClass('carousel-item').addClass('carousel-clone').css({ 'z-index': 1, margin: 0 });
4224 					
4225  					cloneOffset = getPosition.call(this, 0 - (reps * this.items.length - i)).left;
4226  					this._itemList.addItem(0 - (reps * this.items.length - i), clone, {isClone:true, offset:cloneOffset});
4227  					this.stage[0].appendChild(clone[0]);
4228 					
4229  					// add clones to next side
4230  					clone = clone.copy();
4231  					cloneOffset = getPosition.call(this, reps*this.items.length + i).left;
4232  					this._itemList.addItem(reps*this.items.length + i + this._gap.count, clone, {isClone:true, offset:cloneOffset});
4233 					this.stage[0].appendChild(clone[0]);
4234  				}
4235  			}
4236 		}
4237 		
4238 		this.items.addClass('carousel-item');
4239 		// apply positioning to all items and clones
4240  		this._itemList.place(this._itemDimensions.marginTop, undefined);
4241 	}
4242 	
4243 	/**
4244 		@name glow.ui.CarouselPane#destroy
4245 		@function
4246 		@description Remove listeners and added HTML Elements from this instance.
4247 			CarouselPane items will not be destroyed.
4248 			
4249 		@returns undefined
4250 	*/
4251 	CarouselPaneProto.destroy = function() {
4252 		this.stage.get('.carousel-clone').remove();
4253 		detachEvents(this);
4254 		this.stage.insertBefore(this.container).children().css('position', '');
4255 		WidgetProto.destroy.call(this);
4256 	};
4257 	
4258 	/**
4259 		@name glow.ui.CarouselPane#event:select
4260 		@event
4261 		@description Fires when a carousel item is selected.
4262 			Items are selected by clicking, or pressing enter when a child is in the spotlight.
4263 		
4264 			Canceling this event prevents the default click/key action.
4265 		
4266 		@param {glow.events.Event} event Event Object
4267 		@param {glow.NodeList} event.item Item selected
4268 		@param {number} event.itemIndex The index of the selected item in {@link glow.ui.CarouselPane#items}.
4269 	*/
4270 	
4271 	/**
4272 		@name glow.ui.CarouselPane#event:move
4273 		@event
4274 		@description Fires when the carousel is about to move.
4275 			Canceling this event prevents the carousel from moving.
4276 			
4277 			This will fire for repeated move actions. Ie, this will fire many times
4278 			after #start is called.
4279 		
4280 		@param {glow.events.Event} e Event Object
4281 		@param {number} e.currentIndex Index of the current leftmost item.
4282 		@param {number} e.moveBy The number of items the Carousel will move by.
4283 			This is undefined for 'sliding' moves where the destination isn't known.
4284 			
4285 			This value can be overwritten, resulting in the carousel moving a different amount.
4286 			The carousel step will still be respected.
4287 			
4288 		@example
4289 			// double the amount a carousel will move by
4290 			myCarouselPane.on('move', function(e) {
4291 				e.moveBy *= 2;
4292 			});
4293 	*/
4294 
4295 	/**
4296 		@name glow.ui.CarouselPane#event:afterMove
4297 		@event
4298 		@description Fires when the carousel has finished moving.
4299 			Canceling this event prevents the carousel from moving.
4300 			
4301 			This will not fire for repeated move actions. Ie, after #start is
4302 			called this will not fire until the carousel reached an end point
4303 			or when it comes to rest after #stop is called.
4304 			
4305 		@param {glow.events.Event} e Event Object
4306 		@param {number} e.currentIndex Index of the current leftmost item.
4307 			
4308 		@example
4309 			// double the amount a carousel will move by
4310 			myCarouselPane.on('afterMove', function(e) {
4311 				// show content related to this.spotlightItems()[0]
4312 			});
4313 	*/
4314 	
4315 	// EXPORT
4316 	glow.ui.CarouselPane = CarouselPane;
4317 });
4318 Glow.provide(function(glow) {
4319 	var undefined,
4320 		CarouselProto,
4321 		Widget = glow.ui.Widget,
4322 		WidgetProto = Widget.prototype;
4323 	
4324 	/**
4325 		@name glow.ui.Carousel
4326 		@class
4327 		@extends glow.ui.Widget
4328 		@description Create a pane of elements that scroll from one to another.
4329 			
4330 		@param {glow.NodeList|selector|HTMLElement} itemContainer Container of the carousel items.
4331 			The direct children of this item will be treated as carousel items. They will
4332 			be positioned next to each other horizontally.
4333 			
4334 			Each item takes up the same horizontal space, equal to the width of the largest
4335 			item (including padding and border) + the largest of its horizontal margins (as set in CSS).
4336 			
4337 			The height of the container will be equal to the height of the largest item (including
4338 			padding and border) + the total of its vertical margins.
4339 			
4340 		@param {object} [opts] Options
4341 			@param {number} [opts.duration=0.2] Duration of scrolling animations in seconds.
4342 			
4343 			@param {string|function} [opts.tween='easeBoth'] Tween to use for animations.
4344 				This can be a property name of {@link glow.tweens} or a tweening function.
4345 			
4346 			@param {boolean|number} [opts.page=false] Move a whole page at a time.
4347 				If 'true', the page size will be the spotlight size, but you
4348 				can also set this to be an explicit number of items. Space will
4349 				be added to the end of the carousel so pages stay in sync.
4350 				
4351 				If 'false' or 1, the carousel moves one item at a time.
4352 				
4353 			@param {boolean} [opts.loop=false] Loop the carousel from the last item to the first.
4354 			
4355 			@param {number} [opts.spotlight] The number of items to treat as main spotlighted items.
4356 				A carousel may be wide enough to display 2 whole items, but setting
4357 				this to 1 will result in the spotlight item sitting in the middle, with
4358 				half of the previous item appearing before, and half the next item
4359 				appearing after.
4360 				
4361 				By default, this is the largest number of whole items that can exist in
4362 				the width of the container, allowing room for next & previous buttons.
4363 				Any remaining width will be used to partially show the previous/next item
4364 				beneath the next & previous buttons.
4365 				
4366 		@example
4367 			// This creates a carousel out of HTML like...
4368 			// <ol id="carouselItems">
4369 			//   <li>
4370 			//     <a href="anotherpage.html">
4371 			//       <img width="200" height="200" src="img.jpg" alt="" />
4372 			//	   </a>
4373 			//   </li>
4374 			//   ...more list items like above...
4375 			var myCarousel = new glow.ui.Carousel('#carouselItems', {
4376 				loop: true,
4377 				page: true,
4378 			});
4379 			
4380 		@example
4381 			// Make a carousel of thumbnails, which show the full image when clicked.
4382 			// Carousel items look like this...
4383 			// <li>
4384 			//   <a href="fullimage.jpg">
4385 			//     <img src="thumbnail.jpg" width="100" height="100" alt="whatever" />
4386 			//   </a>
4387 			// </li>
4388 			var fullImage = glow('#fullImage'),
4389 				myCarousel = new glow.ui.Carousel('#carouselItems', {
4390 					spotlight: 6
4391 				}).addPageNav('belowCenter').on('select', function(e) {
4392 					fullImage.prop( 'src', e.item.get('a').prop('href') );
4393 					return false;
4394 				});
4395 	*/
4396 	function Carousel(itemContainer, opts) {
4397 		var spot;
4398 		
4399 		Widget.call(this, 'Carousel', opts);
4400 		
4401 		opts = this._opts;
4402 		
4403 		// convert the options for CarouselPane
4404 		if (opts.page) {
4405 			opts.step = opts.page;
4406 			opts.page = true;
4407 		}
4408 		
4409 		this.itemContainer = itemContainer = glow(itemContainer).item(0);
4410 		
4411 		// see if we're going to get enough room for our prev/next buttons
4412 		spot = glow.ui.CarouselPane._getSpot(
4413 			itemContainer.parent().width(),
4414 			itemContainer.children().css('position', 'absolute'),
4415 			0,
4416 			opts
4417 		);
4418 		
4419 		// enfore our minimum back/fwd button size
4420 		if (spot.offset.left < 50) {
4421 			opts.spotlight = spot.capacity - 1;
4422 		}
4423 		
4424 		this._init();
4425 	};
4426 	glow.util.extend(Carousel, glow.ui.Widget);
4427 	CarouselProto = Carousel.prototype;
4428 	
4429 	/**
4430 		@name glow.ui.Carousel#_pane
4431 		@type glow.ui.CarouselPane
4432 		@description The carousel pane used by this Carousel
4433 	*/
4434 	
4435 	/**
4436 		@name glow.ui.Carousel#_prevBtn
4437 		@type glow.NodeList
4438 		@description Element acting as back button
4439 	*/
4440 	/**
4441 		@name glow.ui.Carousel#_nextBtn
4442 		@type glow.NodeList
4443 		@description Element acting as next button
4444 	*/
4445 	
4446 	/**
4447 		@name glow.ui.Carousel#items
4448 		@type glow.NodeList
4449 		@description Carousel items.
4450 	*/
4451 	
4452 	/**
4453 		@name glow.ui.Carousel#itemContainer
4454 		@type glow.NodeList
4455 		@description Parent element of the carousel items.
4456 	*/
4457 	
4458 	// life cycle methods
4459 	CarouselProto._init = function () {
4460 		WidgetProto._init.call(this);
4461 		this._build();
4462 	};
4463 	
4464 	CarouselProto._build = function () {
4465 		var content,
4466 			itemContainer = this.itemContainer,
4467 			pane,
4468 			items,
4469 			spot;
4470 		
4471 		WidgetProto._build.call( this, itemContainer.wrap('<div></div>').parent() );
4472 		content = this.content;
4473 		
4474 		pane = this._pane = new glow.ui.CarouselPane(itemContainer, this._opts);
4475 		spot = pane._spot
4476 		items = this.items = pane.items;
4477 		this.itemContainer = pane.itemContainer;
4478 		
4479 		pane.moveTo(0, {
4480 			tween: null
4481 		});
4482 		
4483 		// add next & prev buttons, autosizing them
4484 		this._prevBtn = glow('<div class="Carousel-prev"><div class="Carousel-btnIcon"></div></div>').prependTo(content).css({
4485 			width: spot.offset.left,
4486 			height: spot.height
4487 		});
4488 		this._nextBtn = glow('<div class="Carousel-next"><div class="Carousel-btnIcon"></div></div>').prependTo(content).css({
4489 			width: spot.offset.right,
4490 			height: spot.height
4491 		});
4492 		
4493 		updateButtons(this);
4494 		
4495 		this._bind();
4496 	};
4497 	
4498 	/**
4499 		@private
4500 		@function
4501 		@description Update the enabled / disabled state of the buttons.
4502 	*/
4503 	function updateButtons(carousel) {
4504 		// buttons are always active for a looping carousel
4505 		if (carousel._opts.loop) {
4506 			return;
4507 		}
4508 		
4509 		var indexes = carousel.spotlightIndexes(),
4510 			lastIndex = indexes[indexes.length - 1],
4511 			lastItemIndex = carousel.items.length - 1;
4512 
4513 		// add or remove the disabled class from the buttons
4514 		carousel._prevBtn[ (indexes[0] === 0) ? 'addClass' : 'removeClass' ]('Carousel-prev-disabled');
4515 		carousel._nextBtn[ (lastIndex === lastItemIndex) ? 'addClass' : 'removeClass' ]('Carousel-next-disabled');
4516 	}
4517 	
4518 	/**
4519 		@private
4520 		@function
4521 		@description Listener for CarouselPane's 'select' event.
4522 			'this' is the Carousel
4523 	*/
4524 	function paneSelect(event) {
4525 		this.fire('select', event);
4526 	}
4527 	
4528 	/**
4529 		@private
4530 		@function
4531 		@description Listener for CarouselPane's 'move' event.
4532 			'this' is the Carousel
4533 	*/
4534 	function paneMove(event) {
4535 		var pane = this._pane;
4536 		
4537 		if ( !this.fire('move', event).defaultPrevented() ) {
4538 			this._updateNav( (pane._index + event.moveBy) % this.items.length / pane._step );
4539 		}
4540 	}
4541 	
4542 	/**
4543 		@private
4544 		@function
4545 		@description Listener for CarouselPane's 'afterMove' event.
4546 			'this' is the Carousel
4547 	*/
4548 	function paneAfterMove(event) {
4549 		if ( !this.fire('afterMove', event).defaultPrevented() ) {
4550 			updateButtons(this);
4551 		}
4552 	}
4553 	
4554 	/**
4555 		@private
4556 		@function
4557 		@description Listener for back button's 'mousedown' event.
4558 			'this' is the Carousel
4559 	*/
4560 	function prevMouseDown(event) {
4561 		if (event.button === 0) {
4562 			this._pane.moveStart(true);
4563 			return false;
4564 		}
4565 	}
4566 	
4567 	/**
4568 		@private
4569 		@function
4570 		@description Listener for fwd button's 'mousedown' event.
4571 			'this' is the Carousel
4572 	*/
4573 	function nextMouseDown(event) {
4574 		if (event.button === 0) {
4575 			this._pane.moveStart();
4576 			return false;
4577 		}
4578 	}
4579 	
4580 	/**
4581 		@private
4582 		@function
4583 		@description Stop the pane moving.
4584 			This is used as a listener for various mouse events on the
4585 			back & forward buttons.
4586 			
4587 			`this` is the Carousel.
4588 	*/
4589 	function paneMoveStop() {
4590 		this._pane.moveStop();
4591 	}
4592 	
4593 	CarouselProto._bind = function () {
4594 		var pane = this._pane,
4595 			carousel = this;
4596 		
4597 		this._tie(pane);
4598 		
4599 		pane.on('select', paneSelect, this)
4600 			.on('afterMove', paneAfterMove, this)
4601 			.on('move', paneMove, this);
4602 		
4603 		this._prevBtn.on('mousedown', prevMouseDown, this)
4604 			.on('mouseup', paneMoveStop, this)
4605 			.on('mouseleave', paneMoveStop, this);
4606 		
4607 		this._nextBtn.on('mousedown', nextMouseDown, this)
4608 			.on('mouseup', paneMoveStop, this)
4609 			.on('mouseleave', paneMoveStop, this);
4610 		
4611 		WidgetProto._bind.call(this);
4612 	};
4613 	
4614 	/**
4615 		@name glow.ui.Carousel#spotlightItems
4616 		@function
4617 		@description Get the currently spotlighted items.
4618 		
4619 		@returns {glow.NodeList}
4620 	*/
4621 	CarouselProto.spotlightItems = function() {
4622 		return this._pane.spotlightItems();
4623 	};
4624 	
4625 	/**
4626 		@name glow.ui.Carousel#spotlightIndexes
4627 		@function
4628 		@description Gets an array of spotlighted indexes.
4629 			These are the indexes of the nodes within {@link glow.ui.Carousel#items}.
4630 		
4631 		@returns {number[]}
4632 	*/
4633 	CarouselProto.spotlightIndexes = function() {
4634 		return this._pane.spotlightIndexes();
4635 	};
4636 	
4637 	/**
4638 		@name glow.ui.Carousel#moveTo
4639 		@function
4640 		@description Move the items so a given index is in the spotlight.
4641 		
4642 		@param {number} itemIndex Item index to move to.
4643 		
4644 		@param {boolean} [animate=true] Transition to the item.
4645 			If false, the carousel will switch to the new index.
4646 		
4647 		@returns this
4648 	*/
4649 	CarouselProto.moveTo = function(itemIndex, animate) {
4650 		this._pane.moveTo(itemIndex, animate);
4651 		return this;
4652 	};
4653 	
4654 	/**
4655 		@private
4656 		@function
4657 		@decription Creates the prev & next functions
4658 		@param {number} direction Either 1 or -1
4659 	*/
4660 	function prevNext(direction) {
4661 		return function() {
4662 			this._pane.moveBy(this._pane._step * direction);
4663 			return this;
4664 		}
4665 	}
4666 	
4667 	/**
4668 		@name glow.ui.Carousel#next
4669 		@function
4670 		@description Move to the next page/item
4671 		
4672 		@returns this
4673 	*/
4674 	CarouselProto.next = prevNext(1);
4675 	
4676 	/**
4677 		@name glow.ui.Carousel#prev
4678 		@function
4679 		@description Move to the previous page/item
4680 		
4681 		@returns this
4682 	*/
4683 	CarouselProto.prev = prevNext(-1);
4684 	
4685 	/**
4686 		@name glow.ui.Carousel#destroy
4687 		@function
4688 		@description Remove listeners and styles from this instance.
4689 			Carousel items will not be destroyed.
4690 			
4691 		@returns undefined
4692 	*/
4693 	CarouselProto.destroy = function() {
4694 		// Move the pane outside our widget
4695 		this._pane.container.insertBefore(this.container);
4696 		WidgetProto.destroy.call(this);
4697 	};
4698 	
4699 	/*
4700 		@name glow.ui.Carousel#updateUi
4701 		@function
4702 		@description Refresh the carousel after moving/adding/removing items.
4703 			You can edit the items within the carousel using NodeLists such
4704 			as {@link glow.ui.Carousel#itemContainer}.
4705 			
4706 		@example
4707 			// Add a new carousel item
4708 			myCarousel.itemContainer.append('<li>New Item</li>');
4709 			// Move the new item into position & update page nav etc...
4710 			myCarousel.updateUi();
4711 			
4712 		@returns this
4713 	*/
4714 	// TODO: populate #items here & check back & fwd button sizes
4715 	
4716 	/**
4717 		@name glow.ui.Carousel#event:select
4718 		@event
4719 		@description Fires when a carousel item is selected.
4720 			Items are selected by clicking, or pressing enter when a child is in the spotlight.
4721 		
4722 			Canceling this event prevents the default click/key action.
4723 		
4724 		@param {glow.events.Event} event Event Object
4725 		@param {glow.NodeList} event.item Item selected
4726 		@param {number} event.itemIndex The index of the selected item in {@link glow.ui.Carousel#items}.
4727 	*/
4728 	
4729 	/**
4730 		@name glow.ui.Carousel#event:move
4731 		@event
4732 		@description Fires when the carousel is about to move.
4733 			Canceling this event prevents the carousel from moving.
4734 			
4735 			This will fire for repeated move actions. Ie, this will fire many times
4736 			while the mouse button is held on one of the arrows.
4737 		
4738 		@param {glow.events.Event} event Event Object
4739 		@param {number} event.moveBy The number of items we're moving by.
4740 			This will be positive for forward movements and negative for backward
4741 			movements.
4742 		
4743 			You can get the current index via `myCarousel.spotlightIndexes()[0]`.
4744 	*/
4745 	
4746 	/**
4747 		@name glow.ui.Carousel#event:afterMove
4748 		@event
4749 		@description Fires when the carousel has finished moving.
4750 			Canceling this event prevents the carousel from moving.
4751 			
4752 			This will not fire for repeated move actions. Ie, after #start is
4753 			called this will not fire until the carousel reached an end point
4754 			or when it comes to rest after #stop is called.
4755 			
4756 		@param {glow.events.Event} event Event Object
4757 			
4758 		@example
4759 			// double the amount a carousel will move by
4760 			myCarousel.on('afterMove', function(e) {
4761 				// show content related to this.spotlightIitems()[0]
4762 			});
4763 	*/
4764 	
4765 	// EXPORT
4766 	glow.ui.Carousel = Carousel;
4767 });
4768 Glow.provide(function(glow) {
4769 	var undefined,
4770 		CarouselProto = glow.ui.Carousel.prototype;
4771 
4772 	/**
4773 		@name glow.ui.Carousel#_pageNav
4774 		@type glow.NodeList
4775 		@description Element containing pageNav blocks
4776 	*/
4777 	/**
4778 		@name glow.ui.Carousel#_pageNavOpts
4779 		@type Object
4780 		@description Options for the page nav.
4781 			Same as the opts arg for #addPageNav
4782 	*/
4783 	
4784 	/**
4785 		@name glow.ui.Carousel#addPageNav
4786 		@function
4787 		@description Add page navigation to the carousel.
4788 			The page navigation show the user which position they are viewing
4789 			within the carousel.
4790 		
4791 		@param {Object} [opts] Options object.
4792 		@param {string|selector|HTMLElement} [opts.position='belowLast'] The position of the page navigation.
4793 			This can be a CSS selector pointing to an element, or one of the following
4794 			shortcuts:
4795 		
4796 			<dl>
4797 				<dt>belowLast</dt>
4798 				<dd>Display the nav beneath the last spotlight item</dd>
4799 				<dt>belowNext</dt>
4800 				<dd>Display the nav beneath the next button</dd>
4801 				<dt>belowMiddle</dt>
4802 				<dd>Display the nav beneath the carousel, centred</dd>
4803 				<dt>aboveLast</dt>
4804 				<dd>Display the nav above the last spotlight item</dd>
4805 				<dt>aboveNext</dt>
4806 				<dd>Display the nav above the next button</dd>
4807 				<dt>aboveMiddle</dt>
4808 				<dd>Display the nav above the carousel, centred</dd>
4809 			</dl>
4810 			
4811 		@param {boolean} [opts.useNumbers=false] Display as numbers rather than blocks.
4812 		
4813 		@returns this
4814 		
4815 		@example
4816 			new glow.ui.Carousel('#carouselContainer').addPageNav({
4817 				position: 'belowMiddle',
4818 				useNumbers: true
4819 			});
4820 	*/
4821 	CarouselProto.addPageNav = function(opts) {
4822 		opts = glow.util.apply({
4823 			position: 'belowLast'
4824 		}, opts);
4825 		
4826 		var className = 'Carousel-pageNav';
4827 		
4828 		if (opts.useNumbers) {
4829 			className += 'Numbers';
4830 		}
4831 		
4832 		this._pageNav = glow('<div class="' + className + '"></div>')
4833 			.delegate('click', 'div', pageNavClick, this);
4834 		
4835 		this._pageNavOpts = opts;
4836 		
4837 		initPageNav(this);
4838 		
4839 		return this;
4840 	};
4841 	
4842 	/**
4843 		@private
4844 		@function
4845 		@description Listener for one of the page buttons being clicked.
4846 			'this' is the carousel
4847 	*/
4848 	function pageNavClick(event) {
4849 		var targetPage = ( glow(event.attachedTo).text() - 1 ) * this._pane._step;
4850 		this.moveTo(targetPage);
4851 	}
4852 	
4853 	/**
4854 		@private
4855 		@function
4856 		@description Calculate the number of pages this carousel has
4857 	*/
4858 	function getNumberOfPages(carousel) {
4859 		var pane = carousel._pane,
4860 			itemsLength = carousel.items.length,
4861 			step = pane._step;
4862 		
4863 		if (carousel._opts.loop) {
4864 			r = Math.ceil( itemsLength / step );
4865 		}
4866 		else {
4867 			r = 1 + Math.ceil( (itemsLength - pane._spot.capacity) / step );
4868 		}
4869 		
4870 		// this can be less than one if there's less than 1 page worth or items
4871 		return Math.max(r, 0);
4872 	}
4873 	
4874 	/**
4875 		@private
4876 		@function
4877 		@description Position & populate the page nav.
4878 			Its position may need refreshed after updating the carousel ui.
4879 	*/
4880 	function initPageNav(carousel) {
4881 		var pageNav = carousel._pageNav,
4882 			position = carousel._pageNavOpts.position,
4883 			positionY = position.slice(0,5),
4884 			positionX = position.slice(5),
4885 			pane = carousel._pane,
4886 			numberOfPages = getNumberOfPages(carousel),
4887 			htmlStr = '';
4888 		
4889 		// either append or prepend the page nav, depending on option
4890 		carousel.container[ (positionY === 'below') ? 'append' : 'prepend' ](pageNav);
4891 		
4892 		// position in the center for Middle positions, otherwise right
4893 		pageNav.css('text-align', (positionX == 'Middle') ? 'center' : 'right');
4894 
4895 		// move it under the last item for *Last positions
4896 		if (positionX === 'Last') {
4897 			pageNav.css( 'margin-right', carousel._nextBtn.width() + pane._itemDimensions.marginRight )
4898 		}
4899 		
4900 		// build the html string
4901 		do {
4902 			htmlStr = '<div>' + numberOfPages + '</div>' + htmlStr;
4903 		} while (--numberOfPages);
4904 		
4905 		pageNav.html(htmlStr);
4906 		carousel._updateNav( pane._index / pane._step );
4907 	}
4908 	
4909 	/**
4910 		@name glow.ui.Carousel#_updateNav
4911 		@function
4912 		@description Activate a particular item on the pageNav
4913 		
4914 		@param {number} indexToActivate
4915 	*/
4916 	CarouselProto._updateNav = function(indexToActivate) {
4917 		if (this._pageNav) {
4918 			var activeClassName = 'active';
4919 			
4920 			this._pageNav.children()
4921 				.removeClass(activeClassName)
4922 				.item(indexToActivate).addClass(activeClassName);	
4923 		}
4924 	}
4925 });
4926 Glow.complete('ui', '2.0.0b1');
4927 
glow/2.0.0b1/core.debug.js100644 0 0 1204271 11405426600 12475 0ustar 0 0 /*! Copyright 2010 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 @VERSION@ @description The glow library namespace The library can also be used as a function, which is a shortcut to {@link glow.NodeList}. @example var links = glow('a'); // is the same as var links = new glow.NodeList('a'); */ if (!window.Glow) { // loading packages via user SCRIPT tags? window.Glow = { provide: function(f) { f(glow); }, complete: function(n, version) { glow.version = version; } }; window.glow = function(nodeListContents) { return new glow.NodeList(nodeListContents); }; glow.UID = 'glow' + Math.floor(Math.random() * (1<<30)); glow.load = function() { throw new Error('Method load() is not available without glow.js'); } } Glow.provide(function(glow) { /*!debug*/ var glowbug = { errors: [] , log: function(message, fileName, lineNumber) { this._add('Log', message, fileName, lineNumber); } , warn: function(message, fileName, lineNumber) { this._add('Warn', message, fileName, lineNumber); } , error: function(message, fileName, lineNumber) { this._add('Error', message, fileName, lineNumber); } , _add: function(level, message, fileName, lineNumber) { var e = new Error(message, fileName, lineNumber); e.message = message; e.name = 'Glow'+level; e.level = level.toLowerCase(); var match = /\[([^\]]+)\]/.exec(message); if (match) e.type = match[1]; // may be undefined else e.type = 'message'; this.errors.push(e); this.out(e); } , out: function(e) { var message = '['+e.level+'] '+e.message; if (window.console) { if (e.level === 'warn' && window.console.warn) { console.warn(message); } else if (e.level === 'error' && window.console.error) { console.error(message); } else if (window.console.log) { console.log(message); } } else if (window.opera && opera.postError) { opera.postError(message); } else { // use our own console glowbug.console.log(e.level, message); } } }; glowbug.console = { messages: [], log: function(level, message) { if (!this._w) { try { this._w = window.open('', 'report', 'width=350,height=250,menubar=0,toolbar=0,location=no,status=0,scrollbars=1,resizable=1'); this._w.document.write( 'Console<\/title><style>body{background-color: #ddd;} .message{background-color:#FFF;padding:4px;margin:0px;border-bottom:1px solid #ccc;} .warn {background-color: #E5E6B6;} .error{background-color: #D39C9E;}<\/style><\/head>' + '<body style="font: 11px monaco"><code id="messages"><\/code><\/body><\/html>' ) this._w.document.close(); } catch(ignored) { this._w = null; } } if (this._w) { var p = this._w.document.createElement('P'); p.className = 'message ' + level; p.innerHTML = message; this._w.document.getElementById('messages').appendChild(p); var dh = this._w.document.body.scrollHeight var ch = this._w.document.body.clientHeight if (dh > ch) { this._w.scrollTo(0, dh-ch); } } } } if (typeof glowbug != 'undefined') { glow.debug = glowbug; } /*gubed!*/ }); Glow.provide(function(glow) { /** @name glow.env @namespace @description Information about the browser and characteristics */ // parse the useragent string, setting NaN if match isn't found var ua = navigator.userAgent.toLowerCase(), 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], toNumber = parseFloat, env = {}; /** @name glow.env.gecko @type number @description Gecko version number to one decimal place (eg 1.9) or NaN on non-gecko browsers. The most popular browser using the Gecko engine is Firefox. @see <a href="http://en.wikipedia.org/wiki/Gecko_(layout_engine)#Usage">Versions of Gecko used by browsers</a> @example if (glow.env.gecko < 1.9) { // runs in Firefox 2 and other browsers that use Gecko earlier than 1.9 } */ env.gecko = toNumber(gecko); /** @name glow.env.ie @type number @description IE version number to one decimal place (eg 6.0) or NaN on non-IE browsers. This number will also be populated for browser based on IE's trident engine @example if (glow.env.ie < 9) { // runs in IE pre-9.0 glow('#content').css('background', 'deadmoomin.png'); } */ env.ie = toNumber(ie); /** @name glow.env.opera @type number @description Opera version number to one decimal place (eg 10.0) or NaN on non-Opera browsers. @example if (glow.env.opera < 10) { // runs in Opera pre-10.0 } */ env.opera = toNumber(opera); /** @name glow.env.webkit @type number @description Webkit version number to one decimal place (eg 531.9) or NaN on non-Webkit browsers. Safari and Google Chrome are the most popular browsers using Webkit. @see <a href="http://en.wikipedia.org/wiki/Safari_version_history#Release_history">Versions of Webkit used by Safari</a> @see <a href="http://en.wikipedia.org/wiki/Google_Chrome#Release_history">Versions of Webkit used by Google Chrome</a> @example if (glow.env.webkit < 526) { // runs in Safari pre-4.0, and Chrome pre-1.0 } */ env.webkit = toNumber(webkit); /** @name glow.env.khtml @type number @description KHTML version number to one decimal place or NaN on non-KHTML browsers. Konqueror is the most popular browsers using KHTML. */ env.khtml = toNumber(khtml); /** @name glow.env.standardsMode @type boolean @description True if the browser reports itself to be in 'standards mode' Otherwise, the browser is in 'quirks mode' @see <a href="http://en.wikipedia.org/wiki/Quirks_mode">Quirks Mode vs Standards Mode</a> */ env.standardsMode = document.compatMode != "BackCompat" && (!env.ie || env.ie >= 6); /** @name glow.env.version @type string @description Version number of the browser in use as a string. This caters for version numbers that aren't 'real' numbers, like "7b" or "1.9.1" */ env.version = ie || gecko || webkit || opera || khtml || ''; // export glow.env = env; }); // start-source: core/ready.js /*debug*///log.info('executing core/ready.js'); Glow.provide( function(glow) { var readyQueue = [], domReadyQueue = [], blockersActive = 0, processingReadyQueue = false; glow._readyBlockers = {}; /*debug*///log.info('overwriting Glow ready with glow.ready'); glow.ready = function (f) { /*debug*///log.info('glow.ready()'); if (this.isReady) { f(); } else { readyQueue.push(f); } return glow; }; glow.onDomReady = function(f) { //just run function if already ready if (glow.isDomReady) { f(); } else { domReadyQueue.push(f); } }; glow._addReadyBlock = function(name) { /*debug*///log.info('_addReadyBlock('+name+')'); if (typeof glow._readyBlockers[name] === 'undefined') { glow._readyBlockers[name] = 0; } glow._readyBlockers[name]++; glow.isReady = false; blockersActive++; /*debug*///log.info(' » blockersActive '+blockersActive+'.'); return glow; } glow._removeReadyBlock = function(name) { /*debug*///log.info('_removeReadyBlock('+name+')'); if (glow._readyBlockers[name]) { glow._readyBlockers[name]--; blockersActive--; /*debug*///log.info(' » blockersActive '+blockersActive+'.'); // if we're out of blockers if (!blockersActive) { // call our queue glow.isReady = true; runReadyQueue(); } } return glow; } // add blockers for any packages that started loading before core (this package) was built if (glow._build) { // only defined when using big Glow for (var i = 0, len = glow._build.loading.length; i < len; i++) { glow._addReadyBlock('glow_loading_'+glow._build.loading[i]); } for (var j = 0, lenj = glow._build.callbacks.length; j < lenj; j++) { if (glow._addReadyBlock) { glow._addReadyBlock('glow_loading_loadedcallback'); } } } function runDomReadyQueue() { /*debug*///log.info('runDomReadyQueue()'); glow.isDomReady = true; // run all functions in the array for (var i = 0, len = domReadyQueue.length; i < len; i++) { domReadyQueue[i](); } } function runReadyQueue() { /*debug*///log.info('runReadyQueue()'); // if we're already processing the queue, just exit, the other instance will take care of it if (processingReadyQueue) { return; } /*debug*///log.info('readyQueue: '+readyQueue.length); processingReadyQueue = true; while (readyQueue.length) { var callback = readyQueue.shift(); /*debug*///log.info('callback: '+callback); callback(glow); // check if the previous function has created a blocker if (blockersActive) { break; } } processingReadyQueue = false; } /** @private @function @name bindReady @description Add listener to document to detect when page is ready. */ var bindReady = function() { // use `var bindReady= function` form instead of `function bindReady()` to prevent FireBug 'cannot access optimized closure' error //don't do this stuff if the dom is already ready if (glow.isDomReady) { return; } glow._addReadyBlock('glow_domReady'); // wait for dom to be ready function onReady() { /*debug*///log.info('onReady()'); runReadyQueue(); glow._removeReadyBlock('glow_domReady'); } if (document.readyState == 'complete') { // already here! /*debug*///log.info('already complete'); onReady(); } else if (glow.env.ie && document.attachEvent) { /*debug*///log.info('bindready() - document.attachEvent'); // like IE // not an iframe... if (document.documentElement.doScroll && window == top) { (function() { /*debug*///log.info('doScroll'); try { document.documentElement.doScroll('left'); } catch(error) { setTimeout(arguments.callee, 0); return; } // and execute any waiting functions onReady(); })(); } else { // an iframe... document.attachEvent( 'onreadystatechange', function() { /*debug*///log.info('onreadystatechange'); if (document.readyState == 'complete') { document.detachEvent('onreadystatechange', arguments.callee); onReady(); } } ); } } else if (document.readyState) { /*debug*///log.info('bindready() - document.readyState'); // like pre Safari (function() { /*debug*///log.info('loaded|complete'); if ( /loaded|complete/.test(document.readyState) ) { onReady(); } else { setTimeout(arguments.callee, 0); } })(); } else if (document.addEventListener) {/*debug*///log.info('bindready() - document.addEventListener'); // like Mozilla, Opera and recent webkit document.addEventListener( 'DOMContentLoaded', function(){ /*debug*///log.info('glow DOMContentLoaded'); document.removeEventListener('DOMContentLoaded', arguments.callee, false); onReady(); }, false ); } else { throw new Error('Unable to bind glow ready listener to document.'); } }; glow.notSupported = ( // here are the browsers we don't support glow.env.ie < 6 || (glow.env.gecko < 1.9 && !/^1\.8\.1/.test(glow.env.version)) || glow.env.opera < 9 || glow.env.webkit < 412 ); // deprecated glow.isSupported = !glow.notSupported; // block 'ready' if browser isn't supported if (glow.notSupported) { glow._addReadyBlock('glow_browserSupport'); } bindReady(); } ); // end-source: core/ready.js /** @name glow.util @namespace @description Core JavaScript helpers */ Glow.provide(function(glow) { var util = {}, undefined, 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 */ TEXT = { AT : "@", EQ : "=", DOT : ".", EMPTY : "", AND : "&", OPEN : "(", CLOSE : ")" }, /* PrivateProperty: JSON nested hash of strings and regular expressions used in encoding/decoding Json */ 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 }; /** @private @name glow.util-_getType @param {Object} object The object to be tested. @returns {string} The data type of the object. */ function _getType(object) { var typeOfObject = typeof object, constructorStr, type; if (object === null) { return 'null'; } // warn: won't work across frames? else if (isFunction(object)) { return 'Function'; } else if (isArray(object)) { return 'Array'; } else if (typeOfObject === 'object') { constructorStr = object.constructor.toString(); if ( /^function (\S+?)\(/.test(constructorStr) ) { type = RegExp.$1; if (type === 'Object') { return 'object'; } else { return type; } } } return typeOfObject; } function isArray(o) { return {}.toString.call(o) === '[object Array]'; } function isFunction(o) { return {}.toString.call(o) === '[object Function]'; } /** @name glow.util.getType @function @description Get the native type or constructor name of an object. This allows you to safely get the type of an object, even if it came from another frame. @param {Object} object Object to get the type of. @example glow.util.getType( null ); // 'null' glow.util.getType( undefined ); // 'undefined' glow.util.getType('Hello'); // 'string' glow.util.getType( {} ); // 'Object' glow.util.getType(12); // 'number' glow.util.getType( [] ); // 'Array' glow.util.getType( function(){} ); // 'Function' glow.util.getType( glow('#whatever') ); // 'NodeList' @example var MyConstructor = function() {}, obj = new MyConstructor; glow.util.getType(obj); // '' // The above returns an empty string as the constructor // is an anonymous function and therefore has no name */ util.getType = _getType; /** @name glow.util.apply @function @description Copies properties from one object to another All properties from 'source' will be copied onto 'destination', potentially overwriting existing properties on 'destination'. Properties from 'source's prototype chain will not be copied. @param {Object} [destination] Destination object. If this object is undefined or falsey, a new object will be created. @param {Object} [source] Properties of this object will be copied onto the destination If this object is undefined or falsey, a new object will be created. @returns {Object} The destination object. @example var obj = glow.util.apply({foo: "hello", bar: "world"}, {bar: "everyone"}); //results in {foo: "hello", bar: "everyone"} */ util.apply = function(destination, source) { destination = destination || {}; source = source || {}; /*!debug*/ if (typeof destination != 'object') { glow.debug.warn('[wrong type] glow.util.apply expects argument "destination" to be of type object, not ' + typeof destination + '.'); } if (typeof source != 'object') { glow.debug.warn('[wrong type] glow.util.apply expects argument "source" to be of type object, not ' + typeof source + '.'); } /*gubed!*/ for (var i in source) { if ( source.hasOwnProperty(i) ) { destination[i] = source[i]; } } return destination; }; /** @name glow.util.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 MyOtherClass.base.apply(this, arguments); } glow.util.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" */ util.extend = function(sub, base, additionalProperties) { /*!debug*/ if (arguments.length < 2) { glow.debug.warn('[wrong count] glow.util.extend expects at least 2 arguments, not '+arguments.length+'.'); } if (typeof sub != 'function') { glow.debug.error('[wrong type] glow.util.extend expects argument "sub" to be of type function, not ' + typeof sub + '.'); } if (typeof base != 'function') { glow.debug.error('[wrong type] glow.util.extend expects argument "base" to be of type function, not ' + typeof base + '.'); } /*gubed!*/ var f = function () {}, p; f.prototype = base.prototype; p = new f(); sub.prototype = p; p.constructor = sub; sub.base = base; if (additionalProperties) { util.apply(sub.prototype, additionalProperties); } }; /** @name glow.util.escapeRegex @function @description Escape special regex chars from a string @param {string} str String to escape @returns {string} Escaped string @example var str = glow.util.escapeRegex('[Hello. Is this escaped?]'); // Outputs: // \[Hello\. Is this escaped\?\] */ util.escapeRegex = function(str) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.util.escapeRegex expects 1 argument, not '+arguments.length+'.'); } /*gubed!*/ return String(str).replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&'); }; /** @name glow.util.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.util.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]}); // will return "foo=Foo&bar=Bar%201&bar=Bar2" */ util.encodeUrl = function (object) { var type = _getType(object), paramsList = [], listLength = 0; /*!debug*/ if (typeof object !== 'object') { throw new Error('glow.util.encodeUrl: cannot encode item'); } /*gubed!*/ for (var key in object) { type = _getType( object[key] ); /*!debug*/ if (type !== 'Array' || type !== 'string') { glow.debug.warn('[wrong type] glow.util.encodeUrl expected Array or String value for "' + key + '", not ' + type + '.'); } /*gubed!*/ if (type === 'Array') { for(var i = 0, l = object[key].length; i < l; i++) { /*!debug*/ if (_getType(object[key])[i] !== 'string') { glow.debug.warn('[wrong type] glow.util.encodeUrl expected string value for "' + key + '" value at index ' + i + ', not ' + _getType(object[key])[i] + '.'); } /*gubed!*/ paramsList[listLength++] = key + '=' + encodeURIComponent(object[key][i]); } } else { // assume string paramsList[listLength++] = key + '=' + encodeURIComponent(object[key]); } } return paramsList.join('&'); }; /** @name glow.util.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.util.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2"); // will return the object {foo: "Foo", bar: ["Bar 1", "Bar2"]} */ util.decodeUrl = function(text) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.util.decodeUrl expects 1 argument, not '+arguments.length+'.'); } if (typeof text !== 'string') { glow.debug.warn('[wrong type] glow.util.decodeUrl expects argument "text" to be of type string, not ' + typeof text + '.'); } /*gubed!*/ var result = {}, keyValues = text.split(/[&;]/), thisPair, key, value; for(var i = 0, leni = keyValues.length; i < leni; i++) { thisPair = keyValues[i].split('='); if (thisPair.length < 2) { key = keyValues[i]; value = ''; } else { key = '' + decodeURIComponent(thisPair[0]); value = '' + decodeURIComponent(thisPair[1]); } // will be either: undefined, string or [object Array] switch (typeof result[key]) { case 'string': result[key] = [result[key], value]; break; case 'undefined': result[key] = value; break; default: result[key].push(value); } } return result; }; /** @name glow.util.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.util.encodeJson(myObj); // will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}' */ util.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.util.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.util.decodeJson('{foo: "Foo", bar: ["Bar 1", "Bar2"]}'); // will return {foo: "Foo", bar: ["Bar 1", "Bar2"]} var getRef = glow.util.decodeJson('foobar', {safeMode: true}); // will throw an error */ util.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.util.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.util.trim(" Hello World "); // "Hello World" */ util.trim = function(str) { //this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript return str.trim ? str.trim() : str.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1'); }; /** @name glow.util.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 <p>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.</p> <p>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.</p> @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. @param {Boolean} [opts.escapeHtml=false] Escape any special html characters found in the data object Use this to safely inject data from the user into an HTML template. The glow.dom module must be present for this feature to work (an error will be thrown otherwise). @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.util.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." @example var data = { name: 'Haxors!!1 <script src="hackhackhack.js"></script>' } var template = '<p>Hello, my name is {name}</p>'; var result = glow.util.interpolate(template, data, { escapeHtml: true }); // result == '<p>Hello, my name is Haxors!!1 <script src="hackhackhack.js"></script></p>' */ util.interpolate = function(template, data, opts) { var placeHolderRx, leftDelimiter, rightDelimiter, // div used for html escaping div; opts = opts || {}; // make sure the dom module is around if (opts.escapeHtml) { div = glow('<div></div>'); } if (opts.delimiter == undefined) { placeHolderRx = /\{[^{}]+\}/g; } else { leftDelimiter = opts.delimiter.substr(0, 1).replace(regexEscape, "\\$1"); rightDelimiter = opts.delimiter.substr(1, 1).replace(regexEscape, "\\$1") || leftDelimiter; placeHolderRx = new RegExp(leftDelimiter + "[^" + leftDelimiter + rightDelimiter + "]+" + rightDelimiter, "g"); } return template.replace(placeHolderRx, function (placeholder) { var key = placeholder.slice(1, -1), keyParts = key.split("."), val, i = 0, len = keyParts.length; if (key in data) { // need to be backwards compatible with "flattened" data. val = data[key]; } else { // look up the chain val = data; for (; i < len; i++) { if (keyParts[i] in val) { val = val[ keyParts[i] ]; } else { return placeholder; } } } if (opts.escapeHtml) { val = div.text(val).html(); } return val; }); }; /** @example glow.util.cookie(key); // get value for key glow.util.cookie({key: val, key2: val2}, opts); // set all keys, vals glow.util.cookie(key, val, opts); // set key, val glow.util.cookie(); // get all keys, vals // use value of undefined */ util.cookie = function(key, value, opts) { /*!debug*/ if (arguments.length > 3) { glow.debug.warn('[wrong count] glow.util.cookie expects 3 or less arguments, not '+arguments.length+'.'); } if (arguments.length === 1 && _getType(key) !== 'string' && _getType(key) !== 'object') { glow.debug.warn('[wrong type] glow.util.cookie expects argument "key" to be of type string or object, not ' + _getType(key) + '.'); } if ( arguments.length === 2 && ( ! (_getType(key) === 'string' && _getType(value) === 'string') || ! (_getType(key) === 'object' && _getType(value) === 'object') ) ) { glow.debug.warn('[wrong type] glow.util.cookie expects arguments to be (key, val) or (keyVals, opts).'); } if (arguments.length === 3 && _getType(key) !== 'string' && _getType(value) !== 'string' && _getType(opts) !== 'object') { glow.debug.warn('[wrong type] glow.util.cookie expects argument "key" and "value" to be strings and "options" to be an object.'); } if (opts && opts.debug && (typeof opts.expires !== 'number' || !opts.expires.toUTCString)) { glow.debug.warn('[wrong type] glow.util.cookie expects opts.expires to be a number or a Date.'); } /*gubed!*/ var date = '', expires = '', path = '', domain = '', secure = '', keyValues, thisPair, key, val, cookieValues; if (opts) { if (opts.expires) { if (typeof opts.expires === 'number') { date = new Date(); date.setTime(date.getTime() + (opts.expires * 24 * 60 * 60 * 1000)); // opts.expires days } else { // is already a Date date = opts.expires; } expires = '; expires=' + date.toUTCString(); } path = opts.path ? '; path=' + (opts.path) : ''; domain = opts.domain ? '; domain=' + (opts.domain) : ''; secure = opts.secure ? '; secure' : ''; } else { opts = {}; } if (typeof key === 'string' && typeof value === 'string') { // a single setter document.cookie = key + '=' + encodeURIComponent(value) + expires + path + domain + secure; } else if (typeof key === 'object') { // an all setter for (var p in key) { document.cookie = p + '=' + encodeURIComponent(key[p]) + expires + path + domain + secure; } } else { // a getter cookieValues = {}; if (document.cookie && document.cookie != '') { keyValues = document.cookie.split(/; ?/); for (var i = 0, leni = keyValues.length; i < leni; i++) { thisPair = keyValues[i].split('='); cookieValues[thisPair[0]] = decodeURIComponent(thisPair[1]); } } if (typeof key === 'string') { // a single getter return cookieValues[key]; } else if (typeof key === 'undefined') { // an all getter return cookieValues; } } }; util.removeCookie = function(key) { util.cookie(key, '', {expires: -1}); }; // export glow.util = util; }); Glow.provide(function(glow) { /** @name glow.events @namespace @description Handling custom events */ var events = {}; /* storage variables */ var eventListeners = {}, // eventName: [ [callback, thisVal], ... ] eventId = 1, objIdCounter = 1, eventKey = '__eventId' + glow.UID; /** @name glow.events.addListeners @function @param {Object[]} attachTo Array of objects to add listeners to. @param {string} name Name of the event to listen for. Event names are case sensitive. @param {function} callback Function to call when the event is fired. The callback will be passed a single event object. The type of this object depends on the event (see documentation for the event you're listening to). @param {Object} [thisVal] Value of 'this' within the callback. By default, this is the object being listened to. @see glow.events.Target#fire @description Convenience method to add listeners to many objects at once. If you want to add a listener to a single object, use its 'on' method. */ events.addListeners = function (attachTo, name, callback, thisVal) { var listenerIds = [], objIdent, listener, eventsOnObject, currentListeners; //attach the event for each element, return an array of listener ids var i = attachTo.length; while (i--) { objIdent = attachTo[i][eventKey]; if (!objIdent){ objIdent = attachTo[i][eventKey] = objIdCounter++; } listener = [ callback, thisVal ]; eventsOnObject = eventListeners[objIdent]; if(!eventsOnObject){ eventsOnObject = eventListeners[objIdent] = {}; } currentListeners = eventsOnObject[name]; if(!currentListeners){ eventsOnObject[name] = [listener]; } else{ currentListeners[currentListeners.length] = listener; } } return events; }; events._getPrivateEventKey = function(node) { if (!node[eventKey]) { node[eventKey] = objIdCounter++; } return node[eventKey]; } /** @name glow.events.fire @function @param {Object[]} items Array of objects to add listeners to @param {string} eventName Name of the event to fire @param {glow.events.Event|Object} [event] Event object to pass into listeners. You can provide a simple object of key-value pairs which will be added as properties on the glow.events.Event instance. @description Convenience method to fire events on multiple items at once. If you want to fire events on a single object, use its 'fire' method. */ events.fire = function (items, eventName, event) { if (! event) { event = new events.Event(); } else if ( event.constructor === Object ) { event = new events.Event( event ) } // for loop, because order matters! for(var i = 0, len = items.length; i < len; i++) { callListeners(items[i], eventName, event); } return event; }; /** @name glow.events-callListeners @private */ function callListeners(item, eventName, event, thisVal) { var objIdent = item[eventKey], listenersForEvent, returnedVal; // set the attachedTo value for this event event.attachedTo = event.attachedTo || item; if (!objIdent || !eventListeners[objIdent]) { return event; } listenersForEvent = eventListeners[objIdent][eventName]; if (!listenersForEvent) { return event; } // Slice to make sure we get a unique copy. listenersForEvent = listenersForEvent.slice(0); for (var i = 0, len = listenersForEvent.length; i < len; i++){ returnedVal = listenersForEvent[i][0].call((listenersForEvent[i][1] || thisVal || item), event); if (returnedVal === false){ event.preventDefault(); } } return event; } events._callListeners = callListeners; /** @name glow.events.removeAllListeners @function @param {Object[]} items Items to remove events from @description Removes all listeners attached to a given object. This removes not only listeners you added, but listeners others added too. For this reason it should only be used as part of a cleanup operation on objects that are about to be destroyed. */ events.removeAllListeners = function (items) { var objIdent, i = items.length; while(i--){ objIdent = items[i][eventKey]; if (!objIdent) { return false; } else { delete eventListeners[objIdent]; } } return true; }; /** @name glow.events.removeListeners @function @param {Object[]} items Items to remove events from. @param {string} eventName Name of the event to remove. @param {function} callback A reference to the original callback used when the listener was added. @decription Removes listeners for an event. */ events.removeListeners = function (item, eventName, callback) { /* TODO: items! */ var objIdent, listenersForEvent, i = item.length; while(i--){ objIdent = item[i][eventKey]; if(!objIdent || !eventListeners[objIdent]){ return events; } listenersForEvent = eventListeners[objIdent][eventName]; if(!listenersForEvent){ return events; } // for loop, because order matters for(var j = 0, lenj = listenersForEvent.length; j < lenj; j++){ if (listenersForEvent[j][0] === callback){ listenersForEvent.splice(j, 1); break; } } } return events; }; /** Copies the events from one NodeList to another @private @name glow.events._copyEvents @see glow.NodeList#clone @function */ events._copyDomEvents = function(from, to){ var objIdent, i = from.length, j, jLen, listeners, listenersForEvent, eventName, toItem; // loop over elements while(i--){ objIdent = from[i][eventKey]; listeners = eventListeners[objIdent]; if (objIdent){ toItem = to.slice(i, i+1); // loop over event names (of listeners attached) for ( eventName in listeners ) { listenersForEvent = listeners[eventName]; // loop over individual listeners and add them to the 'to' item // loop forward to preserve event order for (j = 0, jLen = listenersForEvent.length; j < jLen; j++) { // add listener toItem.on( eventName, listenersForEvent[j][0], listenersForEvent[j][1] ); } } } } } /** @name glow.events._getListeners @private @function @param {Object} item Item to find events for @decription Returns a list of listeners attached for the given item. */ events._getListeners = function(item){ var objIdent = item[eventKey]; if (!objIdent) { return {}; } else { // todo: need to return listeners in a sensible format return eventListeners[objIdent]; } }; ///** //@name glow.events.hasListener //@function //@param {Object[]} item Item to find events for //@param {String} eventName Name of the event to match //@decription Returns true if an event is found for the item supplied // //*/ // //glow.events.hasListener = function (item, eventName) { // var objIdent, // listenersForEvent; // // for (var i = 0, len = item.length; i < len; i++) { // objIdent = item[i][eventKey]; // // if (!objIdent || !eventListeners[objIdent]) { // return false; // } // // listenersForEvent = eventListeners[objIdent][eventName]; // if (!listenersForEvent) { // return false; // } // else { // return true; // } // } // // return false; //}; /** @name glow.events.Target @class @description An object that can have event listeners and fire events. Extend this class to make your own objects have 'on' and 'fire' methods. @example // Ball is our constructor function Ball() { // ... } // make Ball inherit from Target glow.util.extend(Ball, glow.events.Target, { // additional methods for Ball here, eg: bowl: function() { // ... } }); // now instances of Ball can receive event listeners var myBall = new Ball(); myBall.on('bounce', function() { alert('BOING!'); }); // and events can be fired from Ball instances myBall.fire('bounce'); */ events.Target = function () { }; var targetProto = events.Target.prototype; /** @name glow.events.Target.extend @function @param {Object} obj Object to add Target instance methods to. @description Convenience method to add Target instance methods onto an object. If you want to add Target methods to a class, extend glow.events.Target instead. @example var myApplication = {}; glow.events.Target.extend(myApplication); // now myApplication can fire events... myApplication.fire('load'); // and other objects can listen for those events myApplication.on('load', function(e) { alert('App loaded'); }); */ events.Target.extend = function (obj) { glow.util.apply( obj, glow.events.Target.prototype ); }; /** @name glow.events.Target#on @function @param {string} eventName Name of the event to listen for. @param {function} callback Function to call when the event fires. The callback is passed a single event object. The type of this object depends on the event (see documentation for the event you're listening to). @param {Object} [thisVal] Value of 'this' within the callback. By default, this is the object being listened to. @description Listen for an event @returns this @example myObj.on('show', function() { // do stuff }); */ targetProto.on = function(eventName, callback, thisVal) { glow.events.addListeners([this], eventName, callback, thisVal); return this; } /** @name glow.events.Target#detach @function @param {string} eventName Name of the event to remove. @param {function} callback Callback to detach. @param {Object} [thisVal] Value of 'this' within the callback. By default, this is the object being listened to. @description Remove an event listener. @returns this Target object @example function showListener() { // ... } // add listener myObj.on('show', showListener); // remove listener myObj.detach('show', showListener); @example // note the following WILL NOT WORK // add listener myObj.on('show', function() { alert('hi'); }); // remove listener myObj.detach('show', function() { alert('hi'); }); // this is because both callbacks are different function instances */ targetProto.detach = function(eventName, callback) { glow.events.removeListeners(this, eventName, callback); return this; } /** @name glow.events.Target#fire @function @param {string} eventName Name of the event to fire. @param {glow.events.Event|Object} [event] Event object to pass into listeners. You can provide a simple object of key-value pairs which will be added as properties of a glow.events.Event instance. @description Fire an event. @returns glow.events.Event @example myObj.fire('show'); @example // adding properties to the event object myBall.fire('bounce', { velocity: 30 }); @example // BallBounceEvent extends glow.events.Event but has extra methods myBall.fire( 'bounce', new BallBounceEvent(myBall) ); */ targetProto.fire = function(eventName, event) { if (! event) { event = new events.Event(); } else if ( event.constructor === Object ) { event = new events.Event( event ) } return callListeners(this, eventName, event); } /** @name glow.events.Event @class @param {Object} [properties] Properties to add to the Event instance. Each key-value pair in the object will be added to the Event as properties. @description Describes an event that occurred. You don't need to create instances of this class if you're simply listening to events. One will be provided as the first argument in your callback. @example // creating a simple event object var event = new glow.events.Event({ velocity: 50, direction: 180 }); // 'velocity' and 'direction' are simple made-up properties // you may want to add to your event object @example // inheriting from glow.events.Event to make a more // specialised event object function RocketEvent() { // ... } // inherit from glow.events.Event glow.util.extend(RocketEvent, glow.events.Event, { getVector: function() { return // ... } }); // firing the event rocketInstance.fire( 'landingGearDown', new RocketEvent() ); // how a user would listen to the event rocketInstance.on('landingGearDown', function(rocketEvent) { var vector = rocketEvent.getVector(); }); */ events.Event = function(obj) { if (obj) { glow.util.apply(this, obj); } }; var eventProto = events.Event.prototype; /** @name glow.events.Event#attachedTo @type {Object} @description The object the listener was attached or delegated to. */ /** @name glow.events.Event#preventDefault @function @description Prevent the default action of the event. Eg, if the click event on a link is cancelled, the link is not followed. Returning false from an event listener has the same effect as calling this function. For custom events, it's down to whatever fired the event to decide what to do in this case. See {@link glow.events.Event#defaultPrevented defaultPrevented} @example myLinks.on('click', function(event) { event.preventDefault(); }); // same as... myLinks.on('click', function(event) { return false; }); */ eventProto.preventDefault = function () { this._defaultPrevented = true; }; /** @name glow.events.Event#defaultPrevented @function @description Has the default been prevented for this event? This should be used by whatever fires the event to determine if it should carry out of the default action. @returns {Boolean} Returns true if {@link glow.events.Event#preventDefault preventDefault} has been called for this event. @example // fire the 'show' event // read if the default action has been prevented if ( overlayInstance.fire('show').defaultPrevented() == false ) { // go ahead and show } */ eventProto.defaultPrevented = function () { return this._defaultPrevented; }; /* Export */ glow.events = events; }); Glow.provide(function(glow) { var document = window.document, undef = undefined, domEventHandlers = [], // like: domEventHandlers[uniqueId][eventName].count, domEventHandlers[uniqueId][eventName].callback // shortcuts to aim compression events = glow.events, _callListeners = events._callListeners, _getPrivateEventKey = events._getPrivateEventKey, // used for feature detection supportsActivateDeactivate = (document.createElement('div').onactivate !== undefined); /** @name glow.events.DomEvent @constructor @extends glow.events.Event @param {Event|string} nativeEvent A native browser event read properties from, or the name of a native event. @param {Object} [properties] Properties to add to the Event instance. Each key-value pair in the object will be added to the Event as properties @description Describes a DOM event that occurred You don't need to create instances of this class if you're simply listening to events. One will be provided as the first argument in your callback. */ function DomEvent(e, properties) { /** @name glow.events.DomEvent#nativeEvent @type {Event | MouseEvent | UIEvent} @description The native event object provided by the browser. */ this.nativeEvent = e; /** @name glow.events.DomEvent#type @type {string} @description The native type of the event, like 'click' or 'keydown'. */ this.type = e.type; /** @name glow.events.DomEvent#source @type {HTMLElement} @description The 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 {@link glow.DomEvent#attachedTo attachedTo} would be the <ol>. */ this.source = e.target || e.srcElement || undefined; // some rare cases crop up in Firefox where the source is a text node if (this.source && this.source.nodeType === 3) { this.source = this.source.parentNode; } /** @name glow.events.DomEvent#related @type {HTMLElement} @description A related HTMLElement For mouseover / mouseenter events, this will refer to the previous element the mouse was over. For mouseout / mouseleave events, this will refer to the element the mouse is now over. */ this.related = e.relatedTarget || (this.type == 'mouseover' ? e.fromElement : e.toElement); /** @name glow.events.DomEvent#shiftKey @type {boolean | undefined} @description Was the shift key pressed during the event? */ this.shiftKey = (e.shiftKey === undef)? undef : !!e.shiftKey; /** @name glow.events.DomEvent#altKey @type {boolean | undefined} @description Was the alt key pressed during the event? */ this.altKey = (e.altKey === undef)? undef : !!e.altKey; /** @name glow.events.DomEvent#ctrlKey @type {boolean | undefined} @description Was the ctrl key pressed during the event? */ this.ctrlKey = (e.ctrlKey === undef)? undef : !!e.ctrlKey; /** @name glow.events.DomEvent#button @type {number | undefined} @description A number representing which button was pressed. 0 for the left button, 1 for the middle button or 2 for the right button. */ this.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button; /** @name glow.events.DomEvent#mouseTop @type {number} @description The vertical position of the mouse pointer in the page in pixels. */ /** @name glow.events.DomEvent#mouseLeft @type {number} @description The horizontal position of the mouse pointer in the page in pixels. */ if (e.pageX !== undef || e.pageY !== undef) { this.mouseTop = e.pageY; this.mouseLeft = e.pageX; } else if (e.clientX !== undef || e.clientY !== undef) { this.mouseTop = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; this.mouseLeft = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; } /** @name glow.events.DomEvent#wheelData @type {number} @description The number of clicks the mouse wheel moved. Up values are positive, down values are negative. */ if (this.type == 'mousewheel') { // this works in latest opera, but have read that it needs to be switched in direction // if there was an opera bug, I can't find which version it was fixed in this.wheelDelta = e.wheelDelta ? e.wheelDelta / 120 : e.detail ? - e.detail / 3 : 0; } for (var key in properties) { this[key] = properties[key]; } } glow.util.extend(DomEvent, events.Event, { // no docs for this as it simply adds DOM behaviour to glow.events.Event#preventDefault preventDefault: function() { var nativeEvent = this.nativeEvent; if (nativeEvent) { nativeEvent.preventDefault && nativeEvent.preventDefault(); nativeEvent.returnValue = false; } // call the original method events.Event.prototype.preventDefault.call(this); return this; }, /** @name glow.events.DomEvent#stopPropagation @function @description Stop an event bubbling any further. For instance, if you had 2 click listeners, one on a link and one on a parent element, if you stopped the event propogating in the link listener, the event will never be fired on the parent element. @returns this */ stopPropagation: function() { var nativeEvent = this.nativeEvent; if (nativeEvent) { // the ie way nativeEvent.cancelBubble = true; // the proper way nativeEvent.stopPropagation && nativeEvent.stopPropagation(); } return this; } }); /** Add listener for an event fired by the browser. @private @name glow.events._addDomEventListener @see glow.NodeList#on @function */ events._addDomEventListener = function(nodeList, eventName) { var i = nodeList.length, // TODO: should we check that this nodeList is deduped? attachTo, id; while (i--) { attachTo = nodeList[i]; id = _getPrivateEventKey(attachTo); // check if there is already a handler for this kind of event attached // to this node (which will run all associated callbacks in Glow) if (!domEventHandlers[id]) { domEventHandlers[id] = {}; } if (domEventHandlers[id][eventName] && domEventHandlers[id][eventName].count > 0) { // already have handler in place domEventHandlers[id][eventName].count++; continue; } // no bridge in place yet domEventHandlers[id][eventName] = { count:1 }; // attach a handler to tell Glow to run all the associated callbacks (function(attachTo) { var handler = domHandle(attachTo, eventName); if (attachTo.addEventListener) { // like DOM2 browsers attachTo.addEventListener(handler.domName, handler, (eventName === 'focus' || eventName === 'blur')); // run in bubbling phase except for focus and blur, see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html } else if (attachTo.attachEvent) { // like IE attachTo.attachEvent('on' + handler.domName, handler); } // older browsers? domEventHandlers[id][eventName].callback = handler; })(attachTo); } } function domHandle(attachTo, eventName) { var handler; if (eventName === 'mouseenter' || eventName === 'mouseleave') { // mousenter and mouseleave handle their own delegation as its non-standard handler = function(nativeEvent) { var domEvent = new DomEvent(nativeEvent), container, selector, elementsToTest = _getDelegateMatches(attachTo, eventName, domEvent); // add this element to the delegates elementsToTest.push( [attachTo] ); for (var i = 0, leni = elementsToTest.length; i < leni; i++) { container = elementsToTest[i][0]; selector = elementsToTest[i][1]; if (!new glow.NodeList(container).contains(domEvent.related)) { _callListeners(attachTo, selector ? eventName + '/' + selector : eventName, domEvent, container); // fire() returns result of callback } } return !domEvent.defaultPrevented(); }; handler.domName = (eventName === 'mouseenter') ? 'mouseover' : 'mouseout'; } // handle blur & focus differently for IE so it bubbles else if ( supportsActivateDeactivate && (eventName === 'focus' || eventName === 'blur') ) { // activate and deactivate are like focus and blur but bubble // However, <body> and <html> also activate so we need to fix that handler = function(nativeEvent) { var nodeName = nativeEvent.srcElement.nodeName; if (nodeName !== 'HTML' && nodeName !== 'BODY') { _callDomListeners( attachTo, eventName, new DomEvent(nativeEvent) ); } } handler.domName = (eventName === 'focus') ? 'activate' : 'deactivate'; } else { handler = function(nativeEvent) { var domEvent = new DomEvent(nativeEvent); _callDomListeners(attachTo, eventName, domEvent); // fire() returns result of callback return !domEvent.defaultPrevented(); }; handler.domName = eventName; } return handler; } /** Remove listener for an event fired by the browser. @private @name glow.events._removeDomEventListener @see glow.NodeList#detach @function */ events._removeDomEventListener = function(nodeList, eventName) { var i = nodeList.length, attachTo, id, bridge, handler; while (i--) { attachTo = nodeList[i]; // skip if there is no bridge for this kind of event attached id = _getPrivateEventKey(attachTo); if (!domEventHandlers[id] || !domEventHandlers[id][eventName]) { continue; } bridge = domEventHandlers[id][eventName]; // one less listener associated with this event if ( !--bridge.count ) { // no more listeners associated with this event handler = bridge.callback; if (attachTo.removeEventListener) { // like DOM2 browsers attachTo.removeEventListener(handler.domName, handler, (eventName === 'focus' || eventName === 'blur')); // run in bubbling phase except for focus and blur, see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html } else if (attachTo.detachEvent) { // like IE attachTo.detachEvent('on' + handler.domName, handler); } domEventHandlers[id][eventName] = undefined; } } } // see: http://developer.yahoo.com/yui/3/event/#eventsimulation // see: http://developer.yahoo.com/yui/docs/YAHOO.util.UserAction.html // function simulateDomEvent(nodeList, domEvent) { // var i = nodeList.length, // eventName = domEvent.type, // nativeEvent, // node, // fire; // // if (document.createEvent) { // var nativeEvent = document.createEvent('MouseEvent'); // see: // nativeEvent.initEvent(eventName, true, true); // // fire = function(el) { // return !el.dispatchEvent(nativeEvent); // } // } // else { // fire = function(el) { // var nativeEvent = document.createEventObject(); // return el.fireEvent('on'+eventName, nativeEvent); // } // } // // while (i--) { // node = nodeList[i]; // if (node.nodeType !== 1) { continue; } // fire(node); // } // } /* The following is a proposal for dealing with event delegation without multiple bridges. This allows us to have only one listener per element per event therefore only one search for delegates per event. */ // structure: // delegates[eventId][eventName][selector] = number of delegates listening for that selector (for that event on that element) var delegates = {} /** @name glow.events._registerDelegate @private @function @description Register a delegated event This allows selectors for a given element & eventName to be retrieved later @param {glow.NodeList} nodeList Elements to register @param {string} eventName @param {string} selector Selector to match for the delegate */ events._registerDelegate = function(nodeList, eventName, selector) { var id, i = nodeList.length, delegatesForEvent; while (i--) { id = _getPrivateEventKey( nodeList[i] ); delegates[id] = delegates[id] || {}; delegatesForEvent = delegates[id][eventName] = delegates[id][eventName] || {}; // increment the count or set it to 1 delegatesForEvent[selector] = delegatesForEvent[selector] + 1 || 1; } }; /** @name glow.events._unregisterDelegate @private @function @description Unregister a delegated event @param {glow.NodeList} nodeList Elements to unregister @param {string} eventName @param {string} selector Selector to match for the delegate */ events._unregisterDelegate = function(nodeList, eventName, selector) { var id, selectorCounts, i = nodeList.length; while (i--) { id = _getPrivateEventKey( nodeList[i] ); if ( !delegates[id] || !( selectorCounts = delegates[id][eventName] ) ) { continue; } // either decrement the count or delete the entry if ( selectorCounts[selector] && --selectorCounts[selector] === 0 ) { delete selectorCounts[selector]; } } }; /** @name glow.events._getDelegateMatches @private @function @description Get the elements which qualify for a delegated event @param {HTMLElement} element Element the listener is attached to @param {string} eventName @param {glow.events.DomEvent} event DOM event for the original event The events source will be used as a place to start searching @returns {Array[]} An array of arrays like [matchedNode, selectorMatched] */ var _getDelegateMatches = events._getDelegateMatches = function(element, eventName, event) { var id = _getPrivateEventKey(element), selectorCounts, selector, node, r = []; // get delegated listeners if ( delegates[id] && ( selectorCounts = delegates[id][eventName] ) ) { for (selector in selectorCounts) { node = event.source; // if the source matches the selector while (node && node !== element) { if (glow._sizzle.matches( selector, [node] ).length) { r.push( [node, selector] ); break; } node = node.parentNode; } } } return r; } /** @name glow.events._callDomListeners @private @function @description Call delegated listeners and normal listeners for an event Events that don't bubble (like mouseenter and mouseleave) need to handle their own delegation rather than use this. @param {HTMLElement} element Element to fire event on @param {string} eventName @param {glow.events.DomEvent} event @returns {glow.events.DomEvent} Original event passed in */ var _callDomListeners = events._callDomListeners = function(element, eventName, event) { var delegateMatches = _getDelegateMatches(element, eventName, event); // call delegated listeners for (var i = 0, leni = delegateMatches.length; i < leni; i++) { event.attachedTo = delegateMatches[i][0]; _callListeners( element, eventName + '/' + delegateMatches[i][1], event, delegateMatches[i][0] ); } // call non-delegated listeners event.attachedTo = element; _callListeners(element, eventName, event); return event; } // export events.DomEvent = DomEvent; }); Glow.provide(function(glow) { var document = window.document, undefined, keyboardEventProto, env = glow.env, // the keyCode for the last keydown (returned to undefined on keyup) activeKey, // the charCode for the last keypress (returned to undefined on keyup & keydown) activeChar, DomEvent = glow.events.DomEvent, _callDomListeners = glow.events._callDomListeners, _getPrivateEventKey = glow.events._getPrivateEventKey, // object of event names & listeners, eg: // { // eventId: [ // 2, // the number of glow listeners added for this node // keydownListener, // keypressListener, // keyupListener // ] // } // This lets us remove these DOM listeners from the node when the glow listeners reaches zero eventKeysRegistered = {}; /** @name glow.events.KeyboardEvent @constructor @extends glow.events.DomEvent @description Describes a keyboard event. You don't need to create instances of this class if you're simply listening to events. One will be provided as the first argument in your callback. @param {Event} nativeEvent A native browser event read properties from. @param {Object} [properties] Properties to add to the Event instance. Each key-value pair in the object will be added to the Event as properties. */ function KeyboardEvent(nativeEvent) { if (activeKey) { this.key = keyCodeToId(activeKey); } if (activeChar) { this.keyChar = String.fromCharCode(activeChar); } DomEvent.call(this, nativeEvent); } glow.util.extend(KeyboardEvent, DomEvent, { /** @name glow.events.KeyboardEvent#key @type {string} @description The key pressed This is a string representing the key pressed. Alphanumeric keys are represented by 0-9 and a-z (always lowercase). Other safe cross-browser values are: <ul> <li>backspace</li> <li>tab</li> <li>return</li> <li>shift</li> <li>alt</li> <li>escape</li> <li>space</li> <li>pageup</li> <li>pagedown</li> <li>end</li> <li>home</li> <li>left</li> <li>up</li> <li>right</li> <li>down</li> <li>insert</li> <li>delete</li> <li>;</li> <li>=</li> <li>-</li> <li>f1</li> <li>f2</li> <li>f3</li> <li>f4</li> <li>f5</li> <li>f6</li> <li>f7</li> <li>f8</li> <li>f9</li> <li>f10</li> <li>f11</li> <li>f12</li> <li>numlock</li> <li>scrolllock</li> <li>pause</li> <li>,</li> <li>.</li> <li>/</li> <li>[</li> <li>\</li> <li>]</li> </ul> Some keys may trigger actions in your browser and operating system, some are not cancelable. @example glow(document).on('keypress', function(event) { switch (event.key) { case 'up': // do stuff break; case 'down': // do stuff break; } }); */ key: '', /** @name glow.events.KeyboardEvent#keyChar @type {string} @description The character entered. This is only available during 'keypress' events. If the user presses shift and 1, event.key will be "1", but event.keyChar will be "!". @example // only allow numbers to be entered into the ageInput field glow('#ageInput').on('keypress', function(event) { // Convert keyChar to a number and see if we get // a valid number back return !isNaN( Number(event.keyChar) ); }); */ keyChar: '' }); /** @private @description Add a listener onto a DOM element @param {HTMLElement} elm @param {string} name Event name, without 'on' at the start @param {function} callback Callback for the event */ function addListener(elm, name, callback) { if (elm.addEventListener) { // like DOM2 browsers elm.addEventListener(name, callback, false); } else if (elm.attachEvent) { // like IE elm.attachEvent('on' + name, callback); } } /** @private @description Removes a listener onto a DOM element @param {HTMLElement} elm @param {string} name Event name, without 'on' at the start @param {function} callback Callback for the event */ function removeListener(elm, name, callback) { if (elm.removeEventListener) { // like DOM2 browsers elm.removeEventListener(name, callback, false); } else if (elm.detachEvent) { // like IE elm.detachEvent('on' + name, callback); } } /** @private @description Do we expect the browser to fire a keypress after a given keydown? Also fills in activeChar for webkit. @param {number} keyCode The keyCode from a keydown listener. @param {boolean} defaultPrevented Was the keydown prevented? */ function expectKeypress(keyCode, defaultPrevented) { var keyName; // for browsers that fire keypress for the majority of keys if (env.gecko || env.opera || env.webkit < 525) { return !noKeyPress[keyCode]; } // for browsers that only fire keypress for printable chars keyName = keyCodeToId(keyCode); // is this a printable char? if (keyName.length === 1 || keyName === 'tab' || keyName === 'space') { // webkit doesn't fire keypress if the keydown has been prevented // take a good guess at the active char for webkit activeChar = ( keyNameToChar[keyName] || keyName ).charCodeAt(0); return !(env.webkit && defaultPrevented); } return false; } /** @private @description Add the key listeners for firing glow's normalised key events. @param {HTMLElement} attachTo Element to attach listeners to. @returns {Object[]} An entry for eventKeysRegistered. */ function addDomKeyListeners(attachTo) { var keydownHandler, keypressHandler, keyupHandler, // Even though the user may only be interested in one key event, // we need all 3 listeners to normalise any of them. // Hash of which keys are down, keyed by keyCode // Like: {123: true, 124: false} keysDown = {}; keydownHandler = function(nativeEvent) { var keyCode = nativeEvent.keyCode, preventDefault, preventDefaultKeyPress; // some browsers repeat this event while a key is held down, we don't want to do that if ( !keysDown[keyCode] ) { activeKey = keyCode; activeChar = undefined; preventDefault = _callDomListeners( attachTo, 'keydown', new KeyboardEvent(nativeEvent) ).defaultPrevented(); keysDown[keyCode] = true; } // we want to fire a keyPress event here if the browser isn't going to fire one itself if ( !expectKeypress(keyCode, preventDefault) ) { preventDefaultKeyPress = _callDomListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented(); } // return false if either the keydown or fake keypress event was cancelled return !(preventDefault || preventDefaultKeyPress); }; keypressHandler = function(nativeEvent) { var keyName, preventDefault; // some browsers store the charCode in .charCode, some in .keyCode activeChar = nativeEvent.charCode || nativeEvent.keyCode; keyName = keyCodeToId(activeKey); // some browsers fire this event for non-printable chars, look at the previous keydown and see if we're expecting a printable char if ( keyName.length > 1 && keyName !== 'tab' && keyName !== 'space' ) { // non-printable chars usually have an ID length greater than 1 activeChar = undefined; } preventDefault = _callDomListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented(); return !preventDefault; }; keyupHandler = function(nativeEvent) { var keyCode = nativeEvent.keyCode, preventDefault; // set the active key so KeyboardEvent picks it up activeKey = keyCode; activeChar = undefined; preventDefault = _callDomListeners( attachTo, 'keyup', new KeyboardEvent(nativeEvent) ).defaultPrevented(); keysDown[keyCode] = false; activeKey = undefined; return !preventDefault; }; // add listeners to the dom addListener(attachTo, 'keydown', keydownHandler); addListener(attachTo, 'keypress', keypressHandler); addListener(attachTo, 'keyup', keyupHandler); return [1, keydownHandler, keypressHandler, keyupHandler]; } /** @name glow.events._addKeyListener @private @function @description Add DOM listeners for key events fired by the browser. Won't add more than one. @param {glow.NodeList} nodeList Elements to add listeners to. @see glow.NodeList#on */ glow.events._addKeyListener = function(nodeList) { var i = nodeList.length, attachTo, eventKey; while (i--) { attachTo = nodeList[i]; // get the ID for this event eventKey = _getPrivateEventKey(attachTo); // if we've already attached DOM listeners for this, don't add them again if ( eventKeysRegistered[eventKey] ) { // increment the number of things listening to this // This lets us remove these DOM listeners from the node when // the glow listeners reaches zero eventKeysRegistered[eventKey][0]++; continue; } else { eventKeysRegistered[eventKey] = addDomKeyListeners(attachTo); } } } /** @name glow.events._removeKeyListener @private @function @description Remove DOM listeners for key events fired by the browser Avoids removing DOM listeners until all Glow listeners have been removed @param {glow.NodeList} nodeList Elements to remove listeners from @see glow.NodeList#detach */ glow.events._removeKeyListener = function(nodeList) { var i = nodeList.length, attachTo, eventKey, eventRegistry; while (i--) { attachTo = nodeList[i]; // get the ID for this event eventKey = _getPrivateEventKey(attachTo); eventRegistry = eventKeysRegistered[eventKey]; // exist if there are no key events registered for this node if ( !eventRegistry ) { continue; } if ( --eventRegistry[0] === 0 ) { // our glow listener count is zero, we have no need for the dom listeners anymore removeListener( attachTo, 'keydown', eventRegistry[1] ); removeListener( attachTo, 'keypress', eventRegistry[2] ); removeListener( attachTo, 'keyup', eventRegistry[3] ); eventKeysRegistered[eventKey] = undefined; } } } /** @private @function @description convert a keyCode to a string name for that key @param {number} keyCode @returns {string} ID for that key. Is a letter a-z, number 0-9, or id from 'keyIds' */ function keyCodeToId(keyCode) { // key codes for 0-9 A-Z are the same as their char codes if ( (keyCode >= keyCodeA && keyCode <= keyCodeZ) || (keyCode >= keyCode0 && keyCode <= keyCode9) ) { return String.fromCharCode(keyCode).toLowerCase(); } return keyIds[keyCode] || 'unknown' + keyCode; } // keyCode to key name translation var keyCodeA = 65, keyCodeZ = 90, keyCode0 = 48, keyCode9 = 57, // key codes for non-alphanumeric keys keyIds = { 8: 'backspace', 9: 'tab', 13: 'return', 16: 'shift', 17: 'control', 18: 'alt', 19: 'pause', 27: 'escape', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down', //44: 'printscreen', // Only fires keyup in firefox, IE. Doesn't fire in webkit, opera. 45: 'insert', 46: 'delete', 59: ';', 61: '=', //91: 'meta', //93: 'menu', // no keycode in opera, doesn't fire in Chrome // these are number pad numbers, but Opera doesn't distinguish them from normal number keys so we normalise on that 96: '0', 97: '1', 98: '2', 99: '3', 100: '4', 101: '5', 102: '6', 103: '7', 104: '8', 105: '9', //106: '*', // opera fires 2 keypress events //107: '+', // opera fires 2 keypress events 109: '-', // opera sees - as insert, but firefox 3.0 see the normal - key the same as the numpad one //110: '.', // opera sees this as n 111: '/', // end of numpad 112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4', 116: 'f5', 117: 'f6', 118: 'f7', 119: 'f8', 120: 'f9', 121: 'f10', 122: 'f11', 123: 'f12', 144: 'numlock', 145: 'scrolllock', 188: ',', 189: '-', 190: '.', 191: '/', 192: "'", 219: '[', 220: '\\', 221: ']', 222: '#', // opera sees # key as 3. Pah. 223: '`', //224: 'meta', // same as [ in opera 226: '\\' // this key appears on a US layout in webkit windows }, // converting key names to chars, for key names greater than 1 char keyNameToChar = { space: ' ', tab: '\t' } noKeyPress = {}; // corrections for particular browsers :( if (env.gecko) { keyIds[107] = '='; noKeyPress = { 16: 1, // shift 17: 1, // control 18: 1, // alt 144: 1, // numlock 145: 1 // scrolllock }; } else if (env.opera) { keyIds[42] = '*'; keyIds[43] = '+'; keyIds[47] = '/'; keyIds[222] = "'"; keyIds[192] = '`'; noKeyPress = { 16: 1, // shift 17: 1, // control 18: 1 // alt }; } else if (env.webkit || env.ie) { keyIds[186] = ';'; keyIds[187] = '='; } // export glow.events.KeyboardEvent = KeyboardEvent; }); Glow.provide(function(glow) { var NodeListProto, undefined, // shortcuts to aid compression document = window.document, arraySlice = Array.prototype.slice, arrayPush = Array.prototype.push; /** @name glow.NodeList @constructor @description An array-like collection of DOM Nodes It is recommended to create a NodeList using the shortcut function {@link glow}. @param {string | glow.NodeList | Node | Node[] | Window} contents Items to populate the NodeList with. This parameter will be passed to {@link glow.NodeList#push}. Strings will be treated as CSS selectors unless they start with '<', in which case they'll be treated as an HTML string. @example // empty NodeList var myNodeList = glow(); @example // using glow to return a NodeList then chaining methods glow('p').addClass('eg').append('<div>Hello!</div>'); @example // creating an element from a string glow('<div>Hello!</div>').appendTo('body'); @see <a href="http://wiki.github.com/jeresig/sizzle/">Supported CSS selectors</a> */ function NodeList(contents) { // call push if we've been given stuff to add contents && this.push(contents); } NodeListProto = NodeList.prototype; /** @name glow.NodeList#length @type Number @description Number of nodes in the NodeList @example // get the number of paragraphs on the page glow('p').length; */ NodeListProto.length = 0; /** @name glow.NodeList._strToNodes @private @function @description Converts a string to an array of nodes @param {string} str HTML string @returns {Node[]} Array of nodes (including text / comment nodes) */ NodeList._strToNodes = (function() { var tmpDiv = document.createElement('div'), // these wraps are in the format [depth to children, opening html, closing html] tableWrap = [1, '<table>', '</table>'], emptyWrap = [0, '', ''], // Easlier Webkits won't accept <link> & <style> elms to be the only child of an element, // it steals them and hides them in the head for some reason. Using // broken html fixes it for some reason paddingWrap = glow.env.webkit < 526 ? [0, '', '</div>'] : [1, 'b<div>', '</div>'], trWrap = [3, '<table><tbody><tr>', '</tr></tbody></table>'], wraps = { caption: tableWrap, thead: tableWrap, th: trWrap, colgroup: tableWrap, tbody: tableWrap, tr: [2, '<table><tbody>', '</tbody></table>'], td: trWrap, tfoot: tableWrap, option: [1, '<select multiple="multiple">', '</select>'], legend: [1, '<fieldset>', '</fieldset>'], link: paddingWrap, script: paddingWrap, style: paddingWrap, '!': paddingWrap }; function strToNodes(str) { var r = [], tagName = ( /^<([\w!]+)/.exec(str) || [] )[1], // This matches str content with potential elements that cannot // be a child of <div>. elmFilter declared at top of page. wrap = wraps[tagName] || emptyWrap, nodeDepth = wrap[0], childElm = tmpDiv, exceptTbody, rLen = 0, firstChild; // Create the new element using the node tree contents available in filteredElm. childElm.innerHTML = (wrap[1] + str + wrap[2]); // Strip newElement down to just the required elements' parent while(nodeDepth--) { childElm = childElm.lastChild; } // pull nodes out of child if (wrap === tableWrap && str.indexOf('<tbody') === -1) { // IE7 (and earlier) sometimes gives us a <tbody> even though we didn't ask for one while (firstChild = childElm.firstChild) { if (firstChild.nodeName != 'TBODY') { r[rLen++] = firstChild; } childElm.removeChild(firstChild); } } else { while (firstChild = childElm.firstChild) { r[rLen++] = childElm.removeChild(firstChild); } } return r; } return strToNodes; })(); // takes a collection and returns an array var collectionToArray = function(collection) { return arraySlice.call(collection, 0); }; try { // look out for an IE bug arraySlice.call( document.documentElement.childNodes, 0 ); } catch(e) { collectionToArray = function(collection) { // We can't use this trick on IE collections that are com-based, like HTMLCollections // Thankfully they don't have a constructor, so that's how we detect those if (collection instanceof Object) { return arraySlice.call(collection, 0); } var i = collection.length, arr = []; while (i--) { arr[i] = collection[i]; } return arr; } } /** @name glow.NodeList#push @function @description Adds nodes to the NodeList @param {string | Node | Node[] | glow.NodeList} nodes Node(s) to add to the NodeList Strings will be treated as CSS selectors or HTML strings. @returns {glow.NodeList} @example myNodeList.push('<div>Foo</div>').push('h1'); */ NodeListProto.push = function(nodes) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#push expects 1 argument, not '+arguments.length+'.'); } /*gubed!*/ if (nodes) { if (typeof nodes === 'string') { // if the string begins <, treat it as html, otherwise it's a selector if (nodes.charAt(0) === '<') { nodes = NodeList._strToNodes(nodes); } else { nodes = glow._sizzle(nodes) } arrayPush.apply(this, nodes); } else if ( nodes.nodeType || nodes.window == nodes ) { if (this.length) { arrayPush.call(this, nodes); } else { this[0] = nodes; this.length = 1; } } else if (nodes.length !== undefined) { if (nodes.constructor != Array) { // convert array-like objects into an array nodes = collectionToArray(nodes); } arrayPush.apply(this, nodes); } /*!debug*/ else { glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring unexpected argument type, failing silently'); } /*gubed!*/ } /*!debug*/ else { glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring false argument type, failing silently'); } /*gubed!*/ return this; }; /** @name glow.NodeList#eq @function @description Compares this NodeList to another Returns true if both NodeLists contain the same items in the same order @param {Node | Node[] | glow.NodeList} nodeList The NodeList to compare to. @returns {boolean} @see {@link glow.NodeList#is} for testing if a NodeList item matches a selector @example // the following returns true glow('#blah').eq( document.getElementById('blah') ); */ NodeListProto.eq = function(nodeList) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#eq expects 1 argument, not ' + arguments.length + '.'); } if (typeof nodeList !== 'object') { glow.debug.warn('[wrong type] glow.NodeList#eq expects object argument, not ' + typeof nodeList + '.'); } /*gubed!*/ var len = this.length, i = len; // normalise param to NodeList if ( !(nodeList instanceof NodeList) ) { nodeList = new NodeList(nodeList); } // quickly return false if lengths are different if (len != nodeList.length) { return false; } // loop through and return false on inequality while (i--) { if (this[i] !== nodeList[i]) { return false; } } return true; }; /** @name glow.NodeList#slice @function @description Get a section of an NodeList Operates in the same way as an Array's slice method @param {number} start Start index If negative, it specifies a position measured from the end of the list @param {number} [end] End index By default, this is the end of the list. A negative end specifies a position measured from the end of the list. @returns {glow.NodeList} A new sliced NodeList @example var myNodeList = glow("<div></div><p></p>"); myNodeList.slice(1, 2); // selects the paragraph myNodeList.slice(-1); // same thing, selects the paragraph */ NodeListProto.slice = function(/*start, end*/) { return new NodeList( arraySlice.apply(this, arguments) ); }; /** @name glow.NodeList#sort @function @description Sort the elements in the list. Items will already be in document order if a CSS selector was used to fetch them. @param {Function} [func] Function to determine sort order This function will be passed 2 elements (elementA, elementB). The function should return a number less than 0 to sort elementA lower than elementB and greater than 0 to sort elementA higher than elementB. If no function is provided, elements will be sorted in document order. @returns {glow.NodeList} A new sorted NodeList @example //get links in alphabetical (well, lexicographical) order var links = glow("a").sort(function(elementA, elementB) { return glow(elementA).text() < glow(elementB).text() ? -1 : 1; }) */ NodeListProto.sort = function(func) { var items = collectionToArray(this), sortedElms = func ? items.sort(func) : glow._sizzle.uniqueSort(items); return new NodeList(sortedElms); }; /** @name glow.NodeList#item @function @description Get a single item from the list as an NodeList Negative numbers can be used to get items from the end of the list. @param {number} index The numeric index of the node to return. @returns {glow.NodeList} A new NodeList containing a single item @example // get the html from the fourth element myNodeList.item(3).html(); @example // add a class name to the last item myNodeList.item(-1).addClass('last'); */ NodeListProto.item = function(index) { /*!debug*/ if ( arguments.length !== 1 ) { glow.debug.warn('[wrong count] glow.NodeList#item expects 1 argument, got ' + arguments.length); } /*gubed!*/ // TODO: test which of these methods is faster (use the current one unless significantly slower) return this.slice(index, (index + 1) || this.length); // return new NodeList( index < 0 ? this[this.length + index] : this[index] ); }; /** @name glow.NodeList#each @function @description Calls a function for each node in the list. @param {Function} callback The function to call for each node. The function will be passed 2 arguments, the index of the current item, and the NodeList being iterated over. Inside the function 'this' refers to the Node. Returning false from this function stops further iterations @returns {glow.NodeList} @example // add "link number: x" to each link, where x is the index of the link glow("a").each(function(i, nodeList) { glow(this).append(' link number: ' + i); }); @example // breaking out of an each loop glow("a").each(function(i, nodeList) { // do stuff if ( glow(this).hasClass('whatever') ) { // we don't want to process any more links return false; } }); */ NodeListProto.each = function(callback) { /*!debug*/ if ( arguments.length !== 1 ) { glow.debug.warn('[wrong count] glow.NodeList#each expects 1 argument, got ' + arguments.length); } if (typeof callback != 'function') { glow.debug.warn('[wrong type] glow.NodeList#each expects "function", got ' + typeof callback); } /*gubed!*/ for (var i = 0, len = this.length; i<len; i++) { if ( callback.call(this[i], i, this) === false ) { break; } } return this; }; /** @name glow.NodeList#filter @function @description Filter the NodeList @param {Function|string} test Filter test If a string is provided it's treated as a CSS selector. Elements which match the CSS selector are added to the new NodeList. If 'test' is a function, it will be called per node in the NodeList. The function is passed 2 arguments, the index of the current item, and the ElementList being itterated over. Inside the function 'this' refers to the node. Return true to add the element to the new NodeList. @returns {glow.NodeList} A new NodeList containing the filtered nodes @example // return images with a width greater than 320 glow("img").filter(function () { return glow(this).width() > 320; }); @example // Get items that don't have an alt attribute myElementList.filter(':not([alt])'); */ NodeListProto.filter = function(test) { /*!debug*/ if ( arguments.length !== 1 ) { glow.debug.warn('[wrong count] glow.NodeList#filter expects 1 argument, got ' + arguments.length); } if ( !/^(function|string)$/.test(typeof test) ) { glow.debug.warn('[wrong type] glow.NodeList#each expects function/string, got ' + typeof test); } /*gubed!*/ var r = [], ri = 0; if (typeof test === 'string') { r = glow._sizzle.matches(test, this); } else { for (var i = 0, len = this.length; i<len; i++) { if ( test.call(this[i], i, this) ) { r[ri++] = this[i]; } } } return new NodeList(r); }; /** @name glow.NodeList#is @function @description Tests if the first element matches a CSS selector @param {string} selector CSS selector @returns {boolean} @example if ( myNodeList.is(':visible') ) { // ... } */ NodeListProto.is = function(selector) { /*!debug*/ if ( arguments.length !== 1 ) { glow.debug.warn('[wrong count] glow.NodeList#is expects 1 argument, got ' + arguments.length); } if ( typeof selector !== 'string' ) { glow.debug.warn('[wrong type] glow.NodeList#is expects string, got ' + typeof selector); } /*gubed!*/ if ( !this[0] ) { return false; } return !!glow._sizzle.matches( selector, [ this[0] ] ).length; }; // export glow.NodeList = NodeList; }); Glow.provide(function(glow) { var undef , NodeListProto = glow.NodeList.prototype /** @private @name glow.NodeList-dom0PropertyMapping @description Mapping of HTML attribute names to DOM0 property names. */ , dom0PropertyMapping = { // keys must be lowercase 'class' : 'className', 'for' : 'htmlFor', 'maxlength' : 'maxLength' } /** @private @name glow.NodeList-dataPropName @type String @description The property name added to the DomElement by the NodeList#data method. */ , dataPropName = '_uniqueData' + glow.UID /** @private @name glow.NodeList-dataIndex @type String @description The value of the dataPropName added by the NodeList#data method. */ , dataIndex = 1 // must be a truthy value /** @private @name glow.NodeList-dataCache @type Object @description Holds the data used by the NodeList#data method. The structure is like: [ { myKey: "my data" } ] */ , dataCache = []; /** @name glow.NodeList#addClass @function @description Adds a class to each node. @param {string} name The name of the class to add. @returns {glow.NodeList} @example glow("#login a").addClass("highlight"); */ NodeListProto.addClass = function(name) { var i = this.length; /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#addClass expects 1 argument, not '+arguments.length+'.'); } else if (typeof arguments[0] !== 'string') { glow.debug.warn('[wrong type] glow.NodeList#addClass expects argument 1 to be of type string, not '+typeof arguments[0]+'.'); } /*gubed!*/ while (i--) { if (this[i].nodeType === 1) { _addClass(this[i], name); } } return this; }; function _addClass(node, name) { // TODO: handle classnames separated by non-space characters? if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') === -1 ) { node.className += (node.className? ' ' : '') + name; } } /** @name glow.NodeList#attr @function @description Gets or sets attributes. When getting an attribute, it is retrieved from the first node in this NodeList. Setting attributes applies the change to each element in this NodeList. To set an attribute, pass in the name as the first parameter and the value as a second parameter. To set multiple attributes in one call, pass in an object of name/value pairs as a single parameter. For browsers that don't support manipulating attributes using the DOM, this method will try to do the right thing (i.e. don't expect the semantics of this method to be consistent across browsers as this is not possible with currently supported browsers). @param {string | Object} name The name of the attribute, or an object of name/value pairs @param {string} [value] The value to set the attribute to. @returns {string | undefined | glow.NodeList} When setting attributes this method returns its own NodeList, otherwise returns the attribute value. The attribute name is always treated as case-insensitive. When getting, the returned value will be of type string unless that particular attribute was never set and there is no default value, in which case the returned value will be an empty string. @example var myNodeList = glow(".myImgClass"); // get an attribute myNodeList.attr("class"); // set an attribute myNodeList.attr("class", "anotherImgClass"); // set multiple attributes myNodeList.attr({ src: "a.png", alt: "Cat jumping through a field" }); */ // see: http://tobielangel.com/2007/1/11/attribute-nightmare-in-ie/ NodeListProto.attr = function(/*arguments*/) { var args = arguments, argsLen = args.length, thisLen = this.length, keyvals, name = keyvals = args[0], // using this API: attr(name) or attr({key: val}) ? dom0Property = '', node, attrNode; /*!debug*/ if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#attr expects name to be of type string, not '+typeof arguments[0]+'.'); } else if (arguments.length === 1 && (typeof arguments[0] !== 'string' && arguments[0].constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#attr expects argument 1 to be of type string or an instance of Object.'); } else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#attr expects 1 or 2 arguments, not '+arguments.length+'.'); } /*gubed!*/ if (this.length === 0) { // is this an empty nodelist? return (argsLen > 1)? this : undef; } if (typeof keyvals === 'object') { // SETting value from {name: value} object for (name in keyvals) { if (!keyvals.hasOwnProperty(name)) { continue; } // in IE6 and IE7 the attribute name needs to be translated into dom property name if (glow.env.ie < 8) { dom0Property = dom0PropertyMapping[name.toLowerCase()]; } var i = thisLen; while (i--) { node = this[i]; if (node.nodeType !== 1) { continue; } if (dom0Property) { node[dom0Property] = keyvals[name]; } else { node.setAttribute(name, keyvals[name], 0); // IE flags, 0: case-insensitive } } } return this; } else { node = this[0]; if (node.nodeType !== 1) { return (argsLen > 1)? this : undef; } if (argsLen === 1) { // GETting value from name. see http://reference.sitepoint.com/javascript/Element/getAttribute if ( glow.env.ie && (name === 'href' || name === 'src') ) { value = node.getAttribute(name, 2); // IE flags, 0: case-insensitive + 2: exactly as set return (value === null)? '' : value; } else if (node.attributes[name]) { // in IE node.getAttributeNode sometimes returns unspecified default values so we look for specified attributes if we can return (!node.attributes[name].specified)? '' : node.attributes[name].value; } else if (node.getAttributeNode) { // in IE getAttribute() does not always work so we use getAttributeNode if we can attrNode = node.getAttributeNode(name, 0); return (attrNode === null)? '' : attrNode.value; } else { value = node.getAttribute(name, 2); // IE flags, 0: case-insensitive + 2: exactly as set return (value === null)? '' : value; } } else { // SETting a single value like attr(name, value), normalize to an keyval object if (glow.env.ie < 8) { dom0Property = dom0PropertyMapping[name.toLowerCase()]; } if (dom0Property) { node[dom0Property] = args[1]; } else { node.setAttribute(name, args[1], 0); // IE flags, 0: case-insensitive } return this; } } }; /** Copies the data from one nodelist to another @private @name glow.NodeList._copyData @see glow.NodeList#clone @function */ glow.NodeList._copyData = function(from, to){ var i = to.length, data; while (i--) { data = dataCache[ from[i][dataPropName] ]; data && to.slice(i, i+1).data(data); } } /** @name glow.NodeList#data @function @description Use this to safely attach arbitrary data to any DOM Element. This method is useful when you wish to avoid memory leaks that are possible when adding your own data directly to DOM Elements. When called with no arguments, will return glow's entire data store for the first node in this NodeList. Otherwise, when given a name, will return the associated value from the first node in this NodeList. When given both a name and a value, will store that data on every node in this NodeList. Optionally you can pass in a single object composed of multiple name, value pairs. @param {string|Object} [key] The name of the value in glow's data store. @param {Object} [val] The value you wish to associate with the given name. @see glow.NodeList#removeData @example glow("p").data("tea", "milky"); var colour = glow("p").data("tea"); // milky @returns {Object} When setting a value this method can be chained, as in that case it will return itself. @see glow.NodeList#removeData */ NodeListProto.data = function (key, val) { /*debug*///console.log("data("+key+", "+val+")"); var args = arguments, argsLen = args.length, keyvals = key, // like: data({key: val}) or data(key, val) index, node; /*!debug*/ if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#data expects name argument to be of type string.'); } else if (arguments.length === 1 && (typeof arguments[0] !== 'string' && arguments[0].constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#data expects argument 1 to be of type string or an instance of Object.'); } else if (arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#data expects 0, 1 or 2 arguments.'); } /*gubed!*/ if (argsLen > 1) { // SET key, val on every node var i = this.length; while (i--) { node = this[i]; if (node.nodeType !== 1) { continue; } index = node[''+dataPropName]; if (!index) { // assumes index is always > 0 index = dataIndex++; node[dataPropName] = index; dataCache[index] = {}; } dataCache[index][key] = val; } return this; // chainable with (key, val) signature } else if (typeof keyvals === 'object') { // SET keyvals on every node var i = this.length; while (i--) { node = this[i]; if (node.nodeType !== 1) { continue; } index = node[dataPropName]; if (!index) { // assumes index is always > 0 index = dataIndex++; node[dataPropName] = index; dataCache[index] = {}; } for (key in keyvals) { dataCache[index][key] = keyvals[key]; } } return this; // chainable with ({key, val}) signature } else { // GET from first node node = this[0]; if (node === undef || node.nodeType !== 1) { return undef; } if ( !(index = node[dataPropName]) ) { return undef; } if (key !== undef) { return dataCache[index][key]; } // get the entire data cache object for this node return dataCache[index]; } }; /** @name glow.NodeList#hasAttr @function @description Does the node have a particular attribute? The first node in this NodeList is tested. @param {string} name The name of the attribute to test for. @returns {boolean|undefined} Returns undefined if the first node is not an element, or if the NodeList is empty, otherwise returns true/false to indicate if that attribute exists on the first element. @example if ( glow("#myImg").hasAttr("alt") ){ // ... } */ NodeListProto.hasAttr = function(name) { var node; /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasAttr expects 1 argument.'); } else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasAttr expects argument 1 to be of type string.'); } /*gubed!*/ node = this[0]; if (this.length && node.nodeType === 1) { if (node.attributes[name]) { // is an object in IE, or else: undefined in IE < 8, null in IE 8 return !!node.attributes[name].specified; } if (node.hasAttribute) { return node.hasAttribute(name); } // like FF, Safari, etc else { return node.attributes[name] !== undef; } // like IE7 } }; /** @name glow.NodeList#hasClass @function @description Does the node have a particular class? The first node in this NodeList is tested. @param {string} name The name of the class to test for. @returns {boolean} @example if ( glow("#myInput").hasClass("errored") ){ // ... } */ NodeListProto.hasClass = function (name) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasClass expects 1 argument.'); } else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasClass expects argument 1 to be of type string.'); } /*gubed!*/ if (this.length && this[0].nodeType === 1) { return ( (' ' + this[0].className + ' ').indexOf(' ' + name + ' ') > -1 ); } }; /** @name glow.NodeList#prop @function @description Gets or sets node properties. This function gets / sets node properties, to get attributes, see {@link glow.NodeList#attr NodeList#attr}. When getting a property, it is retrieved from the first node in this NodeList. Setting properties to each element in this NodeList. To set multiple properties in one call, pass in an object of name/value pairs. @param {string | Object} name The name of the property, or an object of name/value pairs @param {string} [value] The value to set the property to. @returns {string | glow.NodeList} When setting properties it returns the NodeList, otherwise returns the property value. @example var myNodeList = glow("#formElement"); // get the node name myNodeList.prop("nodeName"); // set a property myNodeList.prop("_secretValue", 10); // set multiple properties myNodeList.prop({ checked: true, _secretValue: 10 }); */ NodeListProto.prop = function(name, val) { var hash = name, argsLen = arguments.length; /*!debug*/ if (arguments.length === 1 && (typeof name !== 'string' && name.constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#prop expects argument 1 to be of type string or Object.'); } else if (arguments.length === 2 && typeof name !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#prop expects name to be of type string.'); } else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#prop expects 1 or 2 arguments.'); } /*gubed!*/ if (this.length === 0) return; if (argsLen === 2 && typeof name === 'string') { for (var i = 0, ilen = this.length; i < ilen; i++) { if (this[i].nodeType === 1) { this[i][name] = val; } } return this; } else if (argsLen === 1 && hash.constructor === Object) { for (var key in hash) { for (var i = 0, ilen = this.length; i < ilen; i++) { if (this[i].nodeType === 1) { this[i][key] = hash[key]; } } } return this; } else if (argsLen === 1 && typeof name === 'string') { if (this[0].nodeType === 1) { return this[0][name]; } } else { throw new Error('Invalid parameters.'); } }; /** @name glow.NodeList#removeAttr @function @description Removes an attribute from each node. @param {string} name The name of the attribute to remove. @returns {glow.NodeList} @example glow("a").removeAttr("target"); */ NodeListProto.removeAttr = function (name) { var dom0Property; /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeAttr expects 1 argument.'); } else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeAttr expects argument 1 to be of type string.'); } /*gubed!*/ for (var i = 0, leni = this.length; i < leni; i++) { if (this[i].nodeType === 1) { if (glow.env.ie < 8) { if ( (dom0Property = dom0PropertyMapping[name.toLowerCase()]) ) { this[i][dom0Property] = ''; } } if (this[i].removeAttribute) this[i].removeAttribute(name); } } return this; }; /** @name glow.NodeList#removeClass @function @description Removes a class from each node. @param {string} name The name of the class to remove. @returns {glow.NodeList} @example glow("#footer #login a").removeClass("highlight"); */ NodeListProto.removeClass = function(name) { var node; /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeClass() expects 1 argument.'); } else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeClass() expects argument 1 to be of type string.'); } /*gubed!*/ var i = this.length; while (i--) { node = this[i]; if (node.className) { _removeClass(node, name); } } return this; }; function _removeClass(node, name) { var oldClasses = node.className.split(' '), newClasses = []; oldClasses = node.className.split(' '); newClasses = []; var i = oldClasses.length; while (i--) { if (oldClasses[i] !== name) { oldClasses[i] && newClasses.unshift(oldClasses[i]); // unshift to maintain original order } } node.className = (newClasses.length)? newClasses.join(' ') : ''; } /** @name glow.NodeList#removeData @function @description Removes data previously added by {@link glow.NodeList#data} from each node in this NodeList. When called with no arguments, will delete glow's entire data store for each node in this NodeList. Otherwise, when given a name, will delete the associated value from each node in this NodeList. @param {string} [key] The name of the value in glow's data store. @see glow.NodeList#data */ NodeListProto.removeData = function(key) { var elm, i = this.length, index; // uses private scoped variables: dataCache, dataPropName /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.NodeList#removeData expects 0 or 1 arguments.'); } else if (arguments.length === 1 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeData expects argument 1 to be of type string.'); } /*gubed!*/ while (i--) { elm = this[i]; index = elm[dataPropName]; if (index !== undef) { switch (arguments.length) { case 0: dataCache[index] = undef; elm[dataPropName] = undef; try { delete elm[dataPropName]; // IE 6 goes wobbly here } catch(e) { // remove expando from IE 6 elm.removeAttribute && elm.removeAttribute(dataPropName); } break; case 1: dataCache[index][key] = undef; delete dataCache[index][key]; break; } } } return this; // chainable }; /** @name glow.NodeList#toggleClass @function @description Toggles a class on each node. @param {string} name The name of the class to toggle. @returns {glow.NodeList} @example glow(".onOffSwitch").toggleClass("on"); */ NodeListProto.toggleClass = function(name) { var node; /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#toggleClass() expects 1 argument.'); } else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#toggleClass() expects argument 1 to be of type string.'); } /*gubed!*/ for (var i = 0, leni = this.length; i < leni; i++) { node = this[i]; if (node.className) { if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') > -1 ) { _removeClass(node, name); } else { _addClass(node, name); } } } return this; }; /** @name glow.NodeList#val @function @description Gets or sets form values for the first node. The returned value depends on the type of element, see below: <dl> <dt>Radio button or checkbox</dt> <dd>If checked, then the contents of the value property, otherwise an empty string.</dd> <dt>Select</dt> <dd>The contents of value property of the selected option</dd> <dt>Select (multiple)</dt> <dd>An array of selected option values.</dd> <dt>Other form elements</dt> <dd>The value of the input.</dd> </dl> 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. Checkboxes and radio buttons will be checked only if the value is the same as the one you provide. Setting 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.NodeList | string | Object} When used to set a value it returns the NodeList, otherwise returns the value as described above. @example // get a value from an input with the id 'username' var username = glow("#username").val(); @example // get values from a form var userDetails = glow("form").val(); @example // set a value glow("#username").val("example username"); @example // set values in a form glow("form").val({ username : "another", name : "A N Other" }); */ NodeListProto.val = function(){ var args = arguments, val = args[0], i = 0, length = this.length; if (args.length === 0) { return this[0].nodeName == 'FORM' ? formValues(this[0]) : elementValue(this[0]); } if (this[0].nodeName == 'FORM') { if (! typeof val == 'object') { throw 'value for FORM must be object'; } setFormValues(this[0], val); } else { for (; i < length; i++) { setValue(this[i], val); } } return this; }; /* @name elementValue @private @returns the value of the 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; } } /* @name: setValue @description 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 @returns val or bool @private */ 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; } } /* @name setFormValues @description Set values of a form to those in passed in object. @private */ 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] && !currentField.options) { // 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]); } } } /* @name formValues @description Get an object containing form data. @private */ function formValues (form) { var vals = {}, radios = {}, formElements = form.elements, i = formElements.length, name, formElement, j, radio, nodeName; while (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) { var length, 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) { alert('15 if name in vals'); vals[name] = undefined; } } return vals; } }); /*! * Sizzle CSS Selector Engine - v1.0 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true; // Here we check if the JavaScript engine is using some sort of // optimization where it does not always call our comparision // function. If that is the case, discard the hasDuplicate value. // Thus far that includes Google Chrome. [0, 0].sort(function(){ baseHasDuplicate = false; return 0; }); var Sizzle = function(selector, context, results, seed) { results = results || []; context = context || document; var origContext = context; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), soFar = selector, ret, cur, pop, i; // Reset the position of the chunker regexp (start from head) do { chunker.exec(""); m = chunker.exec(soFar); if ( m ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } } while ( m ); if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) { selector += parts.shift(); } set = posProcess( selector, set ); } } } else { // Take a shortcut and set the context if the root selector is an ID // (but not if it'll be faster if the inner selector is an ID) if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray(set); } else { prune = false; } while ( parts.length ) { cur = parts.pop(); pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { Sizzle.error( cur || selector ); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function(results){ if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort(sortOrder); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[i-1] ) { results.splice(i--, 1); } } } } return results; }; Sizzle.matches = function(expr, set){ return Sizzle(expr, null, null, set); }; Sizzle.find = function(expr, context, isXML){ var set; if ( !expr ) { return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var type = Expr.order[i], match; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; match.splice(1,1); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = context.getElementsByTagName("*"); } return {set: set, expr: expr}; }; Sizzle.filter = function(expr, set, inplace, not){ var old = expr, result = [], curLoop = set, match, anyFound, isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); while ( expr && set.length ) { for ( var type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { var filter = Expr.filter[ type ], found, item, left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw "Syntax error, unrecognized expression: " + msg; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function(elem){ return elem.getAttribute("href"); } }, relative: { "+": function(checkSet, part){ var isPartStr = typeof part === "string", isTag = isPartStr && !/\W/.test(part), isPartStrNotTag = isPartStr && !isTag; if ( isTag ) { part = part.toLowerCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function(checkSet, part){ var isPartStr = typeof part === "string", elem, i = 0, l = checkSet.length; if ( isPartStr && !/\W/.test(part) ) { part = part.toLowerCase(); for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } } else { for ( ; i < l; i++ ) { elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck, nodeCheck; if ( typeof part === "string" && !/\W/.test(part) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); }, "~": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck, nodeCheck; if ( typeof part === "string" && !/\W/.test(part) ) { part = part.toLowerCase(); nodeCheck = part; checkFn = dirNodeCheck; } checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); } }, find: { ID: function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? [m] : []; } }, NAME: function(match, context){ if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName(match[1]); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function(match, context){ return context.getElementsByTagName(match[1]); } }, preFilter: { CLASS: function(match, curLoop, inplace, result, not, isXML){ match = " " + match[1].replace(/\\/g, "") + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function(match){ return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ return match[1].toLowerCase(); }, CHILD: function(match){ if ( match[1] === "nth" ) { // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); // calculate the numbers (first)n+(last) including if they are negative match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } // TODO: Move to normal caching system match[0] = done++; return match; }, ATTR: function(match, curLoop, inplace, result, not, isXML){ var name = match[1].replace(/\\/g, ""); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function(match, curLoop, inplace, result, not){ if ( match[1] === "not" ) { // If we're dealing with a complex expression, or a simple one if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function(match){ match.unshift( true ); return match; } }, filters: { enabled: function(elem){ return elem.disabled === false && elem.type !== "hidden"; }, disabled: function(elem){ return elem.disabled === true; }, checked: function(elem){ return elem.checked === true; }, selected: function(elem){ // Accessing this property makes selected-by-default // options in Safari work properly elem.parentNode.selectedIndex; return elem.selected === true; }, parent: function(elem){ return !!elem.firstChild; }, empty: function(elem){ return !elem.firstChild; }, has: function(elem, i, match){ return !!Sizzle( match[3], elem ).length; }, header: function(elem){ return (/h\d/i).test( elem.nodeName ); }, text: function(elem){ return "text" === elem.type; }, radio: function(elem){ return "radio" === elem.type; }, checkbox: function(elem){ return "checkbox" === elem.type; }, file: function(elem){ return "file" === elem.type; }, password: function(elem){ return "password" === elem.type; }, submit: function(elem){ return "submit" === elem.type; }, image: function(elem){ return "image" === elem.type; }, reset: function(elem){ return "reset" === elem.type; }, button: function(elem){ return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; }, input: function(elem){ return (/input|select|textarea|button/i).test(elem.nodeName); } }, setFilters: { first: function(elem, i){ return i === 0; }, last: function(elem, i, match, array){ return i === array.length - 1; }, even: function(elem, i){ return i % 2 === 0; }, odd: function(elem, i){ return i % 2 === 1; }, lt: function(elem, i, match){ return i < match[3] - 0; }, gt: function(elem, i, match){ return i > match[3] - 0; }, nth: function(elem, i, match){ return match[3] - 0 === i; }, eq: function(elem, i, match){ return match[3] - 0 === i; } }, filter: { PSEUDO: function(elem, match, i, array){ var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var j = 0, l = not.length; j < l; j++ ) { if ( not[j] === elem ) { return false; } } return true; } else { Sizzle.error( "Syntax error, unrecognized expression: " + name ); } }, CHILD: function(elem, match){ var type = match[1], node = elem; switch (type) { case 'only': case 'first': while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) { return false; } } if ( type === "first" ) { return true; } node = elem; case 'last': while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) { return false; } } return true; case 'nth': var first = match[2], last = match[3]; if ( first === 1 && last === 0 ) { return true; } var doneName = match[0], parent = elem.parentNode; if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent.sizcache = doneName; } var diff = elem.nodeIndex - last; if ( first === 0 ) { return diff === 0; } else { return ( diff % first === 0 && diff / first >= 0 ); } } }, ID: function(elem, match){ return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function(elem, match){ return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; }, CLASS: function(elem, match){ return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function(elem, match){ var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value !== check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function(elem, match, i, array){ var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS, fescape = function(all, num){ return "\\" + (num - 0 + 1); }; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); } var makeArray = function(array, results) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; // Perform a simple check to determine if the browser is capable of // converting a NodeList to an array using builtin methods. // Also verifies that the returned array holds DOM nodes // (which is not the case in the Blackberry browser) try { Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; // Provide a fallback method if it does not work } catch(e){ makeArray = function(array, results) { var ret = results || [], i = 0; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( ; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { if ( a == b ) { hasDuplicate = true; } return a.compareDocumentPosition ? -1 : 1; } var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } else if ( "sourceIndex" in document.documentElement ) { sortOrder = function( a, b ) { if ( !a.sourceIndex || !b.sourceIndex ) { if ( a == b ) { hasDuplicate = true; } return a.sourceIndex ? -1 : 1; } var ret = a.sourceIndex - b.sourceIndex; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } else if ( document.createRange ) { sortOrder = function( a, b ) { if ( !a.ownerDocument || !b.ownerDocument ) { if ( a == b ) { hasDuplicate = true; } return a.ownerDocument ? -1 : 1; } var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); aRange.setStart(a, 0); aRange.setEnd(a, 0); bRange.setStart(b, 0); bRange.setEnd(b, 0); var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } // Utility function for retreiving the text value of an array of DOM nodes Sizzle.getText = function( elems ) { var ret = "", elem; for ( var i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes if ( elem.nodeType === 3 || elem.nodeType === 4 ) { ret += elem.nodeValue; // Traverse everything else, except comment nodes } else if ( elem.nodeType !== 8 ) { ret += Sizzle.getText( elem.childNodes ); } } return ret; }; // Check to see if the browser returns elements by name when // querying by getElementById (and provide a workaround) (function(){ // We're going to inject a fake input element with a specified name var form = document.createElement("div"), id = "script" + (new Date()).getTime(); form.innerHTML = "<a name='" + id + "'/>"; // Inject it into the root element, check its status, and remove it quickly var root = document.documentElement; root.insertBefore( form, root.firstChild ); // The workaround has to do additional checks after a getElementById // Which slows things down for other browsers (hence the branching) if ( document.getElementById( id ) ) { Expr.find.ID = function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function(elem, match){ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); root = form = null; // release memory in IE })(); (function(){ // Check to see if the browser returns only elements // when doing getElementsByTagName("*") // Create a fake element var div = document.createElement("div"); div.appendChild( document.createComment("") ); // Make sure no comments are found if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function(match, context){ var results = context.getElementsByTagName(match[1]); // Filter out possible comments if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } // Check to see if an attribute returns normalized href attributes div.innerHTML = "<a href='#'></a>"; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function(elem){ return elem.getAttribute("href", 2); }; } div = null; // release memory in IE })(); if ( document.querySelectorAll ) { (function(){ var oldSizzle = Sizzle, div = document.createElement("div"); div.innerHTML = "<p class='TEST'></p>"; // Safari can't handle uppercase or unicode characters when // in quirks mode. if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function(query, context, extra, seed){ context = context || document; // Only use querySelectorAll on non-XML documents // (ID selectors don't work in non-HTML documents) if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { try { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } div = null; // release memory in IE })(); } (function(){ var div = document.createElement("div"); div.innerHTML = "<div class='test e'></div><div class='test'></div>"; // Opera can't find a second classname (in 9.6) // Also, make sure that getElementsByClassName actually exists if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { return; } // Safari caches class attributes, doesn't catch changes (in 3.2) div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) { return; } Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context, isXML) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; div = null; // release memory in IE })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem.sizcache = doneName; elem.sizset = i; } if ( elem.nodeName.toLowerCase() === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem.sizcache = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } Sizzle.contains = document.compareDocumentPosition ? function(a, b){ return !!(a.compareDocumentPosition(b) & 16); } : function(a, b){ return a !== b && (a.contains ? a.contains(b) : true); }; Sizzle.isXML = function(elem){ // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; var posProcess = function(selector, context){ var tmpSet = [], later = "", match, root = context.nodeType ? [context] : context; // Position selectors must be done after the filter // And so must :not(positional) so we move all PSEUDOs to the end while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet ); } return Sizzle.filter( later, tmpSet ); }; // Add Sizzle to Glow // This file is injected into sizzle.js by the ant "deps" target Glow.provide(function(glow) { glow._sizzle = Sizzle; }); return; window.Sizzle = Sizzle; })(); Glow.provide(function(glow) { var NodeListProto = glow.NodeList.prototype /* 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; /* PrivateMethod: unique Get an array of nodes without duplicate nodes from an array of nodes. Arguments: aNodes - (Array|<NodeList>) Returns: An array of nodes without duplicates. */ //worth checking if it's an XML document? if (glow.env.ie) { var unique = function(aNodes) { if (aNodes.length == 1) { return aNodes; } //remove duplicates var r = [], ri = 0, i = 0; for (; aNodes[i]; i++) { if (aNodes[i].getAttribute(ucheckPropName) != ucheck && aNodes[i].nodeType == 1) { r[ri++] = aNodes[i]; } aNodes[i].setAttribute(ucheckPropName, ucheck); } for (i=0; aNodes[i]; i++) { aNodes[i].removeAttribute(ucheckPropName); } ucheck++; return r; } } else { var unique = function(aNodes) { if (aNodes.length == 1) { return aNodes; } //remove duplicates var r = [], ri = 0, i = 0; for (; aNodes[i]; i++) { if (aNodes[i][ucheckPropName] != ucheck && aNodes[i].nodeType == 1) { r[ri++] = aNodes[i]; } aNodes[i][ucheckPropName] = ucheck; } ucheck++; return r; } }; /** @name glow.NodeList#parent @function @description Gets the unique parent nodes of each node as a new NodeList. @param {string | HTMLElement | NodeList} [search] Search value If provided, will seek the next parent element until a match is found @returns {glow.NodeList} Returns a new NodeList containing the parent nodes, with duplicates removed @example // elements which contain links var parents = glow.dom.get("a").parent(); */ NodeListProto.parent = function(search) { var ret = [], ri = 0, i = this.length, node; while (i--) { node = this[i]; if (node.nodeType == 1) { if(search){ while(node = node.parentNode){ if (glow._sizzle.filter(search, [node]).length) { ret[ri++] = node; break; } } } else if(node = node.parentNode){ ret[ri++] = node; } } } return new glow.NodeList(unique(ret)); }; /* Private method for prev() and next() */ function getNextOrPrev(nodelist, dir, search) { var ret = [], ri = 0, node, i = 0, length = nodelist.length; while (i < length) { node = nodelist[i]; if(search){ while (node = node[dir + 'Sibling']) { if (node.nodeType == 1 && node.nodeName != '!') { if (glow._sizzle.filter(search, [node]).length) { ret[ri++] = node; break; } } } } else{ while (node = node[dir + 'Sibling']) { if (node.nodeType == 1 && node.nodeName != '!') { ret[ri++] = node; break; } } } i++; } return new glow.NodeList(ret); } /** @name glow.NodeList#prev @function @description Gets the previous sibling element for each node in the ElementList. If a filter is provided, the previous item that matches the filter is returned, or none if no match is found. @param {string | HTMLElement | NodeList} [search] Search value If provided, will seek the previous sibling element until a match is found @returns {glow.ElementList} A new ElementList containing the previous sibling elements that match the (optional) filter. @example // gets the element before #myLink (if there is one) var next = glow.get("#myLink").prev(); @example // get the previous sibling link element before #skipLink glow.get('#skipLink').prev('a') */ NodeListProto.prev = function(search) { return getNextOrPrev(this, 'previous', search); }; /** @name glow.NodeList#next @function @description Gets the next sibling element for each node in the ElementList. If a filter is provided, the next item that matches the filter is returned, or none if no match is found. @param {string | HTMLElement | NodeList} [search] Search value If provided, will seek the next sibling element until a match is found @returns {glow.ElementList} A new ElementList containing the next sibling elements that match the (optional) filter. @example // gets the element following #myLink (if there is one) var next = glow.get("#myLink").next(); @example // get the next sibling link element after #skipLink glow.get('#skipLink').next('a') */ NodeListProto.next = function(search) { return getNextOrPrev(this, 'next', search); }; /** @name glow.NodeList#get @function @description Gets decendents of nodes that match a CSS selector. @param {String} selector CSS selector @returns {glow.NodeList} Returns a new NodeList containing matched elements @example // create a new NodeList var myNodeList = glow.dom.create("<div><a href='s.html'>Link</a></div>"); // get 'a' tags that are decendants of the NodeList nodes myNewNodeList = myNodeList.get("a"); */ NodeListProto.get = function(selector) { var ret = [], i = this.length; while (i--) { glow._sizzle(selector, this[i], ret); } // need to remove uniqueSorts because they're slow. Replace with own method for unique. return new glow.NodeList(unique(ret)); }; /** @name glow.NodeList#ancestors @function @description Gets the unique ancestor nodes of each node as a new NodeList. @param {Function|string} [filter] Filter test If a string is provided, it is used in a call to {@link glow.ElementList#is ElementList#is}. If a function is provided it will be passed 2 arguments, the index of the current item, and the ElementList being itterated over. Inside the function 'this' refers to the HTMLElement. Return true to keep the node, or false to remove it. @returns {glow.dom.NodeList} Returns NodeList @example // get ancestor elements for anchor elements var ancestors = glow.dom.get("a").ancestors(); */ NodeListProto.ancestors = function(filter) { var ret = [], ri = 0, i = 0, length = this.length, node; while (i < length) { node = this[i].parentNode; while (node && node.nodeType == 1) { ret[ri++] = node; node = node.parentNode; } i++; } if(filter){ ret = new glow.NodeList(ret); ret = ret.filter(filter); } return new glow.NodeList(unique(ret)); }; /* Private method to get the child elements for an html node (used by children()) */ function getChildElms(node) { var r = [], childNodes = node.childNodes, i = 0, ri = 0; for (; childNodes[i]; i++) { if (childNodes[i].nodeType == 1 && childNodes[i].nodeName != '!') { r[ri++] = childNodes[i]; } } return r; } /** @name glow.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(); */ NodeListProto.children = function() { var ret = [], i = this.length; while(i--) { ret = ret.concat( getChildElms(this[i]) ); } return new glow.NodeList(ret); }; /** @name glow.NodeList#contains @function @description Find if this NodeList contains the given element @param {string | HTMLELement | NodeList} Single element to check for @returns {boolean} myElementList.contains(elm) // Returns true if an element in myElementList contains elm, or IS elm. */ NodeListProto.contains = function(elm) { var i = 0, node = new glow.NodeList(elm)[0], length = this.length, newNodes, toTest; // missing some nodes? Return false if ( !node || !this.length ) { return false; } if (this[0].compareDocumentPosition) { //w3 method while (i < length) { //break out if the two are teh same if(this[i] == node){ break; } //check against bitwise to see if node is contained in this else if (!(this[i].compareDocumentPosition(node) & 16)) { return false; } i++; } } else if(node.contains){ for (; i < length; i++) { if ( !( this[i].contains( node ) ) ) { return false; } } } else { //manual method for last chance corale while (i < length) { toTest = node; while (toTest = toTest.parentNode) { if (this[i] == toTest) { break; } } if (!toTest) { return false; } i++; } } return true; }; }); Glow.provide(function(glow) { var NodeListProto = glow.NodeList.prototype, document = window.document, undefined; // create a fragment and insert a set of nodes into it function createFragment(nodes) { var fragment = document.createDocumentFragment(), i = 0, node; while ( node = nodes[i++] ) { fragment.appendChild(node); } return fragment; } // generate the #before and #after methods // after: 1 for #(insert)after, 0 for #(insert)before // insert: 1 for #insert(After|Before), 0 for #(after|before) function afterAndBefore(after, insert) { return function(elements) { var toAddList, toAddToList, fragmentToAdd, nextFragmentToAdd, item, itemParent; if (!this.length) { return this; } // normalise 'elements' // if we're dealing with append/prepend then strings are always treated as HTML strings if (!insert && typeof elements === 'string') { elements = new glow.NodeList( glow.NodeList._strToNodes(elements) ); } else { elements = new glow.NodeList(elements); } // set the element we're going to add to, and the elements we're going to add if (insert) { toAddToList = elements; toAddList = new glow.NodeList(this); } else { toAddToList = this; toAddList = elements; } nextFragmentToAdd = createFragment(toAddList); for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) { item = toAddToList[i]; fragmentToAdd = nextFragmentToAdd; // we can only append after if the element has a parent right? if (itemParent = item.parentNode) { if (i !== lasti) { // if not the last item nextFragmentToAdd = fragmentToAdd.cloneNode(true); insert && toAddList.push(nextFragmentToAdd.childNodes); } itemParent.insertBefore(fragmentToAdd, after ? item.nextSibling : item); } } return insert ? toAddList : toAddToList; } } // generate the #append, #appendTo, #prepend and #prependTo methods // append: 1 for #append(To), 0 for #prepend(To) // to: 1 for #(append|prepend)To, 0 for #(append|prepend) function appendAndPrepend(append, to) { return function(elements) { var toAddList, toAddToList, fragmentToAdd, nextFragmentToAdd, item; if (!this.length) { return this; } // normalise 'elements' // if we're dealing with append/prepend then strings are always treated as HTML strings if (!to && typeof elements === 'string') { elements = new glow.NodeList( glow.NodeList._strToNodes(elements) ); } else { elements = new glow.NodeList(elements); } // set the element we're going to add to, and the elements we're going to add if (to) { toAddToList = elements; toAddList = new glow.NodeList(this); } else { toAddToList = this; toAddList = elements; } nextFragmentToAdd = createFragment(toAddList); for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) { item = toAddToList[i]; fragmentToAdd = nextFragmentToAdd; // avoid trying to append to non-elements if (item.nodeType === 1) { if (i !== lasti) { // if not the last item nextFragmentToAdd = fragmentToAdd.cloneNode(true); // add the clones to the return element for appendTo / prependTo to && toAddList.push(nextFragmentToAdd.childNodes); } item.insertBefore(fragmentToAdd, append ? null : item.firstChild); } } return to ? toAddList : toAddToList; } } /** @name glow.NodeList#after @function @description Insert node(s) after each node in this NodeList. If there is more than one node in this NodeList, 'nodes' will be inserted after the first element and clones will be inserted after each subsequent element. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert Strings will be treated as HTML strings. @returns {glow.NodeList} Original NodeList @example // adds a paragraph after each heading glow('h1, h2, h3').after('<p>That was a nice heading.</p>'); */ NodeListProto.after = afterAndBefore(1); /** @name glow.NodeList#before @function @description Insert node(s) before each node in this NodeList. If there is more than one node in this NodeList, 'nodes' will be inserted before the first element and clones will be inserted before each subsequent element. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert Strings will be treated as HTML strings. @returns {glow.NodeList} Original NodeList @example // adds a div before each paragraph glow('p').before('<div>Here comes a paragraph!</div>'); */ NodeListProto.before = afterAndBefore(0); /** @name glow.NodeList#append @function @description Appends node to each node in this NodeList. If there is more than one node in this NodeList, then the given nodes are appended to the first node and clones are appended to the other nodes. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to append Strings will be treated as HTML strings. @returns {glow.NodeList} Original NodeList @example // ends every paragraph with '...' glow('p').append('<span>...</span>'); */ NodeListProto.append = appendAndPrepend(1); /** @name glow.NodeList#prepend @function @description Prepends nodes to each node in this NodeList. If there is more than one node in this NodeList, then the given nodes are prepended to the first node and clones are prepended to the other nodes. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to prepend Strings will be treated as HTML strings. @returns {glow.NodeList} Original NodeList @example // prepends every paragraph with 'Paragraph: ' glow('p').prepend('<span>Paragraph: </span>'); */ NodeListProto.prepend = appendAndPrepend(0); /** @name glow.NodeList#appendTo @function @description Appends nodes in this NodeList to given node(s) If appending to more than one node, the NodeList is appended to the first node and clones are appended to the others. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to append to. Strings will be treated as CSS selectors or HTML strings. @returns {glow.NodeList} The appended nodes. @example // appends '...' to every paragraph glow('<span>...</span>').appendTo('p'); */ NodeListProto.appendTo = appendAndPrepend(1, 1); /** @name glow.NodeList#prependTo @function @description Prepends nodes in this NodeList to given node(s) If prepending to more than one node, the NodeList is prepended to the first node and clones are prepended to the others. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to prepend to Strings will be treated as CSS selectors or HTML strings. @returns {glow.NodeList} The prepended nodes. @example // prepends 'Paragraph: ' to every paragraph glow('<span>Paragraph: </span>').prependTo('p'); */ NodeListProto.prependTo = appendAndPrepend(0, 1); /** @name glow.NodeList#insertAfter @function @description Insert this NodeList after the given nodes If inserting after more than one node, the NodeList is inserted after the first node and clones are inserted after the others. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert after Strings will be treated as CSS selectors. @returns {glow.NodeList} Inserted nodes. @example // adds a paragraph after each heading glow('<p>HAI!</p>').insertAfter('h1, h2, h3'); */ NodeListProto.insertAfter = afterAndBefore(1, 1); /** @name glow.NodeList#insertBefore @function @description Insert this NodeList before the given nodes If inserting before more than one node, the NodeList is inserted before the first node and clones are inserted before the others. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert before Strings will be treated as CSS selectors. @returns {glow.NodeList} Inserted nodes. @example // adds a div before each paragraph glow('<div>Here comes a paragraph!</div>').insertBefore('p'); */ NodeListProto.insertBefore = afterAndBefore(0, 1); /** @name glow.NodeList#destroy @function @description Removes each element from the document The element, attached listeners & attached data will be destroyed to free up memory. Detroyed elements may not be reused in some browsers. @returns undefined @example // destroy all links in the document glow("a").destroy(); */ var tmpDiv = document.createElement('div'); NodeListProto.destroy = function() { var allElements = this.get('*').push(this); // remove data and listeners glow.events.removeAllListeners( allElements.removeData() ); this.appendTo(tmpDiv); tmpDiv.innerHTML = ''; }; /** @name glow.NodeList#remove @function @description Removes each element from the document If you no longer need the elements, consider using {@link glow.NodeList#destroy destroy} @returns {glow.NodeList} The removed elements @example // take all the links out of a document glow("a").remove(); */ NodeListProto.remove = function() { var parent, node, i = this.length; while (i--) { node = this[i]; if (parent = node.parentNode) { parent.removeChild(node); } } return this; }; /** @name glow.NodeList#empty @function @description Removes the nodes' contents @returns {glow.NodeList} Original nodes @example // remove the contents of all textareas glow("textarea").empty(); */ // TODO: is this shortcut worth doing? NodeListProto.empty = glow.env.ie ? // When you clean an element out using innerHTML it destroys its inner text nodes in IE8 and below // Here's an alternative method for IE: function() { var i = this.length, node, child; while (i--) { node = this[i]; while (child = node.firstChild) { node.removeChild(child); } } return this; } : // method for most browsers function() { var i = this.length; while (i--) { this[i].innerHTML = ''; } return this; } /** @name glow.NodeList#replaceWith @function @description Replace elements with another @param {string | HTMLElement | HTMLElement[] | glow.NodeList} elements Element(s) to insert into the document If there is more than one element in the NodeList, then the given elements replace the first element, clones are appended to the other elements. @returns {glow.NodeList} The replaced elements Call {@link glow.NodeList#destroy destroy} on these if you no longer need them. */ NodeListProto.replaceWith = function(elements) { return this.after(elements).remove(); }; /** @name glow.NodeList#wrap @function @description Wraps the given NodeList with the specified element(s). The given NodeList items will always be placed in the first child element that contains no further elements. Each item in a given NodeList will be wrapped individually. @param {string | HTMLElement | HTMLElement[] | glow.NodeList} wrapper Element to use as a wrapper Strings will be treated as HTML strings if they begin with <, else they'll be treated as a CSS selector. @returns {glow.NodeList} The NodeList with new wrapper parents @example // <span id="mySpan">Hello</span> glow("#mySpan").wrap("<div><p></p></div>"); // Makes: // <div> // <p> // <span id="mySpan">Hello</span> // </p> // </div> */ // get first child element node of an element, otherwise undefined function getFirstChildElm(parent) { for (var child = parent.firstChild; child; child = child.nextSibling) { if (child.nodeType == 1) { return child; } } return undefined; } NodeListProto.wrap = function(wrapper) { // normalise input wrapper = new glow.NodeList(wrapper); // escape if the wraper is non-existant or not an element if (!wrapper[0] || wrapper[0].nodeType != 1) { return this; } var toWrap, toWrapTarget, firstChildElm; for (var i = 0, leni = this.length; i<leni; i++) { toWrap = this[i]; // get target element to insert toWrap in toWrapTarget = wrapper[0]; while (toWrapTarget) { firstChildElm = getFirstChildElm(toWrapTarget); if (!firstChildElm) { break; } toWrapTarget = firstChildElm; } if (toWrap.parentNode) { wrapper.insertBefore(toWrap); } // If wrapping multiple nodes, we need to take a clean copy of the wrapping nodes if (i != leni-1) { wrapper = wrapper.clone(); } toWrapTarget.appendChild(toWrap); } return this; }; /** @name glow.NodeList#unwrap @function @description Removes the parent of each item in the list @returns {glow.NodeList} The now unwrapped elements @example // Before: <div><p><span id="mySpan">Hello</span></p></div> // unwrap the given element glow("#mySpan").unwrap(); // After: <div><span id="mySpan">Hello</span></div> */ NodeListProto.unwrap = function() { var parentToRemove, childNodes, // get unique parents parentsToRemove = this.parent(); for (var i = 0, leni = parentsToRemove.length; i < leni; i++) { parentToRemove = parentsToRemove.slice(i, i+1); // make sure we get all children, including text nodes childNodes = new glow.NodeList( parentToRemove[0].childNodes ); // if the item we're removing has no new parent (i.e. is not in document), then we just remove the child and destroy the old parent if (!parentToRemove[0].parentNode){ childNodes.remove(); parentToRemove.destroy(); } else { childNodes.insertBefore(parentToRemove); parentToRemove.destroy(); } } return this; }; /** @name glow.NodeList#clone @function @description Clones each node in the NodeList, along with data & event listeners @returns {glow.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.get("h1, h2, h3, h4, h5, h6").clone(); */ NodeListProto.clone = function() { var clonedNodeList = this.copy(), allCloneElms = clonedNodeList.get('*').push(clonedNodeList), allElms = this.get('*').push(this); // now copy over the data and events for all cloned elements glow.events._copyDomEvents(allElms, allCloneElms); glow.NodeList._copyData(allElms, allCloneElms); return clonedNodeList; }; /** @name glow.NodeList#copy @function @description Copies each node in the NodeList, excluding data & event listeners @returns {glow.NodeList} Returns a new NodeList containing copies of all the nodes in the NodeList @example // get a copy of all heading elements var myCopies = glow.get("h1, h2, h3, h4, h5, h6").copy(); */ NodeListProto.copy = function() { var nodes = [], i = this.length, clonedNodeList, allCloneElms, eventIdProp = '__eventId' + glow.UID, dataPropName = '_uniqueData' + glow.UID; while (i--) { nodes[i] = this[i].cloneNode(true); } clonedNodeList = new glow.NodeList(nodes); // IE also clones node properties as attributes // we need to get rid of the eventId & dataId if (glow.env.ie) { allCloneElms = clonedNodeList.get('*').push(nodes); i = allCloneElms.length; while (i--) { allCloneElms[i][dataPropName] = allCloneElms[i][eventIdProp] = undefined; } } return clonedNodeList; }; /** @name glow.NodeList#html @function @description Gets / sets HTML content Either gets content of the first element, or sets the content for all elements in the list @param {String} [htmlString] String to set as the HTML of elements If omitted, the html for the first element in the list is returned. @returns {glow.NodeList | string} Returns the original NodeList when setting, or the HTML content when getting. @example // get the html in #footer var footerContents = glow("#footer").html(); @example // set a new footer glow("#footer").html("<strong>Hello World!</strong>"); */ NodeListProto.html = function(htmlString) { // getting if (!arguments.length) { return this[0] ? this[0].innerHTML : ''; } // setting var i = this.length, node; // normalise the string htmlString = htmlString === undefined? '' : String(htmlString); while (i--) { node = this[i]; if (node.nodeType == 1) { try { // this has a habit of failing in IE for some elements node.innerHTML = htmlString; } catch (e) { new glow.NodeList(node).empty().append(htmlString); } } } return this; }; /** @name glow.NodeList#text @function @description Gets / set the text content Either gets content of the first element, or sets the content for all elements in the list @param {String} [text] String to set as the text of elements If omitted, the test for the first element in the list is returned. @returns {glow.NodeList | String} Returns the original NodeList when setting, or the text content when getting. @example // set text var div = glow("<div></div>").text("Fun & games!"); // <div>Func & games!</div> @example // get text var mainHeading = glow('#mainHeading').text(); */ NodeListProto.text = function(textString) { var firstNode = this[0], i = this.length, node; // getting if (!arguments.length) { // get the text by checking a load of properties in priority order return firstNode ? firstNode.textContent || firstNode.innerText || firstNode.nodeValue || '' // nodeValue for comment & text nodes : ''; } // setting // normalise the string textString = textString ? String(textString): ''; this.empty(); while (i--) { node = this[i]; if (node.nodeType == 1) { node.appendChild( document.createTextNode(textString) ); } else { node.nodeValue = textString; } } return this; }; }); Glow.provide(function(glow) { var NodeList = glow.NodeList, NodeListProto = NodeList.prototype, win = window, document = win.document, getComputedStyle = document.defaultView && document.defaultView.getComputedStyle, // regex for toStyleProp dashAlphaRe = /-(\w)/g, // regex for getCssValue isNumberButNotPx = /^-?[\d\.]+(?!px)[%a-z]+$/i, ieOpacityRe = /alpha\(opacity=([^\)]+)\)/, // regex for #css hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/; // replace function for toStyleProp function toStylePropReplace(match, p1) { return p1.toUpperCase(); } /** @private @function @description Converts a css property name into its javascript name. Such as "background-color" to "backgroundColor". @param {string} prop CSS Property name. @returns {string} */ function toStyleProp(prop) { if (prop == 'float') { return glow.env.ie ? 'styleFloat' : 'cssFloat'; } return prop.replace(dashAlphaRe, toStylePropReplace); } /** @private @function @description Get a total value of multiple CSS properties @param {HTMLElement} elm @param {string[]} props CSS properties to get the total value of @returns {number} */ function getTotalCssValue(elm, props) { var total = 0, i = props.length; while (i--) { total += parseFloatFunc( getCssValue( elm, props[i] ) ) || 0; } return total; } /** @private @function @description Get a computed css property @param {HTMLElement} elm @param {string} prop CSS property to get the value of @returns {string} */ function getCssValue(elm, prop) { var defaultView = elm.ownerDocument.defaultView, computedStyle, r, currentStyle, oldDisplay, match; if (getComputedStyle) { // the W3 way computedStyle = defaultView.getComputedStyle(elm, null); // http://bugs.webkit.org/show_bug.cgi?id=13343 // Webkit fails to get margin-right for rendered elements. // margin-right is measured from the right of the element to the right of the parent if (glow.env.webkit && prop === 'margin-right') { oldDisplay = elm.style.display; elm.style.display = 'none'; r = computedStyle[prop]; elm.style.display = oldDisplay; } else { r = computedStyle.getPropertyValue(prop); } } else if (currentStyle = elm.currentStyle) { // the IE<9 way if (prop === 'opacity') { // opacity, the IE way match = ieOpacityRe.exec(currentStyle.filter); return match ? String(parseInt(match[1], 10) / 100) || '1' : '1'; } // catch border-*-width. IE gets this wrong if the border style is none else if ( prop.indexOf('border') === 0 && prop.slice(-5) === 'width' && getCssValue(elm, 'border-style') === 'none') { return '0px'; } r = currentStyle[ toStyleProp(prop) ]; // font-size gives us incorrect values when put through getPixelValue, avoid if (isNumberButNotPx.test(r) && prop != 'font-size') { r = getPixelValue( elm, r, prop.indexOf('height') >= 0 || prop.indexOf('top') >= 0 ) + 'px'; } } // post-process return value if (prop === 'opacity') { r = r || '1'; } else if (prop.indexOf('color') != -1) { //deal with colour values r = NodeList._parseColor(r).toString(); } return r; } // vars used in _parseColor var mathRound = Math.round, parseIntFunc = parseInt, parseFloatFunc = parseFloat, htmlColorNames = { black: 0x000000, silver: 0xc0c0c0, gray: 0x808080, white: 0xffffff, maroon: 0x800000, red: 0xff0000, purple: 0x800080, fuchsia: 0xff00ff, green: 0x008000, lime: 0x00ff00, olive: 0x808000, yellow: 0xffff00, navy: 0x000080, blue: 0x0000ff, teal: 0x008080, aqua: 0x00ffff, orange: 0xffa500 }, // match a string like rgba(10%, 10%, 10%, 0.5) where the % and alpha parts are optional colorRegex = /^rgba?\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)(?:,\s*([\d\.]+))?/i, transColorRegex = /^(transparent|rgba\(0, ?0, ?0, ?0\))$/, wordCharRegex = /\w/g; /** @name glow.NodeList._parseColor @private @function @description Convert a CSS colour string into a normalised format @returns {string} String in format rgb(0, 0, 0) Returned string also has r, g & b number properties */ NodeList._parseColor = function (val) { if ( transColorRegex.test(val) ) { return 'rgba(0, 0, 0, 0)'; } var match, //tmp regex match holder r, g, b, a, //final colour vals hex; //tmp hex holder if ( match = colorRegex.exec(val) ) { //rgb() format, cater for percentages r = match[2] ? mathRound( parseFloatFunc(match[1]) * 2.55 ) : parseIntFunc(match[1]); g = match[4] ? mathRound( parseFloatFunc(match[3]) * 2.55 ) : parseIntFunc(match[3]); b = match[6] ? mathRound( parseFloatFunc(match[5]) * 2.55 ) : parseIntFunc(match[5]); a = parseFloatFunc( match[7] || '1' ); } else { if (typeof val == 'number') { hex = val; } else if (val.charAt(0) == '#') { if (val.length === 4) { //deal with #fff shortcut val = val.replace(wordCharRegex, '$&$&'); } hex = parseIntFunc(val.slice(1), 16); } else { hex = htmlColorNames[val]; } r = (hex) >> 16; g = (hex & 0x00ff00) >> 8; b = (hex & 0x0000ff); a = 1; } val = new String('rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'); val.r = r; val.g = g; val.b = b; val.a = a; return val; } // vars for generateWidthAndHeight var horizontalBorderPadding = [ 'border-left-width', 'border-right-width', 'padding-left', 'padding-right' ], verticalBorderPadding = [ 'border-top-width', 'border-bottom-width', 'padding-top', 'padding-bottom' ]; /** @private @function @description Get width or height of an element width/height. @param {HTMLElement} elm Element to measure. @param {string} 'Width' or 'Height'. */ function getDimension(elm, cssProp) { // exit if there's no element, or it isn't an Element, window or document if ( !elm || elm.nodeType === 3 || elm.nodeType === 8 ) { return 0; } var r, document = elm.ownerDocument || elm.document || elm, docElm = document.documentElement, docBody = document.body, docElmOrBody = glow.env.standardsMode ? docElm : docBody, isWidth = (cssProp == 'Width'), cssBorderPadding; if (elm.window) { // is window r = glow.env.webkit ? (isWidth ? docBody.clientWidth : elm.innerHeight) : /* else */ docElmOrBody['client' + cssProp]; } else if (elm.getElementById) { // is document // we previously checked offsetWidth & clientWidth here // but they returned values too large in IE6 r = Math.max( docBody['scroll' + cssProp], docElm['scroll' + cssProp] ) } else { // get an array of css borders & padding cssBorderPadding = isWidth ? horizontalBorderPadding : verticalBorderPadding; r = elm['offset' + cssProp] - getTotalCssValue(elm, cssBorderPadding); } return r; } /** @private @function @description Converts a relative value into an absolute pixel value. Only works in IE with Dimension value (not stuff like relative font-size). Based on some Dean Edwards' code @param {HTMLElement} element Used to calculate relative values @param {string} value Relative value @param {boolean} useYAxis Calulate relative values to the y axis rather than x @returns number */ function getPixelValue(element, value, useYAxis) { // Remember the original values var axisPos = useYAxis ? 'top' : 'left', axisPosUpper = useYAxis ? 'Top' : 'Left', elmStyle = element.style, positionVal = elmStyle[axisPos], runtimePositionVal = element.runtimeStyle[axisPos], r; // copy to the runtime type to prevent changes to the display element.runtimeStyle[axisPos] = element.currentStyle[axisPos]; // set value to left / top elmStyle[axisPos] = value; // get the pixel value r = elmStyle['pixel' + axisPosUpper]; // revert values elmStyle[axisPos] = positionVal; element.runtimeStyle[axisPos] = runtimePositionVal; return r; } /** @name glow.NodeList#css @function @description Get / set a CSS property value @param {string | Object} property The CSS property name, or object of property-value pairs to set @param {string | number} [value] The value to apply Number values will be treated as 'px' unless the CSS property accepts a unitless value. If value is omitted, the value for the given property will be returned @returns {glow.NodeList | string} Returns the NodeList when setting value, or the CSS value when getting values. CSS values are strings. For instance, "height" will return "25px" for an element 25 pixels high. You can use parseInt to convert these values. @example // get value from first node glow('#subNav').css('display'); @example // set left padding to 10px on all nodes glow('#subNav li').css('padding-left', '2em'); @example // where appropriate, px is assumed when no unit is passed glow('#mainPromo').css('margin-top', 300); @example // set multiple CSS values at once // NOTE: Property names containing a hyphen such as font-weight must be quoted glow('#myDiv').css({ 'font-weight': 'bold', 'padding' : '10px', 'color' : '#00cc99' }); */ NodeListProto.css = function(prop, val) { var thisStyle, i = this.length, styleProp, style, firstItem = this[0]; if (prop.constructor === Object) { // set multiple values for (style in prop) { this.css( style, prop[style] ); } return this; } else if (val !== undefined) { //set one CSS value styleProp = toStyleProp(prop); while (i--) { if (this[i].nodeType === 1) { thisStyle = this[i].style; if ( !isNaN(val) && hasUnits.test(prop) ) { val += 'px'; } if (prop === 'opacity' && glow.env.ie) { val = parseFloatFunc(val); //in IE the element needs hasLayout for opacity to work thisStyle.zoom = '1'; thisStyle.filter = (val !== 1) ? 'alpha(opacity=' + mathRound(val * 100) + ')' : ''; } else { thisStyle[styleProp] = val; } } } return this; } else { //getting stuff if (prop === 'width' || prop === 'height') { return this[prop]() + 'px'; } return (firstItem && firstItem.nodeType === 1) ? getCssValue(firstItem, prop) : ''; } }; /** @name glow.NodeList#height @function @description Gets / set element height 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 for each element in the list If ommited, the height of the first element is returned @returns {glow.NodeList | number} Height of first element, or original NodeList when setting heights. @example // get the height of #myDiv glow("#myDiv").height(); @example // set the height of list items in #myList to 200 pixels glow("#myList > li").height(200); @example // get the height of the document glow(document).height(); @example // get the height of the window glow(win).height(); */ NodeListProto.height = function(height) { if (height === undefined) { return getDimension(this[0], 'Height'); } return this.css('height', height); }; /** @name glow.NodeList#width @function @description Gets / set element width 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 for each element in the list If ommited, the width of the first element is returned @returns {glow.NodeList | number} width of first element, or original NodeList when setting widths. @example // get the width of #myDiv glow("#myDiv").width(); @example // set the width of list items in #myList to 200 pixels glow("#myList > li").width(200); @example // get the width of the document glow(document).width(); @example // get the width of the window glow(window).width(); */ NodeListProto.width = function(width) { if (width === undefined) { return getDimension(this[0], 'Width'); } return this.css('width', width); }; /** @name glow.NodeList#scrollLeft @function @description Gets/sets the number of pixels the element has scrolled horizontally To get/set the scroll position of the window, use this method on a nodelist containing the window object. @param {Number} [val] New left scroll position Omit this to get the current scroll position @returns {glow.NodeList | number} Current scrollLeft value, or NodeList when setting scroll position. @example // get the scroll left value of #myDiv var scrollPos = glow('#myDiv').scrollLeft(); // scrollPos is a number, eg: 45 @example // set the scroll left value of #myDiv to 20 glow('#myDiv').scrollLeft(20); @example // get the scrollLeft of the window glow(window).scrollLeft(); // scrollPos is a number, eg: 45 */ NodeListProto.scrollLeft = function(val) { return scrollOffset(this, true, val); }; /** @name glow.NodeList#scrollTop @function @description Gets/sets the number of pixels the element has scrolled vertically To get/set the scroll position of the window, use this method on a nodelist containing the window object. @param {Number} [val] New top scroll position Omit this to get the current scroll position @returns {glow.NodeList | number} Current scrollTop value, or NodeList when setting scroll position. @example // get the scroll top value of #myDiv var scrollPos = glow("#myDiv").scrollTop(); // scrollPos is a number, eg: 45 @example // set the scroll top value of #myDiv to 20 glow("#myDiv").scrollTop(20); @example // get the scrollTop of the window glow(window).scrollTop(); // scrollPos is a number, eg: 45 */ NodeListProto.scrollTop = function(val) { return scrollOffset(this, false, val); }; /** @name glow.dom-getScrollOffset @private @description Get the scrollTop / scrollLeft of a particular element @param {Element} elm Element (or window object) to get the scroll position of @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top */ function getScrollOffset(elm, isLeft) { var r, scrollProp = 'scroll' + (isLeft ? 'Left' : 'Top'); // are we dealing with the window object or the document object? if (elm.window) { // get the scroll of the documentElement or the pageX/Yoffset // - some browsers use one but not the other r = elm.document.documentElement[scrollProp] || (isLeft ? elm.pageXOffset : elm.pageYOffset) || 0; } else { r = elm[scrollProp]; } return r; } /** @name glow.dom-setScrollOffset @private @description Set the scrollTop / scrollLeft of a particular element @param {Element} elm Element (or window object) to get the scroll position of @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top @param {Number} newVal New scroll value */ function setScrollOffset(elm, isLeft, newVal) { // are we dealing with the window object or the document object? if (elm.window) { // we need to get whichever value we're not setting elm.scrollTo( isLeft ? newVal : getScrollOffset(elm, true), !isLeft ? newVal : getScrollOffset(elm, false) ); } else { elm['scroll' + (isLeft ? 'Left' : 'Top')] = newVal; } } /** @name glow.dom-scrollOffset @private @description Set/get the scrollTop / scrollLeft of a NodeList @param {glow.dom.NodeList} nodeList Elements to get / set the position of @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top @param {Number} [val] Val to set (if not provided, we'll get the value) @returns NodeList for sets, Number for gets */ function scrollOffset(nodeList, isLeft, val) { var i = nodeList.length; if (val !== undefined) { while (i--) { setScrollOffset(nodeList[i], isLeft, val); } return nodeList; } else { return getScrollOffset(nodeList[0], isLeft); } } /** @name glow.NodeList#hide @function @description Hides all items in the NodeList. @returns {glow.NodeList} @example // Hides all list items within #myList glow("#myList li").hide(); */ NodeListProto.hide = function() { return this.css('display', 'none').css('visibility', 'hidden'); }; /** @name glow.NodeList#show @function @description Shows all hidden items in the NodeList. @returns {glow.NodeList} @example // Show element with ID myDiv glow("#myDiv").show(); @example // Show all list items within #myList glow("#myList li").show(); */ NodeListProto.show = function() { var i = this.length, currItem, itemStyle; while (i--) { /* Create a NodeList for the current item */ currItem = new glow.NodeList(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.NodeList#offset @function @description Gets the offset from the top left of the document. If the NodeList contains multiple items, the offset of the first item is returned. @returns {Object} Returns an object with "top" & "left" properties in pixels @example glow("#myDiv").offset().top */ NodeListProto.offset = function() { if ( !this[0] || this[0].nodeType !== 1) { return {top: 0, left: 0}; } // http://weblogs.asp.net/bleroy/archive/2008/01/29/getting-absolute-coordinates-from-a-dom-element.aspx - great bit of research, most bugfixes identified here (and also jquery trac) var elm = this[0], doc = elm.ownerDocument, docElm = doc.documentElement, window = doc.defaultView || doc.parentWindow, docScrollPos = { x: getScrollOffset(window, true), y: getScrollOffset(window, false) }; //this is simple(r) if we can use 'getBoundingClientRect' // Sorry but the sooper dooper simple(r) way is not accurate in Safari 4 if (!glow.env.webkit && elm.getBoundingClientRect) { var rect = elm.getBoundingClientRect(); return { top: Math.floor(rect.top) /* getBoundingClientRect is realive to top left of the viewport, so we need to sort out scrolling offset */ + docScrollPos.y /* IE adds the html element's border to the value. We can deduct this value using client(Top|Left). However, if the user has done html{border:0} clientTop will still report a 2px border in IE quirksmode so offset will be off by 2. Hopefully this is an edge case but we may have to revisit this in future */ - docElm.clientTop, left: Math.floor(rect.left) //see above for docs on all this stuff + docScrollPos.x - docElm.clientLeft }; } else { //damnit, let's go the long way around var top = elm.offsetTop, left = elm.offsetLeft, originalElm = elm, nodeNameLower, docBody = document.body, //does the parent chain contain a position:fixed element involvesFixedElement = false, offsetParentBeforeBody = elm; //add up all the offset positions while (elm = elm.offsetParent) { left += elm.offsetLeft; top += elm.offsetTop; //if css position is fixed, we need to add in the scroll offset too, catch it here if (getCssValue(elm, 'position') == 'fixed') { involvesFixedElement = true; } //gecko & webkit (safari 3) don't add on the border for positioned items if (glow.env.gecko || glow.env.webkit > 500) { left += parseInt(getCssValue(elm, 'border-left-width')) || 0; top += parseInt(getCssValue(elm, 'border-top-width')) || 0; } //we need the offset parent (before body) later if (elm.nodeName.toLowerCase() != 'body') { offsetParentBeforeBody = elm; } } //deduct all the scroll offsets elm = originalElm; while ((elm = elm.parentNode) && (elm != docBody) && (elm != docElm)) { left -= elm.scrollLeft; top -= elm.scrollTop; //FIXES //gecko doesn't add the border of contained elements to the offset (overflow!=visible) if (glow.env.gecko && getCssValue(elm, 'overflow') != 'visible') { left += parseInt(getCssValue(elm, 'border-left-width')); top += parseInt(getCssValue(elm, 'border-top-width')); } } //if we found a fixed position element we need to add the scroll offsets if (involvesFixedElement) { left += docScrollPos.x; top += docScrollPos.y; } //FIXES // Gecko - non-absolutely positioned elements that are direct children of body get the body offset counted twice if ( (glow.env.gecko && getCssValue(offsetParentBeforeBody, 'position') != 'absolute') ) { left -= docBody.offsetLeft; top -= docBody.offsetTop; } return {left:left, top:top}; } }; /** @name glow.NodeList#position @function @description Get the top & left position of an element relative to its positioned parent This is useful if you want to make a position:static element position:absolute and retain the original position of the element @returns {Object} An object with 'top' and 'left' number properties @example // get the top distance from the positioned parent glow("#elm").position().top */ NodeListProto.position = function() { var positionedParent = new glow.NodeList( getPositionedParent(this[0]) ), hasPositionedParent = !!positionedParent[0], // element margins to deduct marginLeft = parseInt( this.css('margin-left') ) || 0, marginTop = parseInt( this.css('margin-top') ) || 0, // offset parent borders to deduct, set to zero if there's no positioned parent positionedParentBorderLeft = ( hasPositionedParent && parseInt( positionedParent.css('border-left-width') ) ) || 0, positionedParentBorderTop = ( hasPositionedParent && parseInt( positionedParent.css('border-top-width') ) ) || 0, // element offsets elOffset = this.offset(), positionedParentOffset = hasPositionedParent ? positionedParent.offset() : {top: 0, left: 0}; return { left: elOffset.left - positionedParentOffset.left - marginLeft - positionedParentBorderLeft, top: elOffset.top - positionedParentOffset.top - marginTop - positionedParentBorderTop } }; /* Get the 'real' positioned parent for an element, otherwise return null. */ function getPositionedParent(elm) { var offsetParent = elm.offsetParent, docElm = document.documentElement; // get the real positioned parent // IE places elements with hasLayout in the offsetParent chain even if they're position:static // Also, <body> and <html> can appear in the offsetParent chain, but we don't want to return them if they're position:static while (offsetParent && new glow.NodeList(offsetParent).css('position') == 'static') { offsetParent = offsetParent.offsetParent; } // sometimes the <html> element doesn't appear in the offsetParent chain, even if it has position:relative if (!offsetParent && new glow.NodeList(docElm).css('position') != 'static') { offsetParent = docElm; } return offsetParent || null; } }); Glow.provide(function(glow) { var NodeListProto = glow.NodeList.prototype, document = window.document, undefined, keyEventNames = ' keypress keydown keyup '; /** @name glow.NodeList#on @function @description Listen for an event. This will listen for a particular event on each dom node in the NodeList. If you're listening to many children of a particular item, you may get better performance from {@link glow.NodeList#delegate}. @param {String} eventName Name of the event to listen for. This can be any regular DOM event ('click', 'mouseover' etc) or a special event of NodeList. @param {Function} callback Function to call when the event fires. The callback is passed a single event object. The type of this object is {@link glow.DomEvent} unless otherwise stated. @param {Object} [thisVal] Value of 'this' within the callback. By default, this is the dom node being listened to. @returns this @example glow.get('#testLink').on('click', function(domEvent) { // do stuff // if you want to cancel the default action (following the link)... return false; }); */ NodeListProto.on = function(eventName, callback, thisVal) { var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); // add standard glow listeners glow.events.addListeners(this, eventName, callback, thisVal); // add the bridge functions if needed if (isKeyEvent) { glow.events._addKeyListener(this); } else { // assume it's a DOM event glow.events._addDomEventListener(this, eventName); } return this; } /** @name glow.NodeList#detach @function @description detach a listener from elements This will detach the listener from each dom node in the NodeList. @param {String} eventName Name of the event to detach the listener from @param {Function} callback Listener callback to detach @returns this @example function clickListener(domEvent) { // ... } // adding listeners glow.get('a').on('click', clickListener); // removing listeners glow.get('a').detach('click', clickListener); */ NodeListProto.detach = function(eventName, callback) { var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); // remove standard glow listeners glow.events.removeListeners(this, eventName, callback); // remove the bridge functions if needed if (isKeyEvent) { glow.events._removeKeyListener(this); } else { // assume it's a DOM event glow.events._removeDomEventListener(this, eventName); } return this; } /** @name glow.NodeList#delegate @function @description Listen for an event occurring on child elements matching a selector. 'delegate' will catch events which occur on matching items created after the listener was added. @param {String} eventName Name of the event to listen for. This can be any regular DOM event ('click', 'mouseover' etc) or a special event of NodeList. @param {String} selector CSS selector of child elements to listen for events on For example, if you were wanting to hear events from links, this would be 'a'. @param {Function} callback Function to call when the event fires. The callback is passed a single event object. The type of this object is {@link glow.DomEvent} unless otherwise stated. @param {Object} [thisVal] Value of 'this' within the callback. By default, this is the dom node matched by 'selector'. @returns this @example // Using 'on' to catch clicks on links in a list glow.get('#nav a').on('click', function() { // do stuff }); // The above adds a listener to each link, any links created later // will not have this listener, so we won't hear about them. // Using 'delegate' to catch clicks on links in a list glow.get('#nav').delegate('click', 'a', function() { // do stuff }); // The above only adds one listener to #nav which tracks clicks // to any links within. This includes elements created after 'delegate' // was called. @example // Using delegate to change class names on table rows so :hover // behaviour can be emulated in IE6 glow.get('#contactData').delegate('mouseover', 'tr', function() { glow.get(this).addClass('hover'); }); glow.get('#contactData').delegate('mouseout', 'tr', function() { glow.get(this).removeClass('hover'); }); */ NodeListProto.delegate = function(eventName, selector, callback, thisVal) { var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); // add standard glow listeners glow.events.addListeners(this, eventName + '/' + selector, callback, thisVal); // register delegates glow.events._registerDelegate(this, eventName, selector); // add the bridge functions if needed if (isKeyEvent) { glow.events._addKeyListener(this); } else { // assume it's a DOM event glow.events._addDomEventListener(this, eventName); } return this; } /** @name glow.NodeList#detachDelegate @function @description detach a delegated listener from elements This will detach the listener from each dom node in the NodeList. @param {String} eventName Name of the event to detach the listener from @param {String} selector CSS selector of child elements the listener is listening to @param {Function} callback Listener callback to detach @returns this @example function clickListener(domEvent) { // ... } // adding listeners glow.get('#nav').delegate('click', 'a', clickListener); // removing listeners glow.get('#nav').detachDelegate('click', 'a', clickListener); */ NodeListProto.detachDelegate = function(eventName, selector, callback, thisVal) { var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); // remove standard glow listeners glow.events.removeListeners(this, eventName + '/' + selector, callback); // unregister delegates glow.events._unregisterDelegate(this, eventName, selector); // remove the bridge functions if needed if (isKeyEvent) { glow.events._removeKeyListener(this); } else { // assume it's a DOM event glow.events._removeDomEventListener(this, eventName); } return this; } /** @name glow.NodeList#fire @function @param {String} eventName Name of the event to fire @param {glow.events.Event} [event] Event object to pass into listeners. You can provide a simple object of key / value pairs which will be added as properties of a glow.events.Event instance. @description Fire an event on dom nodes within the NodeList Note, this will only trigger event listeners to be called, it won't for example, move the mouse or click a link on the page. @returns glow.events.Event @example glow.get('#testLink').on('click', function() { alert('Link clicked!'); }); // The following causes 'Link clicked!' to be alerted, but doesn't // cause the browser to follow the link glow.get('#testLink').fire('click'); */ NodeListProto.fire = function(eventName, event) { return glow.events.fire(this, eventName, event); } /** @name glow.NodeList#event:mouseenter @event @description Fires when the mouse enters the element specifically, does not bubble @param {glow.events.DomEvent} event Event Object */ /** @name glow.NodeList#event:mouseleave @event @description Fires when the mouse leaves the element specifically, does not bubble @param {glow.events.DomEvent} event Event Object */ /** @name glow.NodeList#event:keydown @event @description Fires when the user presses a key Only fires if the element has focus, listen for this event on the document to catch all keydowns. This event related to the user pressing a key on the keyboard, if you're more concerned about the character entered, see the {@link glow.NodeList#event:keypress keypress} event. keydown will only fire once, when the user presses the key. The order of events is keydown, keypress*, keyup. keypress may fire many times if the user holds the key down. @param {glow.events.KeyboardEvent} event Event Object */ /** @name glow.NodeList#event:keypress @event @description Fires when a key's command executes. For instance, if you hold down a key, it's action will occur many times. This event will fire on each action. This event is useful when you want to react to keyboard repeating, or to detect when a character is entered into a field. The order of events is keydown, keypress*, keyup. keypress may fire many times if the user holds the key down. @param {glow.events.KeyboardEvent} event Event Object */ /** @name glow.NodeList#event:keyup @event @description Fires when the user releases a key Only fires if the element has focus, listen for this event on the document to catch all keyups. This event related to the user pressing a key on the keyboard, if you're more concerned about the character entered, see the {@link glow.NodeList#event:keypress keypress} event. The order of events is keydown, keypress*, keyup. keypress may fire many times if the user holds the key down. @param {glow.events.KeyboardEvent} event Event Object */ }); Glow.provide(function(glow) { var NodeList = glow.NodeList, NodeListProto = NodeList.prototype, undefined, parseFloat = window.parseFloat, // used to detect which CSS properties require units requiresUnitsRe = /width|height|top$|bottom$|left$|right$|spacing$|indent$|fontSize/i, // which simple CSS values cannot be negative noNegativeValsRe = /width|height|padding|opacity/, getUnit = /\D+$/, usesYAxis = /height|top/; // TODO: get this from appearence.js function toStyleProp(prop) { if (prop == 'float') { return glow.env.ie ? 'styleFloat' : 'cssFloat'; } return prop.replace(/-(\w)/g, function(match, p1) { return p1.toUpperCase(); }); } /** @private @function @param {nodelist} element @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. */ var testElement = glow('<div style="position:absolute;visibility:hidden;border:0;margin:0;padding:0"></div>'); function convertCssUnit(element, value, toUnit, axis) { var elmStyle = testElement[0].style, axisProp = (axis === 'x') ? 'width' : 'height', startPixelValue, toUnitPixelValue; startPixelValue = testElement.css(axisProp, value).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; } /** @private @function @description Animate a colour value */ function animateColor(anim, stylePropName, from, to) { to = NodeList._parseColor(to); to = [to.r, to.g, to.b]; from = NodeList._parseColor(from); from = [from.r, from.g, from.b]; anim.prop(stylePropName, { // we only need a template if we have units template: 'rgb(?,?,?)', from: from, to: to, round: true, min: 0, max: 255 }); } /** @private @function @description Animate opacity in IE's 'special' way */ function animateIeOpacity(elm, anim, from, to) { to = parseFloat(to) * 100; from = parseFloat(from) * 100; // give the element 'hasLayout' elm.style.zoom = 1; anim.prop('filter', { // we only need a template if we have units template: 'alpha(opacity=?)', from: from, to: to, allowNegative: false }); } /** @private @function @description Scroll positions */ function animateScroll(elm, anim, from, to, scrollTopOrLeft) { var diff; to = parseFloat(to); from = parseFloat(from); elm = glow(elm); // auto-get start value if there isn't one if ( isNaN(from) ) { from = elm[scrollTopOrLeft](); } diff = to - from; anim.on('frame', function() { elm[scrollTopOrLeft]( diff * this.value + from ); }); } /** @private @function @description Animate simple values This is a set of space-separated numbers (42) or numbers + unit (42em) Units can be mixed */ function animateValues(element, anim, stylePropName, from, to) { var toUnit, fromUnit, round = [], template = '', requiresUnits = requiresUnitsRe.test(stylePropName), minZero = noNegativeValsRe.test(stylePropName); from = String(from).split(' '); to = String(to).split(' '); for (var i = 0, leni = to.length; i < leni; i++) { toUnit = ( getUnit.exec( to[i] ) || [''] )[0]; fromUnit = ( getUnit.exec( from[i] ) || [''] )[0]; // create initial units if required if (requiresUnits) { toUnit = toUnit || 'px'; fromUnit = fromUnit || 'px'; } round[i] = (toUnit === 'px'); // make the 'from' unit the same as the 'to' unit if (toUnit !== fromUnit) { from = convertCssUnit( element, from, toUnit, usesYAxis.test(stylePropName) ? 'y' : 'x' ); } template += ' ?' + toUnit; from[i] = parseFloat( from[i] ); to[i] = parseFloat( to[i] ); } anim.prop(stylePropName, { template: template, from: from, to: to, round: round, min: minZero ? 0 : undefined }); } /** @private @function @description Makes an animtion adjust CSS values over time */ function addCssAnim(nodeList, anim, properties) { var to, from, i, property, propertyIsArray, stylePropName; for (var propName in properties) { property = properties[propName]; propertyIsArray = property.push; stylePropName = toStyleProp(propName); to = propertyIsArray ? property[1] : property; i = nodeList.length; // do this for each nodelist item while (i--) { // deal with special values, scrollTop and scrollLeft which aren't really CSS // This is the only animation that can work on the window object too if ( propName.indexOf('scroll') === 0 && (nodeList[i].scrollTo || nodeList[i].scrollTop !== undefined) ) { animateScroll(nodeList[i], anim, propertyIsArray ? property[0] : undefined, to, propName); continue; } // skip non-element nodes if ( nodeList[i].nodeType !== 1 ) { continue; } // set new target anim.target( nodeList[i].style ); from = propertyIsArray ? property[0] : nodeList.item(i).css(propName); // deal with colour values if ( propName.indexOf('color') !== -1 ) { animateColor(anim, stylePropName, from, to); } // nice special case for IE else if (glow.env.ie && stylePropName === 'opacity') { animateIeOpacity(nodeList[i], anim, from, to); } // assume we're dealing with simple numbers, or numbers + unit // eg "5px", "5px 2em", "10px 5px 1em 4px" else { animateValues(nodeList[i], anim, stylePropName, from, to); } } } } /** @name glow.NodeList#anim @function @description Animate properties of elements All elements in the NodeList are animated All CSS values which are simple numbers (with optional unit) are supported. Eg: width, margin-top, left All CSS values which are space-separated values are supported (eg background-position, margin, padding), although a 'from' value must be provided for short-hand properties like 'margin'. All CSS colour values are supported. Eg: color, background-color. 'scrollLeft' and 'scrollTop' can be animated for elements and the window object. Other properties, including CSS properties with limited support, can be animated using {@link glow.anim.Anim#prop}. @param {number} duration Length of the animation in seconds. @param {Object} properties Properties to animate. This is an object where the key is the CSS property and the value is the value to animate to. The value can also be an array, where the first item is the value to animate from, and the second is the value to animate to. Numerical values will be treated as 'px' if the property requires units. @param {Object} [opts] Options object @param {function|string} [opts.tween='easeBoth'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops). This will free any DOM references the animation may have created. Once the animation is destroyed, it cannot be started again. @param {boolean} [opts.loop=true] Loop the animation. @param {boolean} [opts.startNow=true] Start the animation straight away? Animations can be started by calling {@link glow.anim.Anim#start} @returns {glow.anim.Anim} @example // change the nav's background colour to white and the top position // to 20px over a duration of 3 seconds glow('#nav').anim(3, { 'background-color': '#fff', 'top': 20 }); @example // Fade an element out and alert 'done' when complete glow('#nav').anim(3, { 'opacity': 0 }).on('complete', function() { alert('done!'); }); @example // Scroll the window to the top glow(window).anim(2, { scrollTop: 0 }); @see {@link glow.NodeList#queueAnim} - Queue an animation to run after the current anim @see {@link glow.NodeList#fadeIn} - Shortcut to fade elements in @see {@link glow.NodeList#fadeOut} - Shortcut to fade elements out @see {@link glow.NodeList#fadeToggle} - Shortcut to toggle the fade of an element @see {@link glow.NodeList#slideOpen} - Shortcut to slide an element open @see {@link glow.NodeList#slideShut} - Shortcut to slide an element shut @see {@link glow.NodeList#slideToggle} - Shortcut to toggle an element open / shut */ NodeListProto.anim = function(duration, properties, opts) { /*!debug*/ if (arguments.length < 2 || arguments.length > 3) { glow.debug.warn('[wrong count] glow.NodeList#anim expects 2 or 3 arguments, not ' + arguments.length + '.'); } if (typeof duration !== 'number') { glow.debug.warn('[wrong type] glow.NodeList#anim expects number as "duration" argument, not ' + typeof duration + '.'); } if (typeof properties !== 'object') { glow.debug.warn('[wrong type] glow.NodeList#anim expects object as "properties" argument, not ' + typeof properties + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.NodeList#anim expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ opts = opts || {}; var anim = new glow.anim.Anim(duration, opts); addCssAnim(this, anim, properties); // auto start !(opts.startNow === false) && anim.start(); return anim; }; /** @private @function @description Used as a listener for an animations's stop event. 'this' is a nodelist of the animating item Set in queueAnim */ function queueAnimStop() { this.removeData('glow_lastQueuedAnim').removeData('glow_currentAnim'); } /** @name glow.NodeList#queueAnim @function @description Queue an animation to run after the current animation All elements in the NodeList are animated This supports the same CSS properties as {@link glow.NodeList#anim}, but the animation is not started until the previous animation (added via {@link glow.NodeList#queueAnim queueAnim}) on that element ends. If there are no queued animations on the element, the animation starts straight away. @param {number} duration Length of the animation in seconds. @param {Object} Properties to animate. This is an object where the key is the CSS property and the value is the value to animate to. The value can also be an array, where the first item is the value to animate from, and the second is the value to animate to. Numerical values will be treated as 'px' if the property requires units. @param {Object} [opts] Options object @param {function|string} [opts.tween='easeBoth'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops). This will free any DOM references the animation may have created. Once the animation is destroyed, it cannot be started again. @returns {glow.NodeList} @example // change a nav item's background colour from white to yellow // when the mouse is over it, and back again when the mouse // exits. glow('#nav').delegate('mouseenter', 'li', function() { glow(this).queueAnim(0.5, { 'background-color': 'yellow' }); }).delegate('mouseleave', 'li', function() { glow(this).queueAnim(0.5, { 'background-color': 'white' }); }); @example // adding listeners to a queued anim glow('#elementToAnimate').queueAnim(0.5, { height: 0 }).lastQueuedAnim().on('complete', function() { alert('Animation complete!'); }); @example // stopping and clearing current animation queue. // The next animation created via queueAnim will start // immediately glow('#elementToAnimate').curentAnim().stop(); @see {@link glow.NodeList#fadeIn} - Shortcut to fade elements in @see {@link glow.NodeList#fadeOut} - Shortcut to fade elements out @see {@link glow.NodeList#fadeToggle} - Shortcut to toggle the fade of an element @see {@link glow.NodeList#slideOpen} - Shortcut to slide an element open @see {@link glow.NodeList#slideShut} - Shortcut to slide an element shut @see {@link glow.NodeList#slideToggle} - Shortcut to toggle an element open / shut */ NodeListProto.queueAnim = function(duration, properties, opts) { /*!debug*/ if (arguments.length < 2 || arguments.length > 3) { glow.debug.warn('[wrong count] glow.NodeList#queueAnim expects 2 or 3 arguments, not ' + arguments.length + '.'); } if (typeof duration !== 'number') { glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects number as "duration" argument, not ' + typeof duration + '.'); } if (typeof properties !== 'object') { glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects object as "properties" argument, not ' + typeof properties + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ opts = opts || {}; var i = this.length, item, lastQueuedAnim, anim, startNextAnim; // we don't want animations starting now opts.startNow = false; while (i--) { item = this.item(i); if (item[0].nodeType !== 1) { continue; } lastQueuedAnim = item.data('glow_lastQueuedAnim'); // add a listener to 'stop', to clear the queue anim = new glow.anim.Anim(duration, opts).on('stop', queueAnimStop, item); item.data('glow_lastQueuedAnim', anim); // closure some properties (function(item, properties, anim) { startNextAnim = function() { addCssAnim(item, anim, properties); anim.start(); item.data('glow_currentAnim', anim); } })(item, properties, anim); // do we start the anim now, or after the next one? if (lastQueuedAnim) { lastQueuedAnim.on('complete', startNextAnim); } else { startNextAnim(); } } return this; }; /** @name glow.NodeList#currentAnim @function @description Get the currently playing animation added via {@link glow.NodeList#queueAnim queueAnim} for this element If no animation is currently playing, an empty animation is returned. This means you don't need to check to see if the item is defined before calling methods on it. This method acts on the first item in the NodeList. @returns {glow.anim.Anim} @example // stopping and clearing current animation queue. // The next animation created via queueAnim will start // immediately glow('#elementToAnimate').curentAnim().stop(); @example // Is the element animating as part of queueAnim? glow('#elementToAnimate').curentAnim().playing; // true/false */ NodeListProto.currentAnim = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.NodeList#currentAnim expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ return this.data('glow_currentAnim') || new glow.anim.Anim(0); } /** @name glow.NodeList#lastQueuedAnim @function @description Get the last animation added via {@link glow.NodeList#queueAnim queueAnim} for this element If no animation has been added, an empty animation is returned. This means you don't need to check to see if the item is defined before calling methods on it. This method acts on the first item in the NodeList. @returns {glow.anim.Anim} */ NodeListProto.lastQueuedAnim = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.NodeList#lastQueuedAnim expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ return this.data('glow_lastQueuedAnim') || new glow.anim.Anim(0); } /** @private @function @description This function generates the various anim shortcut functions */ function animShortcut(animName, animReverseName, animPropsFunc, defaultTween, onComplete, additionalFunc) { return function(duration, opts) { /*!debug*/ if (arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList animation shortcuts expect 0, 1 or 2 arguments, not ' + arguments.length + '.'); } if (duration !== undefined && typeof duration !== 'number') { glow.debug.warn('[wrong type] glow.NodeList animation shortcuts expect number as "duration" argument, not ' + typeof duration + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.NodeList animation shortcuts expect object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ opts = opts || {}; var item, reverseAnim, currentAnim, calcDuration, anim, i = this.length; opts.tween = opts.tween || defaultTween; if (duration === undefined) { duration = 1; } calcDuration = duration; while (i--) { item = this.item(i); currentAnim = item.data('glow_' + animName); // if this isn't an element ,or we're already animating it, skip if ( item[0].nodeType !== 1 || (currentAnim && currentAnim.playing) ) { continue; } // if there's a reverse anim happening & it's playing, get rid reverseAnim = item.data('glow_' + animReverseName); if (reverseAnim && reverseAnim.playing) { // reduce the duration if we're not fading out as much calcDuration = duration * (reverseAnim.position / reverseAnim.duration); reverseAnim.stop().destroy(); } item.data('glow_' + animName, anim = item.anim( calcDuration, animPropsFunc(item), opts ).on('complete', onComplete, item) ); additionalFunc && additionalFunc(anim, item, opts); } return this; } }; /** @name glow.NodeList#fadeIn @function @description Fade elements in If the element is currently fading out, the fadeOut animation will be automatically stopped. @param {number} [duration=1] Duration in seconds @param {Object} [opts] Options object @param {function|string} [opts.tween='easeOut'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @returns {glow.NodeList} @example // make a tooltip fade in & out var tooltip = glow('#emailTooltip'); glow('#emailInput').on('focus', function() { tooltip.fadeIn(); }).on('blur', function() { tooltip.fadeOut(); }); */ NodeListProto.fadeIn = animShortcut('fadeIn', 'fadeOut', function(item) { item.css('display', 'block'); return {opacity: 1}; }, 'easeOut', function() { // on complete // we remove the filter from IE to bring back cleartype if (glow.env.ie) { this[0].style.filter = ''; } }); /** @name glow.NodeList#fadeOut @function @description Fade elements out If the element is currently fading in, the fadeIn animation will be automatically stopped. @param {number} [duration=1] Duration in seconds @param {Object} [opts] Options object @param {function|string} [opts.tween='easeIn'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @returns {glow.NodeList} @example // make a tooltip fade in & out var tooltip = glow('#emailTooltip'); glow('#emailInput').on('focus', function() { tooltip.fadeIn(); }).on('blur', function() { tooltip.fadeOut(); }); */ NodeListProto.fadeOut = animShortcut('fadeOut', 'fadeIn', function() { return {opacity:0} }, 'easeIn', function() { this.css('display', 'none'); }); /** @name glow.NodeList#fadeToggle @function @description Fade elements in/out If the element is currently fading in/out, the fadeIn/fadeOut animation will be automatically stopped. // Implementation note: (delete me later) If the element has an opactity of 0, then fade in, otherwise fade out. UNLESS there's fadeOut animation currently happening on this element, then fade in. @param {number} [duration=1] Duration in seconds @param {Object} [opts] Options object @param {function|string} [opts.tween] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. By default, 'easeIn' is used for fading out, and 'easeOut' is used for fading in. @returns {glow.NodeList} @example // make a tooltip fade in & out var tooltip = glow('#emailTooltip'); glow('#toggleTooltip').on('click', function() { tooltip.fadeToggle(); }); */ NodeListProto.fadeToggle = function(duration, opts) { var i = this.length, item, fadeOutAnim; while (i--) { item = this.item(i); if (item[0].nodeType === 1) { // if the element has an opacity of 0, or is currently fading out if ( item.css('opacity') === '0' || ((fadeOutAnim = item.data('glow_fadeOut')) && fadeOutAnim.playing) ) { item.fadeIn(duration, opts); } else { item.fadeOut(duration, opts); } } } return this; }; /** @name glow.NodeList#slideOpen @function @description Slide elements open This animates an element's height from its current height to its full auto-height size. If the element is currently sliding shut, the slideShut animation will be automatically stopped. @param {number} [duration=1] Duration in seconds @param {Object} [opts] Options object @param {function|string} [opts.tween='easeBoth'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element. This means the bottom of the content is shown first, rather than the top. @returns {glow.NodeList} @example var menuContent = glow('#menu div.content'); glow('#menu').on('mouseenter', function() { menuContent.slideOpen(); }).on('mouseleave', function() { menuContent.slideShut(); }); @example glow('#furtherInfoHeading').on('click', function() { glow('#furtherInfoContent').slideOpen(); }); @example // add content onto an element, and slide to reveal the new content glow('<div>' + newContent + '</div>').appendTo('#content').height(0).slideOpen(); */ NodeListProto.slideOpen = animShortcut('slideOpen', 'slideShut', function(item) { var currentHeight = item.css('height'), fullHeight; if ( item.css('overflow') === 'visible' ) { item.css('overflow', 'hidden'); } item.css('height', 'auto'); fullHeight = item.height(); item.css('height', currentHeight); return {height: fullHeight} }, 'easeBoth', function() { this.css('height', 'auto').scrollTop(0); }, lockToBottom); /** @name glow.NodeList#slideShut @function @description Slide elements shut This animates an element's height from its current height to zero. If the element is currently sliding open, the slideOpen animation will be automatically stopped. @param {number} [duration=1] Duration in seconds @param {Object} [opts] Options object @param {function|string} [opts.tween='easeBoth'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element. This means the top of the content is hidden first, rather than the bottom. @returns {glow.NodeList} @example var menuContent = glow('#menu div.content'); glow('#menu').on('mouseenter', function() { menuContent.slideOpen(); }).on('mouseleave', function() { menuContent.slideShut(); }); */ NodeListProto.slideShut = animShortcut('slideShut', 'slideOpen', function(item) { if ( item.css('overflow') === 'visible' ) { item.css('overflow', 'hidden'); } return {height: 0} }, 'easeBoth', function() {}, lockToBottom); /** @private @function @description Add frame listener to lock content to the bottom of an item. @param {glow.anim.Anim} anim Anim to alter @param {glow.NodeList} element Element being animated @param {Object} opts Options from slide[Open|Shut|Toggle] */ function lockToBottom(anim, element, opts) { var node = element[0], scrollHeight = node.scrollHeight; if (opts.lockToBottom) { anim.on('frame', function() { element.scrollTop( scrollHeight - node.offsetHeight ); }); } } /** @name glow.NodeList#slideToggle @function @description Slide elements open/shut If the element is currently sliding open/shut, the slideOpen/slideShut animation will be automatically stopped. // Implementation note: (delete me later) If the element has a height of 0, then slide open, otherwise slide shut. UNLESS there's slideShut animation currently happening on this element, then slide open. @param {number} [duration=1] Duration in seconds @param {Object} [opts] Options object @param {function|string} [opts.tween='easeBoth'] The motion of the animation. Strings are treated as properties of {@link glow.tweens}, although a tween function can be provided. @param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element. This means the top of the content is hidden first & shown last. @returns {glow.NodeList} @example var menuContent = glow('#menuContent'); glow('#toggleMenu').on('click', function() { menuContent.slideToggle(); }); */ NodeListProto.slideToggle = function(duration, opts) { var i = this.length, item, slideShutAnim; while (i--) { item = this.item(i); if (item[0].nodeType === 1) { // if the element has an height of 0, or is currently sliding shut if ( item.height() === 0 || ((slideShutAnim = item.data('glow_slideShut')) && slideShutAnim.playing) ) { item.slideOpen(duration, opts); } else { item.slideShut(duration, opts); } } } return this; }; }); /** @name glow.net @namespace @description Methods for getting data & resources from other locations. Sometimes referred to as AJAX. */ Glow.provide(function(glow) { var net = {}, undefined, emptyFunc = function(){}; /** @private @function @description Create XhrRequest factory methods @param {string} method HTTP method @returns {function} Factory method */ function createXhrFactory(method) { return function(url, data, opts) { // only put & post use the data param if (method === 'POST' || method === 'PUT') { opts = opts || {}; opts.data = data; } else { opts = data; } return new net.XhrRequest(method, url, opts); } } /** @name glow.net.get @function @description Makes an HTTP GET request to a given url. This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. @param {string} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. @param {Object} [opts] Options. These options are the same as the constructor options for {@link glow.net.XhrRequest}. @returns {glow.net.XhrRequest} @example glow.net.get('myFile.html').on('load', function(response){ alert( 'Got file:' + response.text() ); }).on('error', function(response){ alert( 'Something went wrong:' + response.text() ); }); */ net.get = createXhrFactory('GET'); /** @name glow.net.post @function @description Makes an HTTP POST request to a given url This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. @param {string} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. @param {Object|String} data Data to send. This can be either a JSON-style object or a urlEncoded string. @param {Object} [opts] Options. These options are the same as the constructor options for {@link glow.net.XhrRequest}. @returns {glow.net.XhrRequest} @example glow.net.post('myFile.html', { key: 'value', otherkey: ['value1', 'value2'] }).on('load', function(response) { alert( 'Got file:' + response.text() ); }); */ net.post = createXhrFactory('POST'); /** @name glow.net.put @function @description Makes an HTTP PUT request to a given url This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. @param {string} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. @param {Object|String} data Data to send. This can be either a JSON-style object or a urlEncoded string. @param {Object} [opts] Options. These options are the same as the constructor options for {@link glow.net.XhrRequest}. @returns {glow.net.XhrRequest} @example glow.net.put('myFile.html', { key: 'value', otherkey: ['value1', 'value2'] }).on('load', function(response) { // handle response }); */ net.put = createXhrFactory('PUT'); /** @name glow.net.del @function @description Makes an HTTP DELETE request to a given url. This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. @param {string} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. @param {Object} [opts] Options. These options are the same as the constructor options for {@link glow.net.XhrRequest}. @returns {glow.net.XhrRequest} @example glow.net.del('myFile.html').on('load', function(response) { // handle response }); */ net.del = createXhrFactory('DELETE'); // export glow.net = net; }); Glow.provide(function(glow) { var undefined, XhrRequestProto, events = glow.events, removeAllListeners = events.removeAllListeners; /** @private @function @description Creates an XMLHttpRequest transport @returns XMLHttpRequest */ var xmlHTTPRequest = window.ActiveXObject ? function() { return new ActiveXObject('Microsoft.XMLHTTP'); } : function() { return new XMLHttpRequest(); }; /** @private @function @description Apply option object defaults. @param {object} opts Options object to apply defaults to. @param {string} method HTTP method. @returns {object} New opts object with defaults applied. */ function applyOptsDefaults(opts, method) { opts = glow.util.apply({ headers: {} }, opts); var headers = opts.headers; // convert data to string if (typeof opts.data === 'object') { opts.data = glow.util.encodeUrl(opts.data); } // add requested with header if one hasn't been added if ( !headers['X-Requested-With'] ) { headers['X-Requested-With'] = 'XMLHttpRequest'; } if (method !== 'GET' && !headers["Content-Type"]) { headers["Content-Type"] = 'application/x-www-form-urlencoded;'; } return opts; } /** @name glow.net.XhrRequest @class @param {string} method The HTTP method to use for the request. Methods are case sensitive in some browsers. @param {string} url Url to make the request to. This can be a relative path. You cannot make requests for files on other domains (including sub-domains). For cross-domain requests, see {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. @param {Object} [opts] Options object @param {Object} [opts.headers] A hash of headers to send along with the request. eg `{'Accept-Language': 'en-gb'}` @param {boolean} [opts.cacheBust=false] Prevent the browser returning a cached response. If true, a value 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. Once the time is reached, the error event will fire with a '408' status code. @param {boolean} [opts.forceXml=false] Treat the response as XML. This will allow you to use {@link glow.net.XhrResponse#xml response.xml()} even if the response has a non-XML mime type. @param {Object|string} [opts.data] Data to send. This can be either a JSON-style object or a urlEncoded string. @description Create an XHR request. Most common requests can be made using shortcuts methods in {@link glow.net}, such as {@link glow.net.get}. @example new glow.net.XhrRequest('DELETE', 'whatever.php', { timeout: 10 }).on('load', function(response) { alert( response.text() ); }); */ function XhrRequest(method, url, opts) { this._opts = opts = applyOptsDefaults(opts, method); var request = this, nativeRequest = request.nativeRequest = xmlHTTPRequest(), //request object i; // add the cacheBust to the url if (opts.cacheBust) { url = url + (url.indexOf('?') === -1 ? '?' : '&') + 'cachebuster=' + new Date().valueOf(); } request.complete = false; //open needs to go first to maintain cross-browser support for readystates nativeRequest.open(method, url, true); //add custom headers for (i in opts.headers) { nativeRequest.setRequestHeader( i, opts.headers[i] ); } // force the reponse to be treated as xml // IE doesn't support overrideMineType, we need to deal with that in {@link glow.net.XhrResponse#xml} if (opts.forceXml && nativeRequest.overrideMimeType) { nativeRequest.overrideMimeType('application/xml'); } //sort out the timeout if there is one if (opts.timeout) { request._timeout = setTimeout(function() { var response = new glow.net.XhrResponse(request, true); request.abort().fire('error', response); }, opts.timeout * 1000); } nativeRequest.onreadystatechange = function() { if (nativeRequest.readyState === 4) { var response = new glow.net.XhrResponse(request); //clear the timeout clearTimeout(request._timeout); //set as completed request.completed = true; request.fire(response.successful ? 'load' : 'error', response); // prevent parent scopes leaking (cross-page) in IE nativeRequest.onreadystatechange = new Function(); removeAllListeners(request); } }; // make sure it doesn't complete before listeners are attached setTimeout(function() { nativeRequest.send(opts.data || null); }, 0); } glow.util.extend(XhrRequest, events.Target); XhrRequestProto = XhrRequest.prototype; /** @name glow.net.XhrRequest#_timeout @private @description setTimeout ID @type number */ /** @name glow.net.XhrRequest#complete @description Boolean indicating whether the request has completed @example // request.complete with an asynchronous call var request = glow.net.get( "myFile.html").on('load', function(response){ alert(request.complete); // returns true }) @type boolean */ /** @name glow.net.XhrRequest#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 getJsonp. @type Object */ /** @name glow.net.XhrRequest#abort @function @description Aborts a request The load & error events will not fire. @example var request = glow.net.get('myFile.html').on('load', function(response) { //handle response }).on('abort', function() { alert('Something bad happened. The request was aborted.'); }); request.abort(); // alerts "Something bad happened. The request was aborted" @returns this */ XhrRequestProto.abort = function() { if ( !this.completed && !this.fire('abort').defaultPrevented() ) { clearTimeout(this._timeout); this.nativeRequest.onreadystatechange = new Function(); removeAllListeners(this); } return this; }; /** @name glow.net.XhrRequest#event:load @event @param {glow.net.XhrResponse} response @description Fired when the request is sucessful This will be fired when request returns with an HTTP code of 2xx. */ /** @name glow.net.XhrRequest#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. */ /** @name glow.net.XhrRequest#event:error @event @param {glow.net.XhrResponse} response @description Fired when the request is unsucessful This will be fired when request returns with an HTTP code which isn't 2xx or the request times out. */ glow.net.XhrRequest = XhrRequest; }); Glow.provide(function(glow) { var XhrResponseProto, util = glow.util; /** @name glow.net.XhrResponse @class @extends glow.events.Event @description The event object for {@link glow.net.XhrRequest}'s 'load' & 'error' events. @glowPrivateConstructor There is no direct constructor. */ /* These params are hidden as we don't want users to try and create instances of this... @param {glow.net.XhrRequest} [request] Original request object @param {Boolean} [timedOut=false] Set to true if the response timed out */ function XhrResponse(request, timedOut) { var nativeResponse = this.nativeResponse = request.nativeRequest; this._request = request; //IE reports status as 1223 rather than 204, for laffs this.status = timedOut ? 408 : nativeResponse.status == 1223 ? 204 : nativeResponse.status; this.timedOut = !!timedOut; this.successful = (this.status >= 200 && this.status < 300) || //from cache this.status == 304 || //watch our for requests from file:// (this.status == 0 && nativeResponse.responseText); } util.extend(XhrResponse, glow.events.Event); XhrResponseProto = XhrResponse.prototype; /** @name glow.net.XhrResponse#_request @private @description Original request object @type glow.net.XhrRequest */ /** @name glow.net.XhrResponse#nativeResponse @description The response object from the browser. This may not have the same properties and methods across user agents. @type XMLHttpRequest */ /** @name glow.net.XhrResponse#status @description HTTP status code of the response @type number */ /** @name glow.net.XhrResponse#timedOut @description Boolean indicating if the requests time out was reached. @type boolean */ /** @name glow.net.XhrResponse#successful @description Boolean indicating if the request returned successfully. @type boolean */ /** @name glow.net.XhrResponse#text @function @description Gets the body of the response as plain text @returns {string} */ XhrResponseProto.text = function() { return this.nativeResponse.responseText; }; /** @name glow.net.XhrResponse#xml @function @description Gets the body of the response as xml @returns {XML} */ XhrResponseProto.xml = function() { var nativeResponse = this.nativeResponse, contentType = this.header("Content-Type"); if ( // IE 6 & 7 fail to recognise Content-Types ending +xml (eg application/rss+xml) // Files from the filesystem don't have a content type, but could be xml files, parse them to be safe glow.env.ie && ( contentType.slice(-4) === '+xml' || contentType === '' || this._request._opts.forceXml ) ) { var doc = new ActiveXObject("Microsoft.XMLDOM"); doc.loadXML( nativeResponse.responseText ); return doc; } else { return nativeResponse.responseXML; } }; /** @name glow.net.XhrResponse#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} */ XhrResponseProto.json = function(safe) { return util.decodeJson(this.text(), {safeMode:safe}); }; /** @name glow.net.XhrResponse#nodeList @function @description Gets the body of the response as a {@link glow.NodeList}. @returns {glow.NodeList} */ XhrResponseProto.nodeList = function(safe) { return glow( glow.NodeList._strToNodes( this.text() ) ); }; /** @name glow.net.XhrResponse#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"); */ XhrResponseProto.header = function(name) { return this.nativeResponse.getResponseHeader(name); }; /** @name glow.net.XhrResponse#statusText @function @description Gets the meaning of {@link glow.net.XhrResponse#status status}. @returns {string} */ XhrResponseProto.statusText = function() { return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText; }; glow.net.XhrResponse = XhrResponse; }); Glow.provide(function(glow) { var undefined, JsonpRequestProto, net = glow.net, emptyFunc = function(){}, events = glow.events, // Script elements that have been added via {@link glow.net.jsonp jsonp}, keyed by callback name scriptElements = {}, scriptElementsLen = 0, callbackPrefix = 'c', // Name of the global object used to store jsonp callbacks globalObjectName = '_' + glow.UID + 'jsonp', head = glow('head'), // a reference to the global object holding the callbacks globalObject; /** @private @function @description Handle jsonp load. @param {glow.net.JsonpRequest} request @param {Object[]} args Arguments object passed to the callback from the jsonp source */ function jsonpLoad(request, args) { // we have to call listeners manually as we don't provide a real event object. A bit of a hack. var loadListeners = events._getListeners(request).load, i; if (loadListeners) { loadListeners = loadListeners.slice(0); i = loadListeners.length; while (i--) { loadListeners[i][0].apply( loadListeners[i][1], args ); } } //set as completed request.completed = true; cleanUp(request); } /** @private @function @description Clean up to avoid memory leaks @param {glow.net.JsonpRequest} request @param {boolean} [leaveEmptyFunc] Replace global callback with blank function. If false, the global callback will be set to undefined, which is better for memory, but in some cases the callback may later be called (like a timed out request) so an empty function needs to be used to avoid errors. */ function cleanUp(request, leaveEmptyFunc) { var callbackName = request._callbackName; clearTimeout(request._timeout); globalObject[callbackName] = leaveEmptyFunc ? emptyFunc : undefined; glow( scriptElements[callbackName] ).destroy(); scriptElements[callbackName] = undefined; } /** @name glow.net.JsonpRequest @class @description A JSONP request. Although instance of this can be created manually, using {@link glow.net.jsonp} is preferred. */ // the params for this are the same as {@link glow.net.jsonp}. function JsonpRequest(url, opts) { opts = opts || {}; var newIndex = scriptElements.length, //script element that gets inserted on the page //generated name of the callback callbackName = this._callbackName = callbackPrefix + (scriptElementsLen++), // script element to add to the page script = scriptElements[callbackName] = document.createElement('script'), request = this, timeout = opts.timeout, charset = opts.charset; // add the callback name to the url url = glow.util.interpolate(url, { callback: globalObjectName + '.' + callbackName }); // create the global object if it doesn't exist already globalObject || ( globalObject = window[globalObjectName] = {} ); // create our callback globalObject[callbackName] = function() { jsonpLoad(request, arguments); }; // set charset charset && (script.charset = charset); if (opts.timeout) { request._timeout = setTimeout(function() { request.abort().fire('error'); }, timeout * 1000); } script.src = url; //add script to page head.prepend(script); script = undefined; } glow.util.extend(JsonpRequest, events.Target); JsonpRequestProto = JsonpRequest.prototype; /** @name glow.net.JsonpRequest#_callbackName @private @description The name of the callback, used as a property name in globalObject and scriptElements */ /** @name glow.net.JsonpRequest#_timeout @private @description timeout ID @type number */ /** @name glow.net.JsonpRequest#complete @description Boolean indicating whether the request has completed @type boolean */ JsonpRequestProto.complete = false; /** @name glow.net.JsonpRequest#abort @function @description Abort the request. The script file may still load, but the 'load' event will not fire. @returns this */ JsonpRequestProto.abort = function() { this.fire('abort'); cleanUp(this, true); return this; }; /** @name glow.net.JsonpRequest#event:load @event @description Fired when the request is sucessful. The parameters to this event are whatever the datasource provides. @example glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}') .on('load', function(data) { alert(data); }); */ /** @name glow.net.JsonpRequest#event:abort @event @param {glow.events.Event} event Event Object @description Fired when the request is aborted. */ /** @name glow.net.JsonpRequest#event:error @event @param {glow.events.Event} event Event Object @description Fired when the request times out. */ /** @name glow.net.jsonp @function @description Fetch JSON via JSONP. This can be used cross domain, but should only be used with trusted sources as any javascript included in the script will be executed. This method only works if the server allows you to specify a callback name for JSON data. Not all JSON sources support this, check the API of the data source to ensure you're using the correct querystring parameter to set the callback name. @param {string} url Url of the script. Set the callback name via the querystring to `{callback}`, Glow will replace this with another value and manage the callback internally. Check the API of your data source for the correct parameter name. Eg, in Flickr it's `jsoncallback={callback}`, in Twitter it's `callback={callback}`. @param {object} [opts] @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.JsonpRequest} @example glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}', { timeout: 5 }).on('load', function(data) { alert(data); }).on('error', function() { alert('Request timeout'); }); */ net.jsonp = function(url, opts) { return new glow.net.JsonpRequest(url, opts); }; glow.net.JsonpRequest = JsonpRequest; }); Glow.provide(function(glow) { var undefined, ResourceRequestProto, ResourceResponseProto, net = glow.net; /** @private @function @description Normalise urls param. Normalise ResourceRequest's urls parameter to an object with 'css', 'js' and 'img' properties. */ function normaliseUrlsParam(urls) { var r = { js: [], css: [], img: [] }, url; if (typeof urls === 'object' && !urls.push) { r = glow.util.apply(r, urls); } else { // convert urls to an array if need be typeof urls === 'string' && ( urls = [urls] ); // forwards loop, maintain order for (var i = 0, len = urls.length; i < len; i++) { url = urls[i]; if ( url.slice(-4) === '.css' ) { r.css[r.css.length] = url; } else if ( url.slice(-3) === '.js' ) { r.js[r.js.length] = url; } else { r.img[r.img.length] = url; } } } return r; } /** @name glow.net.ResourceRequest @class @description Request made via {@link glow.net.getResources} @glowPrivateConstructor There is no direct constructor. */ function ResourceRequest(urls) { urls = normaliseUrlsParam(urls); var request = this, js = urls.js, css = urls.css, img = urls.img, jsLen = js.length, cssLen = css.length, imgLen = img.length, i; request.totalResources = jsLen + cssLen + imgLen; // ensure events don't fire until they're added setTimeout(function() { // guess it makes sense to load CSS, js then images (the browser will queue the requests) for (i = 0; i < cssLen; i++) { loadCss( request, css[i] ); } for (i = 0; i < jsLen; i++) { loadJs( request, js[i] ); } for (i = 0; i < imgLen; i++) { loadImg( request, img[i] ); } }, 0); } glow.util.extend(ResourceRequest, glow.events.Target); ResourceRequestProto = ResourceRequest.prototype; /** @name glow.net.ResourceRequest#totalResources @type number @description Total number of resources requested. */ ResourceRequestProto.totalResources = 0; /** @name glow.net.ResourceRequest#totalLoaded @type number @description Total number of resources successfully loaded. */ ResourceRequestProto.totalLoaded = 0; /** @private @function @description Update a request after a resource loads. @param {glow.net.ResourceRequest} request. @param {string} url Url of the requested resource. @param {glow.NodeList} resource The element used to load the resource. @param {string} type 'js', 'css' or 'img' */ function progress(request, url, resource, type) { var totalLoaded = ++request.totalLoaded; request.fire('progress', { resource: resource, url: url, type: type }); if (totalLoaded === request.totalResources) { request.fire('load'); } } /** @private @function @description Start loading an image @param {glow.net.ResourceRequest} request @param {string} imgUrl */ function loadImg(request, imgUrl) { var img = new Image; // keep the url in its original format glow(img).data('srcUrl', imgUrl).on('load', imgLoaded, request); img.src = imgUrl; } /** @private @function @description Process a loaded image. 'this' is the ResourceRequest */ function imgLoaded(event) { var img = glow(event.attachedTo); progress( this, img.data('srcUrl'), img, 'img' ); } /** @private @function @description Start loading a script @param {glow.net.ResourceRequest} request @param {string} scriptUrl */ function loadJs(request, scriptUrl){ var script = glow( document.createElement('script') ) .data('srcUrl', scriptUrl) .prependTo('head'); // two methods, one for IE (readystatechange) and the other for others script.on('readystatechange', jsLoaded, request).on('load', jsLoaded, request); script[0].src = scriptUrl; } /** @private @function @description Process a loaded script. 'this' is the ResourceRequest */ function jsLoaded(event) { var script = glow(event.attachedTo), scriptElm = script[0], readyState = scriptElm.readyState; if ( !readyState || readyState === 'loaded' || readyState === 'complete' ) { // remove events to prevent double-firing script.detach('readystatechange', jsLoaded).detach('load', jsLoaded); progress( this, script.data('srcUrl'), script, 'js' ); } } /** @private @function @description Start loading a CSS file @param {glow.net.ResourceRequest} request @param {string} cssUrl */ // This technique was found in http://code.google.com/p/ajaxsoft/source/browse/trunk/xLazyLoader function loadCss(request, cssUrl){ var currentHostname, urlHostname, link = glow('<link rel="stylesheet" type="text/css" media="all" href="' + cssUrl + '" />').data('srcUrl', cssUrl); // we have to do something special for Gecko browsers when the css is from another domain if ( glow.env.gecko && /^(?:https?\:|\/\/)/.test(cssUrl) ) { currentHostname = location.hostname.replace('www.', ''); urlHostname = cssUrl.replace(/https?:\/\/|www\.|:.*/g, '').replace(/\/.*/g, ''); if (currentHostname !== urlHostname) { // ack, we have to cheat setTimeout(function() { cssLoaded.call(request, { attachedTo: link }); }, 500); } } else { // two methods, one for IE (readystatechange), and one for opera link.on('readystatechange', cssLoaded, request).on('load', cssLoaded, request); // ...and one more for Moz & webkit (function pollCssRules() { try { link[0].sheet.cssRules; // we'll error before the next line if CSS hasn't loaded cssLoaded.call(request, { attachedTo: link }); } catch (e) { if ( !link.data('loaded') ) { setTimeout(pollCssRules, 20); } }; })(); } //link[0].href = cssUrl; link.prependTo('head'); } /** @private @function @description Process a loaded stylesheet. 'this' is the ResourceRequest */ function cssLoaded(event) { var link = glow(event.attachedTo), linkElm = link[0], readyState = linkElm.readyState; if ( !readyState || readyState === 'loaded' || readyState === 'complete' ) { // just incase there's a timeout still waiting if ( link.data('loaded') ) { return; } link.data('loaded', true); // remove events to prevent double-firing link.detach('readystatechange', cssLoaded).detach('load', cssLoaded); progress( this, link.data('srcUrl'), link, 'css' ); } } /** @name glow.net.ResourceRequest#event:load @event @param {glow.events.Event} event Event Object @description Fired when all the requested items have completed. */ /** @name glow.net.ResourceRequest#event:progress @event @description Fired when a single resource loads. @param {glow.events.Event} event Event Object @param {string} event.url Url of the loaded resource. @param {glow.NodeList} event.resource The element used to load the resource. This will be a `<script>`, `<link>`, or `<img>` element. @param {string} event.type 'js', 'css' or 'img'. */ /** @name glow.net.getResources @function @description Load scripts, images & CSS. Files can be loaded from other domains. Note: Due to a cross-browser restriction, 'load' may fire before CSS files from another domain are fully loaded in Gecko browsers. @param {string[]|string|Object} url Url(s) to load. Urls ending in ".css" are assumed to be CSS files, Urls ending in ".js" are assumed to be JavaScript. All other files will be treated as images. You can provide an object in the form `{js: [], css: [], img: []}` to be explicit about how to treat each file. @returns {glow.net.ResourceRequest} @example // load a single CSS file with a callback specified glow.net.getResources('/styles/custom.css').on('load', function() { // CSS has now loaded }); @example // load a single CSS file with a callback specified glow.net.getResources([ '/imgs/whatever.png', '/style/screen.css', ]).on('load', function() { // CSS & image now loaded }); @example // load multiple files by specifying and array glow.net.getResources({ js: ['http://www.server.com/script', 'http://www.server.com/anotherScript'], img: ['http://www.server.com/product4/thumb'] }).on('progress', function(event) { // update a progress meter }).on('load', function(response){ // files now loaded }); */ net.getResources = function(urls, opts) { /*!debug*/ if (arguments.length < 1 && arguments.length > 2) { glow.debug.warn('[wrong count] glow.net.getResources expects 1 or 2 arguments, not ' + arguments.length + '.'); } /*gubed!*/ return new glow.net.ResourceRequest(urls, opts); }; glow.net.ResourceRequest = ResourceRequest; }); Glow.provide(function(glow) { var undefined, CrossDomainRequestProto, CrossDomainResponseProto, net = glow.net, // We borrow some methods from XhrRequest later XhrResponseProto = net.XhrRequest.prototype, Target = glow.events.Target; /** @name glow.net.CrossDomainRequest @class @description Cross-domain request via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window. The URL that's requested should respond with a blank HTML page containing JavaScript that assigns the result to window.name as a string: `<script type="text/javascript">window.name = 'result string';</script>` Instances of this are returned by shortcut methods {@link glow.net.crossDomainGet} and {@link glow.net.crossDomainPost} @param {string} method The HTTP method to use for the request. Only 'POST' and 'GET' are considered cross-browser. @param {string} url The URL to request. @param {Object} [opts] @param {Object|string} [opts.data] Data to send. This can be either a JSON-style object or a urlEncoded string. @param {number} [opts.timeout] Time to allow for the request in seconds. No timeout is set by default. @param {string} [opts.blankUrl='/favicon.ico'] The path of a page on same domain as the caller, ideally a page likely to be in the user's cache. */ function CrossDomainRequest(method, url, opts) { var request = this, timeout; request._opts = opts = glow.util.apply({ data: {}, blankUrl: '/favicon.ico' }, opts); // convert data to object if (typeof opts.data === 'string') { opts.data = glow.util.decodeUrl(opts.data); } // set timeout for the request timeout = opts.timeout; if (timeout) { request._timeout = setTimeout(function () { request.fire('error'); cleanup(request); }, timeout * 1000); } addIframe(request); buildAndSubmitForm(request, method, url); } glow.util.extend(CrossDomainRequest, Target); CrossDomainRequestProto = CrossDomainRequest.prototype; /** @name glow.net.CrossDomainRequest#_opts @private @type Object @description Options object with defaults applied */ /** @name glow.net.CrossDomainRequest#_iframe @private @type glow.NodeList @description Iframe used to send the data. */ /** @name glow.net.CrossDomainRequest#_timeout @private @type number @description setTimeout id for request timeout */ /** @private @function @description Add a hidden iframe for posting the request @param {glow.net.CrossDomainRequest} request */ function addIframe(request) { var iframe = request._iframe = glow( '<iframe style="visibility: hidden; position: absolute; height: 0;"></iframe>' ).appendTo(document.body); }; /** @private @function @description Add a form to the iframe & submit it @param {glow.net.CrossDomainRequest} request @param {string} method The HTTP method to use for the request. Only 'POST' and 'GET' are considered cross-browser. @param {string} url The URL to request. */ function buildAndSubmitForm(request, method, url) { var iframe = request._iframe, win = iframe[0].contentWindow, doc = win.document, form, data = request._opts.data; // IE needs an empty document to be written to written to the iframe if (glow.env.ie) { doc.open(); doc.write('<html><body></body></html>'); doc.close(); } // create form form = doc.createElement('form'); form.action = url; form.method = method; doc.body.appendChild(form); // build form elements for (var i in data) { if ( !data.hasOwnProperty(i) ) { continue; } if (data[i] instanceof Array) { for (var j = 0, jLen = data[i].length; j < jLen; j++) { addHiddenInput( form, i, this.data[i][j] ); } } else { addHiddenInput( form, i, this.data[i] ); } } // submit - the setTimeout makes the function run in the context of the form win.setTimeout(function () { form.submit(); }, 0); // listen for form submitting iframe.on('load', handleResponse, request); } /** @private @function @description Add a hidden input to a form for a piece of data. @param {HTMLFormElement} form @param {string} name Input name @param {string} value Input value */ function addHiddenInput(form, name, value) { var input = form.ownerDocument.createElement('input'); input.type = 'hidden'; input.name = name; input.value = value; form.appendChild(input); } /** @private @function @description Callback for load event in the hidden iframe. `this` is the request. */ function handleResponse() { var err, href, win = this._iframe[0].contentWindow; try { href = win.location.href; } catch (e) { err = e; } if (href !== 'about:blank' || err) { clearTimeout(this._timeout); this._iframe.detach('load', handleResponse).on('load', readHandler, this); win.location = window.location.protocol + '//' + window.location.host + this._opts.blankUrl; } } /** @private @function @description Callback for load event of blank page in same origin. `this` is the request. */ function readHandler() { this.fire( 'load', new CrossDomainResponse(this._iframe[0].contentWindow.name) ); cleanup(this); } /** @private @function @description Removes the iframe and any event listeners. @param {glow.net.CrossDomainRequest} request */ function cleanup(request) { request._iframe.destroy(); glow.events.removeAllListeners(request); } /** @name glow.net.CrossDomainRequest#event:load @event @param {glow.net.CrossDomainResponse} response @description Fired when the request is sucessful. */ /** @name glow.net.CrossDomainRequest#event:error @event @param {glow.events.Event} event Event Object @description Fired when the request times out. */ /** @name glow.net.CrossDomainResponse @class @description Response object for cross-domain requests. This is provided in {@link glow.net.CrossDomainRequest}'s 'load' event. @glowPrivateConstructor There is no direct constructor. */ function CrossDomainResponse(textResponse) { this._text = textResponse; } glow.util.extend(CrossDomainResponse, Target); CrossDomainResponseProto = CrossDomainResponse.prototype; /** @name glow.net.CrossDomainResponse#_text @private @type string @description Text response from the server */ /** @name glow.net.CrossDomainResponse#text @function @description Gets the body of the response as plain text. @returns {string} */ CrossDomainResponseProto.text = function() { return this._text; } /** @name glow.net.CrossDomainResponse#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} */ CrossDomainResponseProto.json = XhrResponseProto.json; /** @name glow.net.CrossDomainResponse#nodeList @function @description Gets the body of the response as a {@link glow.NodeList}. @returns {glow.NodeList} */ CrossDomainResponseProto.nodeList = XhrResponseProto.nodeList; // ...and now, the factory methods! Yey! /** @name glow.net.crossDomainPost @function @description Cross-domain post via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window. The URL that's requested should respond with a blank HTML page containing JavaScript that assigns the result to window.name as a string: `<script type="text/javascript">window.name = 'result string';</script>` @param {string} url The URL to request. @param {Object|string} data Data to send. This can be either a JSON-style object or a urlEncoded string. @param {Object} [opts] @param {number} [opts.timeout] Time to allow for the request in seconds. No timeout is set by default. @param {string} [opts.blankUrl='/favicon.ico'] The path of a page on same domain as the caller, ideally a page likely to be in the user's cache. */ net.crossDomainPost = function(url, data, opts) { opts = opts || {}; opts.data = data; return new CrossDomainRequest('POST', url, opts); }; /** @name glow.net.crossDomainGet @function @description Cross-domain get via window.name A request made via a form submission in a hidden iframe, with the result being communicated via the name attribute of the iframe's window. The URL that's requested should respond with a blank HTML page containing JavaScript that assigns the result to window.name as a string: `<script type="text/javascript">window.name = 'result string';</script>` @param {string} url The URL to request. @param {Object} [opts] @param {number} [opts.timeout] Time to allow for the request in seconds. No timeout is set by default. @param {string} [opts.blankUrl='/favicon.ico'] The path of a page on same domain as the caller, ideally a page likely to be in the user's cache. */ net.crossDomainGet = function(url, opts) { return new CrossDomainRequest('GET', url, opts); }; // export glow.net.CrossDomainRequest = CrossDomainRequest; glow.net.CrossDomainResponse = CrossDomainResponse; }); Glow.provide(function(glow) { var tweens = glow.tweens = {}; /** @name glow.tweens @namespace @description Functions for controlling the motion of an animation */ /* @name _reverse @private @description Takes a tween function and returns a function which does the reverse */ function _reverse(tween) { return function(t) { return 1 - tween(1 - t); } } /** @name glow.tweens.linear @function @description Creates linear tween. Will transition values from start to finish with no acceleration or deceleration. @returns {function} */ tweens.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 will be. The higher the number the slower the animation starts and the quicker it ends. @returns {function} */ tweens.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 will be. The higher the number the quicker the animation starts and the slower it ends. @returns {function} */ tweens.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} */ tweens.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} */ tweens.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} */ tweens.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} */ tweens.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} */ tweens.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} */ tweens.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.elasticIn @function @description Returns the reverse of {@link glow.tweens.elasticOut elasticOut} @param {number} [amplitude=1] How strong the elasticity will be. @param {number} [frequency=3.33] The frequency. @returns {function} */ tweens.elasticIn = function(amplitude, frequency) { return _reverse(this.elasticOut(amplitude, frequency)); }; /** @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} [frequency=3.33] The frequency. @returns {function} */ tweens.elasticOut = function(amplitude, frequency) { var period = 1 / (frequency || 10 / 3); amplitude = amplitude || 1; return function (t) { var s; if (t == 0 || t == 1) { return t; } if (amplitude < 1) { s = period / 4; } else { s = period / (2 * Math.PI) * Math.asin(1 / amplitude); } return amplitude * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / period) + 1; } }; /** @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} */ tweens.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 Creating and synchronising animations */ Glow.provide(function(glow) { var undefined, AnimProto, activeAnims = [], activeAnimsLen = 0, animInterval; /** @private @function @description This is called on each interval This set the properties of each animation per frame. This is the drill sgt of the Anim world. */ function onInterval() { var dateNum = new Date().valueOf(), i = activeAnimsLen, anim; while (i--) { // ideally, this processing would be a function of Anim, but it's quicker this way anim = activeAnims[i]; anim.position = (dateNum - anim._syncTime) / 1000; // see if this animation is ready to complete if (anim.position >= anim.duration) { anim.position = anim.duration; anim.value = anim.tween(1); // render final frame anim.fire('frame'); // fire 'frame' and 'complete' and see if we're going to loop (preventing default) if ( anim.fire('complete').defaultPrevented() || anim.loop ) { // loop the animation anim._syncTime = dateNum; } // else deactivave the anim else { // reset the stop position so further starts start from the beginning anim._stopPos = 0; deactivateAnim(anim); // destroy the anim if needed anim.destroyOnComplete && anim.destroy(); } } else { // set up the value and render a frame anim.value = anim.tween( anim.position / anim.duration ); anim.fire('frame'); } } } /** @private @function @description Calls 'frame' on an animation on an interval */ function activateAnim(anim) { // if this is the first anim, start the timer if (!activeAnimsLen) { animInterval = setInterval(onInterval, 13); } activeAnims[ activeAnimsLen++ ] = anim; anim.playing = true; } /** @private @function @description Stops calling 'frame' on an animation on an interval */ function deactivateAnim(anim) { // decided to search forward, animations ending are more likely to be older & at the start of the array. // This mutates activeAnims for (var i = 0, leni = activeAnims.length; i < leni; i++) { if (activeAnims[i] === anim) { activeAnims.splice(i, 1); activeAnimsLen--; // if we're out of anims, stop the timer if (!activeAnimsLen) { clearInterval(animInterval); } anim.playing = false; return; } } } /** @name glow.anim.Anim @extends glow.events.Target @class @description Animate an object. To animate CSS properties, see {@link glow.NodeList#anim}. Once you have an Anim instance, the {@link glow.anim.Anim#prop} method can be used to easily animate object properties from one value to another. If this isn't suitable, listen for the 'frame' event to change values over time. @param {number} duration Length of the animation in seconds. @param {Object} [opts] Object of options. @param {function|string} [opts.tween='easeBoth'] The way the value changes over time. Strings are treated as properties of {@link glow.tweens} (eg 'bounceOut'), although a tween function can be provided. The default is an {@link glow.tweens.easeBoth easeBoth} tween. Looped animations will fire a 'complete' event on each loop. @param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops). Shortcut for {@link glow.anim.Anim#destroyOnComplete}. @param {boolean} [opts.loop=false] Loop the animation. Shortcut for setting {@link glow.anim.Anim#loop}. @example // Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween // feGaussianBlurElm is a reference to an <feGaussianBlur /> element. new glow.anim.Anim(5, { tween: 'easeOut' }).target(feGaussianBlurElm).prop('stdDeviation', { from: 0, to: 8 }).start(); @example // Animate a CSS property we don't support in glow.NodeList#anim // This rotates a Mozilla CSS gradient var styleObject = glow('#nav').prop('style'); new glow.anim.Anim(10).target(styleObject).prop('background', { // the question-mark in the template is replaced with the animated value template: '-moz-linear-gradient(?deg, red, blue)' from: 0, to: 360 }).start(); @example // Animate a CSS property we don't support in glow.NodeList#anim // This changes the colour of a webkit drop shadow from yellow to blue var styleObject = glow('#nav').prop('style'); new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', { // the ? in the template are replaced with the animate values template: 'rgb(?, ?, ?) 0px 4px 14px' // provide a 'from' and 'to' value for each question-mark from: [255, 255, 0], to: [0, 0, 255], // round the value, colours can't be fractional round: true }).start(); @example // Make an ASCII progress bar animate from: // [--------------------] 0% // to // [++++++++++++++++++++] 100% var progressBar = glow('#progressBar'), // our progress bar is 20 chars barSize = 20; new glow.anim.Anim(2).on('frame', function() { var onChars = Math.floor(this.value * barSize), offChars = barSize - onChars, // add the + and - chars barStr = new Array(onChars + 1).join('+') + new Array(offChars + 1).join('-'); progressBar.text('[' + barStr + '] ' + Math.floor(this.value * 100) + '%'); }).start(); @see {@link glow.NodeList#anim} - shortcut for animating CSS values on an element. */ function Anim(duration, opts) { /*!debug*/ if (arguments.length < 1 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.anim.Anim expects 1 or 2 arguments, not ' + arguments.length + '.'); } if ( isNaN(duration) ) { glow.debug.warn('[wrong type] glow.anim.Anim expects number as "duration" argument, not ' + typeof duration + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.anim.Anim expects object as "opts" argument, not ' + typeof opts + '.'); } if ( opts && typeof opts.tween === 'string' && !glow.tweens[opts.tween] ) { glow.debug.warn('[unexpected value] glow.anim.Anim - tween ' + opts.tween + ' does not exist'); } /*gubed!*/ opts = glow.util.apply({ destroyOnComplete: true // other options have falsey defaults }, opts || {}); this.destroyOnComplete = opts.destroyOnComplete; if (typeof opts.tween === 'string') { this.tween = glow.tweens[opts.tween](); } else if (opts.tween) { this.tween = opts.tween; } this.loop = !!opts.loop; this.duration = +duration; // defined & used in prop.js this._targets = []; }; glow.util.extend(Anim, glow.events.Target); AnimProto = Anim.prototype; /** @name glow.anim.Anim#_syncTime @private @type number @description Number used to work out where the animation should be against the current date If an animation starts at 0, this number will be new Date().valueOf(), it'll be lower for animations that start at a midpoint */ /** @name glow.anim.Anim#_stopPos @private @type number @description The position the animation was stopped at This is set on `.stop()` and used to resume from the same place on `.start()` */ /** @name glow.anim.Anim#duration @type number @description Length of the animation in seconds. */ /** @name glow.anim.Anim#tween @type function @description The tween used by the animation. */ AnimProto.tween = glow.tweens.easeBoth(); /** @name glow.anim.Anim#position @readOnly @type number @description Position of the animation in seconds. */ AnimProto.position = 0; /** @name glow.anim.Anim#playing @readOnly @type boolean @description `true` if the animation is playing. */ AnimProto.playing = false; /** @name glow.anim.Anim#loop @type boolean @description Loop the animation? This value can be changed while an animation is playing. Looped animations will fire a 'complete' event at the end of each loop. */ /** @name glow.anim.Anim#destroyOnComplete @type boolean @description Destroy the animation once it completes (unless it loops). This will free any DOM references the animation may have created. Once the animation is destroyed, it cannot be started again. */ /** @name glow.anim.Anim#value @type number @readOnly @description Current tweened value of the animation, usually between 0 & 1. This can be used in frame events to change values between their start and end value. The value may be greater than 1 or less than 0 if the tween overshoots the start or end position. {@link glow.tweens.elasticOut} for instance will result in values higher than 1, but will still end at 1. @example // Work out a value between startValue & endValue for the current point in the animation var currentValue = (endValue - startValue / myAnim.value) + startValue; */ AnimProto.value = 0; /** @name glow.anim.Anim#start @function @description Starts playing the animation If the animation is already playing, this has no effect. @param {number} [position] Position to start the animation at, in seconds. By default, this will be the last position of the animation (if it was stopped) or 0. @returns this */ AnimProto.start = function(position) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.anim.Anim#start expects 0 or 1 argument, not ' + arguments.length + '.'); } if (position !== undefined && typeof position !== 'number') { glow.debug.warn('[wrong type] glow.anim.Anim#start expects number as "position" argument, not ' + typeof position + '.'); } /*gubed!*/ if ( !this.playing && !this.fire('start').defaultPrevented() ) { // we set 'playing' here so goTo knows this.playing = true; this.goTo(position === undefined ? (this._stopPos || 0) : position); activateAnim(this); } return this; }; /** @name glow.anim.Anim#stop @function @description Stops the animation playing. Stopped animations can be resumed by calling {@link glow.anim.Anim#start start}. If the animation isn't playing, this has no effect. @returns this */ AnimProto.stop = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.anim.Anim#stop expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ if ( this.playing && !this.fire('stop').defaultPrevented() ) { this._stopPos = this.position; deactivateAnim(this); } return this; }; /** @name glow.anim.Anim#destroy @function @description Destroys the animation & detaches references to objects This frees memory & is called automatically when an animation completes. @returns undefined */ AnimProto.destroy = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.anim.Anim#destroy expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ glow.events.removeAllListeners( [this] ); this._targets = undefined; }; /** @name glow.anim.Anim#goTo @function @description Goes to a specific point in the animation. @param {number} pos Position in the animation to go to, in seconds @example // move the animation to 2.5 seconds in // If the animation is playing, it will continue to play from the new position. // Otherwise, it will simply move to that position. myAnim.goTo(2.5); @returns this */ AnimProto.goTo = function(position) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.anim.Anim#goTo expects 1 argument, not ' + arguments.length + '.'); } if (typeof position !== 'number') { glow.debug.warn('[wrong type] glow.anim.Anim#goTo expects number as "position" argument, not ' + typeof position + '.'); } /*gubed!*/ if (position > this.duration) { position = this.duration; } else if (position < 0) { position = 0; } // set stopPos to this so the next call to start() starts from here this._stopPos = this.position = position; // move the syncTime for this position if we're playing if (this.playing) { this._syncTime = new Date - (position * 1000); } this.value = this.tween(position / this.duration); this.fire('frame'); return this; }; /** @name glow.anim.Anim#event:start @event @description Fires when an animation starts. Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from starting. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Anim#event:frame @event @description Fires on each frame of the animation Use a combination of this event and {@link glow.anim.Anim#value value} to create custom animations. See the {@link glow.anim.Anim constructor} for usage examples. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Anim#event:stop @event @description Fires when an animation is stopped before completing Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from stopping. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Anim#event:complete @event @description Fires when an animation completes Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) causes the animation to loop. @param {glow.events.Event} event Event Object @example // Make an animation loop 5 times var loopCount = 5; myAnim.on('complete', function() { return !loopCount--; }); */ // export glow.anim = {}; glow.anim.Anim = Anim; }); Glow.provide(function(glow) { /** @name glow.anim.Anim#_evalFunc @function @private @description Evals a function to be used as a frame listener This function is isolated from the others to reduce the impact of eval() on compression and garbage collection 'targets' is used by the compiled function */ glow.anim.Anim.prototype._evalFunc = function evalFunc(s, targets) { eval('var f=function(){' + s + '}'); return f; } }); Glow.provide(function(glow) { var undefined, AnimProto = glow.anim.Anim.prototype; /** @name glow.anim.Anim#_targets @private @type Object[] @description An array of objects added via #target */ /** @name glow.anim.Anim#target @function @description Set the object for subsequent calls to {@link glow.anim.Anim#prop prop} to act on. @param {Object} newTarget The target object @returns this @example // animate objToAnimate.value from 0 to 10 over 3 seconds // and anotherObjToAnimate.data from -100 to 20 over 3 seconds var objToAnimate = {}, anotherObjToAnimate = {}; new glow.anim.Anim(3).target(objToAnimate).prop('value', { from: 0, to: 10 }).target(anotherObjToAnimate).prop('data', { from: 100, to: -20 }) */ AnimProto.target = function(newTarget) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.anim.Anim#target expects 1 argument, not ' + arguments.length + '.'); } if (typeof newTarget !== 'object') { glow.debug.warn('[wrong type] glow.anim.Anim#target expects object as "newTarget" argument, not ' + typeof newTarget + '.'); } /*gubed!*/ this._targets[ this._targets.length ] = newTarget; return this; }; /** @name glow.anim.Anim#_funcStr @private @type Object @description The string for the function _propFunc This is retained so it can be added to for further calls to prop */ AnimProto._funcStr = ''; /** @private @description Returns a string that calculates the current value for a property */ function buildValueCalculator(from, to, max, min, round) { // start with (from + (from - to) * this.value) var str = '(' + Number(from) + '+' + (to - from) + '*this.value)'; // wrap in functions to keep values within range / round values if needed if (min !== undefined) { str = 'Math.max(' + str + ', ' + min + ')'; } if (max !== undefined) { str = 'Math.min(' + str + ', ' + max + ')'; } if (round) { str = 'Math.round(' + str + ')'; } return str; } /** @private @description Turn a template into a script that outputs values in place of ? */ function compileTemplate(template, from, to, max, min, round) { // no template? That's easy. if (!template) { return buildValueCalculator(from, to, max, min, round); } var templateParts = template.split('?'), templatePart, str = '"' + templateParts[0].replace(/"/g, '\\"') + '"', // discover which values are arrays Array = window.Array, fromIsArray = from.constructor === Array, toIsArray = to.constructor === Array, maxIsArray = max !== undefined && max.constructor === Array, minIsArray = min !== undefined && min.constructor === Array, roundIsArray = round.constructor === Array, iMinusOne = 0; for (var i = 1, leni = templateParts.length; i < leni; i++, iMinusOne++) { templatePart = templateParts[i]; if ( templateParts[iMinusOne].slice(-1) === '\\' ) { // the user wants a literal question mark, put it back str += '+"?"'; } else { // remove trailing slash, it's being used to escape a ? if ( templatePart.slice(-1) === '\\' ) { templatePart = templatePart.slice(0, -1); } str += '+' + buildValueCalculator( fromIsArray ? from[iMinusOne] : from, toIsArray ? to[iMinusOne] : to, maxIsArray ? max[iMinusOne] : max, minIsArray ? min[iMinusOne] : min, roundIsArray ? round[iMinusOne] : round ) + '+"' + templatePart.replace(/"/g, '\\"') + '"'; } } return str; } /** @private @description Builds the function for an animation object's frame listener This function animatate object properties as instructed by #prop */ function buildFunction(anim, targetIndex, propName, conf) { var targets = anim._targets, // this is going to be our listener for the frame event functionStr = anim._funcStr, func; functionStr += 'var target=targets[' + targetIndex + '];' + 'target["' + propName.replace(/"/g, '\\"') + '"]=' + compileTemplate(conf.template, conf.from, conf.to, conf.max, conf.min, conf.round) + ';'; // retain new function string anim._funcStr = functionStr; // eval to create a single function to be called func = anim._evalFunc(functionStr, targets); // remove old listener & add new one anim.detach('frame', anim._propFunc).on('frame', func); // retain new func so we can remove it later anim._propFunc = func; func = functionStr = undefined; } /** @private @description Determines the value(s) to animate from */ function getFromVals(propValue, conf) { var results, template = conf.template, templateRegexStr; // this is easy if from values are already specified // or there isn't a template to follow if (conf.from !== undefined || !template) { return conf.from || propValue; } // turn the template into a regular expression, turning the ? into regex for detecting numbers templateRegexStr = glow.util.escapeRegex(template).replace(/([^\\]|^)\\\?/g, '$1(\\-?(?:\\d+)?(?:\\.\\d+)?)'); results = new RegExp(templateRegexStr).exec(propValue); if (!results) { throw new Error('glow.anim.Anim#prop: Could not detect start values using template: ' + template); } else { return Array.prototype.slice.call(results, 1); } } /** @name glow.anim.Anim#prop @function @description Animate a property of an object. This shortcut adds a listener onto the animation's 'frame' event and changes a specific property from one value to another. Values can be simple, such as '42', or more complex, such as 'rgba(255, 255, 0, 0.8)' Before calling this, set the target object via {@link glow.anim.Anim#target}. @param {string} propertyName Name of the property to animate. @param {Object} conf Animation configuration object. All configuration properties are optional with the exception of 'to', and 'from' in some cases (conditions below). @param {string} [conf.template] Template for complex values Templates can be used for values which are strings rather than numbers. Question-marks are used within templates as placeholders for animated values. For instance, in the template '?em' the question-mark would be replaced with a number resulting in animated values like '1.5em'. Multiple Question-marks can be used for properties with more than one animated value, eg 'rgba(?, ?, ?, ?)'. The values will be animated independently. A literal question-mark can be placed in a template by preceeding it with a backslash. @param {number|number[]} [conf.from] Value(s) to animate from. This can be a single number, or an array of numbers; one for each question-mark in the template. If omitted, the from value(s) will be taken from the object. This will fail if the current value is undefined or is in a format different to the template. @param {number|number[]} conf.to Value(s) to animate to. This can be a single number, or an array of numbers; one for each question-mark in the template. @param {boolean|boolean[]} [conf.round=false] Round values to the nearest whole number. Use this to prevent the property being set to a fractional value. This can be a single boolean, or an array of booleans; one for each question-mark in the template. This is useful for templates like 'rgba(?, ?, ?, ?)', where the rgb values need to be whole numbers, but the alpha value is between 0-1. @param {number|number[]} [conf.min] Minimum value(s) Use this to stop values animating beneath certain values. Eg, some tweens go beyond their end position, but heights cannot be negative. This can be a single number, or an array of numbers; one for each question-mark in the template. 'undefined' means no restriction. @param {number|number[]} [conf.max] Maximum value(s) Use this to stop values animating beyond certain values. Eg, some tweens go beyond their end position, but colour values cannot be greater than 255. This can be a single number, or an array of numbers; one for each question-mark in the template. 'undefined' means no restriction. @returns this @example // Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween new glow.anim.Anim(5, { tween: 'easeOut' }).target(feGaussianBlurElm).prop('stdDeviation', { from: 0, to: 8 }).start(); @example // Animate a CSS property we don't support in glow.NodeList#anim // This rotates a Mozilla CSS gradient var styleObject = glow('#nav').prop('style'); new glow.anim.Anim(10).target(styleObject).prop('background', { // the question-mark in the template is replaced with the animate value template: '-moz-linear-gradient(?deg, red, blue)' from: 0, to: 360 }).start(); @example // Animate a CSS property we don't support in glow.NodeList#anim // This changes the colour of a webkit drop shadow from yellow to blue var styleObject = glow('#nav').prop('style'); new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', { // the ? in the template are replaced with the animate values template: 'rgb(?, ?, ?) 0px 4px 14px' // provide a 'from' and 'to' value for each question-mark from: [255, 255, 0], to: [0, 0, 255], // round the value, colours can't be fractional round: true }).start(); */ AnimProto.prop = function(propName, conf) { /*!debug*/ if (arguments.length !== 2) { glow.debug.warn('[wrong count] glow.anim.Anim#prop expects 2 arguments, not ' + arguments.length + '.'); } if (typeof propName !== 'string') { glow.debug.warn('[wrong type] glow.anim.Anim#prop expects string as "propName" argument, not ' + typeof propName + '.'); } if (typeof conf !== 'object') { glow.debug.warn('[wrong type] glow.anim.Anim#prop expects object as "conf" argument, not ' + typeof conf + '.'); } if (conf.to === undefined || (!conf.to.push && typeof conf.to !== 'number') ) { glow.debug.warn('[wrong type] glow.anim.Anim#prop expects number/array as "conf.to" argument, not ' + typeof conf.to + '.'); } if (conf.from !== undefined && (!conf.from.push && typeof conf.from !== 'number') ) { glow.debug.warn('[wrong type] glow.anim.Anim#prop expects number/array as "conf.from" argument, not ' + typeof conf.from + '.'); } if (conf.template !== undefined && typeof conf.template !== 'string') { glow.debug.warn('[wrong type] glow.anim.Anim#prop expects string as "conf.template" argument, not ' + typeof conf.template + '.'); } if (this._targets.length === 0) { glow.debug.warn('[unmet prerequisite] glow.anim.Anim#target must be called before glow.anim.Anim#prop'); } /*gubed!*/ var targetIndex = this._targets.length - 1, target = this._targets[targetIndex]; // default conf conf = glow.util.apply({ from: getFromVals(target[propName], conf), round: false }, conf); buildFunction(this, targetIndex, propName, conf); return this; }; }); Glow.provide(function(glow) { var undefined, AnimProto = glow.anim.Anim.prototype; /** @private @description Mirrors a tween */ function mirrorTween(tween) { return function(t) { return tween(1 - t); } } /** @name glow.anim.Anim#_preReverseTween @private @type function @description This is the tween before it was reversed This means that anim.reverse().reverse() doesn't wrap the tween function twice, it stores it here so it can reinstate it. */ /** @name glow.anim.Anim#reversed @private @type boolean @description Is the animation in a reversed state? This starts off as false, and is true if {@link glow.anim.Anim#reverse reverse} is called. If reverse is called again, this is false. This is useful in 'complete' listeners to determine where the animation ended. */ AnimProto.reversed = false; /** @name glow.anim.Anim#reverse @function @description Reverses this animation Adjusts the tween of this animation so it plays in reverse. If the animation is currently playing, it will continue to play. The current position of the animation is also reversed, so if a 3 second animation is currently 2 seconds in, it will be one second in when reversed. This is handy for animations that do something on (for example) mouseenter, then need to animate back on mouseleave @returns this @example // change a nav item's background colour from white to yellow // when the mouse is over it, and back again when the mouse // exits. // // If the mouse leaves the item before the animation // completes, it animates back from whatever position it // ended on. glow('#nav').delegate('mouseenter', 'li', function() { var fadeAnim = glow(this).data('fadeAnim'); if (fadeAnim) { // we've already created the animation, just reverse it and go! fadeAnim.reverse().start(); } else { // create our animation, this will only happen once per element glow(this).data('fadeAnim', glow(this).anim(0.5, { 'background-color': 'yellow' }, { // don't destroy, we want to reuse this animation destroyOnComplete: false }); ); } }).delegate('mouseleave', 'li', function() { // Get our animation, reverse it and go! glow(this).data('fadeAnim').reverse().start(); }); */ AnimProto.reverse = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.anim.Anim#reverse expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ var newPosition = this.position && (1 - this.position / this.duration) * this.duration, oldTween = this.tween; // set reversed property this.reversed = !this.reversed; // reverse the tween this.tween = this._preReverseTween || mirrorTween(this.tween); this._preReverseTween = oldTween; return this.goTo(newPosition); } /** @name glow.anim.Anim#pingPong @function @description Alters the animation so it plays forward, then in reverse The duration of the animation is doubled. @returns this @example // Fades #myDiv to red then back to its original colour // The whole animation takes 2 seconds glow('#myDiv').anim(1, { 'background-color': 'red' }).pingPong(); */ AnimProto.pingPong = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.anim.Anim#pingPong expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ var oldTween = this.tween, oldTweenReversed = mirrorTween(oldTween); // double the length of the animation this.duration *= 2; this.tween = function(t) { return (t < 0.5) ? oldTween(t * 2) : oldTweenReversed( (t - 0.5) * 2 ); } // invalidate the stored reversed tween this._preReverseTween = undefined; this.reversed = false; return this.goTo(this.position / 2); } }); Glow.provide(function(glow) { var undefined, TimelineProto, Anim = glow.anim.Anim; /** @private @description Listener for the start event on the sync anim the timeline uses 'this' is the Timeline */ function animStart(e) { this.fire('start', e); this.playing = !e.defaultPrevented(); } /** @private @description Listener for the stop event on the sync anim the timeline uses 'this' is the Timeline */ function animStop(e) { this.fire('stop', e); this.playing = e.defaultPrevented(); } /** @private @description Listener for the frame event on the sync anim the timeline uses 'this' is the Timeline */ function animFrame(e) { this.goTo(this._anim.position); // if we're still playing, fire frame if (this._anim.playing) { this.fire('frame', e); } } /** @private @description Listener for the complete event on the sync anim the timeline uses 'this' is the Timeline */ function animComplete(e) { // mirror .loop this._anim.loop = this.loop; // fire complete with same event object so it can be cancelled by user this.fire('complete', e); // find out if we're going to loop, set .playing var loop = this.playing = ( this.loop || e.defaultPrevented() ); // if we're not looping, destroy if (!loop && this.destroyOnComplete) { this.destroy(); } } /** @name glow.anim.Timeline @extends glow.events.Target @class @description Sequence and synchronise multiple animations This can be used to easily chain animations together and ensure that multiple animations stay in sync with each other. @param {Object} [opts] Options object. @param {boolean} [opts.loop=true] Loop the animation. Looped timelines will fire a 'complete' event on each loop. @param {boolean} [opts.destroyOnComplete=true] Destroy animations in the timeline once it completes (unless it loops). This will free any DOM references the animations may have created. Once the animations are destroyed, the timeline cannot be started again. @example // play 3 animations one after another new glow.anim.Timeline().track(anim1, anim2, anim3).start(); @example // play 2 animations at the same time new glow.anim.Timeline() .track(anim1) .track(anim2) .start(); @example // play 2 animations with a second pause in between new glow.anim.Timeline().track(anim1, 1, anim2).start(); @example // Make a 'mexican wave' // #waveContainer contains 100 divs absolutely positioned next to each other var animTimeline = new glow.anim.Timeline({ loop: true }); //create a wave up & wave down anim for each div var wavingDivs = glow("#waveContainer div").each(function(i) { var div = glow(this); animTimeline.track( // add a pause to the start of the anim, this creates the wave effect (i / 100), // move up & down div.anim(1, { top: [70, 0] }).pingPong() ); }); animTimeline.start(); */ function Timeline(opts) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.anim.Timeline expects 0 or 1 arguments, not ' + arguments.length + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.anim.Iimeline expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ opts = opts || {}; this.destroyOnComplete = (opts.destroyOnComplete !== false); this.loop = !!opts.loop; this._tracks = []; this._currentIndexes = []; this._startPos = []; // create an animation to sync the timeline this._anim = new Anim(0, { destroyOnComplete: false, tween: 'linear' }) .on('start', animStart, this) .on('stop', animStop, this) .on('frame', animFrame, this) .on('complete', animComplete, this); } glow.util.extend(Timeline, glow.events.Target); TimelineProto = Timeline.prototype; /** @name glow.anim.Timeline#duration @type number @description Length of the animation in seconds // implementation note: (delete this later) This will need to be generated after each call to #track Won't be too expensive, just work out the length of the new track and Math.max(newTrack, this.duration) */ TimelineProto.duration = 0; /** @name glow.anim.Timeline#position @type number @description Position of the animation in seconds */ TimelineProto.position = 0; /** @name glow.anim.Timeline#playing @description true if the animation is playing. @returns {boolean} */ TimelineProto.playing = false; /** @name glow.anim.Timeline#loop @description Loop the animation? This value can be changed while the animation is playing. Looped animations will fire a 'complete' event on each loop. @returns {boolean} */ /** @name glow.anim.Timeline#destroyOnComplete @type boolean @description Destroy the animation once it completes (unless it loops)? This will free any DOM references the animation may have created. Once the animation is destroyed, it cannot be started again. */ /** @name glow.anim.Timeline#_tracks @private @type Array[] @description An array of arrays. Each array represents a track, containing a combination of animations and functions */ /** @name glow.anim.Timeline#_currentIndexes @private @type number[] @description Array of the current indexes within _tracks The indexes refer to which items that were last sent a .goTo90 */ /** @name glow.anim.Timeline#_startPos @private @type Array[] @description Mirrors _tracks Contains the start positions of the items in _tracks */ /** @name glow.anim.Timeline#_anim @private @type glow.anim.Anim @description The single animation used to fire frames for this animation */ /** @name glow.anim.Timeline#_lastPos @private @type number @description Last position rendered */ TimelineProto._lastPos = 0; /** @name glow.anim.Timeline#start @function @description Starts playing the animation @param {number} [start] Position to start the animation at, in seconds. By default, this will be the last position of the animation (if it was stopped) or 0. @returns this */ TimelineProto.start = function() { this._anim.start(); return this; }; /** @name glow.anim.Timeline#stop @function @description Stops the animation playing. Stopped animations can be resumed by calling {@link glow.anim.Timeline#start start}. @returns this */ TimelineProto.stop = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.anim.Timeline#stop expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ var i = this._tracks.length, item; this._anim.stop(); // check in case the event has been cancelled if (!this._anim.playing) { while (i--) { // get the current playing item for this track item = this._tracks[i][ this._currentIndexes[i] ]; // check there is an item playing if (item) { item.fire('stop'); item.playing = false; } } } return this; }; /** @name glow.anim.Timeline#destroy @function @description Destroys all animations in the timeline & detaches references to DOM nodes This frees memory & is called automatically when the animation completes @returns undefined */ TimelineProto.destroy = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.anim.Timeline#destroy expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ var i = this._tracks.length, j, item; // destroy animations in tracks while (i--) { j = this._tracks[i].length; while (j--) { item = this._tracks[i][j]; item.destroy && item.destroy(); } } // destroy syncing animation this._anim.destroy(); // remove listeners glow.events.removeAllListeners( [this] ); this._tracks = undefined; }; /** @private @function @description Moves a timeline forward onto timeline.position This deals with moving all the tracks forward from their current position to the new position. This is done on every frame, via timeline.goTo */ function moveForward(timeline) { var i = timeline._tracks.length, track, item, itemIndex, itemStart, timelinePosition = timeline.position; while (i--) { track = timeline._tracks[i]; itemIndex = timeline._currentIndexes[i]; while ( item = track[itemIndex] ) { itemStart = timeline._startPos[i][itemIndex]; // deal with functions in the timeline if (typeof item === 'function') { item(); itemIndex++; break; } // deal with animations in the timeline else if (timelinePosition - itemStart >= item.duration) { // the animation we're currently playing has come to // an end, play the last frame and move on to the next item.goTo(item.duration).fire('complete'); item.playing = false; } else { // the animation we're playing is somewhere in the middle if (!item.playing) { // ohh, we're just starting this animation item.fire('start'); item.playing = true; } item.goTo(timelinePosition - itemStart); // we're not done with this item, break break; } itemIndex++; } timeline._currentIndexes[i] = itemIndex; } } /** @private @function @description This goes through all animations that start after the new position & before the previous position and calls their first frames. */ function moveBackward(timeline) { var i = timeline._tracks.length, j, track, item, itemStart, timelinePosition = timeline.position; while (i--) { track = timeline._tracks[i]; j = timeline._currentIndexes[i] + 1; while (j--) { item = track[j]; if (!item) { continue; } // we don't need to reset items before the new position, // their frames are rendered by 'moveForward' if ( timeline._startPos[i][j] < timeline.position ) { break; } // we only want to deal with animations if (typeof item !== 'function') { item.goTo(0); } } timeline._currentIndexes[i] = j; } // as a shortcut, we use 'moveForward' to trigger the frame for the new position // on the current items moveForward(timeline); } /** @name glow.anim.Timeline#goTo @function @description Goes to a specific point in the animation. @param {number} position Position in the animation to go to, in seconds @example // move the animation to 2.5 seconds in // If the animation is playing, it will continue to play from the new position. // Otherwise, it will simply move to that position. myTimeline.goTo(2.5); @returns {glow.anim.Timeline} */ TimelineProto.goTo = function(position) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.anim.Timeline#goTo expects 1 argument, not ' + arguments.length + '.'); } if (typeof position !== 'number') { glow.debug.warn('[wrong type] glow.anim.Timeline#goTo expects number as "position" argument, not ' + typeof position + '.'); } /*gubed!*/ var resetAll; if (position > this.duration) { position = this.duration; } else if (position < 0) { position = 0; } this.position = position; (position < this._lastPos) ? moveBackward(this) : moveForward(this); this._lastPos = position; return this; }; /** @private @description This method is applied to animations / timeline when they're adopted */ function methodNotAllowed() { throw new Error('Cannot call this method on items contained in a timeline'); } /** @private @description Overwrite methods on animations / timelines that no longer apply */ function adoptAnim(anim) { anim.stop(); anim.start = anim.stop = anim.reverse = anim.pingPong = methodNotAllowed; } /** @name glow.anim.Timeline#track @function @description Add a track of animations to the timeline Animations in a track will run one after another. Each track runs at the same time, always staying in sync. @param {number|function|glow.anim.Anim|glow.anim.Timeline} item+ Item to add to the timelines Animation timelines can be placed within animation timelines Numbers will be treated as number of seconds to pause before the next item. Functions will be called. If the function takes 0.5 seconds to call, the next animation will start 0.5 seconds in, keeping everything in sync. @returns this */ TimelineProto.track = function() { /*!debug*/ if (arguments.length < 1) { glow.debug.warn('[wrong count] glow.anim.Timeline#track expects at least 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ var args = arguments, tracksLen = this._tracks.length, track = this._tracks[tracksLen] = [], trackDuration = 0, trackDurations = this._startPos[tracksLen] = [], trackItem; // loop through the added tracks for (var i = 0, leni = args.length; i < leni; i++) { trackItem = track[i] = args[i]; if (trackItem instanceof Anim || trackItem instanceof Timeline) { adoptAnim(trackItem); } // convert numbers into empty animations else if (typeof trackItem === 'number') { trackItem = track[i] = new Anim(trackItem); } /*!debug*/ else if (typeof trackItem !== 'function') { glow.debug.warn('[wrong type] glow.anim.Timeline#track all arguments must be number/glow.anim.Anim/glow.anim.Timeline/function, arg ' + i + ' is ' + typeof trackItem + '.'); } /*gubed!*/ // record the start time for this anim trackDurations[i] = trackDuration; trackDuration += trackItem.duration || 0; } // update duration and anim duration this._anim.duration = this.duration = Math.max(this.duration, trackDuration); this._currentIndexes[tracksLen] = 0; return this; }; /** @name glow.anim.Timeline#event:start @event @description Fires when an animation starts. Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from starting. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Timeline#event:frame @event @description Fires on each frame of the animation @param {glow.events.Event} event Event Object */ /** @name glow.anim.Timeline#event:stop @event @description Fires when an animation is stopped before completing Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) prevents this animation from stopping. @param {glow.events.Event} event Event Object */ /** @name glow.anim.Timeline#event:complete @event @description Fires when an animation completes Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) causes the animation to loop. @param {glow.events.Event} event Event Object @example // Make an animation loop 5 times var loopCount = 5; myTimeline.on('complete', function() { return !!loopCount--; }); */ // export glow.anim.Timeline = Timeline; }); Glow.complete('core', '2.0.0b1'); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/core.js��������������������������������������������������������������������������������100644 � 0 � 0 � 212011 11405426600 11356� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* Copyright 2010 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. */ if(!window.Glow){window.Glow={provide:function(a){a(glow);},complete:function(b,a){glow.version=a;}};window.glow=function(a){return new glow.NodeList(a);};glow.UID="glow"+Math.floor(Math.random()*(1<<30));glow.load=function(){throw new Error("Method load() is not available without glow.js");};}Glow.provide(function(a){});Glow.provide(function(h){var b=navigator.userAgent.toLowerCase(),j=[0,NaN],e=(/opera[\s\/]([\w\.]+)/.exec(b)||j)[1],a=e?NaN:(/msie ([\w\.]+)/.exec(b)||j)[1],c=(/rv:([\w\.]+).*gecko\//.exec(b)||j)[1],i=(/applewebkit\/([\w\.]+)/.exec(b)||j)[1],d=(/khtml\/([\w\.]+)/.exec(b)||j)[1],k=parseFloat,g={};g.gecko=k(c);g.ie=k(a);g.opera=k(e);g.webkit=k(i);g.khtml=k(d);g.standardsMode=document.compatMode!="BackCompat"&&(!g.ie||g.ie>=6);g.version=a||c||i||e||d||"";h.env=g;});Glow.provide(function(h){var l=[],e=[],n=0,o=false;h._readyBlockers={};h.ready=function(i){if(this.isReady){i();}else{l.push(i);}return h;};h.onDomReady=function(i){if(h.isDomReady){i();}else{e.push(i);}};h._addReadyBlock=function(i){if(typeof h._readyBlockers[i]==="undefined"){h._readyBlockers[i]=0;}h._readyBlockers[i]++;h.isReady=false;n++;return h;};h._removeReadyBlock=function(i){if(h._readyBlockers[i]){h._readyBlockers[i]--;n--;if(!n){h.isReady=true;k();}}return h;};if(h._build){for(var d=0,g=h._build.loading.length;d<g;d++){h._addReadyBlock("glow_loading_"+h._build.loading[d]);}for(var b=0,m=h._build.callbacks.length;b<m;b++){if(h._addReadyBlock){h._addReadyBlock("glow_loading_loadedcallback");}}}function a(){h.isDomReady=true;for(var p=0,j=e.length;p<j;p++){e[p]();}}function k(){if(o){return;}o=true;while(l.length){var i=l.shift();i(h);if(n){break;}}o=false;}var c=function(){if(h.isDomReady){return;}h._addReadyBlock("glow_domReady");function i(){k();h._removeReadyBlock("glow_domReady");}if(document.readyState=="complete"){i();}else{if(h.env.ie&&document.attachEvent){if(document.documentElement.doScroll&&window==top){(function(){try{document.documentElement.doScroll("left");}catch(j){setTimeout(arguments.callee,0);return;}i();})();}else{document.attachEvent("onreadystatechange",function(){if(document.readyState=="complete"){document.detachEvent("onreadystatechange",arguments.callee);i();}});}}else{if(document.readyState){(function(){if(/loaded|complete/.test(document.readyState)){i();}else{setTimeout(arguments.callee,0);}})();}else{if(document.addEventListener){document.addEventListener("DOMContentLoaded",function(){document.removeEventListener("DOMContentLoaded",arguments.callee,false);i();},false);}else{throw new Error("Unable to bind glow ready listener to document.");}}}}};h.notSupported=(h.env.ie<6||(h.env.gecko<1.9&&!/^1\.8\.1/.test(h.env.version))||h.env.opera<9||h.env.webkit<412);h.isSupported=!h.notSupported;if(h.notSupported){h._addReadyBlock("glow_browserSupport");}c();});Glow.provide(function(glow){var util={},undefined,TYPES={UNDEFINED:"undefined",OBJECT:"object",NUMBER:"number",BOOLEAN:"boolean",STRING:"string",ARRAY:"array",FUNCTION:"function",NULL:"null"},TEXT={AT:"@",EQ:"=",DOT:".",EMPTY:"",AND:"&",OPEN:"(",CLOSE:")"},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};function _getType(object){var typeOfObject=typeof object,constructorStr,type;if(object===null){return"null";}else{if(isFunction(object)){return"Function";}else{if(isArray(object)){return"Array";}else{if(typeOfObject==="object"){constructorStr=object.constructor.toString();if(/^function (\S+?)\(/.test(constructorStr)){type=RegExp.$1;if(type==="Object"){return"object";}else{return type;}}}}}}return typeOfObject;}function isArray(o){return{}.toString.call(o)==="[object Array]";}function isFunction(o){return{}.toString.call(o)==="[object Function]";}util.getType=_getType;util.apply=function(destination,source){destination=destination||{};source=source||{};for(var i in source){if(source.hasOwnProperty(i)){destination[i]=source[i];}}return destination;};util.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){util.apply(sub.prototype,additionalProperties);}};util.escapeRegex=function(str){return String(str).replace(/[.*+?^${}()|[\]\/\\]/g,"\\$&");};util.encodeUrl=function(object){var type=_getType(object),paramsList=[],listLength=0;for(var key in object){type=_getType(object[key]);if(type==="Array"){for(var i=0,l=object[key].length;i<l;i++){paramsList[listLength++]=key+"="+encodeURIComponent(object[key][i]);}}else{paramsList[listLength++]=key+"="+encodeURIComponent(object[key]);}}return paramsList.join("&");};util.decodeUrl=function(text){var result={},keyValues=text.split(/[&;]/),thisPair,key,value;for(var i=0,leni=keyValues.length;i<leni;i++){thisPair=keyValues[i].split("=");if(thisPair.length<2){key=keyValues[i];value="";}else{key=""+decodeURIComponent(thisPair[0]);value=""+decodeURIComponent(thisPair[1]);}switch(typeof result[key]){case"string":result[key]=[result[key],value];break;case"undefined":result[key]=value;break;default:result[key].push(value);}}return result;};util.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){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");}};util.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){}}throw new Error("glow.data.decodeJson: cannot decode item");};util.trim=function(str){return str.trim?str.trim():str.replace(/^\s*((?:[\S\s]*\S)?)\s*$/,"$1");};util.interpolate=function(template,data,opts){var placeHolderRx,leftDelimiter,rightDelimiter,div;opts=opts||{};if(opts.escapeHtml){div=glow("<div></div>");}if(opts.delimiter==undefined){placeHolderRx=/\{[^{}]+\}/g;}else{leftDelimiter=opts.delimiter.substr(0,1).replace(regexEscape,"\\$1");rightDelimiter=opts.delimiter.substr(1,1).replace(regexEscape,"\\$1")||leftDelimiter;placeHolderRx=new RegExp(leftDelimiter+"[^"+leftDelimiter+rightDelimiter+"]+"+rightDelimiter,"g");}return template.replace(placeHolderRx,function(placeholder){var key=placeholder.slice(1,-1),keyParts=key.split("."),val,i=0,len=keyParts.length;if(key in data){val=data[key];}else{val=data;for(;i<len;i++){if(keyParts[i] in val){val=val[keyParts[i]];}else{return placeholder;}}}if(opts.escapeHtml){val=div.text(val).html();}return val;});};util.cookie=function(key,value,opts){var date="",expires="",path="",domain="",secure="",keyValues,thisPair,key,val,cookieValues;if(opts){if(opts.expires){if(typeof opts.expires==="number"){date=new Date();date.setTime(date.getTime()+(opts.expires*24*60*60*1000));}else{date=opts.expires;}expires="; expires="+date.toUTCString();}path=opts.path?"; path="+(opts.path):"";domain=opts.domain?"; domain="+(opts.domain):"";secure=opts.secure?"; secure":"";}else{opts={};}if(typeof key==="string"&&typeof value==="string"){document.cookie=key+"="+encodeURIComponent(value)+expires+path+domain+secure;}else{if(typeof key==="object"){for(var p in key){document.cookie=p+"="+encodeURIComponent(key[p])+expires+path+domain+secure;}}else{cookieValues={};if(document.cookie&&document.cookie!=""){keyValues=document.cookie.split(/; ?/);for(var i=0,leni=keyValues.length;i<leni;i++){thisPair=keyValues[i].split("=");cookieValues[thisPair[0]]=decodeURIComponent(thisPair[1]);}}if(typeof key==="string"){return cookieValues[key];}else{if(typeof key==="undefined"){return cookieValues;}}}}};util.removeCookie=function(key){util.cookie(key,"",{expires:-1});};glow.util=util;});Glow.provide(function(h){var j={};var c={},a=1,e=1,b="__eventId"+h.UID;j.addListeners=function(q,k,s,t){var r=[],l,o,m,n;var p=q.length;while(p--){l=q[p][b];if(!l){l=q[p][b]=e++;}o=[s,t];m=c[l];if(!m){m=c[l]={};}n=m[k];if(!n){m[k]=[o];}else{n[n.length]=o;}}return j;};j._getPrivateEventKey=function(k){if(!k[b]){k[b]=e++;}return k[b];};j.fire=function(m,l,o){if(!o){o=new j.Event();}else{if(o.constructor===Object){o=new j.Event(o);}}for(var n=0,k=m.length;n<k;n++){g(m[n],l,o);}return o;};function g(r,n,k,s){var l=r[b],p,q;k.attachedTo=k.attachedTo||r;if(!l||!c[l]){return k;}p=c[l][n];if(!p){return k;}p=p.slice(0);for(var m=0,o=p.length;m<o;m++){q=p[m][0].call((p[m][1]||s||r),k);if(q===false){k.preventDefault();}}return k;}j._callListeners=g;j.removeAllListeners=function(l){var k,m=l.length;while(m--){k=l[m][b];if(!k){return false;}else{delete c[k];}}return true;};j.removeListeners=function(q,n,r){var m,l,p=q.length;while(p--){m=q[p][b];if(!m||!c[m]){return j;}l=c[m][n];if(!l){return j;}for(var o=0,k=l.length;o<k;o++){if(l[o][0]===r){l.splice(o,1);break;}}}return j;};j._copyDomEvents=function(r,s){var k,n=r.length,m,t,q,p,o,l;while(n--){k=r[n][b];q=c[k];if(k){l=s.slice(n,n+1);for(o in q){p=q[o];for(m=0,t=p.length;m<t;m++){l.on(o,p[m][0],p[m][1]);}}}}};j._getListeners=function(l){var k=l[b];if(!k){return{};}else{return c[k];}};j.Target=function(){};var d=j.Target.prototype;j.Target.extend=function(k){h.util.apply(k,h.events.Target.prototype);};d.on=function(k,m,l){h.events.addListeners([this],k,m,l);return this;};d.detach=function(k,l){h.events.removeListeners(this,k,l);return this;};d.fire=function(k,l){if(!l){l=new j.Event();}else{if(l.constructor===Object){l=new j.Event(l);}}return g(this,k,l);};j.Event=function(k){if(k){h.util.apply(this,k);}};var i=j.Event.prototype;i.preventDefault=function(){this._defaultPrevented=true;};i.defaultPrevented=function(){return this._defaultPrevented;};h.events=j;});Glow.provide(function(l){var i=window.document,g=undefined,k=[],n=l.events,d=n._callListeners,m=n._getPrivateEventKey,b=(i.createElement("div").onactivate!==undefined);function e(q,p){this.nativeEvent=q;this.type=q.type;this.source=q.target||q.srcElement||undefined;if(this.source&&this.source.nodeType===3){this.source=this.source.parentNode;}this.related=q.relatedTarget||(this.type=="mouseover"?q.fromElement:q.toElement);this.shiftKey=(q.shiftKey===g)?g:!!q.shiftKey;this.altKey=(q.altKey===g)?g:!!q.altKey;this.ctrlKey=(q.ctrlKey===g)?g:!!q.ctrlKey;this.button=l.env.ie?(q.button&1?0:q.button&2?2:1):q.button;if(q.pageX!==g||q.pageY!==g){this.mouseTop=q.pageY;this.mouseLeft=q.pageX;}else{if(q.clientX!==g||q.clientY!==g){this.mouseTop=q.clientY+i.body.scrollTop+i.documentElement.scrollTop;this.mouseLeft=q.clientX+i.body.scrollLeft+i.documentElement.scrollLeft;}}if(this.type=="mousewheel"){this.wheelDelta=q.wheelDelta?q.wheelDelta/120:q.detail?-q.detail/3:0;}for(var o in p){this[o]=p[o];}}l.util.extend(e,n.Event,{preventDefault:function(){var o=this.nativeEvent;if(o){o.preventDefault&&o.preventDefault();o.returnValue=false;}n.Event.prototype.preventDefault.call(this);return this;},stopPropagation:function(){var o=this.nativeEvent;if(o){o.cancelBubble=true;o.stopPropagation&&o.stopPropagation();}return this;}});n._addDomEventListener=function(p,o){var r=p.length,q,s;while(r--){q=p[r];s=m(q);if(!k[s]){k[s]={};}if(k[s][o]&&k[s][o].count>0){k[s][o].count++;continue;}k[s][o]={count:1};(function(t){var u=a(t,o);if(t.addEventListener){t.addEventListener(u.domName,u,(o==="focus"||o==="blur"));}else{if(t.attachEvent){t.attachEvent("on"+u.domName,u);}}k[s][o].callback=u;})(q);}};function a(p,o){var q;if(o==="mouseenter"||o==="mouseleave"){q=function(s){var x=new e(s),t,r,w=j(p,o,x);w.push([p]);for(var v=0,u=w.length;v<u;v++){t=w[v][0];r=w[v][1];if(!new l.NodeList(t).contains(x.related)){d(p,r?o+"/"+r:o,x,t);}}return !x.defaultPrevented();};q.domName=(o==="mouseenter")?"mouseover":"mouseout";}else{if(b&&(o==="focus"||o==="blur")){q=function(r){var s=r.srcElement.nodeName;if(s!=="HTML"&&s!=="BODY"){c(p,o,new e(r));}};q.domName=(o==="focus")?"activate":"deactivate";}else{q=function(r){var s=new e(r);c(p,o,s);return !s.defaultPrevented();};q.domName=o;}}return q;}n._removeDomEventListener=function(p,o){var r=p.length,q,u,t,s;while(r--){q=p[r];u=m(q);if(!k[u]||!k[u][o]){continue;}t=k[u][o];if(!--t.count){s=t.callback;if(q.removeEventListener){q.removeEventListener(s.domName,s,(o==="focus"||o==="blur"));}else{if(q.detachEvent){q.detachEvent("on"+s.domName,s);}}k[u][o]=undefined;}}};var h={};n._registerDelegate=function(q,p,o){var t,r=q.length,s;while(r--){t=m(q[r]);h[t]=h[t]||{};s=h[t][p]=h[t][p]||{};s[o]=s[o]+1||1;}};n._unregisterDelegate=function(q,p,o){var t,s,r=q.length;while(r--){t=m(q[r]);if(!h[t]||!(s=h[t][p])){continue;}if(s[o]&&--s[o]===0){delete s[o];}}};var j=n._getDelegateMatches=function(q,p,u){var w=m(q),v,o,t,s=[];if(h[w]&&(v=h[w][p])){for(o in v){t=u.source;while(t&&t!==q){if(l._sizzle.matches(o,[t]).length){s.push([t,o]);break;}t=t.parentNode;}}}return s;};var c=n._callDomListeners=function(s,q,t){var o=j(s,q,t);for(var r=0,p=o.length;r<p;r++){t.attachedTo=o[r][0];d(s,q+"/"+o[r][1],t,o[r][0]);}t.attachedTo=s;d(s,q,t);return t;};n.DomEvent=e;});Glow.provide(function(k){var r=window.document,g,i,u=k.env,b,l,s=k.events.DomEvent,t=k.events._callDomListeners,p=k.events._getPrivateEventKey,h={};function q(y){if(b){this.key=a(b);}if(l){this.keyChar=String.fromCharCode(l);}s.call(this,y);}k.util.extend(q,s,{key:"",keyChar:""});function o(A,y,z){if(A.addEventListener){A.addEventListener(y,z,false);}else{if(A.attachEvent){A.attachEvent("on"+y,z);}}}function d(A,y,z){if(A.removeEventListener){A.removeEventListener(y,z,false);}else{if(A.detachEvent){A.detachEvent("on"+y,z);}}}function v(A,y){var z;if(u.gecko||u.opera||u.webkit<525){return !noKeyPress[A];}z=a(A);if(z.length===1||z==="tab"||z==="space"){l=(e[z]||z).charCodeAt(0);return !(u.webkit&&y);}return false;}function w(z){var y,B,A,C={};y=function(D){var G=D.keyCode,F,E;if(!C[G]){b=G;l=g;F=t(z,"keydown",new q(D)).defaultPrevented();C[G]=true;}if(!v(G,F)){E=t(z,"keypress",new q(D)).defaultPrevented();}return !(F||E);};B=function(D){var F,E;l=D.charCode||D.keyCode;F=a(b);if(F.length>1&&F!=="tab"&&F!=="space"){l=g;}E=t(z,"keypress",new q(D)).defaultPrevented();return !E;};A=function(D){var F=D.keyCode,E;b=F;l=g;E=t(z,"keyup",new q(D)).defaultPrevented();C[F]=false;b=g;return !E;};o(z,"keydown",y);o(z,"keypress",B);o(z,"keyup",A);return[1,y,B,A];}k.events._addKeyListener=function(z){var B=z.length,A,y;while(B--){A=z[B];y=p(A);if(h[y]){h[y][0]++;continue;}else{h[y]=w(A);}}};k.events._removeKeyListener=function(z){var B=z.length,A,y,C;while(B--){A=z[B];y=p(A);C=h[y];if(!C){continue;}if(--C[0]===0){d(A,"keydown",C[1]);d(A,"keypress",C[2]);d(A,"keyup",C[3]);h[y]=g;}}};function a(y){if((y>=x&&y<=n)||(y>=j&&y<=c)){return String.fromCharCode(y).toLowerCase();}return m[y]||"unknown"+y;}var x=65,n=90,j=48,c=57,m={8:"backspace",9:"tab",13:"return",16:"shift",17:"control",18:"alt",19:"pause",27:"escape",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"delete",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",109:"-",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scrolllock",188:",",189:"-",190:".",191:"/",192:"'",219:"[",220:"\\",221:"]",222:"#",223:"`",226:"\\"},e={space:" ",tab:"\t"};noKeyPress={};if(u.gecko){m[107]="=";noKeyPress={16:1,17:1,18:1,144:1,145:1};}else{if(u.opera){m[42]="*";m[43]="+";m[47]="/";m[222]="'";m[192]="`";noKeyPress={16:1,17:1,18:1};}else{if(u.webkit||u.ie){m[186]=";";m[187]="=";}}}k.events.KeyboardEvent=q;});Glow.provide(function(k){var g,c,j=window.document,b=Array.prototype.slice,i=Array.prototype.push;function a(e){e&&this.push(e);}g=a.prototype;g.length=0;a._strToNodes=(function(){var m=j.createElement("div"),o=[1,"<table>","</table>"],p=[0,"",""],l=k.env.webkit<526?[0,"","</div>"]:[1,"b<div>","</div>"],e=[3,"<table><tbody><tr>","</tr></tbody></table>"],n={caption:o,thead:o,th:e,colgroup:o,tbody:o,tr:[2,"<table><tbody>","</tbody></table>"],td:e,tfoot:o,option:[1,'<select multiple="multiple">',"</select>"],legend:[1,"<fieldset>","</fieldset>"],link:l,script:l,style:l,"!":l};function q(y){var s=[],v=(/^<([\w!]+)/.exec(y)||[])[1],t=n[v]||p,u=t[0],A=m,w,z=0,x;A.innerHTML=(t[1]+y+t[2]);while(u--){A=A.lastChild;}if(t===o&&y.indexOf("<tbody")===-1){while(x=A.firstChild){if(x.nodeName!="TBODY"){s[z++]=x;}A.removeChild(x);}}else{while(x=A.firstChild){s[z++]=A.removeChild(x);}}return s;}return q;})();var d=function(e){return b.call(e,0);};try{b.call(j.documentElement.childNodes,0);}catch(h){d=function(m){if(m instanceof Object){return b.call(m,0);}var l=m.length,e=[];while(l--){e[l]=m[l];}return e;};}g.push=function(e){if(e){if(typeof e==="string"){if(e.charAt(0)==="<"){e=a._strToNodes(e);}else{e=k._sizzle(e);}i.apply(this,e);}else{if(e.nodeType||e.window==e){if(this.length){i.call(this,e);}else{this[0]=e;this.length=1;}}else{if(e.length!==c){if(e.constructor!=Array){e=d(e);}i.apply(this,e);}}}}return this;};g.eq=function(l){var e=this.length,m=e;if(!(l instanceof a)){l=new a(l);}if(e!=l.length){return false;}while(m--){if(this[m]!==l[m]){return false;}}return true;};g.slice=function(){return new a(b.apply(this,arguments));};g.sort=function(m){var l=d(this),e=m?l.sort(m):k._sizzle.uniqueSort(l);return new a(e);};g.item=function(e){return this.slice(e,(e+1)||this.length);};g.each=function(m){for(var l=0,e=this.length;l<e;l++){if(m.call(this[l],l,this)===false){break;}}return this;};g.filter=function(o){var n=[],l=0;if(typeof o==="string"){n=k._sizzle.matches(o,this);}else{for(var m=0,e=this.length;m<e;m++){if(o.call(this[m],m,this)){n[l++]=this[m];}}}return new a(n);};g.is=function(e){if(!this[0]){return false;}return !!k._sizzle.matches(e,[this[0]]).length;};k.NodeList=a;});Glow.provide(function(n){var e,h=n.NodeList.prototype,g={"class":"className","for":"htmlFor",maxlength:"maxLength"},b="_uniqueData"+n.UID,m=1,c=[];h.addClass=function(o){var p=this.length;while(p--){if(this[p].nodeType===1){l(this[p],o);}}return this;};function l(p,o){if((" "+p.className+" ").indexOf(" "+o+" ")===-1){p.className+=(p.className?" ":"")+o;}}h.attr=function(){var u=arguments,p=u.length,s=this.length,q,o=q=u[0],v="",r,w;if(this.length===0){return(p>1)?this:e;}if(typeof q==="object"){for(o in q){if(!q.hasOwnProperty(o)){continue;}if(n.env.ie<8){v=g[o.toLowerCase()];}var t=s;while(t--){r=this[t];if(r.nodeType!==1){continue;}if(v){r[v]=q[o];}else{r.setAttribute(o,q[o],0);}}}return this;}else{r=this[0];if(r.nodeType!==1){return(p>1)?this:e;}if(p===1){if(n.env.ie&&(o==="href"||o==="src")){value=r.getAttribute(o,2);return(value===null)?"":value;}else{if(r.attributes[o]){return(!r.attributes[o].specified)?"":r.attributes[o].value;}else{if(r.getAttributeNode){w=r.getAttributeNode(o,0);return(w===null)?"":w.value;}else{value=r.getAttribute(o,2);return(value===null)?"":value;}}}}else{if(n.env.ie<8){v=g[o.toLowerCase()];}if(v){r[v]=u[1];}else{r.setAttribute(o,u[1],0);}return this;}}};n.NodeList._copyData=function(r,q){var o=q.length,p;while(o--){p=c[r[o][b]];p&&q.slice(o,o+1).data(p);}};h.data=function(t,v){var r=arguments,p=r.length,o=t,q,u;if(p>1){var s=this.length;while(s--){u=this[s];if(u.nodeType!==1){continue;}q=u[""+b];if(!q){q=m++;u[b]=q;c[q]={};}c[q][t]=v;}return this;}else{if(typeof o==="object"){var s=this.length;while(s--){u=this[s];if(u.nodeType!==1){continue;}q=u[b];if(!q){q=m++;u[b]=q;c[q]={};}for(t in o){c[q][t]=o[t];}}return this;}else{u=this[0];if(u===e||u.nodeType!==1){return e;}if(!(q=u[b])){return e;}if(t!==e){return c[q][t];}return c[q];}}};h.hasAttr=function(o){var p;p=this[0];if(this.length&&p.nodeType===1){if(p.attributes[o]){return !!p.attributes[o].specified;}if(p.hasAttribute){return p.hasAttribute(o);}else{return p.attributes[o]!==e;}}};h.hasClass=function(o){if(this.length&&this[0].nodeType===1){return((" "+this[0].className+" ").indexOf(" "+o+" ")>-1);}};h.prop=function(p,u){var t=p,o=arguments.length;if(this.length===0){return;}if(o===2&&typeof p==="string"){for(var s=0,r=this.length;s<r;s++){if(this[s].nodeType===1){this[s][p]=u;}}return this;}else{if(o===1&&t.constructor===Object){for(var q in t){for(var s=0,r=this.length;s<r;s++){if(this[s].nodeType===1){this[s][q]=t[q];}}}return this;}else{if(o===1&&typeof p==="string"){if(this[0].nodeType===1){return this[0][p];}}else{throw new Error("Invalid parameters.");}}}};h.removeAttr=function(p){var r;for(var q=0,o=this.length;q<o;q++){if(this[q].nodeType===1){if(n.env.ie<8){if((r=g[p.toLowerCase()])){this[q][r]="";}}if(this[q].removeAttribute){this[q].removeAttribute(p);}}}return this;};h.removeClass=function(o){var q;var p=this.length;while(p--){q=this[p];if(q.className){a(q,o);}}return this;};function a(r,p){var o=r.className.split(" "),s=[];o=r.className.split(" ");s=[];var q=o.length;while(q--){if(o[q]!==p){o[q]&&s.unshift(o[q]);}}r.className=(s.length)?s.join(" "):"";}h.removeData=function(q){var s,p=this.length,o;while(p--){s=this[p];o=s[b];if(o!==e){switch(arguments.length){case 0:c[o]=e;s[b]=e;try{delete s[b];}catch(r){s.removeAttribute&&s.removeAttribute(b);}break;case 1:c[o][q]=e;delete c[o][q];break;}}}return this;};h.toggleClass=function(p){var r;for(var q=0,o=this.length;q<o;q++){r=this[q];if(r.className){if((" "+r.className+" ").indexOf(" "+p+" ")>-1){a(r,p);}else{l(r,p);}}}return this;};h.val=function(){var o=arguments,r=o[0],p=0,q=this.length;if(o.length===0){return this[0].nodeName=="FORM"?j(this[0]):i(this[0]);}if(this[0].nodeName=="FORM"){if(!typeof r=="object"){throw"value for FORM must be object";}k(this[0],r);}else{for(;p<q;p++){d(this[p],r);}}return this;};function i(r){var o=r.type,p=r.checked,t=r.value,u=[],q=0;if(o=="radio"){return p?t:"";}else{if(o=="checkbox"){return p?t:"";}else{if(o=="select-one"){return r.selectedIndex>-1?r.options[r.selectedIndex].value:"";}else{if(o=="select-multiple"){for(var s=r.options.length;q<s;q++){if(r.options[q].selected){u[u.length]=r.options[q].value;}}return u;}else{return t;}}}}}function d(q,s){var t=0,p,r=0,w,o,v;if(q.type=="select-one"){for(p=q.options.length;t<p;t++){if(q.options[t].value==s){q.selectedIndex=t;return true;}}return false;}else{if(q.type=="select-multiple"){var u=!!s.push;for(t=0,p=q.options.length;t<p;t++){o=q.options[t];v=o.value;if(u){o.selected=false;for(w=s.length;r<w;r++){if(v==s[r]){o.selected=true;s.splice(r,1);break;}}}else{return o.selected=s==v;}}return false;}else{if(q.type=="radio"||q.type=="checkbox"){q.checked=s==q.value;return s==q.value;}else{q.value=s;return true;}}}}function k(p,x){var o,y,u={},s,t=0,q,v,w,r;for(o in x){y=p[o];if(y&&y[0]&&!y.options){x[o]=x[o]&&x[o].push?x[o]:[x[o]];u.radios=[];u.checkboxesSelects=[];u.multiSelects=[];u.other=[];for(t=0;y[t];t++){r=y[t].type;if(r=="radio"){s="radios";}else{if(r=="select-one"||r=="checkbox"){s="checkboxesSelects";}else{if(r=="select-multiple"){s="multiSelects";}else{s="other";}}}u[s][u[s].length]=y[t];}for(t=0;u.multiSelects[t];t++){x[o]=d(u.multiSelects[t],x[o]);}for(t=0;u.checkboxesSelects[t];t++){d(u.checkboxesSelects[t],"");for(q=0,v=x[o].length;q<v;q++){if(d(u.checkboxesSelects[t],x[o][q])){x[o].slice(q,1);break;}}}for(t=0;u.radios[t];t++){u.radios[t].checked=false;w=false;for(q=0,v=x[o].length;q<v;q++){if(d(u.radios[t],x[o][q])){x[o].slice(q,1);w=true;break;}if(w){break;}}}for(t=0;u.other[t]&&x[o][t]!==undefined;t++){d(u.other[t],x[o][t]);}}else{if(y&&y.nodeName){d(y,x[o]);}}}}function j(p){var w={},v={},s=p.elements,u=s.length,o,y,t,r,x;while(u--){y=s[u];x=y.nodeName.toLowerCase();o=y.name;if(x=="fieldset"||x=="object"||!o){continue;}if(y.type=="checkbox"&&!y.checked){if(!o in w){w[o]=undefined;}}else{if(y.type=="radio"){if(v[o]){v[o][v[o].length]=y;}else{v[o]=[y];}}else{var z=i(y);if(o in w){if(w[o].push){w[o][w[o].length]=z;}else{w[o]=[w[o],z];}}else{w[o]=z;}}}}for(u in v){var q,t=0;for(q=v[u].length;t<q;t++){r=v[u][t];o=r.name;if(r.checked){w[r.name]=r.value;break;}}if(!o in w){alert("15 if name in vals");w[o]=undefined;}}return w;}}); /* * Sizzle CSS Selector Engine - v1.0 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){var q=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,k=0,d=Object.prototype.toString,p=false,j=true;[0,0].sort(function(){j=false;return 0;});var b=function(w,e,z,A){z=z||[];e=e||document;var C=e;if(e.nodeType!==1&&e.nodeType!==9){return[];}if(!w||typeof w!=="string"){return z;}var x=[],t,E,H,s,v=true,u=b.isXML(e),B=w,D,G,F,y;do{q.exec("");t=q.exec(B);if(t){B=t[3];x.push(t[1]);if(t[2]){s=t[3];break;}}}while(t);if(x.length>1&&l.exec(w)){if(x.length===2&&g.relative[x[0]]){E=i(x[0]+x[1],e);}else{E=g.relative[x[0]]?[e]:b(x.shift(),e);while(x.length){w=x.shift();if(g.relative[w]){w+=x.shift();}E=i(w,E);}}}else{if(!A&&x.length>1&&e.nodeType===9&&!u&&g.match.ID.test(x[0])&&!g.match.ID.test(x[x.length-1])){D=b.find(x.shift(),e,u);e=D.expr?b.filter(D.expr,D.set)[0]:D.set[0];}if(e){D=A?{expr:x.pop(),set:a(A)}:b.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&e.parentNode?e.parentNode:e,u);E=D.expr?b.filter(D.expr,D.set):D.set;if(x.length>0){H=a(E);}else{v=false;}while(x.length){G=x.pop();F=G;if(!g.relative[G]){G="";}else{F=x.pop();}if(F==null){F=e;}g.relative[G](H,F,u);}}else{H=x=[];}}if(!H){H=E;}if(!H){b.error(G||w);}if(d.call(H)==="[object Array]"){if(!v){z.push.apply(z,H);}else{if(e&&e.nodeType===1){for(y=0;H[y]!=null;y++){if(H[y]&&(H[y]===true||H[y].nodeType===1&&b.contains(e,H[y]))){z.push(E[y]);}}}else{for(y=0;H[y]!=null;y++){if(H[y]&&H[y].nodeType===1){z.push(E[y]);}}}}}else{a(H,z);}if(s){b(s,C,z,A);b.uniqueSort(z);}return z;};b.uniqueSort=function(s){if(c){p=j;s.sort(c);if(p){for(var e=1;e<s.length;e++){if(s[e]===s[e-1]){s.splice(e--,1);}}}}return s;};b.matches=function(e,s){return b(e,null,null,s);};b.find=function(y,e,z){var x;if(!y){return[];}for(var u=0,t=g.order.length;u<t;u++){var w=g.order[u],v;if((v=g.leftMatch[w].exec(y))){var s=v[1];v.splice(1,1);if(s.substr(s.length-1)!=="\\"){v[1]=(v[1]||"").replace(/\\/g,"");x=g.find[w](v,e,z);if(x!=null){y=y.replace(g.match[w],"");break;}}}}if(!x){x=e.getElementsByTagName("*");}return{set:x,expr:y};};b.filter=function(C,B,F,v){var t=C,H=[],z=B,x,e,y=B&&B[0]&&b.isXML(B[0]);while(C&&B.length){for(var A in g.filter){if((x=g.leftMatch[A].exec(C))!=null&&x[2]){var s=g.filter[A],G,E,u=x[1];e=false;x.splice(1,1);if(u.substr(u.length-1)==="\\"){continue;}if(z===H){H=[];}if(g.preFilter[A]){x=g.preFilter[A](x,z,F,H,v,y);if(!x){e=G=true;}else{if(x===true){continue;}}}if(x){for(var w=0;(E=z[w])!=null;w++){if(E){G=s(E,x,w,z);var D=v^!!G;if(F&&G!=null){if(D){e=true;}else{z[w]=false;}}else{if(D){H.push(E);e=true;}}}}}if(G!==undefined){if(!F){z=H;}C=C.replace(g.match[A],"");if(!e){return[];}break;}}}if(C===t){if(e==null){b.error(C);}else{break;}}t=C;}return z;};b.error=function(e){throw"Syntax error, unrecognized expression: "+e;};var g=b.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(e){return e.getAttribute("href");}},relative:{"+":function(x,s){var u=typeof s==="string",w=u&&!/\W/.test(s),y=u&&!w;if(w){s=s.toLowerCase();}for(var t=0,e=x.length,v;t<e;t++){if((v=x[t])){while((v=v.previousSibling)&&v.nodeType!==1){}x[t]=y||v&&v.nodeName.toLowerCase()===s?v||false:v===s;}}if(y){b.filter(s,x,true);}},">":function(x,s){var v=typeof s==="string",w,t=0,e=x.length;if(v&&!/\W/.test(s)){s=s.toLowerCase();for(;t<e;t++){w=x[t];if(w){var u=w.parentNode;x[t]=u.nodeName.toLowerCase()===s?u:false;}}}else{for(;t<e;t++){w=x[t];if(w){x[t]=v?w.parentNode:w.parentNode===s;}}if(v){b.filter(s,x,true);}}},"":function(u,s,w){var t=k++,e=r,v;if(typeof s==="string"&&!/\W/.test(s)){s=s.toLowerCase();v=s;e=o;}e("parentNode",s,t,u,v,w);},"~":function(u,s,w){var t=k++,e=r,v;if(typeof s==="string"&&!/\W/.test(s)){s=s.toLowerCase();v=s;e=o;}e("previousSibling",s,t,u,v,w);}},find:{ID:function(s,t,u){if(typeof t.getElementById!=="undefined"&&!u){var e=t.getElementById(s[1]);return e?[e]:[];}},NAME:function(t,w){if(typeof w.getElementsByName!=="undefined"){var s=[],v=w.getElementsByName(t[1]);for(var u=0,e=v.length;u<e;u++){if(v[u].getAttribute("name")===t[1]){s.push(v[u]);}}return s.length===0?null:s;}},TAG:function(e,s){return s.getElementsByTagName(e[1]);}},preFilter:{CLASS:function(u,s,t,e,x,y){u=" "+u[1].replace(/\\/g,"")+" ";if(y){return u;}for(var v=0,w;(w=s[v])!=null;v++){if(w){if(x^(w.className&&(" "+w.className+" ").replace(/[\t\n]/g," ").indexOf(u)>=0)){if(!t){e.push(w);}}else{if(t){s[v]=false;}}}}return false;},ID:function(e){return e[1].replace(/\\/g,"");},TAG:function(s,e){return s[1].toLowerCase();},CHILD:function(e){if(e[1]==="nth"){var s=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(e[2]==="even"&&"2n"||e[2]==="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(s[1]+(s[2]||1))-0;e[3]=s[3]-0;}e[0]=k++;return e;},ATTR:function(v,s,t,e,w,x){var u=v[1].replace(/\\/g,"");if(!x&&g.attrMap[u]){v[1]=g.attrMap[u];}if(v[2]==="~="){v[4]=" "+v[4]+" ";}return v;},PSEUDO:function(v,s,t,e,w){if(v[1]==="not"){if((q.exec(v[3])||"").length>1||/^\w/.test(v[3])){v[3]=b(v[3],null,null,s);}else{var u=b.filter(v[3],s,t,true^w);if(!t){e.push.apply(e,u);}return false;}}else{if(g.match.POS.test(v[0])||g.match.CHILD.test(v[0])){return true;}}return v;},POS:function(e){e.unshift(true);return e;}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden";},disabled:function(e){return e.disabled===true;},checked:function(e){return e.checked===true;},selected:function(e){e.parentNode.selectedIndex;return e.selected===true;},parent:function(e){return !!e.firstChild;},empty:function(e){return !e.firstChild;},has:function(t,s,e){return !!b(e[3],t).length;},header:function(e){return(/h\d/i).test(e.nodeName);},text:function(e){return"text"===e.type;},radio:function(e){return"radio"===e.type;},checkbox:function(e){return"checkbox"===e.type;},file:function(e){return"file"===e.type;},password:function(e){return"password"===e.type;},submit:function(e){return"submit"===e.type;},image:function(e){return"image"===e.type;},reset:function(e){return"reset"===e.type;},button:function(e){return"button"===e.type||e.nodeName.toLowerCase()==="button";},input:function(e){return(/input|select|textarea|button/i).test(e.nodeName);}},setFilters:{first:function(s,e){return e===0;},last:function(t,s,e,u){return s===u.length-1;},even:function(s,e){return e%2===0;},odd:function(s,e){return e%2===1;},lt:function(t,s,e){return s<e[3]-0;},gt:function(t,s,e){return s>e[3]-0;},nth:function(t,s,e){return e[3]-0===s;},eq:function(t,s,e){return e[3]-0===s;}},filter:{PSEUDO:function(t,y,x,z){var e=y[1],s=g.filters[e];if(s){return s(t,x,y,z);}else{if(e==="contains"){return(t.textContent||t.innerText||b.getText([t])||"").indexOf(y[3])>=0;}else{if(e==="not"){var u=y[3];for(var w=0,v=u.length;w<v;w++){if(u[w]===t){return false;}}return true;}else{b.error("Syntax error, unrecognized expression: "+e);}}}},CHILD:function(e,u){var x=u[1],s=e;switch(x){case"only":case"first":while((s=s.previousSibling)){if(s.nodeType===1){return false;}}if(x==="first"){return true;}s=e;case"last":while((s=s.nextSibling)){if(s.nodeType===1){return false;}}return true;case"nth":var t=u[2],A=u[3];if(t===1&&A===0){return true;}var w=u[0],z=e.parentNode;if(z&&(z.sizcache!==w||!e.nodeIndex)){var v=0;for(s=z.firstChild;s;s=s.nextSibling){if(s.nodeType===1){s.nodeIndex=++v;}}z.sizcache=w;}var y=e.nodeIndex-A;if(t===0){return y===0;}else{return(y%t===0&&y/t>=0);}}},ID:function(s,e){return s.nodeType===1&&s.getAttribute("id")===e;},TAG:function(s,e){return(e==="*"&&s.nodeType===1)||s.nodeName.toLowerCase()===e;},CLASS:function(s,e){return(" "+(s.className||s.getAttribute("class"))+" ").indexOf(e)>-1;},ATTR:function(w,u){var t=u[1],e=g.attrHandle[t]?g.attrHandle[t](w):w[t]!=null?w[t]:w.getAttribute(t),x=e+"",v=u[2],s=u[4];return e==null?v==="!=":v==="="?x===s:v==="*="?x.indexOf(s)>=0:v==="~="?(" "+x+" ").indexOf(s)>=0:!s?x&&e!==false:v==="!="?x!==s:v==="^="?x.indexOf(s)===0:v==="$="?x.substr(x.length-s.length)===s:v==="|="?x===s||x.substr(0,s.length+1)===s+"-":false;},POS:function(v,s,t,w){var e=s[2],u=g.setFilters[e];if(u){return u(v,t,s,w);}}}};var l=g.match.POS,h=function(s,e){return"\\"+(e-0+1);};for(var n in g.match){g.match[n]=new RegExp(g.match[n].source+(/(?![^\[]*\])(?![^\(]*\))/.source));g.leftMatch[n]=new RegExp(/(^(?:.|\r|\n)*?)/.source+g.match[n].source.replace(/\\(\d+)/g,h));}var a=function(s,e){s=Array.prototype.slice.call(s,0);if(e){e.push.apply(e,s);return e;}return s;};try{Array.prototype.slice.call(document.documentElement.childNodes,0)[0].nodeType;}catch(m){a=function(v,u){var s=u||[],t=0;if(d.call(v)==="[object Array]"){Array.prototype.push.apply(s,v);}else{if(typeof v.length==="number"){for(var e=v.length;t<e;t++){s.push(v[t]);}}else{for(;v[t];t++){s.push(v[t]);}}}return s;};}var c;if(document.documentElement.compareDocumentPosition){c=function(s,e){if(!s.compareDocumentPosition||!e.compareDocumentPosition){if(s==e){p=true;}return s.compareDocumentPosition?-1:1;}var t=s.compareDocumentPosition(e)&4?-1:s===e?0:1;if(t===0){p=true;}return t;};}else{if("sourceIndex" in document.documentElement){c=function(s,e){if(!s.sourceIndex||!e.sourceIndex){if(s==e){p=true;}return s.sourceIndex?-1:1;}var t=s.sourceIndex-e.sourceIndex;if(t===0){p=true;}return t;};}else{if(document.createRange){c=function(u,s){if(!u.ownerDocument||!s.ownerDocument){if(u==s){p=true;}return u.ownerDocument?-1:1;}var t=u.ownerDocument.createRange(),e=s.ownerDocument.createRange();t.setStart(u,0);t.setEnd(u,0);e.setStart(s,0);e.setEnd(s,0);var v=t.compareBoundaryPoints(Range.START_TO_END,e);if(v===0){p=true;}return v;};}}}b.getText=function(e){var s="",u;for(var t=0;e[t];t++){u=e[t];if(u.nodeType===3||u.nodeType===4){s+=u.nodeValue;}else{if(u.nodeType!==8){s+=b.getText(u.childNodes);}}}return s;};(function(){var s=document.createElement("div"),t="script"+(new Date()).getTime();s.innerHTML="<a name='"+t+"'/>";var e=document.documentElement;e.insertBefore(s,e.firstChild);if(document.getElementById(t)){g.find.ID=function(v,w,x){if(typeof w.getElementById!=="undefined"&&!x){var u=w.getElementById(v[1]);return u?u.id===v[1]||typeof u.getAttributeNode!=="undefined"&&u.getAttributeNode("id").nodeValue===v[1]?[u]:undefined:[];}};g.filter.ID=function(w,u){var v=typeof w.getAttributeNode!=="undefined"&&w.getAttributeNode("id");return w.nodeType===1&&v&&v.nodeValue===u;};}e.removeChild(s);e=s=null;})();(function(){var e=document.createElement("div");e.appendChild(document.createComment(""));if(e.getElementsByTagName("*").length>0){g.find.TAG=function(s,w){var v=w.getElementsByTagName(s[1]);if(s[1]==="*"){var u=[];for(var t=0;v[t];t++){if(v[t].nodeType===1){u.push(v[t]);}}v=u;}return v;};}e.innerHTML="<a href='#'></a>";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){g.attrHandle.href=function(s){return s.getAttribute("href",2);};}e=null;})();if(document.querySelectorAll){(function(){var e=b,t=document.createElement("div");t.innerHTML="<p class='TEST'></p>";if(t.querySelectorAll&&t.querySelectorAll(".TEST").length===0){return;}b=function(x,w,u,v){w=w||document;if(!v&&w.nodeType===9&&!b.isXML(w)){try{return a(w.querySelectorAll(x),u);}catch(y){}}return e(x,w,u,v);};for(var s in e){b[s]=e[s];}t=null;})();}(function(){var e=document.createElement("div");e.innerHTML="<div class='test e'></div><div class='test'></div>";if(!e.getElementsByClassName||e.getElementsByClassName("e").length===0){return;}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return;}g.order.splice(1,0,"CLASS");g.find.CLASS=function(s,t,u){if(typeof t.getElementsByClassName!=="undefined"&&!u){return t.getElementsByClassName(s[1]);}};e=null;})();function o(s,x,w,A,y,z){for(var u=0,t=A.length;u<t;u++){var e=A[u];if(e){e=e[s];var v=false;while(e){if(e.sizcache===w){v=A[e.sizset];break;}if(e.nodeType===1&&!z){e.sizcache=w;e.sizset=u;}if(e.nodeName.toLowerCase()===x){v=e;break;}e=e[s];}A[u]=v;}}}function r(s,x,w,A,y,z){for(var u=0,t=A.length;u<t;u++){var e=A[u];if(e){e=e[s];var v=false;while(e){if(e.sizcache===w){v=A[e.sizset];break;}if(e.nodeType===1){if(!z){e.sizcache=w;e.sizset=u;}if(typeof x!=="string"){if(e===x){v=true;break;}}else{if(b.filter(x,[e]).length>0){v=e;break;}}}e=e[s];}A[u]=v;}}}b.contains=document.compareDocumentPosition?function(s,e){return !!(s.compareDocumentPosition(e)&16);}:function(s,e){return s!==e&&(s.contains?s.contains(e):true);};b.isXML=function(e){var s=(e?e.ownerDocument||e:0).documentElement;return s?s.nodeName!=="HTML":false;};var i=function(e,y){var u=[],v="",w,t=y.nodeType?[y]:y;while((w=g.match.PSEUDO.exec(e))){v+=w[0];e=e.replace(g.match.PSEUDO,"");}e=g.relative[e]?e+"*":e;for(var x=0,s=t.length;x<s;x++){b(e,t[x],u);}return b.filter(v,u);};Glow.provide(function(e){e._sizzle=b;});return;window.Sizzle=b;})();Glow.provide(function(h){var b=h.NodeList.prototype,e=1,d="_unique"+h.UID;if(h.env.ie){var g=function(k){if(k.length==1){return k;}var m=[],j=0,l=0;for(;k[l];l++){if(k[l].getAttribute(d)!=e&&k[l].nodeType==1){m[j++]=k[l];}k[l].setAttribute(d,e);}for(l=0;k[l];l++){k[l].removeAttribute(d);}e++;return m;};}else{var g=function(k){if(k.length==1){return k;}var m=[],j=0,l=0;for(;k[l];l++){if(k[l][d]!=e&&k[l].nodeType==1){m[j++]=k[l];}k[l][d]=e;}e++;return m;};}b.parent=function(m){var k=[],j=0,l=this.length,n;while(l--){n=this[l];if(n.nodeType==1){if(m){while(n=n.parentNode){if(h._sizzle.filter(m,[n]).length){k[j++]=n;break;}}}else{if(n=n.parentNode){k[j++]=n;}}}}return new h.NodeList(g(k));};function c(q,l,n){var k=[],j=0,p,m=0,o=q.length;while(m<o){p=q[m];if(n){while(p=p[l+"Sibling"]){if(p.nodeType==1&&p.nodeName!="!"){if(h._sizzle.filter(n,[p]).length){k[j++]=p;break;}}}}else{while(p=p[l+"Sibling"]){if(p.nodeType==1&&p.nodeName!="!"){k[j++]=p;break;}}}m++;}return new h.NodeList(k);}b.prev=function(i){return c(this,"previous",i);};b.next=function(i){return c(this,"next",i);};b.get=function(j){var k=[],l=this.length;while(l--){h._sizzle(j,this[l],k);}return new h.NodeList(g(k));};b.ancestors=function(m){var k=[],j=0,l=0,o=this.length,n;while(l<o){n=this[l].parentNode;while(n&&n.nodeType==1){k[j++]=n;n=n.parentNode;}l++;}if(m){k=new h.NodeList(k);k=k.filter(m);}return new h.NodeList(g(k));};function a(m){var l=[],n=m.childNodes,k=0,j=0;for(;n[k];k++){if(n[k].nodeType==1&&n[k].nodeName!="!"){l[j++]=n[k];}}return l;}b.children=function(){var j=[],k=this.length;while(k--){j=j.concat(a(this[k]));}return new h.NodeList(j);};b.contains=function(o){var k=0,m=new h.NodeList(o)[0],l=this.length,j,n;if(!m||!this.length){return false;}if(this[0].compareDocumentPosition){while(k<l){if(this[k]==m){break;}else{if(!(this[k].compareDocumentPosition(m)&16)){return false;}}k++;}}else{if(m.contains){for(;k<l;k++){if(!(this[k].contains(m))){return false;}}}else{while(k<l){n=m;while(n=n.parentNode){if(this[k]==n){break;}}if(!n){return false;}k++;}}}return true;};});Glow.provide(function(h){var d=h.NodeList.prototype,g=window.document,b;function e(k){var l=g.createDocumentFragment(),m=0,n;while(n=k[m++]){l.appendChild(n);}return l;}function i(l,k){return function(m){var p,r,v,s,u,n;if(!this.length){return this;}if(!k&&typeof m==="string"){m=new h.NodeList(h.NodeList._strToNodes(m));}else{m=new h.NodeList(m);}if(k){r=m;p=new h.NodeList(this);}else{r=this;p=m;}s=e(p);for(var q=0,t=r.length,o=t-1;q<t;q++){u=r[q];v=s;if(n=u.parentNode){if(q!==o){s=v.cloneNode(true);k&&p.push(s.childNodes);}n.insertBefore(v,l?u.nextSibling:u);}}return k?p:r;};}function c(k,l){return function(m){var o,q,u,r,s;if(!this.length){return this;}if(!l&&typeof m==="string"){m=new h.NodeList(h.NodeList._strToNodes(m));}else{m=new h.NodeList(m);}if(l){q=m;o=new h.NodeList(this);}else{q=this;o=m;}r=e(o);for(var p=0,t=q.length,n=t-1;p<t;p++){s=q[p];u=r;if(s.nodeType===1){if(p!==n){r=u.cloneNode(true);l&&o.push(r.childNodes);}s.insertBefore(u,k?null:s.firstChild);}}return l?o:q;};}d.after=i(1);d.before=i(0);d.append=c(1);d.prepend=c(0);d.appendTo=c(1,1);d.prependTo=c(0,1);d.insertAfter=i(1,1);d.insertBefore=i(0,1);var j=g.createElement("div");d.destroy=function(){var k=this.get("*").push(this);h.events.removeAllListeners(k.removeData());this.appendTo(j);j.innerHTML="";};d.remove=function(){var l,m,k=this.length;while(k--){m=this[k];if(l=m.parentNode){l.removeChild(m);}}return this;};d.empty=h.env.ie?function(){var k=this.length,l,m;while(k--){l=this[k];while(m=l.firstChild){l.removeChild(m);}}return this;}:function(){var k=this.length;while(k--){this[k].innerHTML="";}return this;};d.replaceWith=function(k){return this.after(k).remove();};function a(k){for(var l=k.firstChild;l;l=l.nextSibling){if(l.nodeType==1){return l;}}return b;}d.wrap=function(p){p=new h.NodeList(p);if(!p[0]||p[0].nodeType!=1){return this;}var m,n,o;for(var l=0,k=this.length;l<k;l++){m=this[l];n=p[0];while(n){o=a(n);if(!o){break;}n=o;}if(m.parentNode){p.insertBefore(m);}if(l!=k-1){p=p.clone();}n.appendChild(m);}return this;};d.unwrap=function(){var m,o,k=this.parent();for(var n=0,l=k.length;n<l;n++){m=k.slice(n,n+1);o=new h.NodeList(m[0].childNodes);if(!m[0].parentNode){o.remove();m.destroy();}else{o.insertBefore(m);m.destroy();}}return this;};d.clone=function(){var l=this.copy(),k=l.get("*").push(l),m=this.get("*").push(this);h.events._copyDomEvents(m,k);h.NodeList._copyData(m,k);return l;};d.copy=function(){var m=[],o=this.length,n,l,p="__eventId"+h.UID,k="_uniqueData"+h.UID;while(o--){m[o]=this[o].cloneNode(true);}n=new h.NodeList(m);if(h.env.ie){l=n.get("*").push(m);o=l.length;while(o--){l[o][k]=l[o][p]=b;}}return n;};d.html=function(n){if(!arguments.length){return this[0]?this[0].innerHTML:"";}var k=this.length,l;n=n===b?"":String(n);while(k--){l=this[k];if(l.nodeType==1){try{l.innerHTML=n;}catch(m){new h.NodeList(l).empty().append(n);}}}return this;};d.text=function(k){var n=this[0],l=this.length,m;if(!arguments.length){return n?n.textContent||n.innerText||n.nodeValue||"":"";}k=k?String(k):"";this.empty();while(l--){m=this[l];if(m.nodeType==1){m.appendChild(g.createTextNode(k));}else{m.nodeValue=k;}}return this;};});Glow.provide(function(l){var B=l.NodeList,e=B.prototype,i=window,u=i.document,y=u.defaultView&&u.defaultView.getComputedStyle,z=/-(\w)/g,v=/^-?[\d\.]+(?!px)[%a-z]+$/i,q=/alpha\(opacity=([^\)]+)\)/,h=/width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/;function c(E,F){return F.toUpperCase();}function t(E){if(E=="float"){return l.env.ie?"styleFloat":"cssFloat";}return E.replace(z,c);}function D(H,F){var G=0,E=F.length;while(E--){G+=w(p(H,F[E]))||0;}return G;}function p(L,K){var I=L.ownerDocument.defaultView,E,H,G,J,F;if(y){E=I.getComputedStyle(L,null);if(l.env.webkit&&K==="margin-right"){J=L.style.display;L.style.display="none";H=E[K];L.style.display=J;}else{H=E.getPropertyValue(K);}}else{if(G=L.currentStyle){if(K==="opacity"){F=q.exec(G.filter);return F?String(parseInt(F[1],10)/100)||"1":"1";}else{if(K.indexOf("border")===0&&K.slice(-5)==="width"&&p(L,"border-style")==="none"){return"0px";}}H=G[t(K)];if(v.test(H)&&K!="font-size"){H=x(L,H,K.indexOf("height")>=0||K.indexOf("top")>=0)+"px";}}}if(K==="opacity"){H=H||"1";}else{if(K.indexOf("color")!=-1){H=B._parseColor(H).toString();}}return H;}var A=Math.round,g=parseInt,w=parseFloat,s={black:0,silver:12632256,gray:8421504,white:16777215,maroon:8388608,red:16711680,purple:8388736,fuchsia:16711935,green:32768,lime:65280,olive:8421376,yellow:16776960,navy:128,blue:255,teal:32896,aqua:65535,orange:16753920},b=/^rgba?\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)(?:,\s*([\d\.]+))?/i,r=/^(transparent|rgba\(0, ?0, ?0, ?0\))$/,k=/\w/g;B._parseColor=function(K){if(r.test(K)){return"rgba(0, 0, 0, 0)";}var G,J,I,E,F,H;if(G=b.exec(K)){J=G[2]?A(w(G[1])*2.55):g(G[1]);I=G[4]?A(w(G[3])*2.55):g(G[3]);E=G[6]?A(w(G[5])*2.55):g(G[5]);F=w(G[7]||"1");}else{if(typeof K=="number"){H=K;}else{if(K.charAt(0)=="#"){if(K.length===4){K=K.replace(k,"$&$&");}H=g(K.slice(1),16);}else{H=s[K];}}J=(H)>>16;I=(H&65280)>>8;E=(H&255);F=1;}K=new String("rgba("+J+", "+I+", "+E+", "+F+")");K.r=J;K.g=I;K.b=E;K.a=F;return K;};var m=["border-left-width","border-right-width","padding-left","padding-right"],j=["border-top-width","border-bottom-width","padding-top","padding-bottom"];function a(J,H){if(!J||J.nodeType===3||J.nodeType===8){return 0;}var F,K=J.ownerDocument||J.document||J,M=K.documentElement,I=K.body,L=l.env.standardsMode?M:I,E=(H=="Width"),G;if(J.window){F=l.env.webkit?(E?I.clientWidth:J.innerHeight):L["client"+H];}else{if(J.getElementById){F=Math.max(I["scroll"+H],M["scroll"+H]);}else{G=E?m:j;F=J["offset"+H]-D(J,G);}}return F;}function x(J,L,H){var F=H?"top":"left",I=H?"Top":"Left",M=J.style,G=M[F],K=J.runtimeStyle[F],E;J.runtimeStyle[F]=J.currentStyle[F];M[F]=L;E=M["pixel"+I];M[F]=G;J.runtimeStyle[F]=K;return E;}e.css=function(K,I){var J,F=this.length,E,G,H=this[0];if(K.constructor===Object){for(G in K){this.css(G,K[G]);}return this;}else{if(I!==undefined){E=t(K);while(F--){if(this[F].nodeType===1){J=this[F].style;if(!isNaN(I)&&h.test(K)){I+="px";}if(K==="opacity"&&l.env.ie){I=w(I);J.zoom="1";J.filter=(I!==1)?"alpha(opacity="+A(I*100)+")":"";}else{J[E]=I;}}}return this;}else{if(K==="width"||K==="height"){return this[K]()+"px";}return(H&&H.nodeType===1)?p(H,K):"";}}};e.height=function(E){if(E===undefined){return a(this[0],"Height");}return this.css("height",E);};e.width=function(E){if(E===undefined){return a(this[0],"Width");}return this.css("width",E);};e.scrollLeft=function(E){return o(this,true,E);};e.scrollTop=function(E){return o(this,false,E);};function n(H,G){var F,E="scroll"+(G?"Left":"Top");if(H.window){F=H.document.documentElement[E]||(G?H.pageXOffset:H.pageYOffset)||0;}else{F=H[E];}return F;}function d(G,F,E){if(G.window){G.scrollTo(F?E:n(G,true),!F?E:n(G,false));}else{G["scroll"+(F?"Left":"Top")]=E;}}function o(E,H,G){var F=E.length;if(G!==undefined){while(F--){d(E[F],H,G);}return E;}else{return n(E[0],H);}}e.hide=function(){return this.css("display","none").css("visibility","hidden");};e.show=function(){var F=this.length,E,G;while(F--){E=new l.NodeList(this[F]);G=E[0].style;if(E.css("display")=="none"){G.display="";G.visibility="visible";if(E.css("display")=="none"){G.display="block";}}}return this;};e.offset=function(){if(!this[0]||this[0].nodeType!==1){return{top:0,left:0};}var L=this[0],O=L.ownerDocument,P=O.documentElement,K=O.defaultView||O.parentWindow,I={x:n(K,true),y:n(K,false)};if(!l.env.webkit&&L.getBoundingClientRect){var N=L.getBoundingClientRect();return{top:Math.floor(N.top)+I.y-P.clientTop,left:Math.floor(N.left)+I.x-P.clientLeft};}else{var M=L.offsetTop,E=L.offsetLeft,F=L,Q,H=u.body,J=false,G=L;while(L=L.offsetParent){E+=L.offsetLeft;M+=L.offsetTop;if(p(L,"position")=="fixed"){J=true;}if(l.env.gecko||l.env.webkit>500){E+=parseInt(p(L,"border-left-width"))||0;M+=parseInt(p(L,"border-top-width"))||0;}if(L.nodeName.toLowerCase()!="body"){G=L;}}L=F;while((L=L.parentNode)&&(L!=H)&&(L!=P)){E-=L.scrollLeft;M-=L.scrollTop;if(l.env.gecko&&p(L,"overflow")!="visible"){E+=parseInt(p(L,"border-left-width"));M+=parseInt(p(L,"border-top-width"));}}if(J){E+=I.x;M+=I.y;}if((l.env.gecko&&p(G,"position")!="absolute")){E-=H.offsetLeft;M-=H.offsetTop;}return{left:E,top:M};}};e.position=function(){var F=new l.NodeList(C(this[0])),K=!!F[0],J=parseInt(this.css("margin-left"))||0,I=parseInt(this.css("margin-top"))||0,H=(K&&parseInt(F.css("border-left-width")))||0,E=(K&&parseInt(F.css("border-top-width")))||0,L=this.offset(),G=K?F.offset():{top:0,left:0};return{left:L.left-G.left-J-H,top:L.top-G.top-I-E};};function C(G){var F=G.offsetParent,E=u.documentElement;while(F&&new l.NodeList(F).css("position")=="static"){F=F.offsetParent;}if(!F&&new l.NodeList(E).css("position")!="static"){F=E;}return F||null;}});Glow.provide(function(e){var b=e.NodeList.prototype,a=window.document,d,c=" keypress keydown keyup ";b.on=function(g,j,i){var h=(c.indexOf(" "+g+" ")>-1);e.events.addListeners(this,g,j,i);if(h){e.events._addKeyListener(this);}else{e.events._addDomEventListener(this,g);}return this;};b.detach=function(g,i){var h=(c.indexOf(" "+g+" ")>-1);e.events.removeListeners(this,g,i);if(h){e.events._removeKeyListener(this);}else{e.events._removeDomEventListener(this,g);}return this;};b.delegate=function(h,g,k,j){var i=(c.indexOf(" "+h+" ")>-1);e.events.addListeners(this,h+"/"+g,k,j);e.events._registerDelegate(this,h,g);if(i){e.events._addKeyListener(this);}else{e.events._addDomEventListener(this,h);}return this;};b.detachDelegate=function(h,g,k,j){var i=(c.indexOf(" "+h+" ")>-1);e.events.removeListeners(this,h+"/"+g,k);e.events._unregisterDelegate(this,h,g);if(i){e.events._removeKeyListener(this);}else{e.events._removeDomEventListener(this,h);}return this;};b.fire=function(g,h){return e.events.fire(this,g,h);};});Glow.provide(function(i){var r=i.NodeList,d=r.prototype,h,b=window.parseFloat,e=/width|height|top$|bottom$|left$|right$|spacing$|indent$|fontSize/i,q=/width|height|padding|opacity/,c=/\D+$/,a=/height|top/;function o(v){if(v=="float"){return i.env.ie?"styleFloat":"cssFloat";}return v.replace(/-(\w)/g,function(w,x){return x.toUpperCase();});}var m=i('<div style="position:absolute;visibility:hidden;border:0;margin:0;padding:0"></div>');function s(x,C,A,z){var B=m[0].style,y=(z==="x")?"width":"height",w,v;w=m.css(y,C).insertAfter(x)[y]();v=m.css(y,10+A)[y]()/10;m.remove();return w/v;}function n(v,w,y,x){x=r._parseColor(x);x=[x.r,x.g,x.b];y=r._parseColor(y);y=[y.r,y.g,y.b];v.prop(w,{template:"rgb(?,?,?)",from:y,to:x,round:true,min:0,max:255});}function p(y,v,x,w){w=b(w)*100;x=b(x)*100;y.style.zoom=1;v.prop("filter",{template:"alpha(opacity=?)",from:x,to:w,allowNegative:false});}function t(A,w,z,y,v){var x;y=b(y);z=b(z);A=i(A);if(isNaN(z)){z=A[v]();}x=y-z;w.on("frame",function(){A[v](x*this.value+z);});}function u(A,y,w,D,E){var v,x,H=[],F="",B=e.test(w),C=q.test(w);D=String(D).split(" ");E=String(E).split(" ");for(var z=0,G=E.length;z<G;z++){v=(c.exec(E[z])||[""])[0];x=(c.exec(D[z])||[""])[0];if(B){v=v||"px";x=x||"px";}H[z]=(v==="px");if(v!==x){D=s(A,D,v,a.test(w)?"y":"x");}F+=" ?"+v;D[z]=b(D[z]);E[z]=b(E[z]);}y.prop(w,{template:F,from:D,to:E,round:H,min:C?0:h});}function l(w,x,B){var C,D,y,E,A,v;for(var z in B){E=B[z];A=E.push;v=o(z);C=A?E[1]:E;y=w.length;while(y--){if(z.indexOf("scroll")===0&&(w[y].scrollTo||w[y].scrollTop!==h)){t(w[y],x,A?E[0]:h,C,z);continue;}if(w[y].nodeType!==1){continue;}x.target(w[y].style);D=A?E[0]:w.item(y).css(z);if(z.indexOf("color")!==-1){n(x,v,D,C);}else{if(i.env.ie&&v==="opacity"){p(w[y],x,D,C);}else{u(w[y],x,v,D,C);}}}}}d.anim=function(y,v,w){w=w||{};var x=new i.anim.Anim(y,w);l(this,x,v);!(w.startNow===false)&&x.start();return x;};function k(){this.removeData("glow_lastQueuedAnim").removeData("glow_currentAnim");}d.queueAnim=function(C,x,A){A=A||{};var w=this.length,z,v,B,y;A.startNow=false;while(w--){z=this.item(w);if(z[0].nodeType!==1){continue;}v=z.data("glow_lastQueuedAnim");B=new i.anim.Anim(C,A).on("stop",k,z);z.data("glow_lastQueuedAnim",B);(function(E,D,F){y=function(){l(E,F,D);F.start();E.data("glow_currentAnim",F);};})(z,x,B);if(v){v.on("complete",y);}else{y();}}return this;};d.currentAnim=function(){return this.data("glow_currentAnim")||new i.anim.Anim(0);};d.lastQueuedAnim=function(){return this.data("glow_lastQueuedAnim")||new i.anim.Anim(0);};function g(v,x,w,A,z,y){return function(I,F){F=F||{};var E,H,D,B,G,C=this.length;F.tween=F.tween||A;if(I===h){I=1;}B=I;while(C--){E=this.item(C);D=E.data("glow_"+v);if(E[0].nodeType!==1||(D&&D.playing)){continue;}H=E.data("glow_"+x);if(H&&H.playing){B=I*(H.position/H.duration);H.stop().destroy();}E.data("glow_"+v,G=E.anim(B,w(E),F).on("complete",z,E));y&&y(G,E,F);}return this;};}d.fadeIn=g("fadeIn","fadeOut",function(v){v.css("display","block");return{opacity:1};},"easeOut",function(){if(i.env.ie){this[0].style.filter="";}});d.fadeOut=g("fadeOut","fadeIn",function(){return{opacity:0};},"easeIn",function(){this.css("display","none");});d.fadeToggle=function(y,x){var v=this.length,w,z;while(v--){w=this.item(v);if(w[0].nodeType===1){if(w.css("opacity")==="0"||((z=w.data("glow_fadeOut"))&&z.playing)){w.fadeIn(y,x);}else{w.fadeOut(y,x);}}}return this;};d.slideOpen=g("slideOpen","slideShut",function(w){var x=w.css("height"),v;if(w.css("overflow")==="visible"){w.css("overflow","hidden");}w.css("height","auto");v=w.height();w.css("height",x);return{height:v};},"easeBoth",function(){this.css("height","auto").scrollTop(0);},j);d.slideShut=g("slideShut","slideOpen",function(v){if(v.css("overflow")==="visible"){v.css("overflow","hidden");}return{height:0};},"easeBoth",function(){},j);function j(z,v,y){var x=v[0],w=x.scrollHeight;if(y.lockToBottom){z.on("frame",function(){v.scrollTop(w-x.offsetHeight);});}}d.slideToggle=function(z,x){var v=this.length,w,y;while(v--){w=this.item(v);if(w[0].nodeType===1){if(w.height()===0||((y=w.data("glow_slideShut"))&&y.playing)){w.slideOpen(z,x);}else{w.slideShut(z,x);}}}return this;};});Glow.provide(function(e){var c={},d,b=function(){};function a(g){return function(h,j,i){if(g==="POST"||g==="PUT"){i=i||{};i.data=j;}else{i=j;}return new c.XhrRequest(g,h,i);};}c.get=a("GET");c.post=a("POST");c.put=a("PUT");c.del=a("DELETE");e.net=c;});Glow.provide(function(i){var h,g,c=i.events,d=c.removeAllListeners;var b=window.ActiveXObject?function(){return new ActiveXObject("Microsoft.XMLHTTP");}:function(){return new XMLHttpRequest();};function a(j,l){j=i.util.apply({headers:{}},j);var k=j.headers;if(typeof j.data==="object"){j.data=i.util.encodeUrl(j.data);}if(!k["X-Requested-With"]){k["X-Requested-With"]="XMLHttpRequest";}if(l!=="GET"&&!k["Content-Type"]){k["Content-Type"]="application/x-www-form-urlencoded;";}return j;}function e(o,k,n){this._opts=n=a(n,o);var m=this,j=m.nativeRequest=b(),l;if(n.cacheBust){k=k+(k.indexOf("?")===-1?"?":"&")+"cachebuster="+new Date().valueOf();}m.complete=false;j.open(o,k,true);for(l in n.headers){j.setRequestHeader(l,n.headers[l]);}if(n.forceXml&&j.overrideMimeType){j.overrideMimeType("application/xml");}if(n.timeout){m._timeout=setTimeout(function(){var p=new i.net.XhrResponse(m,true);m.abort().fire("error",p);},n.timeout*1000);}j.onreadystatechange=function(){if(j.readyState===4){var p=new i.net.XhrResponse(m);clearTimeout(m._timeout);m.completed=true;m.fire(p.successful?"load":"error",p);j.onreadystatechange=new Function();d(m);}};setTimeout(function(){j.send(n.data||null);},0);}i.util.extend(e,c.Target);g=e.prototype;g.abort=function(){if(!this.completed&&!this.fire("abort").defaultPrevented()){clearTimeout(this._timeout);this.nativeRequest.onreadystatechange=new Function();d(this);}return this;};i.net.XhrRequest=e;});Glow.provide(function(d){var b,a=d.util;function c(g,e){var h=this.nativeResponse=g.nativeRequest;this._request=g;this.status=e?408:h.status==1223?204:h.status;this.timedOut=!!e;this.successful=(this.status>=200&&this.status<300)||this.status==304||(this.status==0&&h.responseText);}a.extend(c,d.events.Event);b=c.prototype;b.text=function(){return this.nativeResponse.responseText;};b.xml=function(){var g=this.nativeResponse,h=this.header("Content-Type");if(d.env.ie&&(h.slice(-4)==="+xml"||h===""||this._request._opts.forceXml)){var e=new ActiveXObject("Microsoft.XMLDOM");e.loadXML(g.responseText);return e;}else{return g.responseXML;}};b.json=function(e){return a.decodeJson(this.text(),{safeMode:e});};b.nodeList=function(e){return d(d.NodeList._strToNodes(this.text()));};b.header=function(e){return this.nativeResponse.getResponseHeader(e);};b.statusText=function(){return this.timedOut?"Request Timeout":this.nativeResponse.statusText;};d.net.XhrResponse=c;});Glow.provide(function(m){var b,h,k=m.net,g=function(){},o=m.events,c={},p=0,l="c",n="_"+m.UID+"jsonp",j=m("head"),e;function i(s,q){var t=o._getListeners(s).load,r;if(t){t=t.slice(0);r=t.length;while(r--){t[r][0].apply(t[r][1],q);}}s.completed=true;a(s);}function a(r,q){var s=r._callbackName;clearTimeout(r._timeout);e[s]=q?g:b;m(c[s]).destroy();c[s]=b;}function d(r,u){u=u||{};var s=c.length,w=this._callbackName=l+(p++),q=c[w]=document.createElement("script"),t=this,v=u.timeout,x=u.charset;r=m.util.interpolate(r,{callback:n+"."+w});e||(e=window[n]={});e[w]=function(){i(t,arguments);};x&&(q.charset=x);if(u.timeout){t._timeout=setTimeout(function(){t.abort().fire("error");},v*1000);}q.src=r;j.prepend(q);q=b;}m.util.extend(d,o.Target);h=d.prototype;h.complete=false;h.abort=function(){this.fire("abort");a(this,true);return this;};k.jsonp=function(q,r){return new m.net.JsonpRequest(q,r);};m.net.JsonpRequest=d;});Glow.provide(function(m){var g,o,b,j=m.net;function k(u){var t={js:[],css:[],img:[]},q;if(typeof u==="object"&&!u.push){t=m.util.apply(t,u);}else{typeof u==="string"&&(u=[u]);for(var s=0,p=u.length;s<p;s++){q=u[s];if(q.slice(-4)===".css"){t.css[t.css.length]=q;}else{if(q.slice(-3)===".js"){t.js[t.js.length]=q;}else{t.img[t.img.length]=q;}}}}return t;}function h(w){w=k(w);var q=this,p=w.js,v=w.css,t=w.img,r=p.length,x=v.length,u=t.length,s;q.totalResources=r+x+u;setTimeout(function(){for(s=0;s<x;s++){l(q,v[s]);}for(s=0;s<r;s++){d(q,p[s]);}for(s=0;s<u;s++){i(q,t[s]);}},0);}m.util.extend(h,m.events.Target);o=h.prototype;o.totalResources=0;o.totalLoaded=0;function a(r,p,t,q){var s=++r.totalLoaded;r.fire("progress",{resource:t,url:p,type:q});if(s===r.totalResources){r.fire("load");}}function i(q,r){var p=new Image;m(p).data("srcUrl",r).on("load",e,q);p.src=r;}function e(q){var p=m(q.attachedTo);a(this,p.data("srcUrl"),p,"img");}function d(q,r){var p=m(document.createElement("script")).data("srcUrl",r).prependTo("head");p.on("readystatechange",n,q).on("load",n,q);p[0].src=r;}function n(s){var q=m(s.attachedTo),r=q[0],p=r.readyState;if(!p||p==="loaded"||p==="complete"){q.detach("readystatechange",n).detach("load",n);a(this,q.data("srcUrl"),q,"js");}}function l(t,r){var u,q,s=m('<link rel="stylesheet" type="text/css" media="all" href="'+r+'" />').data("srcUrl",r);if(m.env.gecko&&/^(?:https?\:|\/\/)/.test(r)){u=location.hostname.replace("www.","");q=r.replace(/https?:\/\/|www\.|:.*/g,"").replace(/\/.*/g,"");if(u!==q){setTimeout(function(){c.call(t,{attachedTo:s});},500);}}else{s.on("readystatechange",c,t).on("load",c,t);(function p(){try{s[0].sheet.cssRules;c.call(t,{attachedTo:s});}catch(v){if(!s.data("loaded")){setTimeout(p,20);}}})();}s.prependTo("head");}function c(r){var q=m(r.attachedTo),s=q[0],p=s.readyState;if(!p||p==="loaded"||p==="complete"){if(q.data("loaded")){return;}q.data("loaded",true);q.detach("readystatechange",c).detach("load",c);a(this,q.data("srcUrl"),q,"css");}}j.getResources=function(q,p){return new m.net.ResourceRequest(q,p);};m.net.ResourceRequest=h;});Glow.provide(function(m){var g,i,b,l=m.net,p=l.XhrRequest.prototype,n=m.events.Target;function o(u,q,s){var r=this,t;r._opts=s=m.util.apply({data:{},blankUrl:"/favicon.ico"},s);if(typeof s.data==="string"){s.data=m.util.decodeUrl(s.data);}t=s.timeout;if(t){r._timeout=setTimeout(function(){r.fire("error");c(r);},t*1000);}k(r);d(r,u,q);}m.util.extend(o,n);i=o.prototype;function k(r){var q=r._iframe=m('<iframe style="visibility: hidden; position: absolute; height: 0;"></iframe>').appendTo(document.body);}function d(u,q,r){var v=u._iframe,y=v[0].contentWindow,z=y.document,s,x=u._opts.data;if(m.env.ie){z.open();z.write("<html><body></body></html>");z.close();}s=z.createElement("form");s.action=r;s.method=q;z.body.appendChild(s);for(var w in x){if(!x.hasOwnProperty(w)){continue;}if(x[w] instanceof Array){for(var t=0,A=x[w].length;t<A;t++){h(s,w,this.data[w][t]);}}else{h(s,w,this.data[w]);}}y.setTimeout(function(){s.submit();},0);v.on("load",j,u);}function h(s,r,t){var q=s.ownerDocument.createElement("input");q.type="hidden";q.name=r;q.value=t;s.appendChild(q);}function j(){var r,q,t=this._iframe[0].contentWindow;try{q=t.location.href;}catch(s){r=s;}if(q!=="about:blank"||r){clearTimeout(this._timeout);this._iframe.detach("load",j).on("load",a,this);t.location=window.location.protocol+"//"+window.location.host+this._opts.blankUrl;}}function a(){this.fire("load",new e(this._iframe[0].contentWindow.name));c(this);}function c(q){q._iframe.destroy();m.events.removeAllListeners(q);}function e(q){this._text=q;}m.util.extend(e,n);b=e.prototype;b.text=function(){return this._text;};b.json=p.json;b.nodeList=p.nodeList;l.crossDomainPost=function(q,s,r){r=r||{};r.data=s;return new o("POST",q,r);};l.crossDomainGet=function(q,r){return new o("GET",q,r);};m.net.CrossDomainRequest=o;m.net.CrossDomainResponse=e;});Glow.provide(function(c){var b=c.tweens={};function a(d){return function(e){return 1-d(1-e);};}b.linear=function(){return function(d){return d;};};b.easeIn=function(d){d=d||2;return function(e){return Math.pow(1,d-1)*Math.pow(e,d);};};b.easeOut=function(d){return a(this.easeIn(d));};b.easeBoth=function(d){return this.combine(this.easeIn(d),this.easeOut(d));};b.overshootIn=function(d){return a(this.overshootOut(d));};b.overshootOut=function(d){d=d||1.70158;return function(e){if(e==0||e==1){return e;}return((e-=1)*e*((d+1)*e+d)+1);};};b.overshootBoth=function(d){return this.combine(this.overshootIn(d),this.overshootOut(d));};b.bounceIn=function(){return a(this.bounceOut());};b.bounceOut=function(){return function(d){if(d<(1/2.75)){return 7.5625*d*d;}else{if(d<(2/2.75)){return(7.5625*(d-=(1.5/2.75))*d+0.75);}else{if(d<(2.5/2.75)){return(7.5625*(d-=(2.25/2.75))*d+0.9375);}else{return(7.5625*(d-=(2.625/2.75))*d+0.984375);}}}};};b.elasticIn=function(d,e){return a(this.elasticOut(d,e));};b.elasticOut=function(d,e){var g=1/(e||10/3);d=d||1;return function(h){var i;if(h==0||h==1){return h;}if(d<1){i=g/4;}else{i=g/(2*Math.PI)*Math.asin(1/d);}return d*Math.pow(2,-10*h)*Math.sin((h-i)*(2*Math.PI)/g)+1;};};b.combine=function(e,d){return function(g){if(g<0.5){return e(g*2)/2;}else{return d((g-0.5)*2)/2+0.5;}};};});Glow.provide(function(i){var e,b,d=[],g=0,c;function a(){var n=new Date().valueOf(),l=g,m;while(l--){m=d[l];m.position=(n-m._syncTime)/1000;if(m.position>=m.duration){m.position=m.duration;m.value=m.tween(1);m.fire("frame");if(m.fire("complete").defaultPrevented()||m.loop){m._syncTime=n;}else{m._stopPos=0;j(m);m.destroyOnComplete&&m.destroy();}}else{m.value=m.tween(m.position/m.duration);m.fire("frame");}}}function k(l){if(!g){c=setInterval(a,13);}d[g++]=l;l.playing=true;}function j(n){for(var m=0,l=d.length;m<l;m++){if(d[m]===n){d.splice(m,1);g--;if(!g){clearInterval(c);}n.playing=false;return;}}}function h(m,l){l=i.util.apply({destroyOnComplete:true},l||{});this.destroyOnComplete=l.destroyOnComplete;if(typeof l.tween==="string"){this.tween=i.tweens[l.tween]();}else{if(l.tween){this.tween=l.tween;}}this.loop=!!l.loop;this.duration=+m;this._targets=[];}i.util.extend(h,i.events.Target);b=h.prototype;b.tween=i.tweens.easeBoth();b.position=0;b.playing=false;b.value=0;b.start=function(l){if(!this.playing&&!this.fire("start").defaultPrevented()){this.playing=true;this.goTo(l===e?(this._stopPos||0):l);k(this);}return this;};b.stop=function(){if(this.playing&&!this.fire("stop").defaultPrevented()){this._stopPos=this.position;j(this);}return this;};b.destroy=function(){i.events.removeAllListeners([this]);this._targets=e;};b.goTo=function(l){if(l>this.duration){l=this.duration;}else{if(l<0){l=0;}}this._stopPos=this.position=l;if(this.playing){this._syncTime=new Date-(l*1000);}this.value=this.tween(l/this.duration);this.fire("frame");return this;};i.anim={};i.anim.Anim=h;});Glow.provide(function(glow){glow.anim.Anim.prototype._evalFunc=function evalFunc(s,targets){eval("var f=function(){"+s+"}");return f;};});Glow.provide(function(h){var g,e=h.anim.Anim.prototype;e.target=function(i){this._targets[this._targets.length]=i;return this;};e._funcStr="";function b(n,m,i,k,j){var l="("+Number(n)+"+"+(m-n)+"*this.value)";if(k!==g){l="Math.max("+l+", "+k+")";}if(i!==g){l="Math.min("+l+", "+i+")";}if(j){l="Math.round("+l+")";}return l;}function d(y,u,k,v,t,x){if(!y){return b(u,k,v,t,x);}var n=y.split("?"),A,r='"'+n[0].replace(/"/g,'\\"')+'"',p=window.Array,q=u.constructor===p,m=k.constructor===p,s=v!==g&&v.constructor===p,j=t!==g&&t.constructor===p,o=x.constructor===p,z=0;for(var w=1,l=n.length;w<l;w++,z++){A=n[w];if(n[z].slice(-1)==="\\"){r+='+"?"';}else{if(A.slice(-1)==="\\"){A=A.slice(0,-1);}r+="+"+b(q?u[z]:u,m?k[z]:k,s?v[z]:v,j?t[z]:t,o?x[z]:x)+'+"'+A.replace(/"/g,'\\"')+'"';}}return r;}function c(n,j,m,k){var i=n._targets,o=n._funcStr,l;o+="var target=targets["+j+'];target["'+m.replace(/"/g,'\\"')+'"]='+d(k.template,k.from,k.to,k.max,k.min,k.round)+";";n._funcStr=o;l=n._evalFunc(o,i);n.detach("frame",n._propFunc).on("frame",l);n._propFunc=l;l=o=g;}function a(m,j){var k,l=j.template,i;if(j.from!==g||!l){return j.from||m;}i=h.util.escapeRegex(l).replace(/([^\\]|^)\\\?/g,"$1(\\-?(?:\\d+)?(?:\\.\\d+)?)");k=new RegExp(i).exec(m);if(!k){throw new Error("glow.anim.Anim#prop: Could not detect start values using template: "+l);}else{return Array.prototype.slice.call(k,1);}}e.prop=function(k,j){var i=this._targets.length-1,l=this._targets[i];j=h.util.apply({from:a(l[k],j),round:false},j);c(this,i,k,j);return this;};});Glow.provide(function(d){var c,b=d.anim.Anim.prototype;function a(e){return function(g){return e(1-g);};}b.reversed=false;b.reverse=function(){var g=this.position&&(1-this.position/this.duration)*this.duration,e=this.tween;this.reversed=!this.reversed;this.tween=this._preReverseTween||a(this.tween);this._preReverseTween=e;return this.goTo(g);};b.pingPong=function(){var e=this.tween,g=a(e);this.duration*=2;this.tween=function(h){return(h<0.5)?e(h*2):g((h-0.5)*2);};this._preReverseTween=c;this.reversed=false;return this.goTo(this.position/2);};});Glow.provide(function(l){var d,m,k=l.anim.Anim;function h(o){this.fire("start",o);this.playing=!o.defaultPrevented();}function a(o){this.fire("stop",o);this.playing=o.defaultPrevented();}function i(o){this.goTo(this._anim.position);if(this._anim.playing){this.fire("frame",o);}}function g(p){this._anim.loop=this.loop;this.fire("complete",p);var o=this.playing=(this.loop||p.defaultPrevented());if(!o&&this.destroyOnComplete){this.destroy();}}function j(o){o=o||{};this.destroyOnComplete=(o.destroyOnComplete!==false);this.loop=!!o.loop;this._tracks=[];this._currentIndexes=[];this._startPos=[];this._anim=new k(0,{destroyOnComplete:false,tween:"linear"}).on("start",h,this).on("stop",a,this).on("frame",i,this).on("complete",g,this);}l.util.extend(j,l.events.Target);m=j.prototype;m.duration=0;m.position=0;m.playing=false;m._lastPos=0;m.start=function(){this._anim.start();return this;};m.stop=function(){var o=this._tracks.length,p;this._anim.stop();if(!this._anim.playing){while(o--){p=this._tracks[o][this._currentIndexes[o]];if(p){p.fire("stop");p.playing=false;}}}return this;};m.destroy=function(){var p=this._tracks.length,o,q;while(p--){o=this._tracks[p].length;while(o--){q=this._tracks[p][o];q.destroy&&q.destroy();}}this._anim.destroy();l.events.removeAllListeners([this]);this._tracks=d;};function c(u){var r=u._tracks.length,p,t,s,o,q=u.position;while(r--){p=u._tracks[r];s=u._currentIndexes[r];while(t=p[s]){o=u._startPos[r][s];if(typeof t==="function"){t();s++;break;}else{if(q-o>=t.duration){t.goTo(t.duration).fire("complete");t.playing=false;}else{if(!t.playing){t.fire("start");t.playing=true;}t.goTo(q-o);break;}}s++;}u._currentIndexes[r]=s;}}function e(u){var s=u._tracks.length,q,p,t,o,r=u.position;while(s--){p=u._tracks[s];q=u._currentIndexes[s]+1;while(q--){t=p[q];if(!t){continue;}if(u._startPos[s][q]<u.position){break;}if(typeof t!=="function"){t.goTo(0);}}u._currentIndexes[s]=q;}c(u);}m.goTo=function(o){var p;if(o>this.duration){o=this.duration;}else{if(o<0){o=0;}}this.position=o;(o<this._lastPos)?e(this):c(this);this._lastPos=o;return this;};function n(){throw new Error("Cannot call this method on items contained in a timeline");}function b(o){o.stop();o.start=o.stop=o.reverse=o.pingPong=n;}m.track=function(){var r=arguments,v=this._tracks.length,p=this._tracks[v]=[],t=0,o=this._startPos[v]=[],u;for(var s=0,q=r.length;s<q;s++){u=p[s]=r[s];if(u instanceof k||u instanceof j){b(u);}else{if(typeof u==="number"){u=p[s]=new k(u);}}o[s]=t;t+=u.duration||0;}this._anim.duration=this.duration=Math.max(this.duration,t);this._currentIndexes[v]=0;return this;};l.anim.Timeline=j;});Glow.complete("core","2.0.0b1");�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/glow.debug.js��������������������������������������������������������������������������100644 � 0 � 0 � 31342 11405426600 12451� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*! Copyright 2010 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. */ (function() { // there can be only one if (window.Glow) { return; } window.Glow = true; var glowMap, defaultBase, document = window.document, scripts = document.getElementsByTagName('script'), thisScriptSrc = ''; // we need to be very explicit to defend against some browser // extensions which add elements to the document unexpectedly for (var i = scripts.length; i--;) { // find the most recent script tag for glow if ( /\bglow\b/.test(scripts[i].src || '') ) { thisScriptSrc = scripts[i].src; break; } } // get default base from last script element defaultBase = thisScriptSrc? thisScriptSrc.slice( 0, thisScriptSrc.lastIndexOf('/') +1 ) + '../' : ''; // track when document is ready, must run before the page is finished loading if (!document.readyState) { if (document.addEventListener) { // like Mozilla document.addEventListener('DOMContentLoaded', function () { document.removeEventListener('DOMContentLoaded', arguments.callee, false); document.readyState = 'complete'; }, false ); } } /** @public @name Glow @function @description Creates an instance of the Glow JavaScript Library. @param {string} [version] @param {object} [opts] @param {string} [opts.base] The path to the base folder, in which the Glow versions are kept. @param {boolean} [opts.debug] Have all filenames modified to point to debug versions. */ window.Glow = function(version, opts) { /*debug*///log.info('new Glow("'+Array.prototype.join.call(arguments, '", "')+'")'); opts = opts || {}; var glowInstance, debug = (opts.debug)? '.debug' : '', base = opts.base || defaultBase; glowMap = { versions: ['2.0.0b1', 'src'], '2.0.0b1': { 'core': ['core'+debug+'.js'], 'ui': ['core', 'ui'+debug+'.js', 'ui'+debug+'.css'] } }; if (opts._map) { glowMap = opts._map; } // for testing purposes map can be overridden version = getVersion(version); /*debug*///log.info('Version is "'+version+'"'); if (Glow._build.instances[version]) { /*debug*///log.info('instance for "'+version+'" already exists.'); return Glow._build.instances[version]; } // opts.base should be formatted like a directory if (base.slice(-1) !== '/') { base += '/'; } glowInstance = createGlowInstance(version, base); Glow._build.instances[version] = glowInstance; glowInstance.UID = 'glow' + Math.floor(Math.random() * (1<<30)); if (!opts._noload) { glowInstance.load('core'); } // core is always loaded; return glowInstance; } /** @private @name getVersion @function @param {string} version A (possibly imprecise) version identifier, like "2". @param {boolean} exact Force this function to only return exact matches for the requested version. @description Finds the most recent, available version of glow that matches the requested version. Versions that contain characters other than numbers and dots are never returned unless you ask for then exactly. @returns {string} The version identifier that best matches the given version. For example, given 2.1 this function could return 2.1.5 as the best match. */ var getVersion = function(version, forceExact) { /*debug*///console.info('getVersion("'+version+'")'); var versions = glowMap.versions, matchThis = version + '.', findExactMatch = forceExact || /[^0-9.]/.test(version); // like 1.1-alpha7 // TODO: an empty version means: the very latest version? var i = versions.length; while (i--) { if (findExactMatch) { if (versions[i] === version) { return versions[i]; } } else if ( (versions[i] + '.').indexOf(matchThis) === 0 && !/[^0-9.]/.test(versions[i]) ) { return versions[i]; } } throw new Error('Version "'+version+'" does not exist'); } /** @private @name getMap @function @description Find the file map for a given version. @param {string} version Resolved identifier, like '2.0.0'. @returns {object} A map of package names to files list. */ var getMap = function(version) { /*debug*///log.info('getMap("'+version+'")'); var versions = glowMap.versions, map = null, versionFound = false; var i = versions.length; while (--i > -1) { if (glowMap[versions[i]]) { map = glowMap[versions[i]]; } if (versions[i] === version) { versionFound = true; } if (versionFound && map) { return map; } } throw new Error('No map available for version "' + version + '".'); } /** @private @name injectJs @function @description Start asynchronously loading an external JavaScript file. */ var injectJs = function(src) { /*debug*///log.info('injectJs("'+src+'")'); var head, script; head = document.getElementsByTagName('head')[0]; script = document.createElement('script'); script.src = src; script.type = 'text/javascript'; head.insertBefore(script, head.firstChild); // rather than appendChild() to avoid IE bug when injecting SCRIPTs after BASE tag opens. see: http://shauninman.com/archive/2007/04/13/operation_aborted } /** @private @name injectCss @function @description Start asynchronously loading an external CSS file. */ var injectCss = function(src) { /*debug*///log.info('injectCss("'+src+'")'); var head, link; head = document.getElementsByTagName('head')[0]; link = document.createElement('link'); link.href = src; link.type = 'text/css'; link.rel = 'stylesheet'; head.insertBefore(link, head.firstChild); } /** @private */ Glow._build = { provided: [], // provided but not yet complete instances: {} // built } /** @private @name Glow.provide @function @param {function} builder A function to run, given an instance of glow, and will add a feature to glow. @description Provide a builder function to Glow as part of a package. */ Glow.provide = function(builder) { /*debug*///log.info('Glow.provide('+typeof builder+')'); Glow._build.provided.push(builder); } /** @private @name Glow.complete @function @param {string} name The name of the completed package. @param {string} version The version of the completed package. @description Signals that no more builder functions will be provided by this package. */ Glow.complete = function(name, version) { /*debug*///log.info('complete('+name+', '+version+')'); var glow, loading, builders; if (version === '@'+'SRC@') { version = 'src'} // now that we have the name and version we can move the builders out of provided cache glow = Glow._build.instances[version]; if (!glow) { /*debug*///log.info('Cannot complete, unknown version of glow: '+version); throw new Error('Cannot complete, unknown version of glow: '+version); } glow._build.builders[name] = Glow._build.provided; Glow._build.provided = []; // shortcuts loading = glow._build.loading; builders = glow._build.builders; // try to build packages, in the same order they were loaded for (var i = 0; i < loading.length; i++) { // loading.length may change during loop if (!builders[loading[i]]) { /*debug*///log.info(loading[i]+' has no builders.'); break; } // run the builders for this package in the same order they were loaded for (var j = 0, jlen = builders[loading[i]].length; j < jlen; j++) { /*debug*///log.info('running builder '+j+ ' for '+loading[i]+' version '+glow.version); builders[loading[i]][j](glow); // builder will modify glow } // remove this package from the loaded and builders list, now that it's built if (glow._removeReadyBlock) { glow._removeReadyBlock('glow_loading_'+loading[i]); } builders[loading[i]] = undefined; loading.splice(i, 1); i--; } // try to run onLoaded callbacks glow._release(); } /** @name createGlowInstance @private @function @description Creates an instance of the Glow library. @param {string} version @param {string} base */ var createGlowInstance = function(version, base) { /*debug*///log.info('new glow("'+Array.prototype.join.call(arguments, '", "')+'")'); var glow = function(nodeListContents) { return new glow.NodeList(nodeListContents); }; glow.version = version; glow.base = base; glow.map = getMap(version); glow._build = { loading: [], // names of packages requested but not yet built, in same order as requested. builders: {}, // completed but not yet built (waiting on dependencies). Like _build.builders[packageName]: [function, function, ...]. history: {}, // names of every package ever loaded for this instance callbacks: [] }; // copy properties from glowInstanceMembers for (var prop in glowInstanceMembers) { glow[prop] = glowInstanceMembers[prop]; } return glow; } /** @name glowInstanceMembers @private @description All members of this object will be copied onto little-glow instances @type {Object} */ var glowInstanceMembers = { /** @public @name glow#load @function @description Add a package to this instance of the Glow library. @param {string[]} ... The names of 1 or more packages to add. */ load: function() { /*debug*///log.info('glow.load("'+Array.prototype.join.call(arguments, '", "')+'") for version '+this.version); var name = '', src, depends; for (var i = 0, len = arguments.length; i < len; i++) { name = arguments[i]; if (this._build.history[name]) { /*debug*///log.info('already loaded package "'+name+'" for version '+this.version+', skipping.'); continue; } this._build.history[name] = true; // packages have dependencies, listed in the map: a single js file, css files, or even other packages depends = this.map[name]; /*debug*///log.info('depends for '+name+' '+this.version+': "'+depends.join('", "')+'"'); for (var j = 0, lenj = depends.length; j < lenj; j++) { if (depends[j].slice(-3) === '.js') { /*debug*///log.info('dependent js: "'+depends[j]+'"'); src = this.base + this.version + '/' + depends[j]; // readyBlocks are removed in _release() if (this._addReadyBlock) { this._addReadyBlock('glow_loading_'+name); } // provided by core this._build.loading.push(name); injectJs(src); } else if (depends[j].slice(-4) === '.css') { /*debug*///log.info('dependent css "'+depends[j]+'"'); src = this.base + this.version + '/' + depends[j]; injectCss(src); } else { /*debug*///log.info('dependent package: "'+depends[j]+'"'); this.load(depends[j]); // recursively load dependency packages } } } return this; }, /** @public @name glow#loaded @function @param {function} onLoadCallback Called when all the packages load. @description Do something when all the packages load. */ loaded: function(onLoadCallback) { /*debug*///log.info('glow.loaded('+typeof onLoadCallback+') for version '+this.version); this._build.callbacks.push(onLoadCallback); if (this._addReadyBlock) { this._addReadyBlock('glow_loading_loadedcallback'); } this._release(); return this; }, /** @private @name glow#_release @function @description If all loaded packages are now built, then run the onLoaded callbacks. */ _release: function() { /*debug*///log.info('glow._release("'+this.version+'")'); var callback; if (this._build.loading.length !== 0) { /*debug*///log.info('waiting for '+this._build.loading.length+' to finish.'); return; } /*debug*///log.info('running '+this._build.callbacks.length+' loaded callbacks for version "'+this.version+'"'); // run and remove each available _onloaded callback while (callback = this._build.callbacks.shift()) { callback(this); if (this._removeReadyBlock) { this._removeReadyBlock('glow_loading_loadedcallback'); } } }, /** @name glow#ready @function @param {function} onReadyCallback Called when all the packages load and the DOM is available. @description Do something when all the packages load and the DOM is ready. */ ready: function(onReadyCallback) { /*debug*///log.info('(ember) glow#ready('+typeof onReadyCallback+') for version '+this.version+'. There are '+this._build.loading.length+' loaded packages waiting to be built.'); this.loaded(function(glow) { glow.ready( function() { onReadyCallback(glow); } ); }); return this; } } })(); ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/glow.js��������������������������������������������������������������������������������100644 � 0 � 0 � 7516 11405426600 11352� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* Copyright 2010 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. */ (function(){if(window.Glow){return;}window.Glow=true;var f,m,h=window.document,c=h.getElementsByTagName("script"),e="";for(var d=c.length;d--;){if(/\bglow\b/.test(c[d].src||"")){e=c[d].src;break;}}m=e?e.slice(0,e.lastIndexOf("/")+1)+"../":"";if(!h.readyState){if(h.addEventListener){h.addEventListener("DOMContentLoaded",function(){h.removeEventListener("DOMContentLoaded",arguments.callee,false);h.readyState="complete";},false);}}window.Glow=function(n,p){p=p||{};var i,o=(p.debug)?".debug":"",q=p.base||m;f={versions:["2.0.0b1","src"],"2.0.0b1":{core:["core"+o+".js"],ui:["core","ui"+o+".js","ui"+o+".css"]}};if(p._map){f=p._map;}n=b(n);if(Glow._build.instances[n]){return Glow._build.instances[n];}if(q.slice(-1)!=="/"){q+="/";}i=k(n,q);Glow._build.instances[n]=i;i.UID="glow"+Math.floor(Math.random()*(1<<30));if(!p._noload){i.load("core");}return i;};var b=function(o,p){var n=f.versions,r=o+".",s=p||/[^0-9.]/.test(o);var q=n.length;while(q--){if(s){if(n[q]===o){return n[q];}}else{if((n[q]+".").indexOf(r)===0&&!/[^0-9.]/.test(n[q])){return n[q];}}}throw new Error('Version "'+o+'" does not exist');};var g=function(o){var n=f.versions,r=null,p=false;var q=n.length;while(--q>-1){if(f[n[q]]){r=f[n[q]];}if(n[q]===o){p=true;}if(p&&r){return r;}}throw new Error('No map available for version "'+o+'".');};var a=function(o){var n,i;n=h.getElementsByTagName("head")[0];i=h.createElement("script");i.src=o;i.type="text/javascript";n.insertBefore(i,n.firstChild);};var l=function(o){var i,n;i=h.getElementsByTagName("head")[0];n=h.createElement("link");n.href=o;n.type="text/css";n.rel="stylesheet";i.insertBefore(n,i.firstChild);};Glow._build={provided:[],instances:{}};Glow.provide=function(i){Glow._build.provided.push(i);};Glow.complete=function(q,o){var u,t,s;if(o==="@SRC@"){o="src";}u=Glow._build.instances[o];if(!u){throw new Error("Cannot complete, unknown version of glow: "+o);}u._build.builders[q]=Glow._build.provided;Glow._build.provided=[];t=u._build.loading;s=u._build.builders;for(var r=0;r<t.length;r++){if(!s[t[r]]){break;}for(var p=0,n=s[t[r]].length;p<n;p++){s[t[r]][p](u);}if(u._removeReadyBlock){u._removeReadyBlock("glow_loading_"+t[r]);}s[t[r]]=undefined;t.splice(r,1);r--;}u._release();};var k=function(i,n){var p=function(q){return new p.NodeList(q);};p.version=i;p.base=n;p.map=g(i);p._build={loading:[],builders:{},history:{},callbacks:[]};for(var o in j){p[o]=j[o];}return p;};var j={load:function(){var q="",t,s;for(var r=0,n=arguments.length;r<n;r++){q=arguments[r];if(this._build.history[q]){continue;}this._build.history[q]=true;s=this.map[q];for(var p=0,o=s.length;p<o;p++){if(s[p].slice(-3)===".js"){t=this.base+this.version+"/"+s[p];if(this._addReadyBlock){this._addReadyBlock("glow_loading_"+q);}this._build.loading.push(q);a(t);}else{if(s[p].slice(-4)===".css"){t=this.base+this.version+"/"+s[p];l(t);}else{this.load(s[p]);}}}}return this;},loaded:function(i){this._build.callbacks.push(i);if(this._addReadyBlock){this._addReadyBlock("glow_loading_loadedcallback");}this._release();return this;},_release:function(){var i;if(this._build.loading.length!==0){return;}while(i=this._build.callbacks.shift()){i(this);if(this._removeReadyBlock){this._removeReadyBlock("glow_loading_loadedcallback");}}},ready:function(i){this.loaded(function(n){n.ready(function(){i(n);});});return this;}};})();����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/images/AutoSuggest/spinner.gif���������������������������������������������������������100644 � 0 � 0 � 1241 11405426600 15715� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������GIF89a��ò��ÿÿÿ���ÂÂÂBBB���bbb‚‚‚’’’!þCreated with ajaxload.info�!ù� ���!ÿ NETSCAPE2.0���,�������3ºÜþ0ÊIkc:œN˜f E±1º™Á¶.`ÄÂqÐ-[9ݦ9 JkçH��!ù� ��,�������4ºÜþNŒ! „ »°æŠDqBQT`1 `LE[¨|µußía€ ×â†C²%$*�!ù� ��,�������6º2#+ÊAÈ�Ì”V/…côNñIBa˜«pð ̳½ƨ+YíüƒÃ2©dŸ¿�!ù� ��,�������3ºb%+Ê2†‘ìœV_…‹¦ …! 1D‡aªF‚°ÑbR]ó=08,�Ȥr9L��!ù� ��,�������2ºr'+JçdðóL� &vÃ`\bT”…„¹hYB)ÏÊ@é<Ã&,�ȤR’��!ù� ��,�������3º Â�9ãtç¼Úž0Çà!.B¶�ÊW¬¢1  sa»°5÷•�0° ‰»Ÿm)J��!ù� ��,�������2ºÜþð ÙœU]šîÚqp•`ˆÝaœÝ4–…AFÅ0�`›¶ Â@›1€ÂÖΑ��!ù� ��,�������2ºÜþ0ÊI«eBÔœ)×à ŽÇq10©Ê°®PÂaVÚ¥ ub‚ž[���;������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/images/Carousel/arrows.png�������������������������������������������������������������100644 � 0 � 0 � 1012 11405426600 15072� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������‰PNG  ��� IHDR���.���>���c±Ùž���gAMA��±� üa���KPLTE���ÿÿÿÿÿÿUUUUUUUUUÿÿÿÿÿÿUUUÿÿÿÿÿÿUUUUUUÿÿÿÿÿÿUUUUUUÿÿÿUUUUUUUUUÿÿÿÿÿÿÿÿÿUUUIžü;���tRNS�@ßß@€€ÏÏ�� 0 0ïï¿p¿p×ê)Ä��GIDATHÇ�”éƒ „¹zWE{Àû?im+Á©²åC>v™$D�Z‰—U§³ >ŸB˜ñ`DÚ„/.1xKÞ4øHg<„=Gï¥iwèkpŸ™ÕÍšÁг…(cP õ†$º5Ü‘´é i�×øÌ§Ï1�n £Éà±t’ÖÕº‘#½5à¥{Jƒ¡›¡k5†H{;”,ïº]UÝhŒZëH¬ßÁßþÕ&4g°)ÍHpONŽ»IÅx½ÈàË5Æ�Vdàlü⃷4áMƒ�tÆcÜqô.Qê˜vG_ƒ}fV7k?€g QÆ ò–$†5<�´õ…´ÎkºçÓû”N7…qdp[ †I»jÝÈ€‘ÞðÒ‹=¥ÁÒÍ8´C¤Ž½Ê –w×®ªk4F-„u$Öïàoÿê?“�š3؃f$6�_w˜X+rJÌà����IEND®B`‚����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/ui.css���������������������������������������������������������������������������������100644 � 0 � 0 � 6111 11405426600 11161� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*!Copyright 2010 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. */ .glow200b1-overlay{position:absolute;z-index:9991;overflow:hidden;}.glow200b1-overlay .hidden .overlay-content{display:block;visibility:hidden;}.glow200b1-overlay .shown .overlay-content{display:block;visibility:visible;}.glow200b1-AutoSuggest{overflow:hidden;font-size:small;}.glow200b1-AutoSuggest .AutoSuggest-content{overflow:auto;margin:0;padding:0;list-style:none;background:#f9fafc;border-right:1px solid #ceced0;border-left:1px solid #d2d2d2;border-top:1px solid #ceced0;zoom:1;}.glow200b1-AutoSuggest .AutoSuggest-item{margin:0;padding:4px 16px;display:block;border-bottom:1px solid #ceced0;color:#000;}.glow200b1-AutoSuggest .active{background:#3a3a3c;color:#fff;}.glow200b1-AutoSuggest .AutoSuggest-match{font-style:normal;text-decoration:underline;}.glow200b1-AutoSuggest .AutoSuggest-item:focus{outline:none;}.glow200b1-AutoSuggest-loading{background:url(images/AutoSuggest/spinner.gif) no-repeat right center;}.glow200b1-Carousel .Carousel-content{position:relative;}.glow200b1-Carousel .Carousel-next,.glow200b1-Carousel .Carousel-prev{position:absolute;z-index:3;cursor:pointer;background:#333;background:rgba(51,51,51,0.75);line-height:100%;background:none\9;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#BF333333,endColorstr=#BF333333);}.glow200b1-Carousel .Carousel-next{right:0;}.glow200b1-Carousel .Carousel-prev{left:0;}.glow200b1-Carousel .Carousel-btnIcon{position:absolute;left:50%;top:50%;margin:-15px 0 0 -11px;background:url(images/Carousel/arrows.png);width:23px;height:31px;}.glow200b1-Carousel .Carousel-prev .Carousel-btnIcon{background-position:-23px 0;}.glow200b1-Carousel .Carousel-prev-disabled .Carousel-btnIcon{background-position:-23px -31px;}.glow200b1-Carousel .Carousel-next-disabled .Carousel-btnIcon{background-position:0 -31px;}.glow200b1-Carousel .Carousel-pageNav{height:8px;margin:4px 0 4px auto;position:relative;}.glow200b1-Carousel .Carousel-pageNav div{display:inline-block;height:8px;width:8px;overflow:hidden;color:#ccc;background:#ccc;margin:0 0 0 4px;cursor:pointer;font-size:medium;vertical-align:top;*display:inline;zoom:1;}.glow200b1-Carousel .Carousel-pageNav div.active{color:#333;background:#333;}.glow200b1-Carousel .Carousel-pageNavNumbers{margin:4px 0;color:#333;}.glow200b1-Carousel .Carousel-pageNavNumbers div{display:inline;cursor:pointer;font-size:medium;margin:0 0 0 4px;}.glow200b1-Carousel .Carousel-pageNavNumbers div.active{font-weight:bold;}.glow200b1-Carousel .Carousel-titles{position:relative;}.glow200b1-Carousel .Carousel-title{position:absolute;background:#ccc;overflow:hidden;}�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/ui.debug.css���������������������������������������������������������������������������100644 � 0 � 0 � 7572 11405426600 12262� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*! Copyright 2010 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. */ /* start widgets.css */ /* end widgets.css */ .glow200b1-overlay { position: absolute; z-index: 9991; overflow: hidden; } .glow200b1-overlay .hidden .overlay-content { display: block; visibility: hidden; } .glow200b1-overlay .shown .overlay-content { display: block; visibility: visible; } .glow200b1-AutoSuggest { overflow: hidden; font-size: small; } .glow200b1-AutoSuggest .AutoSuggest-content { overflow: auto; margin: 0; padding: 0; list-style: none; background: #f9fafc; border-right: 1px solid #ceced0; border-left: 1px solid #d2d2d2; border-top: 1px solid #ceced0; /* a little slap for IE6... */ zoom: 1; } .glow200b1-AutoSuggest .AutoSuggest-item { margin: 0; padding: 4px 16px; display: block; border-bottom: 1px solid #ceced0; color: #000; } .glow200b1-AutoSuggest .active { background: #3a3a3c; color: #fff; } .glow200b1-AutoSuggest .AutoSuggest-match { font-style: normal; text-decoration: underline; } .glow200b1-AutoSuggest .AutoSuggest-item:focus { outline: none; } .glow200b1-AutoSuggest-loading { background: url(images/AutoSuggest/spinner.gif) no-repeat right center; } .glow200b1-Carousel .Carousel-content { position: relative; } /* next/prev buttons */ .glow200b1-Carousel .Carousel-next, .glow200b1-Carousel .Carousel-prev { position: absolute; z-index: 3; cursor: pointer; background: #333; background: rgba(51, 51, 51, 0.75); line-height: 100%; /* nasty hack for the background, IE6 7 & 8 */ background: none\9; filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#BF333333,endColorstr=#BF333333); } .glow200b1-Carousel .Carousel-next { right: 0; } .glow200b1-Carousel .Carousel-prev { left: 0; } .glow200b1-Carousel .Carousel-btnIcon { position: absolute; left: 50%; top: 50%; margin: -15px 0 0 -11px; background: url(images/Carousel/arrows.png); width: 23px; height: 31px; } .glow200b1-Carousel .Carousel-prev .Carousel-btnIcon { background-position: -23px 0; } .glow200b1-Carousel .Carousel-prev-disabled .Carousel-btnIcon { background-position: -23px -31px; } .glow200b1-Carousel .Carousel-next-disabled .Carousel-btnIcon { background-position: 0 -31px; } /* Page nav */ .glow200b1-Carousel .Carousel-pageNav { height: 8px; margin: 4px 0 4px auto; position: relative; } .glow200b1-Carousel .Carousel-pageNav div { display: inline-block; height: 8px; width: 8px; overflow: hidden; color: #ccc; background: #ccc; margin: 0 0 0 4px; cursor: pointer; font-size: medium; vertical-align: top; /* inline-block for IE */ *display: inline; zoom: 1; } .glow200b1-Carousel .Carousel-pageNav div.active { color: #333; background: #333; } .glow200b1-Carousel .Carousel-pageNavNumbers { margin: 4px 0; color: #333; } .glow200b1-Carousel .Carousel-pageNavNumbers div { display: inline; cursor: pointer; font-size: medium; margin: 0 0 0 4px; } .glow200b1-Carousel .Carousel-pageNavNumbers div.active { font-weight: bold; } /* item titles */ .glow200b1-Carousel .Carousel-titles { position: relative; } .glow200b1-Carousel .Carousel-title { position: absolute; background: #ccc; overflow: hidden; } ��������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/ui.debug.js����������������������������������������������������������������������������100644 � 0 � 0 � 442475 11405426600 12153� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/*! Copyright 2010 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. */ Glow.provide(function(glow) { var NodeList = glow.NodeList, NodeListProto = NodeList.prototype, undefined; /** @name glow.NodeList.focusable @function @extends glow.ui.Behaviour @description Manage a focusable element, or group of elements This method is a shortcut to {@link glow.ui.Focusable} and requires the 'ui' package to be loaded. The first item in the NodeList is treated as the focusable's container. An error is thrown if the first item in the NodeList is not an element. This can be used to create a single focus point for a set of focusable elements. Eg, a menu can have a single tab stop, and the arrow keys can be used to cycle through menu items. This means the user doesn't have to tab through every item in the menu to get to the next set of focusable items. The FocusManager can also be used to make a element 'modal', ensuring focus doesn't go to elements outside it. @param {object} [opts] Options The same options as the {@link glow.ui.Focusable} constructor @returns {glow.ui.Focusable} */ NodeListProto.focusable = function(opts) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.NodeList#focusable expects 0 or 1 argument, not ' + arguments.length + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.NodeList#focusable expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ return new glow.ui.Focusable(this, opts); }; }); // start-source: ui.js /** @name glow.ui @namespace */ Glow.provide(function(glow) { glow.ui = glow.ui || {}; }); // end-source: ui.js Glow.provide(function(glow) { /** @name glow.ui.Behaviour @class @extends glow.events.Target @description Abstract behaviour class. @param {string} name The name of this widget. This is added to class names in the generated DOM nodes that wrap the widget interface. */ function Behaviour() {} glow.util.extend(Behaviour, glow.events.Target); /*!debug*/ /** @name glow.ui.Behaviour#enabled @function @description Get/set the enabled state @param {boolean} [state=true] */ Behaviour.prototype.enabled = function() { throw new Error('#enabled not implemented on behaviour'); } /** @name glow.ui.Behaviour#destroy @function @description Removes the behaviour & event listeners */ Behaviour.prototype.destroy = function() { throw new Error('#destroy not implemented on behaviour'); } /*gubed!*/ // EXPORT glow.ui.Behaviour = Behaviour; }); Glow.provide(function(glow) { var undefined, FocusableProto, // array of focusable instances focusables = [], // the focused element focused, // we use this to track the modal focusable, also to ensure there's only one modalFocusable, documentNodeList = glow(document), ignoreFocus = false; // keep track of what element has focus documentNodeList.on('blur', function(event) { focused = undefined; if (focusables.length) { // activate focusables on a timeout so we pick up a possible subsequent // focus event setTimeout(deactivateAllIfBlurred, 0); } }).on('focus', function(event) { if ( modalFocusable && !modalFocusable.container.contains(event.source) ) { // refocus either the active child or container ( modalFocusable.activeChild[0] || modalFocusable.container[0] ).focus(); return false; } focused = event.source; if (ignoreFocus) { return; } ignoreFocus = true; activateFocusables(); setTimeout(stopIgnoringFocus, 0); }); /** @private @function @description Wot it sez on da tin. (used to cater for browsers that fire multiple focuses per click) */ function stopIgnoringFocus() { ignoreFocus = false; } /** @private @function @description Deactivate all our focusables if nothing has focus */ function deactivateAllIfBlurred() { // if nothing has focus, deactivate our focusables !focused && activateFocusables(); } /** @private @function @description React to a change in focus */ function activateFocusables() { // taking a copy of the array in case any destroy var instances = focusables.slice(0), i = instances.length; while (i--) { // activate / deactivate the focusable depending on where focus is. // This calls active(), passing in either the element focused (within the Focusable container) or false. // The 2 mentions of 'focused' is deliberate. instances[i].active( (focused && instances[i].container.contains(focused) && focused) || false ); } } /** @private @function @description Update the children property for a focusable */ function updateChildren(focusable) { focusable.children = focusable.container.get( focusable._opts.children ); // remove focusable items from the tab flow, we're going to conrol this with tab keys glow(focusable.children).push(focusable.container).prop('tabIndex', -1); } /** @private @function @description Create the default key handler functions */ function createKeyHandler(useLeftRight, useUpDown) { return function(event) { // listen for keypresses, react, and return false if the key was used switch (event.key) { case 'up': return !( useUpDown && this.prev() ); case 'left': return !( useLeftRight && this.prev() ); case 'down': return !( useUpDown && this.next() ); case 'right': return !( useLeftRight && this.next() ); } } } /** @private @description The default key handler functions */ var keyHandlers = { 'arrows' : createKeyHandler(1, 1), 'arrows-x': createKeyHandler(1, 0), 'arrows-y': createKeyHandler(0, 1) } /** @private @function @description Hover listener Used to focus items on hover. 'this' is the Focusable. */ function hoverListener(event) { // set the _activeMethod so this can be passed onto the event this._activeMethod = 'hover'; this._activeEvent = event; this.active(event.source); this._activeEvent = this._activeMethod = undefined; } /** @private @function @description Set _activeMethod to a value and call another function. This allows the _activeMethod to be passed to the event. */ function activeMethodWrap(focusable, methodName, func) { return function(event) { var returnVal; focusable._activeMethod = methodName; focusable._activeEvent = event; returnVal = func.apply(this, arguments); focusable._activeEvent = focusable._activeMethod = undefined; return returnVal; } } /** @name glow.ui.Focusable @class @extends glow.ui.Behaviour @description Manage a focusable element, or group of elements This can be used to create a single focus point for a set of focusable elements. Eg, a menu can have a single tab stop, and the arrow keys can be used to cycle through menu items. This means the user doesn't have to tab through every item in the menu to get to the next set of focusable items. The FocusManager can also be used to make a element 'modal', ensuring focus doesn't go to elements outside it. The aim of this behaviour is to make it easier to conform to <a href="http://www.w3.org/TR/2009/WD-wai-aria-practices-20091215/#keyboard"> ARIA best practices for keyboard navigation </a> @param {glow.NodeList|string} container Parent focusable element of the group If tabindex isn't set on this element, it will be given tabindex="0", allowing the element to be focused using the tab key. @param {object} [opts] Options @param {string} [opts.children] Selector for child items that can be active These can be cycled through using the arrow keys when the Focusable or one of its children is active (usually when it has focus). @param {function|string} [opts.keyboardNav='arrows'] Alter the default keyboard behaviour. If 'arrows-x', the left & right arrow keys trigger {@link glow.ui.Focusable#next Focusable#next} and {@link glow.ui.Focusable#prev Focusable#prev} respectively. If 'arrows-y', the up & down arrow keys trigger {@link glow.ui.Focusable#next Focusable#next} and {@link glow.ui.Focusable#prev Focusable#prev} respectively. 'arrows' is a combination of the two. If a function is provided, it will be passed a {@link glow.events.KeyboardEvent} object. Use {@link glow.ui.Focusable#next Focusable#next}, {@link glow.ui.Focusable#prev Focusable#prev} or {@link glow.ui.Focusable#activate Focusable#activate} to react to the key event. 'this' inside this function refers to the Focusable. @param {boolean} [opts.setFocus=true] Sets whether focus is given to the active element. You need to set this to false if you want focus to remain in another element. @param {string} [opts.activeChildClass='active'] Class name to give the active child element. @param {boolean} [opts.activateOnHover=false] Activate items on hover? @param {boolean} [opts.loop=false] Loop from the last child item to the first (and vice-versa)? When this is true, calling {@link glow.ui.Focusable#next Focusable#next} when the last focusable item is active will activate the first. @example // A collection of buttons glow('#toolbar').focusable({ children: '> li.button' }); // The #toolbar now appears in tab order. // Once focused, the left & right arrow keys navigate between // buttons. @example // A modal dialog var dialog = glow('#dialog').hide(); var focusable = dialog.focusable(); glow('#openDialog').on('click', function() { dialog.show(); focusable.modal(true); }); glow('#closeDialog').on('click', function() { dialog.hide(); focusable.modal(false); }); */ function Focusable(container, opts) { /*!debug*/ if (arguments.length > 2) { glow.debug.warn('[wrong count] glow.ui.Focusable expects 1 or 2 arguments, not ' + arguments.length + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.ui.Focusable expects object for "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ var keyboardNav; opts = this._opts = glow.util.apply({ children: '', keyboardNav: 'arrows', setFocus: true, activeChildClass: 'active' // commented as undefined is falsey enough //activateOnHover: false, //loop: false }, opts || {}); this.container = glow(container); keyboardNav = opts.keyboardNav; // build the keyhander, using presets or provided function this._keyHandler = activeMethodWrap(this, 'key', (typeof keyboardNav === 'string' ? keyHandlers[keyboardNav] : keyboardNav) ); /*!debug*/ if ( !this.container[0] ) { glow.debug.warn('[wrong value] glow.ui.Focusable - No container found'); } if (typeof this._keyHandler != 'function') { glow.debug.warn('[wrong value] glow.ui.Focusable - unexpected value for opts.keyboardNav'); } if (typeof opts.children != 'string') { glow.debug.warn('[wrong type] glow.ui.Focusable expects CSS string for "opts.children" argument, not ' + typeof opts.children + '.'); } /*gubed!*/ // populate #children updateChildren(this); // create initial focal point this.container[0].tabIndex = 0; // Add listener for activateOnHover if (opts.activateOnHover) { this.container.on('mouseover', hoverListener, this); } // listen for clicks this.container.on('click', clickSelectListener, this); // add to our array of focusables focusables.push(this); }; glow.util.extend(Focusable, glow.ui.Behaviour); FocusableProto = Focusable.prototype; /** @name glow.ui.Focusable#_opts @type boolean @description Option object used in construction */ /** @name glow.ui.Focusable#_active @type boolean @description True/false to indicate if the Focusable is active */ FocusableProto._active = false; /** @name glow.ui.Focusable#_modal @type boolean @description True/false to indicate if the Focusable is modal */ FocusableProto._modal = false; /** @name glow.ui.Focusable#_disabled @type boolean @description True/false to indicate if the Focusable is enabled */ FocusableProto._disabled = false; /** @name glow.ui.Focusable#_lastActiveChild @type HTMLElement @description Stores the last value of #activeChild while the focusable is inactive */ /** @name glow.ui.Focusable#_keyHandler @type function @description Key handler function */ /** @name glow.ui.Focusable#_activeMethod @type string @description The last method used to activate a child element */ /** @name glow.ui.Focusable#_activeEvent @type string @description The event object accociated with _activeMethod */ /** @name glow.ui.Focusable#activeChild @type glow.NodeList @description The active child item. This will be an empty NodeList if no child is active */ FocusableProto.activeChild = glow(); /** @name glow.ui.Focusable#activeIndex @type number @description The index of the active child item in {@link glow.ui.Focusable#children}. This will be undefined if no child is active. */ /** @name glow.ui.Focusable#container @type glow.NodeList @description Focusable container */ /** @name glow.ui.Focusable#children @type glow.NodeList @description NodeList of child items that are managed by this Focusable. This will be an empty nodelist if the focusable has no children */ FocusableProto.children = glow(); /** @name glow.ui.Focusable#modal @function @description Get/set modality When a Focusable is modal it cannot be deactivated, focus cannot be given to elements outside of it until modal set to false. @param {boolean} setModal New modal value @returns this when setting, true/false when getting */ FocusableProto.modal = function(setModal) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Focusable#modal expects 0 or 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ if (setModal === undefined) { return this._modal; } if (!this._disabled) { // Activate the modal if it isn't modal already if (setModal && !this._modal) { // Ensure we're not going to get a deadlock with another focusable if (modalFocusable) { modalFocusable.modal(false); } modalFocusable = this; this.active(true); } // switch modal off, if this focusable is modal else if (!setModal && this._modal) { modalFocusable = undefined; } this._modal = !!setModal; } return this; }; /** @private @function @description Update activeChild and activeIndex according to an index. */ function activateChildIndex(focusable, index) { var prevActiveChild = focusable.activeChild[0], activeChildClass = focusable._opts.activeChildClass, activeChild = focusable.activeChild = glow( focusable.children[index] ), eventData = { item: activeChild, itemIndex: index, method: focusable._activeMethod || 'api', methodEvent: focusable._activeEvent }; focusable.activeIndex = index; // have we changed child focus? if ( prevActiveChild === activeChild || focusable.fire('childActivate', eventData).defaultPrevented() ) { return; } // take the current active item out of the tab order if (prevActiveChild) { prevActiveChild.tabIndex = -1; glow(prevActiveChild).removeClass(activeChildClass); } // put the current active item into the tab order focusable.activeChild[0].tabIndex = 0; focusable.activeChild.addClass(activeChildClass); // give physical focus to the new item focusable._opts.setFocus && focusable.activeChild[0].focus(); } /** @private @function @description Get the focusable child index of an element. The element may also be an element within the focusable's child items. @param {glow.ui.Focusable} focusable @param {glow.NodeList} child Element to get the index from. @returns {number} Index or -1 if element is not (and is not within) any of the focusable's child items. */ function getIndexFromElement(focusable, child) { var i, children = focusable.children, firstChild = children[0]; // just exit if there are no child items if ( !firstChild ) { return -1; } child = glow(child).item(0); // do we have an active child to re-enable? if ( child[0] ) { i = children.length; // see if it's in the current child set while (i--) { if ( glow( children[i] ).contains(child) ) { return i; } } } return -1; } /** @private @function @description Ensure an index is within the range of indexes for this focusable. @param {glow.ui.Focusable} focusable @param {number} index Index to keep within range @returns {number} The index within range. If the focusable can loop, the index will be looped. Otherwise the index will be limited to its maximum & minimum */ function assertIndexRange(focusable, index) { var childrenLen = focusable.children.length; // ensure the index is within children range if (focusable._opts.loop) { index = index % childrenLen; if (index < 0) { index = childrenLen + index; } } else { index = Math.max( Math.min(index, childrenLen - 1), 0); } return index; } /** @private @function @description Deactivate the focusable */ function deactivate(focusable) { if ( focusable.fire('deactivate').defaultPrevented() ) { return; } // remove active class focusable.activeChild.removeClass(focusable._opts.activeChildClass); // store focusable so we can reactivate it later focusable._lastActiveChild = focusable.activeChild[0]; // blur the active element ( focusable.activeChild[0] || focusable.container[0] ).blur(); focusable.activeIndex = undefined; // reset to empty nodelist focusable.activeChild = FocusableProto.activeChild; focusable._active = false; // remove listeners documentNodeList.detach('keypress', focusable._keyHandler).detach('keydown', keySelectListener); // allow the container to receive focus in case the child elements change focusable.container.prop('tabIndex', 0); } /** @private @function @description Activate the focusable */ function activate(focusable, toActivate) { var _active = focusable._active, focusContainerIfChildNotFound, indexToActivate = -1; // if currently inactive... if (!_active) { if ( focusable.fire('activate').defaultPrevented() ) { return; } updateChildren(focusable); focusable._active = true; // start listening to the keyboard documentNodeList.on('keypress', focusable._keyHandler, focusable).on('keydown', keySelectListener, focusable); // give focus to the container - a child element may steal focus in activateChildIndex focusContainerIfChildNotFound = true; } // Work out what child item to focus. // We avoid doing this if we were if ( focusable.children[0] ) { // activating by index if (typeof toActivate === 'number') { indexToActivate = assertIndexRange(focusable, toActivate); } // activating by element else if (typeof toActivate !== 'boolean') { indexToActivate = getIndexFromElement(focusable, toActivate); } // still no index to activate? If we were previously inactive, try the last active item if (indexToActivate === -1 && !_active) { indexToActivate = getIndexFromElement(focusable, focusable._lastActiveChild); indexToActivate = indexToActivate !== -1 ? indexToActivate : 0; } } // If we have an item to activate, let's go for it if (indexToActivate !== -1 && indexToActivate !== focusable.activeIndex) { activateChildIndex(focusable, indexToActivate); } else if (focusContainerIfChildNotFound) { focusable._opts.setFocus && focusable.container[0].focus(); } } /** @name glow.ui.Focusable#active @function @description Get/set the active state of the Focusable Call without arguments to get the active state. Call with arguments to set the active element. A Focusable will be activated automatically when it receieves focus. @param {number|glow.NodeList|boolean} [toActivate] Item to activate. Numbers will be treated as an index of {@link glow.ui.FocusManager#children children}. 'true' will activate the container, but none of the children. 'false' will deactivate the container and any active child @returns {glow.ui.Focusable|boolean} Returns boolean when getting, Focusable when setting */ FocusableProto.active = function(toActivate) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Focusable#active expects 0 or 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ var _active = this._active; // getting if (toActivate === undefined) { return _active; } // setting if (!this._disabled) { // deactivating if (toActivate === false) { if (!this._modal && _active) { deactivate(this); } } // activating else { activate(this, toActivate) } } return this; }; /** @private @function @description Generates #next and #prev */ function nextPrev(amount) { return function() { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Focusable#' + (amount > 0 ? 'next' : 'prev') + ' expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ if (this._active) { this.active( this.activeIndex + amount ); } return this; } } /** @name glow.ui.Focusable#next @function @description Activate next child item. Has no effect on an inactive Focusable. @returns this */ FocusableProto.next = nextPrev(1); /** @name glow.ui.Focusable#prev @function @description Activate previous child item Has no effect on an inactive Focusable. @returns this */ FocusableProto.prev = nextPrev(-1); /** @name glow.ui.Focusable#disabled @function @description Enable/disable the Focusable, or get the disabled state When the Focusable is disabled, it (and its child items) cannot be activated or receive focus. @param {boolean} [newState] Disable the focusable? 'false' will enable a disabled focusable. @returns {glow.ui.Focusable|boolean} Returns boolean when getting, Focusable when setting */ FocusableProto.disabled = function(newState) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Focusable#disabled expects 0 or 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ // geting if (newState === undefined) { return this._disabled; } // setting if (newState) { this.active(false); this._disabled = !!newState; } else { this._disabled = !!newState; // reactivate it if it were modal if (this._modal) { this.active(true); } } return this; } /** @name glow.ui.Focusable#destroy @function @description Destroy the Focusable This removes all focusable behaviour from the continer and child items. The elements themselves will not be destroyed. @returns this */ FocusableProto.destroy = function() { var i = focusables.length; glow.events.removeAllListeners( [this] ); this.modal(false).active(false).container // remove listeners .detach('mouseover', hoverListener) .detach('click', clickSelectListener) // remove from tab order .prop('tabIndex', -1); this.container = undefined; // remove this focusable from the static array while (i--) { if (focusables[i] === this) { focusables.splice(i, 1); break; } } } /** @name glow.ui.Focusable#event:select @event @description Fires when a child of the Focusable is selected. Items are selected by clicking, or pressing enter when a child is active. Cancelling this event prevents the default click/key action. @param {glow.events.Event} event Event Object @param {glow.NodeList} event.item Item selected. @param {number} event.itemIndex The index of the selected item in {@link glow.ui.Focusable#children}. */ /** @private @function @description Listens for click selections on the Focusable 'this' is the Focusable. */ function clickSelectListener() { if ( this.activeChild[0] ) { return !this.fire('select', { item: this.activeChild, itemIndex: this.activeIndex }).defaultPrevented(); } } /** @private @function @description Same as above, but for keys 'this' is the Focusable. */ function keySelectListener(event) { if (event.key === 'return') { return clickSelectListener.call(this); } } /** @name glow.ui.Focusable#event:activate @event @description Fires when the Focusable becomes active Cancelling this event prevents the Focusable being actived @param {glow.events.Event} event Event Object */ /** @name glow.ui.Focusable#event:childActivate @event @description Fires when a child item of the Focusable becomes active Cancelling this event prevents the child item being actived @param {glow.events.Event} event Event Object @param {glow.NodeList} event.item Item activated. @param {number} event.itemIndex The index of the activated item in {@link glow.ui.Focusable#children}. @param {string} event.method Either 'key', 'hover' or 'api' depending on how the item was activated. This allows you to react to certain kinds of activation. @param {glow.events.DomEvent} [event.methodEvent] An event object for the 'key' or 'hover' event. For 'key' methods this will be a more specific {@link glow.events.KeyboardEvent}. If the method was neither 'key' or 'hover', methodEvent will be undefined. */ /** @name glow.ui.Focusable#event:deactivate @event @description Fires when the Focusable becomes deactive Cancelling this event prevents the Focusable being deactived @param {glow.events.Event} event Event Object */ // EXPORT glow.ui.Focusable = Focusable; }); Glow.provide(function(glow) { var undefined, WidgetProto; /** @name glow.ui.Widget @constructor @extends glow.events.Target @description An abstract Widget class The Widget class serves as a base class that provides a shared framework on which other, more specific, widgets can be implemented. While it is possible to create an instance of this generic widget, it is more likely that your widget class will extend this class. Your widget constructor should call the base constructor, and should end in a call to _init. @param {string} name The name of this widget. This is added to class names in the generated DOM nodes that wrap the widget interface. @param {object} [opts] @param {string} [opts.className] Class name to add to the container. @param {string} [opts.id] Id to add to the container. @example function MyWidget(opts) { // set up your widget... // call the base constructor, passing in the name and the options glow.ui.Widget.call(this, 'MyWidget', opts); // start init this._init(); } glow.util.extend(MyWidget, glow.ui.Widget); */ function Widget(name, opts) { /*!debug*/ if (arguments.length < 1 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.ui.Widget expects 1 or 2 arguments, not '+arguments.length+'.'); } if (typeof arguments[0] !== 'string') { glow.debug.warn('[wrong type] glow.ui.Widget expects argument 1 to be of type string, not '+typeof arguments[0]+'.'); } /*gubed!*/ this._name = name; this._locale = 'en'; // todo: default should come from i18n module this.phase = 'constructed'; this._observers = []; this._opts = opts || {}; } glow.util.extend(Widget, glow.events.Target); // Widget is a Target WidgetProto = Widget.prototype; /** @name glow.ui.Widget#_locale @protected @type string @description The locale of the widget */ /** @name glow.ui.Widget#_name @protected @type string @description The name of the widget. This is the first argument passed into the constructor. */ /** @name glow.ui.Widget#_stateElm @protected @type glow.NodeList @description The wrapper element that contains the state class */ /** @name glow.ui.Widget#_themeElm @protected @type glow.NodeList @description The wrapper element that contains the theme class */ /** @name glow.ui.Widget#_opts @protected @type Object @description The option object passed into the constructor */ /** @name glow.ui.Widget#_disabled @protected @type boolean @description Is the widget disabled? This is read-only */ WidgetProto._disabled = false; /** @name glow.ui.Widget#_observers @protected @type object[] @description Objects (usually widgets & dehaviours) observing this Widget */ /** @name glow.ui.Widget#phase @type string @description The phase within the lifecycle of the widget. Will be one of the following: <dl> <dt>constructed</dt> <dd>The widget has been constructed but not yet initialised</dd> <dt>initialised</dt> <dd>The widget has been initialised but not yet build</dd> <dt>built</dt> <dd>The widget has been built but not yet bound</dd> <dt>ready</dt> <dd>The widget is in a fully usable state</dd> <dt>destroyed</dt> <dd>The widget has been destroyed</dd> </dl> Usually, init, build & bind are done in the constructor, so you may only interact with a widget that is either 'ready' or 'destroyed'. */ /** @name glow.ui.Widget#container @type glow.NodeList @description The outermost wrapper element of the widget. */ /** @name glow.ui.Widget#content @type glow.NodeList @description The content element of the widget This is inside various wrapper elements used to track the state of the widget. */ function syncListener(e) { // handle notifications about changes to the disabled state if (e.disabled !== undefined) { this.disabled(e.disabled); } else if (e.destroy) { this.destroy(); } } /** @name glow.ui.Widget#_tie @protected @function @description Specify a group of widgets that should stay in _sync with this one. These synced widgets can listen for a '_sync' event on themselves, defining their own handler for the provided event. The disabled and destroy methods automatically synchronize with their synced child widgets. @param {glow.ui.Widget} ... Child widgets to synchronize with. @example function MyWidget() { this.value = 0; // initially // this widget handles notifications of new values // from other widgets it is syced to var that = this; this.on('_sync', function(e) { if (typeof e.newValue !== undefined) { that.value = e.newValue; } }); } glow.util.extend(MyWidget, glow.ui.Widget); // MyWidget extends the Base Widget MyWidget.prototype.setValue = function(n) { this.value = n; // this widget notifies about changes to its value this._sync({newValue: this.value}); } // widgets b and c will all be notified when a's value changes var a = new MyWidget('A'); var b = new MyWidget('B'); var c = new MyWidget('C'); a._tie(b, c); a.setValue(1); // a, b, and c all have a value of 1 now */ WidgetProto._tie = function(/*...*/) { /*!debug*/ if (arguments.length === 0) { glow.debug.warn('[wrong count] glow.ui.Widget#_tie expects at least 1 argument, not '+arguments.length+'.'); } /*gubed!*/ var newObservers = Array.prototype.slice.call(arguments), i = newObservers.length; // add a default _sync listener to react to disabled and destroy while (i--) { newObservers[i].on('_sync', syncListener); } this._observers = this._observers.concat(newObservers); return this; } /** @developer @name glow.ui.Widget#_sync @method @param {object} [changes] Key/value collection of new information. Will be added to the listeners' event. @description Tell all widgets synced with this widget about any changes. */ WidgetProto._sync = function(changes) { // public shortcut to fire _notify /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Widget#_sync expects 1 or fewer arguments, not '+arguments.length+'.'); } if (arguments.length && typeof changes !== 'object') { glow.debug.warn('[wrong type] glow.ui.Widget#_sync expects argument 1 to be of type object, not '+(typeof changes)+'.'); } /*gubed!*/ glow.events.fire( this._observers, '_sync', changes || {} ); return this; } /** @name glow.ui.Widget#disabled @function @description Enable/disable the Widget, or get the disabled state If other widgets are synced with this one, they will become disabled too. @param {boolean} [newState] Disable the focusable? 'false' will enable a disabled focusable. @example var a = new MyWidget(); var b = new MyWidget(); var c = new MyWidget(); c._tie(a, b); c.disabled(true); // a, b, and c are now disabled */ WidgetProto.disabled = function(newState) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Widget#disabled expects 0 or 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ // geting if (newState === undefined) { return this._disabled; } // setting newState = !!newState; if ( newState !== this._disabled && !this.fire('disabled', {disabled:newState}).defaultPrevented() ) { this._sync({ disabled: newState }); this._stateElm[newState ? 'addClass' : 'removeClass']('disabled'); this._disabled = !!newState; } return this; } /** @name glow.ui.Widget#_init @protected @function @description Initialise the widget. This is similar to the constructor, but for code that you may need to run again. You init function should call the base _init, and end in a call to _build on your widget. @example MyWidget.prototype._init = function() { // set properties here // call base _init glow.ui.Widget.prototype._init.call(this); // call _build this._build(); } */ WidgetProto._init = function() { this.phase = 'initialised'; } /** @name glow.ui.Widget#_build @protected @function @description Build the html structure for this widget. All actions relating to wrapping, creating & moving elements should be done in this method. The base method creates the container, theme & state elements. Adding behaviour to these elements should be handed in {@link glow.ui.Widget#_bind}. You Widget's _build method should call the base build method and end in a call to _bind. @param {selector|HTMLElement|NodeList} [content] Content element for the widget. This will be wrapped in container, theme & state elements. By default this is an empty div. @example MyWidget.prototype._build = function() { // create some content var content = glow('<p>Hello!</p>'); // call the base build glow.ui.Widget.prototype._build.call(this, content); // call _bind this._bind(); } */ WidgetProto._build = function(content) { /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.Widget#_build expects 0-1 argument, not '+arguments.length+'.'); } /*gubed!*/ var container, name = this._name, opts = this._opts; content = this.content = glow(content || '<div></div>'); /*!debug*/ if (content.length < 1) { glow.debug.warn('[error] glow.ui.Widget#_build expects a content node to attach to. The given "content" argument was empty or not found.'); } /*gubed!*/ container = this.container = glow('' + '<div class="glow200b1-' + name + '">' + '<div class="' + name + '-theme">' + '<div class="' + name + '-state"></div>' + '</div>' + '</div>' + ''); content.addClass(name + '-content').wrap(container); this._stateElm = content.parent(); this._themeElm = this._stateElm.parent(); if (opts.className) { container.addClass(opts.className); } if (opts.id) { container[0].id = opts.id; } this.phase = 'built'; } /** @developer @name glow.ui.Widget#_bind @function @description Add behaviour to elements created in {@link glow.ui.Widget#_build _build}. Your _bind method should call the base _bind and may end in a call to _updateUi for initial positioning etc. @example MyWidget.prototype._bind = function() { // add some behaviour this.content.on('click', function() { alert('Hello!'); }); // call base _bind glow.ui.Widget.prototype._bind.call(this); } */ WidgetProto._bind = function() { this.phase = 'ready'; } /** @name glow.ui.Widget#_updateUi @function @description Cause any functionality that deals with visual layout or UI display to update. This function should be overwritten by Widgets that need to update or redraw. For example, you may use this method to reposition or reorder elements. This is a convention only, the base method does nothing. @example MyWidget.prototype.updateUi = function() { // update the UI } */ WidgetProto._updateUi = function() {} /** @developer @name glow.ui.Widget#destroy @function @description Cause any functionality that deals with removing and deleting this widget to run. By default the container and all it's contents are removed. @fires glow.ui.Widget#event:destroy */ WidgetProto.destroy = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.ui.Widget#destroy expects 0 arguments, not '+arguments.length+'.'); } /*gubed!*/ if ( !this.fire('destroy').defaultPrevented() ) { this._sync({ destroy: 1 }); glow.events.removeAllListeners( [this] ); this.container.destroy(); this.phase = 'destroyed'; } return this; } /** @developer @name glow.ui.Widget#event:disable @event @description Fired after the disabled property is changed via the {@link glow.ui.Widget#disable} or {@link glow.ui.Widget#enable} method. This includes widgets that are changed as a result of being synced to this one. */ /** @developer @name glow.ui.Widget#event:destroy @event @description Fired when destroy is called on this widget. @see glow.ui.Widget#destroy */ // export glow.ui.Widget = Widget; }); Glow.provide(function(glow) { var OverlayProto, WidgetProto = glow.ui.Widget.prototype, idCounter = 0, undefined, instances = {}; // like {uid: overlayInstance} var vis = { SHOWING: 2, SHOWN: 1, HIDING: -1, HIDDEN: -2 }; /** @name glow.ui.Overlay @class @augments glow.ui @description A container element displayed on top of the other page content @param {selector|NodeList|String|boolean} content the element that contains the contents of the overlay. If not in the document, you must append it to the document before calling show(). @param {object} [opts] @param {function|selector|NodeList|boolean} [opts.hideWhenShown] Select which things to hide whenevr the overlay is in a shown state. By default all `object` and `embed` elements will be hidden, in browsers that cannot properly layer those elements, whenever any overlay is shown. Set this option to a false value to cause the overlay to never hide any elements, or set it to a bespoke selector, NodeList or a function that returns a NodeList which will be used instead. @example var myOverlay = new glow.ui.Overlay( glow( '<div>' + ' <p>Your Story has been saved.</p>' + '</div>' ).appendTo(document.body) ); glow('#save-story-button').on('click', function() { myOverlay.show(); }); */ function Overlay(content, opts) { /*!debug*/ if (arguments.length < 1 || content === undefined) { glow.debug.warn('[wrong type] glow.ui.Overlay expects "content" argument to be defined, not ' + typeof content + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.ui.Overlay expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ var that = this, ua; opts = glow.util.apply({ }, opts); //call the base class's constructor Overlay.base.call(this, 'overlay', opts); this.uid = 'overlayId_' + glow.UID + '_' + (++idCounter); instances[this.uid] = this; // useful for modal overlays? this._init(opts); this._build(content); this._bind(); } glow.util.extend(Overlay, glow.ui.Widget); OverlayProto = Overlay.prototype; OverlayProto._init = function() { WidgetProto._init.call(this); /** @name glow.ui.Overlay#shown @description True if the overlay is shown. This is a read-only property to check the state of the overlay. @type boolean */ this.shown = false; return this; } OverlayProto.destroy = function() { WidgetProto.destroy.call(this); delete instances[this.uid]; } OverlayProto._build = function(content) { var that = this; WidgetProto._build.call(this, content); /*!debug*/ if (this.content.length < 1) { glow.debug.warn('[ivalid argument] glow.ui.Overlay expects "content" argument to refer to an element that exists, no elements found for the content argument provided.'); } /*gubed!*/ // some browsers need to hide Flash when the overlay is shown (like non-mac opera and gecko 1.9 or less) if (this._opts.hideWhenShown === undefined) { // need to make our own flash handler ua = navigator.userAgent; // like "... rv:1.9.0.5) gecko ..." /** A function that returns a NodeList containing all elements that need to be hidden. @name glow.ui.Overlay#_whatToHide @private @returns {glow.NodeList} Elements that need to be hidden when the overlay is shown. */ this._whatToHide = ( glow.env.opera && !/macintosh/i.test(ua) || /rv:1\.9\.0.*\bgecko\//i.test(ua) || glow.env.webkit && !/macintosh/i.test(ua) )? function() { return glow('object, embed')/*.filter(function() { return !that.container.contains(this); // don't hide elements that are inside the overlay });*/ } : function() { return glow(); } } else { // user provides their own info about what to hide if (!this._opts.hideWhenShown) { // a value that is false this._whatToHide = function() { return glow(); } } else if (typeof this._opts.hideWhenShown === 'function') { // a function this._whatToHide = this._opts.hideWhenShown; } else if (this._opts.hideWhenShown.length !== undefined) { // nodelist or string? this._whatToHide = function() { return glow('*').filter(this._opts.hideWhenShown); } } } //add IE iframe hack if needed, wrap content in an iFrame to prevent certain elements below from showing through if (glow.env.ie) { this._iframe = glow('<iframe src="javascript:\'\'" style="display:block;width:100%;height:1000px;margin:0;padding:0;border:none;position:absolute;top:0;left:0;filter:alpha(opacity=0);"></iframe>') this._iframe.css('z-index', 0); this._iframe.insertBefore(this.content); } this.content .css('position', 'relative') .css('z-index', 1) .css('top', 0) .css('left', 0); return this; } /** @name glow.ui.Overlay#hideFlash @method @description Hides all Flash elements on the page, outside of the overlay. This should only be neccessary on older browsers that cannot properly display overlay content on top of Flash elements. On those browsers Glow will automatically call this method for you in the onshow event, and will automatically call showFlash for you in the afterhide event. */ OverlayProto.hideFlash = function() { /*debug*///console.log('hideFlash'); var toHide, that = this, hidBy = ''; toHide = this._whatToHide(); // multiple overlays may hide the same element // flash elements keep track of which overlays have hidden them // trying to hide a flash element more than once does nothing for (var i = 0, leni = toHide.length; i < leni; i++) { hidBy = (toHide.item(i).data('overlayHidBy') || ''); if (hidBy === '') { toHide.item(i).data('overlayOrigVisibility', toHide.item(i).css('visibility')); toHide.item(i).css('visibility', 'hidden'); } if (hidBy.indexOf('['+this.uid+']') === -1) { toHide.item(i).data('overlayHidBy', hidBy + '['+this.uid+']'); } } // things were hidden, make sure they get shown again if (toHide.length && !that._doShowFlash) { // do this only once that._doShowFlash = true; that.on('afterHide', function() { /*debug*///console.log('callback'); that.showFlash(); }); } this._hiddenElements = toHide; } /** @name glow.ui.Overlay#showFlash @method @description Hides all Flash elements on the page, outside of the overlay. If a Flash element has been hidden by more than one overlay, you must call showFlash once for each time it was hidden before the Flash will finally appear. */ OverlayProto.showFlash = function() { /*debug*///console.log('showFlash'); var hidBy = ''; if (!this._hiddenElements || this._hiddenElements.length === 0) { // this overlay has not hidden anything? return; } var toShow = this._hiddenElements; for (var i = 0, leni = toShow.length; i < leni; i++) { hidBy = (toShow.item(i).data('overlayHidBy') || ''); if (hidBy.indexOf('['+this.uid+']') > -1) { // I hid this hidBy = hidBy.replace('['+this.uid+']', ''); // remove me from the list of hiders toShow.item(i).data('overlayHidBy', hidBy); } if (hidBy == '') { // no hiders lefts toShow.item(i).css( 'visibility', toShow.item(i).data('overlayOrigVisibility') ); } } } /** @name glow.ui.Overlay#event:show @event @description Fired when the overlay is about to appear on the screen, before any animation. At this point you can access the content of the overlay and make changes before it is shown to the user. If you prevent the default action of this event (by returning false or calling event.preventDefault) the overlay will not show. @param {glow.events.Event} event Event Object */ /** @name glow.ui.Overlay#event:afterShow @event @description Fired when the overlay is showing to the user and any delay or 'show' animation is complete. This event is ideal to assign focus to a particular part of the overlay. If you want to change content of the overlay before it appears, see the 'show' event. @param {glow.events.Event} event Event Object */ /** @name glow.ui.Overlay#event:hide @event @description Fired when the overlay is about to hide. If you prevent the default action of this event (by returning false or calling event.preventDefault) the overlay will not hide. @param {glow.events.Event} event Event Object */ /** @name glow.ui.Overlay#event:afterHide @event @description Fired when the overlay has fully hidden, after any delay or hiding animation has completed. @param {glow.events.Event} event Event Object */ // animations that can be referred to in setAnim by string. // Each is an array of 2 item, one function to put the Overlay in an initial state // for this animation, and one for the animation itself var anims = { slide: [ function(overlay) { overlay.container.height(0); }, function(isShow, callback) { var anim, container = this.container; if (isShow) { anim = container.slideOpen(0.5).data('glow_slideOpen'); } else { anim = container.slideShut(0.5).data('glow_slideShut'); } anim.on('complete', callback); } ], fade: [ function(overlay) { overlay.container.css('opacity', 0); }, function(isShow, callback) { var anim, container = this.container; if (isShow) { anim = container.fadeIn(0.5).data('glow_fadeIn'); } else { anim = container.fadeOut(0.5).data('glow_fadeOut'); } anim.on('complete', callback); } ] } /** @name glow.ui.Overlay#setAnim @function @description Set the animation to use when showing and hiding this overlay. @param {string|Array|Function|null} anim Anim to use. At its simplest, this can be the string 'slide' or 'fade', to give the overlay a fading/sliding animation. If this value is an animation definition, in the form of an array of arguments to pass to the {@link glow.Anim} constructor, those values will be used to create the show animation. The hide animation will then be the reverse of the show. This is the easiest option if you intend your show and hide animations to simply reverse one another. Alternatively, if you need more control over your show and hide animations, you can provide a function. This function will be called whenever the overlay has its show or hide method invoked, and will be provided a boolean (true meaning it's being shown, false meaning it's being hidden), and a callback. You can then manage the animations yourself within that function, and then invoke the callback when either animation is complete. In your function, 'this' refers to the overlay. Passing null will delete any previously set animation. @returns this */ OverlayProto.setAnim = function(anim) { if (anim === null) { delete this._animDef; delete this._animator; } else if (typeof anim === 'string') { anims[anim][0](this); this._animator = anims[anim][1]; } else if (typeof anim === 'function') { this._animator = anim; } else { this._animDef = anim; this._animDef[2] = this._animDef[2] || {}; this._animDef[2].destroyOnComplete = false; } return this; } /** @name glow.ui.Overlay#show @function @param {number} [delay=0] The delay before the overlay is shown. By default, the overlay will show immediately. Specify a number of seconds to delay showing. The event "afterShow" will be called after any delay and animation. @description Displays the overlay after an optional delay period and animation. @returns this */ OverlayProto.show = function(delay) { //if (this.shown) { /*debug*///console.log('show ignored'); // return this; //} var that = this; if ( !this.fire('show').defaultPrevented() ) { if (this._timer) { clearTimeout(this._timer); } if (delay) { this._timer = setTimeout(function() { show.call(that); }, delay * 1000); } else { show.call(that); } } return this; } function show() { /*debug*///console.log('show() curently '+this.state); var that = this; // already being shown? if (this.state === vis.SHOWING || this.state === vis.SHOWN) { return; } setShown(that, true); if (this._whatToHide) { this.hideFlash(); } if (this._animator) { that.state = vis.SHOWING; this._animator.call(this, true, function() { afterShow.call(that); }); } else if (this._animDef) { if (this._anim) { // is hiding? this.state = vis.SHOWING; this._anim.reverse(); } else { // is hidden? this.state = vis.SHOWING; // this same anim is reused (by reversing it) for showing and hiding this._anim = this.container.anim(this._animDef[0], this._animDef[1], this._animDef[2]); this._anim.on('complete', function() { if (that.state === vis.SHOWING) { setShown(that, true); afterShow.call(that); } else if (that.state === vis.HIDING) { setShown(that, false); afterHide.call(that); } }); } this._anim.start(); } else { afterShow.call(this); } } function afterShow() { /*debug*///console.log('after show'); this.state = vis.SHOWN; this.fire('afterShow'); } /** @private @function @description Set the shown state & add/remove a class from the state element */ function setShown(overlay, shownState) { var stateElm = overlay._stateElm; overlay.shown = shownState; if (shownState) { stateElm.removeClass('hidden'); stateElm.addClass('shown'); } else { stateElm.removeClass('shown'); stateElm.addClass('hidden'); } } function hide() { /*debug*///console.log('hide() curently '+this.state); var that = this; if (this.state === vis.HIDING || this.state === vis.HIDDEN) { return; } if (this._animator) { // provided by user this._animator.call(this, false, function() { setShown(that, false); afterHide.call(that); }); } else if (this._anim) { // generated by overlay this.state = vis.HIDING; this._anim.reverse(); this._anim.start(); } else { // no animation setShown(that, false); afterHide.call(this); } } function afterHide() { /*debug*///console.log('after hide'); this.state = vis.HIDDEN; this.fire('afterHide'); } /** @name glow.ui.Overlay#hide @function @param {number} [delay=0] The delay before the overlay is shown. By default, the overlay will show immediately. Specify a number of seconds to delay showing. The event "afterShow" will be called after any delay and animation. @description Hides the overlay after an optional delay period and animation @returns this */ OverlayProto.hide = function(delay) { //if (!this.shown) { /*debug*///console.log('hide ignored'); // return this; //} var that = this; if ( !this.fire('hide').defaultPrevented() ) { if (this._timer) { clearTimeout(this._timer); } if (delay) { this._timer = setTimeout(function() { hide.call(that); }, delay * 1000); } else { hide.call(that); } } return this; } // export glow.ui = glow.ui || {}; glow.ui.Overlay = Overlay; }); Glow.provide(function(glow) { var undefined, AutoSuggestProto, Widget = glow.ui.Widget, WidgetProto = Widget.prototype, // this is used for HTML escaping in _format tmpDiv = glow('<div></div>'); /** @name glow.ui.AutoSuggest @extends glow.ui.Widget @constructor @description Create a menu that displays results filtered by a search term. This widget can be easily linked to a text input via {@link glow.ui.AutoSuggest#linkToInput} so results will be filtered by text entered by the user. This appears as a list of selectable items below the input element (optional) which dynamically updates based on what has been typed so far. By default, items where the search term matches the start of the item (or its 'name' property) will be returned. You can change the filtering behaviour via {@link glow.ui.AutoSuggest#setFilter setFilter}. The matched item (or its 'name' property) will be displayed with the matching portion underlined. You can change the output via {@link glow.ui.AutoSuggest#setFormat setFormat} @param {Object} [opts] Options @param {number} [opts.width] Apply a width to the results list. By default, the AutoSuggest is the full width of its containing element, or the width of the input it's linked to if autoPositioning. @param {number} [opts.maxResults] Limit the number of results to display. @param {number} [opts.minLength=3] Minimum number of chars before search is executed This prevents searching being performed until a specified amount of chars have been entered. @param {boolean} [opts.caseSensitive=false] Whether case is important when matching suggestions. If false, the value passed to the filter will be made lowercase, a custom filter must also lowercase the property it checks. @param {boolean} [opts.activateFirst=true] Activate the first item when results appear? If false, results with be shown with no active item. @param {function|string} [opts.keyboardNav='arrow-y'] Alter the default keyboard behaviour. This is the same as keyboardNav in {@link glow.ui.Focusable}. @example // Make an input auto-complete from an array of tags for a recipe database glow.ui.AutoSuggest() .data(['Vegetarian', 'Soup', 'Sandwich', 'Wheat-free', 'Organic', 'etc etc']) .linkToInput('#recipeTags'); @example // An AutoSuggest embedded in the page, rather than in an overlay var myAutoSuggest = glow.ui.AutoSuggest() .data('recipe.php?ingredients={val}') .linkToInput('#username', { // don't use an overlay, we'll add the autosuggest to the document outselves useOverlay: false }); // add the results into the document myAutoSuggest.container.appendTo('#results'); @example // Make an input suggest from an array of program names, where the // whole string is searched rather than just the start // When the item is clicked, we go to a url new glow.ui.AutoSuggest().setFilter(function(item, val) { return item.name.indexOf(val) !== -1; }).data([ {name: 'Doctor Who', url: '...'}, {name: 'Eastenders', url: '...'}, {name: 'The Thick Of It', url: '...'}, // ... ]).linkToInput('#programSearch').on('select', function(event) { location.href = event.selected.url; }); */ function AutoSuggest(opts) { /*!debug*/ if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.ui.AutoSuggest expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ opts = glow.util.apply({ minLength: 3, keyboardNav: 'arrows-y', activateFirst: true }, opts); Widget.call(this, 'AutoSuggest', opts); this._init(); }; glow.util.extend(AutoSuggest, Widget); AutoSuggestProto = AutoSuggest.prototype; /** @name glow.ui.AutoSuggest#_loading @type boolean @description True if the autosuggest is waiting for data. This happens when getting data is async, has been requested but not returned. */ /** @name glow.ui.AutoSuggest#_pendingFind @type string @description Pending search string. This is populated if find is called while the autoSuggest is _loading. */ /** @name glow.ui.AutoSuggest#_data @type Object[] @description Array of objects, the current datasource for this AutoSuggest. */ AutoSuggestProto._data = []; /** @name glow.ui.AutoSuggest#_dataFunc @type function @description Function used for fetching data (potentially) async. */ /** @name glow.ui.AutoSuggest#_filter @type function @description The current filter function. */ AutoSuggestProto._filter = function(val, caseSensitive) { var nameStart = this.name.slice(0, val.length); nameStart = caseSensitive ? nameStart : nameStart.toLowerCase(); return nameStart === val; }; /** @name glow.ui.AutoSuggest#_format @type function @description The current format function. */ AutoSuggestProto._format = function(result, val) { var text = tmpDiv.text(result.name).html(), valStart = text.toLowerCase().indexOf( val.toLowerCase() ), valEnd = valStart + val.length; // wrap the selected portion in <strong> // This would be so much easier if it weren't for case sensitivity if (valStart !== -1) { text = text.slice(0, valStart) + '<em class="AutoSuggest-match">' + text.slice(valStart, valEnd) + '</em>' + text.slice(valEnd) } return text; }; /** @name glow.ui.AutoSuggest#focusable @type glow.ui.Focusable @description The focusable linked to this autosuggest. */ // Widget lifecycle phases AutoSuggestProto._init = function() { WidgetProto._init.call(this); // call _build this._build(); } AutoSuggestProto._build = function() { WidgetProto._build.call(this, '<ol></ol>'); var opts = this._opts, width = opts.width content = this.content; this.focusable = content.focusable({ children: '> li', keyboardNav: this._opts.keyboardNav, setFocus: false, activateOnHover: true }); width && this.container.width(width); // call _build this._bind(); } /** @private @function @description Select listener for the focusable. 'this' is the AutoSuggest */ function focusableSelectListener(e) { return !this.fire('select', { li: e.item, item: e.item.data('as_data') }).defaultPrevented(); } /** @private @function @description Listens for focus moving in the focusable. 'this' is the autoSuggest */ function focusableChildActivate(e) { var item = e.item, focusable = this.focusable; } function returnFalse() { return false; } AutoSuggestProto._bind = function() { var focusable = this.focusable.on('select', focusableSelectListener, this) .on('childActivate', focusableChildActivate, this); this._tie(focusable); // prevent focus moving on mouse down this.container.on('mousedown', returnFalse); WidgetProto._bind.call(this); } /** @name glow.ui.AutoSuggest#setFilter @function @description Set the function used to filter the dataset for results. Overwrite this to change the filtering behaviour. @param {function} filter Filter function. Your function will be passed 2 arguments, the term entered by the user, and if the search should be case sensitive. Return true to confirm a match. 'this' will be the item in the dataset to check. If the search is case-insensitive, the term entered by the user is automatically lowercased. The default filter will return items where the search term matches the start of their 'name' property. If the dataset is simply an array of strings, that string will be used instead of the 'name' property. @example // Search the name property for strings that contain val myAutoSuggest.setFilter(function(val, caseSensitive) { var name = caseSensitive ? this.name : this.name.toLowerCase(); return name.indexOf(val) !== -1; }); @example // Search the tags property for strings that contain val surrounded by pipe chars // this.tags is like: |hello|world|foo|bar| myAutoSuggest.setFilter(function(val, caseSensitive) { var tags = caseSensitive ? this.tags : this.tags.toLowerCase(); return tags.indexOf('|' + val + '|') !== -1; }); @return this */ AutoSuggestProto.setFilter = function(filter) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.ui.Autosuggest#setFilter expects 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ this._filter = filter; return this; }; /** @name glow.ui.AutoSuggest#setFormat @function @description Control how matches are output. @param {function} formatter Function to generate output. The first param to your function will be the matched item from your data list. The second param is the search value. Return an HTML string or glow.NodeList to display this item in the results list. Ensure you escape any content you don't want treated as HTML. @returns this @example // A username auto-complete // The data url returns a JSON object like [{name='JaffaTheCake', fullName:'Jake Archibald', photo:'JaffaTheCake.jpg'}, ...] glow.ui.AutoSuggest().setFormat(function() { // Format the results like <img src="JaffaTheCake.jpg" alt=""> Jake Archibald (JaffaTheCake) return '<img src="' + data.photo + '" alt=""> ' + data.fullName + ' (' + data.name + ')'; }).data('userSearch.php?usernamePartial={val}').linkToInput('#username'); */ AutoSuggestProto.setFormat = function(formatter) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.ui.Autosuggest#setFormat expects 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ this._format = formatter; return this; }; /** @private @function @description Process the data into an acceptable format for #_data. @param {glow.ui.AutoSuggest} autoSuggest @param {Object[]|string[]|glow.net.XhrResponse} data Array of strings will be converted into an array of objects like {name: val} glow.net.XhrResponse will be converted into Object[]|string[] via .json */ function populateData(autoSuggest, data) { var i, tmpData, event = autoSuggest.fire('data', {data:data}); if ( !event.defaultPrevented() ) { // a listener may have altered the data data = event.data; // if it's an XHR response, convert it to json if (data instanceof glow.net.XhrResponse) { data = data.json(); } if (typeof data[0] === 'string') { tmpData = []; i = data.length; while (i--) { tmpData[i] = { name: data[i] }; } data = tmpData; } /*!debug*/ if ( !data.push ) { glow.debug.warn('[wrong type] glow.ui.Autosuggest data expected to be array, not ' + typeof data + '.'); } else if (data.length && typeof data[0] !== 'object') { glow.debug.warn('[wrong type] glow.ui.Autosuggest data expected to be array of objects, not array of ' + typeof data[0] + '.'); } /*gubed!*/ autoSuggest._data = data; } } /** @private @function @description Create _dataFunc based on a custom function. @param {glow.ui.AutoSuggest} autoSuggest Instance @param {function} func Data fetching function provided by the user via #data */ function setDataFunction(autoSuggest, func) { // create a new function for fetching data autoSuggest._dataFunc = function(val) { var input = autoSuggest.input, bindOpts = autoSuggest._bindOpts, loadingClass = (bindOpts && bindOpts.loadingClass) || ''; // put us in the loading state and call the user's function autoSuggest._loading = true; input.addClass(loadingClass); // call the user's function, providing a callback func.call(this, val, function(data) { var pendingFind = autoSuggest._pendingFind; autoSuggest._loading = false; input.removeClass(loadingClass); // populate data if we've been given some data && populateData(autoSuggest, data); if (pendingFind) { performFind(autoSuggest, pendingFind); autoSuggest._pendingFind = undefined; } }); } } /** @private @function @description Creates a data function to load a single url once. @param url With no {val} placeholder. */ function singleLoadUrl(url) { var dataFetched, currentRequest; return function(val, callback) { // if we've already fetched the data, just call back & return if (dataFetched) { return callback(); } // if we've already sent a request off, just let that one continue if ( !currentRequest ) { currentRequest = glow.net.get(url).on('load', function(response) { // set data for quick retrieval later dataFetched = 1; callback(response); }); } } } /** @private @function @description Creates a data function to load from a url each time a search is made. @param url With {val} placeholder. */ function multiLoadUrl(url) { var currentRequest; return function(val, callback) { var processedUrl = glow.util.interpolate(url, {val:val}); // abort any current request currentRequest && currentRequest.abort(); currentRequest = glow.net.get(processedUrl).on('load', function(response) { callback(response); }); } } /** @name glow.ui.AutoSuggest#data @function @description Set the data or datasource to search. This gives the AutoSuggest the data to search, or the means to fetch the data to search. @param {string|string[]|Object[]|glow.net.Response|function} data Data or datasource. <p><strong>String URL</strong></p> A URL on the same domain can be provided, eg 'results.json?search={val}', where {val} is replaced with the search term. If {val} is used, the URL if fetched on each search, otherwise it is only fetched once on the first search. The result is a {@link glow.net.XhrResponse}, by default this is decoded as json. Use the 'data' event to convert your incoming data from other types (such as XML). <p><strong>glow.net.XhrResponse</strong></p> This will be treated as a json response and decoded to string[] or Object[], see below. <p><strong>string[] or Object[] dataset</strong></p> An Array of strings can be provided. Each string will be converted to {name: theString}, leaving you with an array of objects. An Array of objects can be provided, each object is an object that can be matched. By default the 'name' property of these objects is searched to determine a match, but {@link glow.ui.AutoSuggest#filter filter} can be used to change this. <p><strong>function</strong></p> Providing a function means you have total freedom over fetching the data for your autoSuggest, sync or async. Your function will be called by the AutoSuggest whenever a {@link glow.ui.AutoSuggest#find find} is performed, and will be passed 2 arguments: the search string and a callback. You can fetch the data however you wish. Once you have the data, pass it to the callback to complete the {@link glow.ui.AutoSuggest#find find}. Until the callback is called, the AutoSuggest remains in a 'loading' state. `this` inside the function refers to the AutoSuggest instance. Your function will be called multiple times, ensure you cancel any existing requests before starting a new one. @example // providing a URL myAutoSuggest.data('/search?text={val}'); @example // providing an array of program names myAutoSuggest.data( ['Doctor Who', 'Eastenders', 'The Thick of it', 'etc etc'] ); @example // providing an object of user data myAutoSuggest.data([ {name='JaffaTheCake', fullName:'Jake Archibald', photo:'JaffaTheCake.jpg'}, {name='Bobby', fullName:'Robert Cackpeas', photo:'Bobby.jpg'} ... ]); @example // Getting the data via jsonp var request; myAutoSuggest.data(function(val, callback) { // abort previous request request && request.abort(); request = glow.net.getJsonp('http://blah.com/data?callback={callback}&val=' + val) .on('load', function(data) { callback(data); }) }); @returns this */ AutoSuggestProto.data = function(data) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.ui.Autosuggest#data expects 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ if (typeof data === 'string') { // look for urls without {val}, they get their data once & once only if (data.indexOf('{val}') === -1) { // replace data with function data = singleLoadUrl(data); } // look for urls with {val}, they get their data on each search else { // replace data with function data = multiLoadUrl(data); } } if (typeof data === 'function') { setDataFunction(this, data); } else if (data.push) { // clear any data functions set this._dataFunc = undefined; populateData(this, data); } return this; }; /** @private @function @description Generate the output of a find @param {glow.ui.AutoSuggest} autoSuggest @param {Object[]} results Array of filtered results @param {string} val The search string */ function generateOutput(autoSuggest, results, val) { var content = autoSuggest.content, resultsLen = results.length, i = resultsLen, listItem, itemContent, opts = autoSuggest._opts, focusable = autoSuggest.focusable; focusable.active(false); // if we've got an overlay, we don't bother clearing the list, // just hide the overlay to let it animate away nicely if ( !resultsLen && autoSuggest.overlay ) { autoSuggest._hideOverlay(); return; } // remove any current results content.children().destroy(); while (i--) { itemContent = autoSuggest._format( results[i], val ); listItem = glow('<li class="AutoSuggest-item"></li>') .data( 'as_data', results[i] ) .prependTo(content); // append HTML or nodes (typeof itemContent === 'string') ? listItem.html(itemContent) : listItem.append(itemContent); } // Activate the focusable if we have results if (resultsLen) { opts.activateFirst && focusable.active(true); // show & position our overlay autoSuggest._showOverlay(); } else { autoSuggest._hideOverlay(); } } /** @private @function @description Performs the find operation without calling _dataFunc. Or checking _loading or string length. These are done in #find. @param {glow.ui.AutoSuggest} autoSuggest @param {string} str The search string */ function performFind(autoSuggest, str) { var filteredResults = [], filteredResultsLen = 0, data = autoSuggest._data, findEvent = autoSuggest.fire('find', {val: str}), resultsEvent, caseSensitive = autoSuggest._opts.caseSensitive; if ( !findEvent.defaultPrevented() ) { // pick up any changes a listener has made to the find string str = findEvent.val; str = caseSensitive ? str : str.toLowerCase(); // start filtering the data for (var i = 0, len = data.length; i < len; i++) { if ( autoSuggest._filter.call(data[i], str, caseSensitive) ) { filteredResults[ filteredResultsLen++ ] = data[i]; // break if we have enough results now if (filteredResultsLen === autoSuggest._opts.maxResults) { break; } } } // fire result event resultsEvent = autoSuggest.fire('results', {results: filteredResults}); if ( resultsEvent.defaultPrevented() ) { filteredResults = []; } else { // pick up any changes a listener has made to the results filteredResults = resultsEvent.results } // output results generateOutput(autoSuggest, filteredResults, findEvent.val); } } /** @name glow.ui.AutoSuggest#find @function @description Search the datasource for a given string This fetches results from the datasource and displays them. This may be an asyncrounous action if data needs to be fetched from the server. @param {string} str String to search for {@link glow.ui.AutoSuggest#filter AutoSuggest#filter} is used to determine whether results match or not. @returns this */ AutoSuggestProto.find = function(str) { /*!debug*/ if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.ui.Autosuggest#find expects 1 argument, not ' + arguments.length + '.'); } /*gubed!*/ if (str.length >= this._opts.minLength) { // refresh/load data if there's a function this._dataFunc && this._dataFunc(str); // can't find if we're loading... if (this._loading) { // leave it here, _dataFunc will pick it up and call performFind later this._pendingFind = str; } else { performFind(this, str); } } else { this.hide(); } return this; }; /** @name glow.ui.AutoSuggest#hide @function @description Clear the results so the AutoSuggest is no longer visible @returns this */ AutoSuggestProto.hide = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.ui.Autosuggest#hide expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ clearTimeout(this._inputTimeout); // generating empty output does the trick generateOutput(this, [], ''); return this; }; /** @name glow.ui.AutoSuggest#destroy @function @description Destroy the AutoSuggest. Removes all events that cause the AutoSuggest to run. The input element will remain on the page. */ AutoSuggestProto.destroy = function() { /*!debug*/ if (arguments.length !== 0) { glow.debug.warn('[wrong count] glow.ui.Autosuggest#destroy expects 0 arguments, not ' + arguments.length + '.'); } /*gubed!*/ this._data = undefined; // remove events from the input this.input.detach('keypress', this._inputPress) .detach('blur', this._inputBlur) .detach('onbeforedeactivate', this._inputDeact); WidgetProto.destroy.call(this); }; /** @name glow.ui.AutoSuggest#disabled @function @description Enable/disable the AutoSuggest, or get the disabled state When the AutoSuggest is disabled it is not shown. @param {boolean} [newState] Disable the AutoSuggest? 'false' will enable a disabled AutoSuggest. @returns {glow.ui.AutoSuggest|boolean} Returns boolean when getting, AutoSuggest when setting */ /** @name glow.ui.AutoSuggest#event:data @event @description Fired when the dataset changes This can be the result of calling {@link glow.ui.AutoSuggest#data data} or new data has been fetched from the server. You can use this event to intercept and transform data into the correct JSON format. Cancel this event to ignore the new dataset, and continue with the current one. @param {glow.events.Event} event Event Object @param {*} event.data The new dataset You can modify / overwrite this property to alter the dataset. The type of this object depends on the data source and other listeners which may have overwritten / changed the original data. @example myAutoSuggest.data('data.xml?search={val}').on('data', function(event) { // When providing a url to .data(), event.data is a glow.net.XhrResponse object // Note: xmlToJson is not a function defined by Glow event.data = xmlToJson( event.data.xml() ); }); */ /** @name glow.ui.AutoSuggest#event:results @event @description Fired when the dataset has been filtered but before HTML is output You can use this event to sort the dataset and/or add additional items Cancelling this event is equivalent to setting event.results to an empty array. @param {glow.events.Event} event Event Object @param {string[]|Object[]} event.results The filtered dataset You can modify / overwrite this property to alter the results @example myAutoSuggest.on('results', function(event) { // sort results by an 'author' property event.results = event.results.sort(function(a, b) { return a.author > b.author ? 1 : -1; }); // Add a 'More...' item to the data set event.results.push( {name:'More...'} ); // Behaviour will be added into the 'select' listener to handle what // happens when 'More...' is selected }); */ /** @name glow.ui.AutoSuggest#event:select @event @description Fired when an item in the AutoSuggest is selected. You can use this event to react to the user interacting with the AutoSuggest Cancel this event to prevent the default click action. @param {glow.events.Event} event Event Object @param {string|Object} event.item The item in the dataset that was selected @param {glow.NodeList} event.li The list item in the AutoSuggest that was selected @example myAutoSuggest.on('select', function(event) { // this assumes our data objects have a 'url' property loaction.href = event.item.url; }); */ /** @name glow.ui.AutoSuggest#event:find @event @description Fired when a search starts. Cancel this event to prevent the search. @param {glow.events.Event} event Event Object. @param {string} event.val The search string. You can set this to another value if you wish. */ // EXPORT glow.ui.AutoSuggest = AutoSuggest; }); Glow.provide(function(glow) { var undefined, AutoSuggestProto = glow.ui.AutoSuggest.prototype; /** @name glow.ui.AutoSuggest#bindOpts @type Object @description The options object passed into #bindInput, with defaults added. */ /** @name glow.ui.AutoSuggest#input @type glow.NodeList @description Refers to the input element to which this is linked to, or an empty NodeList. Link an input to an AutoSuggest using {@link glow.ui.AutoSuggest#bindInput bindInput}. */ AutoSuggestProto.input = glow(); /** @name glow.ui.AutoSuggest#overlay @type glow.ui.Overlay @description The overlay linked to this autosuggest. The Overlay is created when {@link glow.ui.AutoSuggest#bindInput bindInput} is called. */ /** @name glow.ui.AutoSuggest#_inputPress @private @function @description Listener for input's keypress event. 'this' is the AutoSuggest. Needed to make this pseudo-private so we could remove the listener later */ function inputPress(e) { var autoSuggest = this, focusable = autoSuggest.focusable, focusableActive, focusableIndex = focusable.activeIndex, childrenLength; // we only care about printable chars and keys that modify input if ( e.keyChar || e.key === 'delete' || e.key === 'backspace' ) { // look out for printable chars going into the input clearTimeout(autoSuggest._inputTimeout); autoSuggest._inputTimeout = setTimeout(function() { autoSuggest.find( getFindValue(autoSuggest) ); }, autoSuggest._bindOpts.delay * 1000); } else { focusableActive = focusable.active(); switch (e.key) { case 'escape': autoSuggest.hide(); deleteSelectedText(autoSuggest); return false; case 'up': // Is up being pressed on the first item? if (focusableActive && !focusableIndex) { // deactivate the focusable focusable.active(false); deleteSelectedText(autoSuggest); return false; } // I'm deliberately not breaking here, want to capture both up & down keys in the next case case 'down': if ( !focusableActive && (childrenLength = autoSuggest.content.children().length) ) { // if the focusable isn't active, activate the first/last item focusable.active(e.key == 'up' ? childrenLength - 1 : 0); e.stopPropagation(); return false; } } } } AutoSuggestProto._inputPress = inputPress; /** @private @function @description Gets the value to find from the input. @returns The value to find. This is the same as the input value unless delimiters are used */ function getFindValue(autoSuggest) { var input = autoSuggest.input, delim = autoSuggest._bindOpts.delim, val = input.val(), lastDelimPos, caretPos; // deal with delims if (delim) { caretPos = getCaretPosition(autoSuggest); // get the text before the caret val = val.slice(0, caretPos); // is there a delimiter before the caret? lastDelimPos = val.lastIndexOf(delim); // if so, ignore the bits before the caret if (lastDelimPos !== -1) { val = val.slice( val.lastIndexOf(delim) + delim.length ); } } return glow.util.trim(val); } /** @name glow.ui.AutoSuggest#_inputBlur @private @function @description Listener for input's blur event. 'this' is the AutoSuggest. Needed to make this pseudo-private so we could remove the listener later */ function inputBlur() { this.hide(); } AutoSuggestProto._inputBlur = inputBlur; /** @name glow.ui.AutoSuggest#_inputDeact @private @function @description Listener for input's beforedeactivate event. 'this' is the AutoSuggest. Prevents IE from bluring the input element when the autosuggest is clicked. Needed to make this pseudo-private so we could remove the listener later */ function inputDeact(e) { if ( this.container.contains( e.related ) ) { return false; } } AutoSuggestProto._inputDeact = inputDeact; /** @private @function @description Listener for AutoSuggest's select event if opts.autoComplete is true This creates the autoComplete behaviour. 'this' is the AutoSuggest. */ function completeSelectListener(event) { completeInput(this.hide(), event.item.name); makeSelection(this, this.input.val().length); } /** @private @function @description Listener for focusable's childActivate event if opts.autoComplete is true. This updates the text as the user cycles through items. 'this' is the AutoSuggest */ function focusablechildActivate(event) { if (event.method !== 'hover') { completeInput(this, event.item.data('as_data').name, true); } } /** @private @function @description Autocomplete value in the input. @param {glow.ui.AutoSuggest} autoSuggest @param {string} newVal Value to complete to @param {boolean} [select=false] Highlight the completed portion? This is used while cycling through values */ function completeInput(autoSuggest, newVal, select) { deleteSelectedText(autoSuggest); var input = autoSuggest.input, oldVal = input.val(), caretPos = getCaretPosition(autoSuggest), rangeStart = caretPos, rangeEnd = newVal.length, delim = autoSuggest._bindOpts.delim, lastDelimPos, firstValPart = ''; // we don't want to overwrite the whole thing if we're using delimiters if (delim) { lastDelimPos = oldVal.slice(0, caretPos).lastIndexOf(delim); if (lastDelimPos !== -1) { firstValPart = oldVal.slice(0, lastDelimPos) + delim + ' '; } newVal = firstValPart + newVal + delim + ' '; rangeEnd = newVal.length; newVal += oldVal.slice(caretPos); } input.val(newVal); select && makeSelection(autoSuggest, rangeStart, rangeEnd); } /** @private @function @description Make a selection in the bound input @param {glow.ui.AutoSuggest} autoSuggest @param {number} start Start point of the selection @param {number} [end=start] End point of the selection */ function makeSelection(autoSuggest, start, end) { end = (end === undefined) ? start : end; var inputElm = autoSuggest.input[0], character = 'character', range; if (!window.opera && inputElm.createTextRange) { // IE range = inputElm.createTextRange(); range.moveStart(character, start); range.moveEnd(character, end - inputElm.value.length); range.select(); } else { // moz, saf, opera inputElm.select(); inputElm.selectionStart = start; inputElm.selectionEnd = end; } } /** @private @function @description Get the caret position within the input */ function getCaretPosition(autoSuggest) { var inputElm = autoSuggest.input[0], r; if (glow.env.ie) { // IE range = document.selection.createRange(); range.collapse(); range.setEndPoint( 'StartToStart', inputElm.createTextRange() ); r = range.text.length; } else { // moz, saf, opera r = inputElm.selectionStart; } return r; } /** @private @function @description Delete the currently selected text in the input. This is used when esc is pressed and in focusablechildActivate */ function deleteSelectedText(autoSuggest) { var inputElm = autoSuggest.input[0], val = inputElm.value, selectionStart; if (glow.env.ie) { // IE document.selection.createRange().text = ''; } else { // others selectionStart = inputElm.selectionStart; inputElm.value = val.slice(0, selectionStart) + val.slice(inputElm.selectionEnd); inputElm.selectionStart = selectionStart; } } /** @name glow.ui.AutoSuggest#_showOverlay @private @function @description Shows the overlay, if one is attached. Also positions the overlay according to options set. */ AutoSuggestProto._showOverlay = function() { var overlay = this.overlay, autoSuggestOpts = this._opts, bindOpts = this._bindOpts, input = this.input, inputOffset; if (!overlay) { return; } if (!autoSuggestOpts.width) { this.container.width( input[0].offsetWidth ); } if (bindOpts.autoPosition) { inputOffset = input.offset(); overlay.container.css({ top: inputOffset.top + input[0].offsetHeight, left: inputOffset.left }) } overlay.show(); } /** @name glow.ui.AutoSuggest#_hideOverlay @private @function @description Hide the overlay, if one is attached. */ AutoSuggestProto._hideOverlay = function() { var overlay = this.overlay; overlay && overlay.hide(); } /** @name glow.ui.AutoSuggest#bindInput @function @description Link this autosuggest to a text input. This triggers {@link glow.ui.AutoSuggest#find} when the value in the input changes. The AutoSuggest is placed in an Overlay beneath the input and displayed when results are found. If the input loses focus, or esc is pressed, the Overlay will be hidden and results cleared. @param {selector|glow.NodeList|HTMLElement} input Test input element @param {Object} [opts] Options @param {selector|glow.NodeList} [opts.appendTo] Add the AutoSuggest somewhere in the document rather than an {@link glow.ui.Overlay Overlay} By default, the AutoSuggest will be wrapped in an {@link glow.ui.Overlay Overlay} and appended to the document's body. @param {boolean} [opts.autoPosition=true] Place the overlay beneath the input If false, you need to position the overlay's container manually. It's recommended to do this as part of the Overlay's show event, so the position is updated each time it appears. @param {boolean} [opts.autoComplete=true] Update the input when an item is highlighted & selected. This will complete the typed text with the result matched. You can create custom actions by listening for the {@link glow.ui.AutoSuggest#event:select 'select' event} @param {string} [opts.delim] Delimiting char(s) for selections. When defined, the input text will be treated as multiple values, separated by this string (with surrounding spaces ignored). @param {number} [opts.delay=0.5] How many seconds to delay before searching. This prevents searches being made on each key press, instead it waits for the input to be idle for a given number of seconds. @param {string} [opts.anim] Animate the Overlay when it shows/hides. This can be any parameter accepted by {@link glow.ui.Overlay#setAnim Overlay#setAnim}. @param {string} [opts.loadingClass] Class name added to the input while data is being loaded. This can be used to change the display of the input element while data is being fetched from the server. By default, a spinner is displayed in the input. @returns this */ AutoSuggestProto.bindInput = function(input, opts) { /*!debug*/ if (arguments.length < 1 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.ui.AutoSuggest#bindInput expects 1 or 2 arguments, not ' + arguments.length + '.'); } if (opts !== undefined && typeof opts !== 'object') { glow.debug.warn('[wrong type] glow.ui.AutoSuggest#bindInput expects object as "opts" argument, not ' + typeof opts + '.'); } /*gubed!*/ var bindOpts = this._bindOpts = glow.util.apply({ autoPosition: true, autoComplete: true, delay: 0.5, loadingClass: 'glow200b1-AutoSuggest-loading' }, opts), appendTo = bindOpts.appendTo, container = this.container, overlay, autoSuggestOpts = this._opts; // if autocomplete isn't turned off, the browser doesn't let // us hear about up & down arrow presses this.input = glow(input).attr('autocomplete', 'off') .on('keypress', inputPress, this) .on('blur', inputBlur, this) .on('beforedeactivate', inputDeact, this); if (bindOpts.autoComplete) { this.on('select', completeSelectListener, this) .focusable.on('childActivate', focusablechildActivate, this); } // add to document, or... if (appendTo) { glow(appendTo).append(container); } // ...make overlay else { this.overlay = overlay = new glow.ui.Overlay(container) .on('hide', overlayHide, this) .on('afterShow', overlayAfterShow, this) .hide(); // the overlay will reactivate the focusable when needed this.focusable.disabled(true); overlay.container.appendTo(document.body); // use alternate slide anim if (bindOpts.anim === 'slide') { bindOpts.anim = altSlideAnim; } bindOpts.anim && overlay.setAnim(bindOpts.anim); this._tie(overlay); } return this; }; /** @private @function @description Alternative slide animation. The AutoSuggest uses a different style of slide animation to the usual Overlay, this creates it. */ function altSlideAnim(isShow, callback) { var anim, container = this.container, animOpts = { lockToBottom: true }; if (isShow) { container.height(0); anim = container.slideOpen(0.5, animOpts).data('glow_slideOpen') } else { anim = container.slideShut(0.5, animOpts).data('glow_slideShut'); } anim.on('complete', callback); } /** @private @function @description Listener for overlay hide. 'this' is the autoSuggest. This stops the focusable being interactive during its hide & show animation. */ function overlayHide() { this.focusable.disabled(true); } /** @private @function @description Listener for overlay show. 'this' is the autoSuggest */ function overlayAfterShow() { var focusable = this.focusable; focusable.disabled(false); if (this._opts.activateFirst) { focusable.active(true); } } }); Glow.provide(function(glow) { var undefined, CarouselPaneProto, WidgetProto = glow.ui.Widget.prototype; /** @name glow.ui.CarouselPane @class @extends glow.ui.Widget @description Create a pane of elements that scroll from one to another. This is a component of Carousel. @param {glow.NodeList|selector|HTMLElement} container Container of the carousel items. The direct children of this item will be treated as carousel items. They will be positioned next to each other horizontally. Each item takes up the same horizontal space, equal to the width of the largest item (including padding and border) + the largest of its horizontal margins (as set in CSS). The height of the container will be equal to the height of the largest item (including padding and border) + the total of its vertical margins. @param {object} [opts] Options @param {number} [opts.duration=0.2] Duration of scrolling animations in seconds. @param {string|function} [opts.tween='easeBoth'] Tween to use for animations. This can be a property name of {@link glow.tweens} or a tweening function. @param {boolean | number} [opts.step=1] Number of items to move at a time. If true, the step will be the same size as the spotlight. @param {boolean} [opts.loop=false] Loop the carousel from the last item to the first. @param {boolean} [opts.page=false] Keep pages in sync by adding space to the end of the carousel. Spaces don't exist as physical HTML elements, but simply a gap from the last item to the end. @param {number} [opts.spotlight] The number of items to treat as main spotlighted items. A carousel may be wide enough to display 2 whole items, but setting this to 1 will result in the spotlight item sitting in the middle, with half of the previous item appearing before, and half the next item appearing after. By default, this is the largest number of whole items that can exist in the width of the container. Any remaining width will be used to partially show the previous/next item. @example new glow.ui.CarouselPane('#carouselItems', { duration: 0.4, step: 2, loop: true }); */ function CarouselPane(container, opts) { /*!debug*/ if (!container) { glow.debug.warn('[wrong count] glow.ui.CarouselPane - argument "container" is required.'); return; } if (!container || glow(container).length === 0) { glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - "'+container+'" is not a valid element specifier for the container.'); } if (opts && opts.spotlight && opts.step && opts.spotlight < opts.step && opts.step !== true) { glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + opts.step +') cannot be greater than opts.spotlight ('+ opts.spotlight + ').'); } if (opts && opts.spotlight && opts.step && opts.page && opts.spotlight !== opts.step && opts.step !== true) { glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + opts.step +') cannot be different than opts.spotlight ('+ opts.spotlight + ') if opts.page is true.'); } /*gubed!*/ var that = this; opts = glow.util.apply({ duration: 0.2, tween: 'easeBoth', step: 1, loop: false, page: false, // add a gap? axis: 'x' // either 'x' or 'y' }, opts || {}); glow.ui.Widget.call(this, 'CarouselPane', opts); if (glow(container).length > 0) { this._init(container, opts); } }; glow.util.extend(CarouselPane, glow.ui.Widget); // CarouselPane is a Widget CarouselPaneProto = CarouselPane.prototype; // shortcut /** Tracks the order and location of all items, including cloned items. @private @constructor @param {glow.NodeList} nodeList The real items to track. */ function ItemList(nodeList) { var thisMeta; this.range = {min: 0, max: 0}; this.items = {}; this.meta = {}; for (var i = 0, leni = nodeList.length; i < leni; i++) { this.addItem(i, nodeList.item(i)); } } ItemList.prototype.addItem = function(index, item, meta) {/*debug*///console.log('ItemList.prototype.addItem('+index+')'); this.range.min = Math.min(this.range.min, index); this.range.max = Math.max(this.range.max, index); this.items[index] = item; this.meta[index] = meta || {}; } ItemList.prototype.addMeta = function(index, meta) {/*debug*///console.log('ItemList.prototype.addMeta('+index+', '+meta.offset+')'); if (this.meta[index]) { this.meta[index] = glow.util.apply(this.meta[index], meta); } } ItemList.prototype.place = function(top, left) { // TODO styleName = this._geom[1] for (var p in this.items) { if (top !== undefined ) this.items[p].css('top', top); this.items[p].css('left', (left === undefined)? this.meta[p].offset : left); } } ItemList.prototype.dump = function(c) { if (typeof console !== 'undefined') { for (var i = c._itemList.range.min, maxi = c._itemList.range.max; i <= maxi; i++) { if (c._itemList.meta[i]) { console.log('>> '+ i + ': ' + (c._itemList.meta[i].isClone? 'clone':'real') + ' at ' + c._itemList.meta[i].offset + ' ' + c._itemList.items[i][0].children[0].alt); } else { console.log('>> '+ i + ': ' + c._itemList.meta[i]); } } } } ItemList.prototype.swap = function(index1, index2) { /*debug*///console.log('ItemList.prototype.swap('+index1+', '+index2+')'); this.items[index1].css('left', this.meta[index2].offset); this.items[index2].css('left', this.meta[index1].offset); } CarouselPaneProto._init = function(container) { /*debug*///console.log('CarouselPaneProto._init'); WidgetProto._init.call(this); // used value vs configured value (they may not be the same). Might be set to spotlight capacity, in _build. this._step = this._opts.step; this._geom = (this._opts.axis === 'y')? ['height', 'top'] : ['width', 'left']; /** @name glow.ui.CarouselPane#stage @type glow.NodeList @description The container passed in to the constructor for glow.ui.CarouselPane. */ this.stage = glow(container).item(0); this._focusable = this.stage.focusable( {children: '> *', loop: true, setFocus: true} ); // what would have been the "content" of this widget, is named "viewport" this._viewport = glow('<div class="CarouselPane-viewport"></div>'); glow(this.stage).wrap(this._viewport); /** @name glow.ui.CarouselPane#items @type glow.NodeList @description Carousel items. This is the same as `myCarouselPane.stage.children()` */ this.items = this.stage.children(); this._itemList = new ItemList(this.items); if (this._opts.spotlight > this.items.length) { /*!debug*/ glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.spotlight (' + this._opts.spotlight +') cannot be greater than the number of items ('+ this.items.length + ').'); /*gubed!*/ this._opts.spotlight = this.items.length; } // track what the offset of the current leftmost spotlighted item is this._index = 0; this._build(); } CarouselPaneProto._build = function() { /*debug*///console.log('CarouselPaneProto._build'); WidgetProto._build.call(this, this._viewport); this.stage.css({ margin: 0, listStyleType: 'none' // useful when content is a list }); this.items.css( {position:'absolute', 'z-index':2} ); this._itemDimensions = getDimensions(this.items); // get this *after* setting position to absolute this.items.css({ margin: 0, width: this._itemDimensions.innerWidth, height: this._itemDimensions.innerHeight }); this._wingSize = Math.ceil(this.items.length * this._itemDimensions[this._geom[0]] * 1.5); this._viewport.css({ overflow: 'scroll', overflowX: 'hidden', // hide scroll bars overflowY: 'hidden', position: 'relative', padding: 0, margin: 0, width: this._opts.axis === 'x'? '100%' : this._itemDimensions.width, height: this._opts.axis === 'y'? '100%' : this._itemDimensions.height }); /** @private @name glow.ui.CarouselPane#_spot @type Object @description Information about the spotlight area. */ this._spot = CarouselPane._getSpot(this._viewport.width(), this.items, this._itemDimensions, this._opts); /** @private @name glow.ui.CarouselPane#_step @type number @description How far to move when going next or prev. */ if (this._opts.step === true) { this._step = this._spot.capacity; } else if (this._opts.step > this._spot.capacity) { /*!debug*/ glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + this._opts.step +') cannot be greater than the calculated spotlight ('+ this._spot.capacity + ').'); /*gubed!*/ this._step = this._spot.capacity; } if (this._opts.page && this._step !== this._spot.capacity) { /*!debug*/ glow.debug.warn('[invalid configuration] glow.ui.CarouselPane - opts.step (' + this._opts.step +') cannot be different than the spotlight ('+ this._spot.capacity + ') when opts.page is true.'); /*gubed!*/ this._step = this._spot.capacity; } /** @private @name glow.ui.CarouselPane#_gap @type Object @description Information about the gap at the end of the items. @property size @property count */ this._gap = getGap(this); // must set height to anything other than 0, else FF won't *ever* render the stage this.stage.css({width: this.stage.width() + this._wingSize * 2, height: '100%'}); // [wing][stage[spot]stage][wing] layout.call(this); this._bind(); calculateIndex.call(this); } /** @private @name getGap @description Calculate the size of the empty space at the end of the items which may be required to enforce paging. @param {glow.ui.CarouselPane} carouselPane */ function getGap(carouselPane) { /*debug*///console.log('getGap()'); var gap = { size: 0, count: 0 }, danglingItemCount = carouselPane.items.length % carouselPane._step; if (carouselPane._opts.page && carouselPane._step > 1) { gap.count = danglingItemCount? carouselPane._spot.capacity - danglingItemCount : 0; gap.size = gap.count * carouselPane._itemDimensions[carouselPane._geom[0]]; } return gap; } CarouselPaneProto._bind = function() { /*debug*///console.log('CarouselPaneProto._bind'); var that = this; WidgetProto._bind.call(that); attachEvent(that, that._focusable, 'childActivate', function(e) { var itemNumber = e.itemIndex, indexes = that.spotlightIndexes(true), isVisible = (' '+indexes.join(' ')+' ').indexOf(' '+itemNumber+' ') > -1; if (itemNumber !== undefined && !isVisible) { that.moveTo(itemNumber, {tween: ''}); that._index = itemNumber; } }); this._focusable.on('select', function(e) { e.itemIndex = e.item.data('itemIndex'); that.fire('select', e); }); } /** @private @name attachEvent @function @decription Add an event listener and handler to a node related to this carouselpane. Stores a reference to that transaction so each handler can easily be detached later. @see glow.ui.CarouselPane-detachEvents @param {glow.ui.CarouselPane} carouselPane @param {glow.EventTarget} target @param {string} name The name of the event to listen for. @param {Function} handler */ function attachEvent(carouselPane, target, name, handler) { target.on(name, handler); carouselPane._addedEvents = carouselPane._addedEvents || []; carouselPane._addedEvents.push( {target:target, name:name, handler:handler} ); } /** @private @name detachEvents @function @decription Remove all events add via the {@link glow.ui.CarouselPane-attachEvent}. @see glow.ui.CarouselPane-removeEvents @param {glow.ui.CarouselPane} carouselPane */ function detachEvents(carouselPane) { var i = carouselPane._addedEvents? carouselPane._addedEvents.length : 0, e; while (i--) { e = carouselPane._addedEvents[i]; e.target.detach(e.name, e.handler); } } CarouselPaneProto.updateUi = function() { /*debug*///console.log('updateUi'); WidgetProto._updateUi.call(this); // must set height to anything other than 0, else FF won't *ever* render the stage this.stage.css({width: this.stage.width() + this._wingSize * 2, height: '100%'}); // [wing][stage[spot]stage][wing] this._spot = CarouselPane._getSpot(this._viewport.width(), this.items, this._itemDimensions, this._opts); if (this._opts.step === true) { this._step = this._spot.capacity; } layout.call(this); this._index = 0; this.fire('updateUi', {}); } /** @name glow.ui.CarouselPane#moveStop @function @description Stop moving the carousel. The current animation will end, leaving the carousel in step. Note that this is asynchronous: expect this method to return before the carousel actually stops. @returns this */ CarouselPaneProto.moveStop = function() { /*debug*///console.log('moveStop()'); // set temporary flag to signal the next animation in the timeline to stop this._gliderBrake = true; } /** @name glow.ui.CarouselPane#moveStart @function @description Start moving the carousel in a particular direction. @param {boolean} [backwards=false] True to move backwards, otherwise move forwards. @returns this @see glow.ui.CarouselPane#moveStop @example nextBtn.on('mousedown', function() { myCarouselPane.moveStart(); }).on('mouseup', function() { myCarouselPane.moveStop(); }); */ CarouselPaneProto.moveStart = function(backwards) { /*debug*///console.log('moveStart('+backwards+')'); /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.moveStart - too many arguments, must be 1 or 0, not '+arguments.length+'.'); } /*gubed!*/ var step = (backwards? -1 : 1) * this._step, carouselPane = this; if (!carouselPane._inMotion) { carouselPane._gliderBrake = false; carouselPane.moveTo( carouselPane._index + step, { callback: function() { if (!carouselPane._gliderBrake) { if ( // if looping or if there's room to go in the given direction carouselPane._opts.loop || ( (backwards && carouselPane._index > 0) || (!backwards && carouselPane._index + carouselPane._spot.capacity < carouselPane.items.length) ) ) { if (carouselPane._step === 1) { glide.call(carouselPane, backwards); } else { carouselPane.moveStart(backwards); // recursive } } } } } ); } return carouselPane; } /** @name glow.ui.CarouselPane#moveToggle @function @description If this CarouselPane is currently moving via moveStart, will call moveStop, otherwise will call moveStart. @param {boolean} [backwards=false] When calling moveStart, move backwards? @returns this */ CarouselPaneProto.moveToggle = function(backwards) { /*debug*///console.log('moveToggle()'); /*!debug*/ if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.ui.moveToggle - too many arguments, must be 1 or 0, not '+arguments.length+'.'); } /*gubed!*/ if (this._inMotion && !this._gliderBrake) { this.moveStop(); } else { this.moveStart(backwards); } return this; } /** @private @name glide @function @description Move this using an animation that is continuous, with a linear tween. @param {boolean} backwards Glide in a previous-direction? */ var glide = function(backwards) { /*debug*///console.log('glide('+backwards+')'); var dir = (backwards? -1 : 1), moves = [], offset = this.content[0].scrollLeft, // from where is the move starting? amount = this._itemDimensions[this._geom[0]], // how many pixels are we moving by? from, to, that = this, moveAnim, // when to loop back to where we started? wrapAt = offset + (backwards? -this._index * amount : (this.items.length - this._index) * amount); swap.call(this, 'back'); for (var i = 0, leni = this.items.length; i < leni; i += this._step) { // calculate the start and end points of the next move from = offset + dir * i * amount; to = offset + dir * (i + this._step) * amount; if ( (backwards && from === wrapAt) || (!backwards && to === wrapAt) ) { offset -= dir * this.items.length * amount; // wrap } moveAnim = this.content.anim( this._opts.duration, {scrollLeft: [from, to]}, {tween: 'linear', startNow: false} ) .on('start', function() { indexMoveTo.call(that); if ( that.fire('move', { moveBy: dir, currentIndex: that._index }).defaultPrevented() ) { glideStop.call(that); } }) .on('complete', function() { that._index += dir; // assumes move amount will be +/- 1 if ( that._gliderBrake || ( !that._opts.loop && (that._index + that._spot.capacity === that.items.length || that._index === 0) ) ) { glideStop.call(that); that.fire( 'afterMove', {currentIndex: that._index} ); } }); moves.push(moveAnim); } this._glider = new glow.anim.Timeline({loop: true}); glow.anim.Timeline.prototype.track.apply(this._glider, moves); this._inMotion = true; this._gliderBrake = false; this._glider.start(); } /** @private @name indexMoveTo @function @description Calculate what the new index would be and set this._index to that. @param {number} index The destination index. @returns this._index @example // items.length is 3 var newIndex = indexMoveTo(10); // newIndex is 1 */ function indexMoveTo(index) { if (index !== undefined) { this._index = index; } // force index to be a number from 0 to items.length this._index = this._index % this.items.length; while (this._index < 0) { this._index += this.items.length; } return this._index; } /** @private @name indexMoveBy @function @description Calculate what the new index would be and set this._index to that. @param {number} delta The amount to change the index by, can be positive or negative. @returns this._index @example // items.length is 3 // currentIndex is 1 var newIndex = indexMoveBy(100); // newIndex is 2 */ function indexMoveBy(delta) { return indexMoveTo.call(this, this._index += delta); } /** @private @name glideStop @description Reset this CarouselPane after a glide is finished. */ function glideStop() { /*debug*///console.log('glideStop()'); this._glider.stop(); this._glider.destroy(); this._inMotion = false; this._index = calculateIndex.call(this); // where did we end up? // in case our clones are showing jump.call(this); swap.call(this); } /** @name glow.ui.CarouselPane#spotlightIndexes @function @description Gets an array of spotlighted indexes. These are the indexes of the nodes within {@link glow.ui.CarouselPane#items}. Only item indexes currently visible in the spotlight will be included. @private-param {boolean} _real Return only indexes of real items, regardless of what clones are visible. @returns {number[]} */ CarouselPaneProto.spotlightIndexes = function(_real) { /*debug*///console.log('CarouselPaneProto.spotlightIndexes()'); var indexes = [], findex = calculateIndex.call(this), index, maxi = (this._opts.loop)? this._spot.capacity : Math.min(this._spot.capacity, this.items.length); // takes into account gaps and wraps for (var i = 0; i < maxi; i++) { index = _real? (findex + i) : (findex + i)%(this.items.length + this._gap.count); // skip gaps if (index >= this.items.length || index < 0) { continue; // or maybe keep gaps? index = NaN; } indexes.push(index); } return indexes; } /** @name glow.ui.CarouselPane#spotlightItems @function @description Get the currently spotlighted items. Only items currently visible in the spotlight will be included. @returns {glow.NodeList} */ CarouselPaneProto.spotlightItems = function() { /*debug*///console.log('CarouselPaneProto.spotlightItems()'); var items = glow(), indexes = this.spotlightIndexes(); // takes into account gaps and wraps for (var i = 0, leni = indexes.length; i < leni; i++) { items.push( this.items[ indexes[i] ] ); } return items; } /** @private @name calculateIndex @function @description Calculate the index of the leftmost item in the spotlight. @returns {number} */ function calculateIndex() { var cindex = this.content[0].scrollLeft - (this._wingSize +this._spot.offset.left); cindex += this._spot.offset.left; cindex /= this._itemDimensions.width; return cindex; } /** @name glow.ui.CarouselPane#moveTo @function @description Move the items so a given index is the leftmost active item. This method respects the carousel's limits and its step. If it's not possible to move the item so it's the leftmost item of the spotlight, it will be placed as close to the left as possible. @param {number} itemIndex Item index to move to. @param opts @param {undefined|string} opts.tween If undefined, use the default animation, if empty string then no animation, if non-empty string then use the named tween. @privateParam {Function} opts.callback Called when move animation is complete. @privateParam {boolean} opts.jump Move without animation and without events. @returns this */ CarouselPaneProto.moveTo = function(itemIndex, opts) { /*debug*///glow.debug.log('moveTo('+itemIndex+')'); var willMove, // trying to move to the same place we already are? destination, // in pixels tween, anim; if (this._inMotion) { return false; } opts = opts || {}; // will the last item be in the spotlight? if (!this._opts.loop && itemIndex > this.items.length - this._spot.capacity) { // if opts.page is on then allow a gap at the end, otherwise don't include gap itemIndex = this.items.length - this._spot.capacity + (this._opts.page? this._gap.count : 0); } else if (!this._opts.loop && itemIndex < 0) { itemIndex = 0; } willMove = ( itemIndex !== this._index && canGo.call(this, itemIndex) ); // move event if (!opts.jump) { // don't fire move event for jumps var e = new glow.events.Event({ currentIndex: this._index, moveBy: (this._index < itemIndex)? (itemIndex - this._index) : (-Math.abs(this._index - itemIndex)) }); if (!opts.jump && willMove && this.fire('move', e).defaultPrevented() ) { return this; } else { itemIndex = this._index + e.moveBy; } } // force items to stay in step when opts.page is on if (this._opts.page) { itemIndex = Math.floor(itemIndex / this._step) * this._step; } // invalid itemIndex value? if (itemIndex > this.items.length + this._step || itemIndex < 0 - this._step) { // moving more than 1 step /*!debug*/ glow.debug.warn('[wrong value] glow.ui.CarouselPane#moveTo - Trying to moveTo an item ('+itemIndex+') that is more than 1 step (' + this._step +' items) away is not possible.'); /*gubed!*/ itemIndex = this._index + (this._index < itemIndex)? -this._step : this._step; } destination = this._wingSize + itemIndex * this._itemDimensions.width; swap.call(this, 'back'); tween = opts.tween || this._opts.tween; var that = this; if (opts.jump === true || opts.tween === '') { // jump this.content[0].scrollLeft = destination; this._index = itemIndex; // in case our clones are showing jump.call(this); swap.call(this); // force index to be a number from 0 to items.length this._index = this._index % (this.items.length + this._gap.count); if (!opts.jump && willMove) { this.fire('afterMove', {currentIndex: this._index}); } this._inMotion = false; } else if (willMove) { this._inMotion = true; anim = this.content.anim( this._opts.duration, { scrollLeft: destination }, { tween: opts.tween || this._opts.tween } ); this._index = itemIndex; anim.on('complete', function() { that._inMotion = false; // in case our clones are showing jump.call(that); swap.call(that); // force index to be a number from 0 to items.length that._index = that._index % (that.items.length + that._gap.count); that.fire('afterMove', {currentIndex: that._index}); if (opts.callback) { opts.callback(); } }); } return this; } /** @private @function @name jump @description Quickly move forward or back to a new set of items that look the same as the current set of items. */ function jump() { /*debug*///console.log('jump()'); if (this._index < 0) { this.moveTo(this.items.length + this._gap.count + this._index, {jump: true}); } else if (this._index >= this.items.length) { this.moveTo(this._index - (this.items.length + this._gap.count), {jump: true}); } } /** Move real items to stand-in for any clones that are in the spotlight, or put the real items back again. @name swap @private @param {boolean} back If a truthy value, will move the real items back. */ function swap(back) { /*debug*///console.log('swap('+back+')'); var swapItemIndex; if (!this._opts.loop) { return; } // no clones, so no swap possible if (back) { this._itemList.place(); } else { for (var i = 0, leni = this._spot.capacity - this._gap.count; i < leni; i++) { swapItemIndex = (this._index + i); if (swapItemIndex >= this.items.length) { // a clone needs to have a real item swapped-in this._itemList.swap(swapItemIndex, swapItemIndex % this.items.length); } } } } /** @name glow.ui.CarouselPane#moveBy @function @description Move by a number of items. @param {number} amount Amount and direction to move. Negative numbers will move backwards, positive number will move forwards. This method respects the carousel's limits and its step. If it's not possible to move the item so it's the leftmost item of the spotlight, it will be placed as close to the left as possible. @returns this */ CarouselPaneProto.moveBy = function(amount) { /*debug*///console.log('moveBy('+amount+')'); this.moveTo(this._index + amount); return this; } /** @name glow.ui.CarouselPane#next @function @description Move forward by the step. @returns this */ CarouselPaneProto.next = function() { /*debug*///console.log('next()'); this.moveTo(this._index + this._step); return this; } /** @name glow.ui.CarouselPane#prev @function @description Move backward by the step. @returns this */ CarouselPaneProto.prev = function() { /*debug*///console.log('prev()'); this.moveTo(this._index - this._step); return this; } /** @private @name canGo @description Determine if the CarouselPane can go to the desired index. @param {number} itemIndex The desired index. @returns {boolean} */ function canGo(itemIndex) { /*debug*///console.log('canGo('+itemIndex+')'); if (this._opts.loop) { return true; } // too far prev if (itemIndex < 0) { return false; } // too far next if (itemIndex - this._step >= this.items.length - this._spot.capacity ) { return false; } return true; } /** @private @name getDimensions @description Calculate the max height and width of all the items. @param {glow.NodeList} items @returns {Object} With properties `width` and 'height`. */ function getDimensions(items) { var el, maxInnerWidth = 0, maxInnerHeight = 0, maxWidth = 0, maxHeight = 0, margin = 0, marginRight = 0, marginLeft = 0, marginTop = 0, marginBottom = 0; items.each(function() { el = glow(this); maxHeight = Math.max(this.offsetHeight, maxHeight); maxWidth = Math.max(this.offsetWidth, maxWidth); maxInnerWidth = Math.max(el.width(), maxInnerWidth); maxInnerHeight = Math.max(el.height(), maxInnerHeight); marginRight = Math.max(autoToValue(el.css('margin-right')), marginRight); marginLeft = Math.max(autoToValue(el.css('margin-left')), marginLeft); marginTop = Math.max(autoToValue(el.css('margin-top')), marginTop); marginBottom = Math.max(autoToValue(el.css('margin-bottom')), marginBottom); }); // simulate margin collapsing. see: http://www.howtocreate.co.uk/tutorials/css/margincollapsing margin = Math.max(marginLeft, marginRight); // the larger of: the largest left matgin and the largest right margin return { width: maxWidth+margin, height: maxHeight+marginTop+marginBottom, innerWidth: maxInnerWidth, innerHeight: maxInnerHeight, marginLeft: marginLeft, marginRight: marginRight, marginTop: marginTop, marginBottom: marginBottom }; } function autoToValue(v) { if (v === 'auto') return 0; else return parseInt(v); } /** @private @name _getSpot @description Calculate the bounds for the spotlighted area, within the viewport. @private */ CarouselPane._getSpot = function(viewportWidth, items, itemDimensions, opts) {/*debug*///console.log('CarouselPane._getSpot()'); var spot = { capacity: 0, top: 0, left: 0, width: 0, height: 0, offset: { top: 0, right: 0, bottom: 0, left: 0 } }, opts = opts || {} if (!itemDimensions) { itemDimensions = getDimensions(items); } if (opts.axis = 'x') { if (items.length === 0) { spot.capacity = 0; } else if (opts.spotlight) { if (opts.spotlight > items.length) { throw new Error('spotlight cannot be larger than item count.'); } spot.capacity = opts.spotlight; } else { spot.capacity = Math.floor( viewportWidth / itemDimensions.width ); } if (spot.capacity > items.length) { spot.capacity = items.length; } spot.width = spot.capacity * itemDimensions.width + Math.min(itemDimensions.marginLeft, itemDimensions.marginRight); spot.height = itemDimensions.height spot.offset.left = Math.floor( (viewportWidth - spot.width) / 2 ); spot.offset.right = viewportWidth - (spot.offset.left + spot.width); } else { throw Error('y axis (vertical) not yet implemented'); } return spot; } function getPosition(itemIndex) { /*debug*///console.log('getPosition('+itemIndex+')'); position = { top: 0, left: 0 }; // TODO: memoise? var size = this._itemDimensions.width, offset = this._spot.offset.left + this._wingSize + this._itemDimensions.marginLeft, gap = 0; if (this._opts.page && itemIndex < 0) { gap = -(1 + Math.floor( Math.abs(itemIndex+this._gap.count) / this.items.length)) * this._gap.count * size; } else if (this._opts.page && itemIndex >= this.items.length) { gap = Math.floor(itemIndex / this.items.length) * this._gap.count * size; } position.left = offset + (itemIndex * size) + gap; position.top = this._itemDimensions.marginTop; return position; } function layout() {/*debug*///console.log('layout()'); var clone, cloneOffset; this.content[0].scrollLeft = this._wingSize; for (var i = 0, leni = this.items.length; i < leni; i++) { // items were already added in ItemList constructor, just add meta now this._itemList.addMeta(i, {offset:getPosition.call(this, i).left, isClone:false}); this.items.item(i).data('itemIndex', +i); } if (this._opts.loop) { // send in the clones this.stage.get('.carousel-clone').remove(); // kill any old clones // how many sets of clones (on each side) are needed to fill the off-spotlight portions of the stage? var repsMax = 1 + Math.ceil(this._spot.offset.left / (this._itemDimensions.width*this.items.length + this._gap.size)); for (var reps = 1; reps <= repsMax; reps++) { i = this.items.length; while (i--) { // add clones to prev side clone = this.items.item(i).copy(); clone.removeClass('carousel-item').addClass('carousel-clone').css({ 'z-index': 1, margin: 0 }); cloneOffset = getPosition.call(this, 0 - (reps * this.items.length - i)).left; this._itemList.addItem(0 - (reps * this.items.length - i), clone, {isClone:true, offset:cloneOffset}); this.stage[0].appendChild(clone[0]); // add clones to next side clone = clone.copy(); cloneOffset = getPosition.call(this, reps*this.items.length + i).left; this._itemList.addItem(reps*this.items.length + i + this._gap.count, clone, {isClone:true, offset:cloneOffset}); this.stage[0].appendChild(clone[0]); } } } this.items.addClass('carousel-item'); // apply positioning to all items and clones this._itemList.place(this._itemDimensions.marginTop, undefined); } /** @name glow.ui.CarouselPane#destroy @function @description Remove listeners and added HTML Elements from this instance. CarouselPane items will not be destroyed. @returns undefined */ CarouselPaneProto.destroy = function() { this.stage.get('.carousel-clone').remove(); detachEvents(this); this.stage.insertBefore(this.container).children().css('position', ''); WidgetProto.destroy.call(this); }; /** @name glow.ui.CarouselPane#event:select @event @description Fires when a carousel item is selected. Items are selected by clicking, or pressing enter when a child is in the spotlight. Canceling this event prevents the default click/key action. @param {glow.events.Event} event Event Object @param {glow.NodeList} event.item Item selected @param {number} event.itemIndex The index of the selected item in {@link glow.ui.CarouselPane#items}. */ /** @name glow.ui.CarouselPane#event:move @event @description Fires when the carousel is about to move. Canceling this event prevents the carousel from moving. This will fire for repeated move actions. Ie, this will fire many times after #start is called. @param {glow.events.Event} e Event Object @param {number} e.currentIndex Index of the current leftmost item. @param {number} e.moveBy The number of items the Carousel will move by. This is undefined for 'sliding' moves where the destination isn't known. This value can be overwritten, resulting in the carousel moving a different amount. The carousel step will still be respected. @example // double the amount a carousel will move by myCarouselPane.on('move', function(e) { e.moveBy *= 2; }); */ /** @name glow.ui.CarouselPane#event:afterMove @event @description Fires when the carousel has finished moving. Canceling this event prevents the carousel from moving. This will not fire for repeated move actions. Ie, after #start is called this will not fire until the carousel reached an end point or when it comes to rest after #stop is called. @param {glow.events.Event} e Event Object @param {number} e.currentIndex Index of the current leftmost item. @example // double the amount a carousel will move by myCarouselPane.on('afterMove', function(e) { // show content related to this.spotlightItems()[0] }); */ // EXPORT glow.ui.CarouselPane = CarouselPane; }); Glow.provide(function(glow) { var undefined, CarouselProto, Widget = glow.ui.Widget, WidgetProto = Widget.prototype; /** @name glow.ui.Carousel @class @extends glow.ui.Widget @description Create a pane of elements that scroll from one to another. @param {glow.NodeList|selector|HTMLElement} itemContainer Container of the carousel items. The direct children of this item will be treated as carousel items. They will be positioned next to each other horizontally. Each item takes up the same horizontal space, equal to the width of the largest item (including padding and border) + the largest of its horizontal margins (as set in CSS). The height of the container will be equal to the height of the largest item (including padding and border) + the total of its vertical margins. @param {object} [opts] Options @param {number} [opts.duration=0.2] Duration of scrolling animations in seconds. @param {string|function} [opts.tween='easeBoth'] Tween to use for animations. This can be a property name of {@link glow.tweens} or a tweening function. @param {boolean|number} [opts.page=false] Move a whole page at a time. If 'true', the page size will be the spotlight size, but you can also set this to be an explicit number of items. Space will be added to the end of the carousel so pages stay in sync. If 'false' or 1, the carousel moves one item at a time. @param {boolean} [opts.loop=false] Loop the carousel from the last item to the first. @param {number} [opts.spotlight] The number of items to treat as main spotlighted items. A carousel may be wide enough to display 2 whole items, but setting this to 1 will result in the spotlight item sitting in the middle, with half of the previous item appearing before, and half the next item appearing after. By default, this is the largest number of whole items that can exist in the width of the container, allowing room for next & previous buttons. Any remaining width will be used to partially show the previous/next item beneath the next & previous buttons. @example // This creates a carousel out of HTML like... // <ol id="carouselItems"> // <li> // <a href="anotherpage.html"> // <img width="200" height="200" src="img.jpg" alt="" /> // </a> // </li> // ...more list items like above... var myCarousel = new glow.ui.Carousel('#carouselItems', { loop: true, page: true, }); @example // Make a carousel of thumbnails, which show the full image when clicked. // Carousel items look like this... // <li> // <a href="fullimage.jpg"> // <img src="thumbnail.jpg" width="100" height="100" alt="whatever" /> // </a> // </li> var fullImage = glow('#fullImage'), myCarousel = new glow.ui.Carousel('#carouselItems', { spotlight: 6 }).addPageNav('belowCenter').on('select', function(e) { fullImage.prop( 'src', e.item.get('a').prop('href') ); return false; }); */ function Carousel(itemContainer, opts) { var spot; Widget.call(this, 'Carousel', opts); opts = this._opts; // convert the options for CarouselPane if (opts.page) { opts.step = opts.page; opts.page = true; } this.itemContainer = itemContainer = glow(itemContainer).item(0); // see if we're going to get enough room for our prev/next buttons spot = glow.ui.CarouselPane._getSpot( itemContainer.parent().width(), itemContainer.children().css('position', 'absolute'), 0, opts ); // enfore our minimum back/fwd button size if (spot.offset.left < 50) { opts.spotlight = spot.capacity - 1; } this._init(); }; glow.util.extend(Carousel, glow.ui.Widget); CarouselProto = Carousel.prototype; /** @name glow.ui.Carousel#_pane @type glow.ui.CarouselPane @description The carousel pane used by this Carousel */ /** @name glow.ui.Carousel#_prevBtn @type glow.NodeList @description Element acting as back button */ /** @name glow.ui.Carousel#_nextBtn @type glow.NodeList @description Element acting as next button */ /** @name glow.ui.Carousel#items @type glow.NodeList @description Carousel items. */ /** @name glow.ui.Carousel#itemContainer @type glow.NodeList @description Parent element of the carousel items. */ // life cycle methods CarouselProto._init = function () { WidgetProto._init.call(this); this._build(); }; CarouselProto._build = function () { var content, itemContainer = this.itemContainer, pane, items, spot; WidgetProto._build.call( this, itemContainer.wrap('<div></div>').parent() ); content = this.content; pane = this._pane = new glow.ui.CarouselPane(itemContainer, this._opts); spot = pane._spot items = this.items = pane.items; this.itemContainer = pane.itemContainer; pane.moveTo(0, { tween: null }); // add next & prev buttons, autosizing them this._prevBtn = glow('<div class="Carousel-prev"><div class="Carousel-btnIcon"></div></div>').prependTo(content).css({ width: spot.offset.left, height: spot.height }); this._nextBtn = glow('<div class="Carousel-next"><div class="Carousel-btnIcon"></div></div>').prependTo(content).css({ width: spot.offset.right, height: spot.height }); updateButtons(this); this._bind(); }; /** @private @function @description Update the enabled / disabled state of the buttons. */ function updateButtons(carousel) { // buttons are always active for a looping carousel if (carousel._opts.loop) { return; } var indexes = carousel.spotlightIndexes(), lastIndex = indexes[indexes.length - 1], lastItemIndex = carousel.items.length - 1; // add or remove the disabled class from the buttons carousel._prevBtn[ (indexes[0] === 0) ? 'addClass' : 'removeClass' ]('Carousel-prev-disabled'); carousel._nextBtn[ (lastIndex === lastItemIndex) ? 'addClass' : 'removeClass' ]('Carousel-next-disabled'); } /** @private @function @description Listener for CarouselPane's 'select' event. 'this' is the Carousel */ function paneSelect(event) { this.fire('select', event); } /** @private @function @description Listener for CarouselPane's 'move' event. 'this' is the Carousel */ function paneMove(event) { var pane = this._pane; if ( !this.fire('move', event).defaultPrevented() ) { this._updateNav( (pane._index + event.moveBy) % this.items.length / pane._step ); } } /** @private @function @description Listener for CarouselPane's 'afterMove' event. 'this' is the Carousel */ function paneAfterMove(event) { if ( !this.fire('afterMove', event).defaultPrevented() ) { updateButtons(this); } } /** @private @function @description Listener for back button's 'mousedown' event. 'this' is the Carousel */ function prevMouseDown(event) { if (event.button === 0) { this._pane.moveStart(true); return false; } } /** @private @function @description Listener for fwd button's 'mousedown' event. 'this' is the Carousel */ function nextMouseDown(event) { if (event.button === 0) { this._pane.moveStart(); return false; } } /** @private @function @description Stop the pane moving. This is used as a listener for various mouse events on the back & forward buttons. `this` is the Carousel. */ function paneMoveStop() { this._pane.moveStop(); } CarouselProto._bind = function () { var pane = this._pane, carousel = this; this._tie(pane); pane.on('select', paneSelect, this) .on('afterMove', paneAfterMove, this) .on('move', paneMove, this); this._prevBtn.on('mousedown', prevMouseDown, this) .on('mouseup', paneMoveStop, this) .on('mouseleave', paneMoveStop, this); this._nextBtn.on('mousedown', nextMouseDown, this) .on('mouseup', paneMoveStop, this) .on('mouseleave', paneMoveStop, this); WidgetProto._bind.call(this); }; /** @name glow.ui.Carousel#spotlightItems @function @description Get the currently spotlighted items. @returns {glow.NodeList} */ CarouselProto.spotlightItems = function() { return this._pane.spotlightItems(); }; /** @name glow.ui.Carousel#spotlightIndexes @function @description Gets an array of spotlighted indexes. These are the indexes of the nodes within {@link glow.ui.Carousel#items}. @returns {number[]} */ CarouselProto.spotlightIndexes = function() { return this._pane.spotlightIndexes(); }; /** @name glow.ui.Carousel#moveTo @function @description Move the items so a given index is in the spotlight. @param {number} itemIndex Item index to move to. @param {boolean} [animate=true] Transition to the item. If false, the carousel will switch to the new index. @returns this */ CarouselProto.moveTo = function(itemIndex, animate) { this._pane.moveTo(itemIndex, animate); return this; }; /** @private @function @decription Creates the prev & next functions @param {number} direction Either 1 or -1 */ function prevNext(direction) { return function() { this._pane.moveBy(this._pane._step * direction); return this; } } /** @name glow.ui.Carousel#next @function @description Move to the next page/item @returns this */ CarouselProto.next = prevNext(1); /** @name glow.ui.Carousel#prev @function @description Move to the previous page/item @returns this */ CarouselProto.prev = prevNext(-1); /** @name glow.ui.Carousel#destroy @function @description Remove listeners and styles from this instance. Carousel items will not be destroyed. @returns undefined */ CarouselProto.destroy = function() { // Move the pane outside our widget this._pane.container.insertBefore(this.container); WidgetProto.destroy.call(this); }; /* @name glow.ui.Carousel#updateUi @function @description Refresh the carousel after moving/adding/removing items. You can edit the items within the carousel using NodeLists such as {@link glow.ui.Carousel#itemContainer}. @example // Add a new carousel item myCarousel.itemContainer.append('<li>New Item</li>'); // Move the new item into position & update page nav etc... myCarousel.updateUi(); @returns this */ // TODO: populate #items here & check back & fwd button sizes /** @name glow.ui.Carousel#event:select @event @description Fires when a carousel item is selected. Items are selected by clicking, or pressing enter when a child is in the spotlight. Canceling this event prevents the default click/key action. @param {glow.events.Event} event Event Object @param {glow.NodeList} event.item Item selected @param {number} event.itemIndex The index of the selected item in {@link glow.ui.Carousel#items}. */ /** @name glow.ui.Carousel#event:move @event @description Fires when the carousel is about to move. Canceling this event prevents the carousel from moving. This will fire for repeated move actions. Ie, this will fire many times while the mouse button is held on one of the arrows. @param {glow.events.Event} event Event Object @param {number} event.moveBy The number of items we're moving by. This will be positive for forward movements and negative for backward movements. You can get the current index via `myCarousel.spotlightIndexes()[0]`. */ /** @name glow.ui.Carousel#event:afterMove @event @description Fires when the carousel has finished moving. Canceling this event prevents the carousel from moving. This will not fire for repeated move actions. Ie, after #start is called this will not fire until the carousel reached an end point or when it comes to rest after #stop is called. @param {glow.events.Event} event Event Object @example // double the amount a carousel will move by myCarousel.on('afterMove', function(e) { // show content related to this.spotlightIitems()[0] }); */ // EXPORT glow.ui.Carousel = Carousel; }); Glow.provide(function(glow) { var undefined, CarouselProto = glow.ui.Carousel.prototype; /** @name glow.ui.Carousel#_pageNav @type glow.NodeList @description Element containing pageNav blocks */ /** @name glow.ui.Carousel#_pageNavOpts @type Object @description Options for the page nav. Same as the opts arg for #addPageNav */ /** @name glow.ui.Carousel#addPageNav @function @description Add page navigation to the carousel. The page navigation show the user which position they are viewing within the carousel. @param {Object} [opts] Options object. @param {string|selector|HTMLElement} [opts.position='belowLast'] The position of the page navigation. This can be a CSS selector pointing to an element, or one of the following shortcuts: <dl> <dt>belowLast</dt> <dd>Display the nav beneath the last spotlight item</dd> <dt>belowNext</dt> <dd>Display the nav beneath the next button</dd> <dt>belowMiddle</dt> <dd>Display the nav beneath the carousel, centred</dd> <dt>aboveLast</dt> <dd>Display the nav above the last spotlight item</dd> <dt>aboveNext</dt> <dd>Display the nav above the next button</dd> <dt>aboveMiddle</dt> <dd>Display the nav above the carousel, centred</dd> </dl> @param {boolean} [opts.useNumbers=false] Display as numbers rather than blocks. @returns this @example new glow.ui.Carousel('#carouselContainer').addPageNav({ position: 'belowMiddle', useNumbers: true }); */ CarouselProto.addPageNav = function(opts) { opts = glow.util.apply({ position: 'belowLast' }, opts); var className = 'Carousel-pageNav'; if (opts.useNumbers) { className += 'Numbers'; } this._pageNav = glow('<div class="' + className + '"></div>') .delegate('click', 'div', pageNavClick, this); this._pageNavOpts = opts; initPageNav(this); return this; }; /** @private @function @description Listener for one of the page buttons being clicked. 'this' is the carousel */ function pageNavClick(event) { var targetPage = ( glow(event.attachedTo).text() - 1 ) * this._pane._step; this.moveTo(targetPage); } /** @private @function @description Calculate the number of pages this carousel has */ function getNumberOfPages(carousel) { var pane = carousel._pane, itemsLength = carousel.items.length, step = pane._step; if (carousel._opts.loop) { r = Math.ceil( itemsLength / step ); } else { r = 1 + Math.ceil( (itemsLength - pane._spot.capacity) / step ); } // this can be less than one if there's less than 1 page worth or items return Math.max(r, 0); } /** @private @function @description Position & populate the page nav. Its position may need refreshed after updating the carousel ui. */ function initPageNav(carousel) { var pageNav = carousel._pageNav, position = carousel._pageNavOpts.position, positionY = position.slice(0,5), positionX = position.slice(5), pane = carousel._pane, numberOfPages = getNumberOfPages(carousel), htmlStr = ''; // either append or prepend the page nav, depending on option carousel.container[ (positionY === 'below') ? 'append' : 'prepend' ](pageNav); // position in the center for Middle positions, otherwise right pageNav.css('text-align', (positionX == 'Middle') ? 'center' : 'right'); // move it under the last item for *Last positions if (positionX === 'Last') { pageNav.css( 'margin-right', carousel._nextBtn.width() + pane._itemDimensions.marginRight ) } // build the html string do { htmlStr = '<div>' + numberOfPages + '</div>' + htmlStr; } while (--numberOfPages); pageNav.html(htmlStr); carousel._updateNav( pane._index / pane._step ); } /** @name glow.ui.Carousel#_updateNav @function @description Activate a particular item on the pageNav @param {number} indexToActivate */ CarouselProto._updateNav = function(indexToActivate) { if (this._pageNav) { var activeClassName = 'active'; this._pageNav.children() .removeClass(activeClassName) .item(indexToActivate).addClass(activeClassName); } } }); Glow.complete('ui', '2.0.0b1'); ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1/ui.js����������������������������������������������������������������������������������100644 � 0 � 0 � 77162 11405426600 11043� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������/* Copyright 2010 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. */ Glow.provide(function(d){var b=d.NodeList,a=b.prototype,c;a.focusable=function(e){return new d.ui.Focusable(this,e);};});Glow.provide(function(a){a.ui=a.ui||{};});Glow.provide(function(b){function a(){}b.util.extend(a,b.events.Target);b.ui.Behaviour=a;});Glow.provide(function(l){var j,s,h=[],n,m,w=l(document),b=false;w.on("blur",function(A){n=j;if(h.length){setTimeout(k,0);}}).on("focus",function(A){if(m&&!m.container.contains(A.source)){(m.activeChild[0]||m.container[0]).focus();return false;}n=A.source;if(b){return;}b=true;d();setTimeout(p,0);});function p(){b=false;}function k(){!n&&d();}function d(){var B=h.slice(0),A=B.length;while(A--){B[A].active((n&&B[A].container.contains(n)&&n)||false);}}function u(A){A.children=A.container.get(A._opts.children);l(A.children).push(A.container).prop("tabIndex",-1);}function x(A,B){return function(C){switch(C.key){case"up":return !(B&&this.prev());case"left":return !(A&&this.prev());case"down":return !(B&&this.next());case"right":return !(A&&this.next());}};}var i={arrows:x(1,1),"arrows-x":x(1,0),"arrows-y":x(0,1)};function o(A){this._activeMethod="hover";this._activeEvent=A;this.active(A.source);this._activeEvent=this._activeMethod=j;}function t(C,A,B){return function(E){var D;C._activeMethod=A;C._activeEvent=E;D=B.apply(this,arguments);C._activeEvent=C._activeMethod=j;return D;};}function v(A,C){var B;C=this._opts=l.util.apply({children:"",keyboardNav:"arrows",setFocus:true,activeChildClass:"active"},C||{});this.container=l(A);B=C.keyboardNav;this._keyHandler=t(this,"key",(typeof B==="string"?i[B]:B));u(this);this.container[0].tabIndex=0;if(C.activateOnHover){this.container.on("mouseover",o,this);}this.container.on("click",y,this);h.push(this);}l.util.extend(v,l.ui.Behaviour);s=v.prototype;s._active=false;s._modal=false;s._disabled=false;s.activeChild=l();s.children=l();s.modal=function(A){if(A===j){return this._modal;}if(!this._disabled){if(A&&!this._modal){if(m){m.modal(false);}m=this;this.active(true);}else{if(!A&&this._modal){m=j;}}this._modal=!!A;}return this;};function q(F,A){var C=F.activeChild[0],B=F._opts.activeChildClass,E=F.activeChild=l(F.children[A]),D={item:E,itemIndex:A,method:F._activeMethod||"api",methodEvent:F._activeEvent};F.activeIndex=A;if(C===E||F.fire("childActivate",D).defaultPrevented()){return;}if(C){C.tabIndex=-1;l(C).removeClass(B);}F.activeChild[0].tabIndex=0;F.activeChild.addClass(B);F._opts.setFocus&&F.activeChild[0].focus();}function g(D,E){var B,A=D.children,C=A[0];if(!C){return -1;}E=l(E).item(0);if(E[0]){B=A.length;while(B--){if(l(A[B]).contains(E)){return B;}}}return -1;}function z(C,A){var B=C.children.length;if(C._opts.loop){A=A%B;if(A<0){A=B+A;}}else{A=Math.max(Math.min(A,B-1),0);}return A;}function e(A){if(A.fire("deactivate").defaultPrevented()){return;}A.activeChild.removeClass(A._opts.activeChildClass);A._lastActiveChild=A.activeChild[0];(A.activeChild[0]||A.container[0]).blur();A.activeIndex=j;A.activeChild=s.activeChild;A._active=false;w.detach("keypress",A._keyHandler).detach("keydown",a);A.container.prop("tabIndex",0);}function f(E,C){var B=E._active,D,A=-1;if(!B){if(E.fire("activate").defaultPrevented()){return;}u(E);E._active=true;w.on("keypress",E._keyHandler,E).on("keydown",a,E);D=true;}if(E.children[0]){if(typeof C==="number"){A=z(E,C);}else{if(typeof C!=="boolean"){A=g(E,C);}}if(A===-1&&!B){A=g(E,E._lastActiveChild);A=A!==-1?A:0;}}if(A!==-1&&A!==E.activeIndex){q(E,A);}else{if(D){E._opts.setFocus&&E.container[0].focus();}}}s.active=function(B){var A=this._active;if(B===j){return A;}if(!this._disabled){if(B===false){if(!this._modal&&A){e(this);}}else{f(this,B);}}return this;};function c(A){return function(){if(this._active){this.active(this.activeIndex+A);}return this;};}s.next=c(1);s.prev=c(-1);s.disabled=function(A){if(A===j){return this._disabled;}if(A){this.active(false);this._disabled=!!A;}else{this._disabled=!!A;if(this._modal){this.active(true);}}return this;};s.destroy=function(){var A=h.length;l.events.removeAllListeners([this]);this.modal(false).active(false).container.detach("mouseover",o).detach("click",y).prop("tabIndex",-1);this.container=j;while(A--){if(h[A]===this){h.splice(A,1);break;}}};function y(){if(this.activeChild[0]){return !this.fire("select",{item:this.activeChild,itemIndex:this.activeIndex}).defaultPrevented();}}function a(A){if(A.key==="return"){return y.call(this);}}l.ui.Focusable=v;});Glow.provide(function(e){var d,a;function c(f,g){this._name=f;this._locale="en";this.phase="constructed";this._observers=[];this._opts=g||{};}e.util.extend(c,e.events.Target);a=c.prototype;a._disabled=false;function b(f){if(f.disabled!==d){this.disabled(f.disabled);}else{if(f.destroy){this.destroy();}}}a._tie=function(){var g=Array.prototype.slice.call(arguments),f=g.length;while(f--){g[f].on("_sync",b);}this._observers=this._observers.concat(g);return this;};a._sync=function(f){e.events.fire(this._observers,"_sync",f||{});return this;};a.disabled=function(f){if(f===d){return this._disabled;}f=!!f;if(f!==this._disabled&&!this.fire("disabled",{disabled:f}).defaultPrevented()){this._sync({disabled:f});this._stateElm[f?"addClass":"removeClass"]("disabled");this._disabled=!!f;}return this;};a._init=function(){this.phase="initialised";};a._build=function(i){var f,g=this._name,h=this._opts;i=this.content=e(i||"<div></div>");f=this.container=e('<div class="glow200b1-'+g+'"><div class="'+g+'-theme"><div class="'+g+'-state"></div></div></div>');i.addClass(g+"-content").wrap(f);this._stateElm=i.parent();this._themeElm=this._stateElm.parent();if(h.className){f.addClass(h.className);}if(h.id){f[0].id=h.id;}this.phase="built";};a._bind=function(){this.phase="ready";};a._updateUi=function(){};a.destroy=function(){if(!this.fire("destroy").defaultPrevented()){this._sync({destroy:1});e.events.removeAllListeners([this]);this.container.destroy();this.phase="destroyed";}return this;};e.ui.Widget=c;});Glow.provide(function(m){var i,n=m.ui.Widget.prototype,e=0,d,a={};var c={SHOWING:2,SHOWN:1,HIDING:-1,HIDDEN:-2};function b(s,q){var p=this,o;q=m.util.apply({},q);b.base.call(this,"overlay",q);this.uid="overlayId_"+m.UID+"_"+(++e);a[this.uid]=this;this._init(q);this._build(s);this._bind();}m.util.extend(b,m.ui.Widget);i=b.prototype;i._init=function(){n._init.call(this);this.shown=false;return this;};i.destroy=function(){n.destroy.call(this);delete a[this.uid];};i._build=function(p){var o=this;n._build.call(this,p);if(this._opts.hideWhenShown===d){ua=navigator.userAgent;this._whatToHide=(m.env.opera&&!/macintosh/i.test(ua)||/rv:1\.9\.0.*\bgecko\//i.test(ua)||m.env.webkit&&!/macintosh/i.test(ua))?function(){return m("object, embed");}:function(){return m();};}else{if(!this._opts.hideWhenShown){this._whatToHide=function(){return m();};}else{if(typeof this._opts.hideWhenShown==="function"){this._whatToHide=this._opts.hideWhenShown;}else{if(this._opts.hideWhenShown.length!==d){this._whatToHide=function(){return m("*").filter(this._opts.hideWhenShown);};}}}}if(m.env.ie){this._iframe=m('<iframe src="javascript:\'\'" style="display:block;width:100%;height:1000px;margin:0;padding:0;border:none;position:absolute;top:0;left:0;filter:alpha(opacity=0);"></iframe>');this._iframe.css("z-index",0);this._iframe.insertBefore(this.content);}this.content.css("position","relative").css("z-index",1).css("top",0).css("left",0);return this;};i.hideFlash=function(){var p,s=this,t="";p=this._whatToHide();for(var q=0,o=p.length;q<o;q++){t=(p.item(q).data("overlayHidBy")||"");if(t===""){p.item(q).data("overlayOrigVisibility",p.item(q).css("visibility"));p.item(q).css("visibility","hidden");}if(t.indexOf("["+this.uid+"]")===-1){p.item(q).data("overlayHidBy",t+"["+this.uid+"]");}}if(p.length&&!s._doShowFlash){s._doShowFlash=true;s.on("afterHide",function(){s.showFlash();});}this._hiddenElements=p;};i.showFlash=function(){var s="";if(!this._hiddenElements||this._hiddenElements.length===0){return;}var o=this._hiddenElements;for(var q=0,p=o.length;q<p;q++){s=(o.item(q).data("overlayHidBy")||"");if(s.indexOf("["+this.uid+"]")>-1){s=s.replace("["+this.uid+"]","");o.item(q).data("overlayHidBy",s);}if(s==""){o.item(q).css("visibility",o.item(q).data("overlayOrigVisibility"));}}};var j={slide:[function(o){o.container.height(0);},function(p,s){var q,o=this.container;if(p){q=o.slideOpen(0.5).data("glow_slideOpen");}else{q=o.slideShut(0.5).data("glow_slideShut");}q.on("complete",s);}],fade:[function(o){o.container.css("opacity",0);},function(p,s){var q,o=this.container;if(p){q=o.fadeIn(0.5).data("glow_fadeIn");}else{q=o.fadeOut(0.5).data("glow_fadeOut");}q.on("complete",s);}]};i.setAnim=function(o){if(o===null){delete this._animDef;delete this._animator;}else{if(typeof o==="string"){j[o][0](this);this._animator=j[o][1];}else{if(typeof o==="function"){this._animator=o;}else{this._animDef=o;this._animDef[2]=this._animDef[2]||{};this._animDef[2].destroyOnComplete=false;}}}return this;};i.show=function(o){var p=this;if(!this.fire("show").defaultPrevented()){if(this._timer){clearTimeout(this._timer);}if(o){this._timer=setTimeout(function(){l.call(p);},o*1000);}else{l.call(p);}}return this;};function l(){var o=this;if(this.state===c.SHOWING||this.state===c.SHOWN){return;}f(o,true);if(this._whatToHide){this.hideFlash();}if(this._animator){o.state=c.SHOWING;this._animator.call(this,true,function(){k.call(o);});}else{if(this._animDef){if(this._anim){this.state=c.SHOWING;this._anim.reverse();}else{this.state=c.SHOWING;this._anim=this.container.anim(this._animDef[0],this._animDef[1],this._animDef[2]);this._anim.on("complete",function(){if(o.state===c.SHOWING){f(o,true);k.call(o);}else{if(o.state===c.HIDING){f(o,false);g.call(o);}}});}this._anim.start();}else{k.call(this);}}}function k(){this.state=c.SHOWN;this.fire("afterShow");}function f(p,o){var q=p._stateElm;p.shown=o;if(o){q.removeClass("hidden");q.addClass("shown");}else{q.removeClass("shown");q.addClass("hidden");}}function h(){var o=this;if(this.state===c.HIDING||this.state===c.HIDDEN){return;}if(this._animator){this._animator.call(this,false,function(){f(o,false);g.call(o);});}else{if(this._anim){this.state=c.HIDING;this._anim.reverse();this._anim.start();}else{f(o,false);g.call(this);}}}function g(){this.state=c.HIDDEN;this.fire("afterHide");}i.hide=function(o){var p=this;if(!this.fire("hide").defaultPrevented()){if(this._timer){clearTimeout(this._timer);}if(o){this._timer=setTimeout(function(){h.call(p);},o*1000);}else{h.call(p);}}return this;};m.ui=m.ui||{};m.ui.Overlay=b;});Glow.provide(function(h){var b,m,f=h.ui.Widget,i=f.prototype,p=h("<div></div>");function c(q){q=h.util.apply({minLength:3,keyboardNav:"arrows-y",activateFirst:true},q);f.call(this,"AutoSuggest",q);this._init();}h.util.extend(c,f);m=c.prototype;m._data=[];m._filter=function(t,q){var s=this.name.slice(0,t.length);s=q?s:s.toLowerCase();return s===t;};m._format=function(q,v){var u=p.text(q.name).html(),t=u.toLowerCase().indexOf(v.toLowerCase()),s=t+v.length;if(t!==-1){u=u.slice(0,t)+'<em class="AutoSuggest-match">'+u.slice(t,s)+"</em>"+u.slice(s);}return u;};m._init=function(){i._init.call(this);this._build();};m._build=function(){i._build.call(this,"<ol></ol>");var s=this._opts,q=s.width;content=this.content;this.focusable=content.focusable({children:"> li",keyboardNav:this._opts.keyboardNav,setFocus:false,activateOnHover:true});q&&this.container.width(q);this._bind();};function g(q){return !this.fire("select",{li:q.item,item:q.item.data("as_data")}).defaultPrevented();}function j(s){var q=s.item,t=this.focusable;}function a(){return false;}m._bind=function(){var q=this.focusable.on("select",g,this).on("childActivate",j,this);this._tie(q);this.container.on("mousedown",a);i._bind.call(this);};m.setFilter=function(q){this._filter=q;return this;};m.setFormat=function(q){this._format=q;return this;};function e(u,v){var q,t,s=u.fire("data",{data:v});if(!s.defaultPrevented()){v=s.data;if(v instanceof h.net.XhrResponse){v=v.json();}if(typeof v[0]==="string"){t=[];q=v.length;while(q--){t[q]={name:v[q]};}v=t;}u._data=v;}}function l(s,q){s._dataFunc=function(w){var t=s.input,v=s._bindOpts,u=(v&&v.loadingClass)||"";s._loading=true;t.addClass(u);q.call(this,w,function(y){var x=s._pendingFind;s._loading=false;t.removeClass(u);y&&e(s,y);if(x){k(s,x);s._pendingFind=b;}});};}function o(q){var t,s;return function(u,v){if(t){return v();}if(!s){s=h.net.get(q).on("load",function(w){t=1;v(w);});}};}function d(q){var s;return function(u,v){var t=h.util.interpolate(q,{val:u});s&&s.abort();s=h.net.get(t).on("load",function(w){v(w);});};}m.data=function(q){if(typeof q==="string"){if(q.indexOf("{val}")===-1){q=o(q);}else{q=d(q);}}if(typeof q==="function"){l(this,q);}else{if(q.push){this._dataFunc=b;e(this,q);}}return this;};function n(z,u,t){var x=z.content,A=u.length,v=A,s,w,q=z._opts,y=z.focusable;y.active(false);if(!A&&z.overlay){z._hideOverlay();return;}x.children().destroy();while(v--){w=z._format(u[v],t);s=h('<li class="AutoSuggest-item"></li>').data("as_data",u[v]).prependTo(x);(typeof w==="string")?s.html(w):s.append(w);}if(A){q.activateFirst&&y.active(true);z._showOverlay();}else{z._hideOverlay();}}function k(A,x){var z=[],s=0,u=A._data,w=A.fire("find",{val:x}),q,y=A._opts.caseSensitive;if(!w.defaultPrevented()){x=w.val;x=y?x:x.toLowerCase();for(var t=0,v=u.length;t<v;t++){if(A._filter.call(u[t],x,y)){z[s++]=u[t];if(s===A._opts.maxResults){break;}}}q=A.fire("results",{results:z});if(q.defaultPrevented()){z=[];}else{z=q.results;}n(A,z,w.val);}}m.find=function(q){if(q.length>=this._opts.minLength){this._dataFunc&&this._dataFunc(q);if(this._loading){this._pendingFind=q;}else{k(this,q);}}else{this.hide();}return this;};m.hide=function(){clearTimeout(this._inputTimeout);n(this,[],"");return this;};m.destroy=function(){this._data=b;this.input.detach("keypress",this._inputPress).detach("blur",this._inputBlur).detach("onbeforedeactivate",this._inputDeact);i.destroy.call(this);};h.ui.AutoSuggest=c;});Glow.provide(function(i){var c,n=i.ui.AutoSuggest.prototype;n.input=i();function o(t){var q=this,v=q.focusable,w,s=v.activeIndex,u;if(t.keyChar||t.key==="delete"||t.key==="backspace"){clearTimeout(q._inputTimeout);q._inputTimeout=setTimeout(function(){q.find(m(q));},q._bindOpts.delay*1000);}else{w=v.active();switch(t.key){case"escape":q.hide();f(q);return false;case"up":if(w&&!s){v.active(false);f(q);return false;}case"down":if(!w&&(u=q.content.children().length)){v.active(t.key=="up"?u-1:0);t.stopPropagation();return false;}}}}n._inputPress=o;function m(u){var s=u.input,w=u._bindOpts.delim,v=s.val(),q,t;if(w){t=a(u);v=v.slice(0,t);q=v.lastIndexOf(w);if(q!==-1){v=v.slice(v.lastIndexOf(w)+w.length);}}return i.util.trim(v);}function h(){this.hide();}n._inputBlur=h;function p(q){if(this.container.contains(q.related)){return false;}}n._inputDeact=p;function j(q){g(this.hide(),q.item.name);e(this,this.input.val().length);}function l(q){if(q.method!=="hover"){g(this,q.item.data("as_data").name,true);}}function g(B,t,y){f(B);var x=B.input,z=x.val(),v=a(B),A=v,q=t.length,s=B._bindOpts.delim,w,u="";if(s){w=z.slice(0,v).lastIndexOf(s);if(w!==-1){u=z.slice(0,w)+s+" ";}t=u+t+s+" ";q=t.length;t+=z.slice(v);}x.val(t);y&&e(B,A,q);}function e(u,w,q){q=(q===c)?w:q;var t=u.input[0],v="character",s;if(!window.opera&&t.createTextRange){s=t.createTextRange();s.moveStart(v,w);s.moveEnd(v,q-t.value.length);s.select();}else{t.select();t.selectionStart=w;t.selectionEnd=q;}}function a(t){var q=t.input[0],s;if(i.env.ie){range=document.selection.createRange();range.collapse();range.setEndPoint("StartToStart",q.createTextRange());s=range.text.length;}else{s=q.selectionStart;}return s;}function f(s){var q=s.input[0],u=q.value,t;if(i.env.ie){document.selection.createRange().text="";}else{t=q.selectionStart;q.value=u.slice(0,t)+u.slice(q.selectionEnd);q.selectionStart=t;}}n._showOverlay=function(){var s=this.overlay,v=this._opts,u=this._bindOpts,q=this.input,t;if(!s){return;}if(!v.width){this.container.width(q[0].offsetWidth);}if(u.autoPosition){t=q.offset();s.container.css({top:t.top+q[0].offsetHeight,left:t.left});}s.show();};n._hideOverlay=function(){var q=this.overlay;q&&q.hide();};n.bindInput=function(s,v){var w=this._bindOpts=i.util.apply({autoPosition:true,autoComplete:true,delay:0.5,loadingClass:"glow200b1-AutoSuggest-loading"},v),u=w.appendTo,q=this.container,t,x=this._opts;this.input=i(s).attr("autocomplete","off").on("keypress",o,this).on("blur",h,this).on("beforedeactivate",p,this);if(w.autoComplete){this.on("select",j,this).focusable.on("childActivate",l,this);}if(u){i(u).append(q);}else{this.overlay=t=new i.ui.Overlay(q).on("hide",b,this).on("afterShow",k,this).hide();this.focusable.disabled(true);t.container.appendTo(document.body);if(w.anim==="slide"){w.anim=d;}w.anim&&t.setAnim(w.anim);this._tie(t);}return this;};function d(s,v){var u,q=this.container,t={lockToBottom:true};if(s){q.height(0);u=q.slideOpen(0.5,t).data("glow_slideOpen");}else{u=q.slideShut(0.5,t).data("glow_slideShut");}u.on("complete",v);}function b(){this.focusable.disabled(true);}function k(){var q=this.focusable;q.disabled(false);if(this._opts.activateFirst){q.active(true);}}});Glow.provide(function(f){var e,b,h=f.ui.Widget.prototype;function s(w,y){var x=this;y=f.util.apply({duration:0.2,tween:"easeBoth",step:1,loop:false,page:false,axis:"x"},y||{});f.ui.Widget.call(this,"CarouselPane",y);if(f(w).length>0){this._init(w,y);}}f.util.extend(s,f.ui.Widget);b=s.prototype;function n(x){var z;this.range={min:0,max:0};this.items={};this.meta={};for(var y=0,w=x.length;y<w;y++){this.addItem(y,x.item(y));}}n.prototype.addItem=function(w,x,y){this.range.min=Math.min(this.range.min,w);this.range.max=Math.max(this.range.max,w);this.items[w]=x;this.meta[w]=y||{};};n.prototype.addMeta=function(w,x){if(this.meta[w]){this.meta[w]=f.util.apply(this.meta[w],x);}};n.prototype.place=function(y,x){for(var w in this.items){if(y!==e){this.items[w].css("top",y);}this.items[w].css("left",(x===e)?this.meta[w].offset:x);}};n.prototype.dump=function(y){if(typeof console!=="undefined"){for(var x=y._itemList.range.min,w=y._itemList.range.max;x<=w;x++){if(y._itemList.meta[x]){console.log(">> "+x+": "+(y._itemList.meta[x].isClone?"clone":"real")+" at "+y._itemList.meta[x].offset+" "+y._itemList.items[x][0].children[0].alt);}else{console.log(">> "+x+": "+y._itemList.meta[x]);}}}};n.prototype.swap=function(x,w){this.items[x].css("left",this.meta[w].offset);this.items[w].css("left",this.meta[x].offset);};b._init=function(w){h._init.call(this);this._step=this._opts.step;this._geom=(this._opts.axis==="y")?["height","top"]:["width","left"];this.stage=f(w).item(0);this._focusable=this.stage.focusable({children:"> *",loop:true,setFocus:true});this._viewport=f('<div class="CarouselPane-viewport"></div>');f(this.stage).wrap(this._viewport);this.items=this.stage.children();this._itemList=new n(this.items);if(this._opts.spotlight>this.items.length){this._opts.spotlight=this.items.length;}this._index=0;this._build();};b._build=function(){h._build.call(this,this._viewport);this.stage.css({margin:0,listStyleType:"none"});this.items.css({position:"absolute","z-index":2});this._itemDimensions=j(this.items);this.items.css({margin:0,width:this._itemDimensions.innerWidth,height:this._itemDimensions.innerHeight});this._wingSize=Math.ceil(this.items.length*this._itemDimensions[this._geom[0]]*1.5);this._viewport.css({overflow:"scroll",overflowX:"hidden",overflowY:"hidden",position:"relative",padding:0,margin:0,width:this._opts.axis==="x"?"100%":this._itemDimensions.width,height:this._opts.axis==="y"?"100%":this._itemDimensions.height});this._spot=s._getSpot(this._viewport.width(),this.items,this._itemDimensions,this._opts);if(this._opts.step===true){this._step=this._spot.capacity;}else{if(this._opts.step>this._spot.capacity){this._step=this._spot.capacity;}}if(this._opts.page&&this._step!==this._spot.capacity){this._step=this._spot.capacity;}this._gap=a(this);this.stage.css({width:this.stage.width()+this._wingSize*2,height:"100%"});t.call(this);this._bind();p.call(this);};function a(w){var y={size:0,count:0},x=w.items.length%w._step;if(w._opts.page&&w._step>1){y.count=x?w._spot.capacity-x:0;y.size=y.count*w._itemDimensions[w._geom[0]];}return y;}b._bind=function(){var w=this;h._bind.call(w);q(w,w._focusable,"childActivate",function(A){var z=A.itemIndex,y=w.spotlightIndexes(true),x=(" "+y.join(" ")+" ").indexOf(" "+z+" ")>-1;if(z!==e&&!x){w.moveTo(z,{tween:""});w._index=z;}});this._focusable.on("select",function(x){x.itemIndex=x.item.data("itemIndex");w.fire("select",x);});};function q(w,z,x,y){z.on(x,y);w._addedEvents=w._addedEvents||[];w._addedEvents.push({target:z,name:x,handler:y});}function i(w){var x=w._addedEvents?w._addedEvents.length:0,y;while(x--){y=w._addedEvents[x];y.target.detach(y.name,y.handler);}}b.updateUi=function(){h._updateUi.call(this);this.stage.css({width:this.stage.width()+this._wingSize*2,height:"100%"});this._spot=s._getSpot(this._viewport.width(),this.items,this._itemDimensions,this._opts);if(this._opts.step===true){this._step=this._spot.capacity;}t.call(this);this._index=0;this.fire("updateUi",{});};b.moveStop=function(){this._gliderBrake=true;};b.moveStart=function(x){var y=(x?-1:1)*this._step,w=this;if(!w._inMotion){w._gliderBrake=false;w.moveTo(w._index+y,{callback:function(){if(!w._gliderBrake){if(w._opts.loop||((x&&w._index>0)||(!x&&w._index+w._spot.capacity<w.items.length))){if(w._step===1){m.call(w,x);}else{w.moveStart(x);}}}}});}return w;};b.moveToggle=function(w){if(this._inMotion&&!this._gliderBrake){this.moveStop();}else{this.moveStart(w);}return this;};var m=function(F){var x=(F?-1:1),w=[],y=this.content[0].scrollLeft,A=this._itemDimensions[this._geom[0]],D,E,B=this,C,H=y+(F?-this._index*A:(this.items.length-this._index)*A);k.call(this,"back");for(var z=0,G=this.items.length;z<G;z+=this._step){D=y+x*z*A;E=y+x*(z+this._step)*A;if((F&&D===H)||(!F&&E===H)){y-=x*this.items.length*A;}C=this.content.anim(this._opts.duration,{scrollLeft:[D,E]},{tween:"linear",startNow:false}).on("start",function(){v.call(B);if(B.fire("move",{moveBy:x,currentIndex:B._index}).defaultPrevented()){o.call(B);}}).on("complete",function(){B._index+=x;if(B._gliderBrake||(!B._opts.loop&&(B._index+B._spot.capacity===B.items.length||B._index===0))){o.call(B);B.fire("afterMove",{currentIndex:B._index});}});w.push(C);}this._glider=new f.anim.Timeline({loop:true});f.anim.Timeline.prototype.track.apply(this._glider,w);this._inMotion=true;this._gliderBrake=false;this._glider.start();};function v(w){if(w!==e){this._index=w;}this._index=this._index%this.items.length;while(this._index<0){this._index+=this.items.length;}return this._index;}function l(w){return v.call(this,this._index+=w);}function o(){this._glider.stop();this._glider.destroy();this._inMotion=false;this._index=p.call(this);d.call(this);k.call(this);}b.spotlightIndexes=function(B){var z=[],x=p.call(this),y,w=(this._opts.loop)?this._spot.capacity:Math.min(this._spot.capacity,this.items.length);for(var A=0;A<w;A++){y=B?(x+A):(x+A)%(this.items.length+this._gap.count);if(y>=this.items.length||y<0){continue;}z.push(y);}return z;};b.spotlightItems=function(){var x=f(),y=this.spotlightIndexes();for(var z=0,w=y.length;z<w;z++){x.push(this.items[y[z]]);}return x;};function p(){var w=this.content[0].scrollLeft-(this._wingSize+this._spot.offset.left);w+=this._spot.offset.left;w/=this._itemDimensions.width;return w;}b.moveTo=function(z,B){var x,w,y,C;if(this._inMotion){return false;}B=B||{};if(!this._opts.loop&&z>this.items.length-this._spot.capacity){z=this.items.length-this._spot.capacity+(this._opts.page?this._gap.count:0);}else{if(!this._opts.loop&&z<0){z=0;}}x=(z!==this._index&&c.call(this,z));if(!B.jump){var D=new f.events.Event({currentIndex:this._index,moveBy:(this._index<z)?(z-this._index):(-Math.abs(this._index-z))});if(!B.jump&&x&&this.fire("move",D).defaultPrevented()){return this;}else{z=this._index+D.moveBy;}}if(this._opts.page){z=Math.floor(z/this._step)*this._step;}if(z>this.items.length+this._step||z<0-this._step){z=this._index+(this._index<z)?-this._step:this._step;}w=this._wingSize+z*this._itemDimensions.width;k.call(this,"back");y=B.tween||this._opts.tween;var A=this;if(B.jump===true||B.tween===""){this.content[0].scrollLeft=w;this._index=z;d.call(this);k.call(this);this._index=this._index%(this.items.length+this._gap.count);if(!B.jump&&x){this.fire("afterMove",{currentIndex:this._index});}this._inMotion=false;}else{if(x){this._inMotion=true;C=this.content.anim(this._opts.duration,{scrollLeft:w},{tween:B.tween||this._opts.tween});this._index=z;C.on("complete",function(){A._inMotion=false;d.call(A);k.call(A);A._index=A._index%(A.items.length+A._gap.count);A.fire("afterMove",{currentIndex:A._index});if(B.callback){B.callback();}});}}return this;};function d(){if(this._index<0){this.moveTo(this.items.length+this._gap.count+this._index,{jump:true});}else{if(this._index>=this.items.length){this.moveTo(this._index-(this.items.length+this._gap.count),{jump:true});}}}function k(y){var w;if(!this._opts.loop){return;}if(y){this._itemList.place();}else{for(var z=0,x=this._spot.capacity-this._gap.count;z<x;z++){w=(this._index+z);if(w>=this.items.length){this._itemList.swap(w,w%this.items.length);}}}}b.moveBy=function(w){this.moveTo(this._index+w);return this;};b.next=function(){this.moveTo(this._index+this._step);return this;};b.prev=function(){this.moveTo(this._index-this._step);return this;};function c(w){if(this._opts.loop){return true;}if(w<0){return false;}if(w-this._step>=this.items.length-this._spot.capacity){return false;}return true;}function j(E){var z,x=0,y=0,F=0,G=0,B=0,D=0,A=0,w=0,C=0;E.each(function(){z=f(this);G=Math.max(this.offsetHeight,G);F=Math.max(this.offsetWidth,F);x=Math.max(z.width(),x);y=Math.max(z.height(),y);D=Math.max(g(z.css("margin-right")),D);A=Math.max(g(z.css("margin-left")),A);w=Math.max(g(z.css("margin-top")),w);C=Math.max(g(z.css("margin-bottom")),C);});B=Math.max(A,D);return{width:F+B,height:G+w+C,innerWidth:x,innerHeight:y,marginLeft:A,marginRight:D,marginTop:w,marginBottom:C};}function g(w){if(w==="auto"){return 0;}else{return parseInt(w);}}s._getSpot=function(w,x,y,A){var z={capacity:0,top:0,left:0,width:0,height:0,offset:{top:0,right:0,bottom:0,left:0}},A=A||{};if(!y){y=j(x);}if(A.axis="x"){if(x.length===0){z.capacity=0;}else{if(A.spotlight){if(A.spotlight>x.length){throw new Error("spotlight cannot be larger than item count.");}z.capacity=A.spotlight;}else{z.capacity=Math.floor(w/y.width);}}if(z.capacity>x.length){z.capacity=x.length;}z.width=z.capacity*y.width+Math.min(y.marginLeft,y.marginRight);z.height=y.height;z.offset.left=Math.floor((w-z.width)/2);z.offset.right=w-(z.offset.left+z.width);}else{throw Error("y axis (vertical) not yet implemented");}return z;};function u(x){position={top:0,left:0};var w=this._itemDimensions.width,y=this._spot.offset.left+this._wingSize+this._itemDimensions.marginLeft,z=0;if(this._opts.page&&x<0){z=-(1+Math.floor(Math.abs(x+this._gap.count)/this.items.length))*this._gap.count*w;}else{if(this._opts.page&&x>=this.items.length){z=Math.floor(x/this.items.length)*this._gap.count*w;}}position.left=y+(x*w)+z;position.top=this._itemDimensions.marginTop;return position;}function t(){var B,A;this.content[0].scrollLeft=this._wingSize;for(var y=0,x=this.items.length;y<x;y++){this._itemList.addMeta(y,{offset:u.call(this,y).left,isClone:false});this.items.item(y).data("itemIndex",+y);}if(this._opts.loop){this.stage.get(".carousel-clone").remove();var z=1+Math.ceil(this._spot.offset.left/(this._itemDimensions.width*this.items.length+this._gap.size));for(var w=1;w<=z;w++){y=this.items.length;while(y--){B=this.items.item(y).copy();B.removeClass("carousel-item").addClass("carousel-clone").css({"z-index":1,margin:0});A=u.call(this,0-(w*this.items.length-y)).left;this._itemList.addItem(0-(w*this.items.length-y),B,{isClone:true,offset:A});this.stage[0].appendChild(B[0]);B=B.copy();A=u.call(this,w*this.items.length+y).left;this._itemList.addItem(w*this.items.length+y+this._gap.count,B,{isClone:true,offset:A});this.stage[0].appendChild(B[0]);}}}this.items.addClass("carousel-item");this._itemList.place(this._itemDimensions.marginTop,e);}b.destroy=function(){this.stage.get(".carousel-clone").remove();i(this);this.stage.insertBefore(this.container).children().css("position","");h.destroy.call(this);};f.ui.CarouselPane=s;});Glow.provide(function(k){var d,g,j=k.ui.Widget,l=j.prototype;function i(o,q){var p;j.call(this,"Carousel",q);q=this._opts;if(q.page){q.step=q.page;q.page=true;}this.itemContainer=o=k(o).item(0);p=k.ui.CarouselPane._getSpot(o.parent().width(),o.children().css("position","absolute"),0,q);if(p.offset.left<50){q.spotlight=p.capacity-1;}this._init();}k.util.extend(i,k.ui.Widget);g=i.prototype;g._init=function(){l._init.call(this);this._build();};g._build=function(){var s,p=this.itemContainer,t,o,q;l._build.call(this,p.wrap("<div></div>").parent());s=this.content;t=this._pane=new k.ui.CarouselPane(p,this._opts);q=t._spot;o=this.items=t.items;this.itemContainer=t.itemContainer;t.moveTo(0,{tween:null});this._prevBtn=k('<div class="Carousel-prev"><div class="Carousel-btnIcon"></div></div>').prependTo(s).css({width:q.offset.left,height:q.height});this._nextBtn=k('<div class="Carousel-next"><div class="Carousel-btnIcon"></div></div>').prependTo(s).css({width:q.offset.right,height:q.height});m(this);this._bind();};function m(q){if(q._opts.loop){return;}var o=q.spotlightIndexes(),s=o[o.length-1],p=q.items.length-1;q._prevBtn[(o[0]===0)?"addClass":"removeClass"]("Carousel-prev-disabled");q._nextBtn[(s===p)?"addClass":"removeClass"]("Carousel-next-disabled");}function a(o){this.fire("select",o);}function b(o){var p=this._pane;if(!this.fire("move",o).defaultPrevented()){this._updateNav((p._index+o.moveBy)%this.items.length/p._step);}}function f(o){if(!this.fire("afterMove",o).defaultPrevented()){m(this);}}function c(o){if(o.button===0){this._pane.moveStart(true);return false;}}function n(o){if(o.button===0){this._pane.moveStart();return false;}}function h(){this._pane.moveStop();}g._bind=function(){var p=this._pane,o=this;this._tie(p);p.on("select",a,this).on("afterMove",f,this).on("move",b,this);this._prevBtn.on("mousedown",c,this).on("mouseup",h,this).on("mouseleave",h,this);this._nextBtn.on("mousedown",n,this).on("mouseup",h,this).on("mouseleave",h,this);l._bind.call(this);};g.spotlightItems=function(){return this._pane.spotlightItems();};g.spotlightIndexes=function(){return this._pane.spotlightIndexes();};g.moveTo=function(p,o){this._pane.moveTo(p,o);return this;};function e(o){return function(){this._pane.moveBy(this._pane._step*o);return this;};}g.next=e(1);g.prev=e(-1);g.destroy=function(){this._pane.container.insertBefore(this.container);l.destroy.call(this);};k.ui.Carousel=i;});Glow.provide(function(f){var e,a=f.ui.Carousel.prototype;a.addPageNav=function(h){h=f.util.apply({position:"belowLast"},h);var g="Carousel-pageNav";if(h.useNumbers){g+="Numbers";}this._pageNav=f('<div class="'+g+'"></div>').delegate("click","div",d,this);this._pageNavOpts=h;b(this);return this;};function d(g){var h=(f(g.attachedTo).text()-1)*this._pane._step;this.moveTo(h);}function c(i){var j=i._pane,g=i.items.length,h=j._step;if(i._opts.loop){r=Math.ceil(g/h);}else{r=1+Math.ceil((g-j._spot.capacity)/h);}return Math.max(r,0);}function b(k){var h=k._pageNav,g=k._pageNavOpts.position,i=g.slice(0,5),j=g.slice(5),n=k._pane,m=c(k),l="";k.container[(i==="below")?"append":"prepend"](h);h.css("text-align",(j=="Middle")?"center":"right");if(j==="Last"){h.css("margin-right",k._nextBtn.width()+n._itemDimensions.marginRight);}do{l="<div>"+m+"</div>"+l;}while(--m);h.html(l);k._updateNav(n._index/n._step);}a._updateNav=function(g){if(this._pageNav){var h="active";this._pageNav.children().removeClass(h).item(g).addClass(h);}};});Glow.complete("ui","2.0.0b1");������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������