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/ 麻豆社 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鈥檛 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鈥檚 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 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 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 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 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 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 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:

<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:

<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:

Documentation generated by 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 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 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 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 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 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 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: '/favicon1.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 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 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: '/favicon1.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: '/favicon1.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 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 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 ` JsDoc Reference - glow.net.XhrRequest

麻豆社

Class glow.net.XhrRequest


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.net.XhrRequest(method, url, opts)
Create an XHR request.
Field Summary
Field Attributes Field Name and Description
 
Boolean indicating whether the request has completed
 
The request object from the browser.
Method Summary
Method Attributes Method Name and Description
 
Aborts a request The load & error events will not fire.
Event Summary
Event Attributes Event Name and Description
 
abort(event)
Fired when the request is aborted If you cancel the default (eg, by returning false) the request will continue.
 
error(response)
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.
 
load(response)
Fired when the request is sucessful This will be fired when request returns with an HTTP code of 2xx.
Class Detail
glow.net.XhrRequest(method, url, opts)
Create an XHR request. Most common requests can be made using shortcuts methods in glow.net, such as glow.net.get.
			new glow.net.XhrRequest('DELETE', 'whatever.php', {
				timeout: 10
			}).on('load', function(response) {
				alert( response.text() );
			});
Parameters:
{string} method
The HTTP method to use for the request. Methods are case sensitive in some browsers.
{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 object
{Object} opts.headers Optional
A hash of headers to send along with the request. eg `{'Accept-Language': 'en-gb'}`
{boolean} opts.cacheBust Optional, Default: 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.
{number} opts.timeout Optional
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.
{boolean} opts.forceXml Optional, Default: 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.
{Object|string} opts.data Optional
Data to send. This can be either a JSON-style object or a urlEncoded string.
Field Detail
{boolean} complete
Boolean indicating whether the request has completed
			// request.complete with an asynchronous call
			var request = glow.net.get(
				"myFile.html").on('load', 
				function(response){
					alert(request.complete); // returns true
				})

{Object} nativeRequest
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.
Method Detail
abort()
Aborts a request The load & error events will not fire.
			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
Event Detail
abort(event)
Fired when the request is aborted If you cancel the default (eg, by returning false) the request will continue.
Parameters:
{glow.events.Event} event
Event Object

error(response)
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.
Parameters:
{glow.net.XhrResponse} response

load(response)
Fired when the request is sucessful This will be fired when request returns with an HTTP code of 2xx.
Parameters:
{glow.net.XhrResponse} response

Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.net.xhrresponse.html100644 0 0 43646 11405426572 15672 0ustar 0 0 JsDoc Reference - glow.net.XhrResponse

麻豆社

Class glow.net.XhrResponse


Extends glow.events.Event.

Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
The event object for glow.net.XhrRequest's 'load' & 'error' events.
Field Summary
Field Attributes Field Name and Description
 
The response object from the browser.
 
HTTP status code of the response
 
Boolean indicating if the request returned successfully.
 
Boolean indicating if the requests time out was reached.
Fields borrowed from class glow.events.Event:
attachedTo
Method Summary
Method Attributes Method Name and Description
 
header(name)
Gets a header from the response.
 
json(safeMode)
Gets the body of the response as a JSON object.
 
Gets the body of the response as a glow.NodeList.
 
Gets the meaning of {@link glow.net.XhrResponse#status status}.
 
text()
Gets the body of the response as plain text
 
xml()
Gets the body of the response as xml
Methods borrowed from class glow.events.Event:
defaultPrevented, preventDefault
Class Detail
glow.net.XhrResponse()
The event object for glow.net.XhrRequest's 'load' & 'error' events.
Field Detail
{XMLHttpRequest} nativeResponse
The response object from the browser. This may not have the same properties and methods across user agents.

{number} status
HTTP status code of the response

{boolean} successful
Boolean indicating if the request returned successfully.

{boolean} timedOut
Boolean indicating if the requests time out was reached.
Method Detail
{string} header(name)
Gets a header from the response.
var contentType = myResponse.header("Content-Type");
Parameters:
{string} name
Header name
Returns:
{string} Header value

{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} statusText()
Gets the meaning of {@link glow.net.XhrResponse#status status}.
Returns:
{string}

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

{XML} xml()
Gets the body of the response as xml
Returns:
{XML}

Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:09 GMT+0100 (BST)
docs/symbols/glow.nodelist.html100644 0 0 433136 11405426572 14363 0ustar 0 0 JsDoc Reference - glow.NodeList

麻豆社

Class glow.NodeList


Defined in: core.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.NodeList(contents)
An array-like collection of DOM Nodes It is recommended to create a NodeList using the shortcut function glow.
Field Summary
Field Attributes Field Name and Description
 
Number of nodes in the NodeList
Method Summary
Method Attributes Method Name and Description
 
addClass(name)
Adds a class to each node.
 
after(nodes)
Insert node(s) after each node in this NodeList.
 
ancestors(filter)
Gets the unique ancestor nodes of each node as a new NodeList.
 
anim(duration, properties, opts)
Animate properties of elements All elements in the NodeList are animated All CSS values which are simple numbers (with optional unit) are supported.
 
append(nodes)
Appends node to each node in this NodeList.
 
appendTo(node)
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.
 
attr(name, value)
Gets or sets attributes.
 
before(nodes)
Insert node(s) before each node in this NodeList.
 
Gets the child elements of each node as a new NodeList.
 
Clones each node in the NodeList, along with data & event listeners
 
contains(Single)
Find if this NodeList contains the given element
 
copy()
Copies each node in the NodeList, excluding data & event listeners
 
css(property, value)
Get / set a CSS property value
 
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.
 
data(key, val)
Use this to safely attach arbitrary data to any DOM Element.
 
delegate(eventName, selector, callback, thisVal)
Listen for an event occurring on child elements matching a selector.
 
Removes each element from the document The element, attached listeners & attached data will be destroyed to free up memory.
 
detach(eventName, callback)
detach a listener from elements This will detach the listener from each dom node in the NodeList.
 
detachDelegate(eventName, selector, callback)
detach a delegated listener from elements This will detach the listener from each dom node in the NodeList.
 
each(callback)
Calls a function for each node in the list.
 
Removes the nodes' contents
 
eq(nodeList)
Compares this NodeList to another Returns true if both NodeLists contain the same items in the same order
 
fadeIn(duration, opts)
Fade elements in If the element is currently fading out, the fadeOut animation will be automatically stopped.
 
fadeOut(duration, opts)
Fade elements out If the element is currently fading in, the fadeIn animation will be automatically stopped.
 
fadeToggle(duration, opts)
Fade elements in/out If the element is currently fading in/out, the fadeIn/fadeOut animation will be automatically stopped.
 
filter(test)
Filter the NodeList
 
fire(eventName, event)
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.
<static>  
glow.NodeList.focusable(opts)
Manage a focusable element, or group of elements This method is a shortcut to glow.ui.Focusable and requires the 'ui' package to be loaded.
 
get(selector)
Gets decendents of nodes that match a CSS selector.
 
hasAttr(name)
Does the node have a particular attribute? The first node in this NodeList is tested.
 
hasClass(name)
Does the node have a particular class? The first node in this NodeList is tested.
 
height(height)
Gets / set element height Return value does not include the padding or border of the element in browsers supporting the correct box model.
 
hide()
Hides all items in the NodeList.
 
html(htmlString)
Gets / sets HTML content Either gets content of the first element, or sets the content for all elements in the list
 
insertAfter(nodes)
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.
 
insertBefore(nodes)
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.
 
is(selector)
Tests if the first element matches a CSS selector
 
item(index)
Get a single item from the list as an NodeList Negative numbers can be used to get items from the end of the list.
 
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.
 
next(search)
Gets the next sibling element for each node in the ElementList.
 
Gets the offset from the top left of the document.
 
on(eventName, callback, thisVal)
Listen for an event.
 
parent(search)
Gets the unique parent nodes of each node as a new NodeList.
 
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
 
prepend(nodes)
Prepends nodes to each node in this NodeList.
 
prependTo(node)
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.
 
prev(search)
Gets the previous sibling element for each node in the ElementList.
 
prop(name, value)
Gets or sets node properties.
 
push(nodes)
Adds nodes to the NodeList
 
queueAnim(duration, Properties, opts)
Queue an animation to run after the current animation All elements in the NodeList are animated This supports the same CSS properties as glow.NodeList#anim, but the animation is not started until the previous animation (added via {@link glow.NodeList#queueAnim queueAnim}) on that element ends.
 
Removes each element from the document If you no longer need the elements, consider using {@link glow.NodeList#destroy destroy}
 
removeAttr(name)
Removes an attribute from each node.
 
Removes a class from each node.
 
Removes data previously added by glow.NodeList#data from each node in this NodeList.
 
replaceWith(elements)
Replace elements with another
 
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.
 
scrollTop(val)
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.
 
show()
Shows all hidden items in the NodeList.
 
slice(start, end)
Get a section of an NodeList Operates in the same way as an Array's slice method
 
slideOpen(duration, opts)
Slide elements open This animates an element's height from its current height to its full auto-height size.
 
slideShut(duration, opts)
Slide elements shut This animates an element's height from its current height to zero.
 
slideToggle(duration, opts)
Slide elements open/shut If the element is currently sliding open/shut, the slideOpen/slideShut animation will be automatically stopped.
 
sort(func)
Sort the elements in the list.
 
text(text)
Gets / set the text content Either gets content of the first element, or sets the content for all elements in the list
 
Toggles a class on each node.
 
Removes the parent of each item in the list
 
val(value)
Gets or sets form values for the first node.
 
width(width)
Gets / set element width Return value does not include the padding or border of the element in browsers supporting the correct box model.
 
wrap(wrapper)
Wraps the given NodeList with the specified element(s).
Event Summary
Event Attributes Event Name and Description
 
keydown(event)
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.
 
keypress(event)
Fires when a key's command executes.
 
keyup(event)
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.
 
mouseenter(event)
Fires when the mouse enters the element specifically, does not bubble
 
mouseleave(event)
Fires when the mouse leaves the element specifically, does not bubble
Class Detail
glow.NodeList(contents)
An array-like collection of DOM Nodes It is recommended to create a NodeList using the shortcut function glow.
			// empty NodeList
			var myNodeList = glow();
			// using glow to return a NodeList then chaining methods
			glow('p').addClass('eg').append('
Hello!
');
			// creating an element from a string
			glow('
Hello!
').appendTo('body');
Parameters:
{string | glow.NodeList | Node | Node[] | Window} contents
Items to populate the NodeList with. This parameter will be passed to 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.
See:
Field Detail
{Number} length
Number of nodes in the NodeList
			// get the number of paragraphs on the page
			glow('p').length;
Method Detail
{glow.NodeList} addClass(name)
Adds a class to each node.
		glow("#login a").addClass("highlight");
Parameters:
{string} name
The name of the class to add.
Returns:
{glow.NodeList}

{glow.NodeList} after(nodes)
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.
			// adds a paragraph after each heading
			glow('h1, h2, h3').after('

That was a nice heading.

');
Parameters:
{string | HTMLElement | HTMLElement[] | glow.NodeList} nodes
Node(s) to insert Strings will be treated as HTML strings.
Returns:
{glow.NodeList} Original NodeList

{glow.dom.NodeList} ancestors(filter)
Gets the unique ancestor nodes of each node as a new NodeList.
		// get ancestor elements for anchor elements 
		var ancestors = glow.dom.get("a").ancestors();
Parameters:
{Function|string} filter Optional
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

{glow.anim.Anim} anim(duration, properties, opts)
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 glow.anim.Anim#prop.
			// 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
			});
			// Fade an element out and alert 'done' when complete
			glow('#nav').anim(3, {
				'opacity': 0
			}).on('complete', function() {
				alert('done!');
			});
			// Scroll the window to the top
			glow(window).anim(2, {
				scrollTop: 0
			});
Parameters:
{number} duration
Length of the animation in seconds.
{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.
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeBoth'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
{boolean} opts.destroyOnComplete Optional, Default: 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.
{boolean} opts.loop Optional, Default: true
Loop the animation.
{boolean} opts.startNow Optional, Default: true
Start the animation straight away? Animations can be started by calling glow.anim.Anim#start
Returns:
{glow.anim.Anim}
See:
- Queue an animation to run after the current anim
- Shortcut to fade elements in
- Shortcut to fade elements out
- Shortcut to toggle the fade of an element
- Shortcut to slide an element open
- Shortcut to slide an element shut
- Shortcut to toggle an element open / shut

{glow.NodeList} append(nodes)
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.
			// ends every paragraph with '...'
			glow('p').append('...');
Parameters:
{string | HTMLElement | HTMLElement[] | glow.NodeList} nodes
Nodes(s) to append Strings will be treated as HTML strings.
Returns:
{glow.NodeList} Original NodeList

{glow.NodeList} appendTo(node)
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.
			// appends '...' to every paragraph
			glow('...').appendTo('p');
Parameters:
{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.

{string | undefined | glow.NodeList} attr(name, value)
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).
		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"
		});
Parameters:
{string | Object} name
The name of the attribute, or an object of name/value pairs
{string} value Optional
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.

{glow.NodeList} before(nodes)
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.
			// adds a div before each paragraph
			glow('p').before('
Here comes a paragraph!
');
Parameters:
{string | HTMLElement | HTMLElement[] | glow.NodeList} nodes
Node(s) to insert Strings will be treated as HTML strings.
Returns:
{glow.NodeList} Original NodeList

{glow.dom.NodeList} children()
Gets the child elements of each node as a new NodeList.
		// get all list items
		var items = glow.dom.get("ul, ol").children();
Returns:
{glow.dom.NodeList} Returns a new NodeList containing all the child nodes

{glow.NodeList} clone()
Clones each node in the NodeList, along with data & event listeners
			// get a copy of all heading elements
			var myClones = glow.get("h1, h2, h3, h4, h5, h6").clone();
Returns:
{glow.NodeList} Returns a new NodeList containing clones of all the nodes in the NodeList

{boolean} contains(Single)
Find if this NodeList contains the given element
Parameters:
{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.

{glow.NodeList} copy()
Copies each node in the NodeList, excluding data & event listeners
			// get a copy of all heading elements
			var myCopies = glow.get("h1, h2, h3, h4, h5, h6").copy();
Returns:
{glow.NodeList} Returns a new NodeList containing copies of all the nodes in the NodeList

{glow.NodeList | string} css(property, value)
Get / set a CSS property value
		// get value from first node
		glow('#subNav').css('display');
		// set left padding to 10px on all nodes
		glow('#subNav li').css('padding-left', '2em');
		// where appropriate, px is assumed when no unit is passed
		glow('#mainPromo').css('margin-top', 300);
		// 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'
		});
Parameters:
{string | Object} property
The CSS property name, or object of property-value pairs to set
{string | number} value Optional
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.

{glow.anim.Anim} currentAnim()
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.
			// stopping and clearing current animation queue.
			// The next animation created via queueAnim will start
			// immediately
			glow('#elementToAnimate').curentAnim().stop();
			// Is the element animating as part of queueAnim?
			glow('#elementToAnimate').curentAnim().playing; // true/false
Returns:
{glow.anim.Anim}

{Object} data(key, val)
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.
	
	glow("p").data("tea", "milky");
	var colour = glow("p").data("tea"); // milky
Parameters:
{string|Object} key Optional
The name of the value in glow's data store.
{Object} val Optional
The value you wish to associate with the given name.
Returns:
{Object} When setting a value this method can be chained, as in that case it will return itself.
See:
glow.NodeList#removeData
glow.NodeList#removeData

delegate(eventName, selector, callback, thisVal)
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.
			// 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.
			// 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');
			});
Parameters:
{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.
{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'.
{Function} callback
Function to call when the event fires. The callback is passed a single event object. The type of this object is glow.DomEvent unless otherwise stated.
{Object} thisVal Optional
Value of 'this' within the callback. By default, this is the dom node matched by 'selector'.
Returns:
this

destroy()
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.
			// destroy all links in the document
			glow("a").destroy();
Returns:
undefined

detach(eventName, callback)
detach a listener from elements This will detach the listener from each dom node in the NodeList.
			function clickListener(domEvent) {
				// ...
			}
			
			// adding listeners
			glow.get('a').on('click', clickListener);
			
			// removing listeners
			glow.get('a').detach('click', clickListener);
Parameters:
{String} eventName
Name of the event to detach the listener from
{Function} callback
Listener callback to detach
Returns:
this

detachDelegate(eventName, selector, callback)
detach a delegated listener from elements This will detach the listener from each dom node in the NodeList.
			function clickListener(domEvent) {
				// ...
			}
			
			// adding listeners
			glow.get('#nav').delegate('click', 'a', clickListener);
			
			// removing listeners
			glow.get('#nav').detachDelegate('click', 'a', clickListener);
Parameters:
{String} eventName
Name of the event to detach the listener from
{String} selector
CSS selector of child elements the listener is listening to
{Function} callback
Listener callback to detach
Returns:
this

{glow.NodeList} each(callback)
Calls a function for each node in the list.
			// 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);
			});
			// 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;
				}
			});
Parameters:
{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}

{glow.NodeList} empty()
Removes the nodes' contents
			// remove the contents of all textareas
			glow("textarea").empty();
Returns:
{glow.NodeList} Original nodes

{boolean} eq(nodeList)
Compares this NodeList to another Returns true if both NodeLists contain the same items in the same order
			// the following returns true
			glow('#blah').eq( document.getElementById('blah') );
Parameters:
{Node | Node[] | glow.NodeList} nodeList
The NodeList to compare to.
Returns:
{boolean}
See:
for testing if a NodeList item matches a selector

{glow.NodeList} fadeIn(duration, opts)
Fade elements in If the element is currently fading out, the fadeOut animation will be automatically stopped.
			// make a tooltip fade in & out
			var tooltip = glow('#emailTooltip');
			
			glow('#emailInput').on('focus', function() {
				tooltip.fadeIn();
			}).on('blur', function() {
				tooltip.fadeOut();
			});
Parameters:
{number} duration Optional, Default: 1
Duration in seconds
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeOut'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
Returns:
{glow.NodeList}

{glow.NodeList} fadeOut(duration, opts)
Fade elements out If the element is currently fading in, the fadeIn animation will be automatically stopped.
			// make a tooltip fade in & out
			var tooltip = glow('#emailTooltip');
			
			glow('#emailInput').on('focus', function() {
				tooltip.fadeIn();
			}).on('blur', function() {
				tooltip.fadeOut();
			});
Parameters:
{number} duration Optional, Default: 1
Duration in seconds
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeIn'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
Returns:
{glow.NodeList}

{glow.NodeList} fadeToggle(duration, opts)
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.
			// make a tooltip fade in & out
			var tooltip = glow('#emailTooltip');
			
			glow('#toggleTooltip').on('click', function() {
				tooltip.fadeToggle();
			});
Parameters:
{number} duration Optional, Default: 1
Duration in seconds
{Object} opts Optional
Options object
{function|string} opts.tween Optional
The motion of the animation. Strings are treated as properties of 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}

{glow.NodeList} filter(test)
Filter the NodeList
			// return images with a width greater than 320
			glow("img").filter(function () {
				return glow(this).width() > 320;
			});
			// Get items that don't have an alt attribute
			myElementList.filter(':not([alt])');
Parameters:
{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

fire(eventName, event)
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.
		   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');
Parameters:
{String} eventName
Name of the event to fire
{glow.events.Event} 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

<static> {glow.ui.Focusable} glow.NodeList.focusable(opts)
Manage a focusable element, or group of elements This method is a shortcut to 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.
Defined in: ui.js.
Parameters:
{object} opts Optional
Options The same options as the glow.ui.Focusable constructor
Returns:
{glow.ui.Focusable}

{glow.NodeList} get(selector)
Gets decendents of nodes that match a CSS selector.
		// create a new NodeList
		var myNodeList = glow.dom.create("");

		// get 'a' tags that are decendants of the NodeList nodes
		myNewNodeList = myNodeList.get("a");
Parameters:
{String} selector
CSS selector
Returns:
{glow.NodeList} Returns a new NodeList containing matched elements

{boolean|undefined} hasAttr(name)
Does the node have a particular attribute? The first node in this NodeList is tested.
		if ( glow("#myImg").hasAttr("alt") ){
			// ...
		}
Parameters:
{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.

{boolean} hasClass(name)
Does the node have a particular class? The first node in this NodeList is tested.
		if ( glow("#myInput").hasClass("errored") ){
			// ...
		}
Parameters:
{string} name
The name of the class to test for.
Returns:
{boolean}

{glow.NodeList | number} height(height)
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.
		// get the height of #myDiv
		glow("#myDiv").height();
		// set the height of list items in #myList to 200 pixels
		glow("#myList > li").height(200);
		// get the height of the document
		glow(document).height();
		// get the height of the window
		glow(win).height();
Parameters:
{Number} height Optional
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.

{glow.NodeList} hide()
Hides all items in the NodeList.
		// Hides all list items within #myList
		glow("#myList li").hide();
Returns:
{glow.NodeList}

{glow.NodeList | string} html(htmlString)
Gets / sets HTML content Either gets content of the first element, or sets the content for all elements in the list
			// get the html in #footer
			var footerContents = glow("#footer").html();
			// set a new footer
			glow("#footer").html("Hello World!");
Parameters:
{String} htmlString Optional
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.

{glow.NodeList} insertAfter(nodes)
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.
			// adds a paragraph after each heading
			glow('

HAI!

').insertAfter('h1, h2, h3');
Parameters:
{string | HTMLElement | HTMLElement[] | glow.NodeList} nodes
Node(s) to insert after Strings will be treated as CSS selectors.
Returns:
{glow.NodeList} Inserted nodes.

{glow.NodeList} insertBefore(nodes)
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.
			// adds a div before each paragraph
			glow('
Here comes a paragraph!
').insertBefore('p');
Parameters:
{string | HTMLElement | HTMLElement[] | glow.NodeList} nodes
Node(s) to insert before Strings will be treated as CSS selectors.
Returns:
{glow.NodeList} Inserted nodes.

{boolean} is(selector)
Tests if the first element matches a CSS selector
			if ( myNodeList.is(':visible') ) {
				// ...
			}
Parameters:
{string} selector
CSS selector
Returns:
{boolean}

{glow.NodeList} item(index)
Get a single item from the list as an NodeList Negative numbers can be used to get items from the end of the list.
			// get the html from the fourth element
			myNodeList.item(3).html();
			// add a class name to the last item
			myNodeList.item(-1).addClass('last');
Parameters:
{number} index
The numeric index of the node to return.
Returns:
{glow.NodeList} A new NodeList containing a single item

{glow.anim.Anim} lastQueuedAnim()
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}

{glow.ElementList} next(search)
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.
		// gets the element following #myLink (if there is one)
		var next = glow.get("#myLink").next();
		// get the next sibling link element after #skipLink
		glow.get('#skipLink').next('a')
Parameters:
{string | HTMLElement | NodeList} search Optional
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.

{Object} offset()
Gets the offset from the top left of the document. If the NodeList contains multiple items, the offset of the first item is returned.
		glow("#myDiv").offset().top
Returns:
{Object} Returns an object with "top" & "left" properties in pixels

on(eventName, callback, thisVal)
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 glow.NodeList#delegate.
		   glow.get('#testLink').on('click', function(domEvent) {
			   // do stuff
			   
			   // if you want to cancel the default action (following the link)...
			   return false;
		   });
Parameters:
{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.
{Function} callback
Function to call when the event fires. The callback is passed a single event object. The type of this object is glow.DomEvent unless otherwise stated.
{Object} thisVal Optional
Value of 'this' within the callback. By default, this is the dom node being listened to.
Returns:
this

{glow.NodeList} parent(search)
Gets the unique parent nodes of each node as a new NodeList.
		// elements which contain links
		var parents = glow.dom.get("a").parent();
Parameters:
{string | HTMLElement | NodeList} search Optional
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

{Object} position()
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
		// get the top distance from the positioned parent
		glow("#elm").position().top
Returns:
{Object} An object with 'top' and 'left' number properties

{glow.NodeList} prepend(nodes)
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.
			// prepends every paragraph with 'Paragraph: '
			glow('p').prepend('Paragraph: ');
Parameters:
{string | HTMLElement | HTMLElement[] | glow.NodeList} nodes
Nodes(s) to prepend Strings will be treated as HTML strings.
Returns:
{glow.NodeList} Original NodeList

{glow.NodeList} prependTo(node)
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.
			// prepends 'Paragraph: ' to every paragraph
			glow('Paragraph: ').prependTo('p');
Parameters:
{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.

{glow.ElementList} prev(search)
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.
		// gets the element before #myLink (if there is one)
		var next = glow.get("#myLink").prev();
		// get the previous sibling link element before #skipLink
		glow.get('#skipLink').prev('a')
Parameters:
{string | HTMLElement | NodeList} search Optional
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.

{string | glow.NodeList} prop(name, value)
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.
		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
		});
Parameters:
{string | Object} name
The name of the property, or an object of name/value pairs
{string} value Optional
The value to set the property to.
Returns:
{string | glow.NodeList} When setting properties it returns the NodeList, otherwise returns the property value.

{glow.NodeList} push(nodes)
Adds nodes to the NodeList
			myNodeList.push('
Foo
').push('h1');
Parameters:
{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}

{glow.NodeList} queueAnim(duration, Properties, opts)
Queue an animation to run after the current animation All elements in the NodeList are animated This supports the same CSS properties as 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.
			// 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'
				});
			});
			// adding listeners to a queued anim
			glow('#elementToAnimate').queueAnim(0.5, {
				height: 0
			}).lastQueuedAnim().on('complete', function() {
				alert('Animation complete!');
			});
			// stopping and clearing current animation queue.
			// The next animation created via queueAnim will start
			// immediately
			glow('#elementToAnimate').curentAnim().stop();
Parameters:
{number} duration
Length of the animation in seconds.
{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.
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeBoth'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
{boolean} opts.destroyOnComplete Optional, Default: 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}
See:
- Shortcut to fade elements in
- Shortcut to fade elements out
- Shortcut to toggle the fade of an element
- Shortcut to slide an element open
- Shortcut to slide an element shut
- Shortcut to toggle an element open / shut

{glow.NodeList} remove()
Removes each element from the document If you no longer need the elements, consider using {@link glow.NodeList#destroy destroy}
			// take all the links out of a document
			glow("a").remove();
Returns:
{glow.NodeList} The removed elements

{glow.NodeList} removeAttr(name)
Removes an attribute from each node.
		glow("a").removeAttr("target");
Parameters:
{string} name
The name of the attribute to remove.
Returns:
{glow.NodeList}

{glow.NodeList} removeClass(name)
Removes a class from each node.
		glow("#footer #login a").removeClass("highlight");
Parameters:
{string} name
The name of the class to remove.
Returns:
{glow.NodeList}

removeData(key)
Removes data previously added by 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.
Parameters:
{string} key Optional
The name of the value in glow's data store.
See:
glow.NodeList#data

{glow.NodeList} replaceWith(elements)
Replace elements with another
Parameters:
{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.

{glow.NodeList | number} scrollLeft(val)
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.
		// get the scroll left value of #myDiv
		var scrollPos = glow('#myDiv').scrollLeft();
		// scrollPos is a number, eg: 45
		// set the scroll left value of #myDiv to 20
		glow('#myDiv').scrollLeft(20);
		// get the scrollLeft of the window
		glow(window).scrollLeft();
		// scrollPos is a number, eg: 45
Parameters:
{Number} val Optional
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.

{glow.NodeList | number} scrollTop(val)
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.
		// get the scroll top value of #myDiv
		var scrollPos = glow("#myDiv").scrollTop();
		// scrollPos is a number, eg: 45
		// set the scroll top value of #myDiv to 20
		glow("#myDiv").scrollTop(20);
		// get the scrollTop of the window
		glow(window).scrollTop();
		// scrollPos is a number, eg: 45
Parameters:
{Number} val Optional
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.

{glow.NodeList} show()
Shows all hidden items in the NodeList.
		// Show element with ID myDiv
		glow("#myDiv").show();
		// Show all list items within #myList
		glow("#myList li").show();
Returns:
{glow.NodeList}

{glow.NodeList} slice(start, end)
Get a section of an NodeList Operates in the same way as an Array's slice method
		var myNodeList = glow("

"); myNodeList.slice(1, 2); // selects the paragraph myNodeList.slice(-1); // same thing, selects the paragraph
Parameters:
{number} start
Start index If negative, it specifies a position measured from the end of the list
{number} end Optional
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

{glow.NodeList} slideOpen(duration, opts)
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.
			var menuContent = glow('#menu div.content');
			
			glow('#menu').on('mouseenter', function() {
				menuContent.slideOpen();
			}).on('mouseleave', function() {
				menuContent.slideShut();
			});
			glow('#furtherInfoHeading').on('click', function() {
				glow('#furtherInfoContent').slideOpen();
			});
			// add content onto an element, and slide to reveal the new content
			glow('
' + newContent + '
').appendTo('#content').height(0).slideOpen();
Parameters:
{number} duration Optional, Default: 1
Duration in seconds
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeBoth'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
{boolean} opts.lockToBottom Optional, Default: 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}

{glow.NodeList} slideShut(duration, opts)
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.
			var menuContent = glow('#menu div.content');
			
			glow('#menu').on('mouseenter', function() {
				menuContent.slideOpen();
			}).on('mouseleave', function() {
				menuContent.slideShut();
			});
Parameters:
{number} duration Optional, Default: 1
Duration in seconds
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeBoth'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
{boolean} opts.lockToBottom Optional, Default: 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}

{glow.NodeList} slideToggle(duration, opts)
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.
			var menuContent = glow('#menuContent');
			
			glow('#toggleMenu').on('click', function() {
				menuContent.slideToggle();
			});
Parameters:
{number} duration Optional, Default: 1
Duration in seconds
{Object} opts Optional
Options object
{function|string} opts.tween Optional, Default: 'easeBoth'
The motion of the animation. Strings are treated as properties of glow.tweens, although a tween function can be provided.
{boolean} opts.lockToBottom Optional, Default: 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}

{glow.NodeList} sort(func)
Sort the elements in the list. Items will already be in document order if a CSS selector was used to fetch them.
			//get links in alphabetical (well, lexicographical) order
			var links = glow("a").sort(function(elementA, elementB) {
				return glow(elementA).text() < glow(elementB).text() ? -1 : 1;
			})
Parameters:
{Function} func Optional
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

{glow.NodeList | String} text(text)
Gets / set the text content Either gets content of the first element, or sets the content for all elements in the list
			// set text
			var div = glow("
").text("Fun & games!"); //
Func & games!
			// get text
			var mainHeading = glow('#mainHeading').text();
Parameters:
{String} text Optional
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.

{glow.NodeList} toggleClass(name)
Toggles a class on each node.
		glow(".onOffSwitch").toggleClass("on");
Parameters:
{string} name
The name of the class to toggle.
Returns:
{glow.NodeList}

{glow.NodeList} unwrap()
Removes the parent of each item in the list
			// Before: 

Hello

// unwrap the given element glow("#mySpan").unwrap(); // After:
Hello
Returns:
{glow.NodeList} The now unwrapped elements

{glow.NodeList | string | Object} val(value)
Gets or sets form values for the first node. The returned value depends on the type of element, see below:
Radio button or checkbox
If checked, then the contents of the value property, otherwise an empty string.
Select
The contents of value property of the selected option
Select (multiple)
An array of selected option values.
Other form elements
The value of the input.
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.
		// get a value from an input with the id 'username'
		var username = glow("#username").val();
		
		// get values from a form
		var userDetails = glow("form").val();
		// set a value
		glow("#username").val("example username");
		// set values in a form
		glow("form").val({
			username : "another",
			name     : "A N Other"
		});
Parameters:
{string | Object} value Optional
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.

{glow.NodeList | number} width(width)
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.
		// get the width of #myDiv
		glow("#myDiv").width();
		// set the width of list items in #myList to 200 pixels
		glow("#myList > li").width(200);
		// get the width of the document
		glow(document).width();
		// get the width of the window
		glow(window).width();
Parameters:
{Number} width Optional
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.

{glow.NodeList} wrap(wrapper)
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.
			// Hello
			glow("#mySpan").wrap("

"); // Makes: //
//

// Hello //

//
Parameters:
{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
Event Detail
keydown(event)
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.
Parameters:
{glow.events.KeyboardEvent} event
Event Object

keypress(event)
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.
Parameters:
{glow.events.KeyboardEvent} event
Event Object

keyup(event)
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.
Parameters:
{glow.events.KeyboardEvent} event
Event Object

mouseenter(event)
Fires when the mouse enters the element specifically, does not bubble
Parameters:
{glow.events.DomEvent} event
Event Object

mouseleave(event)
Fires when the mouse leaves the element specifically, does not bubble
Parameters:
{glow.events.DomEvent} event
Event Object

Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/symbols/glow.tweens.html100644 0 0 57554 11405426572 14035 0ustar 0 0 JsDoc Reference - glow.tweens

麻豆社

Namespace glow.tweens


Defined in: core.js.

Namespace Summary
Constructor Attributes Constructor Name and Description
 
Functions for controlling the motion of an animation
Method Summary
Method Attributes Method Name and Description
<static>  
glow.tweens.bounceIn()
Returns the reverse of {@link glow.tweens.bounceOut bounceOut}
<static>  
glow.tweens.bounceOut()
Returns a tween which bounces against the final value 3 times before stopping
<static>  
glow.tweens.combine(tweenIn, tweenOut)
Create a tween from two tweens.
<static>  
glow.tweens.easeBoth(strength)
Creates a tween which starts off slowly, accelerates then decelerates after the half way point.
<static>  
glow.tweens.easeIn(strength)
Creates a tween which starts off slowly and accelerates.
<static>  
glow.tweens.easeOut(strength)
Creates a tween which starts off fast and decelerates.
<static>  
glow.tweens.elasticIn(amplitude, frequency)
Returns the reverse of {@link glow.tweens.elasticOut elasticOut}
<static>  
glow.tweens.elasticOut(amplitude, frequency)
Creates a tween which has an elastic movement.
<static>  
glow.tweens.linear()
Creates linear tween.
<static>  
glow.tweens.overshootBoth(amount)
Returns a combination of {@link glow.tweens.overshootIn overshootIn} and {@link glow.tweens.overshootOut overshootOut}
<static>  
glow.tweens.overshootIn(amount)
Returns the reverse of {@link glow.tweens.overshootOut overshootOut}
<static>  
glow.tweens.overshootOut(amount)
Creates a tween which overshoots its end point then returns to its end point.
Namespace Detail
glow.tweens
Functions for controlling the motion of an animation
Method Detail
<static> {function} glow.tweens.bounceIn()
Returns the reverse of {@link glow.tweens.bounceOut bounceOut}
Returns:
{function}

<static> {function} glow.tweens.bounceOut()
Returns a tween which bounces against the final value 3 times before stopping
Returns:
{function}

<static> {function} glow.tweens.combine(tweenIn, tweenOut)
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.
		// 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()
		);
Parameters:
{function} tweenIn
Tween to use for the first half
{function} tweenOut
Tween to use for the second half
Returns:
{function}

<static> {function} glow.tweens.easeBoth(strength)
Creates a tween which starts off slowly, accelerates then decelerates after the half way point. This produces a smooth and natural looking transition.
Parameters:
{number} strength Optional, Default: 2
How strong the easing is. A higher number produces a greater difference between start/end speed and the mid speed.
Returns:
{function}

<static> {function} glow.tweens.easeIn(strength)
Creates a tween which starts off slowly and accelerates.
Parameters:
{number} strength Optional, Default: 2
How strong the easing will be. The higher the number the slower the animation starts and the quicker it ends.
Returns:
{function}

<static> {function} glow.tweens.easeOut(strength)
Creates a tween which starts off fast and decelerates.
Parameters:
{number} strength Optional, Default: 2
How strong the easing will be. The higher the number the quicker the animation starts and the slower it ends.
Returns:
{function}

<static> {function} glow.tweens.elasticIn(amplitude, frequency)
Returns the reverse of {@link glow.tweens.elasticOut elasticOut}
Parameters:
{number} amplitude Optional, Default: 1
How strong the elasticity will be.
{number} frequency Optional, Default: 3.33
The frequency.
Returns:
{function}

<static> {function} glow.tweens.elasticOut(amplitude, frequency)
Creates a tween which has an elastic movement. You can tweak the tween using the parameters but you'll probably find the defaults sufficient.
Parameters:
{number} amplitude Optional, Default: 1
How strong the elasticity is.
{number} frequency Optional, Default: 3.33
The frequency.
Returns:
{function}

<static> {function} glow.tweens.linear()
Creates linear tween. Will transition values from start to finish with no acceleration or deceleration.
Returns:
{function}

<static> {function} glow.tweens.overshootBoth(amount)
Returns a combination of {@link glow.tweens.overshootIn overshootIn} and {@link glow.tweens.overshootOut overshootOut}
Parameters:
{number} amount Optional, Default: 1.70158
How much to overshoot. The default is 1.70158 which results in a 10% overshoot.
Returns:
{function}

<static> {function} glow.tweens.overshootIn(amount)
Returns the reverse of {@link glow.tweens.overshootOut overshootOut}
Parameters:
{number} amount Optional, Default: 1.70158
How much to overshoot. The default is 1.70158 which results in a 10% overshoot.
Returns:
{function}

<static> {function} glow.tweens.overshootOut(amount)
Creates a tween which overshoots its end point then returns to its end point.
Parameters:
{number} amount Optional, Default: 1.70158
How much to overshoot. The default is 1.70158 which results in a 10% overshoot.
Returns:
{function}

Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/symbols/glow.ui.autosuggest.html100644 0 0 117764 11405426572 15536 0ustar 0 0 JsDoc Reference - glow.ui.AutoSuggest

麻豆社

Class glow.ui.AutoSuggest


Extends glow.ui.Widget.

Defined in: ui.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
Create a menu that displays results filtered by a search term.
Field Summary
Field Attributes Field Name and Description
 
The options object passed into #bindInput, with defaults added.
 
The focusable linked to this autosuggest.
 
Refers to the input element to which this is linked to, or an empty NodeList.
 
The overlay linked to this autosuggest.
Fields borrowed from class glow.ui.Widget:
container, content, phase
Method Summary
Method Attributes Method Name and Description
 
bindInput(input, opts)
Link this autosuggest to a text input.
 
data(data)
Set the data or datasource to search.
 
Destroy the AutoSuggest.
 
disabled(newState)
Enable/disable the AutoSuggest, or get the disabled state When the AutoSuggest is disabled it is not shown.
 
find(str)
Search the datasource for a given string This fetches results from the datasource and displays them.
 
hide()
Clear the results so the AutoSuggest is no longer visible
 
setFilter(filter)
Set the function used to filter the dataset for results.
 
setFormat(formatter)
Control how matches are output.
Methods borrowed from class glow.events.Target:
detach, fire, on
Event Summary
Event Attributes Event Name and Description
 
data(event)
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.
 
find(event)
Fired when a search starts.
 
results(event)
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.
 
select(event)
Fired when an item in the AutoSuggest is selected.
Events borrowed from class glow.ui.Widget:
destroy, disable
Class Detail
glow.ui.AutoSuggest(opts)
Create a menu that displays results filtered by a search term. This widget can be easily linked to a text input via 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}
			// 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');
			// 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');
			// 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;
			});
Parameters:
{Object} opts Optional
Options
{number} opts.width Optional
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.
{number} opts.maxResults Optional
Limit the number of results to display.
{number} opts.minLength Optional, Default: 3
Minimum number of chars before search is executed This prevents searching being performed until a specified amount of chars have been entered.
{boolean} opts.caseSensitive Optional, Default: 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.
{boolean} opts.activateFirst Optional, Default: true
Activate the first item when results appear? If false, results with be shown with no active item.
{function|string} opts.keyboardNav Optional, Default: 'arrow-y'
Alter the default keyboard behaviour. This is the same as keyboardNav in glow.ui.Focusable.
Field Detail
{Object} bindOpts
The options object passed into #bindInput, with defaults added.

{glow.ui.Focusable} focusable
The focusable linked to this autosuggest.

{glow.NodeList} input
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}.

{glow.ui.Overlay} overlay
The overlay linked to this autosuggest. The Overlay is created when {@link glow.ui.AutoSuggest#bindInput bindInput} is called.
Method Detail
bindInput(input, opts)
Link this autosuggest to a text input. This triggers 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.
Parameters:
{selector|glow.NodeList|HTMLElement} input
Test input element
{Object} opts Optional
Options
{selector|glow.NodeList} opts.appendTo Optional
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.
{boolean} opts.autoPosition Optional, Default: 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.
{boolean} opts.autoComplete Optional, Default: 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}
{string} opts.delim Optional
Delimiting char(s) for selections. When defined, the input text will be treated as multiple values, separated by this string (with surrounding spaces ignored).
{number} opts.delay Optional, Default: 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.
{string} opts.anim Optional
Animate the Overlay when it shows/hides. This can be any parameter accepted by {@link glow.ui.Overlay#setAnim Overlay#setAnim}.
{string} opts.loadingClass Optional
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

data(data)
Set the data or datasource to search. This gives the AutoSuggest the data to search, or the means to fetch the data to search.
			// providing a URL
			myAutoSuggest.data('/search?text={val}');
			// providing an array of program names
			myAutoSuggest.data( ['Doctor Who', 'Eastenders', 'The Thick of it', 'etc etc'] );
			// providing an object of user data
			myAutoSuggest.data([
				{name='JaffaTheCake', fullName:'Jake Archibald', photo:'/glow/download/release/JaffaTheCake.jpg'},
				{name='Bobby', fullName:'Robert Cackpeas', photo:'Bobby.jpg'}
				...
			]);
			// 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);
					})
			});
Parameters:
{string|string[]|Object[]|glow.net.Response|function} data
Data or datasource.

String URL

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 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).

glow.net.XhrResponse

This will be treated as a json response and decoded to string[] or Object[], see below.

string[] or Object[] dataset

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.

function

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.
Returns:
this

destroy()
Destroy the AutoSuggest. Removes all events that cause the AutoSuggest to run. The input element will remain on the page.

{glow.ui.AutoSuggest|boolean} disabled(newState)
Enable/disable the AutoSuggest, or get the disabled state When the AutoSuggest is disabled it is not shown.
Parameters:
{boolean} newState Optional
Disable the AutoSuggest? 'false' will enable a disabled AutoSuggest.
Returns:
{glow.ui.AutoSuggest|boolean} Returns boolean when getting, AutoSuggest when setting

find(str)
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.
Parameters:
{string} str
String to search for {@link glow.ui.AutoSuggest#filter AutoSuggest#filter} is used to determine whether results match or not.
Returns:
this

hide()
Clear the results so the AutoSuggest is no longer visible
Returns:
this

setFilter(filter)
Set the function used to filter the dataset for results. Overwrite this to change the filtering behaviour.
			// 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;
			});
			// 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;
			});
Parameters:
{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.
Returns:
this

setFormat(formatter)
Control how matches are output.
			// A username auto-complete
			
			// The data url returns a JSON object like [{name='JaffaTheCake', fullName:'Jake Archibald', photo:'/glow/download/release/JaffaTheCake.jpg'}, ...]
			glow.ui.AutoSuggest().setFormat(function() {
				// Format the results like  Jake Archibald (JaffaTheCake)
				return ' ' + data.fullName + ' (' + data.name + ')';
			}).data('userSearch.php?usernamePartial={val}').linkToInput('#username');
Parameters:
{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
Event Detail
data(event)
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.
			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() );
			});
Parameters:
{glow.events.Event} event
Event Object
{*} 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.

find(event)
Fired when a search starts. Cancel this event to prevent the search.
Parameters:
{glow.events.Event} event
Event Object.
{string} event.val
The search string. You can set this to another value if you wish.

results(event)
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.
			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
			});
Parameters:
{glow.events.Event} event
Event Object
{string[]|Object[]} event.results
The filtered dataset You can modify / overwrite this property to alter the results

select(event)
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.
			myAutoSuggest.on('select', function(event) {
				// this assumes our data objects have a 'url' property
				loaction.href = event.item.url;
			});
Parameters:
{glow.events.Event} event
Event Object
{string|Object} event.item
The item in the dataset that was selected
{glow.NodeList} event.li
The list item in the AutoSuggest that was selected

Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/symbols/glow.ui.behaviour.html100644 0 0 24515 11405426572 15117 0ustar 0 0 JsDoc Reference - glow.ui.Behaviour

麻豆社

Class glow.ui.Behaviour


Extends glow.events.Target.

Defined in: ui.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
Abstract behaviour class.
Method Summary
Method Attributes Method Name and Description
 
Removes the behaviour & event listeners
 
enabled(state)
Get/set the enabled state
Methods borrowed from class glow.events.Target:
detach, fire, on
Class Detail
glow.ui.Behaviour(name)
Abstract behaviour class.
Parameters:
{string} name
The name of this widget. This is added to class names in the generated DOM nodes that wrap the widget interface.
Method Detail
destroy()
Removes the behaviour & event listeners

enabled(state)
Get/set the enabled state
Parameters:
{boolean} state Optional, Default: true

Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
docs/symbols/glow.ui.carousel.html100644 0 0 66533 11405426572 14756 0ustar 0 0 JsDoc Reference - glow.ui.Carousel

麻豆社

Class glow.ui.Carousel


Extends glow.ui.Widget.

Defined in: ui.js.

Class Summary
Constructor Attributes Constructor Name and Description
 
glow.ui.Carousel(itemContainer, opts)
Create a pane of elements that scroll from one to another.
Field Summary
Field Attributes Field Name and Description
 
Parent element of the carousel items.
 
Carousel items.
Fields borrowed from class glow.ui.Widget:
container, content, phase
Method Summary
Method Attributes Method Name and Description
 
addPageNav(opts)
Add page navigation to the carousel.
 
Remove listeners and styles from this instance.
 
moveTo(itemIndex, animate)
Move the items so a given index is in the spotlight.
 
next()
Move to the next page/item
 
prev()
Move to the previous page/item
 
Gets an array of spotlighted indexes.
 
Get the currently spotlighted items.
Methods borrowed from class glow.ui.Widget:
disabled
Methods borrowed from class glow.events.Target:
detach, fire, on
Event Summary
Event Attributes Event Name and Description
 
afterMove(event)
Fires when the carousel has finished moving.
 
move(event)
Fires when the carousel is about to move.
 
select(event)
Fires when a carousel item is selected.
Events borrowed from class glow.ui.Widget:
destroy, disable
Class Detail
glow.ui.Carousel(itemContainer, opts)
Create a pane of elements that scroll from one to another.
			// This creates a carousel out of HTML like...
			// 
    //
  1. // // // //
  2. // ...more list items like above... var myCarousel = new glow.ui.Carousel('#carouselItems', { loop: true, page: true, });
			// Make a carousel of thumbnails, which show the full image when clicked.
			// Carousel items look like this...
			// 
  • // // whatever // //
  • 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; });
    Parameters:
    {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.
    {object} opts Optional
    Options
    {number} opts.duration Optional, Default: 0.2
    Duration of scrolling animations in seconds.
    {string|function} opts.tween Optional, Default: 'easeBoth'
    Tween to use for animations. This can be a property name of glow.tweens or a tweening function.
    {boolean|number} opts.page Optional, Default: 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.
    {boolean} opts.loop Optional, Default: false
    Loop the carousel from the last item to the first.
    {number} opts.spotlight Optional
    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.
    Field Detail
    {glow.NodeList} itemContainer
    Parent element of the carousel items.

    {glow.NodeList} items
    Carousel items.
    Method Detail
    addPageNav(opts)
    Add page navigation to the carousel. The page navigation show the user which position they are viewing within the carousel.
    			new glow.ui.Carousel('#carouselContainer').addPageNav({
    				position: 'belowMiddle',
    				useNumbers: true
    			});
    Parameters:
    {Object} opts Optional
    Options object.
    {string|selector|HTMLElement} opts.position Optional, Default: 'belowLast'
    The position of the page navigation. This can be a CSS selector pointing to an element, or one of the following shortcuts:
    belowLast
    Display the nav beneath the last spotlight item
    belowNext
    Display the nav beneath the next button
    belowMiddle
    Display the nav beneath the carousel, centred
    aboveLast
    Display the nav above the last spotlight item
    aboveNext
    Display the nav above the next button
    aboveMiddle
    Display the nav above the carousel, centred
    {boolean} opts.useNumbers Optional, Default: false
    Display as numbers rather than blocks.
    Returns:
    this

    destroy()
    Remove listeners and styles from this instance. Carousel items will not be destroyed.
    Returns:
    undefined

    moveTo(itemIndex, animate)
    Move the items so a given index is in the spotlight.
    Parameters:
    {number} itemIndex
    Item index to move to.
    {boolean} animate Optional, Default: true
    Transition to the item. If false, the carousel will switch to the new index.
    Returns:
    this

    next()
    Move to the next page/item
    Returns:
    this

    prev()
    Move to the previous page/item
    Returns:
    this

    {number[]} spotlightIndexes()
    Gets an array of spotlighted indexes. These are the indexes of the nodes within glow.ui.Carousel#items.
    Returns:
    {number[]}

    {glow.NodeList} spotlightItems()
    Get the currently spotlighted items.
    Returns:
    {glow.NodeList}
    Event Detail
    afterMove(event)
    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.
    			// double the amount a carousel will move by
    			myCarousel.on('afterMove', function(e) {
    				// show content related to this.spotlightIitems()[0]
    			});
    Parameters:
    {glow.events.Event} event
    Event Object

    move(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object
    {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]`.

    select(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object
    {glow.NodeList} event.item
    Item selected
    {number} event.itemIndex
    The index of the selected item in glow.ui.Carousel#items.

    Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
    docs/symbols/glow.ui.carouselpane.html100644 0 0 74444 11405426572 15622 0ustar 0 0 JsDoc Reference - glow.ui.CarouselPane

    麻豆社

    Class glow.ui.CarouselPane


    Extends glow.ui.Widget.

    Defined in: ui.js.

    Class Summary
    Constructor Attributes Constructor Name and Description
     
    glow.ui.CarouselPane(container, opts)
    Create a pane of elements that scroll from one to another.
    Field Summary
    Field Attributes Field Name and Description
     
    Carousel items.
     
    The container passed in to the constructor for glow.ui.CarouselPane.
    Fields borrowed from class glow.ui.Widget:
    container, content, phase
    Method Summary
    Method Attributes Method Name and Description
     
    Remove listeners and added HTML Elements from this instance.
     
    moveBy(amount)
    Move by a number of items.
     
    moveStart(backwards)
    Start moving the carousel in a particular direction.
     
    Stop moving the carousel.
     
    moveTo(itemIndex, opts)
    Move the items so a given index is the leftmost active item.
     
    moveToggle(backwards)
    If this CarouselPane is currently moving via moveStart, will call moveStop, otherwise will call moveStart.
     
    next()
    Move forward by the step.
     
    prev()
    Move backward by the step.
     
    Gets an array of spotlighted indexes.
     
    Get the currently spotlighted items.
    Methods borrowed from class glow.ui.Widget:
    disabled
    Methods borrowed from class glow.events.Target:
    detach, fire, on
    Event Summary
    Event Attributes Event Name and Description
     
    Fires when the carousel has finished moving.
     
    move(e)
    Fires when the carousel is about to move.
     
    select(event)
    Fires when a carousel item is selected.
    Events borrowed from class glow.ui.Widget:
    destroy, disable
    Class Detail
    glow.ui.CarouselPane(container, opts)
    Create a pane of elements that scroll from one to another. This is a component of Carousel.
    			new glow.ui.CarouselPane('#carouselItems', {
    				duration: 0.4,
    				step: 2,
    				loop: true
    			});
    Parameters:
    {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.
    {object} opts Optional
    Options
    {number} opts.duration Optional, Default: 0.2
    Duration of scrolling animations in seconds.
    {string|function} opts.tween Optional, Default: 'easeBoth'
    Tween to use for animations. This can be a property name of glow.tweens or a tweening function.
    {boolean | number} opts.step Optional, Default: 1
    Number of items to move at a time. If true, the step will be the same size as the spotlight.
    {boolean} opts.loop Optional, Default: false
    Loop the carousel from the last item to the first.
    {boolean} opts.page Optional, Default: 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.
    {number} opts.spotlight Optional
    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.
    Field Detail
    {glow.NodeList} items
    Carousel items. This is the same as `myCarouselPane.stage.children()`

    {glow.NodeList} stage
    The container passed in to the constructor for glow.ui.CarouselPane.
    Method Detail
    destroy()
    Remove listeners and added HTML Elements from this instance. CarouselPane items will not be destroyed.
    Returns:
    undefined

    moveBy(amount)
    Move by a number of items.
    Parameters:
    {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

    moveStart(backwards)
    Start moving the carousel in a particular direction.
    			nextBtn.on('mousedown', function() {
    				myCarouselPane.moveStart();
    			}).on('mouseup', function() {
    				myCarouselPane.moveStop();
    			});
    Parameters:
    {boolean} backwards Optional, Default: false
    True to move backwards, otherwise move forwards.
    Returns:
    this
    See:
    glow.ui.CarouselPane#moveStop

    moveStop()
    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

    moveTo(itemIndex, opts)
    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.
    Parameters:
    {number} itemIndex
    Item index to move to.
    opts
    {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.
    Returns:
    this

    moveToggle(backwards)
    If this CarouselPane is currently moving via moveStart, will call moveStop, otherwise will call moveStart.
    Parameters:
    {boolean} backwards Optional, Default: false
    When calling moveStart, move backwards?
    Returns:
    this

    next()
    Move forward by the step.
    Returns:
    this

    prev()
    Move backward by the step.
    Returns:
    this

    {number[]} spotlightIndexes()
    Gets an array of spotlighted indexes. These are the indexes of the nodes within glow.ui.CarouselPane#items. Only item indexes currently visible in the spotlight will be included.
    Returns:
    {number[]}

    {glow.NodeList} spotlightItems()
    Get the currently spotlighted items. Only items currently visible in the spotlight will be included.
    Returns:
    {glow.NodeList}
    Event Detail
    afterMove(e)
    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.
    			// double the amount a carousel will move by
    			myCarouselPane.on('afterMove', function(e) {
    				// show content related to this.spotlightItems()[0]
    			});
    Parameters:
    {glow.events.Event} e
    Event Object
    {number} e.currentIndex
    Index of the current leftmost item.

    move(e)
    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.
    			// double the amount a carousel will move by
    			myCarouselPane.on('move', function(e) {
    				e.moveBy *= 2;
    			});
    Parameters:
    {glow.events.Event} e
    Event Object
    {number} e.currentIndex
    Index of the current leftmost item.
    {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.

    select(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object
    {glow.NodeList} event.item
    Item selected
    {number} event.itemIndex
    The index of the selected item in glow.ui.CarouselPane#items.

    Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
    docs/symbols/glow.ui.focusable.html100644 0 0 73727 11405426572 15107 0ustar 0 0 JsDoc Reference - glow.ui.Focusable

    麻豆社

    Class glow.ui.Focusable


    Extends glow.ui.Behaviour.

    Defined in: ui.js.

    Class Summary
    Constructor Attributes Constructor Name and Description
     
    glow.ui.Focusable(container, opts)
    Manage a focusable element, or group of elements This can be used to create a single focus point for a set of focusable elements.
    Field Summary
    Field Attributes Field Name and Description
     
    The active child item.
     
    The index of the active child item in glow.ui.Focusable#children.
     
    NodeList of child items that are managed by this Focusable.
     
    Focusable container
    Method Summary
    Method Attributes Method Name and Description
     
    active(toActivate)
    Get/set the active state of the Focusable Call without arguments to get the active state.
     
    Destroy the Focusable This removes all focusable behaviour from the continer and child items.
     
    disabled(newState)
    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.
     
    modal(setModal)
    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.
     
    next()
    Activate next child item.
     
    prev()
    Activate previous child item Has no effect on an inactive Focusable.
    Methods borrowed from class glow.ui.Behaviour:
    enabled
    Methods borrowed from class glow.events.Target:
    detach, fire, on
    Event Summary
    Event Attributes Event Name and Description
     
    activate(event)
    Fires when the Focusable becomes active Cancelling this event prevents the Focusable being actived
     
    Fires when a child item of the Focusable becomes active Cancelling this event prevents the child item being actived
     
    deactivate(event)
    Fires when the Focusable becomes deactive Cancelling this event prevents the Focusable being deactived
     
    select(event)
    Fires when a child of the Focusable is selected.
    Class Detail
    glow.ui.Focusable(container, opts)
    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 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.
    			// 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);
    			});
    Parameters:
    {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.
    {object} opts Optional
    Options
    {string} opts.children Optional
    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).
    {function|string} opts.keyboardNav Optional, Default: '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 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.
    {boolean} opts.setFocus Optional, Default: 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.
    {string} opts.activeChildClass Optional, Default: 'active'
    Class name to give the active child element.
    {boolean} opts.activateOnHover Optional, Default: false
    Activate items on hover?
    {boolean} opts.loop Optional, Default: 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.
    Field Detail
    {glow.NodeList} activeChild
    The active child item. This will be an empty NodeList if no child is active

    {number} activeIndex
    The index of the active child item in glow.ui.Focusable#children. This will be undefined if no child is active.

    {glow.NodeList} children
    NodeList of child items that are managed by this Focusable. This will be an empty nodelist if the focusable has no children

    {glow.NodeList} container
    Focusable container
    Method Detail
    {glow.ui.Focusable|boolean} active(toActivate)
    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.
    Parameters:
    {number|glow.NodeList|boolean} toActivate Optional
    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

    destroy()
    Destroy the Focusable This removes all focusable behaviour from the continer and child items. The elements themselves will not be destroyed.
    Returns:
    this

    {glow.ui.Focusable|boolean} disabled(newState)
    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.
    Parameters:
    {boolean} newState Optional
    Disable the focusable? 'false' will enable a disabled focusable.
    Returns:
    {glow.ui.Focusable|boolean} Returns boolean when getting, Focusable when setting

    modal(setModal)
    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.
    Parameters:
    {boolean} setModal
    New modal value
    Returns:
    this when setting, true/false when getting

    next()
    Activate next child item. Has no effect on an inactive Focusable.
    Returns:
    this

    prev()
    Activate previous child item Has no effect on an inactive Focusable.
    Returns:
    this
    Event Detail
    activate(event)
    Fires when the Focusable becomes active Cancelling this event prevents the Focusable being actived
    Parameters:
    {glow.events.Event} event
    Event Object

    childActivate(event)
    Fires when a child item of the Focusable becomes active Cancelling this event prevents the child item being actived
    Parameters:
    {glow.events.Event} event
    Event Object
    {glow.NodeList} event.item
    Item activated.
    {number} event.itemIndex
    The index of the activated item in glow.ui.Focusable#children.
    {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.
    {glow.events.DomEvent} event.methodEvent Optional
    An event object for the 'key' or 'hover' event. For 'key' methods this will be a more specific glow.events.KeyboardEvent. If the method was neither 'key' or 'hover', methodEvent will be undefined.

    deactivate(event)
    Fires when the Focusable becomes deactive Cancelling this event prevents the Focusable being deactived
    Parameters:
    {glow.events.Event} event
    Event Object

    select(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object
    {glow.NodeList} event.item
    Item selected.
    {number} event.itemIndex
    The index of the selected item in glow.ui.Focusable#children.

    Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
    docs/symbols/glow.ui.html100644 0 0 17104 11405426572 13130 0ustar 0 0 JsDoc Reference - glow.ui

    麻豆社

    Namespace glow.ui


    Defined in: ui.js.

    Namespace Summary
    Constructor Attributes Constructor Name and Description
     
    Namespace Detail
    glow.ui

    Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
    docs/symbols/glow.ui.overlay.html100644 0 0 54034 11405426572 14613 0ustar 0 0 JsDoc Reference - glow.ui.Overlay

    麻豆社

    Class glow.ui.Overlay


    Extends glow.ui.

    Defined in: ui.js.

    Class Summary
    Constructor Attributes Constructor Name and Description
     
    glow.ui.Overlay(content, opts)
    A container element displayed on top of the other page content
    Field Summary
    Field Attributes Field Name and Description
     
    Hides all Flash elements on the page, outside of the overlay.
     
    Hides all Flash elements on the page, outside of the overlay.
     
    True if the overlay is shown.
    Method Summary
    Method Attributes Method Name and Description
     
    hide(delay)
    Hides the overlay after an optional delay period and animation
     
    setAnim(anim)
    Set the animation to use when showing and hiding this overlay.
     
    show(delay)
    Displays the overlay after an optional delay period and animation.
    Event Summary
    Event Attributes Event Name and Description
     
    afterHide(event)
    Fired when the overlay has fully hidden, after any delay or hiding animation has completed.
     
    afterShow(event)
    Fired when the overlay is showing to the user and any delay or 'show' animation is complete.
     
    hide(event)
    Fired when the overlay is about to hide.
     
    show(event)
    Fired when the overlay is about to appear on the screen, before any animation.
    Class Detail
    glow.ui.Overlay(content, opts)
    A container element displayed on top of the other page content
    			var myOverlay = new glow.ui.Overlay(
    				glow(
    					'
    ' + '

    Your Story has been saved.

    ' + '
    ' ).appendTo(document.body) ); glow('#save-story-button').on('click', function() { myOverlay.show(); });
    Parameters:
    {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().
    {object} opts Optional
    {function|selector|NodeList|boolean} opts.hideWhenShown Optional
    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.
    Field Detail
    hideFlash
    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.

    showFlash
    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.

    {boolean} shown
    True if the overlay is shown. This is a read-only property to check the state of the overlay.
    Method Detail
    hide(delay)
    Hides the overlay after an optional delay period and animation
    Parameters:
    {number} delay Optional, Default: 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.
    Returns:
    this

    setAnim(anim)
    Set the animation to use when showing and hiding this overlay.
    Parameters:
    {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 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

    show(delay)
    Displays the overlay after an optional delay period and animation.
    Parameters:
    {number} delay Optional, Default: 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.
    Returns:
    this
    Event Detail
    afterHide(event)
    Fired when the overlay has fully hidden, after any delay or hiding animation has completed.
    Parameters:
    {glow.events.Event} event
    Event Object

    afterShow(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object

    hide(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object

    show(event)
    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.
    Parameters:
    {glow.events.Event} event
    Event Object

    Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
    docs/symbols/glow.ui.widget.html100644 0 0 43020 11405426572 14406 0ustar 0 0 JsDoc Reference - glow.ui.Widget

    麻豆社

    Class glow.ui.Widget


    Extends glow.events.Target.

    Defined in: ui.js.

    Class Summary
    Constructor Attributes Constructor Name and Description
     
    glow.ui.Widget(name, opts)
    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.
    Field Summary
    Field Attributes Field Name and Description
     
    The outermost wrapper element of the widget.
     
    The content element of the widget This is inside various wrapper elements used to track the state of the widget.
     
    The phase within the lifecycle of the widget.
    Method Summary
    Method Attributes Method Name and Description
     
    Cause any functionality that deals with removing and deleting this widget to run.
     
    disabled(newState)
    Enable/disable the Widget, or get the disabled state If other widgets are synced with this one, they will become disabled too.
    Methods borrowed from class glow.events.Target:
    detach, fire, on
    Event Summary
    Event Attributes Event Name and Description
     
    Fired when destroy is called on this widget.
     
    Fired after the disabled property is changed via the glow.ui.Widget#disable or glow.ui.Widget#enable method.
    Class Detail
    glow.ui.Widget(name, opts)
    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.
    			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);
    Parameters:
    {string} name
    The name of this widget. This is added to class names in the generated DOM nodes that wrap the widget interface.
    {object} opts Optional
    {string} opts.className Optional
    Class name to add to the container.
    {string} opts.id Optional
    Id to add to the container.
    Field Detail
    {glow.NodeList} container
    The outermost wrapper element of the widget.

    {glow.NodeList} content
    The content element of the widget This is inside various wrapper elements used to track the state of the widget.

    {string} phase
    The phase within the lifecycle of the widget. Will be one of the following:
    constructed
    The widget has been constructed but not yet initialised
    initialised
    The widget has been initialised but not yet build
    built
    The widget has been built but not yet bound
    ready
    The widget is in a fully usable state
    destroyed
    The widget has been destroyed
    Usually, init, build & bind are done in the constructor, so you may only interact with a widget that is either 'ready' or 'destroyed'.
    Method Detail
    destroy()
    Cause any functionality that deals with removing and deleting this widget to run. By default the container and all it's contents are removed.

    disabled(newState)
    Enable/disable the Widget, or get the disabled state If other widgets are synced with this one, they will become disabled too.
    			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
    Parameters:
    {boolean} newState Optional
    Disable the focusable? 'false' will enable a disabled focusable.
    Event Detail
    destroy()
    Fired when destroy is called on this widget.
    See:
    glow.ui.Widget#destroy

    disable()
    Fired after the disabled property is changed via the glow.ui.Widget#disable or glow.ui.Widget#enable method. This includes widgets that are changed as a result of being synced to this one.

    Documentation generated by 2.3.2 on Mon Jun 14 2010 14:24:10 GMT+0100 (BST)
    docs/symbols/glow.util.html100644 0 0 65553 11405426572 13503 0ustar 0 0 JsDoc Reference - glow.util

    麻豆社

    Namespace glow.util


    Defined in: core.js.

    Namespace Summary
    Constructor Attributes Constructor Name and Description
     
    Core JavaScript helpers
    Method Summary
    Method Attributes Method Name and Description
    <static>  
    glow.util.apply(destination, source)
    Copies properties from one object to another All properties from 'source' will be copied onto 'destination', potentially overwriting existing properties on 'destination'.
    <static>  
    glow.util.decodeJson(string, opts)
    Decodes a string JSON representation into an object.
    <static>  
    glow.util.decodeUrl(string)
    Decodes a query string into an object.
    <static>  
    glow.util.encodeJson(object)
    Encodes an object into a string JSON representation.
    <static>  
    glow.util.encodeUrl(object)
    Encodes an object for use as a query string.
    <static>  
    glow.util.escapeRegex(str)
    Escape special regex chars from a string
    <static>  
    glow.util.extend(sub, base, additionalProperties)
    Copies the prototype of one object to another The 'subclass' can also access the 'base class' via subclass.base
    <static>  
    glow.util.getType(object)
    Get the native type or constructor name of an object.
    <static>  
    glow.util.interpolate(template, data, opts)
    Replaces placeholders in a string with data from an object
    <static>  
    glow.util.trim(str)
    Removes leading and trailing whitespace from a string
    Namespace Detail
    glow.util
    Core JavaScript helpers
    Method Detail
    <static> {Object} glow.util.apply(destination, source)
    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.
    			var obj = glow.util.apply({foo: "hello", bar: "world"}, {bar: "everyone"});
    			//results in {foo: "hello", bar: "everyone"}
    Parameters:
    {Object} destination Optional
    Destination object. If this object is undefined or falsey, a new object will be created.
    {Object} source Optional
    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.

    <static> {Object} glow.util.decodeJson(string, opts)
    Decodes a string JSON representation into an object. Returns a JavaScript object that mirrors the data given.
    				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
    Parameters:
    {String} string
    The string to be decoded. Must be valid JSON.
    {Object} opts
    Zero or more of the following as properties of an object:
    {Boolean} opts.safeMode Optional, Default: false
    Whether the string should only be decoded if it is deemed "safe". The json.org regular expression checks are used.
    Returns:
    {Object}

    <static> {Object} glow.util.decodeUrl(string)
    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.
    			var getRef = glow.util.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2");
    			// will return the object {foo: "Foo", bar: ["Bar 1", "Bar2"]}
    Parameters:
    {String} string
    The query string to be decoded. It should not include the initial question mark.
    Returns:
    {Object}

    <static> {Object} glow.util.encodeJson(object)
    Encodes an object into a string JSON representation. Returns a string representing the object as JSON.
    				var myObj = {foo: "Foo", bar: ["Bar 1", "Bar2"]};
    				var getRef = glow.util.encodeJson(myObj);
    				// will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}'
    Parameters:
    {Object} object
    The object to be encoded. This can be arbitrarily nested, but must not contain functions or cyclical structures.
    Returns:
    {Object}

    <static> {String} glow.util.encodeUrl(object)
    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.
    			var getRef = glow.util.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]});
    			// will return "foo=Foo&bar=Bar%201&bar=Bar2"
    Parameters:
    {Object} object
    The object to be encoded. This must be a hash whose values can only be primitives or arrays of primitives.
    Returns:
    {String}

    <static> {string} glow.util.escapeRegex(str)
    Escape special regex chars from a string
    			var str = glow.util.escapeRegex('[Hello. Is this escaped?]');
    			// Outputs:
    			// \[Hello\. Is this escaped\?\]
    Parameters:
    {string} str
    String to escape
    Returns:
    {string} Escaped string

    <static> glow.util.extend(sub, base, additionalProperties)
    Copies the prototype of one object to another The 'subclass' can also access the 'base class' via subclass.base
    			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"
    Parameters:
    {Function} sub
    Class which inherits properties.
    {Function} base
    Class to inherit from.
    {Object} additionalProperties
    An object of properties and methods to add to the subclass.

    <static> glow.util.getType(object)
    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.
    			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'
    			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
    Parameters:
    {Object} object
    Object to get the type of.

    <static> {String} glow.util.interpolate(template, data, opts)
    Replaces placeholders in a string with data from an object
    			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."
    			var data = {
    				name: 'Haxors!!1 '
    			}
    			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="/glow/download/release/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 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="none">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="none">Versions of Webkit used by Safari</a>
    206 		@see <a href="none">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="none">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="/glow/download/release/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="/glow/download/release/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="none">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='/favicon1.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: '/favicon1.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='/favicon1.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='/favicon1.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="none">
    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:'/glow/download/release/JaffaTheCake.jpg'}, ...]
    2196 			glow.ui.AutoSuggest().setFormat(function() {
    2197 				// Format the results like <img src="/glow/download/release/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:'/glow/download/release/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="/glow/download/release/anotherpage.html">
    4371 			//       <img width="200" height="200" src="/glow/download/release/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="/glow/download/release/fullimage.jpg">
    4385 			//     <img src="/glow/download/release/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"><h1><a href="/">麻豆社</a></h1><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 @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 @see @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 */ 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="/glow/download/release/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="/glow/download/release/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><h1><a href="/">麻豆社</a></h1><script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(bp, s); })(); </script> 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 */ 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="/glow/download/release/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><h1><a href="/">麻豆社</a></h1><script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(bp, s); })(); </script> 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='/favicon1.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: '/favicon1.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><h1><a href="/">麻豆社</a></h1><script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(bp, s); })(); </script></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='/favicon1.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='/favicon1.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:"/favicon1.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><h1><a href="/">麻豆社</a></h1><script> (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(bp, s); })(); </script></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//glow/download/release/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鈧 脳芒鈥燙虏%$*�!霉� ��,�������6潞2#+脢A脠锟矫屸漋/鈥么N帽IBa藴芦p冒 脤鲁陆脝篓+Y铆眉茠脙2漏d鸥驴�!霉� ��,�������3潞b%+脢2鈥犫樏揤_鈥︹孤 鈥! 1D鈥陋F鈥毬懊bR]贸=08,锟矫埪9L��!霉� ��,�������2潞r'+J莽d冒贸L锟 &v脙`\bT鈥濃︹灺hYB)脧脢@茅<脙&,锟矫埪鈥��!霉� ��,�������3潞 脗锟9茫t莽录脷啪0脟脿!.B露锟矫奧卢垄1  sa禄掳5梅鈥锟0掳 鈥奥慌竚)J��!霉� ��,�������2潞脺镁冒 脵艙U]拧卯脷qp鈥藛脻a艙脻4鈥撯F脜0锟絗鈥郝 脗@鈥1鈧偯柮庘��!霉� ��,�������2潞脺镁0脢I芦eB脭艙)脳脙 沤脟q10漏脢掳庐P脗aV脷楼 ub鈥毰綶锟��;������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������glow/2.0.0b1//glow/download/release/images/Carousel/arrows.png�������������������������������������������������������������100644 � 0 � 0 � 1012 11405426600 15072� 0����������������������������������������������������������������������������������������������������ustar������������������������������������������������������������������� 0 � 0 ������������������������������������������������������������������������������������������������������������������������������������������������������������������������鈥癙NG  ��� IHDR���.���>���c卤脵啪���gAMA��卤锟 眉a���KPLTE���每每每每每每UUUUUUUUU每每每每每每UUU每每每每每每UUUUUU每每每每每每UUUUUU每每每UUUUUUUUU每每每每每每每每每UUUI啪眉;���tRNS�@脽脽@鈧偓脧脧锟锟 0 0茂茂驴p驴p脳锚)脛��GIDATH脟锟解澝茠 鈥灺箊WE{脌没?im+脕漏虏氓C>v鈩$D锟絑鈥扳U搂鲁 >鸥叠藴帽缚顿脷鈥/.1虫碍脼4酶贬驳<鈥=G茂楼iw猫kp鸥鈩⒚暶嵟∶脨鲁鈥(cP 玫鈥$潞5脺鈥樎疵 i锟矫椕该屄1锟絥 拢脡脿卤t鈥櫭柮暵衡#陆5脿楼{J茠隆鈥郝5鈥燞{;鈥,茂潞]U脻h艗Z毛H卢脽脕脽镁脮&4g掳)脥HpON沤禄I脜x陆脠脿脣5脝锟絍d脿l眉芒茠路4谩M茠锟絫脝c脺q么.Q锚藴vG_茠}fV7k?鈧琯 Q脝聽貌鈥$鈥5<锟铰疵碘β疵巏潞莽脫没鈥漀7鈥dp[ 鈥I禄j脻脠鈧樏冒脪鈥=楼脕脪脥8麓C陇沤陆脢 鈥搘脳庐陋k4F-鈥瀠$脰茂脿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(/glow/download/release/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(/glow/download/release/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(/glow/download/release/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(/glow/download/release/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 @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:'/glow/download/release/JaffaTheCake.jpg'}, ...] glow.ui.AutoSuggest().setFormat(function() { // Format the results like <img src="/glow/download/release/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:'/glow/download/release/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="/glow/download/release/anotherpage.html"> // <img width="200" height="200" src="/glow/download/release/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="/glow/download/release/fullimage.jpg"> // <img src="/glow/download/release/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");������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������