" && !hasBody ?
- div.childNodes :
- [];
-
- for ( j = tbody.length - 1; j >= 0 ; --j ) {
- if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
- tbody[ j ].parentNode.removeChild( tbody[ j ] );
- }
- }
- }
-
- // IE completely kills leading whitespace when innerHTML is used
- if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
- div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
- }
-
- elem = div.childNodes;
-
- // Take out of fragment container (we need a fresh div each time)
- div.parentNode.removeChild( div );
- }
- }
-
- if ( elem.nodeType ) {
- ret.push( elem );
- } else {
- jQuery.merge( ret, elem );
- }
- }
-
- // Fix #11356: Clear elements from safeFragment
- if ( div ) {
- elem = div = safe = null;
- }
-
- // Reset defaultChecked for any radios and checkboxes
- // about to be appended to the DOM in IE 6/7 (#8060)
- if ( !jQuery.support.appendChecked ) {
- for ( i = 0; (elem = ret[i]) != null; i++ ) {
- if ( jQuery.nodeName( elem, "input" ) ) {
- fixDefaultChecked( elem );
- } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
- jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
- }
- }
- }
-
- // Append elements to a provided document fragment
- if ( fragment ) {
- // Special handling of each script element
- handleScript = function( elem ) {
- // Check if we consider it executable
- if ( !elem.type || rscriptType.test( elem.type ) ) {
- // Detach the script and store it in the scripts array (if provided) or the fragment
- // Return truthy to indicate that it has been handled
- return scripts ?
- scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
- fragment.appendChild( elem );
- }
- };
-
- for ( i = 0; (elem = ret[i]) != null; i++ ) {
- // Check if we're done after handling an executable script
- if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
- // Append to fragment and handle embedded scripts
- fragment.appendChild( elem );
- if ( typeof elem.getElementsByTagName !== "undefined" ) {
- // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
- jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
-
- // Splice the scripts into ret after their former ancestor and advance our index beyond them
- ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
- i += jsTags.length;
- }
- }
- }
- }
-
- return ret;
- },
-
- cleanData: function( elems, /* internal */ acceptData ) {
- var data, id, elem, type,
- i = 0,
- internalKey = jQuery.expando,
- cache = jQuery.cache,
- deleteExpando = jQuery.support.deleteExpando,
- special = jQuery.event.special;
-
- for ( ; (elem = elems[i]) != null; i++ ) {
-
- if ( acceptData || jQuery.acceptData( elem ) ) {
-
- id = elem[ internalKey ];
- data = id && cache[ id ];
-
- if ( data ) {
- if ( data.events ) {
- for ( type in data.events ) {
- if ( special[ type ] ) {
- jQuery.event.remove( elem, type );
-
- // This is a shortcut to avoid jQuery.event.remove's overhead
- } else {
- jQuery.removeEvent( elem, type, data.handle );
- }
- }
- }
-
- // Remove cache only if it was not already removed by jQuery.event.remove
- if ( cache[ id ] ) {
-
- delete cache[ id ];
-
- // IE does not allow us to delete expando properties from nodes,
- // nor does it have a removeAttribute function on Document nodes;
- // we must handle all of these cases
- if ( deleteExpando ) {
- delete elem[ internalKey ];
-
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( internalKey );
-
- } else {
- elem[ internalKey ] = null;
- }
-
- jQuery.deletedIds.push( id );
- }
- }
- }
- }
- }
-});
-// Limit scope pollution from any deprecated API
-(function() {
-
-var matched, browser;
-
-// Use of jQuery.browser is frowned upon.
-// More details: http://api.jquery.com/jQuery.browser
-// jQuery.uaMatch maintained for back-compat
-jQuery.uaMatch = function( ua ) {
- ua = ua.toLowerCase();
-
- var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
- /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
- /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
- /(msie) ([\w.]+)/.exec( ua ) ||
- ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
- [];
-
- return {
- browser: match[ 1 ] || "",
- version: match[ 2 ] || "0"
- };
-};
-
-matched = jQuery.uaMatch( navigator.userAgent );
-browser = {};
-
-if ( matched.browser ) {
- browser[ matched.browser ] = true;
- browser.version = matched.version;
-}
-
-// Chrome is Webkit, but Webkit is also Safari.
-if ( browser.chrome ) {
- browser.webkit = true;
-} else if ( browser.webkit ) {
- browser.safari = true;
-}
-
-jQuery.browser = browser;
-
-jQuery.sub = function() {
- function jQuerySub( selector, context ) {
- return new jQuerySub.fn.init( selector, context );
- }
- jQuery.extend( true, jQuerySub, this );
- jQuerySub.superclass = this;
- jQuerySub.fn = jQuerySub.prototype = this();
- jQuerySub.fn.constructor = jQuerySub;
- jQuerySub.sub = this.sub;
- jQuerySub.fn.init = function init( selector, context ) {
- if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
- context = jQuerySub( context );
- }
-
- return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
- };
- jQuerySub.fn.init.prototype = jQuerySub.fn;
- var rootjQuerySub = jQuerySub(document);
- return jQuerySub;
-};
-
-})();
-var curCSS, iframe, iframeDoc,
- ralpha = /alpha\([^)]*\)/i,
- ropacity = /opacity=([^)]*)/,
- rposition = /^(top|right|bottom|left)$/,
- // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
- // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
- rdisplayswap = /^(none|table(?!-c[ea]).+)/,
- rmargin = /^margin/,
- rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
- rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
- rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
- elemdisplay = {},
-
- cssShow = { position: "absolute", visibility: "hidden", display: "block" },
- cssNormalTransform = {
- letterSpacing: 0,
- fontWeight: 400
- },
-
- cssExpand = [ "Top", "Right", "Bottom", "Left" ],
- cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
-
- eventsToggle = jQuery.fn.toggle;
-
-// return a css property mapped to a potentially vendor prefixed property
-function vendorPropName( style, name ) {
-
- // shortcut for names that are not vendor prefixed
- if ( name in style ) {
- return name;
- }
-
- // check for vendor prefixed names
- var capName = name.charAt(0).toUpperCase() + name.slice(1),
- origName = name,
- i = cssPrefixes.length;
-
- while ( i-- ) {
- name = cssPrefixes[ i ] + capName;
- if ( name in style ) {
- return name;
- }
- }
-
- return origName;
-}
-
-function isHidden( elem, el ) {
- elem = el || elem;
- return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
-}
-
-function showHide( elements, show ) {
- var elem, display,
- values = [],
- index = 0,
- length = elements.length;
-
- for ( ; index < length; index++ ) {
- elem = elements[ index ];
- if ( !elem.style ) {
- continue;
- }
- values[ index ] = jQuery._data( elem, "olddisplay" );
- if ( show ) {
- // Reset the inline display of this element to learn if it is
- // being hidden by cascaded rules or not
- if ( !values[ index ] && elem.style.display === "none" ) {
- elem.style.display = "";
- }
-
- // Set elements which have been overridden with display: none
- // in a stylesheet to whatever the default browser style is
- // for such an element
- if ( elem.style.display === "" && isHidden( elem ) ) {
- values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
- }
- } else {
- display = curCSS( elem, "display" );
-
- if ( !values[ index ] && display !== "none" ) {
- jQuery._data( elem, "olddisplay", display );
- }
- }
- }
-
- // Set the display of most of the elements in a second loop
- // to avoid the constant reflow
- for ( index = 0; index < length; index++ ) {
- elem = elements[ index ];
- if ( !elem.style ) {
- continue;
- }
- if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
- elem.style.display = show ? values[ index ] || "" : "none";
- }
- }
-
- return elements;
-}
-
-jQuery.fn.extend({
- css: function( name, value ) {
- return jQuery.access( this, function( elem, name, value ) {
- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- }, name, value, arguments.length > 1 );
- },
- show: function() {
- return showHide( this, true );
- },
- hide: function() {
- return showHide( this );
- },
- toggle: function( state, fn2 ) {
- var bool = typeof state === "boolean";
-
- if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
- return eventsToggle.apply( this, arguments );
- }
-
- return this.each(function() {
- if ( bool ? state : isHidden( this ) ) {
- jQuery( this ).show();
- } else {
- jQuery( this ).hide();
- }
- });
- }
-});
-
-jQuery.extend({
- // Add in style property hooks for overriding the default
- // behavior of getting and setting a style property
- cssHooks: {
- opacity: {
- get: function( elem, computed ) {
- if ( computed ) {
- // We should always get a number back from opacity
- var ret = curCSS( elem, "opacity" );
- return ret === "" ? "1" : ret;
-
- }
- }
- }
- },
-
- // Exclude the following css properties to add px
- cssNumber: {
- "fillOpacity": true,
- "fontWeight": true,
- "lineHeight": true,
- "opacity": true,
- "orphans": true,
- "widows": true,
- "zIndex": true,
- "zoom": true
- },
-
- // Add in properties whose names you wish to fix before
- // setting or getting the value
- cssProps: {
- // normalize float css property
- "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
- },
-
- // Get and set the style property on a DOM Node
- style: function( elem, name, value, extra ) {
- // Don't set styles on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
- return;
- }
-
- // Make sure that we're working with the right name
- var ret, type, hooks,
- origName = jQuery.camelCase( name ),
- style = elem.style;
-
- name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
-
- // gets hook for the prefixed version
- // followed by the unprefixed version
- hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
- // Check if we're setting a value
- if ( value !== undefined ) {
- type = typeof value;
-
- // convert relative number strings (+= or -=) to relative numbers. #7345
- if ( type === "string" && (ret = rrelNum.exec( value )) ) {
- value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
- // Fixes bug #9237
- type = "number";
- }
-
- // Make sure that NaN and null values aren't set. See: #7116
- if ( value == null || type === "number" && isNaN( value ) ) {
- return;
- }
-
- // If a number was passed in, add 'px' to the (except for certain CSS properties)
- if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
- value += "px";
- }
-
- // If a hook was provided, use that value, otherwise just set the specified value
- if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
- // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
- // Fixes bug #5509
- try {
- style[ name ] = value;
- } catch(e) {}
- }
-
- } else {
- // If a hook was provided get the non-computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
- return ret;
- }
-
- // Otherwise just get the value from the style object
- return style[ name ];
- }
- },
-
- css: function( elem, name, numeric, extra ) {
- var val, num, hooks,
- origName = jQuery.camelCase( name );
-
- // Make sure that we're working with the right name
- name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
-
- // gets hook for the prefixed version
- // followed by the unprefixed version
- hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
-
- // If a hook was provided get the computed value from there
- if ( hooks && "get" in hooks ) {
- val = hooks.get( elem, true, extra );
- }
-
- // Otherwise, if a way to get the computed value exists, use that
- if ( val === undefined ) {
- val = curCSS( elem, name );
- }
-
- //convert "normal" to computed value
- if ( val === "normal" && name in cssNormalTransform ) {
- val = cssNormalTransform[ name ];
- }
-
- // Return, converting to number if forced or a qualifier was provided and val looks numeric
- if ( numeric || extra !== undefined ) {
- num = parseFloat( val );
- return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
- }
- return val;
- },
-
- // A method for quickly swapping in/out CSS properties to get correct calculations
- swap: function( elem, options, callback ) {
- var ret, name,
- old = {};
-
- // Remember the old values, and insert the new ones
- for ( name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
-
- ret = callback.call( elem );
-
- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
-
- return ret;
- }
-});
-
-// NOTE: To any future maintainer, we've window.getComputedStyle
-// because jsdom on node.js will break without it.
-if ( window.getComputedStyle ) {
- curCSS = function( elem, name ) {
- var ret, width, minWidth, maxWidth,
- computed = window.getComputedStyle( elem, null ),
- style = elem.style;
-
- if ( computed ) {
-
- ret = computed[ name ];
- if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
- ret = jQuery.style( elem, name );
- }
-
- // A tribute to the "awesome hack by Dean Edwards"
- // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
- // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
- // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
- if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
- width = style.width;
- minWidth = style.minWidth;
- maxWidth = style.maxWidth;
-
- style.minWidth = style.maxWidth = style.width = ret;
- ret = computed.width;
-
- style.width = width;
- style.minWidth = minWidth;
- style.maxWidth = maxWidth;
- }
- }
-
- return ret;
- };
-} else if ( document.documentElement.currentStyle ) {
- curCSS = function( elem, name ) {
- var left, rsLeft,
- ret = elem.currentStyle && elem.currentStyle[ name ],
- style = elem.style;
-
- // Avoid setting ret to empty string here
- // so we don't default to auto
- if ( ret == null && style && style[ name ] ) {
- ret = style[ name ];
- }
-
- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
- // If we're not dealing with a regular pixel number
- // but a number that has a weird ending, we need to convert it to pixels
- // but not position css attributes, as those are proportional to the parent element instead
- // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
- if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
-
- // Remember the original values
- left = style.left;
- rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
-
- // Put in the new values to get a computed value out
- if ( rsLeft ) {
- elem.runtimeStyle.left = elem.currentStyle.left;
- }
- style.left = name === "fontSize" ? "1em" : ret;
- ret = style.pixelLeft + "px";
-
- // Revert the changed values
- style.left = left;
- if ( rsLeft ) {
- elem.runtimeStyle.left = rsLeft;
- }
- }
-
- return ret === "" ? "auto" : ret;
- };
-}
-
-function setPositiveNumber( elem, value, subtract ) {
- var matches = rnumsplit.exec( value );
- return matches ?
- Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
- value;
-}
-
-function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
- var i = extra === ( isBorderBox ? "border" : "content" ) ?
- // If we already have the right measurement, avoid augmentation
- 4 :
- // Otherwise initialize for horizontal or vertical properties
- name === "width" ? 1 : 0,
-
- val = 0;
-
- for ( ; i < 4; i += 2 ) {
- // both box models exclude margin, so add it if we want it
- if ( extra === "margin" ) {
- // we use jQuery.css instead of curCSS here
- // because of the reliableMarginRight CSS hook!
- val += jQuery.css( elem, extra + cssExpand[ i ], true );
- }
-
- // From this point on we use curCSS for maximum performance (relevant in animations)
- if ( isBorderBox ) {
- // border-box includes padding, so remove it if we want content
- if ( extra === "content" ) {
- val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
- }
-
- // at this point, extra isn't border nor margin, so remove border
- if ( extra !== "margin" ) {
- val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
- }
- } else {
- // at this point, extra isn't content, so add padding
- val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
-
- // at this point, extra isn't content nor padding, so add border
- if ( extra !== "padding" ) {
- val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
- }
- }
- }
-
- return val;
-}
-
-function getWidthOrHeight( elem, name, extra ) {
-
- // Start with offset property, which is equivalent to the border-box value
- var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
- valueIsBorderBox = true,
- isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
-
- // some non-html elements return undefined for offsetWidth, so check for null/undefined
- // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
- // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
- if ( val <= 0 || val == null ) {
- // Fall back to computed then uncomputed css if necessary
- val = curCSS( elem, name );
- if ( val < 0 || val == null ) {
- val = elem.style[ name ];
- }
-
- // Computed unit is not pixels. Stop here and return.
- if ( rnumnonpx.test(val) ) {
- return val;
- }
-
- // we need the check for style in case a browser which returns unreliable values
- // for getComputedStyle silently falls back to the reliable elem.style
- valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
-
- // Normalize "", auto, and prepare for extra
- val = parseFloat( val ) || 0;
- }
-
- // use the active box-sizing model to add/subtract irrelevant styles
- return ( val +
- augmentWidthOrHeight(
- elem,
- name,
- extra || ( isBorderBox ? "border" : "content" ),
- valueIsBorderBox
- )
- ) + "px";
-}
-
-
-// Try to determine the default display value of an element
-function css_defaultDisplay( nodeName ) {
- if ( elemdisplay[ nodeName ] ) {
- return elemdisplay[ nodeName ];
- }
-
- var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
- display = elem.css("display");
- elem.remove();
-
- // If the simple way fails,
- // get element's real default display by attaching it to a temp iframe
- if ( display === "none" || display === "" ) {
- // Use the already-created iframe if possible
- iframe = document.body.appendChild(
- iframe || jQuery.extend( document.createElement("iframe"), {
- frameBorder: 0,
- width: 0,
- height: 0
- })
- );
-
- // Create a cacheable copy of the iframe document on first call.
- // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
- // document to it; WebKit & Firefox won't allow reusing the iframe document.
- if ( !iframeDoc || !iframe.createElement ) {
- iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
- iframeDoc.write("");
- iframeDoc.close();
- }
-
- elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
-
- display = curCSS( elem, "display" );
- document.body.removeChild( iframe );
- }
-
- // Store the correct default display
- elemdisplay[ nodeName ] = display;
-
- return display;
-}
-
-jQuery.each([ "height", "width" ], function( i, name ) {
- jQuery.cssHooks[ name ] = {
- get: function( elem, computed, extra ) {
- if ( computed ) {
- // certain elements can have dimension info if we invisibly show them
- // however, it must have a current display style that would benefit from this
- if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) {
- return jQuery.swap( elem, cssShow, function() {
- return getWidthOrHeight( elem, name, extra );
- });
- } else {
- return getWidthOrHeight( elem, name, extra );
- }
- }
- },
-
- set: function( elem, value, extra ) {
- return setPositiveNumber( elem, value, extra ?
- augmentWidthOrHeight(
- elem,
- name,
- extra,
- jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
- ) : 0
- );
- }
- };
-});
-
-if ( !jQuery.support.opacity ) {
- jQuery.cssHooks.opacity = {
- get: function( elem, computed ) {
- // IE uses filters for opacity
- return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
- ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
- computed ? "1" : "";
- },
-
- set: function( elem, value ) {
- var style = elem.style,
- currentStyle = elem.currentStyle,
- opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
- filter = currentStyle && currentStyle.filter || style.filter || "";
-
- // IE has trouble with opacity if it does not have layout
- // Force it by setting the zoom level
- style.zoom = 1;
-
- // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
- if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
- style.removeAttribute ) {
-
- // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
- // if "filter:" is present at all, clearType is disabled, we want to avoid this
- // style.removeAttribute is IE Only, but so apparently is this code path...
- style.removeAttribute( "filter" );
-
- // if there there is no filter style applied in a css rule, we are done
- if ( currentStyle && !currentStyle.filter ) {
- return;
- }
- }
-
- // otherwise, set new filter values
- style.filter = ralpha.test( filter ) ?
- filter.replace( ralpha, opacity ) :
- filter + " " + opacity;
- }
- };
-}
-
-// These hooks cannot be added until DOM ready because the support test
-// for it is not run until after DOM ready
-jQuery(function() {
- if ( !jQuery.support.reliableMarginRight ) {
- jQuery.cssHooks.marginRight = {
- get: function( elem, computed ) {
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- // Work around by temporarily setting element display to inline-block
- return jQuery.swap( elem, { "display": "inline-block" }, function() {
- if ( computed ) {
- return curCSS( elem, "marginRight" );
- }
- });
- }
- };
- }
-
- // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
- // getComputedStyle returns percent when specified for top/left/bottom/right
- // rather than make the css module depend on the offset module, we just check for it here
- if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
- jQuery.each( [ "top", "left" ], function( i, prop ) {
- jQuery.cssHooks[ prop ] = {
- get: function( elem, computed ) {
- if ( computed ) {
- var ret = curCSS( elem, prop );
- // if curCSS returns percentage, fallback to offset
- return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
- }
- }
- };
- });
- }
-
-});
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.hidden = function( elem ) {
- return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
- };
-
- jQuery.expr.filters.visible = function( elem ) {
- return !jQuery.expr.filters.hidden( elem );
- };
-}
-
-// These hooks are used by animate to expand properties
-jQuery.each({
- margin: "",
- padding: "",
- border: "Width"
-}, function( prefix, suffix ) {
- jQuery.cssHooks[ prefix + suffix ] = {
- expand: function( value ) {
- var i,
-
- // assumes a single number if not a string
- parts = typeof value === "string" ? value.split(" ") : [ value ],
- expanded = {};
-
- for ( i = 0; i < 4; i++ ) {
- expanded[ prefix + cssExpand[ i ] + suffix ] =
- parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
- }
-
- return expanded;
- }
- };
-
- if ( !rmargin.test( prefix ) ) {
- jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
- }
-});
-var r20 = /%20/g,
- rbracket = /\[\]$/,
- rCRLF = /\r?\n/g,
- rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
- rselectTextarea = /^(?:select|textarea)/i;
-
-jQuery.fn.extend({
- serialize: function() {
- return jQuery.param( this.serializeArray() );
- },
- serializeArray: function() {
- return this.map(function(){
- return this.elements ? jQuery.makeArray( this.elements ) : this;
- })
- .filter(function(){
- return this.name && !this.disabled &&
- ( this.checked || rselectTextarea.test( this.nodeName ) ||
- rinput.test( this.type ) );
- })
- .map(function( i, elem ){
- var val = jQuery( this ).val();
-
- return val == null ?
- null :
- jQuery.isArray( val ) ?
- jQuery.map( val, function( val, i ){
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }) :
- { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }).get();
- }
-});
-
-//Serialize an array of form elements or a set of
-//key/values into a query string
-jQuery.param = function( a, traditional ) {
- var prefix,
- s = [],
- add = function( key, value ) {
- // If value is a function, invoke it and return its value
- value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
- s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
- };
-
- // Set traditional to true for jQuery <= 1.3.2 behavior.
- if ( traditional === undefined ) {
- traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
- }
-
- // If an array was passed in, assume that it is an array of form elements.
- if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
- // Serialize the form elements
- jQuery.each( a, function() {
- add( this.name, this.value );
- });
-
- } else {
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for ( prefix in a ) {
- buildParams( prefix, a[ prefix ], traditional, add );
- }
- }
-
- // Return the resulting serialization
- return s.join( "&" ).replace( r20, "+" );
-};
-
-function buildParams( prefix, obj, traditional, add ) {
- var name;
-
- if ( jQuery.isArray( obj ) ) {
- // Serialize array item.
- jQuery.each( obj, function( i, v ) {
- if ( traditional || rbracket.test( prefix ) ) {
- // Treat each array item as a scalar.
- add( prefix, v );
-
- } else {
- // If array item is non-scalar (array or object), encode its
- // numeric index to resolve deserialization ambiguity issues.
- // Note that rack (as of 1.0.0) can't currently deserialize
- // nested arrays properly, and attempting to do so may cause
- // a server error. Possible fixes are to modify rack's
- // deserialization algorithm or to provide an option or flag
- // to force array serialization to be shallow.
- buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
- }
- });
-
- } else if ( !traditional && jQuery.type( obj ) === "object" ) {
- // Serialize object item.
- for ( name in obj ) {
- buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
- }
-
- } else {
- // Serialize scalar item.
- add( prefix, obj );
- }
-}
-var
- // Document location
- ajaxLocParts,
- ajaxLocation,
-
- rhash = /#.*$/,
- rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
- // #7653, #8125, #8152: local protocol detection
- rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
- rnoContent = /^(?:GET|HEAD)$/,
- rprotocol = /^\/\//,
- rquery = /\?/,
- rscript = /
+
+
+
+
diff --git a/lib/angular/docs/docs-scenario.js b/lib/angular/docs/docs-scenario.js
new file mode 100644
index 0000000..0546f9f
--- /dev/null
+++ b/lib/angular/docs/docs-scenario.js
@@ -0,0 +1,4606 @@
+describe("angular+jqlite", function() {
+ describe("api/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/index");
+ });
+
+});
+
+ describe("api/ng", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng");
+ });
+
+});
+
+ describe("cookbook/advancedform", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/advancedform");
+ });
+
+ it('should enable save button', function() {
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('');
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('change');
+ expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
+ element(':button:contains(Save)').click();
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ });
+ it('should enable cancel button', function() {
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('change');
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
+ element(':button:contains(Cancel)').click();
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
+ expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
+ });
+
+});
+
+ describe("cookbook/buzz", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/buzz");
+ });
+
+ xit('fetch buzz and expand', function() {
+ element(':button:contains(fetch)').click();
+ expect(repeater('div.buzz').count()).toBeGreaterThan(0);
+ element('.buzz a:contains(Expand replies):first').click();
+ expect(repeater('div.reply').count()).toBeGreaterThan(0);
+ });
+
+});
+
+ describe("cookbook/deeplinking", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/deeplinking");
+ });
+
+ it('should navigate to URL', function() {
+ element('a:contains(Welcome)').click();
+ expect(element('[ng-view]').text()).toMatch(/Hello anonymous/);
+ element('a:contains(Settings)').click();
+ input('form.name').enter('yourname');
+ element(':button:contains(Save)').click();
+ element('a:contains(Welcome)').click();
+ expect(element('[ng-view]').text()).toMatch(/Hello yourname/);
+ });
+
+});
+
+ describe("cookbook/form", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/form");
+ });
+
+ it('should show debug', function() {
+ expect(binding('user')).toMatch(/John Smith/);
+ });
+ it('should add contact', function() {
+ using('.example').element('a:contains(add)').click();
+ using('.example div:last').input('contact.value').enter('you@example.org');
+ expect(binding('user')).toMatch(/\(234\) 555\-1212/);
+ expect(binding('user')).toMatch(/you@example.org/);
+ });
+
+ it('should remove contact', function() {
+ using('.example').element('a:contains(X)').click();
+ expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
+ });
+
+ it('should validate zip', function() {
+ expect(using('.example').
+ element(':input[ng\\:model="user.address.zip"]').
+ prop('className')).not().toMatch(/ng-invalid/);
+ using('.example').input('user.address.zip').enter('abc');
+ expect(using('.example').
+ element(':input[ng\\:model="user.address.zip"]').
+ prop('className')).toMatch(/ng-invalid/);
+ });
+
+ it('should validate state', function() {
+ expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
+ .not().toMatch(/ng-invalid/);
+ using('.example').input('user.address.state').enter('XXX');
+ expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
+ .toMatch(/ng-invalid/);
+ });
+
+});
+
+ describe("cookbook/helloworld", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/helloworld");
+ });
+
+ it('should change the binding when user enters text', function() {
+ expect(binding('name')).toEqual('World');
+ input('name').enter('angular');
+ expect(binding('name')).toEqual('angular');
+ });
+
+});
+
+ describe("cookbook/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/index");
+ });
+
+});
+
+ describe("cookbook/mvc", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/cookbook/mvc");
+ });
+
+ it('should play a game', function() {
+ piece(1, 1);
+ expect(binding('nextMove')).toEqual('O');
+ piece(3, 1);
+ expect(binding('nextMove')).toEqual('X');
+ piece(1, 2);
+ piece(3, 2);
+ piece(1, 3);
+ expect(element('.winner').text()).toEqual('Player X has won!');
+ });
+
+ function piece(row, col) {
+ element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
+ }
+
+});
+
+ describe("guide/bootstrap", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/bootstrap");
+ });
+
+});
+
+ describe("guide/compiler", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/compiler");
+ });
+
+});
+
+ describe("guide/concepts", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/concepts");
+ });
+
+});
+
+ describe("guide/dev_guide.e2e-testing", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.e2e-testing");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.mvc");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc.understanding_controller", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.mvc.understanding_controller");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc.understanding_model", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.mvc.understanding_model");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc.understanding_view", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.mvc.understanding_view");
+ });
+
+});
+
+ describe("guide/dev_guide.services.$location", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services.$location");
+ });
+
+});
+
+ describe("guide/dev_guide.services.creating_services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services.creating_services");
+ });
+
+});
+
+ describe("guide/dev_guide.services.injecting_controllers", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services.injecting_controllers");
+ });
+
+ it('should test service', function() {
+ expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
+ });
+
+});
+
+ describe("guide/dev_guide.services.managing_dependencies", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services.managing_dependencies");
+ });
+
+});
+
+ describe("guide/dev_guide.services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services");
+ });
+
+});
+
+ describe("guide/dev_guide.services.testing_services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services.testing_services");
+ });
+
+});
+
+ describe("guide/dev_guide.services.understanding_services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.services.understanding_services");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.css-styling", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.templates.css-styling");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.databinding", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.templates.databinding");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.filters.creating_filters", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.templates.filters.creating_filters");
+ });
+
+ it('should reverse greeting', function() {
+ expect(binding('greeting|reverse')).toEqual('olleh');
+ input('greeting').enter('ABC');
+ expect(binding('greeting|reverse')).toEqual('CBA');
+ });
+
+});
+
+ describe("guide/dev_guide.templates.filters", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.templates.filters");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.filters.using_filters", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.templates.filters.using_filters");
+ });
+
+});
+
+ describe("guide/dev_guide.templates", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.templates");
+ });
+
+});
+
+ describe("guide/dev_guide.unit-testing", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/dev_guide.unit-testing");
+ });
+
+});
+
+ describe("guide/di", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/di");
+ });
+
+});
+
+ describe("guide/directive", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/directive");
+ });
+
+ it('should show off bindings', function() {
+ expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text()).toBe('angular');
+ });
+
+ it('should bind and open / close', function() {
+ input('title').enter('TITLE');
+ input('text').enter('TEXT');
+ expect(element('.title').text()).toEqual('Details: TITLE...');
+ expect(binding('text')).toEqual('TEXT');
+
+ expect(element('.zippy').prop('className')).toMatch(/closed/);
+ element('.zippy > .title').click();
+ expect(element('.zippy').prop('className')).toMatch(/opened/);
+ });
+
+});
+
+ describe("guide/expression", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/expression");
+ });
+
+ it('should calculate expression in binding', function() {
+ expect(binding('1+2')).toEqual('3');
+ });
+
+ it('should allow user expression testing', function() {
+ element('.expressions :button').click();
+ var li = using('.expressions ul').repeater('li');
+ expect(li.count()).toBe(1);
+ expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
+ });
+
+ it('should calculate expression in binding', function() {
+ var alertText;
+ this.addFutureAction('set mock', function($window, $document, done) {
+ $window.mockWindow = {
+ alert: function(text){ alertText = text; }
+ };
+ done();
+ });
+ element(':button:contains(Greet)').click();
+ expect(this.addFuture('alert text', function(done) {
+ done(null, alertText);
+ })).toBe('Hello World');
+ });
+
+});
+
+ describe("guide/forms", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/forms");
+ });
+
+});
+
+ describe("guide/i18n", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/i18n");
+ });
+
+});
+
+ describe("guide/ie", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/ie");
+ });
+
+});
+
+ describe("guide/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/index");
+ });
+
+});
+
+ describe("guide/introduction", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/introduction");
+ });
+
+});
+
+ describe("guide/module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/module");
+ });
+
+});
+
+ describe("guide/overview", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/overview");
+ });
+
+ it('should show of angular binding', function() {
+ expect(binding('qty * cost')).toEqual('$19.95');
+ input('qty').enter('2');
+ input('cost').enter('5.00');
+ expect(binding('qty * cost')).toEqual('$10.00');
+ });
+
+});
+
+ describe("guide/scope", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/scope");
+ });
+
+});
+
+ describe("guide/type", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/guide/type");
+ });
+
+});
+
+ describe("misc/contribute", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/misc/contribute");
+ });
+
+});
+
+ describe("misc/downloading", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/misc/downloading");
+ });
+
+});
+
+ describe("misc/faq", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/misc/faq");
+ });
+
+});
+
+ describe("misc/started", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/misc/started");
+ });
+
+});
+
+ describe("tutorial/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/index");
+ });
+
+});
+
+ describe("tutorial/step_00", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_00");
+ });
+
+});
+
+ describe("tutorial/step_01", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_01");
+ });
+
+});
+
+ describe("tutorial/step_02", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_02");
+ });
+
+});
+
+ describe("tutorial/step_03", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_03");
+ });
+
+});
+
+ describe("tutorial/step_04", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_04");
+ });
+
+});
+
+ describe("tutorial/step_05", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_05");
+ });
+
+});
+
+ describe("tutorial/step_06", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_06");
+ });
+
+});
+
+ describe("tutorial/step_07", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_07");
+ });
+
+});
+
+ describe("tutorial/step_08", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_08");
+ });
+
+});
+
+ describe("tutorial/step_09", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_09");
+ });
+
+});
+
+ describe("tutorial/step_10", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_10");
+ });
+
+});
+
+ describe("tutorial/step_11", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/step_11");
+ });
+
+});
+
+ describe("tutorial/the_end", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/tutorial/the_end");
+ });
+
+});
+
+ describe("api/angular.lowercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.lowercase");
+ });
+
+});
+
+ describe("api/angular.uppercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.uppercase");
+ });
+
+});
+
+ describe("api/angular.forEach", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.forEach");
+ });
+
+});
+
+ describe("api/angular.extend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.extend");
+ });
+
+});
+
+ describe("api/angular.noop", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.noop");
+ });
+
+});
+
+ describe("api/angular.identity", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.identity");
+ });
+
+});
+
+ describe("api/angular.isUndefined", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isUndefined");
+ });
+
+});
+
+ describe("api/angular.isDefined", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isDefined");
+ });
+
+});
+
+ describe("api/angular.isObject", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isObject");
+ });
+
+});
+
+ describe("api/angular.isString", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isString");
+ });
+
+});
+
+ describe("api/angular.isNumber", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isNumber");
+ });
+
+});
+
+ describe("api/angular.isDate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isDate");
+ });
+
+});
+
+ describe("api/angular.isArray", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isArray");
+ });
+
+});
+
+ describe("api/angular.isFunction", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isFunction");
+ });
+
+});
+
+ describe("api/angular.isElement", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.isElement");
+ });
+
+});
+
+ describe("api/angular.copy", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.copy");
+ });
+
+});
+
+ describe("api/angular.equals", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.equals");
+ });
+
+});
+
+ describe("api/angular.bind", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.bind");
+ });
+
+});
+
+ describe("api/angular.toJson", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.toJson");
+ });
+
+});
+
+ describe("api/angular.fromJson", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.fromJson");
+ });
+
+});
+
+ describe("api/ng.directive:ngApp", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngApp");
+ });
+
+});
+
+ describe("api/angular.bootstrap", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.bootstrap");
+ });
+
+});
+
+ describe("api/angular.version", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.version");
+ });
+
+});
+
+ describe("api/angular.injector", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.injector");
+ });
+
+});
+
+ describe("api/AUTO", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/AUTO");
+ });
+
+});
+
+ describe("api/AUTO.$injector", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/AUTO.$injector");
+ });
+
+});
+
+ describe("api/AUTO.$provide", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/AUTO.$provide");
+ });
+
+});
+
+ describe("api/angular.element", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.element");
+ });
+
+});
+
+ describe("api/angular.Module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.Module");
+ });
+
+});
+
+ describe("api/angular.module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.module");
+ });
+
+});
+
+ describe("api/ng.$anchorScroll", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$anchorScroll");
+ });
+
+});
+
+ describe("api/ng.$cacheFactory", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$cacheFactory");
+ });
+
+});
+
+ describe("api/ng.$templateCache", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$templateCache");
+ });
+
+});
+
+ describe("api/ng.$controllerProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$controllerProvider");
+ });
+
+});
+
+ describe("api/ng.$controller", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$controller");
+ });
+
+});
+
+ describe("api/ng.$compile", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$compile");
+ });
+
+ it('should auto compile', function() {
+ expect(element('div[compile]').text()).toBe('Hello Angular');
+ input('html').enter('{{name}}!');
+ expect(element('div[compile]').text()).toBe('Angular!');
+ });
+
+});
+
+ describe("api/ng.$compileProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$compileProvider");
+ });
+
+});
+
+ describe("api/ng.$compile.directive.Attributes", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$compile.directive.Attributes");
+ });
+
+});
+
+ describe("api/ng.directive:a", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:a");
+ });
+
+});
+
+ describe("api/ng.directive:ngHref", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngHref");
+ });
+
+ it('should execute ng-click but not reload when href without value', function() {
+ element('#link-1').click();
+ expect(input('value').val()).toEqual('1');
+ expect(element('#link-1').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click but not reload when href empty string', function() {
+ element('#link-2').click();
+ expect(input('value').val()).toEqual('2');
+ expect(element('#link-2').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click and change url when ng-href specified', function() {
+ expect(element('#link-3').attr('href')).toBe("/123");
+
+ element('#link-3').click();
+ expect(browser().window().path()).toEqual('/123');
+ });
+
+ it('should execute ng-click but not reload when href empty string and name specified', function() {
+ element('#link-4').click();
+ expect(input('value').val()).toEqual('4');
+ expect(element('#link-4').attr('href')).toBe('');
+ });
+
+ it('should execute ng-click but not reload when no href but name specified', function() {
+ element('#link-5').click();
+ expect(input('value').val()).toEqual('5');
+ expect(element('#link-5').attr('href')).toBe('');
+ });
+
+ it('should only change url when only ng-href', function() {
+ input('value').enter('6');
+ expect(element('#link-6').attr('href')).toBe('6');
+
+ element('#link-6').click();
+ expect(browser().location().url()).toEqual('/6');
+ });
+
+});
+
+ describe("api/ng.directive:ngSrc", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngSrc");
+ });
+
+});
+
+ describe("api/ng.directive:ngDisabled", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngDisabled");
+ });
+
+ it('should toggle button', function() {
+ expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngChecked", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngChecked");
+ });
+
+ it('should check both checkBoxes', function() {
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
+ input('master').check();
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngMultiple", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMultiple");
+ });
+
+ it('should toggle multiple', function() {
+ expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngReadonly", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngReadonly");
+ });
+
+ it('should toggle readonly attr', function() {
+ expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngSelected", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngSelected");
+ });
+
+ it('should select Greetings!', function() {
+ expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
+ input('selected').check();
+ expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngOpen", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngOpen");
+ });
+
+ it('should toggle open', function() {
+ expect(element('#details').prop('open')).toBeFalsy();
+ input('open').check();
+ expect(element('#details').prop('open')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:form.FormController", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:form.FormController");
+ });
+
+});
+
+ describe("api/ng.directive:ngForm", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngForm");
+ });
+
+});
+
+ describe("api/ng.directive:form", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:form");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('userType')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('userType').enter('');
+ expect(binding('userType')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:ngBind", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngBind");
+ });
+
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('name')).toBe('Whirled');
+ using('.doc-example-live').input('name').enter('world');
+ expect(using('.doc-example-live').binding('name')).toBe('world');
+ });
+
+});
+
+ describe("api/ng.directive:ngBindTemplate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngBindTemplate");
+ });
+
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Hello');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('World');
+ using('.doc-example-live').input('salutation').enter('Greetings');
+ using('.doc-example-live').input('name').enter('user');
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Greetings');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('user');
+ });
+
+});
+
+ describe("api/ng.directive:ngBindHtmlUnsafe", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngBindHtmlUnsafe");
+ });
+
+});
+
+ describe("api/ng.directive:input.text", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input.text");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if multi word', function() {
+ input('text').enter('hello world');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should not be trimmed', function() {
+ input('text').enter('untrimmed ');
+ expect(binding('text')).toEqual('untrimmed ');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+});
+
+ describe("api/ng.directive:input.number", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input.number");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('12');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('123');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:input.url", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input.url");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('http://google.com');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if not url', function() {
+ input('text').enter('xxx');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:input.email", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input.email");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('me@example.com');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if not email', function() {
+ input('text').enter('xxx');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:input.radio", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input.radio");
+ });
+
+ it('should change state', function() {
+ expect(binding('color')).toEqual('blue');
+
+ input('color').select('red');
+ expect(binding('color')).toEqual('red');
+ });
+
+});
+
+ describe("api/ng.directive:input.checkbox", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input.checkbox");
+ });
+
+ it('should change state', function() {
+ expect(binding('value1')).toEqual('true');
+ expect(binding('value2')).toEqual('YES');
+
+ input('value1').check();
+ input('value2').check();
+ expect(binding('value1')).toEqual('false');
+ expect(binding('value2')).toEqual('NO');
+ });
+
+});
+
+ describe("api/ng.directive:textarea", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:textarea");
+ });
+
+});
+
+ describe("api/ng.directive:input", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:input");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
+ expect(binding('myForm.userName.$valid')).toEqual('true');
+ expect(binding('myForm.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty when required', function() {
+ input('user.name').enter('');
+ expect(binding('user')).toEqual('{"last":"visitor"}');
+ expect(binding('myForm.userName.$valid')).toEqual('false');
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+ it('should be valid if empty when min length is set', function() {
+ input('user.last').enter('');
+ expect(binding('user')).toEqual('{"name":"guest","last":""}');
+ expect(binding('myForm.lastName.$valid')).toEqual('true');
+ expect(binding('myForm.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if less than required min length', function() {
+ input('user.last').enter('xx');
+ expect(binding('user')).toEqual('{"name":"guest"}');
+ expect(binding('myForm.lastName.$valid')).toEqual('false');
+ expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if longer than max length', function() {
+ input('user.last').enter('some ridiculously long name');
+ expect(binding('user'))
+ .toEqual('{"name":"guest"}');
+ expect(binding('myForm.lastName.$valid')).toEqual('false');
+ expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:ngModel.NgModelController", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngModel.NgModelController");
+ });
+
+ it('should data-bind and become invalid', function() {
+ var contentEditable = element('[contenteditable]');
+
+ expect(contentEditable.text()).toEqual('Change me!');
+ input('userContent').enter('');
+ expect(contentEditable.text()).toEqual('');
+ expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
+ });
+
+});
+
+ describe("api/ng.directive:ngModel", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngModel");
+ });
+
+});
+
+ describe("api/ng.directive:ngChange", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngChange");
+ });
+
+ it('should evaluate the expression if changing from view', function() {
+ expect(binding('counter')).toEqual('0');
+ element('#ng-change-example1').click();
+ expect(binding('counter')).toEqual('1');
+ expect(binding('confirmed')).toEqual('true');
+ });
+
+ it('should not evaluate the expression if changing from model', function() {
+ element('#ng-change-example2').click();
+ expect(binding('counter')).toEqual('0');
+ expect(binding('confirmed')).toEqual('true');
+ });
+
+});
+
+ describe("api/ng.directive:ngList", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngList");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('names')).toEqual('["igor","misko","vojta"]');
+ expect(binding('myForm.namesInput.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('names').enter('');
+ expect(binding('names')).toEqual('[]');
+ expect(binding('myForm.namesInput.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:ngClass", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngClass");
+ });
+
+ it('should check ng-class', function() {
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/my-class/);
+
+ using('.doc-example-live').element(':button:first').click();
+
+ expect(element('.doc-example-live span').prop('className')).
+ toMatch(/my-class/);
+
+ using('.doc-example-live').element(':button:last').click();
+
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/my-class/);
+ });
+
+});
+
+ describe("api/ng.directive:ngClassOdd", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngClassOdd");
+ });
+
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+
+});
+
+ describe("api/ng.directive:ngClassEven", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngClassEven");
+ });
+
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+
+});
+
+ describe("api/ng.directive:ngCloak", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngCloak");
+ });
+
+ it('should remove the template directive and css class', function() {
+ expect(element('.doc-example-live #template1').attr('ng-cloak')).
+ not().toBeDefined();
+ expect(element('.doc-example-live #template2').attr('ng-cloak')).
+ not().toBeDefined();
+ });
+
+});
+
+ describe("api/ng.directive:ngController", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngController");
+ });
+
+ it('should check controller', function() {
+ expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
+ expect(element('.doc-example-live li:nth-child(1) input').val())
+ .toBe('408 555 1212');
+ expect(element('.doc-example-live li:nth-child(2) input').val())
+ .toBe('john.smith@example.org');
+
+ element('.doc-example-live li:first a:contains("clear")').click();
+ expect(element('.doc-example-live li:first input').val()).toBe('');
+
+ element('.doc-example-live li:last a:contains("add")').click();
+ expect(element('.doc-example-live li:nth-child(3) input').val())
+ .toBe('yourname@example.org');
+ });
+
+});
+
+ describe("api/ng.directive:ngCsp", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngCsp");
+ });
+
+});
+
+ describe("api/ng.directive:ngClick", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngClick");
+ });
+
+ it('should check ng-click', function() {
+ expect(binding('count')).toBe('0');
+ element('.doc-example-live :button').click();
+ expect(binding('count')).toBe('1');
+ });
+
+});
+
+ describe("api/ng.directive:ngDblclick", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngDblclick");
+ });
+
+});
+
+ describe("api/ng.directive:ngMousedown", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMousedown");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseup", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMouseup");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseover", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMouseover");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseenter", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMouseenter");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseleave", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMouseleave");
+ });
+
+});
+
+ describe("api/ng.directive:ngMousemove", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngMousemove");
+ });
+
+});
+
+ describe("api/ng.directive:ngKeydown", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngKeydown");
+ });
+
+});
+
+ describe("api/ng.directive:ngKeyup", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngKeyup");
+ });
+
+});
+
+ describe("api/ng.directive:ngSubmit", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngSubmit");
+ });
+
+ it('should check ng-submit', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ expect(input('text').val()).toBe('');
+ });
+ it('should ignore empty strings', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ });
+
+});
+
+ describe("api/ng.directive:ngInclude", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngInclude");
+ });
+
+ it('should load template1.html', function() {
+ expect(element('.doc-example-live [ng-include]').text()).
+ toMatch(/Content of template1.html/);
+ });
+ it('should load template2.html', function() {
+ select('template').option('1');
+ expect(element('.doc-example-live [ng-include]').text()).
+ toMatch(/Content of template2.html/);
+ });
+ it('should change to blank', function() {
+ select('template').option('');
+ expect(element('.doc-example-live [ng-include]').text()).toEqual('');
+ });
+
+});
+
+ describe("api/ng.directive:ngInit", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngInit");
+ });
+
+ it('should check greeting', function() {
+ expect(binding('greeting')).toBe('Hello');
+ expect(binding('person')).toBe('World');
+ });
+
+});
+
+ describe("api/ng.directive:ngNonBindable", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngNonBindable");
+ });
+
+ it('should check ng-non-bindable', function() {
+ expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
+ expect(using('.doc-example-live').element('div:last').text()).
+ toMatch(/1 \+ 2/);
+ });
+
+});
+
+ describe("api/ng.directive:ngPluralize", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngPluralize");
+ });
+
+ it('should show correct pluralized string', function() {
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('1 person is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('0');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('Nobody is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Nobody is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('2');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('2 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor and Misko are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('3');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('3 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and one other person are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('4 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+ });
+
+ it('should show data-binded names', function() {
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+
+ using('.doc-example-live').input('person1').enter('Di');
+ using('.doc-example-live').input('person2').enter('Vojta');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Di, Vojta and 2 other people are viewing.');
+ });
+
+});
+
+ describe("api/ng.directive:ngRepeat", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngRepeat");
+ });
+
+ it('should check ng-repeat', function() {
+ var r = using('.doc-example-live').repeater('ul li');
+ expect(r.count()).toBe(2);
+ expect(r.row(0)).toEqual(["1","John","25"]);
+ expect(r.row(1)).toEqual(["2","Mary","28"]);
+ });
+
+});
+
+ describe("api/ng.directive:ngShow", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngShow");
+ });
+
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+
+});
+
+ describe("api/ng.directive:ngHide", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngHide");
+ });
+
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+
+});
+
+ describe("api/ng.directive:ngStyle", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngStyle");
+ });
+
+ it('should check ng-style', function() {
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ element('.doc-example-live :button[value=set]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
+ element('.doc-example-live :button[value=clear]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ });
+
+});
+
+ describe("api/ng.directive:ngSwitch", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngSwitch");
+ });
+
+ it('should start in settings', function() {
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
+ });
+ it('should change to home', function() {
+ select('selection').option('home');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
+ });
+ it('should select deafault', function() {
+ select('selection').option('other');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
+ });
+
+});
+
+ describe("api/ng.directive:ngTransclude", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngTransclude");
+ });
+
+ it('should have transcluded', function() {
+ input('title').enter('TITLE');
+ input('text').enter('TEXT');
+ expect(binding('title')).toEqual('TITLE');
+ expect(binding('text')).toEqual('TEXT');
+ });
+
+});
+
+ describe("api/ng.directive:ngView", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:ngView");
+ });
+
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+
+});
+
+ describe("api/ng.directive:script", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:script");
+ });
+
+ it('should load template defined inside script tag', function() {
+ element('#tpl-link').click();
+ expect(element('#tpl-content').text()).toMatch(/Content of the template/);
+ });
+
+});
+
+ describe("api/ng.directive:select", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.directive:select");
+ });
+
+ it('should check ng-options', function() {
+ expect(binding('{selected_color:color}')).toMatch('red');
+ select('color').option('0');
+ expect(binding('{selected_color:color}')).toMatch('black');
+ using('.nullable').select('color').option('');
+ expect(binding('{selected_color:color}')).toMatch('null');
+ });
+
+});
+
+ describe("api/ng.$document", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$document");
+ });
+
+});
+
+ describe("api/ng.$exceptionHandler", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$exceptionHandler");
+ });
+
+});
+
+ describe("api/ng.filter:filter", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:filter");
+ });
+
+ it('should search across all fields when filtering with a string', function() {
+ input('searchText').enter('m');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Adam']);
+
+ input('searchText').enter('76');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['John', 'Julie']);
+ });
+
+ it('should search in specific fields when filtering with a predicate object', function() {
+ input('search.$').enter('i');
+ expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Julie']);
+ });
+
+});
+
+ describe("api/ng.filter:currency", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:currency");
+ });
+
+ it('should init with 1234.56', function() {
+ expect(binding('amount | currency')).toBe('$1,234.56');
+ expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
+ });
+ it('should update', function() {
+ input('amount').enter('-1234');
+ expect(binding('amount | currency')).toBe('($1,234.00)');
+ expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
+ });
+
+});
+
+ describe("api/ng.filter:number", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:number");
+ });
+
+ it('should format numbers', function() {
+ expect(binding('val | number')).toBe('1,234.568');
+ expect(binding('val | number:0')).toBe('1,235');
+ expect(binding('-val | number:4')).toBe('-1,234.5679');
+ });
+
+ it('should update', function() {
+ input('val').enter('3374.333');
+ expect(binding('val | number')).toBe('3,374.333');
+ expect(binding('val | number:0')).toBe('3,374');
+ expect(binding('-val | number:4')).toBe('-3,374.3330');
+ });
+
+});
+
+ describe("api/ng.filter:date", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:date");
+ });
+
+ it('should format date', function() {
+ expect(binding("1288323623006 | date:'medium'")).
+ toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
+ expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
+ toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
+ expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
+ toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
+ });
+
+});
+
+ describe("api/ng.filter:json", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:json");
+ });
+
+ it('should jsonify filtered objects', function() {
+ expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
+ });
+
+});
+
+ describe("api/ng.filter:lowercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:lowercase");
+ });
+
+});
+
+ describe("api/ng.filter:uppercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:uppercase");
+ });
+
+});
+
+ describe("api/ng.filter:limitTo", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:limitTo");
+ });
+
+ it('should limit the number array to first three items', function() {
+ expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
+ expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
+ });
+
+ it('should update the output when -3 is entered', function() {
+ input('numLimit').enter(-3);
+ input('letterLimit').enter(-3);
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
+ });
+
+ it('should not exceed the maximum size of input array', function() {
+ input('numLimit').enter(100);
+ input('letterLimit').enter(100);
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
+ });
+
+});
+
+ describe("api/ng.filter:orderBy", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.filter:orderBy");
+ });
+
+ it('should be reverse ordered by aged', function() {
+ expect(binding('predicate')).toBe('-age');
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '29', '21', '19', '10']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
+ });
+
+ it('should reorder the table when user selects different predicate', function() {
+ element('.doc-example-live a:contains("Name")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '10', '29', '19', '21']);
+
+ element('.doc-example-live a:contains("Phone")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
+ toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
+ });
+
+});
+
+ describe("api/ng.$filterProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$filterProvider");
+ });
+
+});
+
+ describe("api/ng.$filter", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$filter");
+ });
+
+});
+
+ describe("api/ng.$http", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$http");
+ });
+
+ it('should make an xhr GET request', function() {
+ element(':button:contains("Sample GET")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Hello, \$http!/);
+ });
+
+ it('should make a JSONP request to angularjs.org', function() {
+ element(':button:contains("Sample JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Super Hero!/);
+ });
+
+ it('should make JSONP request to invalid URL and invoke the error handler',
+ function() {
+ element(':button:contains("Invalid JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('0');
+ expect(binding('data')).toBe('Request failed');
+ });
+
+});
+
+ describe("api/ng.$httpBackend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$httpBackend");
+ });
+
+});
+
+ describe("api/ng.$interpolateProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$interpolateProvider");
+ });
+
+});
+
+ describe("api/ng.$interpolate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$interpolate");
+ });
+
+});
+
+ describe("api/ng.$locale", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$locale");
+ });
+
+});
+
+ describe("api/ng.$location", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$location");
+ });
+
+});
+
+ describe("api/ng.$locationProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$locationProvider");
+ });
+
+});
+
+ describe("api/ng.$log", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$log");
+ });
+
+});
+
+ describe("api/ng.$logProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$logProvider");
+ });
+
+});
+
+ describe("api/ng.$parse", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$parse");
+ });
+
+});
+
+ describe("api/ng.$q", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$q");
+ });
+
+});
+
+ describe("api/ng.$rootElement", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$rootElement");
+ });
+
+});
+
+ describe("api/ng.$rootScopeProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$rootScopeProvider");
+ });
+
+});
+
+ describe("api/ng.$rootScope", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$rootScope");
+ });
+
+});
+
+ describe("api/ng.$rootScope.Scope", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$rootScope.Scope");
+ });
+
+});
+
+ describe("api/ng.$routeProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$routeProvider");
+ });
+
+});
+
+ describe("api/ng.$route", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$route");
+ });
+
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ sleep(2); // promises are not part of scenario waiting
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+
+});
+
+ describe("api/ng.$routeParams", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$routeParams");
+ });
+
+});
+
+ describe("api/ng.$timeout", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$timeout");
+ });
+
+});
+
+ describe("api/ng.$window", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ng.$window");
+ });
+
+
+
+});
+
+ describe("api/ngCookies", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngCookies");
+ });
+
+});
+
+ describe("api/ngCookies.$cookies", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngCookies.$cookies");
+ });
+
+});
+
+ describe("api/ngCookies.$cookieStore", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngCookies.$cookieStore");
+ });
+
+});
+
+ describe("api/ngResource", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngResource");
+ });
+
+});
+
+ describe("api/ngResource.$resource", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngResource.$resource");
+ });
+
+
+
+});
+
+ describe("api/angular.mock", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.mock");
+ });
+
+});
+
+ describe("api/ngMock.$exceptionHandlerProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMock.$exceptionHandlerProvider");
+ });
+
+});
+
+ describe("api/ngMock.$exceptionHandler", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMock.$exceptionHandler");
+ });
+
+});
+
+ describe("api/ngMock.$log", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMock.$log");
+ });
+
+});
+
+ describe("api/angular.mock.TzDate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.mock.TzDate");
+ });
+
+});
+
+ describe("api/angular.mock.dump", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.mock.dump");
+ });
+
+});
+
+ describe("api/ngMock.$httpBackend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMock.$httpBackend");
+ });
+
+});
+
+ describe("api/ngMock.$timeout", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMock.$timeout");
+ });
+
+});
+
+ describe("api/ngMock", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMock");
+ });
+
+});
+
+ describe("api/ngMockE2E", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMockE2E");
+ });
+
+});
+
+ describe("api/ngMockE2E.$httpBackend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngMockE2E.$httpBackend");
+ });
+
+});
+
+ describe("api/angular.mock.module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.mock.module");
+ });
+
+});
+
+ describe("api/angular.mock.inject", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/angular.mock.inject");
+ });
+
+});
+
+ describe("api/ngSanitize.directive:ngBindHtml", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngSanitize.directive:ngBindHtml");
+ });
+
+});
+
+ describe("api/ngSanitize.filter:linky", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngSanitize.filter:linky");
+ });
+
+ it('should linkify the snippet with urls', function() {
+ expect(using('#linky-filter').binding('snippet | linky')).
+ toBe('Pretty text with some links:
' +
+ 'http://angularjs.org/ ,
' +
+ 'us@somewhere.org ,
' +
+ 'another@somewhere.org ,
' +
+ 'and one more: ftp://127.0.0.1/ .');
+ });
+
+ it ('should not linkify snippet without the linky filter', function() {
+ expect(using('#escaped-html').binding('snippet')).
+ toBe("Pretty text with some links:\n" +
+ "http://angularjs.org/,\n" +
+ "mailto:us@somewhere.org,\n" +
+ "another@somewhere.org,\n" +
+ "and one more: ftp://127.0.0.1/.");
+ });
+
+ it('should update', function() {
+ input('snippet').enter('new http://link.');
+ expect(using('#linky-filter').binding('snippet | linky')).
+ toBe('new http://link .');
+ expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
+ });
+
+ it('should work with the target property', function() {
+ expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
+ toBe('http://angularjs.org/ ');
+ });
+
+});
+
+ describe("api/ngSanitize", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngSanitize");
+ });
+
+});
+
+ describe("api/ngSanitize.$sanitize", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-nocache.html#!/api/ngSanitize.$sanitize");
+ });
+
+ it('should sanitize the html snippet ', function() {
+ expect(using('#html-filter').element('div').html()).
+ toBe('an html\nclick here \nsnippet
');
+ });
+
+ it('should escape snippet without any filter', function() {
+ expect(using('#escaped-html').element('div').html()).
+ toBe("<p style=\"color:blue\">an html\n" +
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
+ "snippet</p>");
+ });
+
+ it('should inline raw snippet if filtered as unsafe', function() {
+ expect(using('#html-unsafe-filter').element("div").html()).
+ toBe("an html\n" +
+ "click here \n" +
+ "snippet
");
+ });
+
+ it('should update', function() {
+ input('snippet').enter('new text ');
+ expect(using('#html-filter').binding('snippet')).toBe('new text ');
+ expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>");
+ expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text ');
+ });
+
+});
+
+});
+
+
+describe("angular+jquery", function() {
+ describe("api/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/index");
+ });
+
+});
+
+ describe("api/ng", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng");
+ });
+
+});
+
+ describe("cookbook/advancedform", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/advancedform");
+ });
+
+ it('should enable save button', function() {
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('');
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('change');
+ expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
+ element(':button:contains(Save)').click();
+ expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
+ });
+ it('should enable cancel button', function() {
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
+ input('form.name').enter('change');
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
+ element(':button:contains(Cancel)').click();
+ expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
+ expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
+ });
+
+});
+
+ describe("cookbook/buzz", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/buzz");
+ });
+
+ xit('fetch buzz and expand', function() {
+ element(':button:contains(fetch)').click();
+ expect(repeater('div.buzz').count()).toBeGreaterThan(0);
+ element('.buzz a:contains(Expand replies):first').click();
+ expect(repeater('div.reply').count()).toBeGreaterThan(0);
+ });
+
+});
+
+ describe("cookbook/deeplinking", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/deeplinking");
+ });
+
+ it('should navigate to URL', function() {
+ element('a:contains(Welcome)').click();
+ expect(element('[ng-view]').text()).toMatch(/Hello anonymous/);
+ element('a:contains(Settings)').click();
+ input('form.name').enter('yourname');
+ element(':button:contains(Save)').click();
+ element('a:contains(Welcome)').click();
+ expect(element('[ng-view]').text()).toMatch(/Hello yourname/);
+ });
+
+});
+
+ describe("cookbook/form", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/form");
+ });
+
+ it('should show debug', function() {
+ expect(binding('user')).toMatch(/John Smith/);
+ });
+ it('should add contact', function() {
+ using('.example').element('a:contains(add)').click();
+ using('.example div:last').input('contact.value').enter('you@example.org');
+ expect(binding('user')).toMatch(/\(234\) 555\-1212/);
+ expect(binding('user')).toMatch(/you@example.org/);
+ });
+
+ it('should remove contact', function() {
+ using('.example').element('a:contains(X)').click();
+ expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
+ });
+
+ it('should validate zip', function() {
+ expect(using('.example').
+ element(':input[ng\\:model="user.address.zip"]').
+ prop('className')).not().toMatch(/ng-invalid/);
+ using('.example').input('user.address.zip').enter('abc');
+ expect(using('.example').
+ element(':input[ng\\:model="user.address.zip"]').
+ prop('className')).toMatch(/ng-invalid/);
+ });
+
+ it('should validate state', function() {
+ expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
+ .not().toMatch(/ng-invalid/);
+ using('.example').input('user.address.state').enter('XXX');
+ expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
+ .toMatch(/ng-invalid/);
+ });
+
+});
+
+ describe("cookbook/helloworld", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/helloworld");
+ });
+
+ it('should change the binding when user enters text', function() {
+ expect(binding('name')).toEqual('World');
+ input('name').enter('angular');
+ expect(binding('name')).toEqual('angular');
+ });
+
+});
+
+ describe("cookbook/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/index");
+ });
+
+});
+
+ describe("cookbook/mvc", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/cookbook/mvc");
+ });
+
+ it('should play a game', function() {
+ piece(1, 1);
+ expect(binding('nextMove')).toEqual('O');
+ piece(3, 1);
+ expect(binding('nextMove')).toEqual('X');
+ piece(1, 2);
+ piece(3, 2);
+ piece(1, 3);
+ expect(element('.winner').text()).toEqual('Player X has won!');
+ });
+
+ function piece(row, col) {
+ element('.board tr:nth-child('+row+') td:nth-child('+col+')').click();
+ }
+
+});
+
+ describe("guide/bootstrap", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/bootstrap");
+ });
+
+});
+
+ describe("guide/compiler", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/compiler");
+ });
+
+});
+
+ describe("guide/concepts", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/concepts");
+ });
+
+});
+
+ describe("guide/dev_guide.e2e-testing", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.e2e-testing");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.mvc");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc.understanding_controller", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.mvc.understanding_controller");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc.understanding_model", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.mvc.understanding_model");
+ });
+
+});
+
+ describe("guide/dev_guide.mvc.understanding_view", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.mvc.understanding_view");
+ });
+
+});
+
+ describe("guide/dev_guide.services.$location", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services.$location");
+ });
+
+});
+
+ describe("guide/dev_guide.services.creating_services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services.creating_services");
+ });
+
+});
+
+ describe("guide/dev_guide.services.injecting_controllers", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services.injecting_controllers");
+ });
+
+ it('should test service', function() {
+ expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
+ });
+
+});
+
+ describe("guide/dev_guide.services.managing_dependencies", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services.managing_dependencies");
+ });
+
+});
+
+ describe("guide/dev_guide.services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services");
+ });
+
+});
+
+ describe("guide/dev_guide.services.testing_services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services.testing_services");
+ });
+
+});
+
+ describe("guide/dev_guide.services.understanding_services", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.services.understanding_services");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.css-styling", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.templates.css-styling");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.databinding", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.templates.databinding");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.filters.creating_filters", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.templates.filters.creating_filters");
+ });
+
+ it('should reverse greeting', function() {
+ expect(binding('greeting|reverse')).toEqual('olleh');
+ input('greeting').enter('ABC');
+ expect(binding('greeting|reverse')).toEqual('CBA');
+ });
+
+});
+
+ describe("guide/dev_guide.templates.filters", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.templates.filters");
+ });
+
+});
+
+ describe("guide/dev_guide.templates.filters.using_filters", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.templates.filters.using_filters");
+ });
+
+});
+
+ describe("guide/dev_guide.templates", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.templates");
+ });
+
+});
+
+ describe("guide/dev_guide.unit-testing", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/dev_guide.unit-testing");
+ });
+
+});
+
+ describe("guide/di", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/di");
+ });
+
+});
+
+ describe("guide/directive", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/directive");
+ });
+
+ it('should show off bindings', function() {
+ expect(element('div[ng-controller="Ctrl1"] span[ng-bind]').text()).toBe('angular');
+ });
+
+ it('should bind and open / close', function() {
+ input('title').enter('TITLE');
+ input('text').enter('TEXT');
+ expect(element('.title').text()).toEqual('Details: TITLE...');
+ expect(binding('text')).toEqual('TEXT');
+
+ expect(element('.zippy').prop('className')).toMatch(/closed/);
+ element('.zippy > .title').click();
+ expect(element('.zippy').prop('className')).toMatch(/opened/);
+ });
+
+});
+
+ describe("guide/expression", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/expression");
+ });
+
+ it('should calculate expression in binding', function() {
+ expect(binding('1+2')).toEqual('3');
+ });
+
+ it('should allow user expression testing', function() {
+ element('.expressions :button').click();
+ var li = using('.expressions ul').repeater('li');
+ expect(li.count()).toBe(1);
+ expect(li.row(0)).toEqual(["3*10|currency", "$30.00"]);
+ });
+
+ it('should calculate expression in binding', function() {
+ var alertText;
+ this.addFutureAction('set mock', function($window, $document, done) {
+ $window.mockWindow = {
+ alert: function(text){ alertText = text; }
+ };
+ done();
+ });
+ element(':button:contains(Greet)').click();
+ expect(this.addFuture('alert text', function(done) {
+ done(null, alertText);
+ })).toBe('Hello World');
+ });
+
+});
+
+ describe("guide/forms", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/forms");
+ });
+
+});
+
+ describe("guide/i18n", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/i18n");
+ });
+
+});
+
+ describe("guide/ie", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/ie");
+ });
+
+});
+
+ describe("guide/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/index");
+ });
+
+});
+
+ describe("guide/introduction", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/introduction");
+ });
+
+});
+
+ describe("guide/module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/module");
+ });
+
+});
+
+ describe("guide/overview", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/overview");
+ });
+
+ it('should show of angular binding', function() {
+ expect(binding('qty * cost')).toEqual('$19.95');
+ input('qty').enter('2');
+ input('cost').enter('5.00');
+ expect(binding('qty * cost')).toEqual('$10.00');
+ });
+
+});
+
+ describe("guide/scope", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/scope");
+ });
+
+});
+
+ describe("guide/type", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/guide/type");
+ });
+
+});
+
+ describe("misc/contribute", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/misc/contribute");
+ });
+
+});
+
+ describe("misc/downloading", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/misc/downloading");
+ });
+
+});
+
+ describe("misc/faq", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/misc/faq");
+ });
+
+});
+
+ describe("misc/started", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/misc/started");
+ });
+
+});
+
+ describe("tutorial/index", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/index");
+ });
+
+});
+
+ describe("tutorial/step_00", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_00");
+ });
+
+});
+
+ describe("tutorial/step_01", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_01");
+ });
+
+});
+
+ describe("tutorial/step_02", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_02");
+ });
+
+});
+
+ describe("tutorial/step_03", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_03");
+ });
+
+});
+
+ describe("tutorial/step_04", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_04");
+ });
+
+});
+
+ describe("tutorial/step_05", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_05");
+ });
+
+});
+
+ describe("tutorial/step_06", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_06");
+ });
+
+});
+
+ describe("tutorial/step_07", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_07");
+ });
+
+});
+
+ describe("tutorial/step_08", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_08");
+ });
+
+});
+
+ describe("tutorial/step_09", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_09");
+ });
+
+});
+
+ describe("tutorial/step_10", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_10");
+ });
+
+});
+
+ describe("tutorial/step_11", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/step_11");
+ });
+
+});
+
+ describe("tutorial/the_end", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/tutorial/the_end");
+ });
+
+});
+
+ describe("api/angular.lowercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.lowercase");
+ });
+
+});
+
+ describe("api/angular.uppercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.uppercase");
+ });
+
+});
+
+ describe("api/angular.forEach", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.forEach");
+ });
+
+});
+
+ describe("api/angular.extend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.extend");
+ });
+
+});
+
+ describe("api/angular.noop", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.noop");
+ });
+
+});
+
+ describe("api/angular.identity", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.identity");
+ });
+
+});
+
+ describe("api/angular.isUndefined", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isUndefined");
+ });
+
+});
+
+ describe("api/angular.isDefined", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isDefined");
+ });
+
+});
+
+ describe("api/angular.isObject", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isObject");
+ });
+
+});
+
+ describe("api/angular.isString", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isString");
+ });
+
+});
+
+ describe("api/angular.isNumber", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isNumber");
+ });
+
+});
+
+ describe("api/angular.isDate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isDate");
+ });
+
+});
+
+ describe("api/angular.isArray", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isArray");
+ });
+
+});
+
+ describe("api/angular.isFunction", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isFunction");
+ });
+
+});
+
+ describe("api/angular.isElement", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.isElement");
+ });
+
+});
+
+ describe("api/angular.copy", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.copy");
+ });
+
+});
+
+ describe("api/angular.equals", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.equals");
+ });
+
+});
+
+ describe("api/angular.bind", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.bind");
+ });
+
+});
+
+ describe("api/angular.toJson", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.toJson");
+ });
+
+});
+
+ describe("api/angular.fromJson", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.fromJson");
+ });
+
+});
+
+ describe("api/ng.directive:ngApp", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngApp");
+ });
+
+});
+
+ describe("api/angular.bootstrap", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.bootstrap");
+ });
+
+});
+
+ describe("api/angular.version", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.version");
+ });
+
+});
+
+ describe("api/angular.injector", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.injector");
+ });
+
+});
+
+ describe("api/AUTO", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/AUTO");
+ });
+
+});
+
+ describe("api/AUTO.$injector", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/AUTO.$injector");
+ });
+
+});
+
+ describe("api/AUTO.$provide", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/AUTO.$provide");
+ });
+
+});
+
+ describe("api/angular.element", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.element");
+ });
+
+});
+
+ describe("api/angular.Module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.Module");
+ });
+
+});
+
+ describe("api/angular.module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.module");
+ });
+
+});
+
+ describe("api/ng.$anchorScroll", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$anchorScroll");
+ });
+
+});
+
+ describe("api/ng.$cacheFactory", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$cacheFactory");
+ });
+
+});
+
+ describe("api/ng.$templateCache", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$templateCache");
+ });
+
+});
+
+ describe("api/ng.$controllerProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$controllerProvider");
+ });
+
+});
+
+ describe("api/ng.$controller", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$controller");
+ });
+
+});
+
+ describe("api/ng.$compile", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$compile");
+ });
+
+ it('should auto compile', function() {
+ expect(element('div[compile]').text()).toBe('Hello Angular');
+ input('html').enter('{{name}}!');
+ expect(element('div[compile]').text()).toBe('Angular!');
+ });
+
+});
+
+ describe("api/ng.$compileProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$compileProvider");
+ });
+
+});
+
+ describe("api/ng.$compile.directive.Attributes", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$compile.directive.Attributes");
+ });
+
+});
+
+ describe("api/ng.directive:a", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:a");
+ });
+
+});
+
+ describe("api/ng.directive:ngHref", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngHref");
+ });
+
+ it('should execute ng-click but not reload when href without value', function() {
+ element('#link-1').click();
+ expect(input('value').val()).toEqual('1');
+ expect(element('#link-1').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click but not reload when href empty string', function() {
+ element('#link-2').click();
+ expect(input('value').val()).toEqual('2');
+ expect(element('#link-2').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click and change url when ng-href specified', function() {
+ expect(element('#link-3').attr('href')).toBe("/123");
+
+ element('#link-3').click();
+ expect(browser().window().path()).toEqual('/123');
+ });
+
+ it('should execute ng-click but not reload when href empty string and name specified', function() {
+ element('#link-4').click();
+ expect(input('value').val()).toEqual('4');
+ expect(element('#link-4').attr('href')).toBe('');
+ });
+
+ it('should execute ng-click but not reload when no href but name specified', function() {
+ element('#link-5').click();
+ expect(input('value').val()).toEqual('5');
+ expect(element('#link-5').attr('href')).toBe('');
+ });
+
+ it('should only change url when only ng-href', function() {
+ input('value').enter('6');
+ expect(element('#link-6').attr('href')).toBe('6');
+
+ element('#link-6').click();
+ expect(browser().location().url()).toEqual('/6');
+ });
+
+});
+
+ describe("api/ng.directive:ngSrc", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngSrc");
+ });
+
+});
+
+ describe("api/ng.directive:ngDisabled", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngDisabled");
+ });
+
+ it('should toggle button', function() {
+ expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngChecked", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngChecked");
+ });
+
+ it('should check both checkBoxes', function() {
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
+ input('master').check();
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngMultiple", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMultiple");
+ });
+
+ it('should toggle multiple', function() {
+ expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngReadonly", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngReadonly");
+ });
+
+ it('should toggle readonly attr', function() {
+ expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngSelected", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngSelected");
+ });
+
+ it('should select Greetings!', function() {
+ expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
+ input('selected').check();
+ expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:ngOpen", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngOpen");
+ });
+
+ it('should toggle open', function() {
+ expect(element('#details').prop('open')).toBeFalsy();
+ input('open').check();
+ expect(element('#details').prop('open')).toBeTruthy();
+ });
+
+});
+
+ describe("api/ng.directive:form.FormController", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:form.FormController");
+ });
+
+});
+
+ describe("api/ng.directive:ngForm", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngForm");
+ });
+
+});
+
+ describe("api/ng.directive:form", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:form");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('userType')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('userType').enter('');
+ expect(binding('userType')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:ngBind", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngBind");
+ });
+
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('name')).toBe('Whirled');
+ using('.doc-example-live').input('name').enter('world');
+ expect(using('.doc-example-live').binding('name')).toBe('world');
+ });
+
+});
+
+ describe("api/ng.directive:ngBindTemplate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngBindTemplate");
+ });
+
+ it('should check ng-bind', function() {
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Hello');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('World');
+ using('.doc-example-live').input('salutation').enter('Greetings');
+ using('.doc-example-live').input('name').enter('user');
+ expect(using('.doc-example-live').binding('salutation')).
+ toBe('Greetings');
+ expect(using('.doc-example-live').binding('name')).
+ toBe('user');
+ });
+
+});
+
+ describe("api/ng.directive:ngBindHtmlUnsafe", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngBindHtmlUnsafe");
+ });
+
+});
+
+ describe("api/ng.directive:input.text", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input.text");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('guest');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if multi word', function() {
+ input('text').enter('hello world');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should not be trimmed', function() {
+ input('text').enter('untrimmed ');
+ expect(binding('text')).toEqual('untrimmed ');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+});
+
+ describe("api/ng.directive:input.number", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input.number");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('value')).toEqual('12');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('value').enter('');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if over max', function() {
+ input('value').enter('123');
+ expect(binding('value')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:input.url", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input.url");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('http://google.com');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if not url', function() {
+ input('text').enter('xxx');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:input.email", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input.email");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('text')).toEqual('me@example.com');
+ expect(binding('myForm.input.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('text').enter('');
+ expect(binding('text')).toEqual('');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if not email', function() {
+ input('text').enter('xxx');
+ expect(binding('myForm.input.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:input.radio", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input.radio");
+ });
+
+ it('should change state', function() {
+ expect(binding('color')).toEqual('blue');
+
+ input('color').select('red');
+ expect(binding('color')).toEqual('red');
+ });
+
+});
+
+ describe("api/ng.directive:input.checkbox", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input.checkbox");
+ });
+
+ it('should change state', function() {
+ expect(binding('value1')).toEqual('true');
+ expect(binding('value2')).toEqual('YES');
+
+ input('value1').check();
+ input('value2').check();
+ expect(binding('value1')).toEqual('false');
+ expect(binding('value2')).toEqual('NO');
+ });
+
+});
+
+ describe("api/ng.directive:textarea", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:textarea");
+ });
+
+});
+
+ describe("api/ng.directive:input", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:input");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}');
+ expect(binding('myForm.userName.$valid')).toEqual('true');
+ expect(binding('myForm.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty when required', function() {
+ input('user.name').enter('');
+ expect(binding('user')).toEqual('{"last":"visitor"}');
+ expect(binding('myForm.userName.$valid')).toEqual('false');
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+ it('should be valid if empty when min length is set', function() {
+ input('user.last').enter('');
+ expect(binding('user')).toEqual('{"name":"guest","last":""}');
+ expect(binding('myForm.lastName.$valid')).toEqual('true');
+ expect(binding('myForm.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if less than required min length', function() {
+ input('user.last').enter('xx');
+ expect(binding('user')).toEqual('{"name":"guest"}');
+ expect(binding('myForm.lastName.$valid')).toEqual('false');
+ expect(binding('myForm.lastName.$error')).toMatch(/minlength/);
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+ it('should be invalid if longer than max length', function() {
+ input('user.last').enter('some ridiculously long name');
+ expect(binding('user'))
+ .toEqual('{"name":"guest"}');
+ expect(binding('myForm.lastName.$valid')).toEqual('false');
+ expect(binding('myForm.lastName.$error')).toMatch(/maxlength/);
+ expect(binding('myForm.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:ngModel.NgModelController", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngModel.NgModelController");
+ });
+
+ it('should data-bind and become invalid', function() {
+ var contentEditable = element('[contenteditable]');
+
+ expect(contentEditable.text()).toEqual('Change me!');
+ input('userContent').enter('');
+ expect(contentEditable.text()).toEqual('');
+ expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/);
+ });
+
+});
+
+ describe("api/ng.directive:ngModel", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngModel");
+ });
+
+});
+
+ describe("api/ng.directive:ngChange", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngChange");
+ });
+
+ it('should evaluate the expression if changing from view', function() {
+ expect(binding('counter')).toEqual('0');
+ element('#ng-change-example1').click();
+ expect(binding('counter')).toEqual('1');
+ expect(binding('confirmed')).toEqual('true');
+ });
+
+ it('should not evaluate the expression if changing from model', function() {
+ element('#ng-change-example2').click();
+ expect(binding('counter')).toEqual('0');
+ expect(binding('confirmed')).toEqual('true');
+ });
+
+});
+
+ describe("api/ng.directive:ngList", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngList");
+ });
+
+ it('should initialize to model', function() {
+ expect(binding('names')).toEqual('["igor","misko","vojta"]');
+ expect(binding('myForm.namesInput.$valid')).toEqual('true');
+ });
+
+ it('should be invalid if empty', function() {
+ input('names').enter('');
+ expect(binding('names')).toEqual('[]');
+ expect(binding('myForm.namesInput.$valid')).toEqual('false');
+ });
+
+});
+
+ describe("api/ng.directive:ngClass", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngClass");
+ });
+
+ it('should check ng-class', function() {
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/my-class/);
+
+ using('.doc-example-live').element(':button:first').click();
+
+ expect(element('.doc-example-live span').prop('className')).
+ toMatch(/my-class/);
+
+ using('.doc-example-live').element(':button:last').click();
+
+ expect(element('.doc-example-live span').prop('className')).not().
+ toMatch(/my-class/);
+ });
+
+});
+
+ describe("api/ng.directive:ngClassOdd", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngClassOdd");
+ });
+
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+
+});
+
+ describe("api/ng.directive:ngClassEven", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngClassEven");
+ });
+
+ it('should check ng-class-odd and ng-class-even', function() {
+ expect(element('.doc-example-live li:first span').prop('className')).
+ toMatch(/odd/);
+ expect(element('.doc-example-live li:last span').prop('className')).
+ toMatch(/even/);
+ });
+
+});
+
+ describe("api/ng.directive:ngCloak", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngCloak");
+ });
+
+ it('should remove the template directive and css class', function() {
+ expect(element('.doc-example-live #template1').attr('ng-cloak')).
+ not().toBeDefined();
+ expect(element('.doc-example-live #template2').attr('ng-cloak')).
+ not().toBeDefined();
+ });
+
+});
+
+ describe("api/ng.directive:ngController", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngController");
+ });
+
+ it('should check controller', function() {
+ expect(element('.doc-example-live div>:input').val()).toBe('John Smith');
+ expect(element('.doc-example-live li:nth-child(1) input').val())
+ .toBe('408 555 1212');
+ expect(element('.doc-example-live li:nth-child(2) input').val())
+ .toBe('john.smith@example.org');
+
+ element('.doc-example-live li:first a:contains("clear")').click();
+ expect(element('.doc-example-live li:first input').val()).toBe('');
+
+ element('.doc-example-live li:last a:contains("add")').click();
+ expect(element('.doc-example-live li:nth-child(3) input').val())
+ .toBe('yourname@example.org');
+ });
+
+});
+
+ describe("api/ng.directive:ngCsp", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngCsp");
+ });
+
+});
+
+ describe("api/ng.directive:ngClick", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngClick");
+ });
+
+ it('should check ng-click', function() {
+ expect(binding('count')).toBe('0');
+ element('.doc-example-live :button').click();
+ expect(binding('count')).toBe('1');
+ });
+
+});
+
+ describe("api/ng.directive:ngDblclick", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngDblclick");
+ });
+
+});
+
+ describe("api/ng.directive:ngMousedown", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMousedown");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseup", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMouseup");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseover", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMouseover");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseenter", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMouseenter");
+ });
+
+});
+
+ describe("api/ng.directive:ngMouseleave", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMouseleave");
+ });
+
+});
+
+ describe("api/ng.directive:ngMousemove", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngMousemove");
+ });
+
+});
+
+ describe("api/ng.directive:ngKeydown", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngKeydown");
+ });
+
+});
+
+ describe("api/ng.directive:ngKeyup", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngKeyup");
+ });
+
+});
+
+ describe("api/ng.directive:ngSubmit", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngSubmit");
+ });
+
+ it('should check ng-submit', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ expect(input('text').val()).toBe('');
+ });
+ it('should ignore empty strings', function() {
+ expect(binding('list')).toBe('[]');
+ element('.doc-example-live #submit').click();
+ element('.doc-example-live #submit').click();
+ expect(binding('list')).toBe('["hello"]');
+ });
+
+});
+
+ describe("api/ng.directive:ngInclude", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngInclude");
+ });
+
+ it('should load template1.html', function() {
+ expect(element('.doc-example-live [ng-include]').text()).
+ toMatch(/Content of template1.html/);
+ });
+ it('should load template2.html', function() {
+ select('template').option('1');
+ expect(element('.doc-example-live [ng-include]').text()).
+ toMatch(/Content of template2.html/);
+ });
+ it('should change to blank', function() {
+ select('template').option('');
+ expect(element('.doc-example-live [ng-include]').text()).toEqual('');
+ });
+
+});
+
+ describe("api/ng.directive:ngInit", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngInit");
+ });
+
+ it('should check greeting', function() {
+ expect(binding('greeting')).toBe('Hello');
+ expect(binding('person')).toBe('World');
+ });
+
+});
+
+ describe("api/ng.directive:ngNonBindable", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngNonBindable");
+ });
+
+ it('should check ng-non-bindable', function() {
+ expect(using('.doc-example-live').binding('1 + 2')).toBe('3');
+ expect(using('.doc-example-live').element('div:last').text()).
+ toMatch(/1 \+ 2/);
+ });
+
+});
+
+ describe("api/ng.directive:ngPluralize", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngPluralize");
+ });
+
+ it('should show correct pluralized string', function() {
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('1 person is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('0');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('Nobody is viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Nobody is viewing.');
+
+ using('.doc-example-live').input('personCount').enter('2');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('2 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor and Misko are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('3');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('3 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and one other person are viewing.');
+
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:first').text()).
+ toBe('4 people are viewing.');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+ });
+
+ it('should show data-binded names', function() {
+ using('.doc-example-live').input('personCount').enter('4');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Igor, Misko and 2 other people are viewing.');
+
+ using('.doc-example-live').input('person1').enter('Di');
+ using('.doc-example-live').input('person2').enter('Vojta');
+ expect(element('.doc-example-live ng-pluralize:last').text()).
+ toBe('Di, Vojta and 2 other people are viewing.');
+ });
+
+});
+
+ describe("api/ng.directive:ngRepeat", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngRepeat");
+ });
+
+ it('should check ng-repeat', function() {
+ var r = using('.doc-example-live').repeater('ul li');
+ expect(r.count()).toBe(2);
+ expect(r.row(0)).toEqual(["1","John","25"]);
+ expect(r.row(1)).toEqual(["2","Mary","28"]);
+ });
+
+});
+
+ describe("api/ng.directive:ngShow", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngShow");
+ });
+
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+
+});
+
+ describe("api/ng.directive:ngHide", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngHide");
+ });
+
+ it('should check ng-show / ng-hide', function() {
+ expect(element('.doc-example-live span:first:hidden').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:visible').count()).toEqual(1);
+
+ input('checked').check();
+
+ expect(element('.doc-example-live span:first:visible').count()).toEqual(1);
+ expect(element('.doc-example-live span:last:hidden').count()).toEqual(1);
+ });
+
+});
+
+ describe("api/ng.directive:ngStyle", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngStyle");
+ });
+
+ it('should check ng-style', function() {
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ element('.doc-example-live :button[value=set]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)');
+ element('.doc-example-live :button[value=clear]').click();
+ expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)');
+ });
+
+});
+
+ describe("api/ng.directive:ngSwitch", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngSwitch");
+ });
+
+ it('should start in settings', function() {
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
+ });
+ it('should change to home', function() {
+ select('selection').option('home');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
+ });
+ it('should select deafault', function() {
+ select('selection').option('other');
+ expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
+ });
+
+});
+
+ describe("api/ng.directive:ngTransclude", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngTransclude");
+ });
+
+ it('should have transcluded', function() {
+ input('title').enter('TITLE');
+ input('text').enter('TEXT');
+ expect(binding('title')).toEqual('TITLE');
+ expect(binding('text')).toEqual('TEXT');
+ });
+
+});
+
+ describe("api/ng.directive:ngView", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:ngView");
+ });
+
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+
+});
+
+ describe("api/ng.directive:script", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:script");
+ });
+
+ it('should load template defined inside script tag', function() {
+ element('#tpl-link').click();
+ expect(element('#tpl-content').text()).toMatch(/Content of the template/);
+ });
+
+});
+
+ describe("api/ng.directive:select", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.directive:select");
+ });
+
+ it('should check ng-options', function() {
+ expect(binding('{selected_color:color}')).toMatch('red');
+ select('color').option('0');
+ expect(binding('{selected_color:color}')).toMatch('black');
+ using('.nullable').select('color').option('');
+ expect(binding('{selected_color:color}')).toMatch('null');
+ });
+
+});
+
+ describe("api/ng.$document", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$document");
+ });
+
+});
+
+ describe("api/ng.$exceptionHandler", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$exceptionHandler");
+ });
+
+});
+
+ describe("api/ng.filter:filter", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:filter");
+ });
+
+ it('should search across all fields when filtering with a string', function() {
+ input('searchText').enter('m');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Adam']);
+
+ input('searchText').enter('76');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['John', 'Julie']);
+ });
+
+ it('should search in specific fields when filtering with a predicate object', function() {
+ input('search.$').enter('i');
+ expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Julie']);
+ });
+
+});
+
+ describe("api/ng.filter:currency", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:currency");
+ });
+
+ it('should init with 1234.56', function() {
+ expect(binding('amount | currency')).toBe('$1,234.56');
+ expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
+ });
+ it('should update', function() {
+ input('amount').enter('-1234');
+ expect(binding('amount | currency')).toBe('($1,234.00)');
+ expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
+ });
+
+});
+
+ describe("api/ng.filter:number", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:number");
+ });
+
+ it('should format numbers', function() {
+ expect(binding('val | number')).toBe('1,234.568');
+ expect(binding('val | number:0')).toBe('1,235');
+ expect(binding('-val | number:4')).toBe('-1,234.5679');
+ });
+
+ it('should update', function() {
+ input('val').enter('3374.333');
+ expect(binding('val | number')).toBe('3,374.333');
+ expect(binding('val | number:0')).toBe('3,374');
+ expect(binding('-val | number:4')).toBe('-3,374.3330');
+ });
+
+});
+
+ describe("api/ng.filter:date", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:date");
+ });
+
+ it('should format date', function() {
+ expect(binding("1288323623006 | date:'medium'")).
+ toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
+ expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
+ toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
+ expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
+ toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
+ });
+
+});
+
+ describe("api/ng.filter:json", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:json");
+ });
+
+ it('should jsonify filtered objects', function() {
+ expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
+ });
+
+});
+
+ describe("api/ng.filter:lowercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:lowercase");
+ });
+
+});
+
+ describe("api/ng.filter:uppercase", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:uppercase");
+ });
+
+});
+
+ describe("api/ng.filter:limitTo", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:limitTo");
+ });
+
+ it('should limit the number array to first three items', function() {
+ expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
+ expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
+ });
+
+ it('should update the output when -3 is entered', function() {
+ input('numLimit').enter(-3);
+ input('letterLimit').enter(-3);
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
+ });
+
+ it('should not exceed the maximum size of input array', function() {
+ input('numLimit').enter(100);
+ input('letterLimit').enter(100);
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
+ });
+
+});
+
+ describe("api/ng.filter:orderBy", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.filter:orderBy");
+ });
+
+ it('should be reverse ordered by aged', function() {
+ expect(binding('predicate')).toBe('-age');
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '29', '21', '19', '10']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
+ });
+
+ it('should reorder the table when user selects different predicate', function() {
+ element('.doc-example-live a:contains("Name")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '10', '29', '19', '21']);
+
+ element('.doc-example-live a:contains("Phone")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
+ toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
+ });
+
+});
+
+ describe("api/ng.$filterProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$filterProvider");
+ });
+
+});
+
+ describe("api/ng.$filter", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$filter");
+ });
+
+});
+
+ describe("api/ng.$http", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$http");
+ });
+
+ it('should make an xhr GET request', function() {
+ element(':button:contains("Sample GET")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Hello, \$http!/);
+ });
+
+ it('should make a JSONP request to angularjs.org', function() {
+ element(':button:contains("Sample JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Super Hero!/);
+ });
+
+ it('should make JSONP request to invalid URL and invoke the error handler',
+ function() {
+ element(':button:contains("Invalid JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('0');
+ expect(binding('data')).toBe('Request failed');
+ });
+
+});
+
+ describe("api/ng.$httpBackend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$httpBackend");
+ });
+
+});
+
+ describe("api/ng.$interpolateProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$interpolateProvider");
+ });
+
+});
+
+ describe("api/ng.$interpolate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$interpolate");
+ });
+
+});
+
+ describe("api/ng.$locale", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$locale");
+ });
+
+});
+
+ describe("api/ng.$location", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$location");
+ });
+
+});
+
+ describe("api/ng.$locationProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$locationProvider");
+ });
+
+});
+
+ describe("api/ng.$log", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$log");
+ });
+
+});
+
+ describe("api/ng.$logProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$logProvider");
+ });
+
+});
+
+ describe("api/ng.$parse", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$parse");
+ });
+
+});
+
+ describe("api/ng.$q", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$q");
+ });
+
+});
+
+ describe("api/ng.$rootElement", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$rootElement");
+ });
+
+});
+
+ describe("api/ng.$rootScopeProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$rootScopeProvider");
+ });
+
+});
+
+ describe("api/ng.$rootScope", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$rootScope");
+ });
+
+});
+
+ describe("api/ng.$rootScope.Scope", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$rootScope.Scope");
+ });
+
+});
+
+ describe("api/ng.$routeProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$routeProvider");
+ });
+
+});
+
+ describe("api/ng.$route", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$route");
+ });
+
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ sleep(2); // promises are not part of scenario waiting
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+
+});
+
+ describe("api/ng.$routeParams", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$routeParams");
+ });
+
+});
+
+ describe("api/ng.$timeout", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$timeout");
+ });
+
+});
+
+ describe("api/ng.$window", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ng.$window");
+ });
+
+
+
+});
+
+ describe("api/ngCookies", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngCookies");
+ });
+
+});
+
+ describe("api/ngCookies.$cookies", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngCookies.$cookies");
+ });
+
+});
+
+ describe("api/ngCookies.$cookieStore", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngCookies.$cookieStore");
+ });
+
+});
+
+ describe("api/ngResource", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngResource");
+ });
+
+});
+
+ describe("api/ngResource.$resource", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngResource.$resource");
+ });
+
+
+
+});
+
+ describe("api/angular.mock", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.mock");
+ });
+
+});
+
+ describe("api/ngMock.$exceptionHandlerProvider", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMock.$exceptionHandlerProvider");
+ });
+
+});
+
+ describe("api/ngMock.$exceptionHandler", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMock.$exceptionHandler");
+ });
+
+});
+
+ describe("api/ngMock.$log", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMock.$log");
+ });
+
+});
+
+ describe("api/angular.mock.TzDate", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.mock.TzDate");
+ });
+
+});
+
+ describe("api/angular.mock.dump", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.mock.dump");
+ });
+
+});
+
+ describe("api/ngMock.$httpBackend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMock.$httpBackend");
+ });
+
+});
+
+ describe("api/ngMock.$timeout", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMock.$timeout");
+ });
+
+});
+
+ describe("api/ngMock", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMock");
+ });
+
+});
+
+ describe("api/ngMockE2E", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMockE2E");
+ });
+
+});
+
+ describe("api/ngMockE2E.$httpBackend", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngMockE2E.$httpBackend");
+ });
+
+});
+
+ describe("api/angular.mock.module", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.mock.module");
+ });
+
+});
+
+ describe("api/angular.mock.inject", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/angular.mock.inject");
+ });
+
+});
+
+ describe("api/ngSanitize.directive:ngBindHtml", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngSanitize.directive:ngBindHtml");
+ });
+
+});
+
+ describe("api/ngSanitize.filter:linky", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngSanitize.filter:linky");
+ });
+
+ it('should linkify the snippet with urls', function() {
+ expect(using('#linky-filter').binding('snippet | linky')).
+ toBe('Pretty text with some links:
' +
+ 'http://angularjs.org/ ,
' +
+ 'us@somewhere.org ,
' +
+ 'another@somewhere.org ,
' +
+ 'and one more: ftp://127.0.0.1/ .');
+ });
+
+ it ('should not linkify snippet without the linky filter', function() {
+ expect(using('#escaped-html').binding('snippet')).
+ toBe("Pretty text with some links:\n" +
+ "http://angularjs.org/,\n" +
+ "mailto:us@somewhere.org,\n" +
+ "another@somewhere.org,\n" +
+ "and one more: ftp://127.0.0.1/.");
+ });
+
+ it('should update', function() {
+ input('snippet').enter('new http://link.');
+ expect(using('#linky-filter').binding('snippet | linky')).
+ toBe('new http://link .');
+ expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
+ });
+
+ it('should work with the target property', function() {
+ expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
+ toBe('http://angularjs.org/ ');
+ });
+
+});
+
+ describe("api/ngSanitize", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngSanitize");
+ });
+
+});
+
+ describe("api/ngSanitize.$sanitize", function() {
+ beforeEach(function() {
+ browser().navigateTo("index-jq-nocache.html#!/api/ngSanitize.$sanitize");
+ });
+
+ it('should sanitize the html snippet ', function() {
+ expect(using('#html-filter').element('div').html()).
+ toBe('an html\nclick here \nsnippet
');
+ });
+
+ it('should escape snippet without any filter', function() {
+ expect(using('#escaped-html').element('div').html()).
+ toBe("<p style=\"color:blue\">an html\n" +
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
+ "snippet</p>");
+ });
+
+ it('should inline raw snippet if filtered as unsafe', function() {
+ expect(using('#html-unsafe-filter').element("div").html()).
+ toBe("an html\n" +
+ "click here \n" +
+ "snippet
");
+ });
+
+ it('should update', function() {
+ input('snippet').enter('new text ');
+ expect(using('#html-filter').binding('snippet')).toBe('new text ');
+ expect(using('#escaped-html').element('div').html()).toBe("new <b>text</b>");
+ expect(using('#html-unsafe-filter').binding("snippet")).toBe('new text ');
+ });
+
+});
+
+});
\ No newline at end of file
diff --git a/lib/angular/docs/favicon.ico b/lib/angular/docs/favicon.ico
new file mode 100644
index 0000000..fe24a63
Binary files /dev/null and b/lib/angular/docs/favicon.ico differ
diff --git a/lib/angular/docs/font/fontawesome-webfont.eot b/lib/angular/docs/font/fontawesome-webfont.eot
new file mode 100755
index 0000000..3f669a7
Binary files /dev/null and b/lib/angular/docs/font/fontawesome-webfont.eot differ
diff --git a/lib/angular/docs/font/fontawesome-webfont.svg b/lib/angular/docs/font/fontawesome-webfont.svg
new file mode 100755
index 0000000..73c0ad9
--- /dev/null
+++ b/lib/angular/docs/font/fontawesome-webfont.svg
@@ -0,0 +1,175 @@
+
+
+
+
+This is a custom SVG webfont generated by Font Squirrel.
+Designer : Dave Gandy
+Foundry : Fort Awesome
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lib/angular/docs/font/fontawesome-webfont.svgz b/lib/angular/docs/font/fontawesome-webfont.svgz
new file mode 100755
index 0000000..2a73cd7
Binary files /dev/null and b/lib/angular/docs/font/fontawesome-webfont.svgz differ
diff --git a/lib/angular/docs/font/fontawesome-webfont.ttf b/lib/angular/docs/font/fontawesome-webfont.ttf
new file mode 100755
index 0000000..4972eb4
Binary files /dev/null and b/lib/angular/docs/font/fontawesome-webfont.ttf differ
diff --git a/lib/angular/docs/font/fontawesome-webfont.woff b/lib/angular/docs/font/fontawesome-webfont.woff
new file mode 100755
index 0000000..6e4cb41
Binary files /dev/null and b/lib/angular/docs/font/fontawesome-webfont.woff differ
diff --git a/lib/angular/docs/img/AngularJS-small.png b/lib/angular/docs/img/AngularJS-small.png
new file mode 100644
index 0000000..ab5e20f
Binary files /dev/null and b/lib/angular/docs/img/AngularJS-small.png differ
diff --git a/lib/angular/docs/img/One_Way_Data_Binding.png b/lib/angular/docs/img/One_Way_Data_Binding.png
new file mode 100644
index 0000000..60e7e3b
Binary files /dev/null and b/lib/angular/docs/img/One_Way_Data_Binding.png differ
diff --git a/lib/angular/docs/img/Two_Way_Data_Binding.png b/lib/angular/docs/img/Two_Way_Data_Binding.png
new file mode 100644
index 0000000..3a9c6d1
Binary files /dev/null and b/lib/angular/docs/img/Two_Way_Data_Binding.png differ
diff --git a/lib/angular/docs/img/angular_parts.png b/lib/angular/docs/img/angular_parts.png
new file mode 100644
index 0000000..a33a10b
Binary files /dev/null and b/lib/angular/docs/img/angular_parts.png differ
diff --git a/lib/angular/docs/img/bullet.png b/lib/angular/docs/img/bullet.png
new file mode 100755
index 0000000..3575a8e
Binary files /dev/null and b/lib/angular/docs/img/bullet.png differ
diff --git a/lib/angular/docs/img/form_data_flow.png b/lib/angular/docs/img/form_data_flow.png
new file mode 100644
index 0000000..60e947a
Binary files /dev/null and b/lib/angular/docs/img/form_data_flow.png differ
diff --git a/lib/angular/docs/img/glyphicons-halflings-white.png b/lib/angular/docs/img/glyphicons-halflings-white.png
new file mode 100644
index 0000000..a20760b
Binary files /dev/null and b/lib/angular/docs/img/glyphicons-halflings-white.png differ
diff --git a/lib/angular/docs/img/glyphicons-halflings.png b/lib/angular/docs/img/glyphicons-halflings.png
new file mode 100644
index 0000000..92d4445
Binary files /dev/null and b/lib/angular/docs/img/glyphicons-halflings.png differ
diff --git a/lib/angular/docs/img/guide/about_model_final.png b/lib/angular/docs/img/guide/about_model_final.png
new file mode 100644
index 0000000..d2d8165
Binary files /dev/null and b/lib/angular/docs/img/guide/about_model_final.png differ
diff --git a/lib/angular/docs/img/guide/about_view_final.png b/lib/angular/docs/img/guide/about_view_final.png
new file mode 100644
index 0000000..097e591
Binary files /dev/null and b/lib/angular/docs/img/guide/about_view_final.png differ
diff --git a/lib/angular/docs/img/guide/concepts-controller.png b/lib/angular/docs/img/guide/concepts-controller.png
new file mode 100644
index 0000000..91f3e76
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-controller.png differ
diff --git a/lib/angular/docs/img/guide/concepts-directive.png b/lib/angular/docs/img/guide/concepts-directive.png
new file mode 100644
index 0000000..b77d5f8
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-directive.png differ
diff --git a/lib/angular/docs/img/guide/concepts-model.png b/lib/angular/docs/img/guide/concepts-model.png
new file mode 100644
index 0000000..ac6db02
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-model.png differ
diff --git a/lib/angular/docs/img/guide/concepts-module-injector.png b/lib/angular/docs/img/guide/concepts-module-injector.png
new file mode 100644
index 0000000..31fcf84
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-module-injector.png differ
diff --git a/lib/angular/docs/img/guide/concepts-runtime.png b/lib/angular/docs/img/guide/concepts-runtime.png
new file mode 100644
index 0000000..eededc2
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-runtime.png differ
diff --git a/lib/angular/docs/img/guide/concepts-scope.png b/lib/angular/docs/img/guide/concepts-scope.png
new file mode 100644
index 0000000..83ad8f8
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-scope.png differ
diff --git a/lib/angular/docs/img/guide/concepts-startup.png b/lib/angular/docs/img/guide/concepts-startup.png
new file mode 100644
index 0000000..b896a2b
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-startup.png differ
diff --git a/lib/angular/docs/img/guide/concepts-view.png b/lib/angular/docs/img/guide/concepts-view.png
new file mode 100644
index 0000000..0222544
Binary files /dev/null and b/lib/angular/docs/img/guide/concepts-view.png differ
diff --git a/lib/angular/docs/img/guide/di_sequence_final.png b/lib/angular/docs/img/guide/di_sequence_final.png
new file mode 100644
index 0000000..d4d743b
Binary files /dev/null and b/lib/angular/docs/img/guide/di_sequence_final.png differ
diff --git a/lib/angular/docs/img/guide/dom_scope_final.png b/lib/angular/docs/img/guide/dom_scope_final.png
new file mode 100644
index 0000000..c6385d1
Binary files /dev/null and b/lib/angular/docs/img/guide/dom_scope_final.png differ
diff --git a/lib/angular/docs/img/guide/hashbang_vs_regular_url.jpg b/lib/angular/docs/img/guide/hashbang_vs_regular_url.jpg
new file mode 100644
index 0000000..632b4fe
Binary files /dev/null and b/lib/angular/docs/img/guide/hashbang_vs_regular_url.jpg differ
diff --git a/lib/angular/docs/img/guide/scenario_runner.png b/lib/angular/docs/img/guide/scenario_runner.png
new file mode 100644
index 0000000..a39037a
Binary files /dev/null and b/lib/angular/docs/img/guide/scenario_runner.png differ
diff --git a/lib/angular/docs/img/guide/simple_scope_final.png b/lib/angular/docs/img/guide/simple_scope_final.png
new file mode 100644
index 0000000..99a0479
Binary files /dev/null and b/lib/angular/docs/img/guide/simple_scope_final.png differ
diff --git a/lib/angular/docs/img/helloworld.png b/lib/angular/docs/img/helloworld.png
new file mode 100644
index 0000000..957ce8e
Binary files /dev/null and b/lib/angular/docs/img/helloworld.png differ
diff --git a/lib/angular/docs/img/helloworld_2way.png b/lib/angular/docs/img/helloworld_2way.png
new file mode 100644
index 0000000..2c02313
Binary files /dev/null and b/lib/angular/docs/img/helloworld_2way.png differ
diff --git a/lib/angular/docs/img/tutorial/catalog_screen.png b/lib/angular/docs/img/tutorial/catalog_screen.png
new file mode 100644
index 0000000..0b1968c
Binary files /dev/null and b/lib/angular/docs/img/tutorial/catalog_screen.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_00.png b/lib/angular/docs/img/tutorial/tutorial_00.png
new file mode 100644
index 0000000..65f3973
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_00.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_00_final.png b/lib/angular/docs/img/tutorial/tutorial_00_final.png
new file mode 100644
index 0000000..838a094
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_00_final.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_02.png b/lib/angular/docs/img/tutorial/tutorial_02.png
new file mode 100644
index 0000000..b4c1cab
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_02.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_03.png b/lib/angular/docs/img/tutorial/tutorial_03.png
new file mode 100644
index 0000000..3e432a3
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_03.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_04.png b/lib/angular/docs/img/tutorial/tutorial_04.png
new file mode 100644
index 0000000..3d02877
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_04.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_07_final.png b/lib/angular/docs/img/tutorial/tutorial_07_final.png
new file mode 100644
index 0000000..3e7776c
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_07_final.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_08-09_final.png b/lib/angular/docs/img/tutorial/tutorial_08-09_final.png
new file mode 100644
index 0000000..02601d2
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_08-09_final.png differ
diff --git a/lib/angular/docs/img/tutorial/tutorial_10-11_final.png b/lib/angular/docs/img/tutorial/tutorial_10-11_final.png
new file mode 100644
index 0000000..55a821a
Binary files /dev/null and b/lib/angular/docs/img/tutorial/tutorial_10-11_final.png differ
diff --git a/lib/angular/docs/img/tutorial/xhr_service_final.png b/lib/angular/docs/img/tutorial/xhr_service_final.png
new file mode 100644
index 0000000..b7250e3
Binary files /dev/null and b/lib/angular/docs/img/tutorial/xhr_service_final.png differ
diff --git a/lib/angular/docs/index-debug.html b/lib/angular/docs/index-debug.html
new file mode 100644
index 0000000..87f445f
--- /dev/null
+++ b/lib/angular/docs/index-debug.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+ AngularJS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like full offline support for this AngularJS Docs App?
+
✕
+
+ If you want to be able to access the entire AngularJS documentation offline, click the
+ button below. This will reload the current page and trigger background downloads of all the
+ necessary files (approximately 3.5MB). The next time you load the docs, the browser will
+ use these cached files.
+
+ This feature is supported on all modern browsers, except for IE9 which lacks application
+ cache support.
+
+
Let me have them all!
+
+
+
+
+
+
+
diff --git a/lib/angular/docs/index-jq-debug.html b/lib/angular/docs/index-jq-debug.html
new file mode 100644
index 0000000..87f445f
--- /dev/null
+++ b/lib/angular/docs/index-jq-debug.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+ AngularJS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like full offline support for this AngularJS Docs App?
+
✕
+
+ If you want to be able to access the entire AngularJS documentation offline, click the
+ button below. This will reload the current page and trigger background downloads of all the
+ necessary files (approximately 3.5MB). The next time you load the docs, the browser will
+ use these cached files.
+
+ This feature is supported on all modern browsers, except for IE9 which lacks application
+ cache support.
+
+
Let me have them all!
+
+
+
+
+
+
+
diff --git a/lib/angular/docs/index-jq-nocache.html b/lib/angular/docs/index-jq-nocache.html
new file mode 100644
index 0000000..87f445f
--- /dev/null
+++ b/lib/angular/docs/index-jq-nocache.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+ AngularJS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like full offline support for this AngularJS Docs App?
+
✕
+
+ If you want to be able to access the entire AngularJS documentation offline, click the
+ button below. This will reload the current page and trigger background downloads of all the
+ necessary files (approximately 3.5MB). The next time you load the docs, the browser will
+ use these cached files.
+
+ This feature is supported on all modern browsers, except for IE9 which lacks application
+ cache support.
+
+
Let me have them all!
+
+
+
+
+
+
+
diff --git a/lib/angular/docs/index-jq.html b/lib/angular/docs/index-jq.html
new file mode 100644
index 0000000..87f445f
--- /dev/null
+++ b/lib/angular/docs/index-jq.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+ AngularJS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like full offline support for this AngularJS Docs App?
+
✕
+
+ If you want to be able to access the entire AngularJS documentation offline, click the
+ button below. This will reload the current page and trigger background downloads of all the
+ necessary files (approximately 3.5MB). The next time you load the docs, the browser will
+ use these cached files.
+
+ This feature is supported on all modern browsers, except for IE9 which lacks application
+ cache support.
+
+
Let me have them all!
+
+
+
+
+
+
+
diff --git a/lib/angular/docs/index-nocache.html b/lib/angular/docs/index-nocache.html
new file mode 100644
index 0000000..87f445f
--- /dev/null
+++ b/lib/angular/docs/index-nocache.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+ AngularJS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like full offline support for this AngularJS Docs App?
+
✕
+
+ If you want to be able to access the entire AngularJS documentation offline, click the
+ button below. This will reload the current page and trigger background downloads of all the
+ necessary files (approximately 3.5MB). The next time you load the docs, the browser will
+ use these cached files.
+
+ This feature is supported on all modern browsers, except for IE9 which lacks application
+ cache support.
+
+
Let me have them all!
+
+
+
+
+
+
+
diff --git a/lib/angular/docs/index.html b/lib/angular/docs/index.html
new file mode 100644
index 0000000..87f445f
--- /dev/null
+++ b/lib/angular/docs/index.html
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+ AngularJS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Would you like full offline support for this AngularJS Docs App?
+
✕
+
+ If you want to be able to access the entire AngularJS documentation offline, click the
+ button below. This will reload the current page and trigger background downloads of all the
+ necessary files (approximately 3.5MB). The next time you load the docs, the browser will
+ use these cached files.
+
+ This feature is supported on all modern browsers, except for IE9 which lacks application
+ cache support.
+
+
Let me have them all!
+
+
+
+
+
+
+
diff --git a/lib/angular/docs/js/docs.js b/lib/angular/docs/js/docs.js
new file mode 100644
index 0000000..a43aa60
--- /dev/null
+++ b/lib/angular/docs/js/docs.js
@@ -0,0 +1,572 @@
+var docsApp = {
+ controller: {},
+ directive: {},
+ serviceFactory: {}
+};
+
+
+docsApp.directive.focused = function($timeout) {
+ return function(scope, element, attrs) {
+ element[0].focus();
+ element.bind('focus', function() {
+ scope.$apply(attrs.focused + '=true');
+ });
+ element.bind('blur', function() {
+ // have to use $timeout, so that we close the drop-down after the user clicks,
+ // otherwise when the user clicks we process the closing before we process the click.
+ $timeout(function() {
+ scope.$eval(attrs.focused + '=false');
+ });
+ });
+ scope.$eval(attrs.focused + '=true')
+ }
+};
+
+
+docsApp.directive.code = function() {
+ return { restrict:'E', terminal: true };
+};
+
+
+docsApp.directive.sourceEdit = function(getEmbeddedTemplate) {
+ return {
+ template: '',
+ scope: true,
+ controller: function($scope, $attrs, openJsFiddle, openPlunkr) {
+ var sources = {
+ module: $attrs.sourceEdit,
+ deps: read($attrs.sourceEditDeps),
+ html: read($attrs.sourceEditHtml),
+ css: read($attrs.sourceEditCss),
+ js: read($attrs.sourceEditJs),
+ unit: read($attrs.sourceEditUnit),
+ scenario: read($attrs.sourceEditScenario)
+ };
+ $scope.fiddle = function(e) {
+ e.stopPropagation();
+ openJsFiddle(sources);
+ };
+ $scope.plunkr = function(e) {
+ e.stopPropagation();
+ openPlunkr(sources);
+ };
+ }
+ }
+
+ function read(text) {
+ var files = [];
+ angular.forEach(text ? text.split(' ') : [], function(refId) {
+ // refId is index.html-343, so we need to strip the unique ID when exporting the name
+ files.push({name: refId.replace(/-\d+$/, ''), content: getEmbeddedTemplate(refId)});
+ });
+ return files;
+ }
+};
+
+
+docsApp.directive.docTutorialNav = function(templateMerge) {
+ var pages = [
+ '',
+ 'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
+ 'step_05', 'step_06', 'step_07', 'step_08', 'step_09',
+ 'step_10', 'step_11', 'the_end'
+ ];
+ return {
+ compile: function(element, attrs) {
+ var seq = 1 * attrs.docTutorialNav,
+ props = {
+ seq: seq,
+ prev: pages[seq],
+ next: pages[2 + seq],
+ diffLo: seq ? (seq - 1): '0~1',
+ diffHi: seq
+ };
+
+ element.addClass('btn-group');
+ element.addClass('tutorial-nav');
+ element.append(templateMerge(
+ ' Previous \n' +
+ ' Live Demo \n' +
+ ' Code Diff \n' +
+ 'Next ', props));
+ }
+ };
+};
+
+
+docsApp.directive.docTutorialReset = function() {
+ function tab(name, command, id, step) {
+ return '' +
+ ' \n' +
+ '
\n' +
+ ' Reset the workspace to step ' + step + '.
' +
+ ' ' + command + ' \n' +
+ ' Refresh your browser or check the app out on Angular\'s server .
\n' +
+ ' \n' +
+ '
\n';
+ }
+
+ return {
+ compile: function(element, attrs) {
+ var step = attrs.docTutorialReset;
+ element.html(
+ '\n' +
+ '\n' +
+ tab('Git on Mac/Linux', 'git checkout -f step-' + step, 'gitUnix', step) +
+ tab('Git on Windows', 'git checkout -f step-' + step, 'gitWin', step) +
+ '
\n');
+ }
+ };
+}
+
+
+docsApp.serviceFactory.angularUrls = function($document) {
+ var urls = {};
+
+ angular.forEach($document.find('script'), function(script) {
+ var match = script.src.match(/^.*\/(angular[^\/]*\.js)$/);
+ if (match) {
+ urls[match[1].replace(/(\-\d.*)?(\.min)?\.js$/, '.js')] = match[0];
+ }
+ });
+
+ return urls;
+}
+
+
+docsApp.serviceFactory.formPostData = function($document) {
+ return function(url, fields) {
+ var form = angular.element('');
+ angular.forEach(fields, function(value, name) {
+ var input = angular.element(' ');
+ input.attr('value', value);
+ form.append(input);
+ });
+ $document.find('body').append(form);
+ form[0].submit();
+ form.remove();
+ };
+};
+
+docsApp.serviceFactory.openPlunkr = function(templateMerge, formPostData, angularUrls) {
+ return function(content) {
+ var allFiles = [].concat(content.js, content.css, content.html);
+ var indexHtmlContent = '\n' +
+ '\n' +
+ ' \n' +
+ ' \n' +
+ '{{scriptDeps}}\n' +
+ ' \n' +
+ ' \n\n' +
+ '{{indexContents}}' +
+ '\n\n \n' +
+ '\n';
+ var scriptDeps = '';
+ angular.forEach(content.deps, function(file) {
+ if (file.name !== 'angular.js') {
+ scriptDeps += ' \n'
+ }
+ });
+ indexProp = {
+ angularJSUrl: angularUrls['angular.js'],
+ scriptDeps: scriptDeps,
+ indexContents: content.html[0].content
+ };
+ var postData = {};
+ angular.forEach(allFiles, function(file, index) {
+ if (file.content && file.name != 'index.html') {
+ postData['files[' + file.name + ']'] = file.content;
+ }
+ });
+
+ postData['files[index.html]'] = templateMerge(indexHtmlContent, indexProp);
+ postData['tags[]'] = "angularjs";
+
+ postData.private = true;
+ postData.description = 'AngularJS Example Plunkr';
+
+ formPostData('http://plnkr.co/edit/?p=preview', postData);
+ };
+};
+
+docsApp.serviceFactory.openJsFiddle = function(templateMerge, formPostData, angularUrls) {
+
+ var HTML = '\n{{html:2}}
',
+ CSS = ' \n' +
+ '{{head:0}}
+
+
+Demo
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngClassEven.html b/lib/angular/docs/partials/api/ng.directive:ngClassEven.html
new file mode 100644
index 0000000..b5fa8b7
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngClassEven.html
@@ -0,0 +1,64 @@
+ngClassEven
+(directive in module ng
+)
+
+Description
+
The ngClassOdd
and ngClassEven
works exactly as
+ngClass
, except it works in
+conjunction with ngRepeat
and takes affect only on odd (even) rows.
+
+
This directive can be applied only within a scope of an
+ngRepeat
.
+
Usage
+
as attribute
<ANY ng-class-even="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-class-even: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngClassOdd.html b/lib/angular/docs/partials/api/ng.directive:ngClassOdd.html
new file mode 100644
index 0000000..fa1b94d
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngClassOdd.html
@@ -0,0 +1,64 @@
+ngClassOdd
+(directive in module ng
+)
+
+Description
+
The ngClassOdd
and ngClassEven
directives work exactly as
+ngClass
, except it works in
+conjunction with ngRepeat
and takes affect only on odd (even) rows.
+
+
This directive can be applied only within a scope of an
+ngRepeat
.
+
Usage
+
as attribute
<ANY ng-class-odd="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-class-odd: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngClick.html b/lib/angular/docs/partials/api/ng.directive:ngClick.html
new file mode 100644
index 0000000..3a73cc0
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngClick.html
@@ -0,0 +1,45 @@
+ngClick
+(directive in module ng
+)
+
+Description
+
The ngClick allows you to specify custom behavior when
+element is clicked.
+
Usage
+
as attribute
<ANY ng-click="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-click: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngCloak.html b/lib/angular/docs/partials/api/ng.directive:ngCloak.html
new file mode 100644
index 0000000..e87aefa
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngCloak.html
@@ -0,0 +1,65 @@
+ngCloak
+(directive in module ng
+)
+
+Description
+
The ngCloak
directive is used to prevent the Angular html template from being briefly
+displayed by the browser in its raw (uncompiled) form while your application is loading. Use this
+directive to avoid the undesirable flicker effect caused by the html template display.
+
+
The directive can be applied to the <body>
element, but typically a fine-grained application is
+prefered in order to benefit from progressive rendering of the browser view.
+
+
ngCloak
works in cooperation with a css rule that is embedded within angular.js
and
+ angular.min.js
files. Following is the css rule:
+
+
+[ng\:cloak], [ng-cloak], .ng-cloak {
+ display: none;
+}
+
+
+
When this css rule is loaded by the browser, all html elements (including their children) that
+are tagged with the ng-cloak
directive are hidden. When Angular comes across this directive
+during the compilation of the template it deletes the ngCloak
element attribute, which
+makes the compiled element visible.
+
+
For the best result, angular.js
script must be loaded in the head section of the html file;
+alternatively, the css rule (above) must be included in the external stylesheet of the
+application.
+
+
Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
+cannot match the [ng\:cloak]
selector. To work around this limitation, you must add the css
+class ngCloak
in addition to ngCloak
directive as shown in the example below.
+
Usage
+
as attribute
<ANY ng-cloak>
+ ...
+</ANY>
+as class
<ANY class="ng-cloak">
+ ...
+</ANY>
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngController.html b/lib/angular/docs/partials/api/ng.directive:ngController.html
new file mode 100644
index 0000000..830e3e9
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngController.html
@@ -0,0 +1,120 @@
+ngController
+(directive in module ng
+)
+
+Description
+
The ngController
directive assigns behavior to a scope. This is a key aspect of how angular
+supports the principles behind the Model-View-Controller design pattern.
+
+
MVC components in angular:
+
+
+Model — The Model is data in scope properties; scopes are attached to the DOM.
+View — The template (HTML with data bindings) is rendered into the View.
+Controller — The ngController
directive specifies a Controller class; the class has
+methods that typically express the business logic behind the application.
+
+
+
Note that an alternative way to define controllers is via the <a href="api/ng.$route"><code>ng.$route</code></a>
+service.
+
Usage
+
as attribute
<ANY ng-controller="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-controller: {expression};">
+ ...
+</ANY>
+
Directive info
+
This directive creates new scope.
+
+
+
Parameters
+
+
+
Example
+
Here is a simple form for editing user contact information. Adding, removing, clearing, and
+greeting are methods declared on the controller (see source tab). These methods can
+easily be called from the angular markup. Notice that the scope becomes the this
for the
+controller's instance. This allows for easy access to the view data from the controller. Also
+notice that any changes to the data are automatically reflected in the View without the need
+for a manual update.
+
Source
+
+
Demo
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngCsp.html b/lib/angular/docs/partials/api/ng.directive:ngCsp.html
new file mode 100644
index 0000000..59799bf
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngCsp.html
@@ -0,0 +1,25 @@
+ngCsp
+(directive in module ng
+)
+
+Description
+
Enables CSP (Content Security Policy) support.
+This directive should be used on the root element of the application (typically the <html>
+element or other element with the ngApp
+directive).
+
+
If enabled the performance of template expression evaluator will suffer slightly, so don't enable
+this mode unless you need it.
+
Usage
+
as attribute
<html ng-csp>
+ ...
+</html>
+as class
<html class="ng-csp">
+ ...
+</html>
+
Directive info
+
This directive executes at priority level 1000.
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngDblclick.html b/lib/angular/docs/partials/api/ng.directive:ngDblclick.html
new file mode 100644
index 0000000..6e83d7a
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngDblclick.html
@@ -0,0 +1,22 @@
+ngDblclick
+(directive in module ng
+)
+
+Description
+
The ngDblclick
directive allows you to specify custom behavior on dblclick event.
+
Usage
+
as attribute
<ANY ng-dblclick="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-dblclick: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngDisabled.html b/lib/angular/docs/partials/api/ng.directive:ngDisabled.html
new file mode 100644
index 0000000..79e027b
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngDisabled.html
@@ -0,0 +1,48 @@
+ngDisabled
+(directive in module ng
+)
+
+Description
+
The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
+
+<div ng-init="scope = { isDisabled: false }">
+ <button disabled="{{scope.isDisabled}}">Disabled</button>
+</div>
+
+
+
The HTML specs do not require browsers to preserve the special attributes such as disabled.
+(The presence of them means true and absence means false)
+This prevents the angular compiler from correctly retrieving the binding expression.
+To solve this problem, we introduce the ngDisabled
directive.
+
Usage
+
as attribute
<INPUT ng-disabled="{expression}">
+ ...
+</INPUT>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngForm.html b/lib/angular/docs/partials/api/ng.directive:ngForm.html
new file mode 100644
index 0000000..4d9a515
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngForm.html
@@ -0,0 +1,26 @@
+ngForm
+(directive in module ng
+)
+
+Description
+
Nestable alias of form
directive. HTML
+does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
+sub-group of controls needs to be determined.
+
Usage
+
as element (see
IE restrictions )
<ng-form
+ [ngForm="{string}"]>
+</ng-form>
+as attribute
<ANY ng-form
+ [name="{string}"]>
+ ...
+</ANY>
+as class
<ANY class="ng-form [name: {string};]">
+ ...
+</ANY>
+
Parameters
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngHide.html b/lib/angular/docs/partials/api/ng.directive:ngHide.html
new file mode 100644
index 0000000..a50367f
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngHide.html
@@ -0,0 +1,48 @@
+ngHide
+(directive in module ng
+)
+
+Description
+
The ngHide
and ngShow
directives hide or show a portion of the DOM tree (HTML)
+conditionally.
+
Usage
+
as attribute
<ANY ng-hide="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-hide: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngHref.html b/lib/angular/docs/partials/api/ng.directive:ngHref.html
new file mode 100644
index 0000000..d1da860
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngHref.html
@@ -0,0 +1,91 @@
+ngHref
+(directive in module ng
+)
+
+Description
+
Using Angular markup like {{hash}} in an href attribute makes
+the page open to a wrong URL, if the user clicks that link before
+angular has a chance to replace the {{hash}} with actual URL, the
+link will be broken and will most likely return a 404 error.
+The ngHref
directive solves this problem.
+
+
The buggy way to write it:
+
+<a href="http://www.gravatar.com/avatar/{{hash}}"/>
+
+
+
The correct way to write it:
+
+<a ng-href="http://www.gravatar.com/avatar/{{hash}}"/>
+
+
Usage
+
as attribute
<A ng-href="{template}">
+ ...
+</A>
+
Parameters
+
+
+
Example
+
This example uses link
variable inside href
attribute:
+
Source
+
+
Demo
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngInclude.html b/lib/angular/docs/partials/api/ng.directive:ngInclude.html
new file mode 100644
index 0000000..5f190ac
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngInclude.html
@@ -0,0 +1,116 @@
+ngInclude
+(directive in module ng
+)
+
+Description
+
Fetches, compiles and includes an external HTML fragment.
+
+
Keep in mind that Same Origin Policy applies to included resources
+(e.g. ngInclude won't work for cross-domain requests on all browsers and for
+ file:// access on some browsers).
+
Usage
+
as element (see
IE restrictions )
<ng-include
+ src="{string}"
+ [onload="{string}"]
+ [autoscroll="{string}"]>
+</ng-include>
+as attribute
<ANY ng-include="{string}"
+ [onload="{string}"]
+ [autoscroll="{string}"]>
+ ...
+</ANY>
+as class
<ANY class="ng-include: {string}; [onload: {string};] [autoscroll: {string};]">
+ ...
+</ANY>
+
Directive info
+
This directive creates new scope.
+
+
+
Parameters
+
ngInclude|src – {string} –
+angular expression evaluating to URL. If the source is a string constant,
+make sure you wrap it in quotes, e.g. src="'myPartialTemplate.html'"
.
+onload(optional) – {string=} –
+Expression to evaluate when a new partial is loaded.
+autoscroll(optional) – {string=} –
+Whether ngInclude
should call $anchorScroll
to scroll the viewport after the content is loaded.
+
+
+If the attribute is not set, disable scrolling.
+If the attribute is set without value, enable scrolling.
+Otherwise enable scrolling only if the expression evaluates to truthy value.
+
+
+
+
Events
+
$includeContentLoaded
+Emitted every time the ngInclude content is reloaded.
+
Target:
+
the current ngInclude scope
+
+
+
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngInit.html b/lib/angular/docs/partials/api/ng.directive:ngInit.html
new file mode 100644
index 0000000..9a61518
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngInit.html
@@ -0,0 +1,42 @@
+ngInit
+(directive in module ng
+)
+
+Description
+
The ngInit
directive specifies initialization tasks to be executed
+before the template enters execution mode during bootstrap.
+
Usage
+
as attribute
<ANY ng-init="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-init: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngKeydown.html b/lib/angular/docs/partials/api/ng.directive:ngKeydown.html
new file mode 100644
index 0000000..10a3856
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngKeydown.html
@@ -0,0 +1,22 @@
+ngKeydown
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on keydown event.
+
Usage
+
as attribute
<ANY ng-keydown="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-keydown: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngKeyup.html b/lib/angular/docs/partials/api/ng.directive:ngKeyup.html
new file mode 100644
index 0000000..9479622
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngKeyup.html
@@ -0,0 +1,22 @@
+ngKeyup
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on keyup event.
+
Usage
+
as attribute
<ANY ng-keyup="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-keyup: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngList.html b/lib/angular/docs/partials/api/ng.directive:ngList.html
new file mode 100644
index 0000000..c52065a
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngList.html
@@ -0,0 +1,64 @@
+ngList
+(directive in module ng
+)
+
+Description
+
Text input that converts between comma-separated string into an array of strings.
+
Usage
+
as attribute
<input ng-list="{string}">
+ ...
+</input>
+as class
<input class="ng-list: {string};">
+ ...
+</input>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngModel.NgModelController.html b/lib/angular/docs/partials/api/ng.directive:ngModel.NgModelController.html
new file mode 100644
index 0000000..3a6532d
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngModel.NgModelController.html
@@ -0,0 +1,167 @@
+NgModelController
+(type in module ng
+)
+
+Description
+
NgModelController
provides API for the ng-model
directive. The controller contains
+services for data-binding, validation, CSS update, value formatting and parsing. It
+specifically does not contain any logic which deals with DOM rendering or listening to
+DOM events. The NgModelController
is meant to be extended by other directives where, the
+directive provides DOM manipulation and the NgModelController
provides the data-binding.
+
+
This example shows how to use NgModelController
with a custom control to achieve
+data-binding. Notice how different directives (contenteditable
, ng-model
, and required
)
+collaborate together to achieve the desired result.
+
+
Source
+
+
Demo
+
+
Methods
+
$render()
+Called when the view needs to be updated. It is expected that the user of the ng-model
+directive will implement this method.
+
+$setPristine()
+Sets the control to its pristine state.
+
+
This method can be called to remove the 'ng-dirty' class and set the control to its pristine
+state (ng-pristine class).
+
+$setValidity(validationErrorKey, isValid)
+Change the validity state, and notifies the form when the control changes validity. (i.e. it
+does not notify form if given validator is already marked as invalid).
+
+
This method should be called by validators - i.e. the parser or formatter functions.
Parameters
+
validationErrorKey – {string} –
+Name of the validator. the validationErrorKey
will assign
+to $error[validationErrorKey]=isValid
so that it is available for data-binding.
+The validationErrorKey
should be in camelCase and will get converted into dash-case
+for class name. Example: myError
will result in ng-valid-my-error
and ng-invalid-my-error
+class and can be bound to as {{someForm.someControl.$error.myError}}
.
+isValid – {boolean} –
+Whether the current state is valid (true) or invalid (false).
+
+
+
+$setViewValue(value)
+Read a value from view.
+
+
This method should be called from within a DOM event handler.
+For example input
or
+select
directives call it.
+
+
It internally calls all formatters
and if resulted value is valid, updates the model and
+calls all registered change listeners.
Parameters
+
value – {string} –
+Value from the view.
+
+
+
+
+
+
Properties
+
$viewValue
+Actual string value in the view.
+
+$modelValue
+The value in the model, that the control is bound to.
+
+$parsers
+Whenever the control reads value from the DOM, it executes
+all of these functions to sanitize / convert the value as well as validate.
+
+
+
+
+$error
+An bject hash with all errors as keys.
+
+$pristine
+True if user has not interacted with the control yet.
+
+$dirty
+True if user has already interacted with the control.
+
+$valid
+True if there is no error.
+
+$invalid
+True if at least one error on the control.
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngModel.html b/lib/angular/docs/partials/api/ng.directive:ngModel.html
new file mode 100644
index 0000000..64d2590
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngModel.html
@@ -0,0 +1,41 @@
+ngModel
+(directive in module ng
+)
+
+Description
+
Is directive that tells Angular to do two-way data binding. It works together with input
,
+select
, textarea
. You can easily write your own directives to use ngModel
as well.
+
+
ngModel
is responsible for:
+
+
+binding the view into the model, which other directives such as input
, textarea
or select
+require,
+providing validation behavior (i.e. required, number, email, url),
+keeping state of the control (valid/invalid, dirty/pristine, validation errors),
+setting related css class onto the element (ng-valid
, ng-invalid
, ng-dirty
, ng-pristine
),
+register the control with parent form
.
+
+
+
For basic examples, how to use ngModel
, see:
+
+
+
Usage
+
as attribute
<input ng-model>
+ ...
+</input>
+as class
<input class="ng-model">
+ ...
+</input>
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMousedown.html b/lib/angular/docs/partials/api/ng.directive:ngMousedown.html
new file mode 100644
index 0000000..572c251
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMousedown.html
@@ -0,0 +1,22 @@
+ngMousedown
+(directive in module ng
+)
+
+Description
+
The ngMousedown directive allows you to specify custom behavior on mousedown event.
+
Usage
+
as attribute
<ANY ng-mousedown="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-mousedown: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMouseenter.html b/lib/angular/docs/partials/api/ng.directive:ngMouseenter.html
new file mode 100644
index 0000000..eec24dd
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMouseenter.html
@@ -0,0 +1,22 @@
+ngMouseenter
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on mouseenter event.
+
Usage
+
as attribute
<ANY ng-mouseenter="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-mouseenter: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMouseleave.html b/lib/angular/docs/partials/api/ng.directive:ngMouseleave.html
new file mode 100644
index 0000000..3914e4a
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMouseleave.html
@@ -0,0 +1,22 @@
+ngMouseleave
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on mouseleave event.
+
Usage
+
as attribute
<ANY ng-mouseleave="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-mouseleave: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMousemove.html b/lib/angular/docs/partials/api/ng.directive:ngMousemove.html
new file mode 100644
index 0000000..85fb244
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMousemove.html
@@ -0,0 +1,22 @@
+ngMousemove
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on mousemove event.
+
Usage
+
as attribute
<ANY ng-mousemove="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-mousemove: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMouseover.html b/lib/angular/docs/partials/api/ng.directive:ngMouseover.html
new file mode 100644
index 0000000..fabd210
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMouseover.html
@@ -0,0 +1,22 @@
+ngMouseover
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on mouseover event.
+
Usage
+
as attribute
<ANY ng-mouseover="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-mouseover: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMouseup.html b/lib/angular/docs/partials/api/ng.directive:ngMouseup.html
new file mode 100644
index 0000000..65f439e
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMouseup.html
@@ -0,0 +1,22 @@
+ngMouseup
+(directive in module ng
+)
+
+Description
+
Specify custom behavior on mouseup event.
+
Usage
+
as attribute
<ANY ng-mouseup="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-mouseup: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngMultiple.html b/lib/angular/docs/partials/api/ng.directive:ngMultiple.html
new file mode 100644
index 0000000..d0118d4
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngMultiple.html
@@ -0,0 +1,46 @@
+ngMultiple
+(directive in module ng
+)
+
+Description
+
The HTML specs do not require browsers to preserve the special attributes such as multiple.
+(The presence of them means true and absence means false)
+This prevents the angular compiler from correctly retrieving the binding expression.
+To solve this problem, we introduce the ngMultiple
directive.
+
Usage
+
as attribute
<SELECT ng-multiple="{expression}">
+ ...
+</SELECT>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngNonBindable.html b/lib/angular/docs/partials/api/ng.directive:ngNonBindable.html
new file mode 100644
index 0000000..4bf20d3
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngNonBindable.html
@@ -0,0 +1,42 @@
+ngNonBindable
+(directive in module ng
+)
+
+Description
+
Sometimes it is necessary to write code which looks like bindings but which should be left alone
+by angular. Use ngNonBindable
to make angular ignore a chunk of HTML.
+
Usage
+
as attribute
<ANY ng-non-bindable>
+ ...
+</ANY>
+as class
<ANY class="ng-non-bindable">
+ ...
+</ANY>
+
Directive info
+
This directive executes at priority level 1000.
+
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngOpen.html b/lib/angular/docs/partials/api/ng.directive:ngOpen.html
new file mode 100644
index 0000000..1e84c6b
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngOpen.html
@@ -0,0 +1,44 @@
+ngOpen
+(directive in module ng
+)
+
+Description
+
The HTML specs do not require browsers to preserve the special attributes such as open.
+(The presence of them means true and absence means false)
+This prevents the angular compiler from correctly retrieving the binding expression.
+To solve this problem, we introduce the ngOpen
directive.
+
Usage
+
as attribute
<DETAILS ng-open
+ expression="{string}">
+ ...
+</DETAILS>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngPluralize.html b/lib/angular/docs/partials/api/ng.directive:ngPluralize.html
new file mode 100644
index 0000000..cfce3f4
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngPluralize.html
@@ -0,0 +1,197 @@
+ngPluralize
+(directive in module ng
+)
+
+Description
+
Overview
+
+
ngPluralize
is a directive that displays messages according to en-US localization rules.
+These rules are bundled with angular.js and the rules can be overridden
+(see Angular i18n dev guide). You configure ngPluralize directive
+by specifying the mappings between
+plural categories and the strings to be displayed.
+
+
Plural categories and explicit number rules
+
+
There are two
+plural categories in Angular's default en-US locale: "one" and "other".
+
+
While a pural category may match many numbers (for example, in en-US locale, "other" can match
+any number that is not 1), an explicit number rule can only match one number. For example, the
+explicit number rule for "3" matches the number 3. You will see the use of plural categories
+and explicit number rules throughout later parts of this documentation.
+
+
Configuring ngPluralize
+
+
You configure ngPluralize by providing 2 attributes: count
and when
.
+You can also provide an optional attribute, offset
.
+
+
The value of the count
attribute can be either a string or an Angular expression ; these are evaluated on the current scope for its bound value.
+
+
The when
attribute specifies the mappings between plural categories and the actual
+string to be displayed. The value of the attribute should be a JSON object so that Angular
+can interpret it correctly.
+
+
The following example shows how to configure ngPluralize:
+
+
+<ng-pluralize count="personCount"
+ when="{'0': 'Nobody is viewing.',
+ 'one': '1 person is viewing.',
+ 'other': '{} people are viewing.'}">
+</ng-pluralize>
+
+
+
In the example, "0: Nobody is viewing."
is an explicit number rule. If you did not
+specify this rule, 0 would be matched to the "other" category and "0 people are viewing"
+would be shown instead of "Nobody is viewing". You can specify an explicit number rule for
+other numbers, for example 12, so that instead of showing "12 people are viewing", you can
+show "a dozen people are viewing".
+
+
You can use a set of closed braces({}
) as a placeholder for the number that you want substituted
+into pluralized strings. In the previous example, Angular will replace {}
with
+{{personCount}}
. The closed braces {}
is a placeholder
+for {{numberExpression}} .
+
+
Configuring ngPluralize with offset
+
+
The offset
attribute allows further customization of pluralized text, which can result in
+a better user experience. For example, instead of the message "4 people are viewing this document",
+you might display "John, Kate and 2 others are viewing this document".
+The offset attribute allows you to offset a number by any desired value.
+Let's take a look at an example:
+
+
+<ng-pluralize count="personCount" offset=2
+ when="{'0': 'Nobody is viewing.',
+ '1': '{{person1}} is viewing.',
+ '2': '{{person1}} and {{person2}} are viewing.',
+ 'one': '{{person1}}, {{person2}} and one other person are viewing.',
+ 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}">
+</ng-pluralize>
+
+
+
Notice that we are still using two plural categories(one, other), but we added
+three explicit number rules 0, 1 and 2.
+When one person, perhaps John, views the document, "John is viewing" will be shown.
+When three people view the document, no explicit number rule is found, so
+an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
+In this case, plural category 'one' is matched and "John, Marry and one other person are viewing"
+is shown.
+
+
Note that when you specify offsets, you must provide explicit number rules for
+numbers from 0 up to and including the offset. If you use an offset of 3, for example,
+you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for
+plural categories "one" and "other".
+
Usage
+
as element (see
IE restrictions )
<ng-pluralize
+ count="{string|expression}"
+ when="{string}"
+ [offset="{number}"]>
+</ng-pluralize>
+as attribute
<ANY ng-pluralize
+ count="{string|expression}"
+ when="{string}"
+ [offset="{number}"]>
+ ...
+</ANY>
+
Parameters
+
count – {string|expression} –
+The variable to be bounded to.
+when – {string} –
+The mapping between plural category to its correspoding strings.
+offset(optional) – {number=} –
+Offset to deduct from the total number.
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngReadonly.html b/lib/angular/docs/partials/api/ng.directive:ngReadonly.html
new file mode 100644
index 0000000..0947170
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngReadonly.html
@@ -0,0 +1,42 @@
+ngReadonly
+(directive in module ng
+)
+
+Description
+
The HTML specs do not require browsers to preserve the special attributes such as readonly.
+(The presence of them means true and absence means false)
+This prevents the angular compiler from correctly retrieving the binding expression.
+To solve this problem, we introduce the ngReadonly
directive.
+
Usage
+
as attribute
<INPUT ng-readonly
+ expression="{string}">
+ ...
+</INPUT>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngRepeat.html b/lib/angular/docs/partials/api/ng.directive:ngRepeat.html
new file mode 100644
index 0000000..eb78a3b
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngRepeat.html
@@ -0,0 +1,78 @@
+ngRepeat
+(directive in module ng
+)
+
+Description
+
The ngRepeat
directive instantiates a template once per item from a collection. Each template
+instance gets its own scope, where the given loop variable is set to the current collection item,
+and $index
is set to the item index or key.
+
+
Special properties are exposed on the local scope of each template instance, including:
+
+
+$index
– {number}
– iterator offset of the repeated element (0..length-1)
+$first
– {boolean}
– true if the repeated element is first in the iterator.
+$middle
– {boolean}
– true if the repeated element is between the first and last in the iterator.
+$last
– {boolean}
– true if the repeated element is last in the iterator.
+
+
Usage
+
as attribute
<ANY ng-repeat="{repeat_expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-repeat: {repeat_expression};">
+ ...
+</ANY>
+
Directive info
+
This directive creates new scope.
+This directive executes at priority level 1000.
+
+
+
Parameters
+
+
+
Example
+
This example initializes the scope to a list of names and
+then uses ngRepeat
to display every person:
+
Source
+
+
Demo
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngSelected.html b/lib/angular/docs/partials/api/ng.directive:ngSelected.html
new file mode 100644
index 0000000..9f449b9
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngSelected.html
@@ -0,0 +1,45 @@
+ngSelected
+(directive in module ng
+)
+
+Description
+
The HTML specs do not require browsers to preserve the special attributes such as selected.
+(The presence of them means true and absence means false)
+This prevents the angular compiler from correctly retrieving the binding expression.
+To solve this problem, we introduced the ngSelected
directive.
+
Usage
+
as attribute
<OPTION ng-selected
+ expression="{string}">
+ ...
+</OPTION>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngShow.html b/lib/angular/docs/partials/api/ng.directive:ngShow.html
new file mode 100644
index 0000000..a4c84c1
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngShow.html
@@ -0,0 +1,48 @@
+ngShow
+(directive in module ng
+)
+
+Description
+
The ngShow
and ngHide
directives show or hide a portion of the DOM tree (HTML)
+conditionally.
+
Usage
+
as attribute
<ANY ng-show="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-show: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngSrc.html b/lib/angular/docs/partials/api/ng.directive:ngSrc.html
new file mode 100644
index 0000000..347ba25
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngSrc.html
@@ -0,0 +1,29 @@
+ngSrc
+(directive in module ng
+)
+
+Description
+
Using Angular markup like {{hash}}
in a src
attribute doesn't
+work right: The browser will fetch from the URL with the literal
+text {{hash}}
until Angular replaces the expression inside
+{{hash}}
. The ngSrc
directive solves this problem.
+
+
The buggy way to write it:
+
+<img src="http://www.gravatar.com/avatar/{{hash}}"/>
+
+
+
The correct way to write it:
+
+<img ng-src="http://www.gravatar.com/avatar/{{hash}}"/>
+
+
Usage
+
as attribute
<IMG ng-src="{template}">
+ ...
+</IMG>
+
Parameters
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngStyle.html b/lib/angular/docs/partials/api/ng.directive:ngStyle.html
new file mode 100644
index 0000000..ee3dd0b
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngStyle.html
@@ -0,0 +1,56 @@
+ngStyle
+(directive in module ng
+)
+
+Description
+
The ngStyle
directive allows you to set CSS style on an HTML element conditionally.
+
Usage
+
as attribute
<ANY ng-style="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-style: {expression};">
+ ...
+</ANY>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngSubmit.html b/lib/angular/docs/partials/api/ng.directive:ngSubmit.html
new file mode 100644
index 0000000..cfca9b4
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngSubmit.html
@@ -0,0 +1,71 @@
+ngSubmit
+(directive in module ng
+)
+
+Description
+
Enables binding angular expressions to onsubmit events.
+
+
Additionally it prevents the default action (which for form means sending the request to the
+server and reloading the current page).
+
Usage
+
as attribute
<form ng-submit="{expression}">
+ ...
+</form>
+as class
<form class="ng-submit: {expression};">
+ ...
+</form>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngSwitch.html b/lib/angular/docs/partials/api/ng.directive:ngSwitch.html
new file mode 100644
index 0000000..0fb23a3
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngSwitch.html
@@ -0,0 +1,70 @@
+ngSwitch
+(directive in module ng
+)
+
+Description
+
Conditionally change the DOM structure.
+
Usage
+
as element (see
IE restrictions )
<ng-switch
+ on="{*}">
+</ng-switch>
+as attribute
<ANY ng-switch="{*}">
+ ...
+</ANY>
+
Directive info
+
This directive creates new scope.
+
+
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngTransclude.html b/lib/angular/docs/partials/api/ng.directive:ngTransclude.html
new file mode 100644
index 0000000..d6c5ff1
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngTransclude.html
@@ -0,0 +1,65 @@
+ngTransclude
+(directive in module ng
+)
+
+Description
+
Insert the transcluded DOM here.
+
Usage
+
as attribute
<ANY ng-transclude>
+ ...
+</ANY>
+as class
<ANY class="ng-transclude">
+ ...
+</ANY>
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:ngView.html b/lib/angular/docs/partials/api/ng.directive:ngView.html
new file mode 100644
index 0000000..64f4546
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:ngView.html
@@ -0,0 +1,131 @@
+ngView
+(directive in module ng
+)
+
+Description
+
Overview
+
+
ngView
is a directive that complements the $route
service by
+including the rendered template of the current route into the main layout (index.html
) file.
+Every time the current route changes, the included view changes with it according to the
+configuration of the $route
service.
+
Usage
+
as element (see
IE restrictions )
<ng-view>
+</ng-view>
+as attribute
<ANY ng-view>
+ ...
+</ANY>
+as class
<ANY class="ng-view">
+ ...
+</ANY>
+
Directive info
+
This directive creates new scope.
+
+
+
+
Events
+
$viewContentLoaded
+Emitted every time the ngView content is reloaded.
+
Target:
+
the current ngView scope
+
+
+
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:script.html b/lib/angular/docs/partials/api/ng.directive:script.html
new file mode 100644
index 0000000..f5bce97
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:script.html
@@ -0,0 +1,42 @@
+script
+(directive in module ng
+)
+
+Description
+
Load content of a script tag, with type text/ng-template
, into $templateCache
, so that the
+template can be used by ngInclude
, ngView
or directive templates.
+
Usage
+
as element (see
IE restrictions )
<script
+ type="text/ng-template">
+</script>
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:select.html b/lib/angular/docs/partials/api/ng.directive:select.html
new file mode 100644
index 0000000..f2b6b14
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:select.html
@@ -0,0 +1,146 @@
+select
+(directive in module ng
+)
+
+Description
+
HTML SELECT
element with angular data-binding.
+
+
ngOptions
+
+
Optionally ngOptions
attribute can be used to dynamically generate a list of <option>
+elements for a <select>
element using an array or an object obtained by evaluating the
+ngOptions
expression.
+˝˝
+When an item in the select menu is select, the value of array element or object property
+represented by the selected option will be bound to the model identified by the ngModel
+directive of the parent select element.
+
+
Optionally, a single hard-coded <option>
element, with the value set to an empty string, can
+be nested into the <select>
element. This element will then represent null
or "not selected"
+option. See example below for demonstration.
+
+
Note: ngOptions
provides iterator facility for <option>
element which should be used instead
+of ngRepeat
when you want the
+select
model to be bound to a non-string value. This is because an option element can currently
+be bound to string values only.
+
Usage
+
as element (see
IE restrictions )
<select
+ name="{string}"
+ [required]
+ [ngRequired="{string}"]
+ [ngOptions="{comprehension_expression}"]>
+</select>
+
Parameters
+
name – {string} –
+assignable expression to data-bind to.
+required(optional) – {string=} –
+The control is considered valid only if value is entered.
+ngRequired(optional) – {string=} –
+Adds required
attribute and required
validation constraint to
+the element when the ngRequired expression evaluates to true. Use ngRequired
instead of
+required
when you want to data-bind to the required
attribute.
+ngOptions(optional) – {comprehension_expression=} –
+in one of the following forms:
+
+
+for array data sources:
+label
for
value
in
array
+select
as
label
for
value
in
array
+label
group by
group
for
value
in
array
+select
as
label
group by
group
for
value
in
array
+for object data sources:
+label
for (
key
,
value
) in
object
+select
as
label
for (
key
,
value
) in
object
+label
group by
group
for (
key
,
value
) in
object
+select
as
label
group by
group
+for
(
key
,
value
) in
object
+
+
+Where:
+
+
+array
/ object
: an expression which evaluates to an array / object to iterate over.
+value
: local variable which will refer to each item in the array
or each property value
+ of object
during iteration.
+key
: local variable which will refer to a property name in object
during iteration.
+label
: The result of this expression will be the label for <option>
element. The
+expression
will most likely refer to the value
variable (e.g. value.propertyName
).
+select
: The result of this expression will be bound to the model of the parent <select>
+ element. If not specified, select
expression will default to value
.
+group
: The result of this expression will be used to group options using the <optgroup>
+ DOM element.
+
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.directive:textarea.html b/lib/angular/docs/partials/api/ng.directive:textarea.html
new file mode 100644
index 0000000..fa33fa4
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.directive:textarea.html
@@ -0,0 +1,46 @@
+textarea
+(directive in module ng
+)
+
+Description
+
HTML textarea element control with angular data-binding. The data-binding and validation
+properties of this element are exactly the same as those of the
+input element
.
+
Usage
+
as element (see
IE restrictions )
<textarea
+ ngModel="{string}"
+ [name="{string}"]
+ [required]
+ [ngRequired="{string}"]
+ [ngMinlength="{number}"]
+ [ngMaxlength="{number}"]
+ [ngPattern="{string}"]
+ [ngChange="{string}"]>
+</textarea>
+
Parameters
+
ngModel – {string} –
+Assignable angular expression to data-bind to.
+name(optional) – {string=} –
+Property name of the form under which the control is published.
+required(optional) – {string=} –
+Sets required
validation error key if the value is not entered.
+ngRequired(optional) – {string=} –
+Adds required
attribute and required
validation constraint to
+the element when the ngRequired expression evaluates to true. Use ngRequired
instead of
+required
when you want to data-bind to the required
attribute.
+ngMinlength(optional) – {number=} –
+Sets minlength
validation error key if the value is shorter than
+minlength.
+ngMaxlength(optional) – {number=} –
+Sets maxlength
validation error key if the value is longer than
+maxlength.
+ngPattern(optional) – {string=} –
+Sets pattern
validation error key if the value does not match the
+RegExp pattern expression. Expected value is /regexp/
for inline patterns or regexp
for
+patterns defined as scope expressions.
+ngChange(optional) – {string=} –
+Angular expression to be executed when input changes due to user
+interaction with the input element.
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:currency.html b/lib/angular/docs/partials/api/ng.filter:currency.html
new file mode 100644
index 0000000..b64a5bb
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:currency.html
@@ -0,0 +1,63 @@
+currency
+(filter in module ng
+)
+
+Description
+
Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
+symbol for current locale is used.
+
Usage
+
In HTML Template Binding
+
{{ currency_expression | currency[:symbol] }}
+
+
In JavaScript
+
$filter('currency')(amount[, symbol])
+
+
Parameters
+
+
Returns
+
{string}
+–
Formatted number.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:date.html b/lib/angular/docs/partials/api/ng.filter:date.html
new file mode 100644
index 0000000..32c5c0f
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:date.html
@@ -0,0 +1,102 @@
+date
+(filter in module ng
+)
+
+Description
+
Formats date
to a string based on the requested format
.
+
+
format
string can be composed of the following elements:
+
+
+'yyyy'
: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
+'yy'
: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
+'y'
: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
+'MMMM'
: Month in year (January-December)
+'MMM'
: Month in year (Jan-Dec)
+'MM'
: Month in year, padded (01-12)
+'M'
: Month in year (1-12)
+'dd'
: Day in month, padded (01-31)
+'d'
: Day in month (1-31)
+'EEEE'
: Day in Week,(Sunday-Saturday)
+'EEE'
: Day in Week, (Sun-Sat)
+'HH'
: Hour in day, padded (00-23)
+'H'
: Hour in day (0-23)
+'hh'
: Hour in am/pm, padded (01-12)
+'h'
: Hour in am/pm, (1-12)
+'mm'
: Minute in hour, padded (00-59)
+'m'
: Minute in hour (0-59)
+'ss'
: Second in minute, padded (00-59)
+'s'
: Second in minute (0-59)
+'a'
: am/pm marker
+'Z'
: 4 digit (+sign) representation of the timezone offset (-1200-1200)
+
+
+
format
string can also be one of the following predefined
+localizable formats :
+
+
+'medium'
: equivalent to 'MMM d, y h:mm:ss a'
for en_US locale
+(e.g. Sep 3, 2010 12:05:08 pm)
+'short'
: equivalent to 'M/d/yy h:mm a'
for en_US locale (e.g. 9/3/10 12:05 pm)
+'fullDate'
: equivalent to 'EEEE, MMMM d,y'
for en_US locale
+(e.g. Friday, September 3, 2010)
+'longDate'
: equivalent to 'MMMM d, y'
for en_US locale (e.g. September 3, 2010
+'mediumDate'
: equivalent to 'MMM d, y'
for en_US locale (e.g. Sep 3, 2010)
+'shortDate'
: equivalent to 'M/d/yy'
for en_US locale (e.g. 9/3/10)
+'mediumTime'
: equivalent to 'h:mm:ss a'
for en_US locale (e.g. 12:05:08 pm)
+'shortTime'
: equivalent to 'h:mm a'
for en_US locale (e.g. 12:05 pm)
+
+
+
format
string can contain literal values. These need to be quoted with single quotes (e.g.
+"h 'in the morning'"
). In order to output single quote, use two single quotes in a sequence
+(e.g. "h o''clock"
).
+
Usage
+
In HTML Template Binding
+
{{ date_expression | date[:format] }}
+
+
In JavaScript
+
$filter('date')(date[, format])
+
+
Parameters
+
date – {(Date|number|string)} –
+Date to format either as Date object, milliseconds (string or
+number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
+shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
+format(optional) – {string=} –
+Formatting rules (see Description). If not specified,
+mediumDate
is used.
+
+
Returns
+
{string}
+–
Formatted string or the input if input is not recognized as date/millis.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:filter.html b/lib/angular/docs/partials/api/ng.filter:filter.html
new file mode 100644
index 0000000..2bf3634
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:filter.html
@@ -0,0 +1,97 @@
+filter
+(filter in module ng
+)
+
+Description
+
Selects a subset of items from array
and returns it as a new array.
+
+
Note: This function is used to augment the Array
type in Angular expressions. See
+ng.$filter
for more information about Angular arrays.
+
Usage
+
In HTML Template Binding
+
{{ filter_expression | filter:expression }}
+
+
In JavaScript
+
$filter('filter')(array, expression)
+
+
Parameters
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:json.html b/lib/angular/docs/partials/api/ng.filter:json.html
new file mode 100644
index 0000000..28ccb06
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:json.html
@@ -0,0 +1,44 @@
+json
+(filter in module ng
+)
+
+Description
+
Allows you to convert a JavaScript object into JSON string.
+
+
This filter is mostly useful for debugging. When using the double curly {{value}} notation
+the binding is automatically converted to JSON.
+
Usage
+
In HTML Template Binding
+
{{ json_expression | json }}
+
+
In JavaScript
+
$filter('json')(object)
+
+
Parameters
+
+
Returns
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:limitTo.html b/lib/angular/docs/partials/api/ng.filter:limitTo.html
new file mode 100644
index 0000000..707047d
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:limitTo.html
@@ -0,0 +1,81 @@
+limitTo
+(filter in module ng
+)
+
+Description
+
Creates a new array or string containing only a specified number of elements. The elements
+are taken from either the beginning or the end of the source array or string, as specified by
+the value and sign (positive or negative) of limit
.
+
+
Note: This function is used to augment the Array
type in Angular expressions. See
+ng.$filter
for more information about Angular arrays.
+
Usage
+
filter:limitTo(input, limit);
+
Parameters
+
input – {Array|string} –
+Source array or string to be limited.
+limit – {string|number} –
+The length of the returned array or string. If the limit
number
+is positive, limit
number of items from the beginning of the source array/string are copied.
+If the number is negative, limit
number of items from the end of the source array/string
+are copied. The limit
will be trimmed if it exceeds array.length
+
+
Returns
+
{Array|string}
+–
A new sub-array or substring of length limit
or less if input array
+had less than limit
elements.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:lowercase.html b/lib/angular/docs/partials/api/ng.filter:lowercase.html
new file mode 100644
index 0000000..aa6b35a
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:lowercase.html
@@ -0,0 +1,15 @@
+lowercase
+(filter in module ng
+)
+
+Description
+
Converts string to lowercase.
+
Usage
+
In HTML Template Binding
+
{{ lowercase_expression | lowercase }}
+
+
In JavaScript
+
$filter('lowercase')()
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:number.html b/lib/angular/docs/partials/api/ng.filter:number.html
new file mode 100644
index 0000000..768e623
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:number.html
@@ -0,0 +1,68 @@
+number
+(filter in module ng
+)
+
+Description
+
Formats a number as text.
+
+
If the input is not a number an empty string is returned.
+
Usage
+
In HTML Template Binding
+
{{ number_expression | number[:fractionSize] }}
+
+
In JavaScript
+
$filter('number')(number[, fractionSize])
+
+
Parameters
+
+
Returns
+
{string}
+–
Number rounded to decimalPlaces and places a “,” after each third digit.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:orderBy.html b/lib/angular/docs/partials/api/ng.filter:orderBy.html
new file mode 100644
index 0000000..9cd94ef
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:orderBy.html
@@ -0,0 +1,106 @@
+orderBy
+(filter in module ng
+)
+
+Description
+
Orders a specified array
by the expression
predicate.
+
+
Note: this function is used to augment the Array
type in Angular expressions. See
+ng.$filter
for more informaton about Angular arrays.
+
Usage
+
filter:orderBy(array, expression[, reverse]);
+
Parameters
+
+
Returns
+
{Array}
+–
Sorted copy of the source array.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ng.filter:uppercase.html b/lib/angular/docs/partials/api/ng.filter:uppercase.html
new file mode 100644
index 0000000..ca4e375
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.filter:uppercase.html
@@ -0,0 +1,15 @@
+uppercase
+(filter in module ng
+)
+
+Description
+
Converts string to uppercase.
+
Usage
+
In HTML Template Binding
+
{{ uppercase_expression | uppercase }}
+
+
In JavaScript
+
$filter('uppercase')()
+
+
+
diff --git a/lib/angular/docs/partials/api/ng.html b/lib/angular/docs/partials/api/ng.html
new file mode 100644
index 0000000..b654c7a
--- /dev/null
+++ b/lib/angular/docs/partials/api/ng.html
@@ -0,0 +1,4 @@
+
+
+
+The ng
is an angular module which contains all of the core angular services.
diff --git a/lib/angular/docs/partials/api/ngCookies.$cookieStore.html b/lib/angular/docs/partials/api/ngCookies.$cookieStore.html
new file mode 100644
index 0000000..d7b6dbb
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngCookies.$cookieStore.html
@@ -0,0 +1,44 @@
+$cookieStore
+(service in module ngCookies
+)
+
+Description
+
Provides a key-value (string-object) storage, that is backed by session cookies.
+Objects put or retrieved from this storage are automatically serialized or
+deserialized by angular's toJson/fromJson.
+
Dependencies
+
+
Methods
+
get(key)
+Returns the value of given cookie key
Parameters
+
key – {string} –
+Id to use for lookup.
+
+
Returns
+
{Object}
+–
Deserialized cookie value.
+
+
+put(key, value)
+Sets a value for given cookie key
Parameters
+
key – {string} –
+Id for the value
.
+value – {Object} –
+Value to be stored.
+
+
+
+remove(key)
+Remove given cookie
Parameters
+
+
+
+
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ngCookies.$cookies.html b/lib/angular/docs/partials/api/ngCookies.$cookies.html
new file mode 100644
index 0000000..a60f879
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngCookies.$cookies.html
@@ -0,0 +1,16 @@
+$cookies
+(service in module ngCookies
+)
+
+Description
+
Provides read/write access to browser's cookies.
+
+
Only a simple Object is exposed and by adding or removing properties to/from
+this object, new cookies are created/deleted at the end of current $eval.
+
Dependencies
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ngCookies.html b/lib/angular/docs/partials/api/ngCookies.html
new file mode 100644
index 0000000..4024f38
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngCookies.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngMock.$exceptionHandler.html b/lib/angular/docs/partials/api/ngMock.$exceptionHandler.html
new file mode 100644
index 0000000..da1d956
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMock.$exceptionHandler.html
@@ -0,0 +1,32 @@
+$exceptionHandler
+(service in module ngMock
+)
+
+Description
+
Mock implementation of ng.$exceptionHandler
that rethrows or logs errors passed
+into it. See $exceptionHandlerProvider for configuration
+information.
+
+
+ describe('$exceptionHandlerProvider', function() {
+
+ it('should capture log messages and exceptions', function() {
+
+ module(function($exceptionHandlerProvider) {
+ $exceptionHandlerProvider.mode('log');
+ });
+
+ inject(function($log, $exceptionHandler, $timeout) {
+ $timeout(function() { $log.log(1); });
+ $timeout(function() { $log.log(2); throw 'banana peel'; });
+ $timeout(function() { $log.log(3); });
+ expect($exceptionHandler.errors).toEqual([]);
+ expect($log.assertEmpty());
+ $timeout.flush();
+ expect($exceptionHandler.errors).toEqual(['banana peel']);
+ expect($log.log.logs).toEqual([[1], [2], [3]]);
+ });
+ });
+ });
+
+
diff --git a/lib/angular/docs/partials/api/ngMock.$exceptionHandlerProvider.html b/lib/angular/docs/partials/api/ngMock.$exceptionHandlerProvider.html
new file mode 100644
index 0000000..863a1ce
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMock.$exceptionHandlerProvider.html
@@ -0,0 +1,28 @@
+$exceptionHandlerProvider
+(service in module ngMock
+)
+
+Description
+
Configures the mock implementation of ng.$exceptionHandler
to rethrow or to log errors passed
+into the $exceptionHandler
.
+
Methods
+
mode(mode)
+Sets the logging mode.
Parameters
+
+
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngMock.$httpBackend.html b/lib/angular/docs/partials/api/ngMock.$httpBackend.html
new file mode 100644
index 0000000..7819d77
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMock.$httpBackend.html
@@ -0,0 +1,433 @@
+$httpBackend
+(service in module ngMock
+)
+
+Description
+
Fake HTTP backend implementation suitable for unit testing application that use the
+$http service
.
+
+
Note : For fake http backend implementation suitable for end-to-end testing or backend-less
+development please see e2e $httpBackend mock .
+
+
During unit testing, we want our unit tests to run quickly and have no external dependencies so
+we don’t want to send XHR or
+JSONP requests to a real server. All we really need is
+to verify whether a certain request has been sent or not, or alternatively just let the
+application make requests, respond with pre-trained responses and assert that the end result is
+what we expect it to be.
+
+
This mock implementation can be used to respond with static or dynamic responses via the
+expect
and when
apis and their shortcuts (expectGET
, whenPOST
, etc).
+
+
When an Angular application needs some data from a server, it calls the $http service, which
+sends the request to a real server using $httpBackend service. With dependency injection, it is
+easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
+the requests and respond with some testing data without sending a request to real server.
+
+
There are two ways to specify what test data should be returned as http responses by the mock
+backend when the code under test makes http requests:
+
+
+$httpBackend.expect
- specifies a request expectation
+$httpBackend.when
- specifies a backend definition
+
+
+
Request Expectations vs Backend Definitions
+
+
Request expectations provide a way to make assertions about requests made by the application and
+to define responses for those requests. The test will fail if the expected requests are not made
+or they are made in the wrong order.
+
+
Backend definitions allow you to define a fake backend for your application which doesn't assert
+if a particular request was made or not, it just returns a trained response if a request is made.
+The test will pass whether or not the request gets made during testing.
+
+
+ Request expectations Backend definitions
+
+ Syntax
+ .expect(...).respond(...)
+ .when(...).respond(...)
+
+
+ Typical usage
+ strict unit tests
+ loose (black-box) unit testing
+
+
+ Fulfills multiple requests
+ NO
+ YES
+
+
+ Order of requests matters
+ YES
+ NO
+
+
+ Request required
+ YES
+ NO
+
+
+ Response required
+ optional (see below)
+ YES
+
+
+
+
In cases where both backend definitions and request expectations are specified during unit
+testing, the request expectations are evaluated first.
+
+
If a request expectation has no response specified, the algorithm will search your backend
+definitions for an appropriate response.
+
+
If a request didn't match any expectation or if the expectation doesn't have the response
+defined, the backend definitions are evaluated in sequential order to see if any of them match
+the request. The response from the first matched definition is returned.
+
+
Flushing HTTP requests
+
+
The $httpBackend used in production, always responds to requests with responses asynchronously.
+If we preserved this behavior in unit testing, we'd have to create async unit tests, which are
+hard to write, follow and maintain. At the same time the testing mock, can't respond
+synchronously because that would change the execution of the code under test. For this reason the
+mock $httpBackend has a flush()
method, which allows the test to explicitly flush pending
+requests and thus preserving the async api of the backend, while allowing the test to execute
+synchronously.
+
+
Unit testing with mock $httpBackend
+
+
+ // controller
+ function MyController($scope, $http) {
+ $http.get('/auth.py').success(function(data) {
+ $scope.user = data;
+ });
+
+ this.saveMessage = function(message) {
+ $scope.status = 'Saving...';
+ $http.post('/add-msg.py', message).success(function(response) {
+ $scope.status = '';
+ }).error(function() {
+ $scope.status = 'ERROR!';
+ });
+ };
+ }
+
+ // testing controller
+ var $httpBackend;
+
+ beforeEach(inject(function($injector) {
+ $httpBackend = $injector.get('$httpBackend');
+
+ // backend definition common for all tests
+ $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
+ }));
+
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+
+ it('should fetch authentication token', function() {
+ $httpBackend.expectGET('/auth.py');
+ var controller = scope.$new(MyController);
+ $httpBackend.flush();
+ });
+
+
+ it('should send msg to server', function() {
+ // now you don’t care about the authentication, but
+ // the controller will still send the request and
+ // $httpBackend will respond without you having to
+ // specify the expectation and response for this request
+ $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
+
+ var controller = scope.$new(MyController);
+ $httpBackend.flush();
+ controller.saveMessage('message content');
+ expect(controller.status).toBe('Saving...');
+ $httpBackend.flush();
+ expect(controller.status).toBe('');
+ });
+
+
+ it('should send auth header', function() {
+ $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
+ // check if the header was send, if it wasn't the expectation won't
+ // match the request and the test will fail
+ return headers['Authorization'] == 'xxx';
+ }).respond(201, '');
+
+ var controller = scope.$new(MyController);
+ controller.saveMessage('whatever');
+ $httpBackend.flush();
+ });
+
+
Methods
+
expect(method, url, data, headers)
+Creates a new request expectation.
Parameters
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+respond – {function([status,] data[, headers])|function(function(method, url, data, headers)}
+– The respond method takes a set of static data to be returned or a function that can return
+an array containing response status (number), response data (string) and response headers
+(Object).
+
+
+
+expectDELETE(url, headers)
+Creates a new request expectation for DELETE requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {Object=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+expectGET(url, headers)
+Creates a new request expectation for GET requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {Object=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled. See #expect for more info.
+
+
+expectHEAD(url, headers)
+Creates a new request expectation for HEAD requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {Object=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+expectJSONP(url)
+Creates a new request expectation for JSONP requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+expectPATCH(url, data, headers)
+Creates a new request expectation for PATCH requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {Object=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+expectPOST(url, data, headers)
+Creates a new request expectation for POST requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {Object=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+expectPUT(url, data, headers)
+Creates a new request expectation for PUT requests. For more info see expect()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {Object=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+flush(count)
+Flushes all pending requests using the trained responses.
Parameters
+
+
+
+resetExpectations()
+Resets all request expectations, but preserves all backend definitions. Typically, you would
+call resetExpectations during a multiple-phase test when you want to reuse the same instance of
+$httpBackend mock.
+
+verifyNoOutstandingExpectation()
+Verifies that all of the requests defined via the expect
api were made. If any of the
+requests were not made, verifyNoOutstandingExpectation throws an exception.
+
+
Typically, you would call this method following each test case that asserts requests using an
+"afterEach" clause.
+
+
+ afterEach($httpBackend.verifyExpectations);
+
+
+verifyNoOutstandingRequest()
+Verifies that there are no outstanding requests that need to be flushed.
+
+
Typically, you would call this method following each test case that asserts requests using an
+"afterEach" clause.
+
+
+ afterEach($httpBackend.verifyNoOutstandingRequest);
+
+
+when(method, url, data, headers)
+Creates a new backend definition.
Parameters
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+respond – {function([status,] data[, headers])|function(function(method, url, data, headers)}
+– The respond method takes a set of static data to be returned or a function that can return
+an array containing response status (number), response data (string) and response headers
+(Object).
+
+
+
+whenDELETE(url, headers)
+Creates a new backend definition for DELETE requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+whenGET(url, headers)
+Creates a new backend definition for GET requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+whenHEAD(url, headers)
+Creates a new backend definition for HEAD requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+whenJSONP(url)
+Creates a new backend definition for JSONP requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+whenPOST(url, data, headers)
+Creates a new backend definition for POST requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+whenPUT(url, data, headers)
+Creates a new backend definition for PUT requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
method that control how a matched
+request is handled.
+
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngMock.$log.html b/lib/angular/docs/partials/api/ngMock.$log.html
new file mode 100644
index 0000000..b92ce57
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMock.$log.html
@@ -0,0 +1,33 @@
+$log
+(service in module ngMock
+)
+
+Description
+
Mock implementation of ng.$log
that gathers all logged messages in arrays
+(one array per logging level). These arrays are exposed as logs
property of each of the
+level-specific log function, e.g. for level error
the array is exposed as $log.error.logs
.
+
Methods
+
assertEmpty()
+Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown.
+
+reset()
+Reset all of the logging arrays to empty.
+
+
+
+
Properties
+
logs
+Array of logged messages.
+
+logs
+Array of logged messages.
+
+logs
+Array of logged messages.
+
+logs
+Array of logged messages.
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngMock.$timeout.html b/lib/angular/docs/partials/api/ngMock.$timeout.html
new file mode 100644
index 0000000..a3475ad
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMock.$timeout.html
@@ -0,0 +1,20 @@
+$timeout
+(service in module ngMock
+)
+
+Description
+
This service is just a simple decorator for $timeout
service
+that adds a "flush" and "verifyNoPendingTasks" methods.
+
Usage
+
+
Methods
+
flush()
+Flushes the queue of pending tasks.
+
+verifyNoPendingTasks()
+Verifies that there are no pending tasks that need to be flushed.
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngMock.html b/lib/angular/docs/partials/api/ngMock.html
new file mode 100644
index 0000000..a82a78d
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMock.html
@@ -0,0 +1,5 @@
+
+
+
+The ngMock
is an angular module which is used with ng
module and adds unit-test configuration as well as useful
+mocks to the $injector
.
diff --git a/lib/angular/docs/partials/api/ngMockE2E.$httpBackend.html b/lib/angular/docs/partials/api/ngMockE2E.$httpBackend.html
new file mode 100644
index 0000000..fcc006a
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMockE2E.$httpBackend.html
@@ -0,0 +1,174 @@
+$httpBackend
+(service in module ngMockE2E
+)
+
+Description
+
Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of
+applications that use the $http service
.
+
+
Note : For fake http backend implementation suitable for unit testing please see
+unit-testing $httpBackend mock .
+
+
This implementation can be used to respond with static or dynamic responses via the when
api
+and its shortcuts (whenGET
, whenPOST
, etc) and optionally pass through requests to the
+real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch
+templates from a webserver).
+
+
As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application
+is being developed with the real backend api replaced with a mock, it is often desirable for
+certain category of requests to bypass the mock and issue a real http request (e.g. to fetch
+templates or static files from the webserver). To configure the backend with this behavior
+use the passThrough
request handler of when
instead of respond
.
+
+
Additionally, we don't want to manually have to flush mocked out requests like we do during unit
+testing. For this reason the e2e $httpBackend automatically flushes mocked out requests
+automatically, closely simulating the behavior of the XMLHttpRequest object.
+
+
To setup the application to run with this http backend, you have to create a module that depends
+on the ngMockE2E
and your application modules and defines the fake backend:
+
+
+ myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
+ myAppDev.run(function($httpBackend) {
+ phones = [{name: 'phone1'}, {name: 'phone2'}];
+
+ // returns the current list of phones
+ $httpBackend.whenGET('/phones').respond(phones);
+
+ // adds a new phone to the phones array
+ $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
+ phones.push(angular.fromJSON(data));
+ });
+ $httpBackend.whenGET(/^\/templates\//).passThrough();
+ //...
+ });
+
+
+
Afterwards, bootstrap your app with this new module.
+
Methods
+
when(method, url, data, headers)
+Creates a new backend definition.
Parameters
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+respond – {function([status,] data[, headers])|function(function(method, url, data, headers)}
+– The respond method takes a set of static data to be returned or a function that can return
+an array containing response status (number), response data (string) and response headers
+(Object).
+passThrough – {function()}
– Any request matching a backend definition with passThrough
+handler, will be pass through to the real backend (an XHR request will be made to the
+server.
+
+
+
+whenDELETE(url, headers)
+Creates a new backend definition for DELETE requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+whenGET(url, headers)
+Creates a new backend definition for GET requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+whenHEAD(url, headers)
+Creates a new backend definition for HEAD requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+whenJSONP(url)
+Creates a new backend definition for JSONP requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+whenPATCH(url, data, headers)
+Creates a new backend definition for PATCH requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+whenPOST(url, data, headers)
+Creates a new backend definition for POST requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+whenPUT(url, data, headers)
+Creates a new backend definition for PUT requests. For more info see when()
.
Parameters
+
url – {string|RegExp} –
+HTTP url.
+data(optional) – {(string|RegExp)=} –
+HTTP request body.
+headers(optional) – {(Object|function(Object))=} –
+HTTP headers.
+
+
Returns
+
{requestHandler}
+–
Returns an object with respond
and passThrough
methods that
+control how a matched request is handled.
+
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngMockE2E.html b/lib/angular/docs/partials/api/ngMockE2E.html
new file mode 100644
index 0000000..811b3e4
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngMockE2E.html
@@ -0,0 +1,6 @@
+
+
+
+The ngMockE2E
is an angular module which contains mocks suitable for end-to-end testing.
+Currently there is only one mock present in this module -
+the e2e $httpBackend mock.
diff --git a/lib/angular/docs/partials/api/ngResource.$resource.html b/lib/angular/docs/partials/api/ngResource.$resource.html
new file mode 100644
index 0000000..ab34138
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngResource.$resource.html
@@ -0,0 +1,244 @@
+$resource
+(service in module ngResource
+)
+
+Description
+
A factory which creates a resource object that lets you interact with
+RESTful server-side data sources.
+
+
The returned resource object has action methods which provide high-level behaviors without
+the need to interact with the low level $http
service.
+
Dependencies
+
+
Usage
+
$resource(url[, paramDefaults][, actions]);
+
Parameters
+
url – {string} –
+A parameterized URL template with parameters prefixed by :
as in
+/user/:username
. If you are using a URL with a port number (e.g.
+http://example.com:8080/api
), you'll need to escape the colon character before the port
+number, like this: $resource('http://example.com\\:8080/api')
.
+paramDefaults(optional) – {Object=} –
+Default values for url
parameters. These can be overridden in
+actions
methods. If any of the parameter value is a function, it will be executed every time
+when a param value needs to be obtained for a request (unless the param was overriden).
+
+Each key value in the parameter object is first bound to url template if present and then any
+excess keys are appended to the url search query after the ?
.
+
+Given a template /path/:verb
and parameter {verb:'greet', salutation:'Hello'}
results in
+URL /path/greet?salutation=Hello
.
+
+If the parameter value is prefixed with @
then the value of that parameter is extracted from
+the data object (useful for non-GET operations).
+actions(optional) – {Object.<Object>=} –
+Hash with declaration of custom action that should extend the
+default set of resource actions. The declaration should be created in the format of $http.config
:
+
+{action1: {method:?, params:?, isArray:?, headers:?, ...},
+ action2: {method:?, params:?, isArray:?, headers:?, ...},
+ ...}
+
+
+Where:
+
+
+action
– {string} – The name of action. This name becomes the name of the method on your
+resource object.
+method
– {string} – HTTP request method. Valid methods are: GET
, POST
, PUT
, DELETE
,
+and JSONP
.
+params
– {Object=} – Optional set of pre-bound parameters for this action. If any of the
+parameter value is a function, it will be executed every time when a param value needs to be
+obtained for a request (unless the param was overriden).
+isArray
– {boolean=} – If true then the returned object for this action is an array, see
+returns
section.
+transformRequest
– {function(data, headersGetter)|Array.<function(data, headersGetter)>}
–
+transform function or an array of such functions. The transform function takes the http
+request body and headers and returns its transformed (typically serialized) version.
+transformResponse
– {function(data, headersGetter)|Array.<function(data, headersGetter)>}
–
+transform function or an array of such functions. The transform function takes the http
+response body and headers and returns its transformed (typically deserialized) version.
+cache
– {boolean|Cache}
– If true, a default $http cache will be used to cache the
+GET request, otherwise if a cache instance built with
+$cacheFactory
, this cache will be used for
+caching.
+timeout
– {number}
– timeout in milliseconds.
+withCredentials
- {boolean}
- whether to to set the withCredentials
flag on the
+XHR object. See requests with credentials for more information.
+responseType
- {string}
- see requestType .
+
+
+
Returns
+
{Object}
+–
A resource "class" object with methods for the default set of resource actions
+optionally extended with custom actions
. The default set contains these actions:
+
+
{ 'get': {method:'GET'},
+ 'save': {method:'POST'},
+ 'query': {method:'GET', isArray:true},
+ 'remove': {method:'DELETE'},
+ 'delete': {method:'DELETE'} };
+
+
+
Calling these methods invoke an ng.$http
with the specified http method,
+destination and parameters. When the data is returned from the server then the object is an
+instance of the resource class save
, remove
and delete
actions are available on it as
+methods with the $
prefix. This allows you to easily perform CRUD operations (create, read,
+update, delete) on server-side data like this:
+
+ var User = $resource('/user/:userId', {userId:'@id'});
+ var user = User.get({userId:123}, function() {
+ user.abc = true;
+ user.$save();
+ });
+
+
+
It is important to realize that invoking a $resource object method immediately returns an
+empty reference (object or array depending on isArray
). Once the data is returned from the
+server the existing reference is populated with the actual data. This is a useful trick since
+usually the resource is assigned to a model which is then rendered by the view. Having an empty
+object results in no rendering, once the data arrives from the server then the object is
+populated with the data and the view automatically re-renders itself showing the new data. This
+means that in most case one never has to write a callback function for the action methods.
+
+
The action methods on the class object or instance object can be invoked with the following
+parameters:
+
+
+HTTP GET "class" actions: Resource.action([parameters], [success], [error])
+non-GET "class" actions: Resource.action([parameters], postData, [success], [error])
+non-GET instance actions: instance.$action([parameters], [success], [error])
+
+
+
Example
+
Credit card resource
+
+
+ // Define CreditCard class
+ var CreditCard = $resource('/user/:userId/card/:cardId',
+ {userId:123, cardId:'@id'}, {
+ charge: {method:'POST', params:{charge:true}}
+ });
+
+ // We can retrieve a collection from the server
+ var cards = CreditCard.query(function() {
+ // GET: /user/123/card
+ // server returns: [ {id:456, number:'1234', name:'Smith'} ];
+
+ var card = cards[0];
+ // each item is an instance of CreditCard
+ expect(card instanceof CreditCard).toEqual(true);
+ card.name = "J. Smith";
+ // non GET methods are mapped onto the instances
+ card.$save();
+ // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
+ // server returns: {id:456, number:'1234', name: 'J. Smith'};
+
+ // our custom method is mapped as well.
+ card.$charge({amount:9.99});
+ // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
+ });
+
+ // we can create an instance as well
+ var newCard = new CreditCard({number:'0123'});
+ newCard.name = "Mike Smith";
+ newCard.$save();
+ // POST: /user/123/card {number:'0123', name:'Mike Smith'}
+ // server returns: {id:789, number:'01234', name: 'Mike Smith'};
+ expect(newCard.id).toEqual(789);
+
+
+
The object returned from this function execution is a resource "class" which has "static" method
+for each action in the definition.
+
+
Calling these methods invoke $http
on the url
template with the given method
, params
and headers
.
+When the data is returned from the server then the object is an instance of the resource type and
+all of the non-GET methods are available with $
prefix. This allows you to easily support CRUD
+operations (create, read, update, delete) on server-side data.
+
+
+ var User = $resource('/user/:userId', {userId:'@id'});
+ var user = User.get({userId:123}, function() {
+ user.abc = true;
+ user.$save();
+ });
+
+
+
It's worth noting that the success callback for `get`, `query` and other method gets passed
+in the response that came from the server as well as $http header getter function, so one
+could rewrite the above example and get access to http headers as:
+
+
+
+ var User = $resource('/user/:userId', {userId:'@id'});
+ User.get({userId:123}, function(u, getResponseHeaders){
+ u.abc = true;
+ u.$save(function(u, putResponseHeaders) {
+ //u => saved user object
+ //putResponseHeaders => $http header getter
+ });
+ });
+
+
+
Buzz client
+
+
Let's look at what a buzz client created with the $resource
service looks like:
+
Source
+
+
Demo
+
+
diff --git a/lib/angular/docs/partials/api/ngResource.html b/lib/angular/docs/partials/api/ngResource.html
new file mode 100644
index 0000000..4024f38
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngResource.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/lib/angular/docs/partials/api/ngSanitize.$sanitize.html b/lib/angular/docs/partials/api/ngSanitize.$sanitize.html
new file mode 100644
index 0000000..cd7844b
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngSanitize.$sanitize.html
@@ -0,0 +1,102 @@
+$sanitize
+(service in module ngSanitize
+)
+
+Description
+
The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
+then serialized back to properly escaped html string. This means that no unsafe input can make
+it into the returned string, however, since our parser is more strict than a typical browser
+parser, it's possible that some obscure input, which would be recognized as valid HTML by a
+browser, won't make it through the sanitizer.
+
Usage
+
$sanitize(html);
+
Parameters
+
html – {string} –
+Html input.
+
+
Returns
+
{string}
+–
Sanitized html.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ngSanitize.directive:ngBindHtml.html b/lib/angular/docs/partials/api/ngSanitize.directive:ngBindHtml.html
new file mode 100644
index 0000000..114e02d
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngSanitize.directive:ngBindHtml.html
@@ -0,0 +1,22 @@
+ngBindHtml
+(directive in module ngSanitize
+)
+
+Description
+
Creates a binding that will sanitize the result of evaluating the expression
with the
+$sanitize service and innerHTML the result into the current element.
+
+
See $sanitize docs for examples.
+
Usage
+
as attribute
<ANY ng-bind-html="{expression}">
+ ...
+</ANY>
+as class
<ANY class="ng-bind-html: {expression};">
+ ...
+</ANY>
+
Parameters
+
ngBindHtml – {expression} –
+Expression to evaluate.
+
+
+
diff --git a/lib/angular/docs/partials/api/ngSanitize.filter:linky.html b/lib/angular/docs/partials/api/ngSanitize.filter:linky.html
new file mode 100644
index 0000000..18355ab
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngSanitize.filter:linky.html
@@ -0,0 +1,116 @@
+linky
+(filter in module ngSanitize
+)
+
+Description
+
Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
+plain email address links.
+
Usage
+
In HTML Template Binding
+
<span ng-bind-html="linky_expression | linky"></span>
+
+
In JavaScript
+
$filter('linky')(text, target)
+
+
Parameters
+
+
Returns
+
{string}
+–
Html-linkified text.
+
+
Example
+
+
diff --git a/lib/angular/docs/partials/api/ngSanitize.html b/lib/angular/docs/partials/api/ngSanitize.html
new file mode 100644
index 0000000..4024f38
--- /dev/null
+++ b/lib/angular/docs/partials/api/ngSanitize.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/lib/angular/docs/partials/cookbook/advancedform.html b/lib/angular/docs/partials/cookbook/advancedform.html
new file mode 100644
index 0000000..943eb93
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/advancedform.html
@@ -0,0 +1,139 @@
+
+
+
+Here we extend the basic form example to include common features such as reverting, dirty state
+detection, and preventing invalid form submission.
+
+
Source
+
+
Demo
+
+
+
Things to notice
+
+
+Cancel & save buttons are only enabled if the form is dirty — there is something to cancel or
+save.
+Save button is only enabled if there are no validation errors on the form.
+Cancel reverts the form changes back to original state.
+Save updates the internal model of the form.
+Debug view shows the two models. One presented to the user form and the other being the pristine
+copy master.
+
diff --git a/lib/angular/docs/partials/cookbook/buzz.html b/lib/angular/docs/partials/cookbook/buzz.html
new file mode 100644
index 0000000..57d1397
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/buzz.html
@@ -0,0 +1,73 @@
+
+
+
+External resources are URLs that provide JSON data, which are then rendered with the help of
+templates. Angular has a resource factory that can be used to give names to the URLs and then
+attach behavior to them. For example you can use the
+Google Buzz API
+to retrieve Buzz activity and comments.
+
+
Source
+
+
Demo
+
diff --git a/lib/angular/docs/partials/cookbook/deeplinking.html b/lib/angular/docs/partials/cookbook/deeplinking.html
new file mode 100644
index 0000000..124dd44
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/deeplinking.html
@@ -0,0 +1,172 @@
+
+
+
+Deep linking allows you to encode the state of the application in the URL so that it can be
+bookmarked and the application can be restored from the URL to the same state.
+
+
While Angular does not force you to deal with bookmarks in any particular way, it has services
+which make the common case described here very easy to implement.
+
+
Assumptions
+
+
Your application consists of a single HTML page which bootstraps the application. We will refer
+to this page as the chrome.
+Your application is divided into several screens (or views) which the user can visit. For example,
+the home screen, settings screen, details screen, etc. For each of these screens, we would like to
+assign a URL so that it can be bookmarked and later restored. Each of these screens will be
+associated with a controller which define the screen's behavior. The most common case is that the
+screen will be constructed from an HTML snippet, which we will refer to as the partial. Screens can
+have multiple partials, but a single partial is the most common construct. This example makes the
+partial boundary visible using a blue line.
+
+
You can make a routing table which shows which URL maps to which partial view template and which
+controller.
+
+
Example
+
+
In this example we have a simple app which consist of two screens:
+
+
+Welcome: url welcome
Show the user contact information.
+Settings: url settings
Show an edit screen for user contact information.
+
+
+
Source
+
+
Demo
+
+
+
Things to notice
+
+
+Routes are defined in the AppCntl
class. The initialization of the controller causes the
+initialization of the $route
service with the proper URL
+routes.
+The $route
service then watches the URL and instantiates the
+appropriate controller when the URL changes.
+The ngView
widget loads the
+view when the URL changes. It also sets the view scope to the newly instantiated controller.
+Changing the URL is sufficient to change the controller and view. It makes no difference whether
+the URL is changed programatically or by the user.
+
diff --git a/lib/angular/docs/partials/cookbook/form.html b/lib/angular/docs/partials/cookbook/form.html
new file mode 100644
index 0000000..ae59372
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/form.html
@@ -0,0 +1,125 @@
+
+
+
+A web application's main purpose is to present and gather data. For this reason Angular strives
+to make both of these operations trivial. This example shows off how you can build a simple form to
+allow a user to enter data.
+
+
Source
+
+
Demo
+
+
+
Things to notice
+
+
+The user data model is initialized controller
and is
+available in the scope
with the initial data.
+For debugging purposes we have included a debug view of the model to better understand what
+is going on.
+The input directives
simply refer
+to the model and are data-bound.
+The inputs validate. (Try leaving them blank or entering non digits in the zip field)
+In your application you can simply read from or write to the model and the form will be updated.
+By clicking the 'add' link you are adding new items into the user.contacts
array which are then
+reflected in the view.
+
diff --git a/lib/angular/docs/partials/cookbook/helloworld.html b/lib/angular/docs/partials/cookbook/helloworld.html
new file mode 100644
index 0000000..945a6fa
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/helloworld.html
@@ -0,0 +1,52 @@
+
+
+
+Source
+
+
Demo
+
+
+
Things to notice
+
+
Take a look through the source and note:
+
+
+The script tag that bootstraps the Angular environment.
+The text input form control
which is
+bound to the greeting name text.
+There is no need for listener registration and event firing on change events.
+The implicit presence of the name
variable which is in the root scope
.
+The double curly brace {{markup}}
, which binds the name variable to the greeting text.
+The concept of data binding , which reflects any
+changes to the
+input field in the greeting text.
+
diff --git a/lib/angular/docs/partials/cookbook/index.html b/lib/angular/docs/partials/cookbook/index.html
new file mode 100644
index 0000000..c19b3bc
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/index.html
@@ -0,0 +1,50 @@
+
+
+
+Welcome to the Angular cookbook. Here we will show you typical uses of Angular by example.
+
+
Hello World
+
+
Hello World : The simplest possible application that demonstrates the
+classic Hello World!
+
+
Basic Form
+
+
Basic Form : Displaying forms to the user for editing is the bread and butter
+of web applications. Angular makes forms easy through bidirectional data binding.
+
+
Advanced Form
+
+
Advanced Form : Taking the form example to the next level and
+providing advanced features such as dirty detection, form reverting and submit disabling if
+validation errors exist.
+
+
Model View Controller
+
+
MVC : Tic-Tac-Toe: Model View Controller (MVC) is a time-tested design pattern
+to separate the behavior (JavaScript controller) from the presentation (HTML view). This
+separation aids in maintainability and testability of your project.
+
+
Multi-page App and Deep Linking
+
+
Deep Linking : An AJAX application never navigates away from the
+first page it loads. Instead, it changes the DOM of its single page. Eliminating full-page reloads
+is what makes AJAX apps responsive, but it creates a problem in that apps with a single URL
+prevent you from emailing links to a particular screen within your application.
+
+
Deep linking tries to solve this by changing the URL anchor without reloading a page, thus
+allowing you to send links to specific screens in your app.
+
+
Services
+
+
Services : Services are long lived objects in your applications that are
+available across controllers. A collection of useful services are pre-bundled with Angular but you
+will likely add your own. Services are initialized using dependency injection, which resolves the
+order of initialization. This safeguards you from the perils of global state (a common way to
+implement long lived objects).
+
+
External Resources
+
+
Resources : Web applications must be able to communicate with the external
+services to get and update data. Resources are the abstractions of external URLs which are
+specially tailored to Angular data binding.
diff --git a/lib/angular/docs/partials/cookbook/mvc.html b/lib/angular/docs/partials/cookbook/mvc.html
new file mode 100644
index 0000000..ebac5ef
--- /dev/null
+++ b/lib/angular/docs/partials/cookbook/mvc.html
@@ -0,0 +1,138 @@
+
+
+
+MVC allows for a clean and testable separation between the behavior (controller) and the view
+(HTML template). A Controller is just a JavaScript class which is grafted onto the scope of the
+view. This makes it very easy for the controller and the view to share the model.
+
+
The model is a set of objects and primitives that are referenced from the Scope ($scope) object.
+This makes it very easy to test the controller in isolation since one can simply instantiate the
+controller and test without a view, because there is no connection between the controller and the
+view.
+
+
Source
+
+
Demo
+
+
+
Things to notice
+
+
+The controller is defined in JavaScript and has no reference to the rendering logic.
+The controller is instantiated by Angular and injected into the view.
+The controller can be instantiated in isolation (without a view) and the code will still execute.
+This makes it very testable.
+The HTML view is a projection of the model. In the above example, the model is stored in the
+board variable.
+All of the controller's properties (such as board and nextMove) are available to the view.
+Changing the model changes the view.
+The view can call any controller function.
+In this example, the setUrl()
and readUrl()
functions copy the game state to/from the URL's
+hash so the browser's back button will undo game steps. See deep-linking. This example calls $watch()
to set up a listener that invokes readUrl()
when needed.
+
diff --git a/lib/angular/docs/partials/guide/bootstrap.html b/lib/angular/docs/partials/guide/bootstrap.html
new file mode 100644
index 0000000..2d88767
--- /dev/null
+++ b/lib/angular/docs/partials/guide/bootstrap.html
@@ -0,0 +1,101 @@
+
+
+
+Overview
+
+
This page explains the Angular initialization process and how you can manually initialize Angular
+if necessary.
+
+
Angular <script>
Tag
+
+
This example shows the recommended path for integrating Angular with what we call automatic
+initialization.
+
+
+<!doctype html>
+<html xmlns:ng="http://angularjs.org" ng-app>
+ <body>
+ ...
+ <script src="angular.js">
+ </body>
+</html>
+
+
+
+Place the script
tag at the bottom of the page. Placing script tags at the end of the page
+improves app load time because the HTML loading is not blocked by loading of the angular.js
+script. You can get the latest bits from http://code.angularjs.org . Please don't link
+your production code to this URL, as it will expose a security hole on your site. For
+experimental development linking to our site is fine.
+Choose: angular-[version].js
for a human-readable file, suitable for development and
+debugging.
+Choose: angular-[version].min.js
for a compressed and obfuscated file, suitable for use in
+production.
+Place ng-app
to the root of your application, typically on the <html>
tag if you want
+angular to auto-bootstrap your application.
+
+<html ng-app>
+
+If you choose to use the old style directive syntax ng:
then include xml-namespace in html
+to make IE happy. (This is here for historical reasons, and we no longer recommend use of
+ng:
.)
+
+<html xmlns:ng="http://angularjs.org">
+
+
+
+
Automatic Initialization
+
+
Angular initializes automatically upon DOMContentLoaded
event, at which point Angular looks for
+the ng-app
directive which
+designates your application root. If the ng-app
directive is found then Angular
+will:
+
+
+load the module associated with the directive.
+create the application injector
+compile the DOM treating the ng-app
directive as the root of the compilation. This allows you to tell it to treat only a
+portion of the DOM as an Angular application.
+
+
+
+<!doctype html>
+<html ng-app="optionalModuleName">
+ <body>
+ I can add: {{ 1+2 }}.
+ <script src="angular.js"></script>
+ </body>
+</html>
+
+
+
Manual Initialization
+
+
If you need to have more control over the initialization process, you can use a manual
+bootstrapping method instead. Examples of when you'd need to do this include using script loaders
+or the need to perform an operation before Angular compiles a page.
+
+
Here is an example of manually initializing Angular. The example is equivalent to using the ng-app
directive.
+
+
+<!doctype html>
+<html xmlns:ng="http://angularjs.org">
+ <body>
+ Hello {{'World'}}!
+ <script src="http://code.angularjs.org/angular.js"></script>
+ <script>
+ angular.element(document).ready(function() {
+ angular.bootstrap(document);
+ });
+ </script>
+ </body>
+</html>
+
+
+
This is the sequence that your code should follow:
+
+
+After the page and all of the code is loaded, find the root of the HTML template, which is
+typically the root of the document.
+Call api/angular.bootstrap
to compile the template into an
+executable, bi-directionally bound application.
+
diff --git a/lib/angular/docs/partials/guide/compiler.html b/lib/angular/docs/partials/guide/compiler.html
new file mode 100644
index 0000000..65ac75b
--- /dev/null
+++ b/lib/angular/docs/partials/guide/compiler.html
@@ -0,0 +1,142 @@
+
+
+
+Overview
+
+
Angular's HTML compiler
allows the developer to teach the
+browser new HTML syntax. The compiler allows you to attach behavior to any HTML element or attribute
+and even create new HTML element or attributes with custom behavior. Angular calls these behavior
+extensions directives
.
+
+
HTML has a lot of constructs for formatting the HTML for static documents in a declarative fashion.
+For example if something needs to be centered, there is no need to provide instructions to the
+browser how the window size needs to be divided in half so that center is found, and that this
+center needs to be aligned with the text's center. Simply add align="center"
attribute to any
+element to achieve the desired behavior. Such is the power of declarative language.
+
+
But the declarative language is also limited, since it does not allow you to teach the browser new
+syntax. For example there is no easy way to get the browser to align the text at 1/3 the position
+instead of 1/2. What is needed is a way to teach browser new HTML syntax.
+
+
Angular comes pre-bundled with common directives which are useful for building any app. We also
+expect that you will create directives that are specific to your app. These extension become a
+Domain Specific Language for building your application.
+
+
All of this compilation takes place in the web browser; no server side or pre-compilation step is
+involved.
+
+
Compiler
+
+
Compiler is an angular service which traverses the DOM looking for attributes. The compilation
+process happens into two phases.
+
+
+Compile: traverse the DOM and collect all of the directives. The result is a linking
+function.
+Link: combine the directives with a scope and produce a live view. Any changes in the
+scope model are reflected in the view, and any user interactions with the view are reflected
+in the scope model. This makes the scope model the single source of truth.
+
+
+
Some directives such ng-repeat
clone DOM elements once for each item in collection. Having a compile and link phase
+improves performance since the cloned template only needs to be compiled once, and then linked
+once for each clone instance.
+
+
Directive
+
+
A directive is a behavior which should be triggered when specific HTML constructs are encountered in
+the compilation process. The directives can be placed in element names, attributes, class names, as
+well as comments. Here are some equivalent examples of invoking the ng-bind
directive.
+
+
+ <span ng-bind="exp"></span>
+ <span class="ng-bind: exp;"></span>
+ <ng-bind></ng-bind>
+ <!-- directive: ng-bind exp -->
+
+
+
A directive is just a function which executes when the compiler encounters it in the DOM. See directive API
for in-depth documentation on how
+to write directives.
+
+
Here is a directive which makes any element draggable. Notice the draggable
attribute on the
+<span>
element.
+
+
Source
+
+
Demo
+
+
+
The presence of the draggable
attribute on any element gives the element new behavior. The beauty of
+this approach is that we have taught the browser a new trick. We have extended the vocabulary of
+what the browser understands in a way which is natural to anyone who is familiar with HTML
+principles.
+
+
Understanding View
+
+
There are many templating systems out there. Most of them consume a static string template and
+combine it with data, resulting in a new string. The resulting text is then innerHTML
ed into
+an element.
+
+
+
+
This means that any changes to the data need to be re-merged with the template and then
+innerHTML
ed into the DOM. Some of the issues with this approach are: reading user input and merging it with data,
+clobbering user input by overwriting it, managing the whole update process, and lack of behavior
+expressiveness.
+
+
Angular is different. The Angular compiler consumes the DOM with directives, not string templates.
+The result is a linking function, which when combined with a scope model results in a live view. The
+view and scope model bindings are transparent. No action from the developer is needed to update
+the view. And because no innerHTML
is used there are no issues of clobbering user input.
+Furthermore, Angular directives can contain not just text bindings, but behavioral constructs as
+well.
+
+
+
+
The Angular approach produces a stable DOM. This means that the DOM element instance bound to a model
+item instance does not change for the lifetime of the binding. This means that the code can get
+hold of the elements and register event handlers and know that the reference will not be destroyed
+by template data merge.
diff --git a/lib/angular/docs/partials/guide/concepts.html b/lib/angular/docs/partials/guide/concepts.html
new file mode 100644
index 0000000..5b1fba1
--- /dev/null
+++ b/lib/angular/docs/partials/guide/concepts.html
@@ -0,0 +1,523 @@
+
+
+
+Overview
+
+
This document gives a quick overview of the main angular components and how they work together.
+These are:
+
+
+startup - bring up hello world
+runtime - overview of angular runtime
+scope - the glue between the view and the controller
+controller - application behavior
+model - your application data
+view - what the user sees
+directives - extend HTML vocabulary
+filters - format the data in user locale
+injector - assembles your application
+module - configures the injector
+$
- angular namespace
+
+
+
+
+
Startup
+
+
This is how we get the ball rolling (refer to the diagram and example below):
+
+
+
+
+The browser loads the HTML and parses it into a DOM
+The browser loads angular.js
script
+Angular waits for DOMContentLoaded
event
+Angular looks for ng-app
+directive , which designates the application boundary
+The Module specified in ng-app
(if any) is used to configure
+ the $injector
+The $injector
is used to create the $compile
service as well as $rootScope
+The $compile
service is used to compile the DOM and link
+ it with $rootScope
+The ng-init
directive assigns World
to the name
property on the scope
+The {{name}}
interpolates
the expression to
+ Hello World!
+
+
+
+
+
+
Source
+
+
Demo
+
+
+
+
+
Runtime
+
+
+
+
The diagram and the example below describe how Angular interacts with the browser's event loop.
+
+
+The browser's event-loop waits for an event to arrive. An event is a user interactions, timer event,
+ or network event (response from a server).
+The event's callback gets executed. This enters the JavaScript context. The callback can
+ modify the DOM structure.
+Once the callback executes, the browser leaves the JavaScript context and
+ re-renders the view based on DOM changes.
+
+
+
Angular modifies the normal JavaScript flow by providing its own event processing loop. This
+splits the JavaScript into classical and Angular execution context. Only operations which are
+applied in Angular execution context will benefit from Angular data-binding, exception handling,
+property watching, etc... You can also use $apply() to enter Angular execution context from JavaScript. Keep in
+mind that in most places (controllers, services) $apply has already been called for you by the
+directive which is handling the event. An explicit call to $apply is needed only when
+implementing custom event callbacks, or when working with a third-party library callbacks.
+
+
+Enter Angular execution context by calling scope .
$apply
(stimulusFn)
. Where stimulusFn
is
+ the work you wish to do in Angular execution context.
+Angular executes the stimulusFn()
, which typically modifies application state.
+Angular enters the $digest
loop. The
+ loop is made up of two smaller loops which process $evalAsync
queue and the $watch
list. The $digest
loop keeps iterating until the model
+ stabilizes, which means that the $evalAsync
queue is empty and the $watch
list does not detect any changes.
+The $evalAsync
queue is used to
+ schedule work which needs to occur outside of current stack frame, but before the browser's
+ view render. This is usually done with setTimeout(0)
, but the setTimeout(0)
approach
+ suffers from slowness and may cause view flickering since the browser renders the view after
+ each event.
+The $watch
list is a set of expressions
+ which may have changed since last iteration. If a change is detected then the $watch
+ function is called which typically updates the DOM with the new value.
+Once the Angular $digest
loop finishes
+ the execution leaves the Angular and JavaScript context. This is followed by the browser
+ re-rendering the DOM to reflect any changes.
+
+
+
Here is the explanation of how the Hello wold
example achieves the data-binding effect when the
+user enters text into the text field.
+
+
+During the compilation phase:
+the ng-model
and input
directive set up a keydown
listener on the <input>
control.
+the {{name}}
interpolation
+sets up a $watch
to be notified of
+name
changes.
+During the runtime phase:
+Pressing an 'X
' key causes the browser to emit a keydown
event on the input control.
+The input
directive
+captures the change to the input's value and calls $apply
("name = 'X';")
to update the
+application model inside the Angular execution context.
+Angular applies the name = 'X';
to the model.
+The $digest
loop begins
+The $watch
list detects a change
+on the name
property and notifies the {{name}}
interpolation, which in turn updates the DOM.
+Angular exits the execution context, which in turn exits the keydown
event and with it
+the JavaScript execution context.
+The browser re-renders the view with update text.
+
+
+
+
+
+
Source
+
+
Demo
+
+
+
+
+
Scope
+
+
The scope is responsible for detecting changes to the model section and
+provides the execution context for expressions. The scopes are nested in a hierarchical structure
+which closely follow the DOM structure. (See individual directive documentation to see which
+directives cause a creation of new scopes.)
+
+
The following example demonstrates how name
expression will evaluate
+into different value depending on which scope it is evaluated in. The example is followed by
+a diagram depicting the scope boundaries.
+
+
+
+
+
+
+
+
+
+
+
Controller
+
+
+
+
A controller is the code behind the view. Its job is to construct the model and publish it to the
+view along with callback methods. The view is a projection of the scope onto the template (the
+HTML). The scope is the glue which marshals the model to the view and forwards the events to the
+controller.
+
+
The separation of the controller and the view is important because:
+
+
+The controller is written in JavaScript. JavaScript is imperative. Imperative is a good fit
+for specifying application behavior. The controller should not contain any rendering
+information (DOM references or HTML fragments).
+The view template is written in HTML. HTML is declarative. Declarative is a good fit for
+specifying UI. The View should not contain any behavior.
+Since the controller is unaware of the view, there could be many views for the same
+controller. This is important for re-skinning, device specific views (i.e. mobile vs desktop),
+and testability.
+
+
+
+
+
+
Source
+
+
Demo
+
+
+
+
+
Model
+
+
+
+
The model is the data which is used merged with the template to produce the view. To be able to
+render the model into the view, the model has to be able to be referenced from the scope. Unlike many
+other frameworks Angular makes no restrictions or requirements an the model. There are no classes
+to inherit from or special accessor methods for accessing or changing the model. The model can be
+primitive, object hash, or a full object Type. In short the model is a plain JavaScript object.
+
+
+
+
+
+
+
View
+
+
+
+
The view is what the users sees. The view begins its life as a template, it is merged with the
+model and finally rendered into the browser DOM. Angular takes a very different approach to
+rendering the view, compared to most other templating systems.
+
+
+Others - Most templating systems begin as an HTML string with special templating markup.
+Often the template markup breaks the HTML syntax which means that the template can not be
+edited by an HTML editor. The template string is then parsed by the template engine, and
+merged with the data. The result of the merge is an HTML string. The HTML string is then
+written to the browser using the .innerHTML
, which causes the browser to render the HTML.
+When the model changes the whole process needs to be repeated. The granularity of the template
+is the granularity of the DOM updates. The key here is that the templating system manipulates
+strings.
+Angular - Angular is different, since its templating system works on DOM objects not on
+strings. The template is still written in an HTML string, but it is HTML (not HTML with
+template sprinkled in.) The browser parses the HTML into the DOM, and the DOM becomes the input to
+the template engine known as the compiler
. The compiler
+looks for directives which in turn set up watches
on the model. The result is a
+continuously updating view which does not need template model re-merging. Your model becomes
+the single source-of-truth for your view.
+
+
+
+
+
+
Source
+
+
Demo
+
+
+
+
+
Directives
+
+
A directive is a behavior or DOM transformation which is triggered by the presence of a custom attribute,
+element name, or a class name. A directive allows you to extend the HTML vocabulary in a
+declarative fashion. Following is an example which enables data-binding for the contenteditable
+in HTML.
+
+
Source
+
+
Demo
+
+
+
+
+
Filters
+
+
Filters
perform data transformation. Typically
+they are used in conjunction with the locale to format the data in locale specific output.
+They follow the spirit of UNIX filters and use similar syntax |
(pipe).
+
+
Source
+
+
Demo
+
+
+
+
+
+
Modules and the Injector
+
+
+
+
The injector
is a service locator. There is a single
+injector
per Angular application
. The injector
provides a way to look up an object instance by its
+name. The injector keeps an internal cache of all objects so that repeated calls to get the same
+object name result in the same instance. If the object does not exist, then the injector
asks the instance factory to create a new instance.
+
+
A module
is a way to configure the injector's instance factory, known
+as a provider
.
+
+
+
+
+
+ // Create a module
+ var myModule = angular.module('myModule', [])
+
+ // Configure the injector
+ myModule.factory('serviceA', function() {
+ return {
+ // instead of {}, put your object creation here
+ };
+ });
+
+ // create an injector and configure it from 'myModule'
+ var $injector = angular.injector(['myModule']);
+
+ // retrieve an object from the injector by name
+ var serviceA = $injector.get('serviceA');
+
+ // always true because of instance cache
+ $injector.get('serviceA') === $injector.get('serviceA');
+
+
+
But the real magic of the injector
is that it can be
+used to call
methods and instantiate
types. This subtle feature is what
+allows the methods and types to ask for their dependencies instead of having to look for them.
+
+
+ // You write functions such as this one.
+ function doSomething(serviceA, serviceB) {
+ // do something here.
+ }
+
+ // Angular provides the injector for your application
+ var $injector = ...;
+
+ ///////////////////////////////////////////////
+ // the old-school way of getting dependencies.
+ var serviceA = $injector.get('serviceA');
+ var serviceB = $injector.get('serviceB');
+
+ // now call the function
+ doSomething(serviceA, serviceB);
+
+ ///////////////////////////////////////////////
+ // the cool way of getting dependencies.
+ // the $injector will supply the arguments to the function automatically
+ $injector.invoke(doSomething); // This is how the framework calls your functions
+
+
+
Notice that the only thing you needed to write was the function, and list the dependencies in the
+function arguments. When angular calls the function, it will use the call
which will automatically fill the function
+arguments.
+
+
Examine the ClockCtrl
bellow, and notice how it lists the dependencies in the constructor. When the
+ng-controller
instantiates
+the controller it automatically provides the dependencies. There is no need to create
+dependencies, look for dependencies, or even get a reference to the injector.
+
+
Source
+
+
Demo
+
+
+
+
+
Angular Namespace
+
+
To prevent accidental name collision, Angular prefixes names of objects which could potentially
+collide with $
. Please do not use the $
prefix in your code as it may accidentally collide
+with Angular code.
diff --git a/lib/angular/docs/partials/guide/dev_guide.e2e-testing.html b/lib/angular/docs/partials/guide/dev_guide.e2e-testing.html
new file mode 100644
index 0000000..0ff152e
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.e2e-testing.html
@@ -0,0 +1,211 @@
+
+
+
+As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
+verify the correctness of new features, catch bugs and notice regressions.
+
+
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
+that will help you verify the health of your Angular application.
+
+
Overview
+
+
You will write scenario tests in JavaScript, which describe how your application should behave,
+given a certain interaction in a specific state. A scenario is comprised of one or more it
blocks
+(you can think of these as the requirements of your application), which in turn are made of
+commands and expectations . Commands tell the Runner to do something with the application
+(such as navigate to a page or click on a button), and expectations tell the Runner to assert
+something about the state (such as the value of a field or the current URL). If any expectation
+fails, the runner marks the it
as "failed" and continues on to the next one. Scenarios may also
+have beforeEach and afterEach blocks, which will be run before (or after) each it
block,
+regardless of whether they pass or fail.
+
+
+
+
In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
+code in the it
blocks.
+
+
Here is an example of a simple scenario:
+
+describe('Buzz Client', function() {
+it('should filter results', function() {
+ input('user').enter('jacksparrow');
+ element(':button').click();
+ expect(repeater('ul li').count()).toEqual(10);
+ input('filterText').enter('Bees');
+ expect(repeater('ul li').count()).toEqual(1);
+});
+});
+
+This scenario describes the requirements of a Buzz Client, specifically, that it should be able to
+filter the stream of the user. It starts by entering a value in the 'user' input field, clicking
+the only button on the page, and then it verifies that there are 10 items listed. It then enters
+'Bees' in the 'filterText' input field and verifies that the list is reduced to a single item.
+
+
The API section below lists the available commands and expectations for the Runner.
+
+
API
+
+
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js
+
+
pause()
+
+
Pauses the execution of the tests until you call resume()
in the console (or click the resume
+link in the Runner UI).
+
+
sleep(seconds)
+
+
Pauses the execution of the tests for the specified number of seconds
.
+
+
browser().navigateTo(url)
+
+
Loads the url
into the test frame.
+
+
browser().navigateTo(url, fn)
+
+
Loads the URL returned by fn
into the testing frame. The given url
is only used for the test
+output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
+write the test).
+
+
browser().reload()
+
+
Refreshes the currently loaded page in the test frame.
+
+
browser().window().href()
+
+
Returns the window.location.href of the currently loaded page in the test frame.
+
+
browser().window().path()
+
+
Returns the window.location.pathname of the currently loaded page in the test frame.
+
+
browser().window().search()
+
+
Returns the window.location.search of the currently loaded page in the test frame.
+
+
browser().window().hash()
+
+
Returns the window.location.hash (without #
) of the currently loaded page in the test frame.
+
+
browser().location().url()
+
+
Returns the $location.url()
of the currently loaded page in
+the test frame.
+
+
browser().location().path()
+
+
Returns the $location.path()
of the currently loaded page in
+the test frame.
+
+
browser().location().search()
+
+
Returns the $location.search()
of the currently loaded page
+in the test frame.
+
+
browser().location().hash()
+
+
Returns the $location.hash()
of the currently loaded page in
+the test frame.
+
+
expect(future).{matcher}
+
+
Asserts the value of the given future
satisfies the matcher
. All API statements return a
+future
object, which get a value
assigned after they are executed. Matchers are defined using
+angular.scenario.matcher
, and they use the value of futures to run the expectation. For example:
+expect(browser().location().href()).toEqual('http://www.google.com')
+
+
expect(future).not().{matcher}
+
+
Asserts the value of the given future
satisfies the negation of the matcher
.
+
+
using(selector, label)
+
+
Scopes the next DSL element selection.
+
+
binding(name)
+
+
Returns the value of the first binding matching the given name
.
+
+
input(name).enter(value)
+
+
Enters the given value
in the text field with the given name
.
+
+
input(name).check()
+
+
Checks/unchecks the checkbox with the given name
.
+
+
input(name).select(value)
+
+
Selects the given value
in the radio button with the given name
.
+
+
input(name).val()
+
+
Returns the current value of an input field with the given name
.
+
+
repeater(selector, label).count()
+
+
Returns the number of rows in the repeater matching the given jQuery selector
. The label
is
+used for test output.
+
+
repeater(selector, label).row(index)
+
+
Returns an array with the bindings in the row at the given index
in the repeater matching the
+given jQuery selector
. The label
is used for test output.
+
+
repeater(selector, label).column(binding)
+
+
Returns an array with the values in the column with the given binding
in the repeater matching
+the given jQuery selector
. The label
is used for test output.
+
+
select(name).option(value)
+
+
Picks the option with the given value
on the select with the given name
.
+
+
select(name).option(value1, value2...)
+
+
Picks the options with the given values
on the multi select with the given name
.
+
+
element(selector, label).count()
+
+
Returns the number of elements that match the given jQuery selector
. The label
is used for test
+output.
+
+
element(selector, label).click()
+
+
Clicks on the element matching the given jQuery selector
. The label
is used for test output.
+
+
element(selector, label).query(fn)
+
+
Executes the function fn(selectedElements, done)
, where selectedElements are the elements that
+match the given jQuery selector
and done
is a function that is called at the end of the fn
+function. The label
is used for test output.
+
+
element(selector, label).{method}()
+
+
Returns the result of calling method
on the element matching the given jQuery selector
, where
+method
can be any of the following jQuery methods: val
, text
, html
, height
,
+innerHeight
, outerHeight
, width
, innerWidth
, outerWidth
, position
, scrollLeft
,
+scrollTop
, offset
. The label
is used for test output.
+
+
element(selector, label).{method}(value)
+
+
Executes the method
passing in value
on the element matching the given jQuery selector
, where
+method
can be any of the following jQuery methods: val
, text
, html
, height
,
+innerHeight
, outerHeight
, width
, innerWidth
, outerWidth
, position
, scrollLeft
,
+scrollTop
, offset
. The label
is used for test output.
+
+
element(selector, label).{method}(key)
+
+
Returns the result of calling method
passing in key
on the element matching the given jQuery
+selector
, where method
can be any of the following jQuery methods: attr
, prop
, css
. The
+label
is used for test output.
+
+
element(selector, label).{method}(key, value)
+
+
Executes the method
passing in key
and value
on the element matching the given jQuery
+selector
, where method
can be any of the following jQuery methods: attr
, prop
, css
. The
+label
is used for test output.
+
+
JavaScript is a dynamically typed language which comes with great power of expression, but it also
+come with almost no-help from the compiler. For this reason we feel very strongly that any code
+written in JavaScript needs to come with a strong set of tests. We have built many features into
+angular which makes testing your angular applications easy. So there is no excuse for not testing.
diff --git a/lib/angular/docs/partials/guide/dev_guide.mvc.html b/lib/angular/docs/partials/guide/dev_guide.mvc.html
new file mode 100644
index 0000000..5e697b6
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.mvc.html
@@ -0,0 +1,25 @@
+
+
+
+While Model-View-Controller (MVC) has acquired different shades of meaning over the years since it
+first appeared, Angular incorporates the basic principles behind the original MVC software design pattern into its way of
+building client-side web applications.
+
+
The MVC pattern summarized:
+
+
+Separate applications into distinct presentation, data, and logic components
+Encourage loose coupling between these components
+
+
+
Along with services and dependency injection , MVC
+makes angular applications better structured, easier to maintain and more testable.
+
+
The following topics explain how angular incorporates the MVC pattern into the angular way of
+developing web applications:
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_controller.html b/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_controller.html
new file mode 100644
index 0000000..4582581
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_controller.html
@@ -0,0 +1,289 @@
+
+
+
+In Angular, a controller is a JavaScript function(type/class) that is used to augment instances of
+angular Scope , excluding the root scope. When you or Angular create a new
+child scope object via the scope.$new
API , there is an
+option to pass in a controller as a method argument. This will tell Angular to associate the
+controller with the new scope and to augment its behavior.
+
+
Use controllers to:
+
+
+Set up the initial state of a scope object.
+Add behavior to the scope object.
+
+
+
Setting up the initial state of a scope object
+
+
Typically, when you create an application you need to set up an initial state for an Angular scope.
+
+
Angular applies (in the sense of JavaScript's Function#apply
) the controller constructor function
+to a new Angular scope object, which sets up an initial scope state. This means that Angular never
+creates instances of the controller type (by invoking the new
operator on the controller
+constructor). Constructors are always applied to an existing scope object.
+
+
You set up the initial state of a scope by creating model properties. For example:
+
+
function GreetingCtrl($scope) {
+ $scope.greeting = 'Hola!';
+}
+
+
The GreetingCtrl
controller creates a greeting
model which can be referred to in a template.
+
+
NOTE : Many of the examples in the documentation show the creation of functions
+in the global scope. This is only for demonstration purposes - in a real
+application you should use the .controller
method of your Angular module for
+your application as follows:
+
+
var myApp = angular.module('myApp',[]);
+
+
myApp.controller('GreetingCtrl', ['$scope', function(scope) {
+ scope.greeting = 'Hola!';
+}]);
+
+
Note also that we use the array notation to explicitly specify the dependency
+of the controller on the $scope
service provided by Angular.
+
+
Adding Behavior to a Scope Object
+
+
Behavior on an Angular scope object is in the form of scope method properties available to the
+template/view. This behavior interacts with and modifies the application model.
+
+
As discussed in the Model section of this guide, any
+objects (or primitives) assigned to the scope become model properties. Any functions assigned to
+the scope are available in the template/view, and can be invoked via angular expressions
+and ng
event handler directives (e.g. ngClick
).
+
+
Using Controllers Correctly
+
+
In general, a controller shouldn't try to do too much. It should contain only the business logic
+needed for a single view.
+
+
The most common way to keep controllers slim is by encapsulating work that doesn't belong to
+controllers into services and then using these services in controllers via dependency injection.
+This is discussed in the Dependency Injection Services sections of this guide.
+
+
Do not use controllers for:
+
+
+Any kind of DOM manipulation — Controllers should contain only business logic. DOM
+manipulation—the presentation logic of an application—is well known for being hard to test.
+Putting any presentation logic into controllers significantly affects testability of the business
+logic. Angular offers databinding for automatic DOM manipulation. If
+you have to perform your own manual DOM manipulation, encapsulate the presentation logic in
+directives .
+Input formatting — Use angular form controls instead.
+Output filtering — Use angular filters instead.
+To run stateless or stateful code shared across controllers — Use angular services instead.
+To instantiate or manage the life-cycle of other components (for example, to create service
+instances).
+
+
+
Associating Controllers with Angular Scope Objects
+
+
You can associate controllers with scope objects explicitly via the scope.$new
api or implicitly via the ngController directive
or $route service
.
+
+
Controller Constructor and Methods Example
+
+
To illustrate how the controller component works in angular, let's create a little app with the
+following components:
+
+
+A template with two buttons and a simple message
+A model consisting of a string named spice
+A controller with two functions that set the value of spice
+
+
+
The message in our template contains a binding to the spice
model, which by default is set to the
+string "very". Depending on which button is clicked, the spice
model is set to chili
or
+jalapeño
, and the message is automatically updated by data-binding.
+
+
A Spicy Controller Example
+
+
+<body ng-controller="SpicyCtrl">
+ <button ng-click="chiliSpicy()">Chili</button>
+ <button ng-click="jalapenoSpicy()">Jalapeño</button>
+ <p>The food is {{spice}} spicy!</p>
+</body>
+
+function SpicyCtrl($scope) {
+ $scope.spice = 'very';
+ $scope.chiliSpicy = function() {
+ $scope.spice = 'chili';
+ }
+ $scope.jalapenoSpicy = function() {
+ $scope.spice = 'jalapeño';
+ }
+}
+
+
+
+
+
Things to notice in the example above:
+
+
+The ngController
directive is used to (implicitly) create a scope for our template, and the
+scope is augmented (managed) by the SpicyCtrl
controller.
+SpicyCtrl
is just a plain JavaScript function. As an (optional) naming convention the name
+starts with capital letter and ends with "Ctrl" or "Controller".
+Assigning a property to $scope
creates or updates the model.
+Controller methods can be created through direct assignment to scope (the chiliSpicy
method)
+Both controller methods are available in the template (for the body
element and and its
+children).
+NB: Previous versions of Angular (pre 1.0 RC) allowed you to use this
interchangeably with
+the $scope method, but this is no longer the case. Inside of methods defined on the scope
+this
and $scope are interchangeable (angular sets this
to $scope), but not otherwise
+inside your controller constructor.
+NB: Previous versions of Angular (pre 1.0 RC) added prototype methods into the scope
+automatically, but this is no longer the case; all methods need to be added manually to
+the scope.
+
+
+
Controller methods can also take arguments, as demonstrated in the following variation of the
+previous example.
+
+
Controller Method Arguments Example
+
+
+<body ng-controller="SpicyCtrl">
+ <input ng-model="customSpice" value="wasabi">
+ <button ng-click="spicy('chili')">Chili</button>
+ <button ng-click="spicy(customSpice)">Custom spice</button>
+ <p>The food is {{spice}} spicy!</p>
+</body>
+
+function SpicyCtrl($scope) {
+ $scope.spice = 'very';
+ $scope.spicy = function(spice) {
+ $scope.spice = spice;
+ }
+}
+
+
+
Notice that the SpicyCtrl
controller now defines just one method called spicy
, which takes one
+argument called spice
. The template then refers to this controller method and passes in a string
+constant 'chili'
in the binding for the first button and a model property spice
(bound to an
+input box) in the second button.
+
+
Controller Inheritance Example
+
+
Controller inheritance in Angular is based on Scope
inheritance. Let's
+have a look at an example:
+
+
+<body ng-controller="MainCtrl">
+ <p>Good {{timeOfDay}}, {{name}}!</p>
+ <div ng-controller="ChildCtrl">
+ <p>Good {{timeOfDay}}, {{name}}!</p>
+ <p ng-controller="BabyCtrl">Good {{timeOfDay}}, {{name}}!</p>
+</body>
+
+function MainCtrl($scope) {
+ $scope.timeOfDay = 'morning';
+ $scope.name = 'Nikki';
+}
+
+function ChildCtrl($scope) {
+ $scope.name = 'Mattie';
+}
+
+function BabyCtrl($scope) {
+ $scope.timeOfDay = 'evening';
+ $scope.name = 'Gingerbreak Baby';
+}
+
+
+
Notice how we nested three ngController
directives in our template. This template construct will
+result in 4 scopes being created for our view:
+
+
+The root scope
+The MainCtrl
scope, which contains timeOfDay
and name
models
+The ChildCtrl
scope, which shadows the name
model from the previous scope and inherits the
+timeOfDay
model
+The BabyCtrl
scope, which shadows both the timeOfDay
model defined in MainCtrl
and name
+model defined in the ChildCtrl
+
+
+
Inheritance works between controllers in the same way as it does with models. So in our previous
+examples, all of the models could be replaced with controller methods that return string values.
+
+
Note: Standard prototypical inheritance between two controllers doesn't work as one might expect,
+because as we mentioned earlier, controllers are not instantiated directly by Angular, but rather
+are applied to the scope object.
+
+
Testing Controllers
+
+
Although there are many ways to test a controller, one of the best conventions, shown below,
+involves injecting the $rootScope
and $controller
+
+
Controller Function:
+
+function myController($scope) {
+ $scope.spices = [{"name":"pasilla", "spiciness":"mild"},
+ {"name":"jalapeno", "spiceiness":"hot hot hot!"},
+ {"name":"habanero", "spiceness":"LAVA HOT!!"}];
+
+ $scope.spice = "habanero";
+}
+
+
+
Controller Test:
+
+describe('myController function', function() {
+
+ describe('myController', function() {
+ var scope;
+
+ beforeEach(inject(function($rootScope, $controller) {
+ scope = $rootScope.$new();
+ var ctrl = $controller(myController, {$scope: scope});
+ }));
+
+ it('should create "spices" model with 3 spices', function() {
+ expect(scope.spices.length).toBe(3);
+ });
+
+ it('should set the default value of spice', function() {
+ expect(scope.spice).toBe('habanero');
+ });
+ });
+});
+
+
+
If you need to test a nested controller you need to create the same scope hierarchy
+in your test that exists in the DOM.
+
+
+describe('state', function() {
+ var mainScope, childScope, babyScope;
+
+ beforeEach(inject(function($rootScope, $controller) {
+ mainScope = $rootScope.$new();
+ var mainCtrl = $controller(MainCtrl, {$scope: mainScope});
+ childScope = mainScope.$new();
+ var childCtrl = $controller(ChildCtrl, {$scope: childScope});
+ babyScope = childCtrl.$new();
+ var babyCtrl = $controller(BabyCtrl, {$scope: babyScope});
+ }));
+
+ it('should have over and selected', function() {
+ expect(mainScope.timeOfDay).toBe('morning');
+ expect(mainScope.name).toBe('Nikki');
+ expect(childScope.timeOfDay).toBe('morning');
+ expect(childScope.name).toBe('Mattie');
+ expect(babyScope.timeOfDay).toBe('evening');
+ expect(babyScope.name).toBe('Gingerbreak Baby');
+ });
+});
+
+
+
Related Topics
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_model.html b/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_model.html
new file mode 100644
index 0000000..644a804
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_model.html
@@ -0,0 +1,77 @@
+
+
+
+Depending on the context of the discussion in the Angular documentation, the term model can refer to
+either a single object representing one entity (for example, a model called "phones" with its value
+being an array of phones) or the entire data model for the application (all entities).
+
+
In Angular, a model is any data that is reachable as a property of an angular Scope object. The name of the property is the model identifier and the value is
+any JavaScript object (including arrays and primitives).
+
+
The only requirement for a JavaScript object to be a model in Angular is that the object must be
+referenced by an Angular scope as a property of that scope object. This property reference can be
+created explicitly or implicitly.
+
+
You can create models by explicitly creating scope properties referencing JavaScript objects in the
+following ways:
+
+
+Make a direct property assignment to the scope object in JavaScript code; this most commonly
+occurs in controllers:
+
+ function MyCtrl($scope) {
+ // create property 'foo' on the MyCtrl's scope
+ // and assign it an initial value 'bar'
+ $scope.foo = 'bar';
+ }
+
+Use an angular expression with an assignment operator in templates:
+
+ <button ng-click="{{foos='ball'}}">Click me</button>
+
+Use ngInit directive
in templates (for toy/example apps
+only, not recommended for real applications):
+
+ <body ng-init=" foo = 'bar' ">
+
+
+
+
Angular creates models implicitly (by creating a scope property and assigning it a suitable value)
+when processing the following template constructs:
+
+
+Form input, select, textarea and other form elements:
+
+ <input ng-model="query" value="fluffy cloud">
+
+
+The code above creates a model called "query" on the current scope with the value set to "fluffy
+cloud".
+An iterator declaration in ngRepeater
:
+
+ <p ng-repeat="phone in phones"></p>
+
+
+The code above creates one child scope for each item in the "phones" array and creates a "phone"
+object (model) on each of these scopes with its value set to the value of "phone" in the array.
+
+
+
In Angular, a JavaScript object stops being a model when:
+
+
+
+
The following illustration shows a simple data model created implicitly from a simple template:
+
+
+
+
Related Topics
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_view.html b/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_view.html
new file mode 100644
index 0000000..10c18b0
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.mvc.understanding_view.html
@@ -0,0 +1,21 @@
+
+
+
+In Angular, the view is the DOM loaded and rendered in the browser, after Angular has transformed
+the DOM based on information in the template, controller and model.
+
+
+
+
In the Angular implementation of MVC, the view has knowledge of both the model and the controller.
+The view knows about the model where two-way data-binding occurs. The view has knowledge of the
+controller through Angular directives, such as ngController
and ngView
, and through bindings of this form:
+{{someControllerFunction()}}
. In these ways, the view can call functions in an associated
+controller function.
+
+
Related Topics
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.$location.html b/lib/angular/docs/partials/guide/dev_guide.services.$location.html
new file mode 100644
index 0000000..bd3a8a5
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.$location.html
@@ -0,0 +1,652 @@
+
+
+
+What does it do?
+
+
The $location
service parses the URL in the browser address bar (based on the window.location ) and makes the URL available to
+your application. Changes to the URL in the address bar are reflected into $location service and
+changes to $location are reflected into the browser address bar.
+
+
The $location service:
+
+
+Exposes the current URL in the browser address bar, so you can
+Watch and observe the URL.
+Change the URL.
+Synchronizes the URL with the browser when the user
+Changes the address bar.
+Clicks the back or forward button (or clicks a History link).
+Clicks on a link.
+Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
+
+
+
Comparing $location to window.location
+
+
+
+
+
+
+ window.location
+ $location service
+
+
+
+
+
+
+ purpose
+ allow read/write access to the current browser location
+ same
+
+
+
+ API
+ exposes "raw" object with properties that can be directly modified
+ exposes jQuery-style getters and setters
+
+
+
+ integration with angular application life-cycle
+ none
+ knows about all internal life-cycle phases, integrates with $watch, ...
+
+
+
+ seamless integration with HTML5 API
+ no
+ yes (with a fallback for legacy browsers)
+
+
+
+ aware of docroot/context from which the application is loaded
+ no - window.location.path returns "/docroot/actual/path"
+ yes - $location.path() returns "/actual/path"
+
+
+
+
+
+
When should I use $location?
+
+
Any time your application needs to react to a change in the current URL or if you want to change
+the current URL in the browser.
+
+
What does it not do?
+
+
It does not cause a full page reload when the browser URL is changed. To reload the page after
+changing the URL, use the lower-level API, $window.location.href
.
+
+
General overview of the API
+
+
The $location
service can behave differently, depending on the configuration that was provided to
+it when it was instantiated. The default configuration is suitable for many applications, for
+others customizing the configuration can enable new features.
+
+
Once the $location
service is instantiated, you can interact with it via jQuery-style getter and
+setter methods that allow you to get or change the current URL in the browser.
+
+
$location service configuration
+
+
To configure the $location
service, retrieve the
+$locationProvider
and set the parameters as follows:
+
+
+html5Mode(mode) : {boolean}
+true
- see HTML5 mode
+false
- see Hashbang mode
+default: false
+hashPrefix(prefix) : {string}
+prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)
+default: '!'
+
+
+
Example configuration
+
+
+$locationProvider.html5Mode(true).hashPrefix('!');
+
+
+
Getter and setter methods
+
+
$location
service provides getter methods for read-only parts of the URL (absUrl, protocol, host,
+port) and getter / setter methods for url, path, search, hash:
+
+// get the current path
+$location.path();
+
+// change the path
+$location.path('/newValue')
+
+
+
All of the setter methods return the same $location
object to allow chaining. For example, to
+change multiple segments in one go, chain setters like this:
+
$location.path('/newValue').search({key: value});
+
+
There is a special replace
method which can be used to tell the $location service that the next
+time the $location service is synced with the browser, the last history record should be replaced
+instead of creating a new one. This is useful when you want to implement redirection, which would
+otherwise break the back button (navigating back would retrigger the redirection). To change the
+current URL without creating a new browser history record you can call:
+
+ $location.path('/someNewPath');
+ $location.replace();
+ // or you can chain these as: $location.path('/someNewPath').replace();
+
+
+
Note that the setters don't update window.location
immediately. Instead, the $location
service is
+aware of the scope
life-cycle and coalesces multiple $location
+mutations into one "commit" to the window.location
object during the scope $digest
phase. Since
+multiple changes to the $location's state will be pushed to the browser as a single change, it's
+enough to call the replace()
method just once to make the entire "commit" a replace operation
+rather than an addition to the browser history. Once the browser is updated, the $location service
+resets the flag set by replace()
method and future mutations will create new history records,
+unless replace()
is called again.
+
+
Setters and character encoding
+
+
You can pass special characters to $location
service and it will encode them according to rules
+specified in RFC 3986 . When you access the methods:
+
+
+All values that are passed to $location
setter methods, path()
, search()
, hash()
, are
+encoded.
+Getters (calls to methods without parameters) return decoded values for the following methods
+path()
, search()
, hash()
.
+When you call the absUrl()
method, the returned value is a full url with its segments encoded.
+When you call the url()
method, the returned value is path, search and hash, in the form
+/path?search=a&b=c#hash
. The segments are encoded as well.
+
+
+
Hashbang and HTML5 Modes
+
+
$location
service has two configuration modes which control the format of the URL in the browser
+address bar: Hashbang mode (the default) and the HTML5 mode which is based on using the
+HTML5 History API . Applications use the same API in
+both modes and the $location
service will work with appropriate URL segments and browser APIs to
+facilitate the browser URL change and history management.
+
+
+
+
+
+
+
+
+ Hashbang mode
+ HTML5 mode
+
+
+
+
+
+
+ configuration
+ the default
+ { html5Mode: true }
+
+
+
+ URL format
+ hashbang URLs in all browsers
+ regular URLs in modern browser, hashbang URLs in old browser
+
+
+
+ <a href=""> link rewriting
+ no
+ yes
+
+
+
+ requires server-side configuration
+ no
+ yes
+
+
+
+
+
Hashbang mode (default mode)
+
+
In this mode, $location
uses Hashbang URLs in all browsers.
+
+
Example
+
+
+it('should show example', inject(
+ function($locationProvider) {
+ $locationProvider.html5Mode(false);
+ $locationProvider.hashPrefix = '!';
+ },
+ function($location) {
+ // open http://host.com/base/index.html#!/a
+ $location.absUrl() == 'http://host.com/base/index.html#!/a'
+ $location.path() == '/a'
+
+ $location.path('/foo')
+ $location.absUrl() == 'http://host.com/base/index.html#!/foo'
+
+ $location.search() == {}
+ $location.search({a: 'b', c: true});
+ $location.absUrl() == 'http://host.com/base/index.html#!/foo?a=b&c'
+
+ $location.path('/new').search('x=y');
+ $location.absUrl() == 'http://host.com/base/index.html#!/new?x=y'
+ }
+));
+
+
+
Crawling your app
+
+
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
+your document:
+
<meta name="fragment" content="!" />
+
+
This will cause crawler bot to request links with _escaped_fragment_
param so that your server
+can recognize the crawler and serve a HTML snapshots. For more information about this technique,
+see Making AJAX Applications Crawlable .
+
+
HTML5 mode
+
+
In HTML5 mode, the $location
service getters and setters interact with the browser URL address
+through the HTML5 history API, which allows for use of regular URL path and search segments,
+instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the
+$location
service will fall back to using the hashbang URLs automatically. This frees you from
+having to worry about whether the browser displaying your app supports the history API or not; the
+$location
service transparently uses the best available option.
+
+
+Opening a regular URL in a legacy browser -> redirects to a hashbang URL
+Opening hashbang URL in a modern browser -> rewrites to a regular URL
+
+
+
Example
+
+
+it('should show example', inject(
+ function($locationProvider) {
+ $locationProvider.html5Mode(true);
+ $locationProvider.hashPrefix = '!';
+ },
+ function($location) {
+ // in browser with HTML5 history support:
+ // open http://host.com/#!/a -> rewrite to http://host.com/a
+ // (replacing the http://host.com/#!/a history record)
+ $location.path() == '/a'
+
+ $location.path('/foo');
+ $location.absUrl() == 'http://host.com/foo'
+
+ $location.search() == {}
+ $location.search({a: 'b', c: true});
+ $location.absUrl() == 'http://host.com/foo?a=b&c'
+
+ $location.path('/new').search('x=y');
+ $location.url() == 'new?x=y'
+ $location.absUrl() == 'http://host.com/new?x=y'
+
+ // in browser without html5 history support:
+ // open http://host.com/new?x=y -> redirect to http://host.com/#!/new?x=y
+ // (again replacing the http://host.com/new?x=y history item)
+ $location.path() == '/new'
+ $location.search() == {x: 'y'}
+
+ $location.path('/foo/bar');
+ $location.path() == '/foo/bar'
+ $location.url() == '/foo/bar?x=y'
+ $location.absUrl() == 'http://host.com/#!/foo/bar?x=y'
+ }
+));
+
+
+
Fallback for legacy browsers
+
+
For browsers that support the HTML5 history API, $location
uses the HTML5 history API to write
+path and search. If the history API is not supported by a browser, $location
supplies a Hasbang
+URL. This frees you from having to worry about whether the browser viewing your app supports the
+history API or not; the $location
service makes this transparent to you.
+
+
Html link rewriting
+
+
When you use HTML5 history API mode, you will need different links in different browsers, but all you
+have to do is specify regular URL links, such as: <a href="/some?foo=bar">link</a>
+
+
When a user clicks on this link,
+
+
+In a legacy browser, the URL changes to /index.html#!/some?foo=bar
+In a modern browser, the URL changes to /some?foo=bar
+
+
+
In cases like the following, links are not rewritten; instead, the browser will perform a full page
+reload to the original link.
+
+
+Links that contain target
element
+Example: <a href="/ext/link?a=b" target="_self">link</a>
+Absolute links that go to a different domain
+Example: <a href="http://angularjs.org/">link</a>
+Links starting with '/' that lead to a different base path when base is defined
+Example: <a href="/not-my-base/link">link</a>
+
+
+
Server side
+
+
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
+to entry point of your application (e.g. index.html)
+
+
Crawling your app
+
+
If you want your AJAX application to be indexed by web crawlers, you will need to add the following
+meta tag to the HEAD section of your document:
+
<meta name="fragment" content="!" />
+
+
This statement causes a crawler to request links with an empty _escaped_fragment_
parameter so that
+your server can recognize the crawler and serve it HTML snapshots. For more information about this
+technique, see Making AJAX Applications Crawlable .
+
+
Relative links
+
+
Be sure to check all relative links, images, scripts etc. You must either specify the url base in
+the head of your main html file (<base href="/my-base">
) or you must use absolute urls
+(starting with /
) everywhere because relative urls will be resolved to absolute urls using the
+initial absolute url of the document, which is often different from the root of the application.
+
+
Running Angular apps with the History API enabled from document root is strongly encouraged as it
+takes care of all relative link issues.
+
+
Sending links among different browsers
+
+
Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in
+legacy browsers and hashbang links in modern browser:
+
+
+Modern browser will rewrite hashbang URLs to regular URLs.
+Older browsers will redirect regular URLs to hashbang URLs.
+
+
+
Example
+
+
Here you can see two $location
instances, both in Html5 mode , but on different browsers, so
+that you can see the differences. These $location
services are connected to a fake browsers. Each
+input represents address bar of the browser.
+
+
Note that when you type hashbang url into first browser (or vice versa) it doesn't rewrite /
+redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL
+= on page reload.
+
+
In this examples we use <base href="/base/index.html" />
+
Source
+
+
Demo
+
+
+
Caveats
+
+
Page reload navigation
+
+
The $location
service allows you to change only the URL; it does not allow you to reload the
+page. When you need to change the URL and reload the page or navigate to a different page, please
+use a lower level API, $window.location.href
.
+
+
Using $location outside of the scope life-cycle
+
+
$location
knows about Angular's scope
life-cycle. When a URL changes in
+the browser it updates the $location
and calls $apply
so that all $watchers / $observers are
+notified.
+When you change the $location
inside the $digest
phase everything is ok; $location
will
+propagate this change into browser and will notify all the $watchers / $observers.
+When you want to change the $location
from outside Angular (for example, through a DOM Event or
+during testing) - you must call $apply
to propagate the changes.
+
+
$location.path() and ! or / prefixes
+
+
A path should always begin with forward slash (/
); the $location.path()
setter will add the
+forward slash if it is missing.
+
+
Note that the !
prefix in the hashbang mode is not part of $location.path()
; it is actually
+hashPrefix.
+
+
Testing with the $location service
+
+
When using $location
service during testing, you are outside of the angular's scope
life-cycle. This means it's your responsibility to call scope.$apply()
.
+
+
+describe('serviceUnderTest', function() {
+ beforeEach(module(function($provide) {
+ $provide.factory('serviceUnderTest', function($location){
+ // whatever it does...
+ });
+ });
+
+ it('should...', inject(function($location, $rootScope, serviceUnderTest) {
+ $location.path('/new/path');
+ $rootScope.$apply();
+
+ // test whatever the service should do...
+
+ }));
+});
+
+
+
Migrating from earlier AngularJS releases
+
+
In earlier releases of Angular, $location
used hashPath
or hashSearch
to process path and
+search methods. With this release, the $location
service processes path and search methods and
+then uses the information it obtains to compose hashbang URLs (such as
+http://server.com/#!/path?search=a
), when necessary.
+
+
Changes to your code
+
+
+
+
+ Navigation inside the app
+ Change to
+
+
+
+
+
+ $location.href = value $location.hash = value $location.update(value) $location.updateHash(value)
+ $location.path(path).search(search)
+
+
+
+ $location.hashPath = path
+ $location.path(path)
+
+
+
+ $location.hashSearch = search
+ $location.search(search)
+
+
+
+ Navigation outside the app
+ Use lower level API
+
+
+
+ $location.href = value $location.update(value)
+ $window.location.href = value
+
+
+
+ $location[protocol | host | port | path | search]
+ $window.location[protocol | host | port | path | search]
+
+
+
+ Read access
+ Change to
+
+
+
+ $location.hashPath
+ $location.path()
+
+
+
+ $location.hashSearch
+ $location.search()
+
+
+
+ $location.href $location.protocol $location.host $location.port $location.hash
+ $location.absUrl() $location.protocol() $location.host() $location.port() $location.path() + $location.search()
+
+
+
+ $location.path $location.search
+ $window.location.path $window.location.search
+
+
+
+
+
Two-way binding to $location
+
+
The Angular's compiler currently does not support two-way binding for methods (see issue ). If you should require two-way binding
+to the $location object (using ngModel
directive on an input field), you will need to specify an extra model property
+(e.g. locationPath
) with two watchers which push $location updates in both directions. For
+example:
+
+<!-- html -->
+<input type="text" ng-model="locationPath" />
+
+
+// js - controller
+$scope.$watch('locationPath', function(path) {
+ $location.path(path);
+});
+
+$scope.$watch('$location.path()', function(path) {
+ scope.locationPath = path;
+});
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.creating_services.html b/lib/angular/docs/partials/guide/dev_guide.services.creating_services.html
new file mode 100644
index 0000000..9bd66f6
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.creating_services.html
@@ -0,0 +1,101 @@
+
+
+
+While Angular offers several useful services, for any nontrivial application you'll find it useful
+to write your own custom services. To do this you begin by registering a service factory function
+with a module either via the Module#factory api
or directly
+via the $provide
api inside of module config function.
+
+
All Angular services participate in dependency injection (DI) by registering
+themselves with Angular's DI system (injector) under a name
(id) as well as by declaring
+dependencies which need to be provided for the factory function of the registered service. The
+ability to swap dependencies for mocks/stubs/dummies in tests allows for services to be highly
+testable.
+
+
Registering Services
+
+
To register a service, you must have a module that this service will be part of. Afterwards, you
+can register the service with the module either via the Module api
or
+by using the $provide
service in the module configuration
+function.The following pseudo-code shows both approaches:
+
+
Using the angular.Module api:
+
+var myModule = angular.module('myModule', []);
+myModule.factory('serviceId', function() {
+ var shinyNewServiceInstance;
+ //factory function body that constructs shinyNewServiceInstance
+ return shinyNewServiceInstance;
+});
+
+
+
Using the $provide service:
+
+angular.module('myModule', [], function($provide) {
+ $provide.factory('serviceId', function() {
+ var shinyNewServiceInstance;
+ //factory function body that constructs shinyNewServiceInstance
+ return shinyNewServiceInstance;
+ });
+});
+
+
+
Note that you are not registering a service instance, but rather a factory function that will
+create this instance when called.
+
+
Dependencies
+
+
Services can not only be depended upon, but can also have their own dependencies. These can be specified
+as arguments of the factory function. Read more about dependency injection (DI)
+in Angular and the use of array notation and the $inject property to make DI annotation
+minification-proof.
+
+
Following is an example of a very simple service. This service depends on the $window
service
+(which is passed as a parameter to the factory function) and is just a function. The service simply
+stores all notifications; after the third one, the service displays all of the notifications by
+window alert.
+
+
+angular.module('myModule', [], function($provide) {
+ $provide.factory('notify', ['$window', function(win) {
+ var msgs = [];
+ return function(msg) {
+ msgs.push(msg);
+ if (msgs.length == 3) {
+ win.alert(msgs.join("\n"));
+ msgs = [];
+ }
+ };
+ }]);
+});
+
+
+
Instantiating Angular Services
+
+
All services in Angular are instantiated lazily. This means that a service will be created
+only when it is needed for instantiation of a service or an application component that depends on it.
+In other words, Angular won't instantiate services unless they are requested directly or
+indirectly by the application.
+
+
Services as singletons
+
+
Lastly, it is important to realize that all Angular services are application singletons. This means
+that there is only one instance of a given service per injector. Since Angular is lethally allergic
+to global state, it is possible to create multiple injectors, each with its own instance of a
+given service, but that is rarely needed, except in tests where this property is crucially
+important.
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.html b/lib/angular/docs/partials/guide/dev_guide.services.html
new file mode 100644
index 0000000..dd1f175
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.html
@@ -0,0 +1,22 @@
+
+
+
+Services are a feature that Angular brings to client-side web apps from the server side, where
+services have been commonly used for a long time. Services in Angular apps are substitutable
+objects that are wired together using dependency injection (DI) .
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.injecting_controllers.html b/lib/angular/docs/partials/guide/dev_guide.services.injecting_controllers.html
new file mode 100644
index 0000000..414e378
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.injecting_controllers.html
@@ -0,0 +1,138 @@
+
+
+
+Using services as dependencies for controllers is very similar to using services as dependencies
+for another service.
+
+
Since JavaScript is a dynamic language, DI can't figure out which services to inject by static
+types (like in static typed languages). Therefore, you can specify the service name by using the
+$inject
property, which is an array containing strings with names of services to be injected.
+The name must match the corresponding service ID registered with angular. The order of the service
+IDs matters: the order of the services in the array will be used when calling the factory function
+with injected parameters. The names of parameters in factory function don't matter, but by
+convention they match the service IDs, which has added benefits discussed below.
+
+
+function myController($loc, $log) {
+this.firstMethod = function() {
+ // use $location service
+ $loc.setHash();
+};
+this.secondMethod = function() {
+ // use $log service
+ $log.info('...');
+};
+}
+// which services to inject ?
+myController.$inject = ['$location', '$log'];
+
+
+
Source
+
+
Demo
+
+
+
Implicit Dependency Injection
+
+
A new feature of Angular DI allows it to determine the dependency from the name of the parameter.
+Let's rewrite the above example to show the use of this implicit dependency injection of
+$window
, $scope
, and our notify
service:
+
+
Source
+
+
Demo
+
+
+
However, if you plan to minify your
+code, your variable names will get renamed in which case you will still need to explicitly specify
+dependencies with the $inject
property.
+
+
Related Topics
+
+
Understanding Angular Services
+Creating Angular Services
+Managing Service Dependencies
+Testing Angular Services
+
+
Related API
+
+
Angular Service API
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.managing_dependencies.html b/lib/angular/docs/partials/guide/dev_guide.services.managing_dependencies.html
new file mode 100644
index 0000000..a263486
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.managing_dependencies.html
@@ -0,0 +1,113 @@
+
+
+
+Angular allows services to declare other services as dependencies needed for construction of their
+instances.
+
+
To declare dependencies, you specify them in the factory function signature and annotate the
+function with the inject annotations either using by setting the $inject
property, as an array of
+string identifiers or using the array notation. Optionally the $inject
property declaration can be
+dropped (see "Inferring $inject
" but note that that is currently an experimental feature).
+
+
Using the array notation:
+
+
+function myModuleCfgFn($provide) {
+ $provide.factory('myService', ['dep1', 'dep2', function(dep1, dep2) {}]);
+}
+
+
+
Using the $inject property:
+
+
+function myModuleCfgFn($provide) {
+ var myServiceFactory = function(dep1, dep2) {};
+ myServiceFactory.$inject = ['dep1', 'dep2'];
+ $provide.factory('myService', myServiceFactory);
+}
+
+
+
Using DI inference (incompatible with minifiers):
+
+
+function myModuleCfgFn($provide) {
+ $provide.factory('myService', function(dep1, dep2) {});
+}
+
+
+
Here is an example of two services, one of which depends on the other and both
+of which depend on other services that are provided by the Angular framework:
+
+
+/**
+ * batchLog service allows for messages to be queued in memory and flushed
+ * to the console.log every 50 seconds.
+ *
+ * @param {*} message Message to be logged.
+ */
+ function batchLogModule($provide){
+ $provide.factory('batchLog', ['$timeout', '$log', function($timeout, $log) {
+ var messageQueue = [];
+
+ function log() {
+ if (messageQueue.length) {
+ $log('batchLog messages: ', messageQueue);
+ messageQueue = [];
+ }
+ $timeout(log, 50000);
+ }
+
+ // start periodic checking
+ log();
+
+ return function(message) {
+ messageQueue.push(message);
+ }
+ }]);
+
+ /**
+ * routeTemplateMonitor monitors each $route change and logs the current
+ * template via the batchLog service.
+ */
+ $provide.factory('routeTemplateMonitor',
+ ['$route', 'batchLog', '$rootScope',
+ function($route, batchLog, $rootScope) {
+ $rootScope.$on('$routeChangeSuccess', function() {
+ batchLog($route.current ? $route.current.template : null);
+ });
+ }]);
+ }
+
+ // get the main service to kick of the application
+ angular.injector([batchLogModule]).get('routeTemplateMonitor');
+
+
+
Things to notice in this example:
+
+
+The batchLog
service depends on the built-in $timeout
and
+$log
services, and allows messages to be logged into the
+console.log
in batches.
+The routeTemplateMonitor
service depends on the built-in $route
service as well as our custom batchLog
service.
+Both of our services use the factory function signature and array notation for inject annotations
+to declare their dependencies. It is important that the order of the string identifiers in the array
+is the same as the order of argument names in the signature of the factory function. Unless the
+dependencies are inferred from the function signature, it is this array with IDs and their order
+that the injector uses to determine which services and in which order to inject.
+
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.testing_services.html b/lib/angular/docs/partials/guide/dev_guide.services.testing_services.html
new file mode 100644
index 0000000..c0c6b72
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.testing_services.html
@@ -0,0 +1,63 @@
+
+
+
+The following is a unit test for the 'notify' service in the 'Dependencies' example in Creating Angular Services . The unit test example uses Jasmine
+spy (mock) instead of a real browser alert.
+
+
+var mock, notify;
+
+beforeEach(function() {
+ mock = {alert: jasmine.createSpy()};
+
+ module(function($provide) {
+ $provide.value('$window', mock);
+ });
+
+ inject(function($injector) {
+ notify = $injector.get('notify');
+ });
+});
+
+it('should not alert first two notifications', function() {
+ notify('one');
+ notify('two');
+
+ expect(mock.alert).not.toHaveBeenCalled();
+});
+
+it('should alert all after third notification', function() {
+ notify('one');
+ notify('two');
+ notify('three');
+
+ expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree");
+});
+
+it('should clear messages after alert', function() {
+ notify('one');
+ notify('two');
+ notify('third');
+ notify('more');
+ notify('two');
+ notify('third');
+
+ expect(mock.alert.callCount).toEqual(2);
+ expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]);
+});
+
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.services.understanding_services.html b/lib/angular/docs/partials/guide/dev_guide.services.understanding_services.html
new file mode 100644
index 0000000..400c53a
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.services.understanding_services.html
@@ -0,0 +1,39 @@
+
+
+
+Angular services are singletons that carry out specific tasks common to web apps, such as the
+$http service
that provides low level access to the browser's
+XMLHttpRequest
object.
+
+
To use an Angular service, you identify it as a dependency for the dependent (a controller, or
+another service) that depends on the service. Angular's dependency injection subsystem takes care
+of the rest. The Angular injector subsystem is in charge of service instantiation, resolution of
+dependencies, and provision of dependencies to factory functions as requested.
+
+
Angular injects dependencies using "constructor" injection (the service is passed in via a factory
+function). Because JavaScript is a dynamically typed language, Angular's dependency injection
+subsystem cannot use static types to identify service dependencies. For this reason a dependent
+must explicitly define its dependencies by using the $inject
property. For example:
+
+
myController.$inject = ['$location'];
+
+
+
The Angular web framework provides a set of services for common operations. Like other core Angular
+variables and identifiers, the built-in services always start with $
(such as $http
mentioned
+above). You can also create your own custom services.
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.templates.css-styling.html b/lib/angular/docs/partials/guide/dev_guide.templates.css-styling.html
new file mode 100644
index 0000000..5f77d9a
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.templates.css-styling.html
@@ -0,0 +1,25 @@
+
+
+
+Angular sets these CSS classes. It is up to your application to provide useful styling.
+
+
CSS classes used by angular
+
+
+ng-invalid
, ng-valid
+
+Usage: angular applies this class to an input widget element if that element's input does
+not pass validation. (see input
directive).
+ng-pristine
, ng-dirty
+
+Usage: angular input
directive applies ng-pristine
class
+to a new input widget element which did not have user interaction. Once the user interacts with
+the input widget the class is changed to ng-dirty
.
+
+
+
Related Topics
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.templates.databinding.html b/lib/angular/docs/partials/guide/dev_guide.templates.databinding.html
new file mode 100644
index 0000000..6f6a633
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.templates.databinding.html
@@ -0,0 +1,38 @@
+
+
+
+Data-binding in Angular web apps is the automatic syncronization of data between the model and view
+components. The way that Angular implements data-binding lets you treat the model as the
+single-source-of-truth in your application. The view is a projection of the model at all times.
+When the model changes, the view reflects the change, and vice versa.
+
+
Data Binding in Classical Template Systems
+
+
+Most templating systems bind data in only one direction: they merge template and model components
+together into a view, as illustrated in the diagram. After the merge occurs, changes to the model
+or related sections of the view are NOT automatically reflected in the view. Worse, any changes
+that the user makes to the view are not reflected in the model. This means that the developer has
+to write code that constantly syncs the view with the model and the model with the view.
+
+
Data Binding in Angular Templates
+
+
+The way Angular templates works is different, as illustrated in the diagram. They are different
+because first the template (which is the uncompiled HTML along with any additional markup or
+directives) is compiled on the browser, and second, the compilation step produces a live view. We
+say live because any changes to the view are immediately reflected in the model, and any changes in
+the model are propagated to the view. This makes the model always the single-source-of-truth for
+the application state, greatly simplifying the programming model for the developer. You can think of
+the view as simply an instant projection of your model.
+
+
Because the view is just a projection of the model, the controller is completely separated from the
+view and unaware of it. This makes testing a snap because it is easy to test your controller in
+isolation without the view and the related DOM/browser dependency.
+
+
Related Topics
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.templates.filters.creating_filters.html b/lib/angular/docs/partials/guide/dev_guide.templates.filters.creating_filters.html
new file mode 100644
index 0000000..a5759a5
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.templates.filters.creating_filters.html
@@ -0,0 +1,74 @@
+
+
+
+Writing your own filter is very easy: just register a new filter (injectable) factory function with
+your module. This factory function should return a new filter function which takes the input value
+as the first argument. Any filter arguments are passed in as additional arguments to the filter
+function.
+
+
The following sample filter reverses a text string. In addition, it conditionally makes the
+text upper-case and assigns color.
+
+
Source
+
+
Demo
+
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.templates.filters.html b/lib/angular/docs/partials/guide/dev_guide.templates.filters.html
new file mode 100644
index 0000000..87d292d
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.templates.filters.html
@@ -0,0 +1,28 @@
+
+
+
+Angular filters format data for display to the user. In addition to formatting data, filters can
+also modify the DOM. This allows filters to handle tasks such as conditionally applying CSS styles
+to filtered output.
+
+
For example, you might have a data object that needs to be formatted according to the locale before
+displaying it to the user. You can pass expressions through a chain of filters like this:
+
+
name | uppercase
+
+
+
The expression evaluator simply passes the value of name to
+uppercase filter
.
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.templates.filters.using_filters.html b/lib/angular/docs/partials/guide/dev_guide.templates.filters.using_filters.html
new file mode 100644
index 0000000..f953faf
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.templates.filters.using_filters.html
@@ -0,0 +1,47 @@
+
+
+
+Filters can be part of any api/ng.$rootScope.Scope
evaluation but are typically used to format
+expressions in bindings in your templates:
+
+
{{ expression | filter }}
+
+
+
Filters typically transform the data to a new data type, formatting the data in the process.
+Filters can also be chained, and can take optional arguments.
+
+
You can chain filters using this syntax:
+
+
{{ expression | filter1 | filter2 }}
+
+
+
You can also pass colon-delimited arguments to filters, for example, to display the number 123 with
+2 decimal points:
+
+
123 | number:2
+
+
+
Here are some examples that show values before and after applying different filters to an
+expression in a binding:
+
+
+No filter: {{1234.5678}}
=> 1234.5678
+Number filter: {{1234.5678|number}}
=> 1,234.57
. Notice the "," and rounding to two
+significant digits.
+Filter with arguments: {{1234.5678|number:5}}
=> 1,234.56780
. Filters can take optional
+arguments, separated by colons in a binding. For example, the "number" filter takes a number
+argument that specifies how many digits to display to the right of the decimal point.
+
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.templates.html b/lib/angular/docs/partials/guide/dev_guide.templates.html
new file mode 100644
index 0000000..0060615
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.templates.html
@@ -0,0 +1,60 @@
+
+
+
+An Angular template is the declarative specification that, along with information from the model
+and controller, becomes the rendered view that a user sees in the browser. It is the static DOM,
+containing HTML, CSS, and angular-specific elements and angular-specific element attributes. The
+Angular elements and attributes direct angular to add behavior and transform the template DOM into
+the dynamic view DOM.
+
+
These are the types of Angular elements and element attributes you can use in a template:
+
+
+Directive — An attribute or element that
+augments an existing DOM element or represents a reusable DOM component - a widget.
+Markup
— The double
+curly brace notation {{ }}
to bind expressions to elements is built-in angular markup.
+Filter — Formats your data for display to the user.
+Form controls — Lets you validate user input.
+
+
+
Note: In addition to declaring the elements above in templates, you can also access these elements
+in JavaScript code.
+
+
The following code snippet shows a simple Angular template made up of standard HTML tags along with
+Angular directives and curly-brace bindings
+with expressions :
+
+
+<html ng-app>
+ <!-- Body tag augmented with ngController directive -->
+ <body ng-controller="MyController">
+ <input ng-model="foo" value="bar">
+ <!-- Button tag with ng-click directive, and
+ string expression 'buttonText'
+ wrapped in "{{ }}" markup -->
+ <button ng-click="changeFoo()">{{buttonText}}</button>
+ <script src="angular.js">
+ </body>
+</html>
+
+
+
In a simple single-page app, the template consists of HTML, CSS, and angular directives contained
+in just one HTML file (usually index.html
). In a more complex app, you can display multiple views
+within one main page using "partials", which are segments of template located in separate HTML
+files. You "include" the partials in the main page using the $route
service in conjunction with the ngView
directive. An
+example of this technique is shown in the angular tutorial , in steps seven and
+eight.
+
+
Related Topics
+
+
+
+
Related API
+
+
diff --git a/lib/angular/docs/partials/guide/dev_guide.unit-testing.html b/lib/angular/docs/partials/guide/dev_guide.unit-testing.html
new file mode 100644
index 0000000..368183d
--- /dev/null
+++ b/lib/angular/docs/partials/guide/dev_guide.unit-testing.html
@@ -0,0 +1,302 @@
+
+
+
+JavaScript is a dynamically typed language which comes with great power of expression, but it also
+come with almost no-help from the compiler. For this reason we feel very strongly that any code
+written in JavaScript needs to come with a strong set of tests. We have built many features into
+Angular which makes testing your Angular applications easy. So there is no excuse for not testing.
+
+
It is all about NOT mixing concerns
+
+
Unit testing as the name implies is about testing individual units of code. Unit tests try to
+answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list
+in the right order?"
+
+
In order to answer such question it is very important that we can isolate the unit of code under test.
+That is because when we are testing the sort function we don't want to be forced into creating
+related pieces such as the DOM elements, or making any XHR calls in getting the data to sort.
+
+
While
+this may seem obvious it usually is very difficult to be able to call an individual function on a
+typical project. The reason is that the developers often mix concerns, and they end up with a
+piece of code which does everything. It reads the data from XHR, it sorts it and then it
+manipulates the DOM.
+
+
With Angular we try to make it easy for you to do the right thing, and so we
+provide dependency injection for your XHR (which you can mock out) and we created abstraction which
+allow you to sort your model without having to resort to manipulating the DOM. So that in the end,
+it is easy to write a sort function which sorts some data, so that your test can create a data set,
+apply the function, and assert that the resulting model is in the correct order. The test does not
+have to wait for XHR, or create the right kind of DOM, or assert that your function has mutated the
+DOM in the right way.
+
+
With great power comes great responsibility
+
+
Angular is written with testability in mind, but it still requires that you
+do the right thing. We tried to make the right thing easy, but Angular is not magic, which means if
+you don't follow these guidelines you may very well end up with an untestable application.
+
+
Dependency Injection
+
+
There are several ways in which you can get a hold of a dependency:
+1. You could create it using the new
operator.
+2. You could look for it in a well known place, also known as global singleton.
+3. You could ask a registry (also known as service registry) for it. (But how do you get a hold of
+the registry? Most likely by looking it up in a well known place. See #2)
+4. You could expect that it be handed to you.
+
+
Out of the four options in the list above, only the last one is testable. Let's look at why:
+
+
Using the new
operator
+
+
While there is nothing wrong with the new
operator fundamentally the issue is that calling a new
+on a constructor permanently binds the call site to the type. For example lets say that we are
+trying to instantiate an XHR
so that we can get some data from the server.
+
+
+function MyClass() {
+ this.doWork = function() {
+ var xhr = new XHR();
+ xhr.open(method, url, true);
+ xhr.onreadystatechange = function() {...}
+ xhr.send();
+ }
+}
+
+
+
The issue becomes that in tests, we would very much like to instantiate a MockXHR
which would
+allow us to return fake data and simulate network failures. By calling new XHR()
we are
+permanently bound to the actual XHR, and there is no good way to replace it. Yes there is monkey
+patching. That is a bad idea for many reasons which are outside the scope of this document.
+
+
The class above is hard to test since we have to resort to monkey patching:
+
+var oldXHR = XHR;
+XHR = function MockXHR() {};
+var myClass = new MyClass();
+myClass.doWork();
+// assert that MockXHR got called with the right arguments
+XHR = oldXHR; // if you forget this bad things will happen
+
+
+
Global look-up:
+
+
Another way to approach the problem is to look for the service in a well known location.
+
+
+function MyClass() {
+ this.doWork = function() {
+ global.xhr({
+ method:'...',
+ url:'...',
+ complete:function(response){ ... }
+ })
+ }
+}
+
+
+
While no new instance of the dependency is being created, it is fundamentally the same as new
, in
+that there is no good way to intercept the call to global.xhr
for testing purposes, other then
+through monkey patching. The basic issue for testing is that global variable needs to be mutated in
+order to replace it with call to a mock method. For further explanation why this is bad see: Brittle Global State & Singletons
+
+
The class above is hard to test since we have to change global state:
+
+var oldXHR = global.xhr;
+global.xhr = function mockXHR() {};
+var myClass = new MyClass();
+myClass.doWork();
+// assert that mockXHR got called with the right arguments
+global.xhr = oldXHR; // if you forget this bad things will happen
+
+
+
Service Registry:
+
+
It may seem as that this can be solved by having a registry for all of the services, and then
+having the tests replace the services as needed.
+
+
+function MyClass() {
+ var serviceRegistry = ????;
+ this.doWork = function() {
+ var xhr = serviceRegistry.get('xhr');
+ xhr({
+ method:'...',
+ url:'...',
+ complete:function(response){ ... }
+ })
+}
+
+
+
However, where does the serviceRegistry come from? if it is:
+* new
-ed up, the the test has no chance to reset the services for testing
+* global look-up, then the service returned is global as well (but resetting is easier, since
+there is only one global variable to be reset).
+
+
The class above is hard to test since we have to change global state:
+
+var oldServiceLocator = global.serviceLocator;
+global.serviceLocator.set('xhr', function mockXHR() {});
+var myClass = new MyClass();
+myClass.doWork();
+// assert that mockXHR got called with the right arguments
+global.serviceLocator = oldServiceLocator; // if you forget this bad things will happen
+
+
+
Passing in Dependencies:
+
+
Lastly the dependency can be passed in.
+
+
+function MyClass(xhr) {
+ this.doWork = function() {
+ xhr({
+ method:'...',
+ url:'...',
+ complete:function(response){ ... }
+ })
+}
+
+
+
This is the preferred way since the code makes no assumptions as to where the xhr
comes from,
+rather that whoever created the class was responsible for passing it in. Since the creator of the
+class should be different code than the user of the class, it separates the responsibility of
+creation from the logic, and that is what dependency-injection is in a nutshell.
+
+
The class above is very testable, since in the test we can write:
+
+function xhrMock(args) {...}
+var myClass = new MyClass(xhrMock);
+myClass.doWork();
+// assert that xhrMock got called with the right arguments
+
+
+
Notice that no global variables were harmed in the writing of this test.
+
+
Angular comes with dependency injection built in which makes the right thing
+easy to do, but you still need to do it if you wish to take advantage of the testability story.
+
+
Controllers
+
+
What makes each application unique is its logic, which is what we would like to test. If the logic
+for your application is mixed in with DOM manipulation, it will be hard to test as in the example
+below:
+
+
+function PasswordCtrl() {
+ // get references to DOM elements
+ var msg = $('.ex1 span');
+ var input = $('.ex1 input');
+ var strength;
+
+ this.grade = function() {
+ msg.removeClass(strength);
+ var pwd = input.val();
+ password.text(pwd);
+ if (pwd.length > 8) {
+ strength = 'strong';
+ } else if (pwd.length > 3) {
+ strength = 'medium';
+ } else {
+ strength = 'weak';
+ }
+ msg
+ .addClass(strength)
+ .text(strength);
+ }
+}
+
+
+
The code above is problematic from a testability point of view, since it requires your test to have the right kind
+of DOM present when the code executes. The test would look like this:
+
+
+var input = $('<input type="text"/>');
+var span = $('<span>');
+$('body').html('<div class="ex1">')
+ .find('div')
+ .append(input)
+ .append(span);
+var pc = new PasswordCtrl();
+input.val('abc');
+pc.grade();
+expect(span.text()).toEqual('weak');
+$('body').html('');
+
+
+
In angular the controllers are strictly separated from the DOM manipulation logic which results in
+a much easier testability story as can be seen in this example:
+
+
+function PasswordCtrl($scope) {
+ $scope.password = '';
+ $scope.grade = function() {
+ var size = $scope.password.length;
+ if (size > 8) {
+ $scope.strength = 'strong';
+ } else if (size > 3) {
+ $scope.strength = 'medium';
+ } else {
+ $scope.strength = 'weak';
+ }
+ };
+}
+
+
+
and the test is straight forward
+
+
+var pc = new PasswordCtrl();
+pc.password('abc');
+pc.grade();
+expect(pc.strength).toEqual('weak');
+
+
+
Notice that the test is not only much shorter but it is easier to follow what is going on. We say
+that such a test tells a story, rather then asserting random bits which don't seem to be related.
+
+
Filters
+
+
Filters
are functions which transform the data into user readable
+format. They are important because they remove the formatting responsibility from the application
+logic, further simplifying the application logic.
+
+
+myModule.filter('length', function() {
+ return function(text){
+ return (''+(text||'')).length;
+ }
+});
+
+var length = $filter('length');
+expect(length(null)).toEqual(0);
+expect(length('abc')).toEqual(3);
+
+
+
Directives
+
+
Directives in angular are responsible for updating the DOM when the state of the model changes.
+
+
Mocks
+
+
oue
+
+
Global State Isolation
+
+
oue
+
+
Preferred way of Testing
+
+
uo
+
+
JavaScriptTestDriver
+
+
ou
+
+
Jasmine
+
+
ou
+
+
Sample project
+
+
uoe
diff --git a/lib/angular/docs/partials/guide/di.html b/lib/angular/docs/partials/guide/di.html
new file mode 100644
index 0000000..797ebb0
--- /dev/null
+++ b/lib/angular/docs/partials/guide/di.html
@@ -0,0 +1,223 @@
+
+
+
+Dependency Injection
+
+
Dependency Injection (DI) is a software design pattern that deals with how code gets hold of its
+dependencies.
+
+
For in-depth discussion about DI, see Dependency Injection at Wikipedia, Inversion of Control by Martin Fowler, or read about DI in your favorite software design pattern
+book.
+
+
DI in a nutshell
+
+
There are only three ways how an object or a function can get a hold of its dependencies:
+
+
+The dependency can be created, typically using the new
operator.
+The dependency can be looked up by referring to a global variable.
+The dependency can be passed in to where it is needed.
+
+
+
The first two option of creating or looking up dependencies are not optimal, because they hard
+code the dependency, making it difficult, if not impossible, to modify the dependencies.
+This is especially problematic in tests, where it is often desirable to provide mock dependencies
+for test isolation.
+
+
The third option is the most viable, since it removes the responsibility of locating the
+dependency from the component. The dependency is simply handed to the component.
+
+
+ function SomeClass(greeter) {
+ this.greeter = greeter
+ }
+
+ SomeClass.prototype.doSomething = function(name) {
+ this.greeter.greet(name);
+ }
+
+
+
In the above example the SomeClass
is not concerned with locating the greeter
dependency, it
+is simply handed the greeter
at runtime.
+
+
This is desirable, but it puts the responsibility of getting hold of the dependency onto the
+code responsible for the construction of SomeClass
.
+
+
To manage the responsibility of dependency creation, each Angular application has an injector
. The injector is a service locator that is responsible for
+construction and lookup of dependencies.
+
+
Here is an example of using the injector service.
+
+ // Provide the wiring information in a module
+ angular.module('myModule', []).
+
+ // Teach the injector how to build a 'greeter'
+ // Notice that greeter itself is dependent on '$window'
+ factory('greeter', function($window) {
+ // This is a factory function, and is responsible for
+ // creating the 'greet' service.
+ return {
+ greet: function(text) {
+ $window.alert(text);
+ }
+ };
+ });
+
+ // New injector is created from the module.
+ // (This is usually done automatically by angular bootstrap)
+ var injector = angular.injector(['myModule', 'ng']);
+
+ // Request any dependency from the injector
+ var greeter = injector.get('greeter');
+
+
+
Asking for dependencies solves the issue of hard coding, but it also means that the injector needs
+to be passed throughout the application. Passing the injector breaks the Law of Demeter . To remedy this, we turn the
+dependency lookup responsibility to the injector by declaring the dependencies as in this example:
+
+
+ <!-- Given this HTML -->
+ <div ng-controller="MyController">
+ <button ng-click="sayHello()">Hello</button>
+ </div>
+
+
+ // And this controller definition
+ function MyController($scope, greeter) {
+ $scope.sayHello = function() {
+ greeter.greet('Hello World');
+ };
+ }
+
+ // The 'ng-controller' directive does this behind the scenes
+ injector.instantiate(MyController);
+
+
+
Notice that by having the ng-controller
instantiate the class, it can satisfy all of the
+dependencies of the MyController
without the controller ever knowing about the injector. This is
+the best outcome. The application code simply ask for the dependencies it needs, without having to
+deal with the injector. This setup does not break the Law of Demeter.
+
+
Dependency Annotation
+
+
How does the injector know what service needs to be injected?
+
+
The application developer needs to provide annotation information that the injector uses in order
+to resolve the dependencies. Throughout Angular certain API functions are invoked using the
+injector, as per the API documentation. The injector needs to know what services to inject into
+the function. Below are three equivalent ways of annotating your code with service name
+information. These can be used interchangeably as you see fit and are equivalent.
+
+
Inferring Dependencies
+
+
The simplest way to get hold of the dependencies, is to assume that the function parameter names
+are the names of the dependencies.
+
+
+ function MyController($scope, greeter) {
+ ...
+ }
+
+
+
Given a function the injector can infer the names of the service to inject by examining the
+function declaration and extracting the parameter names. In the above example $scope
, and
+greeter
are two services which need to be injected into the function.
+
+
While straightforward, this method will not work with JavaScript minifiers/obfuscators as they
+rename the method parameter names. This makes this way of annotating only useful for pretotyping , and demo applications.
+
+
$inject
Annotation
+
+
To allow the minifers to rename the function parameters and still be able to inject right services
+the function needs to be annotate with the $inject
property. The $inject
property is an array
+of service names to inject.
+
+
+ var MyController = function(renamed$scope, renamedGreeter) {
+ ...
+ }
+ MyController.$inject = ['$scope', 'greeter'];
+
+
+
Care must be taken that the $inject
annotation is kept in sync with the actual arguments in the
+function declaration.
+
+
This method of annotation is useful for controller declarations since it assigns the annotation
+information with the function.
+
+
Inline Annotation
+
+
Sometimes using the $inject
annotation style is not convenient such as when annotating
+directives.
+
+
For example:
+
+ someModule.factory('greeter', function($window) {
+ ...;
+ });
+
+
+
Results in code bloat due to the need of temporary variable:
+
+ var greeterFactory = function(renamed$window) {
+ ...;
+ };
+
+ greeterFactory.$inject = ['$window'];
+
+ someModule.factory('greeter', greeterFactory);
+
+
+
For this reason the third annotation style is provided as well.
+
+ someModule.factory('greeter', ['$window', function(renamed$window) {
+ ...;
+ }]);
+
+
+
Keep in mind that all of the annotation styles are equivalent and can be used anywhere in Angular
+where injection is supported.
+
+
Where can I use DI?
+
+
DI is pervasive throughout Angular. It is typically used in controllers and factory methods.
+
+
DI in controllers
+
+
Controllers are classes which are responsible for application behavior. The recommended way of
+declaring controllers is:
+
+
+ var MyController = function($scope, dep1, dep2) {
+ ...
+ $scope.aMethod = function() {
+ ...
+ }
+ }
+ MyController.$inject = ['$scope', 'dep1', 'dep2'];
+
+
+
Factory methods
+
+
Factory methods are responsible for creating most objects in Angular. Examples are directives,
+services, and filters. The factory methods are registered with the module, and the recommended way
+of declaring factories is:
+
+
+ angualar.module('myModule', []).
+ config(['depProvider', function(depProvider){
+ ...
+ }]).
+ factory('serviceId', ['depService', function(depService) {
+ ...
+ }]).
+ directive('directiveName', ['depService', function(depService) {
+ ...
+ }]).
+ filter('filterName', ['depService', function(depService) {
+ ...
+ }]).
+ run(['depService', function(depService) {
+ ...
+ }]);
+
diff --git a/lib/angular/docs/partials/guide/directive.html b/lib/angular/docs/partials/guide/directive.html
new file mode 100644
index 0000000..d6c5c2f
--- /dev/null
+++ b/lib/angular/docs/partials/guide/directive.html
@@ -0,0 +1,694 @@
+
+
+
+Directives are a way to teach HTML new tricks. During DOM compilation directives are matched
+against the HTML and executed. This allows directives to register behavior, or transform the DOM.
+
+
Angular comes with a built in set of directives which are useful for building web applications but
+can be extended such that HTML can be turned into a declarative domain specific language (DSL).
+
+
Invoking directives from HTML
+
+
Directives have camel cased names such as ngBind
. The directive can be invoked by translating
+the camel case name into snake case with these special characters :
, -
, or _
. Optionally the
+directive can be prefixed with x-
, or data-
to make it HTML validator compliant. Here is a
+list of some of the possible directive names: ng:bind
, ng-bind
, ng_bind
, x-ng-bind
and
+data-ng-bind
.
+
+
The directives can be placed in element names, attributes, class names, as well as comments. Here
+are some equivalent examples of invoking myDir
. (However, most directives are restricted to
+attribute only.)
+
+
+ <span my-dir="exp"></span>
+ <span class="my-dir: exp;"></span>
+ <my-dir></my-dir>
+ <!-- directive: my-dir exp -->
+
+
+
Directives can be invoked in many different ways, but are equivalent in the end result as shown in
+the following example.
+
+
Source
+
+
Demo
+
+
+
String interpolation
+
+
During the compilation process the compiler
matches text and
+attributes using the $interpolate
service to see if they
+contain embedded expressions. These expressions are registered as watches
and will update as part of normal digest
cycle. An example of interpolation is shown
+here:
+
+
+<a href="img/{{username}}.jpg">Hello {{username}}!</a>
+
+
+
Compilation process, and directive matching
+
+
Compilation of HTML happens in three phases:
+
+
+First the HTML is parsed into DOM using the standard browser API. This is important to
+realize because the templates must be parsable HTML. This is in contrast to most templating
+systems that operate on strings, rather than on DOM elements.
+The compilation of the DOM is performed by the call to the $compile()
method. The method traverses the DOM and matches the directives. If a match is found
+it is added to the list of directives associated with the given DOM element. Once all directives
+for a given DOM element have been identified they are sorted by priority and their compile()
+functions are executed. The directive compile function has a chance to modify the DOM structure
+and is responsible for producing a link()
function explained next. The $compile()
method returns a combined linking function, which is a
+collection of all of the linking functions returned from the individual directive compile
+functions.
+Link the template with scope by calling the linking function returned from the previous step.
+This in turn will call the linking function of the individual directives allowing them to
+register any listeners on the elements and set up any watches
with the scope
. The result of this is a live binding between the
+scope and the DOM. A change in the scope is reflected in the DOM.
+
+
+
+ var $compile = ...; // injected into your code
+ var scope = ...;
+
+ var html = '<div ng-bind='exp'></div>';
+
+ // Step 1: parse HTML into DOM element
+ var template = angular.element(html);
+
+ // Step 2: compile the template
+ var linkFn = $compile(template);
+
+ // Step 3: link the compiled template with the scope.
+ linkFn(scope);
+
+
+
Reasons behind the compile/link separation
+
+
At this point you may wonder why the compile process is broken down to a compile and link phase.
+To understand this, let's look at a real world example with a repeater:
+
+
+ Hello {{user}}, you have these actions:
+ <ul>
+ <li ng-repeat="action in user.actions">
+ {{action.description}}
+ </li>
+ </ul>
+
+
+
The short answer is that compile and link separation is needed any time a change in model causes
+a change in DOM structure such as in repeaters.
+
+
When the above example is compiled, the compiler visits every node and looks for directives. The
+{{user}}
is an example of an interpolation
directive. ngRepeat
is another directive. But ngRepeat
has a dilemma. It needs to be
+able to quickly stamp out new li
s for every action
in user.actions
. This means that it needs
+to save a clean copy of the li
element for cloning purposes and as new action
s are inserted,
+the template li
element needs to be cloned and inserted into ul
. But cloning the li
element
+is not enough. It also needs to compile the li
so that its directives such as
+{{action.descriptions}}
evaluate against the right scope
. A naive method would be to simply insert a copy of the li
element and then compile it.
+But compiling on every li
element clone would be slow, since the compilation requires that we
+traverse the DOM tree and look for directives and execute them. If we put the compilation inside a
+repeater which needs to unroll 100 items we would quickly run into performance problems.
+
+
The solution is to break the compilation process into two phases; the compile phase where all of
+the directives are identified and sorted by priority, and a linking phase where any work which
+links a specific instance of the scope
and the specific
+instance of an li
is performed.
+
+
ngRepeat
works by preventing the
+compilation process from descending into the li
element. Instead the ngRepeat
directive compiles li
+separately. The result of the li
element compilation is a linking function which contains all
+of the directives contained in the li
element, ready to be attached to a specific clone of the li
+element. At runtime the ngRepeat
+watches the expression and as items are added to the array it clones the li
element, creates a
+new scope
for the cloned li
element and calls the
+link function on the cloned li
.
+
+
Summary:
+
+
+compile function - The compile function is relatively rare in directives, since most
+directives are concerned with working with a specific DOM element instance rather than
+transforming the template DOM element. Any operation which can be shared among the instance of
+directives should be moved to the compile function for performance reasons.
+link function - It is rare for the directive not to have a link function. A link function
+allows the directive to register listeners to the specific cloned DOM element instance as well
+as to copy content into the DOM from the scope.
+
+
+
Writing directives (short version)
+
+
In this example we will build a directive that displays the current time.
+
+
Source
+
+
Demo
+
+
+
Writing directives (long version)
+
+
An example skeleton of the directive is shown here, for the complete list see below.
+
+
+ var myModule = angular.module(...);
+
+ myModule.directive('directiveName', function factory(injectables) {
+ var directiveDefinitionObject = {
+ priority: 0,
+ template: '<div></div>',
+ templateUrl: 'directive.html',
+ replace: false,
+ transclude: false,
+ restrict: 'A',
+ scope: false,
+ compile: function compile(tElement, tAttrs, transclude) {
+ return {
+ pre: function preLink(scope, iElement, iAttrs, controller) { ... },
+ post: function postLink(scope, iElement, iAttrs, controller) { ... }
+ }
+ },
+ link: function postLink(scope, iElement, iAttrs) { ... }
+ };
+ return directiveDefinitionObject;
+ });
+
+
+
In most cases you will not need such fine control and so the above can be simplified. All of the
+different parts of this skeleton are explained in following sections. In this section we are
+interested only in some of this skeleton.
+
+
The first step in simplyfing the code is to rely on the default values. Therefore the above can be
+simplified as:
+
+
+ var myModule = angular.module(...);
+
+ myModule.directive('directiveName', function factory(injectables) {
+ var directiveDefinitionObject = {
+ compile: function compile(tElement, tAttrs) {
+ return function postLink(scope, iElement, iAttrs) { ... }
+ }
+ };
+ return directiveDefinitionObject;
+ });
+
+
+
Most directives concern themselves only with instances, not with template transformations, allowing
+further simplification:
+
+
+ var myModule = angular.module(...);
+
+ myModule.directive('directiveName', function factory(injectables) {
+ return function postLink(scope, iElement, iAttrs) { ... }
+ });
+
+
+
Factory method
+
+
The factory method is responsible for creating the directive. It is invoked only once, when the
+compiler
matches the directive for the first time. You can
+perform any initialization work here. The method is invoked using the $injector.invoke
which
+makes it injectable following all of the rules of injection annotation.
+
+
Directive Definition Object
+
+
The directive definition object provides instructions to the compiler
. The attributes are:
+
+
+name
- Name of the current scope. Optional and defaults to the name at registration.
+priority
- When there are multiple directives defined on a single DOM element, sometimes it
+is necessary to specify the order in which the directives are applied. The priority
is used
+to sort the directives before their compile
functions get called. Higher priority
goes
+first. The order of directives within the same priority is undefined.
+terminal
- If set to true then the current priority
will be the last set of directives
+which will execute (any directives at the current priority will still execute
+as the order of execution on same priority
is undefined).
+scope
- If set to:
+
+true
- then a new scope will be created for this directive. If multiple directives on the
+same element request a new scope, only one new scope is created. The new scope rule does not
+apply for the root of the template since the root of the template always gets a new scope.
+{}
(object hash) - then a new 'isolate' scope is created. The 'isolate' scope differs from
+normal scope in that it does not prototypically inherit from the parent scope. This is useful
+when creating reusable components, which should not accidentally read or modify data in the
+parent scope.
+The 'isolate' scope takes an object hash which defines a set of local scope properties
+derived from the parent scope. These local properties are useful for aliasing values for
+templates. Locals definition is a hash of local scope property to its source:
+
+@
or @attr
- bind a local scope property to the value of DOM attribute. The result is
+always a string since DOM attributes are strings. If no attr
name is specified then the
+attribute name is assumed to be the same as the local name.
+Given <widget my-attr="hello {{name}}">
and widget definition
+of scope: { localName:'@myAttr' }
, then widget scope property localName
will reflect
+the interpolated value of hello {{name}}
. As the name
attribute changes so will the
+localName
property on the widget scope. The name
is read from the parent scope (not
+component scope).
+=
or =attr
- set up bi-directional binding between a local scope property and the
+parent scope property of name defined via the value of the attr
attribute. If no attr
+name is specified then the attribute name is assumed to be the same as the local name.
+Given <widget my-attr="parentModel">
and widget definition of
+scope: { localModel:'=myAttr' }
, then widget scope property localModel
will reflect the
+value of parentModel
on the parent scope. Any changes to parentModel
will be reflected
+in localModel
and any changes in localModel
will reflect in parentModel
.
+&
or &attr
- provides a way to execute an expression in the context of the parent scope.
+If no attr
name is specified then the attribute name is assumed to be the same as the
+local name. Given <widget my-attr="count = count + value">
and widget definition of
+scope: { localFn:'&myAttr' }
, then isolate scope property localFn
will point to
+a function wrapper for the count = count + value
expression. Often it's desirable to
+pass data from the isolated scope via an expression and to the parent scope, this can be
+done by passing a map of local variable names and values into the expression wrapper fn.
+For example, if the expression is increment(amount)
then we can specify the amount value
+by calling the localFn
as localFn({amount: 22})
.
+controller
- Controller constructor function. The controller is instantiated before the
+pre-linking phase and it is shared with other directives if they request it by name (see
+require
attribute). This allows the directives to communicate with each other and augment
+each other's behavior. The controller is injectable with the following locals:
+
+$scope
- Current scope associated with the element
+$element
- Current element
+$attrs
- Current attributes obeject for the element
+$transclude
- A transclude linking function pre-bound to the correct transclusion scope:
+function(cloneLinkingFn)
.
+require
- Require another controller be passed into current directive linking function. The
+require
takes a name of the directive controller to pass in. If no such controller can be
+found an error is raised. The name can be prefixed with:
+
+?
- Don't raise an error. This makes the require dependency optional.
+^
- Look for the controller on parent elements as well.
+restrict
- String of subset of EACM
which restricts the directive to a specific directive
+declaration style. If omitted directives are allowed on attributes only.
+
+E
- Element name: <my-directive></my-directive>
+A
- Attribute: <div my-directive="exp">
+</div>
+C
- Class: <div class="my-directive: exp;"></div>
+M
- Comment: <!-- directive: my-directive exp -->
+template
- replace the current element with the contents of the HTML. The replacement process
+migrates all of the attributes / classes from the old element to the new one. See Creating
+Widgets section below for more information.
+templateUrl
- Same as template
but the template is loaded from the specified URL. Because
+the template loading is asynchronous the compilation/linking is suspended until the template
+is loaded.
+replace
- if set to true
then the template will replace the current element, rather than
+append the template to the element.
+transclude
- compile the content of the element and make it available to the directive.
+Typically used with ngTransclude
. The advantage of transclusion is that the linking function receives a
+transclusion function which is pre-bound to the correct scope. In a typical setup the widget
+creates an isolate
scope, but the transclusion is not a child, but a sibling of the isolate
+scope. This makes it possible for the widget to have private state, and the transclusion to
+be bound to the parent (pre-isolate
) scope.
+
+true
- transclude the content of the directive.
+'element'
- transclude the whole element including any directives defined at lower priority.
+compile
: This is the compile function described in the section below.
+link
: This is the link function described in the section below. This property is used only
+if the compile
property is not defined.
+
+
+
Compile function
+
+
+ function compile(tElement, tAttrs, transclude) { ... }
+
+
+
The compile function deals with transforming the template DOM. Since most directives do not do
+template transformation, it is not used often. Examples that require compile functions are
+directives that transform template DOM, such as ngRepeat
, or load the contents
+asynchronously, such as ngView
. The
+compile function takes the following arguments.
+
+
+tElement
- template element - The element where the directive has been declared. It is
+safe to do template transformation on the element and child elements only.
+tAttrs
- template attributes - Normalized list of attributes declared on this element shared
+between all directive compile functions. See Attributes .
+transclude
- A transclude linking function: function(scope, cloneLinkingFn)
.
+
+
+
NOTE: The template instance and the link instance may not be the same objects if the template has
+been cloned. For this reason it is not safe in the compile function to do anything other than DOM
+transformation that applies to all DOM clones. Specifically, DOM listener registration should be
+done in a linking function rather than in a compile function.
+
+
A compile function can have a return value which can be either a function or an object.
+
+
+returning a function - is equivalent to registering the linking function via the link
property
+of the config object when the compile function is empty.
+returning an object with function(s) registered via pre
and post
properties - allows you to
+control when a linking function should be called during the linking phase. See info about
+pre-linking and post-linking functions below.
+
+
+
Linking function
+
+
+ function link(scope, iElement, iAttrs, controller) { ... }
+
+
+
The link function is responsible for registering DOM listeners as well as updating the DOM. It is
+executed after the template has been cloned. This is where most of the directive logic will be
+put.
+
+
+scope
- Scope
- The scope to be used by the
+directive for registering watches
.
+iElement
- instance element - The element where the directive is to be used. It is safe to
+manipulate the children of the element only in postLink
function since the children have
+already been linked.
+iAttrs
- instance attributes - Normalized list of attributes declared on this element shared
+between all directive linking functions. See Attributes .
+controller
- a controller instance - A controller instance if at least one directive on the
+element defines a controller. The controller is shared among all the directives, which allows
+the directives to use the controllers as a communication channel.
+
+
+
Pre-linking function
+
+
Executed before the child elements are linked. Not safe to do DOM transformation since the
+compiler linking function will fail to locate the correct elements for linking.
+
+
Post-linking function
+
+
Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function.
+
+
+
+
Attributes
+
+
The Attributes
object - passed as a parameter in the
+link() or compile() functions - is a way of accessing:
+
+
+normalized attribute names: Since a directive such as 'ngBind' can be expressed in many ways
+such as 'ng:bind', or 'x-ng-bind', the attributes object allows for normalized accessed to
+the attributes.
+directive inter-communication: All directives share the same instance of the attributes
+object which allows the directives to use the attributes object as inter directive
+communication.
+supports interpolation: Interpolation attributes are assigned to the attribute object
+allowing other directives to read the interpolated value.
+observing interpolated attributes: Use $observe
to observe the value changes of attributes
+that contain interpolation (e.g. src="{{bar}}"
). Not only is this very efficient but it's also
+the only way to easily get the actual value because during the linking phase the interpolation
+hasn't been evaluated yet and so the value is at this time set to undefined
.
+
+
+
+function linkingFn(scope, elm, attrs, ctrl) {
+ // get the attribute value
+ console.log(attrs.ngModel);
+
+ // change the attribute
+ attrs.$set('ngModel', 'new value');
+
+ // observe changes to interpolated attribute
+ attrs.$observe('ngModel', function(value) {
+ console.log('ngModel has changed value to ' + value);
+ });
+}
+
+
+
Understanding Transclusion and Scopes
+
+
It is often desirable to have reusable components. Below is a pseudo code showing how a simplified
+dialog component may work.
+
+
+ <div>
+ <button ng-click="show=true">show</button>
+ <dialog title="Hello {{username}}."
+ visible="show"
+ on-cancel="show = false"
+ on-ok="show = false; doSomething()">
+ Body goes here: {{username}} is {{title}}.
+ </dialog>
+ </div>
+
+
+
Clicking on the "show" button will open the dialog. The dialog will have a title, which is
+data bound to username
, and it will also have a body which we would like to transclude
+into the dialog.
+
+
Here is an example of what the template definition for the dialog
widget may look like.
+
+
+ <div ng-show="visible">
+ <h3>{{title}}</h3>
+ <div class="body" ng-transclude></div>
+ <div class="footer">
+ <button ng-click="onOk()">Save changes</button>
+ <button ng-click="onCancel()">Close</button>
+ </div>
+ </div>
+
+
+
This will not render properly, unless we do some scope magic.
+
+
The first issue we have to solve is that the dialog box template expects title
to be defined, but
+the place of instantiation would like to bind to username
. Furthermore the buttons expect the
+onOk
and onCancel
functions to be present in the scope. This limits the usefulness of the
+widget. To solve the mapping issue we use the locals
to create local variables which the template
+expects as follows:
+
+
+ scope: {
+ title: '@', // the title uses the data-binding from the parent scope
+ onOk: '&', // create a delegate onOk function
+ onCancel: '&', // create a delegate onCancel function
+ visible: '=' // set up visible to accept data-binding
+ }
+
+
+
Creating local properties on widget scope creates two problems:
+
+
+isolation - if the user forgets to set title
attribute of the dialog widget the dialog
+ template will bind to parent scope property. This is unpredictable and undesirable.
+transclusion - the transcluded DOM can see the widget locals, which may overwrite the
+ properties which the transclusion needs for data-binding. In our example the title
+ property of the widget clobbers the title
property of the transclusion.
+
+
+
To solve the issue of lack of isolation, the directive declares a new isolated
scope. An
+isolated scope does not prototypically inherit from the child scope, and therefore we don't have
+to worry about accidentally clobbering any properties.
+
+
However isolated
scope creates a new problem: if a transcluded DOM is a child of the widget
+isolated scope then it will not be able to bind to anything. For this reason the transcluded scope
+is a child of the original scope, before the widget created an isolated scope for its local
+variables. This makes the transcluded and widget isolated scope siblings.
+
+
This may seem to be unexpected complexity, but it gives the widget user and developer the least
+surprise.
+
+
Therefore the final directive definition looks something like this:
+
+
+transclude: true,
+scope: {
+ title: '@', // the title uses the data-binding from the parent scope
+ onOk: '&', // create a delegate onOk function
+ onCancel: '&', // create a delegate onCancel function
+ visible: '=' // set up visible to accept data-binding
+},
+restrict: 'E',
+replace: true
+
+
+
Creating Components
+
+
It is often desirable to replace a single directive with a more complex DOM structure. This
+allows the directives to become a short hand for reusable components from which applications
+can be built.
+
+
Following is an example of building a reusable widget.
+
+
Source
+
+
Demo
+
diff --git a/lib/angular/docs/partials/guide/expression.html b/lib/angular/docs/partials/guide/expression.html
new file mode 100644
index 0000000..0f19304
--- /dev/null
+++ b/lib/angular/docs/partials/guide/expression.html
@@ -0,0 +1,216 @@
+
+
+
+Expressions are JavaScript-like code snippets that are usually placed in bindings such as {{
+expression }}
. Expressions are processed by $parse
+service.
+
+
For example, these are all valid expressions in angular:
+
+
+1+2
+3*10 | currency
+user.name
+
+
+
Angular Expressions vs. JS Expressions
+
+
It might be tempting to think of Angular view expressions as JavaScript expressions, but that is
+not entirely correct, since Angular does not use a JavaScript eval()
to evaluate expressions.
+You can think of Angular expressions as JavaScript expressions with following differences:
+
+
+Attribute Evaluation: evaluation of all properties are against the scope, doing the
+evaluation, unlike in JavaScript where the expressions are evaluated against the global
+window
.
+Forgiving: expression evaluation is forgiving to undefined and null, unlike in JavaScript,
+where such evaluations generate NullPointerExceptions
.
+No Control Flow Statements: you cannot do any of the following in angular expression:
+conditionals, loops, or throw.
+Filters: you can pass result of expression evaluations through filter chains. For example
+to convert date object into a local specific human-readable format.
+
+
+
If, on the other hand, you do want to run arbitrary JavaScript code, you should make it a
+controller method and call the method. If you want to eval()
an angular expression from
+JavaScript, use the $eval()
method.
+
+
Example
+
+
Source
+
+
Demo
+
+
+
You can try evaluating different expressions here:
+
+
Source
+
+
Demo
+
+
+
Property Evaluation
+
+
Evaluation of all properties takes place against a scope. Unlike JavaScript, where names default
+to global window properties, Angular expressions have to use $window
to refer to the global window
object. For example, if you want to call alert()
, which is
+defined on window
, in an expression you must use $window.alert()
. This is done intentionally to
+prevent accidental access to the global state (a common source of subtle bugs).
+
+
Source
+
+
Demo
+
+
+
Forgiving
+
+
Expression evaluation is forgiving to undefined and null. In JavaScript, evaluating a.b.c
throws
+an exception if a
is not an object. While this makes sense for a general purpose language, the
+expression evaluations are primarily used for data binding, which often look like this:
+
+
{{a.b.c}}
+
+
+
It makes more sense to show nothing than to throw an exception if a
is undefined (perhaps we are
+waiting for the server response, and it will become defined soon). If expression evaluation wasn't
+forgiving we'd have to write bindings that clutter the code, for example: {{((a||{}).b||{}).c}}
+
+
Similarly, invoking a function a.b.c()
on undefined or null simply returns undefined.
+
+
No Control Flow Statements
+
+
You cannot write a control flow statement in an expression. The reason behind this is core to the
+Angular philosophy that application logic should be in controllers, not in the view. If you need a
+conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead.
+
+
Filters
+
+
When presenting data to the user, you might need to convert the data from its raw format to a
+user-friendly format. For example, you might have a data object that needs to be formatted
+according to the locale before displaying it to the user. You can pass expressions through a chain
+of filters like this:
+
+
name | uppercase
+
+
+
The expression evaluator simply passes the value of name to uppercase
filter.
+
+
Chain filters using this syntax:
+
+
value | filter1 | filter2
+
+
+
You can also pass colon-delimited arguments to filters, for example, to display the number 123
+with 2 decimal points:
+
+
123 | number:2
+
+
+
The $
+
+
You might be wondering, what is the significance of the $ prefix? It is simply a prefix that
+angular uses, to differentiate its API names from others. If angular didn't use $, then evaluating
+a.length()
would return undefined because neither a nor angular define such a property.
+
+
Consider that in a future version of Angular we might choose to add a length method, in which case
+the behavior of the expression would change. Worse yet, you the developer could create a length
+property and then we would have a collision. This problem exists because Angular augments existing
+objects with additional behavior. By prefixing its additions with $ we are reserving our namespace
+so that angular developers and developers who use Angular can develop in harmony without collisions.
diff --git a/lib/angular/docs/partials/guide/forms.html b/lib/angular/docs/partials/guide/forms.html
new file mode 100644
index 0000000..8b15d3b
--- /dev/null
+++ b/lib/angular/docs/partials/guide/forms.html
@@ -0,0 +1,368 @@
+
+
+
+Controls (input
, select
, textarea
) are a way for user to enter data.
+Form is a collection of controls for the purpose of grouping related controls together.
+
+
Form and controls provide validation services, so that the user can be notified of invalid input.
+This provides a better user experience, because the user gets instant feedback on how to correct the error.
+Keep in mind that while client-side validation plays an important role in providing good user experience, it can easily be circumvented and thus can not be trusted.
+Server-side validation is still necessary for a secure application.
+
+
Simple form
+
+
The key directive in understanding two-way data-binding is ngModel
.
+The ngModel
directive provides the two-way data-binding by synchronizing the model to the view, as well as view to the model.
+In addition it provides an API
for other directives to augment its behavior.
+
+
Source
+
+
Demo
+
+
+
Note that novalidate
is used to disable browser's native form validation.
+
+
Using CSS classes
+
+
To allow styling of form as well as controls, ngModel
add these CSS classes:
+
+
+ng-valid
+ng-invalid
+ng-pristine
+ng-dirty
+
+
+
The following example uses the CSS to display validity of each form control.
+In the example both user.name
and user.email
are required, but are rendered with red background only when they are dirty.
+This ensures that the user is not distracted with an error until after interacting with the control, and failing to satisfy its validity.
+
+
Source
+
+
Demo
+
+
+
Binding to form and control state
+
+
A form is in instance of FormController
.
+The form instance can optionally be published into the scope using the name
attribute.
+Similarly control is an instance of NgModelController
.
+The control instance can similarly be published into the form instance using the name
attribute.
+This implies that the internal state of both the form and the control is available for binding in the view using the standard binding primitives.
+
+
This allows us to extend the above example with these features:
+
+
+RESET button is enabled only if form has some changes
+SAVE button is enabled only if form has some changes and is valid
+custom error messages for user.email
and user.agree
+
+
+
Source
+
+
Demo
+
+
+
Custom Validation
+
+
Angular provides basic implementation for most common html5 input
+types: (text
, number
, url
, email
, radio
, checkbox
), as well as some directives for validation (required
, pattern
, minlength
, maxlength
, min
, max
).
+
+
Defining your own validator can be done by defining your own directive which adds a custom validation function to the ngModel
controller
.
+To get a hold of the controller the directive specifies a dependency as shown in the example below.
+The validation can occur in two places:
+
+
+
+
In the following example we create two directives.
+
+
+The first one is integer
and it validates whether the input is a valid integer.
+For example 1.23
is an invalid value, since it contains a fraction.
+Note that we unshift the array instead of pushing.
+This is because we want to be first parser and consume the control string value, as we need to execute the validation function before a conversion to number occurs.
+The second directive is a smart-float
.
+It parses both 1.2
and 1,2
into a valid float number 1.2
.
+Note that we can't use input type number
here as HTML5 browsers would not allow the user to type what it would consider an invalid number such as 1,2
.
+
+
+
Source
+
+
Demo
+
+
+
Implementing custom form controls (using ngModel
)
+
+
Angular implements all of the basic HTML form controls (input
, select
, textarea
), which should be sufficient for most cases.
+However, if you need more flexibility, you can write your own form control as a directive.
+
+
In order for custom control to work with ngModel
and to achieve two-way data-binding it needs to:
+
+
+implement render
method, which is responsible for rendering the data after it passed the NgModelController#$formatters
,
+call $setViewValue
method, whenever the user interacts with the control and model needs to be updated. This is usually done inside a DOM Event listener.
+
+
+
See $compileProvider.directive for more info.
+
+
The following example shows how to add two-way data-binding to contentEditable elements.
+
+
Source
+
+
Demo
+
diff --git a/lib/angular/docs/partials/guide/i18n.html b/lib/angular/docs/partials/guide/i18n.html
new file mode 100644
index 0000000..731a362
--- /dev/null
+++ b/lib/angular/docs/partials/guide/i18n.html
@@ -0,0 +1,113 @@
+
+
+
+I18n and L10n in AngularJS
+
+
What is i18n and l10n?
+
+
Internationalization, abbreviated i18n, is the process of developing products in such a way that
+they can be localized for languages and cultures easily. Localization, abbreviated l10n, is the
+process of adapting applications and text to enable their usability in a particular cultural or
+linguistic market. For application developers, internationalizing an application means abstracting
+all of the strings and other locale-specific bits (such as date or currency formats) out of the
+application. Localizing an application means providing translations and localized formats for the
+abstracted bits.
+
+
What level of support for i18n/l10n is currently in Angular?
+
+
Currently, Angular supports i18n/l10n for datetime , number and currency filters.
+
+
Additionally, Angular supports localizable pluralization support provided by the ngPluralize directive
.
+
+
All localizable Angular components depend on locale-specific rule sets managed by the $locale service
.
+
+
For readers who want to jump straight into examples, we have a few web pages that showcase how to
+use Angular filters with various locale rule sets. You can find these examples either on Github or in the i18n/e2e folder of
+Angular development package.
+
+
What is a locale id?
+
+
A locale is a specific geographical, political, or cultural region. The most commonly used locale
+ID consists of two parts: language code and country code. For example, en-US, en-AU, zh-CN are all
+valid locale IDs that have both language codes and country codes. Because specifying a country code
+in locale ID is optional, locale IDs such as en, zh, and sk are also valid. See the ICU website for more information about using locale IDs.
+
+
Supported locales in Angular
+Angular separates number and datetime format rule sets into different files, each file for a
+particular locale. You can find a list of currently supported locales here
+
+
Providing locale rules to Angular
+
+
There are two approaches to providing locale rules to Angular:
+
+
1. Pre-bundled rule sets
+
+
You can pre-bundle the desired locale file with Angular by concatenating the content of the
+locale-specific file to the end of angular.js
or angular.min.js
file.
+
+
For example on *nix, to create a an angular.js file that contains localization rules for german
+locale, you can do the following:
+
+
cat angular.js i18n/angular-locale_de-ge.js > angular_de-ge.js
+
+
When the application containing angular_de-ge.js
script instead of the generic angular.js script
+starts, Angular is automatically pre-configured with localization rules for the german locale.
+
+
2. Including locale js script in index.html page
+
+
You can also include the locale specific js file in the index.html page. For example, if one client
+requires German locale, you would serve index_de-ge.html which will look something like this:
+
+
+<html ng-app>
+ <head>
+….
+ <script src="angular.js"></script>
+ <script src="i18n/angular-locale_de-ge.js"></script>
+….
+ </head>
+</html>
+
+
+
Comparison of the two approaches
+Both approaches described above requires you to prepare different index.html pages or js files for
+each locale that your app may be localized into. You also need to configure your server to serve
+the correct file that correspond to the desired locale.
+
+
However, the second approach (Including locale js script in index.html page) is likely to be slower
+because an extra script needs to be loaded.
+
+
"Gotchas"
+
+
Currency symbol "gotcha"
+
+
Angular's currency filter allows
+you to use the default currency symbol from the locale service
,
+or you can provide the filter with a custom currency symbol. If your app will be used only in one
+locale, it is fine to rely on the default currency symbol. However, if you anticipate that viewers
+in other locales might use your app, you should provide your own currency symbol to make sure the
+actual value is understood.
+
+
For example, if you want to display account balance of 1000 dollars with the following binding
+containing currency filter: {{ 1000 | currency }}
, and your app is currently in en-US locale.
+'$1000.00' will be shown. However, if someone in a different local (say, Japan) views your app, her
+browser will specify the locale as ja, and the balance of '¥1000.00' will be shown instead. This
+will really upset your client.
+
+
In this case, you need to override the default currency symbol by providing the currency filter with a currency symbol as
+a parameter when you configure the filter, for example, {{ 1000 | currency:"USD$"}}. This way,
+Angular will always show a balance of 'USD$1000' and disregard any locale changes.
+
+
Translation length "gotcha"
+
+
Keep in mind that translated strings/datetime formats can vary greatly in length. For example,
+June 3, 1977
will be translated to Spanish as 3 de junio de 1977
. There are bound to be other
+more extreme cases. Hence, when internationalizing your apps, you need to apply CSS rules
+accordingly and do thorough testing to make sure UI components do not overlap.
+
+
Timezones
+
+
Keep in mind that Angular datetime filter uses the time zone settings of the browser. So the same
+application will show different time information depending on the time zone settings of the
+computer that the application is running on. Neither Javascript nor Angular currently supports
+displaying the date with a timezone specified by the developer.
diff --git a/lib/angular/docs/partials/guide/ie.html b/lib/angular/docs/partials/guide/ie.html
new file mode 100644
index 0000000..7790f37
--- /dev/null
+++ b/lib/angular/docs/partials/guide/ie.html
@@ -0,0 +1,160 @@
+
+
+
+Overview
+
+
This document describes the Internet Explorer (IE) idiosyncrasies when dealing with custom HTML
+attributes and tags. Read this document if you are planning on deploying your Angular application
+on IE v8.0 or earlier.
+
+
Short Version
+
+
To make your Angular application work on IE please make sure that:
+
+
+You polyfill JSON.stringify if necessary (IE7 will need this). You can use
+ JSON2 or
+ JSON3 polyfills for this.
+you do not use custom element tags such as <ng:view>
(use the attribute version
+ <div ng-view>
instead), or
+if you do use custom element tags, then you must take these steps to make IE happy:
+
+
+
+ <html xmlns:ng="http://angularjs.org">
+ <head>
+ <!--[if lte IE 8]>
+ <script>
+ document.createElement('ng-include');
+ document.createElement('ng-pluralize');
+ document.createElement('ng-view');
+
+ // Optionally these for CSS
+ document.createElement('ng:include');
+ document.createElement('ng:pluralize');
+ document.createElement('ng:view');
+ </script>
+ <![endif]-->
+ </head>
+ <body>
+ ...
+ </body>
+ </html>
+
+
+
The important parts are:
+
+
+xmlns:ng
- namespace - you need one namespace for each custom tag you are planning on
+using.
+document.createElement(yourTagName)
- creation of custom tag names - Since this is an
+issue only for older version of IE you need to load it conditionally. For each tag which does
+not have namespace and which is not defined in HTML you need to pre-declare it to make IE
+happy.
+
+
+
Long Version
+
+
IE has issues with element tag names which are not standard HTML tag names. These fall into two
+categories, and each category has its own fix.
+
+
+If the tag name starts with my:
prefix than it is considered an XML namespace and must
+have corresponding namespace declaration on <html xmlns:my="ignored">
+If the tag has no :
but it is not a standard HTML tag, then it must be pre-created using
+document.createElement('my-tag')
+If you are planning on styling the custom tag with CSS selectors, then it must be
+pre-created using document.createElement('my-tag')
regardless of XML namespace.
+
+
+
The Good News
+
+
The good news is that these restrictions only apply to element tag names, and not to element
+attribute names. So this requires no special handling in IE: <div my-tag your:tag>
+</div>
.
+
+
What happens if I fail to do this?
+
+
Suppose you have HTML with unknown tag mytag
(this could also be my:tag
or my-tag
with same
+result):
+
+
+ <html>
+ <body>
+ <mytag>some text</mytag>
+ </body>
+ </html>
+
+
+
It should parse into the following DOM:
+
+
+#document
+ +- HTML
+ +- BODY
+ +- mytag
+ +- #text: some text
+
+
+
The expected behavior is that the BODY
element has a child element mytag
, which in turn has
+the text some text
.
+
+
But this is not what IE does (if the above fixes are not included):
+
+
+#document
+ +- HTML
+ +- BODY
+ +- mytag
+ +- #text: some text
+ +- /mytag
+
+
+
In IE, the behavior is that the BODY
element has three children:
+
+
+A self closing mytag
. Example of self closing tag is <br/>
. The trailing /
is optional,
+but the <br>
tag is not allowed to have any children, and browsers consider <br>some
+text</br>
as three siblings not a <br>
with some text
as child.
+A text node with some text
. This should have been a child of mytag
above, not a sibling.
+A corrupt self closing /mytag
. This is corrupt since element names are not allowed to have
+the /
character. Furthermore this closing element should not be part of the DOM since it is
+only used to delineate the structure of the DOM.
+
+
+
CSS Styling of Custom Tag Names
+
+
To make CSS selectors work with custom elements, the custom element name must be pre-created with
+document.createElement('my-tag')
regardless of XML namespace.
+
+
+ <html xmlns:ng="needed for ng: namespace">
+ <head>
+ <!--[if lte IE 8]>
+ <script>
+ // needed to make ng-include parse properly
+ document.createElement('ng-include');
+
+ // needed to enable CSS reference
+ document.createElement('ng:view');
+ </script>
+ <![endif]-->
+ <style>
+ ng\\:view {
+ display: block;
+ border: 1px solid red;
+ }
+
+ ng-include {
+ display: block;
+ border: 1px solid blue;
+ }
+ </style>
+ </head>
+ <body>
+ <ng:view></ng:view>
+ <ng-include></ng-include>
+ ...
+ </body>
+ </html>
+
diff --git a/lib/angular/docs/partials/guide/index.html b/lib/angular/docs/partials/guide/index.html
new file mode 100644
index 0000000..5569c52
--- /dev/null
+++ b/lib/angular/docs/partials/guide/index.html
@@ -0,0 +1,13 @@
+
+
+
+Welcome to the angular Developer Guide. If you are here to learn the details of how to use angular
+to develop web apps, you've come to the right place.
+
+
If you are completely or relatively unfamiliar with angular, you may want to check out one or both
+of the following documents before returning here to the Developer Guide:
+
+
diff --git a/lib/angular/docs/partials/guide/introduction.html b/lib/angular/docs/partials/guide/introduction.html
new file mode 100644
index 0000000..eac503b
--- /dev/null
+++ b/lib/angular/docs/partials/guide/introduction.html
@@ -0,0 +1,45 @@
+
+
+
+Angular is pure client-side technology, written entirely in JavaScript. It works with the
+long-established technologies of the web (HTML, CSS, and JavaScript) to make the development of
+web apps easier and faster than ever before.
+
+
One important way that Angular simplifies web development is by increasing the level of abstraction
+between the developer and most low-level web app development tasks. Angular automatically takes
+care of many of these tasks, including:
+
+
+DOM Manipulation
+Setting Up Listeners and Notifiers
+Input Validation
+
+
+
Because Angular handles much of the work involved in these tasks, developers can concentrate more
+on application logic and less on repetitive, error-prone, lower-level coding.
+
+
At the same time that Angular simplifies the development of web apps, it brings relatively
+sophisticated techniques to the client-side, including:
+
+
+Separation of data, application logic, and presentation components
+Data Binding between data and presentation components
+Services (common web app operations, implemented as substitutable objects)
+Dependency Injection (used primarily for wiring together services)
+An extensible HTML compiler (written entirely in JavaScript)
+Ease of Testing
+
+
+
These techniques have been for the most part absent from the client-side for far too long.
+
+
Single-page / Round-trip Applications
+
+
You can use Angular to develop both single-page and round-trip apps, but Angular is designed
+primarily for developing single-page apps. Angular supports browser history, forward and back
+buttons, and bookmarking in single-page apps.
+
+
You normally wouldn't want to load Angular with every page change, as would be the case with using
+Angular in a round-trip app. However, it would make sense to do so if you were adding a subset of
+Angular's features (for example, templates to leverage angular's data-binding feature) to an
+existing round-trip app. You might follow this course of action if you were migrating an older app
+to a single-page Angular app.
diff --git a/lib/angular/docs/partials/guide/module.html b/lib/angular/docs/partials/guide/module.html
new file mode 100644
index 0000000..dc2dfd1
--- /dev/null
+++ b/lib/angular/docs/partials/guide/module.html
@@ -0,0 +1,276 @@
+
+
+
+What is a Module?
+
+
Most applications have a main method which instantiates, wires, and bootstraps the application.
+Angular apps don't have a main method. Instead modules declaratively specify how an application
+should be bootstrapped. There are several advantages to this approach:
+
+
+The process is more declarative which is easier to understand
+In unit-testing there is no need to load all modules, which may aid in writing unit-tests.
+Additional modules can be loaded in scenario tests, which can override some of the
+configuration and help end-to-end test the application
+Third party code can be packaged as reusable modules.
+The modules can be loaded in any/parallel order (due to delayed nature of module execution).
+
+
+
The Basics
+
+
Ok, I'm in a hurry. How do I get a Hello World module working?
+
+
Important things to notice:
+
+
+Module
API
+Notice the reference to the myApp
module in the <html ng-app="myApp">
, it is what
+bootstraps the app using your module.
+
+
+
Source
+
+
Demo
+
+
+
Recommended Setup
+
+
While the example above is simple, it will not scale to large applications. Instead we recommend
+that you break your application to multiple modules like this:
+
+
+A service module, for service declaration
+A directive module, for directive declaration
+A filter module, for filter declaration
+And an application level module which depends on the above modules, and which has
+initialization code.
+
+
+
The reason for this breakup is that in your tests, it is often necessary to ignore the
+initialization code, which tends to be difficult to test. By putting it into a separate module it
+can be easily ignored in tests. The tests can also be more focused by only loading the modules
+that are relevant to tests.
+
+
The above is only a suggestion, so feel free to tailor it to your needs.
+
+
Source
+
+
Demo
+
+
+
Module Loading & Dependencies
+
+
A module is a collection of configuration and run blocks which get applied to the application
+during the bootstrap process. In its simplest form the module consist of collection of two kinds
+of blocks:
+
+
+Configuration blocks - get executed during the provider registrations and configuration
+phase. Only providers and constants can be injected into configuration blocks. This is to
+prevent accidental instantiation of services before they have been fully configured.
+Run blocks - get executed after the injector is created and are used to kickstart the
+application. Only instances and constants can be injected into run blocks. This is to prevent
+further system configuration during application run time.
+
+
+
+angular.module('myModule', []).
+ config(function(injectables) { // provider-injector
+ // This is an example of config block.
+ // You can have as many of these as you want.
+ // You can only inject Providers (not instances)
+ // into the config blocks.
+ }).
+ run(function(injectables) { // instance-injector
+ // This is an example of a run block.
+ // You can have as many of these as you want.
+ // You can only inject instances (not Providers)
+ // into the run blocks
+ });
+
+
+
Configuration Blocks
+
+
There are some convenience methods on the module which are equivalent to the config block. For
+example:
+
+
+angular.module('myModule', []).
+ value('a', 123).
+ factory('a', function() { return 123; }).
+ directive('directiveName', ...).
+ filter('filterName', ...);
+
+// is same as
+
+angular.module('myModule', []).
+ config(function($provide, $compileProvider, $filterProvider) {
+ $provide.value('a', 123)
+ $provide.factory('a', function() { return 123; })
+ $compileProvider.directive('directiveName', ...).
+ $filterProvider.register('filterName', ...);
+ });
+
+
+
The configuration blocks get applied in the order in which they are registered. The only exception
+to it are constant definitions, which are placed at the beginning of all configuration blocks.
+
+
Run Blocks
+
+
Run blocks are the closest thing in Angular to the main method. A run block is the code which
+needs to run to kickstart the application. It is executed after all of the service have been
+configured and the injector has been created. Run blocks typically contain code which is hard
+to unit-test, and for this reason should be declared in isolated modules, so that they can be
+ignored in the unit-tests.
+
+
Dependencies
+
+
Modules can list other modules as their dependencies. Depending on a module implies that required
+module needs to be loaded before the requiring module is loaded. In other words the configuration
+blocks of the required modules execute before the configuration blocks or the requiring module.
+The same is true for the run blocks. Each module can only be loaded once, even if multiple other
+modules require it.
+
+
Asynchronous Loading
+
+
Modules are a way of managing $injector configuration, and have nothing to do with loading of
+scripts into a VM. There are existing projects which deal with script loading, which may be used
+with Angular. Because modules do nothing at load time they can be loaded into the VM in any order
+and thus script loaders can take advantage of this property and parallelize the loading process.
+
+
Unit Testing
+
+
In its simplest form a unit test is a way of instantiating a subset of the application in test and
+then applying a stimulus to it. It is important to realize that each module can only be loaded
+once per injector. Typically an app has only one injector. But in tests, each test has its own
+injector, which means that the modules are loaded multiple times per VM. Properly structured
+modules can help with unit testing, as in this example:
+
+
In all of these examples we are going to assume this module definition:
+
+ angular.module('greetMod', []).
+
+ factory('alert', function($window) {
+ return function(text) {
+ $window.alert(text);
+ }
+ }).
+
+ value('salutation', 'Hello').
+
+ factory('greet', function(alert, salutation) {
+ return function(name) {
+ alert(salutation + ' ' + name + '!');
+ }
+ });
+
+
+
Let's write some tests:
+
+describe('myApp', function() {
+ // load the relevant application modules then load a special
+ // test module which overrides the $window with a mock version,
+ // so that calling window.alert() will not block the test
+ // runner with a real alert box. This is an example of overriding
+ // configuration information in tests.
+ beforeEach(module('greetMod', function($provide) {
+ $provide.value('$window', {
+ alert: jasmine.createSpy('alert')
+ });
+ }));
+
+ // The inject() will create the injector and inject the greet and
+ // $window into the tests. The test need not concern itself with
+ // wiring of the application, only with testing it.
+ it('should alert on $window', inject(function(greet, $window) {
+ greet('World');
+ expect($window.alert).toHaveBeenCalledWith('Hello World!');
+ }));
+
+ // this is another way of overriding configuration in the
+ // tests using an inline module and inject methods.
+ it('should alert using the alert service', function() {
+ var alertSpy = jasmine.createSpy('alert');
+ module(function($provide) {
+ $provide.value('alert', alertSpy);
+ });
+ inject(function(greet) {
+ greet('World');
+ expect(alertSpy).toHaveBeenCalledWith('Hello World!');
+ });
+ });
+});
+
diff --git a/lib/angular/docs/partials/guide/overview.html b/lib/angular/docs/partials/guide/overview.html
new file mode 100644
index 0000000..833c77a
--- /dev/null
+++ b/lib/angular/docs/partials/guide/overview.html
@@ -0,0 +1,222 @@
+
+
+
+What Is Angular?
+
+
AngularJS is a structural framework for dynamic web apps. It lets you use HTML as your template
+language and lets you extend HTML's syntax to express your application's components clearly and
+succinctly. Out of the box, it eliminates much of the code you currently write through data
+binding and dependency injection. And it all happens in JavaScript within the browser making it an
+ideal partner with any server technology.
+
+
Angular is what HTML would have been had it been designed for applications. HTML is a great
+declarative language for static documents. It does not contain much in the way of creating
+applications, and as a result building web applications is an exercise in what do I have to do, so
+that I trick the browser in to doing what I want.
+
+
The impedance mismatch between dynamic applications and static documents is often solved as:
+
+
+library - a collection of functions which are useful when writing web apps. Your code is
+in charge and it calls into the library when it sees fit. E.g., jQuery
.
+frameworks - a particular implementation of a web application, where your code fills in
+the details. The framework is in charge and it calls into your code when it needs something
+app specific. E.g., knockout
, sproutcore
, etc.
+
+
+
Angular takes another approach. It attempts to minimize the impedance mismatch between document
+centric HTML and what an application needs by creating new HTML constructs. Angular teaches the
+browser new syntax through a construct we call directives. Examples include:
+
+
+Data binding as in {{}}
.
+DOM control structures for repeating/hiding DOM fragments.
+Support for forms and form validation.
+Attaching code-behind to DOM elements.
+Grouping of HTML into reusable components.
+
+
+
End-to-end solution
+
+
Angular tries to be an end-to-end solution, when building a web application. This means it is
+not a single piece in an overall puzzle of building a web application, but an end-to-end solution.
+This makes Angular opinionated about how a CRUD application should be built. But while it is
+opinionated, it also tries to make sure that its opinion is just a starting point, which you can
+easily change. Angular comes with the following out-of-the-box:
+
+
+Everything you need to build a CRUD app in a cohesive set: data-binding, basic templating
+directives, form validation, routing, deep-linking, reusable components, dependency injection.
+Testability story: unit-testing, end-to-end testing, mocks, test harnesses.
+Seed application with directory layout and test scripts as a starting point.
+
+
+
Angular Sweet Spot
+
+
Angular simplifies application development by presenting a higher level of abstraction to the
+developer. Like any abstraction, it comes at a cost of flexibility. In other words not every app
+is a good fit for Angular. Angular was built for the CRUD application in mind. Luckily CRUD
+applications represent at least 90% of the web applications. But to understand what Angular is
+good at one also has to understand when an app is not a good fit for Angular.
+
+
Games, and GUI editors are examples of very intensive and tricky DOM manipulation. These kinds of
+apps are different from CRUD apps, and as a result are not a good fit for Angular. In these cases
+using something closer to bare metal such as jQuery
may be a better fit.
+
+
An Introductory Angular Example
+
+
Below is a typical CRUD application which contains a form. The form values are validated, and
+are used to compute the total, which is formatted to a particular locale. These are some common
+concepts which the application developer may face:
+
+
+attaching data-model to the UI.
+writing, reading and validating user input.
+computing new values based on the model.
+formatting output in a user specific locale.
+
+
+
Source
+
+
Demo
+
+
+
Try out the Live Preview above, and then let's walk through the example and describe what's going
+on.
+
+
In the <html>
tag, we specify that it is an Angular
+application with the ng-app
directive. The ng-app
will cause Angular to auto initialize your application.
+
+
<html ng-app>
+
+
+
We load Angular using the <script>
tag:
+
+
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/?.?.?/angular.min.js"></script>
+
+
+
From the ng-model
attribute of the <input>
tags, Angular automatically sets up two-way data
+binding, and we also demonstrate some easy input validation:
+
+
Quantity: <input type="integer" min="0" ng-model="qty" required >
+Cost: <input type="number" ng-model="cost" required >
+
+
+
These input widgets look normal enough, but consider these points:
+
+
+When this page loaded, Angular bound the names of the input widgets (qty
and cost
) to
+variables of the same name. Think of those variables as the "Model" component of the
+Model-View-Controller design pattern.
+Note that the HTML widget input
+has special powers. The input invalidates itself by turning red when you enter invalid data or
+leave the the input fields blank. These new widget behaviors make it easier to implement field
+validation common in CRUD applications.
+
+
+
And finally, the mysterious {{ double curly braces }}
:
+
+
Total: {{qty * cost | currency}}
+
+
+
This notation, {{ _expression_ }}
, is Angular markup for data-binding. The expression itself can
+be a combination of both an expression and a filter : {{
+expression | filter }}
. Angular provides filters for formatting display data.
+
+
In the example above, the expression in double-curly braces directs Angular to "bind the data we
+got from the input widgets to the display, multiply them together, and format the resulting number
+into output that looks like money."
+
+
Notice that we achieved this application behavior not by calling Angular methods, nor by
+implementing application specific behavior as a framework. We achieved the behavior because the
+browser behaved more in line with what is needed for a dynamic web application rather then what is
+needed for a static document. Angular has lowered the impedance mismatch to the point where no
+library/framework calls are needed.
+
+
The Zen of Angular
+
+
Angular is built around the belief that declarative code is better than imperative when it comes
+to building UIs and wiring software components together, while imperative code is excellent for
+expressing business logic.
+
+
+It is a very good idea to decouple DOM manipulation from app logic. This dramatically improves
+the testability of the code.
+It is a really, really good idea to regard app testing as equal in importance to app
+writing. Testing difficulty is dramatically affected by the way the code is structured.
+It is an excellent idea to decouple the client side of an app from the server side. This
+allows development work to progress in parallel, and allows for reuse of both sides.
+It is very helpful indeed if the framework guides developers through the entire journey of
+building an app: from designing the UI, through writing the business logic, to testing.
+It is always good to make common tasks trivial and difficult tasks possible.
+
+
+
Angular frees you from the following pain:
+
+
+Registering callbacks: Registering callbacks clutters your code, making it hard to see the
+forest for the trees. Removing common boilerplate code such as callbacks is a good thing. It
+vastly reduces the amount of JavaScript coding you have to do, and it makes it easier to see
+what your application does.
+Manipulating HTML DOM programmatically: Manipulating HTML DOM is a cornerstone of AJAX
+applications, but it's cumbersome and error-prone. By declaratively describing how the UI
+should change as your application state changes, you are freed from low level DOM manipulation
+tasks. Most applications written with Angular never have to programmatically manipulate the
+DOM, although you can if you want to.
+Marshaling data to and from the UI: CRUD operations make up the majority of AJAX
+applications. The flow of marshaling data from the server to an internal object to an HTML
+form, allowing users to modify the form, validating the form, displaying validation errors,
+returning to an internal model, and then back to the server, creates a lot of boilerplate
+code. Angular eliminates almost all of this boilerplate, leaving code that describes the
+overall flow of the application rather than all of the implementation details.
+Writing tons of initialization code just to get started: Typically you need to write a lot
+of plumbing just to get a basic "Hello World" AJAX app working. With Angular you can bootstrap
+your app easily using services, which are auto-injected into your application in a Guice -like dependency-injection style. This allows you
+to get started developing features quickly. As a bonus, you get full control over the
+initialization process in automated tests.
+
+
+
Watch a Presentation About Angular
+
+
Here is a presentation on Angular from May 2012.
+
+
VIDEO
diff --git a/lib/angular/docs/partials/guide/scope.html b/lib/angular/docs/partials/guide/scope.html
new file mode 100644
index 0000000..5ab6f40
--- /dev/null
+++ b/lib/angular/docs/partials/guide/scope.html
@@ -0,0 +1,328 @@
+
+
+
+What are Scopes?
+
+
scope
is an object that refers to the application
+model. It is an execution context for expressions . Scopes are
+arranged in hierarchical structure which mimic the DOM structure of the application. Scopes can
+watch expressions and propagate events.
+
+
Scope characteristics
+
+
+Scopes provide APIs ($watch
) to observe
+model mutations.
+Scopes provide APIs ($apply
) to
+propagate any model changes through the system into the view from outside of the "Angular
+realm" (controllers, services, Angular event handlers).
+Scopes can be nested to isolate application components while providing access to shared model
+properties. A scope (prototypically) inherits properties from its parent scope.
+Scopes provide context against which expressions are evaluated. For
+example {{username}}
expression is meaningless, unless it is evaluated against a specific
+scope which defines the username
property.
+
+
+
Scope as Data-Model
+
+
Scope is the glue between application controller and the view. During the template linking phase the directives
set up
+$watch
expressions on the scope. The
+$watch
allows the directives to be notified of property changes, which allows the directive to
+render the updated value to the DOM.
+
+
Both controllers and directives have reference to the scope, but not to each other. This
+arrangement isolates the controller from the directive as well as from DOM. This is an important
+point since it makes the controllers view agnostic, which greatly improves the testing story of
+the applications.
+
+
Source
+
+
Demo
+
+
+
In the above example notice that the MyController
assigns World
to the username
property of
+the scope. The scope then notifies the input
of the assignment, which then renders the input
+with username pre-filled. This demonstrates how a controller can write data into the scope.
+
+
Similarly the controller can assign behavior to scope as seen by the sayHello
method, which is
+invoked when the user clicks on the 'greet' button. The sayHello
method can read the username
+property and create a greeting
property. This demonstrates that the properties on scope update
+automatically when they are bound to HTML input widgets.
+
+
Logically the rendering of {{greeting}}
involves:
+
+
+retrieval of the scope associated with DOM node where {{greeting}}
is defined in template.
+In this example this is the same scope as the scope which was passed into MyController
. (We
+will discuss scope hierarchies later.)
+Evaluate the greeting
expression against the scope retrieved above,
+and assign the result to the text of the enclosing DOM element.
+
+
+
You can think of the scope and its properties as the data which is used to render the view. The
+scope is the single source-of-truth for all things view related.
+
+
From a testability point of view, the separation of the controller and the view is desirable, because it allows us
+to test the behavior without being distracted by the rendering details.
+
+
+ it('should say hello', function() {
+ var scopeMock = {};
+ var cntl = new MyController(scopeMock);
+
+ // Assert that username is pre-filled
+ expect(scopeMock.username).toEqual('World');
+
+ // Assert that we read new username and greet
+ scopeMock.username = 'angular';
+ scopeMock.sayHello();
+ expect(scopeMock.greeting).toEqual('Hello angular!');
+ });
+
+
+
Scope Hierarchies
+
+
Each Angular application has exactly one root scope
, but
+may have several child scopes.
+
+
The application can have multiple scopes, because some directives create
+new child scopes (refer to directive documentation to see which directives create new scopes).
+When new scopes are created, they are added as children of their parent scope. This creates a tree
+structure which parallels the DOM where they're attached
+
+
When Angular evaluates {{username}}
, it first looks at the scope associated with the given
+element for the username
property. If no such property is found, it searches the parent scope
+and so on until the root scope is reached. In JavaScript this behavior is known as prototypical
+inheritance, and child scopes prototypically inherit from their parents.
+
+
This example illustrates scopes in application, and prototypical inheritance of properties.
+
+
Source
+
+
Demo
+
+
+
Notice that Angular automatically places ng-scope
class on elements where scopes are
+attached. The <style>
definition in this example highlights in red the new scope locations. The
+child scopes are necessary because the repeater evaluates {{employee.name}}
expression, but
+depending on which scope the expression is evaluated it produces different result. Similarly the
+evaluation of {{department}}
prototypically inherits from root scope, as it is the only place
+where the department
property is defined.
+
+
Retrieving Scopes from the DOM.
+
+
Scopes are attached to the DOM as $scope
data property, and can be retrieved for debugging
+purposes. (It is unlikely that one would need to retrieve scopes in this way inside the
+application.) The location where the root scope is attached to the DOM is defined by the location
+of ng-app
directive. Typically
+ng-app
is placed an the <html>
element, but it can be placed on other elements as well, if,
+for example, only a portion of the view needs to be controlled by Angular.
+
+
To examine the scope in the debugger:
+
+
+right click on the element of interest in your browser and select 'inspect element'. You
+should see the browser debugger with the element you clicked on highlighted.
+The debugger allows you to access the currently selected element in the console as $0
+variable.
+To retrieve the associated scope in console execute: angular.element($0).scope()
+
+
+
Scope Events Propagation
+
+
Scopes can propagate events in similar fashion to DOM events. The event can be broadcasted
to the scope children or emitted
to scope parents.
+
+
Source
+
+
Demo
+
+
+
Scope Life Cycle
+
+
The normal flow of a browser receiving an event is that it executes a corresponding JavaScript
+callback. Once the callback completes the browser re-renders the DOM and returns to waiting for
+more events.
+
+
When the browser calls into JavaScript the code executes outside the Angular execution context,
+which means that Angular is unaware of model modifications. To properly process model
+modifications the execution has to enter the Angular execution context using the $apply
method. Only model modifications which
+execute inside the $apply
method will be properly accounted for by Angular. For example if a
+directive listens on DOM events, such as ng-click
it must evaluate the
+expression inside the $apply
method.
+
+
After evaluating the expression, the $apply
method performs a $digest
. In the $digest phase the scope examines all
+of the $watch
expressions and compares them with the previous value. This dirty checking is done
+asynchronously. This means that assignment such as $scope.username="angular"
will not
+immediately cause a $watch
to be notified, instead the $watch
notification is delayed until
+the $digest
phase. This delay is desirable, since it coalesces multiple model updates into one
+$watch
notification as well as it guarantees that during the $watch
notification no other
+$watch
es are running. If a $watch
changes the value of the model, it will force additional
+$digest
cycle.
+
+
+Creation
+
+The root scope
is created during the application
+ bootstrap by the $injector
. During template
+ linking, some directives create new child scopes.
+Watcher registration
+
+During template linking directives register watches
on the scope. These watches will be
+ used to propagate model values to the DOM.
+Model mutation
+
+For mutations to be properly observed, you should make them only within the scope.$apply()
. (Angular APIs do this
+ implicitly, so no extra $apply
call is needed when doing synchronous work in controllers,
+ or asynchronous work with $http
or $timeout
services.
+Mutation observation
+
+At the end $apply
, Angular performs a $digest
cycle on the root scope, which then propagates throughout all child scopes. During
+ the $digest
cycle, all $watch
ed expressions or functions are checked for model mutation
+ and if a mutation is detected, the $watch
listener is called.
+Scope destruction
+
+When child scopes are no longer needed, it is the responsibility of the child scope creator
+ to destroy them via scope.$destroy()
+ API. This will stop propagation of $digest
calls into the child scope and allow for memory
+ used by the child scope models to be reclaimed by the garbage collector.
+
+
+
Scopes and Directives
+
+
During the compilation phase, the compiler matches directives
against the DOM template. The directives
+usually fall into one of two categories:
+
+
+Observing directives
, such as
+double-curly expressions {{expression}}
, register listeners using the $watch()
method. This type of directive needs
+to be notified whenever the expression changes so that it can update the view.
+Listener directives, such as ng-click
, register a listener with the DOM. When the DOM listener fires, the directive
+executes the associated expression and updates the view using the $apply()
method.
+
+
+
When an external event (such as a user action, timer or XHR) is received, the associated expression must be applied to the scope through the $apply()
method so that all listeners are updated
+correctly.
+
+
Directives that Create Scopes
+
+
In most cases, directives
and scopes interact
+but do not create new instances of scope. However, some directives, such as ng-controller
and ng-repeat
, create new child scopes
+and attach the child scope to the corresponding DOM element. You can retrieve a scope for any DOM
+element by using an angular.element(aDomElement).scope()
method call.
+
+
Controllers and Scopes
+
+
Scopes and controllers interact with each other in the following situations:
+
+
+Controllers use scopes to expose controller methods to templates (see ng-controller
).
+Controllers define methods (behavior) that can mutate the model (properties on the scope).
+Controllers may register watches
on
+ the model. These watches execute immediately after the controller behavior executes.
+
+
+
See the ng-controller
for more
+information.
+
+
Scope $watch
Performance Considerations
+
+
Dirty checking the scope for property changes is a common operation in Angular and for this reason
+the dirty checking function must be efficient. Care should be taken that the dirty checking
+function does not do any DOM access, as DOM access is orders of magnitude slower then property
+access on JavaScript object.
diff --git a/lib/angular/docs/partials/guide/type.html b/lib/angular/docs/partials/guide/type.html
new file mode 100644
index 0000000..4024f38
--- /dev/null
+++ b/lib/angular/docs/partials/guide/type.html
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/lib/angular/docs/partials/misc/contribute.html b/lib/angular/docs/partials/misc/contribute.html
new file mode 100644
index 0000000..98f328b
--- /dev/null
+++ b/lib/angular/docs/partials/misc/contribute.html
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
+
License
+
+
AngularJS is an open source project licensed under the MIT license . Your contributions are
+always welcome. When working with AngularJS code base, please follow the guidelines provided on
+this page.
+
+
+
+
Contributing to Source Code
+
+
We'd love for you to contribute to our source code and to make AngularJS even better than it is
+today! Here are the guidelines we'd like you to follow:
+
+
+Major changes that you intend to contribute to the project should be discussed first on our mailing list so that we can better
+coordinate our efforts, prevent duplication of work, and help you to craft the change so that it
+is successfully accepted upstream.
+Small changes and bug fixes can be crafted and submitted to Github as a pull
+request .
+
+
+
+
+
Applying Code Standards
+
+
To ensure consistency throughout the source code, keep these rules in mind as you are working:
+
+
+All features or bug fixes must be tested by one or more specs .
+All public API methods must be documented with ngdoc, an extended version of jsdoc (we added
+support for markdown and templating via @ngdoc
tag). To see how we document our APIs, please
+check out the existing ngdocs.
+With the exceptions listed below, we follow the rules contained in Google's JavaScript Style Guide :
+
+Do not use namespaces: Instead, we wrap the entire angular
code base in an anonymous closure
+and export our API explicitly rather than implicitly.
+Wrap all code at 100 characters.
+Instead of complex inheritance hierarchies, we prefer simple objects. We use prototypical
+inheritance only when absolutely necessary.
+We love functions and closures and, whenever possible, prefer them over objects.
+To write concise code that can be better minified, internally we use aliases that map to the
+external API. See our existing code to see what we mean.
+We don't go crazy with type annotations for private internal APIs unless it's an internal API
+that is used throughout AngularJS. The best guidance is to do what makes the most sense.
+
+
+
+
+
Checking Out and Building Angular
+
+
The AngularJS source code is hosted at Github , which we also use to
+accept code contributions. The AngularJS repository can be found at https://github.com/angular/angular.js .
+
+
Several steps are needed to check out and build AngularJS:
+
+
Installation Dependencies
+
+
Before you can build AngularJS, you must install or configure the following dependencies on your
+machine:
+
+
+Rake : We use Rake as our build system, which is pre-installed
+on most Macintosh and Linux machines. If that is not true in your case, you can grab it from the
+Rake website.
+Git: The Github Guide to Installing Git is
+quite a good source for information on Git.
+Node.js : We use Node to generate the documentation and to run a
+development web server. Depending on your system, you can install Node either from source or as a
+pre-packaged bundle.
+
+Once installed, you'll also need several npms (node packages), which you can install once you checked out a local copy
+of the Angular repository (see below) with:
+
+cd angular.js
+npm install
+
+
+
Creating a Github Account and Forking Angular
+
+
To create a Github account, follow the instructions here .
+Afterwards, go ahead and fork the main angular repository .
+
+
Building AngularJS
+
+
To build AngularJS, you check out the source code and use Rake to generate the non-minified and
+minified AngularJS files:
+
+
+To clone your Github repository, run:
+
+git clone git@github.com:<github username>/angular.js.git
+
+To go to the AngularJS directory, run:
+
+cd angular.js
+
+To add the main AngularJS repository as an upstream remote to your repository, run:
+
+git remote add upstream https://github.com/angular/angular.js.git
+
+To add node.js dependencies
+
+npm install
+
+To build AngularJS, run:
+
+rake package
+
+
+
+
The build output can be located under the build
directory. It consists of the following files and
+directories:
+
+
+angular-<version>.zip
— This is the complete zip file, which contains all of the release build
+artifacts.
+angular.js
— The non-minified angular
script.
+angular.min.js
— The minified angular
script.
+angular-scenario.js
— The angular
End2End test runner.
+docs/
— A directory that contains all of the files needed to run docs.angularjs.org
.
+docs/index.html
— The main page for the documentation.
+docs/docs-scenario.html
— The End2End test runner for the documentation application.
+
+
+
+
+
Running a Local Development Web Server
+
+
To debug code and run end-to-end tests, it is often useful to have a local HTTP server. For this purpose, we have
+made available a local web server based on Node.js.
+
+
+To start the web server, run:
+
+rake webserver
+
+To access the local server, go to this website:
+
+http://localhost:8000/
+
+
+By default, it serves the contents of the AngularJS project directory.
+
+
+
+
+
Running the Unit Test Suite
+
+
Our unit and integration tests are written with Jasmine and executed with Testacular. To run all of the
+tests once on Chrome run:
+
+
rake test:unit
+
+
+
To run the tests on other browsers (Chrome, ChromeCanary, Firefox, Opera and Safari are pre-configured) use:
+
+
rake test:unit[Opera+Firefox]
+
+
+
During development it's however more productive to continuously run unit tests every time the source or test files
+change. To execute tests in this mode run:
+
+
+To start the Testacular server, capture Chrome browser and run unit tests, run:
+
+rake autotest:jqlite
+
+To capture more browsers, open this url in the desired browser (url might be different if you have multiple instance
+of Testacular running, read Testacular's console output for the correct url):
+
+http://localhost:9876/
+
+To re-run tests just change any source or test file.
+
+
+
To learn more about all of the preconfigured Rake tasks run:
+
+
rake -T
+
+
+
Running the end-to-end Test Suite
+
+
To run the E2E test suite:
+
+
+Start the local web server if it's not running already.
+
+rake webserver
+
+In a browser, go to:
+
+http://localhost:8000/build/docs/docs-scenario.html
+
+
+or in terminal run:
+
+rake test:e2e
+
+
+
+
+
+
Submitting Your Changes
+
+
To create and submit a change:
+
+
+Please sign our Contributor License Agreement (CLA) before sending pull requests. For any code changes to be
+accepted, the CLA must be signed. It's a quick process, we promise!
+
+For individuals we have a simple click-through form . For
+corporations we'll need you to
+print, sign and one of scan+email, fax or mail the form .
+Create a new branch off the master for your changes:
+
+git branch my-fix-branch
+
+Check out the branch:
+
+git checkout my-fix-branch
+
+Create your patch, make sure to have plenty of tests (that pass).
+Commit your changes and create a descriptive commit message (the commit message is used to generate release notes,
+please check out our
+commit message conventions
+and our commit message presubmit hook validate-commit-msg.js
):
+
+git commit -a
+
+Push your branch to Github:
+
+git push origin my-fix-branch
+
+In Github, send a pull request to angular:master
.
+When the patch is reviewed and merged, delete your branch and pull yours — and other — changes
+from the main (upstream) repository:
+
+To delete the branch in Github, run:
+
+git push origin :my-fix-branch
+
+To check out the master branch, run:
+
+git checkout master
+
+To delete a local branch, run:
+
+git branch -D my-fix-branch
+
+To update your master with the latest upstream version, run:
+
+git pull --ff upstream master
+
+
+
+
That's it! Thank you for your contribution!
diff --git a/lib/angular/docs/partials/misc/downloading.html b/lib/angular/docs/partials/misc/downloading.html
new file mode 100644
index 0000000..8fcae43
--- /dev/null
+++ b/lib/angular/docs/partials/misc/downloading.html
@@ -0,0 +1,73 @@
+
+
+
+Including angular scripts from the Google CDN
+
+
The quickest way to get started is to point your html <script>
tag to a Google CDN URL.
+This way, you don't have to download anything or maintain a local copy.
+
+
There are two types of angular script URLs you can point to, one for development and one for
+production:
+
+
+angular-.js — This is the human-readable, non-minified version, suitable for web
+development.
+angular-.min.js — This is the minified version, which we strongly suggest you use in
+production.
+
+
+
To point your code to an angular script on the Google CDN server, use the following template. This
+example points to the minified version 1.0.2:
+
+
+ <!doctype html>
+ <html ng-app>
+ <head>
+ <title>My Angular App</title>
+ <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
+ </head>
+ <body>
+ </body>
+ </html>
+
+
+
Note that only versions 1.0.1 and above are available on the CDN, if you need an earlier version
+you can use the http://code.angularjs.org/ URL which was the previous recommended location for
+hosted code source. If you're still using the angular server you should switch to the CDN version
+for even faster loading times.
+
+
Downloading and hosting angular files locally
+
+
This option is for those who want to work with angular offline, or those who want to host the
+angular files on their own servers.
+
+
If you navigate to http://code.angularjs.org/ , you'll see a directory listing with all of the
+angular versions since we started releasing versioned build artifacts (quite late in the project
+lifetime). Each directory contains all artifacts that we released for a particular version.
+Download the version you want and have fun.
+
+
Each directory under http://code.angularjs.org/ includes the following set of files:
+
+
+angular.js
— This file is non-obfuscated, non-minified, and human-readable by
+opening it it any editor or browser. In order to get better error messages during development, you
+should always use this non-minified angular script.
+angular.min.js
— This is a minified and obfuscated version of
+angular.js
created with the Closure compiler. Use this version for production in order
+to minimize the size of the application that is downloaded by your user's browser.
+angular.zip
— This is a zip archive that contains all of the files released
+for this angular version. Use this file to get everything in a single download.
+angular-mocks.js
— This file contains an implementation of mocks that makes
+testing angular apps even easier. Your unit/integration test harness should load this file after
+angular-<version>.js
is loaded.
+angular-scenario.js
— This file is a very nifty JavaScript file that allows you
+to write and execute end-to-end tests for angular applications.
+angular-loader.min.js
— Module loader for Angular modules. If you are loading multiple script files containing
+Angular modules, you can load them asynchronosuly and in any order as long as you load this file first. Often the
+contents of this file are copy&pasted into the index.html
to avoid even the inial request to angular-loader.min.js
.
+See angular-seed for an example of usage.
+angular-resource.js
, angular-cookies.js
, etc - extra Angular modules with additional functionality.
+docs
— this directory contains all the files that compose the
+http://docs.angularjs.org/ documentation app. These files are handy to see the older version of
+our docs, or even more importantly, view the docs offline.
+
diff --git a/lib/angular/docs/partials/misc/faq.html b/lib/angular/docs/partials/misc/faq.html
new file mode 100644
index 0000000..10cf551
--- /dev/null
+++ b/lib/angular/docs/partials/misc/faq.html
@@ -0,0 +1,206 @@
+
+
+
+FAQ
+
+
Questions
+
+
Why is this project called "AngularJS"? Why is the namespace called "ng"?
+
+
Because HTML has Angular brackets and "ng" sounds like "Angular".
+
+
Is AngularJS a library, framework, plugin or a browser extension?
+
+
AngularJS fits the definition of a framework the best, even though it's much more lightweight than
+a typical framework and that's why many confuse it with a library.
+
+
AngularJS is 100% JavaScript, 100% client side and compatible with both desktop and mobile browsers.
+So it's definitely not a plugin or some other native browser extension.
+
+
Is AngularJS a templating system?
+
+
At the highest level, Angular does look like a just another templating system. But there is one
+important reason why the Angular templating system is different, that makes it very good fit for
+application development: bidirectional data binding. The template is compiled in the browser and
+the compilation step produces a live view. This means you, the developers, don't need to write
+code to constantly sync the view with the model and the model with the view as in other
+templating systems.
+
+
Do I need to worry about security holes in AngularJS?
+
+
Like any other technology, AngularJS is not impervious to attack. Angular does, however, provide
+built-in protection from basic security holes including cross-site scripting and HTML injection
+attacks. AngularJS does round-trip escaping on all strings for you and even offers XSRF protection
+for server-side communication.
+
+
AngularJS was designed to be compatible with other security measures like Content Security Policy
+(CSP), HTTPS (SSL/TLS) and server-side authentication and authorization that greatly reduce the
+possible attack vectors and we highly recommended their use.
+
+
Can I download the source, build, and host the AngularJS environment locally?
+
+
Yes. See instructions in downloading .
+
+
What browsers does Angular work with?
+
+
We run our extensive test suite against the following browsers: Safari, Chrome, Firefox, Opera,
+IE8, IE9 and mobile browsers (Android, Chrome Mobile, iOS Safari).
+
+
What's Angular's performance like?
+
+
The startup time heavily depends on your network connection, state of the cache, browser used and
+available hardware, but typically we measure bootstrap time in tens or hundreds of milliseconds.
+
+
The runtime performance will vary depending on the number and complexity of bindings on the page
+as well as the speed of your backend (for apps that fetch data from the backend). Just for an
+illustration we typically build snappy apps with hundreds or thousands of active bindings.
+
+
How big is the angular.js file that I need to include?
+
+
The size of the file is < 29KB compressed and minified.
+
+
Can I use the open-source Closure Library with Angular?
+
+
Yes, you can use widgets from the Closure Library
+in Angular.
+
+
Does Angular use the jQuery library?
+
+
Yes, Angular can use jQuery if it's present in your app when the
+application is being bootstrapped. If jQuery is not present in your script path, Angular falls back
+to its own implementation of the subset of jQuery that we call jQLite
.
+
+
What is testability like in Angular?
+
+
Very testable and designed this way from ground up. It has an integrated dependency injection
+framework, provides mocks for many heavy dependencies (server-side communication). See
+service for details.
+
+
How can I learn more about Angular?
+
+
Watch the July 17, 2012 talk
+"AngularJS Intro + Dependency Injection ".
+
+
How is Angular licensed?
+
+
The MIT License.
+
+
Can I download and use the Angular logo artwork?
+
+
Yes! You can find design files in our github repository, under "angular.js/images/logo "
+The logo design is licensed under a "Creative Commons Attribution-ShareAlike 3.0 Unported License ". If you have some other use in mind, contact us.
+
+
How can I get some AngularJS schwag?
+
+
We often bring a few t-shirts and stickers to events where we're presenting. If you want to order your own, the folks who
+make our schwag will be happy to do a custom run for you, based on our existing template. By using the design they have on file,
+they'll waive the setup costs, and you can order any quantity you need.
+
+
Stickers
+Contact Tom Witting (or anyone in sales) via email at tom@stickergiant.com, and tell him you want to order some AngularJS
+stickers just like the ones in job #42711. You'll have to give them your own info for billing and shipping.
+
+
As long as the design stays exactly the same, StickerGiant will give you a reorder discount.
+
+
T-shirts
+Contact sales at www.customink.com and tell them you want some shirts with design name "angularjs",
+just like past order #2106371. You'll have to give them your own info for billing and shipping.
+
+
As long as the design stays exactly the same, CustomInk won't charge for any set up fees, and they'll give you a reorder discount.
+
+
Common Pitfalls
+
+
The Angular support channel (#angularjs on Freenode) sees a number of recurring pitfalls that new users of Angular fall into.
+This document aims to point them out before you discover them the hard way.
+
+
DOM Manipulation
+
+
Stop trying to use jQuery to modify the DOM in controllers. Really.
+That includes adding elements, removing elements, retrieving their contents, showing and hiding them.
+Use built-in directives, or write your own where necessary, to do your DOM manipulation.
+See below about duplicating functionality.
+
+
If you're struggling to break the habit, consider removing jQuery from your app.
+Really. Angular has the $http service and powerful directives that make it almost always unnecessary.
+Angular's bundled jQLite has a handful of the features most commonly used in writing Angular directives, especially binding to events.
+
+
Trying to duplicate functionality that already exists
+
+
There's a good chance that your app isn't the first to require certain functionality.
+There are a few pieces of Angular that are particularly likely to be reimplemented out of old habits.
+
+
ng-repeat
+
+
ng-repeat
gets this a lot.
+People try to use jQuery (see above) to add more elements to some container as they're fetched from the server.
+No, bad dog.
+This is what ng-repeat
is for, and it does its job very well.
+Store the data from the server in an array on your $scope
, and bind it to the DOM with ng-repeat
.
+
+
ng-show
+
+
ng-show
gets this frequently too.
+Conditionally showing and hiding things using jQuery is a common pattern in other apps, but Angular has a better way.
+ng-show
(and ng-hide
) conditionally show and hide elements based on boolean expressions.
+Describe the conditions for showing and hiding an element in terms of $scope
variables:
+
+
<div ng-show="!loggedIn">Click <a href="#/login">here</a> to log in</div>
+
+
+
Note also the counterpart ng-hide
and similar ng-disabled
.
+Note especially the powerful ng-switch
that should be used instead of several mutually exclusive ng-show
s.
+
+
ng-class
+
+
ng-class
is the last of the big three.
+Conditionally applying classes to elements is another thing commonly done manually using jQuery.
+Angular, of course, has a better way.
+You can give ng-class
a whitespace-separated set of class names, and then it's identical to ordinary class
.
+That's not very exciting, so there's a second syntax:
+
+
<div ng-class="{ errorClass: isError, warningClass: isWarning, okClass: !isError && !isWarning }">...</div>
+
+
+
Where you give ng-class
an object, whose keys are CSS class names and whose values are conditional expressions using $scope
variables.
+The element will then have all the classes whose conditions are truthy, and none of those whose conditions are falsy.
+
+
Note also the handy ng-class-even
and ng-class-odd
, and the related though somewhat different ng-style
.
+
+
$watch
and $apply
+
+
Angular's two-way data binding is the root of all awesome in Angular.
+However, it's not magic, and there are some situations where you need to give it a nudge in the right direction.
+
+
When you bind a value to an element in Angular using ng-model
, ng-repeat
, etc., Angular creates a $watch
on that value.
+Then whenever a value on a scope changes, all $watch
es observing that element are executed, and everything updates.
+
+
Sometimes, usually when you're writing a custom directive, you will have to define your own $watch
on a scope value to make the directive react to changes.
+
+
On the flip side, sometimes you change a scope value in some code but the app doesn't react to it.
+Angular checks for scope variable changes after pieces of your code have finished running; for example, when ng-click
calls a function on your scope, Angular will check for changes and react.
+However, some code is outside of Angular and you'll have to call scope.$apply()
yourself to trigger the update.
+This is most commonly seen in event handlers in custom directives.
+
+
Combining ng-repeat
with other directives
+
+
ng-repeat
is extremely useful, one of the most powerful directives in Angular.
+However the transformation it applies to the DOM is substantial.
+Therefore applying other directives (such as ng-show
, ng-controller
and others) to the same element as ng-repeat
generally leads to problems.
+
+
If you want to apply a directive to the whole repeat, wrap the repeat in a parent element and put it there.
+If you want to apply a directive to each inner piece of the repeat, put it on a child of the element with ng-repeat
.
+
+
$rootScope
exists, but it can be used for evil
+
+
Scopes in Angular form a hierarchy, prototypically inheriting from a root scope at the top of the tree.
+Usually this can be ignored, since most views have a controller, and therefore a scope, of their own.
+
+
Occasionally there are pieces of data that you want to make global to the whole app.
+For these, you can inject $rootScope
and set values on it like any other scope.
+Since the scopes inherit from the root scope, these values will be available to the expressions attached to directives like ng-show
just like values on your local $scope
.
+
+
Of course, global state sucks and you should use $rootScope
sparingly, like you would (hopefully) use with global variables in any language.
+In particular, don't use it for code, only data.
+If you're tempted to put a function on $rootScope
, it's almost always better to put it in a service that can be injected where it's needed, and more easily tested.
+
+
Conversely, don't create a service whose only purpose in life is to store and return bits of data.
diff --git a/lib/angular/docs/partials/misc/started.html b/lib/angular/docs/partials/misc/started.html
new file mode 100644
index 0000000..9a1915b
--- /dev/null
+++ b/lib/angular/docs/partials/misc/started.html
@@ -0,0 +1,40 @@
+
+
+
+We want you to have an easy time while starting to use Angular. We've put together the following steps on your path to
+becoming an Angular expert.
+
+
+Read the conceptual overview . Understand Angular's vocabulary and how all the Angular
+components work together.
+Do the AngularJS Tutorial . Walk end-to-end through building and application complete with tests
+on top of a node.js web server. Covers every major AngularJS feature and show you how to set up your development
+environment.
+Download or clone the Seed App project template . Gives you a
+starter app with a directory layout, test harness, and scripts to begin building your application.
+
+
+
Further Steps
+
+
Watch Videos
+
+
If you haven’t had a chance to watch the videos from the homepage, please check out:
+* Introduction to AngularJS
+* Creating Directives
+* Communicating with Servers
+
+
And visit our YouTube channel for more AngularJS video presentations and
+tutorials.
+
+
Subscribe
+
+
+
+
Read more
+
+
The AngularJS documentation includes the Developer Guide covering concepts and the
+API Reference for syntax and usage.
diff --git a/lib/angular/docs/partials/tutorial/index.html b/lib/angular/docs/partials/tutorial/index.html
new file mode 100644
index 0000000..f553326
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/index.html
@@ -0,0 +1,112 @@
+
+
+
+A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
+the construction of an AngularJS web app. The app you will build is a catalog that displays a list
+of Android devices, lets you filter the list to see only devices that interest you, and then view
+details for any device.
+
+
+
+
Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
+or plug-ins. As you work through the tutorial, you will:
+
+
+See examples of how to use client-side data binding and dependency injection to build dynamic
+views of data that change immediately in response to user actions.
+See how Angular creates listeners on your data without the need for DOM manipulation.
+Learn a better, easier way to test your web apps.
+Learn how to use Angular services to make common web tasks, such as getting data into your app,
+easier.
+
+
+
And all of this works in any browser without modification to the browser!
+
+
When you finish the tutorial you will be able to:
+
+
+Create a dynamic application that works in any browser.
+Define the differences between Angular and common JavaScript frameworks.
+Understand how data binding works in AngularJS.
+Use the angular-seed project to quickly boot-strap your own projects.
+Create and run tests.
+Identify resources for learning more about AngularJS.
+
+
+
The tutorial guides you through the entire process of building a simple application, including
+writing and running unit and end-to-end tests. Experiments at the end of each step provide
+suggestions for you to learn more about AngularJS and the application you are building.
+
+
You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day
+really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
+Getting Started document.
+
+
Working with the code
+
+
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
+environment. The tutorial relies on the use of Git versioning system for source code management.
+You don't need to know anything about Git to follow the tutorial. Select one of the tabs below
+and follow the instructions for setting up your computer.
+
+
+
+
+ You will need Node.js and Testacular to run unit tests, so please verify that you have
+ Node.js v0.8 or better installed
+ and that the node
executable is on your PATH
by running the following
+ command in a terminal window:
+ node --version
+ Additionally install Testacular if you
+ don't have it already:
+ npm install -g testacular
+ You'll also need Git, which you can get from
+ the Git site .
+ Clone the angular-phonecat repository located at Github by running the following command:
+ git clone git://github.com/angular/angular-phonecat.git
+ This command creates the angular-phonecat
directory in your current
+directory.
+ Change your current directory to angular-phonecat
:
+ cd angular-phonecat
+ The tutorial instructions assume you are running all commands from the angular-phonecat
+directory.
+ You will need an http server running on your system. Mac and Linux machines typically
+have Apache pre-installed, but If you don't already have one installed, you can use node
+to run scripts/web-server.js
, a simple bundled http server.
+
+
+
+
+
+ You will need Node.js and Testacular to run unit tests, so please verify that you have
+ Node.js v0.8 or better installed
+ and that the node
executable is on your PATH
by running the following
+ command in a terminal window:
+ node --version
+ Additionally install Testacular if you
+ don't have it already:
+ npm install -g testacular
+
+ You'll also need Git, which you can get from
+ the Git site .
+ Clone the angular-phonecat repository located at Github by running the following command:
+ git clone git://github.com/angular/angular-phonecat.git
+ This command creates the angular-phonecat directory in your current directory.
+ Change your current directory to angular-phonecat.
+ cd angular-phonecat
+ The tutorial instructions assume you are running all commands from the angular-phonecat
+directory.
+ You should run all git
commands from Git bash.
+ Other commands like test.bat
or e2e-test.bat
should be
+executed from the Windows command line.
+ You need an http server running on your system, but if you don't already have one
+already installed, you can use node
to run scripts\web-server.js
, a simple
+bundled http server.
+
+
+
+
The last thing to do is to make sure your computer has a web browser and a good text editor
+installed. Now, let's get some cool stuff done!
+
+
Get Started!
diff --git a/lib/angular/docs/partials/tutorial/step_00.html b/lib/angular/docs/partials/tutorial/step_00.html
new file mode 100644
index 0000000..b47387b
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_00.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+
You are now ready to build the AngularJS phonecat app. In this step, you will become familiar
+with the most important source code files, learn how to start the development servers bundled with
+angular-seed, and run the application in the browser.
+
+
+
+
+ In angular-phonecat directory, run this command:
+ git checkout -f step-0
+ This resets your workspace to step 0 of the tutorial app.
+ You must repeat this for every future step in the tutorial and change the number to
+ the number of the step you are on. This will cause any changes you made within
+ your working directory to be lost.
+
+ To see the app running in a browser, do one of the following:
+
+ For node.js users:
+
+ In a separate terminal tab or window, run
+./scripts/web-server.js
to start the web server.
+ Open a browser window for the app and navigate to http://localhost:8000/app/index.html
+
+
+ For other http servers:
+
+ Configure the server to serve the files in the angular-phonecat
+directory.
+ Navigate in your browser to
+http://localhost:[port-number]/[context-path]/app/index.html
.
+
+
+
+
+
+
+
+
+
+
+ Open Git bash and run this command (in angular-phonecat directory):
+ git checkout -f step-0
+ This resets your workspace to step 0 of the tutorial app.
+ You must repeat this for every future step in the tutorial and change the number to
+ the number of the step you are on. This will cause any changes you made within
+ your working directory to be lost.
+ To see the app running in a browser, do one of the following:
+
+ For node.js users:
+
+ In a separate terminal tab or window, run node
+scripts\web-server.js
to start the web server.
+ Open a browser window for the app and navigate to http://localhost:8000/app/index.html
+
+
+ For other http servers:
+
+ Configure the server to serve the files in the angular-phonecat
+directory.
+ Navigate in your browser to
+http://localhost:[port-number]/[context-path]/app/index.html
.
+
+
+
+
+
+
+
+
+
You can now see the page in your browser. It's not very exciting, but that's OK.
+
+
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
+The code contains some key Angular elements that we will need going forward.
+
+
app/index.html
:
+
+<!doctype html>
+<html lang="en" ng-app>
+<head>
+ <meta charset="utf-8">
+ <title>My HTML File</title>
+ <link rel="stylesheet" href="css/app.css">
+ <link rel="stylesheet" href="css/bootstrap.css">
+ <script src="lib/angular/angular.js"></script>
+</head>
+<body>
+
+ <p>Nothing here {{'yet' + '!'}}</p>
+
+</body>
+</html>
+
+
+
What is the code doing?
+
+
+ng-app
directive:
+
+ <html ng-app>
+
+
+The ng-app
attribute is represents an Angular directive (named ngApp
; Angular uses
+name-with-dashes
for attribute names and camelCase
for the corresponding directive name)
+used to flag an element which Angular should consider to be the root element of our application.
+This gives application developers the freedom to tell Angular if the entire html page or only a
+portion of it should be treated as the Angular application.
+AngularJS script tag:
+
+ <script src="lib/angular/angular.js">
+
+
+This code downloads the angular.js
script and registers a callback that will be executed by the
+browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
+looks for the ngApp
directive. If
+Angular finds the directive, it will bootstrap the application with the root of the application DOM
+being the element on which the ngApp
directive was defined.
+Double-curly binding with an expression:
+
+ Nothing here {{'yet' + '!'}}`
+
+
+This line demonstrates the core feature of Angular's templating capabilities – a binding, denoted
+by double-curlies {{ }}
as well as a simple expression 'yet' + '!'
used in this binding.
+
+The binding tells Angular that it should evaluate an expression and insert the result into the
+DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
+binding will result in efficient continuous updates whenever the result of the expression
+evaluation changes.
+
+Angular expression is a JavaScript-like code snippet that is
+evaluated by Angular in the context of the current model scope, rather than within the scope of
+the global context (window
).
+
+As expected, once this template is processed by Angular, the html page contains the text:
+"Nothing here yet!".
+
+
+
Bootstrapping AngularJS apps
+
+
Bootstrapping AngularJS apps automatically using the ngApp
directive is very easy and suitable
+for most cases. In advanced cases, such as when using script loaders, you can use
+imperative / manual way to bootstrap the app.
+
+
There are 3 important things that happen during the app bootstrap:
+
+
+The injector
that will be used for dependency injection
+within this app is created.
+The injector will then create the root scope
that will
+become the context for the model of our application.
+Angular will then "compile" the DOM starting at the ngApp
root element, processing any
+directives and bindings found along the way.
+
+
+
Once an application is bootstrapped, it will then wait for incoming browser events (such as mouse
+click, key press or incoming HTTP response) that might change the model. Once such an event occurs,
+Angular detects if it caused any model changes and if changes are found, Angular will reflect them
+in the view by updating all of the affected bindings.
+
+
The structure of our application is currently very simple. The template contains just one directive
+and one static binding, and our model is empty. That will soon change!
+
+
+
+
What are all these files in my working directory?
+
+
Most of the files in your working directory come from the angular-seed project which is typically used to bootstrap
+new Angular projects. The seed project includes the latest Angular libraries, test libraries,
+scripts and a simple example app, all pre-configured for developing a typical web app.
+
+
For the purposes of this tutorial, we modified the angular-seed with the following changes:
+
+
+Removed the example app
+Added phone images to app/img/phones/
+Added phone data files (JSON) to app/phones/
+Added Bootstrap files to app/css/
and app/img/
+
+
+
Experiments
+
+
+
+
Summary
+
+
Now let's go to step 1 and add some content to the web app.
+
+
+
+
+Note: During the bootstrap the injector and the root scope will then be associated with the
+ element on which the `ngApp` directive was declared, so when debugging the app you can retrieve
+ them from browser console via `angular.element(rootElement).scope()` and
+ `angular.element(rootElement).injector()`.
+
diff --git a/lib/angular/docs/partials/tutorial/step_01.html b/lib/angular/docs/partials/tutorial/step_01.html
new file mode 100644
index 0000000..38b49d3
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_01.html
@@ -0,0 +1,50 @@
+
+
+
+
+
+
In order to illustrate how Angular enhances standard HTML, you will create a purely static HTML
+page and then examine how we can turn this HTML code into a template that Angular will use to
+dynamically display the same result with any set of data.
+
+
In this step you will add some basic information about two cell phones to an HTML page.
+
+
+
+
+
The page now contains a list with information about two phones.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
app/index.html
:
+
+ <ul>
+ <li>
+ <span>Nexus S</span>
+ <p>
+ Fast just got faster with Nexus S.
+ </p>
+ </li>
+ <li>
+ <span>Motorola XOOM™ with Wi-Fi</span>
+ <p>
+ The Next, Next Generation tablet.
+ </p>
+ </li>
+ </ul>
+
+
+
Experiments
+
+
+
+
Summary
+
+
This addition to your app uses static HTML to display the list. Now, let's go to step 2 to learn how to use AngularJS to dynamically generate the same list.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_02.html b/lib/angular/docs/partials/tutorial/step_02.html
new file mode 100644
index 0000000..58b6a80
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_02.html
@@ -0,0 +1,194 @@
+
+
+
+
+
+
Now it's time to make the web page dynamic — with AngularJS. We'll also add a test that verifies the
+code for the controller we are going to add.
+
+
There are many ways to structure the code for an application. For Angular apps, we encourage the
+use of the Model-View-Controller (MVC) design pattern to decouple the code and to separate concerns. With that in mind, let's use a
+little Angular and JavaScript to add model, view, and controller components to our app.
+
+
+
+
+
The app now contains a list with three phones.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
View and Template
+
+
In Angular, the view is a projection of the model through the HTML template . This means that
+whenever the model changes, Angular refreshes the appropriate binding points, which updates the
+view.
+
+
The view component is constructed by Angular from this template:
+
+
app/index.html
:
+
+<html ng-app>
+<head>
+ ...
+ <script src="lib/angular/angular.js"></script>
+ <script src="js/controllers.js"></script>
+</head>
+<body ng-controller="PhoneListCtrl">
+
+ <ul>
+ <li ng-repeat="phone in phones">
+ {{phone.name}}
+ <p>{{phone.snippet}}</p>
+ </li>
+ </ul>
+</body>
+</html>
+
+
+
We replaced the hard-coded phone list with the
+ngRepeat directive
and two
+Angular expressions enclosed in curly braces:
+{{phone.name}}
and {{phone.snippet}}
:
+
+
+The ng-repeat="phone in phones"
statement in the <li>
tag is an Angular repeater. The
+repeater tells Angular to create a <li>
element for each phone in the list using the first <li>
+tag as the template.
+As we've learned in step 0, the curly braces around phone.name
and phone.snippet
denote
+bindings. As opposed to evaluating constants, these expressions are referring to our application
+model, which was set up in our PhoneListCtrl
controller.
+
+
+
+
+
Model and Controller
+
+
The data model (a simple array of phones in object literal notation) is instantiated within
+the PhoneListCtrl
controller :
+
+
app/js/controllers.js
:
+
+function PhoneListCtrl($scope) {
+ $scope.phones = [
+ {"name": "Nexus S",
+ "snippet": "Fast just got faster with Nexus S."},
+ {"name": "Motorola XOOM™ with Wi-Fi",
+ "snippet": "The Next, Next Generation tablet."},
+ {"name": "MOTOROLA XOOM™",
+ "snippet": "The Next, Next Generation tablet."}
+ ];
+}
+
+
+
Although the controller is not yet doing very much controlling, it is playing a crucial role. By
+providing context for our data model, the controller allows us to establish data-binding between
+the model and the view. We connected the dots between the presentation, data, and logic components
+as follows:
+
+
+PhoneListCtrl
— the name of our controller function (located in the JavaScript file
+controllers.js
), matches the value of the
+ngController
directive located
+on the <body>
tag.
+The phone data is then attached to the scope ($scope
) that was injected into our controller
+function. The controller scope is a prototypical descendant of the root scope that was created
+when the application bootstrapped. This controller scope is available to all bindings located within
+the <body ng-controller="PhoneListCtrl">
tag.
+
+The concept of a scope in Angular is crucial; a scope can be seen as the glue which allows the
+template, model and controller to work together. Angular uses scopes, along with the information
+contained in the template, data model, and controller, to keep models and views separate, but in
+sync. Any changes made to the model are reflected in the view; any changes that occur in the view
+are reflected in the model.
+
+To learn more about Angular scopes, see the angular scope documentation
.
+
+
+
Tests
+
+
The "Angular way" makes it easy to test code as it is being developed. Take a look at the following
+unit test for your newly created controller:
+
+
test/unit/controllersSpec.js
:
+
+describe('PhoneCat controllers', function() {
+
+ describe('PhoneListCtrl', function(){
+
+ it('should create "phones" model with 3 phones', function() {
+ var scope = {},
+ ctrl = new PhoneListCtrl(scope);
+
+ expect(scope.phones.length).toBe(3);
+ });
+ });
+});
+
+
+
The test verifies that we have three records in the phones array and the example demonstrates how
+easy it is to create a unit test for code in Angular. Since testing is such a critical part of
+software development, we make it easy to create tests in Angular so that developers are encouraged
+to write them.
+
+
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
+writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
+this tutorial in Jasmine. You can learn about Jasmine on the Jasmine home page and on the Jasmine wiki .
+
+
The angular-seed project is pre-configured to run all unit tests using Testacular . To run the test, do the following:
+
+
+In a separate terminal window or tab, go to the angular-phonecat
directory and run
+./scripts/test.sh
to start the Testacular server.
+Testacular will start a new instance of Chrome browser automatically. Just ignore it and let it run in
+the background. Testacular will use this browser for test execution.
+You should see the following or similar output in the terminal:
+
+ info: Testacular server started at http://localhost:9876/
+ info (launcher): Starting browser "Chrome"
+ info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
+ Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
+
+
+Yay! The test passed! Or not...
+To rerun the tests, just change any of the source or test files. Testacular will notice the change
+and will rerun the tests for you. Now isn't that sweet?
+
+
+
Experiments
+
+
+Add another binding to index.html
. For example:
+
+ <p>Total number of phones: {{phones.length}}</p>
+
+Create a new model property in the controller and bind to it from the template. For example:
+
+ $scope.hello = "Hello, World!"
+
+
+Refresh your browser to make sure it says, "Hello, World!"
+Create a repeater that constructs a simple table:
+
+ <table>
+ <tr><th>row number</th></tr>
+ <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
+ </table>
+
+
+Now, make the list 1-based by incrementing i
by one in the binding:
+
+ <table>
+ <tr><th>row number</th></tr>
+ <tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
+ </table>
+
+Make the unit test fail by changing the toBe(3)
statement to toBe(4)
.
+
+
+
Summary
+
+
You now have a dynamic app that features separate model, view, and controller components, and you
+are testing as you go. Now, let's go to step 3 to learn how to add full text search
+to the app.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_03.html b/lib/angular/docs/partials/tutorial/step_03.html
new file mode 100644
index 0000000..1c67b1e
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_03.html
@@ -0,0 +1,196 @@
+
+
+
+
+
+
We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
+simple; we will add full text search (yes, it will be simple!). We will also write an end-to-end
+test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it,
+and quickly detects regressions.
+
+
+
+
+
The app now has a search box. Notice that the phone list on the page changes depending on what a
+user types into the search box.
+
+
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
+GitHub :
+
+
Controller
+
+
We made no changes to the controller.
+
+
Template
+
+
app/index.html
:
+
+ <div class="container-fluid">
+ <div class="row-fluid">
+ <div class="span2">
+ <!--Sidebar content-->
+
+ Search: <input ng-model="query">
+
+ </div>
+ <div class="span10">
+ <!--Body content-->
+
+ <ul class="phones">
+ <li ng-repeat="phone in phones | filter:query">
+ {{phone.name}}
+ <p>{{phone.snippet}}</p>
+ </li>
+ </ul>
+
+ </div>
+ </div>
+ </div>
+
+
+
We added a standard HTML <input>
tag and used Angular's
+$filter
function to process the input for the
+ngRepeat
directive.
+
+
This lets a user enter search criteria and immediately see the effects of their search on the phone
+list. This new code demonstrates the following:
+
+
+Data-binding: This is one of the core features in Angular. When the page loads, Angular binds the
+name of the input box to a variable of the same name in the data model and keeps the two in sync.
+
+In this code, the data that a user types into the input box (named query
) is immediately
+available as a filter input in the list repeater (phone in phones | filter:
query
). When
+changes to the data model cause the repeater's input to change, the repeater efficiently updates
+the DOM to reflect the current state of the model.
+
+
+Use of the filter
filter: The filter
function uses the
+query
value to create a new array that contains only those records that match the query
.
+
+ngRepeat
automatically updates the view in response to the changing number of phones returned
+by the filter
filter. The process is completely transparent to the developer.
+
+
+
Test
+
+
In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
+controllers and other components of our application written in JavaScript, but they can't easily
+test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much
+better choice.
+
+
The search feature was fully implemented via templates and data-binding, so we'll write our first
+end-to-end test, to verify that the feature works.
+
+
test/e2e/scenarios.js
:
+
+describe('PhoneCat App', function() {
+
+ describe('Phone list view', function() {
+
+ beforeEach(function() {
+ browser().navigateTo('../../app/index.html');
+ });
+
+
+ it('should filter the phone list as user types into the search box', function() {
+ expect(repeater('.phones li').count()).toBe(3);
+
+ input('query').enter('nexus');
+ expect(repeater('.phones li').count()).toBe(1);
+
+ input('query').enter('motorola');
+ expect(repeater('.phones li').count()).toBe(2);
+ });
+ });
+});
+
+
+
Even though the syntax of this test looks very much like our controller unit test written with
+Jasmine, the end-to-end test uses APIs of Angular's end-to-end test runner .
+
+
To run the end-to-end test, open one of the following in a new browser tab:
+
+
+
+
Previously we've seen how Testacular can be used to execute unit tests. Well, it can also run the
+end-to-end tests! Use ./scripts/e2e-test.sh
script for that. End-to-end tests are slow, so unlike
+with unit tests, Testacular will exit after the test run and will not automatically rerun the test
+suite on every file change. To rerun the test suite, execute the e2e-test.sh
script again.
+
+
This test verifies that the search box and the repeater are correctly wired together. Notice how
+easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
+really is that easy to set up any functional, readable, end-to-end test.
+
+
Experiments
+
+
+Display the current value of the query
model by adding a {{query}}
binding into the
+index.html
template, and see how it changes when you type in the input box.
+Let's see how we can get the current value of the query
model to appear in the HTML page title.
+
+You might think you could just add the {{query}} to the title tag element as follows:
+
+ <title>Google Phone Gallery: {{query}}</title>
+
+
+However, when you reload the page, you won't see the expected result. This is because the "query"
+model lives in the scope defined by the body element:
+
+ <body ng-controller="PhoneListCtrl">
+
+
+If you want to bind to the query model from the <title>
element, you must move the
+ngController
declaration to the HTML element because it is the common parent of both the body
+and title elements:
+
+ <html ng-app ng-controller="PhoneListCtrl">
+
+
+Be sure to remove the ng-controller
declaration from the body element.
+
+While using double curlies works fine within the title element, you might have noticed that
+for a split second they are actually displayed to the user while the page is loading. A better
+solution would be to use the ngBind
or ngBindTemplate
directives, which are invisible to the user while the page is loading:
+
+ <title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
+
+Add the following end-to-end test into the describe
block within test/e2e/scenarios.js
:
+
+
+ it('should display the current filter value within an element with id "status"',
+ function() {
+ expect(element('#status').text()).toMatch(/Current filter: \s*$/);
+
+ input('query').enter('nexus');
+
+ expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
+
+ //alternative version of the last assertion that tests just the value of the binding
+ using('#status').expect(binding('query')).toBe('nexus');
+ });
+
+
+Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
+pass, edit the index.html
template to add a div
or p
element with id
"status"
and content
+with the query
binding, prefixed by "Current filter:". For instance:
+
+ <div id="status">Current filter: {{query}}</div>
+
+Add a pause()
statement inside of an end-to-end test and rerun it. You'll see the runner pause;
+this gives you the opportunity to explore the state of your application while it is displayed in
+the browser. The app is live! You can change the search query to prove it. Notice how useful this
+is for troubleshooting end-to-end tests.
+
+
+
Summary
+
+
We have now added full text search and included a test to verify that search works! Now let's go on
+to step 4 to learn how to add sorting capability to the phone app.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_04.html b/lib/angular/docs/partials/tutorial/step_04.html
new file mode 100644
index 0000000..f12f415
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_04.html
@@ -0,0 +1,178 @@
+
+
+
+
+
+
In this step, you will add a feature to let your users control the order of the items in the phone
+list. The dynamic ordering is implemented by creating a new model property, wiring it together with
+the repeater, and letting the data binding magic do the rest of the work.
+
+
+
+
+
You should see that in addition to the search box, the app displays a drop down menu that allows
+users to control the order in which the phones are listed.
+
+
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
+GitHub :
+
+
Template
+
+
app/index.html
:
+
+ Search: <input ng-model="query">
+ Sort by:
+ <select ng-model="orderProp">
+ <option value="name">Alphabetical</option>
+ <option value="age">Newest</option>
+ </select>
+
+
+ <ul class="phones">
+ <li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
+ {{phone.name}}
+ <p>{{phone.snippet}}</p>
+ </li>
+ </ul>
+
+
+
We made the following changes to the index.html
template:
+
+
+First, we added a <select>
html element named orderProp
, so that our users can pick from the
+two provided sorting options.
+
+
+We then chained the filter
filter with orderBy
+filter to further process the input into the repeater. orderBy
is a filter that takes an input
+array, copies it and reorders the copy which is then returned.
+
+
+
Angular creates a two way data-binding between the select element and the orderProp
model.
+orderProp
is then used as the input for the orderBy
filter.
+
+
As we discussed in the section about data-binding and the repeater in step 3, whenever the model
+changes (for example because a user changes the order with the select drop down menu), Angular's
+data-binding will cause the view to automatically update. No bloated DOM manipulation code is
+necessary!
+
+
Controller
+
+
app/js/controllers.js
:
+
+function PhoneListCtrl($scope) {
+ $scope.phones = [
+ {"name": "Nexus S",
+ "snippet": "Fast just got faster with Nexus S.",
+ "age": 0},
+ {"name": "Motorola XOOM™ with Wi-Fi",
+ "snippet": "The Next, Next Generation tablet.",
+ "age": 1},
+ {"name": "MOTOROLA XOOM™",
+ "snippet": "The Next, Next Generation tablet.",
+ "age": 2}
+ ];
+
+ $scope.orderProp = 'age';
+}
+
+
+
+We modified the phones
model - the array of phones - and added an age
property to each phone
+record. This property is used to order phones by age.
+We added a line to the controller that sets the default value of orderProp
to age
. If we had
+not set the default value here, the model would stay uninitialized until our user would pick an
+option from the drop down menu.
+
+This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
+browser, "Newest" is selected in the drop down menu. This is because we set orderProp
to 'age'
+in the controller. So the binding works in the direction from our model to the UI. Now if you
+select "Alphabetically" in the drop down menu, the model will be updated as well and the phones
+will be reordered. That is the data-binding doing its job in the opposite direction — from the UI
+to the model.
+
+
+
Test
+
+
The changes we made should be verified with both a unit test and an end-to-end test. Let's look at
+the unit test first.
+
+
test/unit/controllersSpec.js
:
+
+describe('PhoneCat controllers', function() {
+
+ describe('PhoneListCtrl', function(){
+ var scope, ctrl;
+
+ beforeEach(function() {
+ scope = {},
+ ctrl = new PhoneListCtrl(scope);
+ });
+
+
+ it('should create "phones" model with 3 phones', function() {
+ expect(scope.phones.length).toBe(3);
+ });
+
+
+ it('should set the default value of orderProp model', function() {
+ expect(scope.orderProp).toBe('age');
+ });
+ });
+});
+
+
+
The unit test now verifies that the default ordering property is set.
+
+
We used Jasmine's API to extract the controller construction into a beforeEach
block, which is
+shared by all tests in the parent describe
block.
+
+
You should now see the following output in the Testacular tab:
+
+
Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
+
+
+
Let's turn our attention to the end-to-end test.
+
+
test/e2e/scenarios.js
:
+
+...
+ it('should be possible to control phone order via the drop down select box',
+ function() {
+ //let's narrow the dataset to make the test assertions shorter
+ input('query').enter('tablet');
+
+ expect(repeater('.phones li', 'Phone List').column('phone.name')).
+ toEqual(["Motorola XOOM\u2122 with Wi-Fi",
+ "MOTOROLA XOOM\u2122"]);
+
+ select('orderProp').option('Alphabetical');
+
+ expect(repeater('.phones li', 'Phone List').column('phone.name')).
+ toEqual(["MOTOROLA XOOM\u2122",
+ "Motorola XOOM\u2122 with Wi-Fi"]);
+ });
+...
+
+
+
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
+
+
You can now rerun ./scripts/e2e-test.sh
or refresh the browser tab with the end-to-end test
+runner.html
to see the tests run, or you can see them running on Angular's server .
+
+
Experiments
+
+
+In the PhoneListCtrl
controller, remove the statement that sets the orderProp
value and
+you'll see that Angular will temporarily add a new "unknown" option to the drop-down list and the
+ordering will default to unordered/natural order.
+Add an {{orderProp}}
binding into the index.html
template to display its current value as
+text.
+
+
+
Summary
+
+
Now that you have added list sorting and tested the app, go to step 5 to learn
+about Angular services and how Angular uses dependency injection.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_05.html b/lib/angular/docs/partials/tutorial/step_05.html
new file mode 100644
index 0000000..d5f1d1e
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_05.html
@@ -0,0 +1,227 @@
+
+
+
+
+
+
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
+from our server using one of angular's built-in services called $http
. We will use angular's dependency injection (DI) to provide the service to the PhoneListCtrl
controller.
+
+
+
+
+
You should now see a list of 20 phones.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
Data
+
+
The app/phones/phones.json
file in your project is a dataset that contains a larger list of phones
+stored in the JSON format.
+
+
Following is a sample of the file:
+
+[
+ {
+ "age": 13,
+ "id": "motorola-defy-with-motoblur",
+ "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
+ "snippet": "Are you ready for everything life throws your way?"
+ ...
+ },
+...
+]
+
+
+
Controller
+
+
We'll use angular's $http
service in our controller to make an HTTP
+request to your web server to fetch the data in the app/phones/phones.json
file. $http
is just
+one of several built-in angular services that handle common operations
+in web apps. Angular injects these services for you where you need them.
+
+
Services are managed by angular's DI subsystem . Dependency injection
+helps to make your web apps both well-structured (e.g., separate components for presentation, data,
+and control) and loosely coupled (dependencies between components are not resolved by the
+components themselves, but by the DI subsystem).
+
+
app/js/controllers.js:
+
+function PhoneListCtrl($scope, $http) {
+ $http.get('phones/phones.json').success(function(data) {
+ $scope.phones = data;
+ });
+
+ $scope.orderProp = 'age';
+}
+
+//PhoneListCtrl.$inject = ['$scope', '$http'];
+
+
+
$http
makes an HTTP GET request to our web server, asking for phone/phones.json
(the url is
+relative to our index.html
file). The server responds by providing the data in the json file.
+(The response might just as well have been dynamically generated by a backend server. To the
+browser and our app they both look the same. For the sake of simplicity we used a json file in this
+tutorial.)
+
+
The $http
service returns a promise object
with a success
+method. We call this method to handle the asynchronous response and assign the phone data to the
+scope controlled by this controller, as a model called phones
. Notice that angular detected the
+json response and parsed it for us!
+
+
To use a service in angular, you simply declare the names of the dependencies you need as arguments
+to the controller's constructor function, as follows:
+
+
function PhoneListCtrl($scope, $http) {...}
+
+
+
Angular's dependency injector provides services to your controller when the controller is being
+constructed. The dependency injector also takes care of creating any transitive dependencies the
+service may have (services often depend upon other services).
+
+
Note that the names of arguments are significant, because the injector uses these to look up the
+dependencies.
+
+
+
+
'$' Prefix Naming Convention
+
+
You can create your own services, and in fact we will do exactly that in step 11. As a naming
+convention, angular's built-in services, Scope methods and a few other angular APIs have a '$'
+prefix in front of the name. Don't use a '$' prefix when naming your services and models, in order
+to avoid any possible naming collisions.
+
+
A Note on Minification
+
+
Since angular infers the controller's dependencies from the names of arguments to the controller's
+constructor function, if you were to minify the JavaScript code for PhoneListCtrl
controller, all of its function arguments would be
+minified as well, and the dependency injector would not be able to identify services correctly.
+
+
To overcome issues caused by minification, just assign an array with service identifier strings
+into the $inject
property of the controller function, just like the last line in the snippet
+(commented out) suggests:
+
+
PhoneListCtrl.$inject = ['$scope', '$http'];
+
+
+
There is also one more way to specify this dependency list and avoid minification issues — using the
+bracket notation which wraps the function to be injected into an array of strings (representing the
+dependency names) followed by the function to be injected:
+
+
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) { /* constructor body */ }];
+
+
+
Both of these methods work with any function that can be injected by Angular, so it's up to your
+project's style guide to decide which one you use.
+
+
Test
+
+
test/unit/controllersSpec.js
:
+
+
Because we started using dependency injection and our controller has dependencies, constructing the
+controller in our tests is a bit more complicated. We could use the new
operator and provide the
+constructor with some kind of fake $http
implementation. However, the recommended (and easier) way
+is to create a controller in the test environment in the same way that angular does it in the
+production code behind the scenes, as follows:
+
+
+describe('PhoneCat controllers', function() {
+
+ describe('PhoneListCtrl', function(){
+ var scope, ctrl, $httpBackend;
+
+ beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
+ $httpBackend = _$httpBackend_;
+ $httpBackend.expectGET('phones/phones.json').
+ respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
+
+ scope = $rootScope.$new();
+ ctrl = $controller(PhoneListCtrl, {$scope: scope});
+ }));
+
+
+
Note: Because we loaded Jasmine and angular-mocks.js
in our test environment, we got two helper
+methods module
and inject
that we'll
+use to access and configure the injector.
+
+
We created the controller in the test environment, as follows:
+
+
+We used the inject
helper method to inject instances of
+$rootScope
,
+$controller
and
+$httpBackend
services into the Jasmine's beforeEach
+function. These instances come from an injector which is recreated from scratch for every single
+test. This guarantees that each test starts from a well known starting point and each test is
+isolated from the work done in other tests.
+We created a new scope for our controller by calling $rootScope.$new()
+We called the injected $controller
function passing the PhoneListCtrl
function and the created
+scope as parameters.
+
+
+
Because our code now uses the $http
service to fetch the phone list data in our controller, before
+we create the PhoneListCtrl
child scope, we need to tell the testing harness to expect an
+incoming request from the controller. To do this we:
+
+
+Request $httpBackend
service to be injected into our beforeEach
function. This is a mock
+version of the service that in a production environment facilitates all XHR and JSONP requests.
+The mock version of this service allows you to write tests without having to deal with
+native APIs and the global state associated with them — both of which make testing a nightmare.
+Use the $httpBackend.expectGET
method to train the $httpBackend
service to expect an incoming
+HTTP request and tell it what to respond with. Note that the responses are not returned until we call
+the $httpBackend.flush
method.
+
+
+
Now, we will make assertions to verify that the phones
model doesn't exist on scope
before
+the response is received:
+
+
+ it('should create "phones" model with 2 phones fetched from xhr', function() {
+ expect(scope.phones).toBeUndefined();
+ $httpBackend.flush();
+
+ expect(scope.phones).toEqual([{name: 'Nexus S'},
+ {name: 'Motorola DROID'}]);
+ });
+
+
+
+We flush the request queue in the browser by calling $httpBackend.flush()
. This causes the
+promise returned by the $http
service to be resolved with the trained response.
+We make the assertions, verifying that the phone model now exists on the scope.
+
+
+
Finally, we verify that the default value of orderProp
is set correctly:
+
+
+ it('should set the default value of orderProp model', function() {
+ expect(scope.orderProp).toBe('age');
+ });
+ });
+});
+
+
+
You should now see the following output in the Testacular tab:
+
+
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
+
+
+
Experiments
+
+
+At the bottom of index.html
, add a {{phones | json}}
binding to see the list of phones
+displayed in json format.
+In the PhoneListCtrl
controller, pre-process the http response by limiting the number of phones
+to the first 5 in the list. Use the following code in the $http callback:
+
+ $scope.phones = data.splice(0, 5);
+
+
+
+
Summary
+
+
Now that you have learned how easy it is to use angular services (thanks to Angular's dependency
+injection), go to step 6 , where you will add some
+thumbnail images of phones and some links.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_06.html b/lib/angular/docs/partials/tutorial/step_06.html
new file mode 100644
index 0000000..6179d06
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_06.html
@@ -0,0 +1,99 @@
+
+
+
+
+
+
In this step, you will add thumbnail images for the phones in the phone list, and links that, for
+now, will go nowhere. In subsequent steps you will use the links to display additional information
+about the phones in the catalog.
+
+
+
+
+
You should now see links and images of the phones in the list.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
Data
+
+
Note that the phones.json
file contains unique ids and image urls for each of the phones. The
+urls point to the app/img/phones/
directory.
+
+
app/phones/phones.json
(sample snippet):
+
+ [
+ {
+ ...
+ "id": "motorola-defy-with-motoblur",
+ "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
+ "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
+ ...
+ },
+ ...
+ ]
+
+
+
Template
+
+
app/index.html
:
+
+...
+ <ul class="phones">
+ <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
+ <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
+ <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
+ <p>{{phone.snippet}}</p>
+ </li>
+ </ul>
+...
+
+
+
To dynamically generate links that will in the future lead to phone detail pages, we used the
+now-familiar double-curly brace binding in the href
attribute values. In step 2, we added the
+{{phone.name}}
binding as the element content. In this step the {{phone.id}}
binding is used in
+the element attribute.
+
+
We also added phone images next to each record using an image tag with the ngSrc
directive. That directive prevents the
+browser from treating the angular {{ expression }}
markup literally, and initiating a request to
+invalid url http://localhost:8000/app/{{phone.imageUrl}}
, which it would have done if we had only
+specified an attribute binding in a regular src
attribute (<img class="diagram" src="{{phone.imageUrl}}">
).
+Using the ngSrc
directive prevents the browser from making an http request to an invalid location.
+
+
Test
+
+
test/e2e/scenarios.js
:
+
+...
+ it('should render phone specific links', function() {
+ input('query').enter('nexus');
+ element('.phones li a').click();
+ expect(browser().location().url()).toBe('/phones/nexus-s');
+ });
+...
+
+
+
We added a new end-to-end test to verify that the app is generating correct links to the phone
+views that we will implement in the upcoming steps.
+
+
You can now rerun ./scripts/e2e-test.sh
or refresh the browser tab with the end-to-end test
+runner to see the tests run, or you can see them running on Angular's server .
+
+
Experiments
+
+
+Replace the ng-src
directive with a plain old src
attribute. Using tools such as Firebug,
+or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed
+making an extraneous request to /app/%7B%7Bphone.imageUrl%7D%7D
(or
+/app/{{phone.imageUrl}}
).
+
+The issue here is that the browser will fire a request for that invalid image address as soon as
+it hits the img
tag, which is before Angular has a chance to evaluate the expression and inject
+the valid address.
+
+
+
Summary
+
+
Now that you have added phone images and links, go to step 7 to learn about Angular
+layout templates and how Angular makes it easy to create applications that have multiple views.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_07.html b/lib/angular/docs/partials/tutorial/step_07.html
new file mode 100644
index 0000000..a5b2dfc
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_07.html
@@ -0,0 +1,245 @@
+
+
+
+
+
+
In this step, you will learn how to create a layout template and how to build an app that has
+multiple views by adding routing.
+
+
+
+
+
Note that when you now navigate to app/index.html
, you are redirected to app/index.html#/phones
+and the same phone list appears in the browser. When you click on a phone link the stub of a phone
+detail page is displayed.
+
+
The most important changes are listed below. You can see the full diff on GitHub .
+
+
Multiple Views, Routing and Layout Template
+
+
Our app is slowly growing and becoming more complex. Before step 7, the app provided our users with
+a single view (the list of all phones), and all of the template code was located in the
+index.html
file. The next step in building the app is to add a view that will show detailed
+information about each of the devices in our list.
+
+
To add the detailed view, we could expand the index.html
file to contain template code for both
+views, but that would get messy very quickly. Instead, we are going to turn the index.html
+template into what we call a "layout template". This is a template that is common for all views in
+our application. Other "partial templates" are then included into this layout template depending on
+the current "route" — the view that is currently displayed to the user.
+
+
Application routes in Angular are declared via the
+$routeProvider
, which is the provider of the
+$route service
. This service makes it easy to wire together
+controllers, view templates, and the current
+URL location in the browser. Using this feature we can implement deep linking , which lets us utilize the browser's
+history (back and forward navigation) and bookmarks.
+
+
A Note About DI, Injector and Providers
+
+
As you noticed , dependency injection (DI) is the core feature of
+AngularJS, so it's important for you to understand a thing or two about how it works.
+
+
When the application bootstraps, Angular creates an injector that will be used for all DI stuff in
+this app. The injector itself doesn't know anything about what $http
or $route
services do, in
+fact it doesn't even know about the existence of these services unless it is configured with proper
+module definitions. The sole responsibilities of the injector are to load specified module
+definition(s), register all service providers defined in these modules and when asked inject
+a specified function with dependencies (services) that it lazily instantiates via their providers.
+
+
Providers are objects that provide (create) instances of services and expose configuration APIs
+that can be used to control the creation and runtime behavior of a service. In case of the $route
+service, the $routeProvider
exposes APIs that allow you to define routes for your application.
+
+
Angular modules solve the problem of removing global state from the application and provide a way
+of configuring the injector. As opposed to AMD or require.js modules, Angular modules don't try to
+solve the problem of script load ordering or lazy script fetching. These goals are orthogonal and
+both module systems can live side by side and fulfil their goals.
+
+
The App Module
+
+
app/js/app.js
:
+
+angular.module('phonecat', []).
+ config(['$routeProvider', function($routeProvider) {
+ $routeProvider.
+ when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
+ when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
+ otherwise({redirectTo: '/phones'});
+}]);
+
+
+
In order to configure our application with routes, we need to create a module for our application.
+We call this module phonecat
and using the config
API we request the $routeProvider
to be
+injected into our config function and use $routeProvider.when
API to define our routes.
+
+
Note that during the injector configuration phase, the providers can be injected as well, but they
+will not be available for injection once the injector is created and starts creating service
+instances.
+
+
Our application routes were defined as follows:
+
+
+The phone list view will be shown when the URL hash fragment is /phones
. To construct this
+view, Angular will use the phone-list.html
template and the PhoneListCtrl
controller.
+The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
+:phoneId
is a variable part of the URL. To construct the phone details view, angular will use the
+phone-detail.html
template and the PhoneDetailCtrl
controller.
+
+
+
We reused the PhoneListCtrl
controller that we constructed in previous steps and we added a new,
+empty PhoneDetailCtrl
controller to the app/js/controllers.js
file for the phone details view.
+
+
The statement $route.otherwise({redirectTo: '/phones'})
triggers a redirection to /phones
when
+the browser address doesn't match either of our routes.
+
+
Note the use of the :phoneId
parameter in the second route declaration. The $route
service uses
+the route declaration — '/phones/:phoneId'
— as a template that is matched against the current
+URL. All variables defined with the :
notation are extracted into the
+$routeParams
object.
+
+
In order for our application to bootstrap with our newly created module we'll also need to specify
+the module name as the value of the ngApp
+directive:
+
+
app/index.html
:
+
+<!doctype html>
+<html lang="en" ng-app="phonecat">
+...
+
+
+
Controllers
+
+
app/js/controllers.js
:
+
+...
+function PhoneDetailCtrl($scope, $routeParams) {
+ $scope.phoneId = $routeParams.phoneId;
+}
+
+//PhoneDetailCtrl.$inject = ['$scope', '$routeParams'];
+
+
+
Template
+
+
The $route
service is usually used in conjunction with the ngView
directive. The role of the ngView
directive is to include the view template for the current
+route into the layout template, which makes it a perfect fit for our index.html
template.
+
+
app/index.html
:
+
+<html lang="en" ng-app="phonecat">
+<head>
+...
+ <script src="lib/angular/angular.js"></script>
+ <script src="js/app.js"></script>
+ <script src="js/controllers.js"></script>
+</head>
+<body>
+
+ <div ng-view></div>
+
+</body>
+</html>
+
+
+
Note that we removed most of the code in the index.html
template and replaced it with a single
+line containing a div with the ng-view
attribute. The code that we removed was placed into the
+phone-list.html
template:
+
+
app/partials/phone-list.html
:
+
+<div class="container-fluid">
+ <div class="row-fluid">
+ <div class="span2">
+ <!--Sidebar content-->
+
+ Search: <input ng-model="query">
+ Sort by:
+ <select ng-model="orderProp">
+ <option value="name">Alphabetical</option>
+ <option value="age">Newest</option>
+ </select>
+
+ </div>
+ <div class="span10">
+ <!--Body content-->
+
+ <ul class="phones">
+ <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
+ <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
+ <a href="#/phones/{{phone.id}}">{{phone.name}}</a>
+ <p>{{phone.snippet}}</p>
+ </li>
+ </ul>
+
+ </div>
+ </div>
+</div>
+
+
+
+TODO!
+
+
+
+
We also added a placeholder template for the phone details view:
+
+
app/partials/phone-detail.html
:
+
+TBD: detail view for {{phoneId}}
+
+
+
Note how we are using phoneId
model defined in the PhoneDetailCtrl
controller.
+
+
Test
+
+
To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate
+to various URLs and verify that the correct view was rendered.
+
+
+...
+ it('should redirect index.html to index.html#/phones', function() {
+ browser().navigateTo('../../app/index.html');
+ expect(browser().location().url()).toBe('/phones');
+ });
+...
+
+ describe('Phone detail view', function() {
+
+ beforeEach(function() {
+ browser().navigateTo('../../app/index.html#/phones/nexus-s');
+ });
+
+
+ it('should display placeholder page with phoneId', function() {
+ expect(binding('phoneId')).toBe('nexus-s');
+ });
+ });
+
+
+
You can now rerun ./scripts/e2e-test.sh
or refresh the browser tab with the end-to-end test
+runner to see the tests run, or you can see them running on Angular's server .
+
+
Experiments
+
+
+Try to add an {{orderProp}}
binding to index.html
, and you'll see that nothing happens even
+when you are in the phone list view. This is because the orderProp
model is visible only in the
+scope managed by PhoneListCtrl
, which is associated with the <div ng-view>
element. If you add
+the same binding into the phone-list.html
template, the binding will work as expected.
+
+
+
+* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
+`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and in `PhoneDetailCtrl` we'll use
+`this.hero = "Captain Proton"`. Then add the `
hero = {{hero}}
` to all three of our templates
+(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
+inheritance and model property shadowing do some wonders.
+
+
+
Summary
+
+
With the routing set up and the phone list view implemented, we're ready to go to step 8 to implement the phone details view.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_08.html b/lib/angular/docs/partials/tutorial/step_08.html
new file mode 100644
index 0000000..2af9ac3
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_08.html
@@ -0,0 +1,181 @@
+
+
+
+
+
+
In this step, you will implement the phone details view, which is displayed when a user clicks on a
+phone in the phone list.
+
+
+
+
+
Now when you click on a phone on the list, the phone details page with phone-specific information
+is displayed.
+
+
To implement the phone details view we will use $http
to fetch
+our data, and we'll flesh out the phone-detail.html
view template.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
Data
+
+
In addition to phones.json
, the app/phones/
directory also contains one json file for each
+phone:
+
+
app/phones/nexus-s.json
: (sample snippet)
+
+{
+ "additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
+ "android": {
+ "os": "Android 2.3",
+ "ui": "Android"
+ },
+ ...
+ "images": [
+ "img/phones/nexus-s.0.jpg",
+ "img/phones/nexus-s.1.jpg",
+ "img/phones/nexus-s.2.jpg",
+ "img/phones/nexus-s.3.jpg"
+ ],
+ "storage": {
+ "flash": "16384MB",
+ "ram": "512MB"
+ }
+}
+
+
+
Each of these files describes various properties of the phone using the same data structure. We'll
+show this data in the phone detail view.
+
+
Controller
+
+
We'll expand the PhoneDetailCtrl
by using the $http
service to fetch the json files. This works
+the same way as the phone list controller.
+
+
app/js/controllers.js
:
+
+function PhoneDetailCtrl($scope, $routeParams, $http) {
+ $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
+ $scope.phone = data;
+ });
+}
+
+//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
+
+
+
To construct the URL for the HTTP request, we use $routeParams.phoneId
extracted from the current
+route by the $route
service.
+
+
Template
+
+
The TBD placeholder line has been replaced with lists and bindings that comprise the phone details.
+Note where we use the angular {{expression}}
markup and ngRepeat
to project phone data from
+our model into the view.
+
+
app/partials/phone-detail.html
:
+
+<img ng-src="{{phone.images[0]}}" class="phone">
+
+<h1>{{phone.name}}</h1>
+
+<p>{{phone.description}}</p>
+
+<ul class="phone-thumbs">
+ <li ng-repeat="img in phone.images">
+ <img ng-src="{{img}}">
+ </li>
+</ul>
+
+<ul class="specs">
+ <li>
+ <span>Availability and Networks</span>
+ <dl>
+ <dt>Availability</dt>
+ <dd ng-repeat="availability in phone.availability">{{availability}}</dd>
+ </dl>
+ </li>
+ ...
+ </li>
+ <span>Additional Features</span>
+ <dd>{{phone.additionalFeatures}}</dd>
+ </li>
+</ul>
+
+
+
+TODO!
+
+
+
+
Test
+
+
We wrote a new unit test that is similar to the one we wrote for the PhoneListCtrl
controller in
+step 5.
+
+
test/unit/controllersSpec.js
:
+
+...
+ describe('PhoneDetailCtrl', function(){
+ var scope, $httpBackend, ctrl;
+
+ beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
+ $httpBackend = _$httpBackend_;
+ $httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});
+
+ $routeParams.phoneId = 'xyz';
+ scope = $rootScope.$new();
+ ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
+ }));
+
+
+ it('should fetch phone detail', function() {
+ expect(scope.phone).toBeUndefined();
+ $httpBackend.flush();
+
+ expect(scope.phone).toEqual({name:'phone xyz'});
+ });
+ });
+...
+
+
+
You should now see the following output in the Testacular tab:
+
+
Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)
+
+
+
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the
+heading on the page is "Nexus S".
+
+
test/e2e/scenarios.js
:
+
+...
+ describe('Phone detail view', function() {
+
+ beforeEach(function() {
+ browser().navigateTo('../../app/index.html#/phones/nexus-s');
+ });
+
+
+ it('should display nexus-s page', function() {
+ expect(binding('phone.name')).toBe('Nexus S');
+ });
+ });
+...
+
+
+
You can now rerun ./scripts/e2e-test.sh
or refresh the browser tab with the end-to-end test
+runner to see the tests run, or you can see them running on Angular's server .
+
+
Experiments
+
+
+
+
Summary
+
+
Now that the phone details view is in place, proceed to step 9 to learn how to
+write your own custom display filter.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_09.html b/lib/angular/docs/partials/tutorial/step_09.html
new file mode 100644
index 0000000..7ae6d71
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_09.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+
In this step you will learn how to create your own custom display filter.
+
+
+
+
+
Navigate to one of the detail pages.
+
+
In the previous step, the details page displayed either "true" or "false" to indicate whether
+certain phone features were present or not. We have used a custom filter to convert those text
+strings into glyphs: ✓ for "true", and ✘ for "false". Let's see what the filter code looks like.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
Custom Filter
+
+
In order to create a new filter, you are going to create a phonecatFilters
module and register
+your custom filter with this module:
+
+
app/js/filters.js
:
+
+angular.module('phonecatFilters', []).filter('checkmark', function() {
+ return function(input) {
+ return input ? '\u2713' : '\u2718';
+ };
+});
+
+
+
The name of our filter is "checkmark". The input
evaluates to either true
or false
, and we
+return one of two unicode characters we have chosen to represent true or false (\u2713
and
+\u2718
).
+
+
Now that our filter is ready, we need to register the phonecatFilters
module as a dependency for
+our main phonecat
module.
+
+
app/js/app.js
:
+
+...
+angular.module('phonecat', ['phonecatFilters']).
+...
+
+
+
Template
+
+
Since the filter code lives in the app/js/filters.js
file, we need to include this file in our
+layout template.
+
+
app/index.html
:
+
+...
+ <script src="js/controllers.js"></script>
+ <script src="js/filters.js"></script>
+...
+
+
+
The syntax for using filters in Angular templates is as follows:
+
+
{{ expression | filter }}
+
+
+
Let's employ the filter in the phone details template:
+
+
app/partials/phone-detail.html
:
+
+...
+ <dl>
+ <dt>Infrared</dt>
+ <dd>{{phone.connectivity.infrared | checkmark}}</dd>
+ <dt>GPS</dt>
+ <dd>{{phone.connectivity.gps | checkmark}}</dd>
+ </dl>
+...
+
+
+
Test
+
+
Filters, like any other component, should be tested and these tests are very easy to write.
+
+
test/unit/filtersSpec.js
:
+
+describe('filter', function() {
+
+ beforeEach(module('phonecatFilters'));
+
+
+ describe('checkmark', function() {
+
+ it('should convert boolean values to unicode checkmark or cross',
+ inject(function(checkmarkFilter) {
+ expect(checkmarkFilter(true)).toBe('\u2713');
+ expect(checkmarkFilter(false)).toBe('\u2718');
+ }));
+ });
+});
+
+
+
Note that you need to configure our test injector with the phonecatFilters
module before any of
+our filter tests execute.
+
+
You should now see the following output in the Testacular tab:
+
+
Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)
+
+
+
Experiments
+
+
+Let's experiment with some of the built-in Angular filters
and add the
+following bindings to index.html
:
+
+{{ "lower cap string" | uppercase }}
+{{ {foo: "bar", baz: 23} | json }}
+{{ 1304375948024 | date }}
+{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}
+We can also create a model with an input element, and combine it with a filtered binding. Add
+the following to index.html:
+
+<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
+
+
+
+
Summary
+
+
Now that you have learned how to write and test a custom filter, go to step 10 to
+learn how we can use Angular to enhance the phone details page further.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_10.html b/lib/angular/docs/partials/tutorial/step_10.html
new file mode 100644
index 0000000..69bbbbb
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_10.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+
In this step, you will add a clickable phone image swapper to the phone details page.
+
+
+
+
+
The phone details view displays one large image of the current phone and several smaller thumbnail
+images. It would be great if we could replace the large image with any of the thumbnails just by
+clicking on the desired thumbnail image. Let's have a look at how we can do this with Angular.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
Controller
+
+
app/js/controllers.js
:
+
+...
+function PhoneDetailCtrl($scope, $routeParams, $http) {
+ $http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
+ $scope.phone = data;
+ $scope.mainImageUrl = data.images[0];
+ });
+
+ $scope.setImage = function(imageUrl) {
+ $scope.mainImageUrl = imageUrl;
+ }
+}
+
+//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', '$http'];
+
+
+
In the PhoneDetailCtrl
controller, we created the mainImageUrl
model property and set its
+default value to the first phone image URL.
+
+
We also created a setImage
event handler function that will change the value of mainImageUrl
.
+
+
Template
+
+
app/partials/phone-detail.html
:
+
+<img ng-src="{{mainImageUrl}}" class="phone">
+
+...
+
+<ul class="phone-thumbs">
+ <li ng-repeat="img in phone.images">
+ <img ng-src="{{img}}" ng-click="setImage(img)">
+ </li>
+</ul>
+...
+
+
+
We bound the ngSrc
directive of the large image to the mainImageUrl
property.
+
+
We also registered an ngClick
+handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will
+use the setImage
event handler function to change the value of the mainImageUrl
property to the
+URL of the thumbnail image.
+
+
+TODO!
+
+
+
+
Test
+
+
To verify this new feature, we added two end-to-end tests. One verifies that the main image is set
+to the first phone image by default. The second test clicks on several thumbnail images and
+verifies that the main image changed appropriately.
+
+
test/e2e/scenarios.js
:
+
+...
+ describe('Phone detail view', function() {
+
+...
+
+ it('should display the first phone image as the main phone image', function() {
+ expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
+ });
+
+
+ it('should swap main image if a thumbnail image is clicked on', function() {
+ element('.phone-thumbs li:nth-child(3) img').click();
+ expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
+
+ element('.phone-thumbs li:nth-child(1) img').click();
+ expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
+ });
+ });
+});
+
+
+
You can now rerun ./scripts/e2e-test.sh
or refresh the browser tab with the end-to-end test
+runner to see the tests run, or you can see them running on Angular's server .
+
+
Experiments
+
+
+Let's add a new controller method to PhoneDetailCtrl
:
+
+ $scope.hello = function(name) {
+ alert('Hello ' + (name || 'world') + '!');
+ }
+
+
+and add:
+
+ <button ng-click="hello('Elmo')">Hello</button>
+
+
+to the phone-details.html
template.
+
+
+
+TODO!
+ The controller methods are inherited between controllers/scopes, so you can use the same snippet
+in the `phone-list.html` template as well.
+
+* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button
+declared in `index.html` will stop working, while the one declared in the `phone-list.html`
+template remains operational.
+
+
+
Summary
+
+
With the phone image swapper in place, we're ready for step 11 (the last step!) to
+learn an even better way to fetch data.
+
+
diff --git a/lib/angular/docs/partials/tutorial/step_11.html b/lib/angular/docs/partials/tutorial/step_11.html
new file mode 100644
index 0000000..b5feb6f
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/step_11.html
@@ -0,0 +1,215 @@
+
+
+
+
+
+
In this step, you will improve the way our app fetches data.
+
+
+
+
+
The last improvement we will make to our app is to define a custom service that represents a RESTful client. Using this client we
+can make XHR requests for data in an easier way, without having to deal with the lower-level $http
API, HTTP methods and URLs.
+
+
The most important changes are listed below. You can see the full diff on GitHub :
+
+
Template
+
+
The custom service is defined in app/js/services.js
so we need to include this file in our layout
+template. Additionally, we also need to load the angular-resource.js
file, which contains the
+ngResource
module and in it the $resource
service, that we'll soon use:
+
+
app/index.html
.
+
+...
+ <script src="js/services.js"></script>
+ <script src="lib/angular/angular-resource.js"></script>
+...
+
+
+
Service
+
+
app/js/services.js
.
+
+angular.module('phonecatServices', ['ngResource']).
+ factory('Phone', function($resource){
+ return $resource('phones/:phoneId.json', {}, {
+ query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
+ });
+});
+
+
+
We used the module API to register a custom service using a factory function. We passed in the name
+of the service - 'Phone' - and the factory function. The factory function is similar to a
+controller's constructor in that both can declare dependencies via function arguments. The Phone
+service declared a dependency on the $resource
service.
+
+
The $resource
service makes it easy to create a
+RESTful client with just a few
+lines of code. This client can then be used in our application, instead of the lower-level $http
service.
+
+
app/js/app.js
.
+
+...
+angular.module('phonecat', ['phonecatFilters', 'phonecatServices']).
+...
+
+
+
We need to add 'phonecatServices' to 'phonecat' application's requires array.
+
+
Controller
+
+
We simplified our sub-controllers (PhoneListCtrl
and PhoneDetailCtrl
) by factoring out the
+lower-level $http
service, replacing it with a new service called
+Phone
. Angular's $resource
service is easier to
+use than $http
for interacting with data sources exposed as RESTful resources. It is also easier
+now to understand what the code in our controllers is doing.
+
+
app/js/controllers.js
.
+
+...
+
+function PhoneListCtrl($scope, Phone) {
+ $scope.phones = Phone.query();
+ $scope.orderProp = 'age';
+}
+
+//PhoneListCtrl.$inject = ['$scope', 'Phone'];
+
+
+
+function PhoneDetailCtrl($scope, $routeParams, Phone) {
+ $scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
+ $scope.mainImageUrl = phone.images[0];
+ });
+
+ $scope.setImage = function(imageUrl) {
+ $scope.mainImageUrl = imageUrl;
+ }
+}
+
+//PhoneDetailCtrl.$inject = ['$scope', '$routeParams', 'Phone'];
+
+
+
Notice how in PhoneListCtrl
we replaced:
+
+
$http.get('phones/phones.json').success(function(data) {
+ $scope.phones = data;
+});
+
+
+
with:
+
+
$scope.phones = Phone.query();
+
+
+
This is a simple statement that we want to query for all phones.
+
+
An important thing to notice in the code above is that we don't pass any callback functions when
+invoking methods of our Phone service. Although it looks as if the result were returned
+synchronously, that is not the case at all. What is returned synchronously is a "future" — an
+object, which will be filled with data when the XHR response returns. Because of the data-binding
+in Angular, we can use this future and bind it to our template. Then, when the data arrives, the
+view will automatically update.
+
+
Sometimes, relying on the future object and data-binding alone is not sufficient to do everything
+we require, so in these cases, we can add a callback to process the server response. The
+PhoneDetailCtrl
controller illustrates this by setting the mainImageUrl
in a callback.
+
+
Test
+
+
We have modified our unit tests to verify that our new service is issuing HTTP requests and
+processing them as expected. The tests also check that our controllers are interacting with the
+service correctly.
+
+
The $resource service augments the response object
+with methods for updating and deleting the resource. If we were to use the standard toEqual
+matcher, our tests would fail because the test values would not match the responses exactly. To
+solve the problem, we use a newly-defined toEqualData
Jasmine matcher . When the
+toEqualData
matcher compares two objects, it takes only object properties into account and
+ignores methods.
+
+
test/unit/controllersSpec.js
:
+
+describe('PhoneCat controllers', function() {
+
+ beforeEach(function(){
+ this.addMatchers({
+ toEqualData: function(expected) {
+ return angular.equals(this.actual, expected);
+ }
+ });
+ });
+
+
+ beforeEach(module('phonecatServices'));
+
+
+ describe('PhoneListCtrl', function(){
+ var scope, ctrl, $httpBackend;
+
+ beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
+ $httpBackend = _$httpBackend_;
+ $httpBackend.expectGET('phones/phones.json').
+ respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
+
+ scope = $rootScope.$new();
+ ctrl = $controller(PhoneListCtrl, {$scope: scope});
+ }));
+
+
+ it('should create "phones" model with 2 phones fetched from xhr', function() {
+ expect(scope.phones).toEqual([]);
+ $httpBackend.flush();
+
+ expect(scope.phones).toEqualData(
+ [{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
+ });
+
+
+ it('should set the default value of orderProp model', function() {
+ expect(scope.orderProp).toBe('age');
+ });
+ });
+
+
+ describe('PhoneDetailCtrl', function(){
+ var scope, $httpBackend, ctrl,
+ xyzPhoneData = function() {
+ return {
+ name: 'phone xyz',
+ images: ['image/url1.png', 'image/url2.png']
+ }
+ };
+
+
+ beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
+ $httpBackend = _$httpBackend_;
+ $httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
+
+ $routeParams.phoneId = 'xyz';
+ scope = $rootScope.$new();
+ ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
+ }));
+
+
+ it('should fetch phone detail', function() {
+ expect(scope.phone).toEqualData({});
+ $httpBackend.flush();
+
+ expect(scope.phone).toEqualData(xyzPhoneData());
+ });
+ });
+});
+
+
+
You should now see the following output in the Testacular tab:
+
+
Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)
+
+
+
Summary
+
+
There you have it! We have created a web app in a relatively short amount of time. In the closing notes we'll cover where to go from here.
+
+
diff --git a/lib/angular/docs/partials/tutorial/the_end.html b/lib/angular/docs/partials/tutorial/the_end.html
new file mode 100644
index 0000000..b362520
--- /dev/null
+++ b/lib/angular/docs/partials/tutorial/the_end.html
@@ -0,0 +1,19 @@
+
+
+
+
Our application is now complete. Feel free to experiment with the code further, and jump back to
+previous steps using the git checkout
command.
+
+
For more details and examples of the Angular concepts we touched on in this tutorial, see the
+Developer Guide .
+
+
For several more examples of code, see the Cookbook .
+
+
When you are ready to start developing a project using Angular, we recommend that you bootstrap
+your development with the angular-seed project.
+
+
We hope this tutorial was useful to you and that you learned enough about Angular to make you want
+to learn more. We especially hope you are inspired to go out and develop Angular web apps of your
+own, and that you might be interested in contributing to Angular.
+
+
If you have questions or feedback or just want to say "hi", please post a message at https://groups.google.com/forum/#!forum/angular .
diff --git a/lib/angular/docs/robots.txt b/lib/angular/docs/robots.txt
new file mode 100644
index 0000000..f93b7c0
--- /dev/null
+++ b/lib/angular/docs/robots.txt
@@ -0,0 +1 @@
+Sitemap: http://docs.angularjs.org/sitemap.xml
diff --git a/lib/angular/docs/sitemap.xml b/lib/angular/docs/sitemap.xml
new file mode 100644
index 0000000..dbb4555
--- /dev/null
+++ b/lib/angular/docs/sitemap.xml
@@ -0,0 +1,214 @@
+
+
+ http://docs.angularjs.org/api/index weekly
+ http://docs.angularjs.org/api/ng weekly
+ http://docs.angularjs.org/cookbook/advancedform weekly
+ http://docs.angularjs.org/cookbook/buzz weekly
+ http://docs.angularjs.org/cookbook/deeplinking weekly
+ http://docs.angularjs.org/cookbook/form weekly
+ http://docs.angularjs.org/cookbook/helloworld weekly
+ http://docs.angularjs.org/cookbook/index weekly
+ http://docs.angularjs.org/cookbook/mvc weekly
+ http://docs.angularjs.org/guide/bootstrap weekly
+ http://docs.angularjs.org/guide/compiler weekly
+ http://docs.angularjs.org/guide/concepts weekly
+ http://docs.angularjs.org/guide/dev_guide.e2e-testing weekly
+ http://docs.angularjs.org/guide/dev_guide.mvc weekly
+ http://docs.angularjs.org/guide/dev_guide.mvc.understanding_controller weekly
+ http://docs.angularjs.org/guide/dev_guide.mvc.understanding_model weekly
+ http://docs.angularjs.org/guide/dev_guide.mvc.understanding_view weekly
+ http://docs.angularjs.org/guide/dev_guide.services.$location weekly
+ http://docs.angularjs.org/guide/dev_guide.services.creating_services weekly
+ http://docs.angularjs.org/guide/dev_guide.services.injecting_controllers weekly
+ http://docs.angularjs.org/guide/dev_guide.services.managing_dependencies weekly
+ http://docs.angularjs.org/guide/dev_guide.services weekly
+ http://docs.angularjs.org/guide/dev_guide.services.testing_services weekly
+ http://docs.angularjs.org/guide/dev_guide.services.understanding_services weekly
+ http://docs.angularjs.org/guide/dev_guide.templates.css-styling weekly
+ http://docs.angularjs.org/guide/dev_guide.templates.databinding weekly
+ http://docs.angularjs.org/guide/dev_guide.templates.filters.creating_filters weekly
+ http://docs.angularjs.org/guide/dev_guide.templates.filters weekly
+ http://docs.angularjs.org/guide/dev_guide.templates.filters.using_filters weekly
+ http://docs.angularjs.org/guide/dev_guide.templates weekly
+ http://docs.angularjs.org/guide/dev_guide.unit-testing weekly
+ http://docs.angularjs.org/guide/di weekly
+ http://docs.angularjs.org/guide/directive weekly
+ http://docs.angularjs.org/guide/expression weekly
+ http://docs.angularjs.org/guide/forms weekly
+ http://docs.angularjs.org/guide/i18n weekly
+ http://docs.angularjs.org/guide/ie weekly
+ http://docs.angularjs.org/guide/index weekly
+ http://docs.angularjs.org/guide/introduction weekly
+ http://docs.angularjs.org/guide/module weekly
+ http://docs.angularjs.org/guide/overview weekly
+ http://docs.angularjs.org/guide/scope weekly
+ http://docs.angularjs.org/guide/type weekly
+ http://docs.angularjs.org/misc/contribute weekly
+ http://docs.angularjs.org/misc/downloading weekly
+ http://docs.angularjs.org/misc/faq weekly
+ http://docs.angularjs.org/misc/started weekly
+ http://docs.angularjs.org/tutorial/index weekly
+ http://docs.angularjs.org/tutorial/step_00 weekly
+ http://docs.angularjs.org/tutorial/step_01 weekly
+ http://docs.angularjs.org/tutorial/step_02 weekly
+ http://docs.angularjs.org/tutorial/step_03 weekly
+ http://docs.angularjs.org/tutorial/step_04 weekly
+ http://docs.angularjs.org/tutorial/step_05 weekly
+ http://docs.angularjs.org/tutorial/step_06 weekly
+ http://docs.angularjs.org/tutorial/step_07 weekly
+ http://docs.angularjs.org/tutorial/step_08 weekly
+ http://docs.angularjs.org/tutorial/step_09 weekly
+ http://docs.angularjs.org/tutorial/step_10 weekly
+ http://docs.angularjs.org/tutorial/step_11 weekly
+ http://docs.angularjs.org/tutorial/the_end weekly
+ http://docs.angularjs.org/api/angular.lowercase weekly
+ http://docs.angularjs.org/api/angular.uppercase weekly
+ http://docs.angularjs.org/api/angular.forEach weekly
+ http://docs.angularjs.org/api/angular.extend weekly
+ http://docs.angularjs.org/api/angular.noop weekly
+ http://docs.angularjs.org/api/angular.identity weekly
+ http://docs.angularjs.org/api/angular.isUndefined weekly
+ http://docs.angularjs.org/api/angular.isDefined weekly
+ http://docs.angularjs.org/api/angular.isObject weekly
+ http://docs.angularjs.org/api/angular.isString weekly
+ http://docs.angularjs.org/api/angular.isNumber weekly
+ http://docs.angularjs.org/api/angular.isDate weekly
+ http://docs.angularjs.org/api/angular.isArray weekly
+ http://docs.angularjs.org/api/angular.isFunction weekly
+ http://docs.angularjs.org/api/angular.isElement weekly
+ http://docs.angularjs.org/api/angular.copy weekly
+ http://docs.angularjs.org/api/angular.equals weekly
+ http://docs.angularjs.org/api/angular.bind weekly
+ http://docs.angularjs.org/api/angular.toJson weekly
+ http://docs.angularjs.org/api/angular.fromJson weekly
+ http://docs.angularjs.org/api/ng.directive:ngApp weekly
+ http://docs.angularjs.org/api/angular.bootstrap weekly
+ http://docs.angularjs.org/api/angular.version weekly
+ http://docs.angularjs.org/api/angular.injector weekly
+ http://docs.angularjs.org/api/AUTO weekly
+ http://docs.angularjs.org/api/AUTO.$injector weekly
+ http://docs.angularjs.org/api/AUTO.$provide weekly
+ http://docs.angularjs.org/api/angular.element weekly
+ http://docs.angularjs.org/api/angular.Module weekly
+ http://docs.angularjs.org/api/angular.module weekly
+ http://docs.angularjs.org/api/ng.$anchorScroll weekly
+ http://docs.angularjs.org/api/ng.$cacheFactory weekly
+ http://docs.angularjs.org/api/ng.$templateCache weekly
+ http://docs.angularjs.org/api/ng.$controllerProvider weekly
+ http://docs.angularjs.org/api/ng.$controller weekly
+ http://docs.angularjs.org/api/ng.$compile weekly
+ http://docs.angularjs.org/api/ng.$compileProvider weekly
+ http://docs.angularjs.org/api/ng.$compile.directive.Attributes weekly
+ http://docs.angularjs.org/api/ng.directive:a weekly
+ http://docs.angularjs.org/api/ng.directive:ngHref weekly
+ http://docs.angularjs.org/api/ng.directive:ngSrc weekly
+ http://docs.angularjs.org/api/ng.directive:ngDisabled weekly
+ http://docs.angularjs.org/api/ng.directive:ngChecked weekly
+ http://docs.angularjs.org/api/ng.directive:ngMultiple weekly
+ http://docs.angularjs.org/api/ng.directive:ngReadonly weekly
+ http://docs.angularjs.org/api/ng.directive:ngSelected weekly
+ http://docs.angularjs.org/api/ng.directive:ngOpen weekly
+ http://docs.angularjs.org/api/ng.directive:form.FormController weekly
+ http://docs.angularjs.org/api/ng.directive:ngForm weekly
+ http://docs.angularjs.org/api/ng.directive:form weekly
+ http://docs.angularjs.org/api/ng.directive:ngBind weekly
+ http://docs.angularjs.org/api/ng.directive:ngBindTemplate weekly
+ http://docs.angularjs.org/api/ng.directive:ngBindHtmlUnsafe weekly
+ http://docs.angularjs.org/api/ng.directive:input.text weekly
+ http://docs.angularjs.org/api/ng.directive:input.number weekly
+ http://docs.angularjs.org/api/ng.directive:input.url weekly
+ http://docs.angularjs.org/api/ng.directive:input.email weekly
+ http://docs.angularjs.org/api/ng.directive:input.radio weekly
+ http://docs.angularjs.org/api/ng.directive:input.checkbox weekly
+ http://docs.angularjs.org/api/ng.directive:textarea weekly
+ http://docs.angularjs.org/api/ng.directive:input weekly
+ http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController weekly
+ http://docs.angularjs.org/api/ng.directive:ngModel weekly
+ http://docs.angularjs.org/api/ng.directive:ngChange weekly
+ http://docs.angularjs.org/api/ng.directive:ngList weekly
+ http://docs.angularjs.org/api/ng.directive:ngClass weekly
+ http://docs.angularjs.org/api/ng.directive:ngClassOdd weekly
+ http://docs.angularjs.org/api/ng.directive:ngClassEven weekly
+ http://docs.angularjs.org/api/ng.directive:ngCloak weekly
+ http://docs.angularjs.org/api/ng.directive:ngController weekly
+ http://docs.angularjs.org/api/ng.directive:ngCsp weekly
+ http://docs.angularjs.org/api/ng.directive:ngClick weekly
+ http://docs.angularjs.org/api/ng.directive:ngDblclick weekly
+ http://docs.angularjs.org/api/ng.directive:ngMousedown weekly
+ http://docs.angularjs.org/api/ng.directive:ngMouseup weekly
+ http://docs.angularjs.org/api/ng.directive:ngMouseover weekly
+ http://docs.angularjs.org/api/ng.directive:ngMouseenter weekly
+ http://docs.angularjs.org/api/ng.directive:ngMouseleave weekly
+ http://docs.angularjs.org/api/ng.directive:ngMousemove weekly
+ http://docs.angularjs.org/api/ng.directive:ngKeydown weekly
+ http://docs.angularjs.org/api/ng.directive:ngKeyup weekly
+ http://docs.angularjs.org/api/ng.directive:ngSubmit weekly
+ http://docs.angularjs.org/api/ng.directive:ngInclude weekly
+ http://docs.angularjs.org/api/ng.directive:ngInit weekly
+ http://docs.angularjs.org/api/ng.directive:ngNonBindable weekly
+ http://docs.angularjs.org/api/ng.directive:ngPluralize weekly
+ http://docs.angularjs.org/api/ng.directive:ngRepeat weekly
+ http://docs.angularjs.org/api/ng.directive:ngShow weekly
+ http://docs.angularjs.org/api/ng.directive:ngHide weekly
+ http://docs.angularjs.org/api/ng.directive:ngStyle weekly
+ http://docs.angularjs.org/api/ng.directive:ngSwitch weekly
+ http://docs.angularjs.org/api/ng.directive:ngTransclude weekly
+ http://docs.angularjs.org/api/ng.directive:ngView weekly
+ http://docs.angularjs.org/api/ng.directive:script weekly
+ http://docs.angularjs.org/api/ng.directive:select weekly
+ http://docs.angularjs.org/api/ng.$document weekly
+ http://docs.angularjs.org/api/ng.$exceptionHandler weekly
+ http://docs.angularjs.org/api/ng.filter:filter weekly
+ http://docs.angularjs.org/api/ng.filter:currency weekly
+ http://docs.angularjs.org/api/ng.filter:number weekly
+ http://docs.angularjs.org/api/ng.filter:date weekly
+ http://docs.angularjs.org/api/ng.filter:json weekly
+ http://docs.angularjs.org/api/ng.filter:lowercase weekly
+ http://docs.angularjs.org/api/ng.filter:uppercase weekly
+ http://docs.angularjs.org/api/ng.filter:limitTo weekly
+ http://docs.angularjs.org/api/ng.filter:orderBy weekly
+ http://docs.angularjs.org/api/ng.$filterProvider weekly
+ http://docs.angularjs.org/api/ng.$filter weekly
+ http://docs.angularjs.org/api/ng.$http weekly
+ http://docs.angularjs.org/api/ng.$httpBackend weekly
+ http://docs.angularjs.org/api/ng.$interpolateProvider weekly
+ http://docs.angularjs.org/api/ng.$interpolate weekly
+ http://docs.angularjs.org/api/ng.$locale weekly
+ http://docs.angularjs.org/api/ng.$location weekly
+ http://docs.angularjs.org/api/ng.$locationProvider weekly
+ http://docs.angularjs.org/api/ng.$log weekly
+ http://docs.angularjs.org/api/ng.$logProvider weekly
+ http://docs.angularjs.org/api/ng.$parse weekly
+ http://docs.angularjs.org/api/ng.$q weekly
+ http://docs.angularjs.org/api/ng.$rootElement weekly
+ http://docs.angularjs.org/api/ng.$rootScopeProvider weekly
+ http://docs.angularjs.org/api/ng.$rootScope weekly
+ http://docs.angularjs.org/api/ng.$rootScope.Scope weekly
+ http://docs.angularjs.org/api/ng.$routeProvider weekly
+ http://docs.angularjs.org/api/ng.$route weekly
+ http://docs.angularjs.org/api/ng.$routeParams weekly
+ http://docs.angularjs.org/api/ng.$timeout weekly
+ http://docs.angularjs.org/api/ng.$window weekly
+ http://docs.angularjs.org/api/ngCookies weekly
+ http://docs.angularjs.org/api/ngCookies.$cookies weekly
+ http://docs.angularjs.org/api/ngCookies.$cookieStore weekly
+ http://docs.angularjs.org/api/ngResource weekly
+ http://docs.angularjs.org/api/ngResource.$resource weekly
+ http://docs.angularjs.org/api/angular.mock weekly
+ http://docs.angularjs.org/api/ngMock.$exceptionHandlerProvider weekly
+ http://docs.angularjs.org/api/ngMock.$exceptionHandler weekly
+ http://docs.angularjs.org/api/ngMock.$log weekly
+ http://docs.angularjs.org/api/angular.mock.TzDate weekly
+ http://docs.angularjs.org/api/angular.mock.dump weekly
+ http://docs.angularjs.org/api/ngMock.$httpBackend weekly
+ http://docs.angularjs.org/api/ngMock.$timeout weekly
+ http://docs.angularjs.org/api/ngMock weekly
+ http://docs.angularjs.org/api/ngMockE2E weekly
+ http://docs.angularjs.org/api/ngMockE2E.$httpBackend weekly
+ http://docs.angularjs.org/api/angular.mock.module weekly
+ http://docs.angularjs.org/api/angular.mock.inject weekly
+ http://docs.angularjs.org/api/ngSanitize.directive:ngBindHtml weekly
+ http://docs.angularjs.org/api/ngSanitize.filter:linky weekly
+ http://docs.angularjs.org/api/ngSanitize weekly
+ http://docs.angularjs.org/api/ngSanitize.$sanitize weekly
+
diff --git a/lib/resize/resize.js b/lib/resize/resize.js
new file mode 100644
index 0000000..35aa96f
--- /dev/null
+++ b/lib/resize/resize.js
@@ -0,0 +1,450 @@
+//JavaScript Image Resizer (c) 2012 - Grant Galitz
+var scripts = document.getElementsByTagName("script");
+var sourceOfWorker = scripts[scripts.length-1].src;
+function Resize(widthOriginal, heightOriginal, targetWidth, targetHeight, blendAlpha, interpolationPass, useWebWorker, resizeCallback) {
+ this.widthOriginal = Math.abs(parseInt(widthOriginal) || 0);
+ this.heightOriginal = Math.abs(parseInt(heightOriginal) || 0);
+ this.targetWidth = Math.abs(parseInt(targetWidth) || 0);
+ this.targetHeight = Math.abs(parseInt(targetHeight) || 0);
+ this.colorChannels = (!!blendAlpha) ? 4 : 3;
+ this.interpolationPass = !!interpolationPass;
+ this.useWebWorker = !!useWebWorker;
+ this.resizeCallback = (typeof resizeCallback == "function") ? resizeCallback : function (returnedArray) {};
+ this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels;
+ this.originalWidthMultipliedByChannels = this.widthOriginal * this.colorChannels;
+ this.originalHeightMultipliedByChannels = this.heightOriginal * this.colorChannels;
+ this.widthPassResultSize = this.targetWidthMultipliedByChannels * this.heightOriginal;
+ this.finalResultSize = this.targetWidthMultipliedByChannels * this.targetHeight;
+ this.initialize();
+}
+Resize.prototype.initialize = function () {
+ //Perform some checks:
+ if (this.widthOriginal > 0 && this.heightOriginal > 0 && this.targetWidth > 0 && this.targetHeight > 0) {
+ if (this.useWebWorker) {
+ this.useWebWorker = (this.widthOriginal != this.targetWidth || this.heightOriginal != this.targetHeight);
+ if (this.useWebWorker) {
+ this.configureWorker();
+ }
+ }
+ if (!this.useWebWorker) {
+ this.configurePasses();
+ }
+ }
+ else {
+ throw(new Error("Invalid settings specified for the resizer."));
+ }
+}
+Resize.prototype.configureWorker = function () {
+ try {
+ var parentObj = this;
+ this.worker = new Worker(sourceOfWorker.substring(0, sourceOfWorker.length - 3) + "Worker.js");
+ this.worker.onmessage = function (event) {
+ parentObj.heightBuffer = event.data;
+ parentObj.resizeCallback(parentObj.heightBuffer);
+ }
+ this.worker.postMessage(["setup", this.widthOriginal, this.heightOriginal, this.targetWidth, this.targetHeight, this.colorChannels, this.interpolationPass]);
+ }
+ catch (error) {
+ this.useWebWorker = false;
+ }
+}
+Resize.prototype.configurePasses = function () {
+ if (this.widthOriginal == this.targetWidth) {
+ //Bypass the width resizer pass:
+ this.resizeWidth = this.bypassResizer;
+ }
+ else {
+ //Setup the width resizer pass:
+ this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth;
+ if (this.ratioWeightWidthPass < 1 && this.interpolationPass) {
+ this.initializeFirstPassBuffers(true);
+ this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthInterpolatedRGBA : this.resizeWidthInterpolatedRGB;
+ }
+ else {
+ this.initializeFirstPassBuffers(false);
+ this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthRGBA : this.resizeWidthRGB;
+ }
+ }
+ if (this.heightOriginal == this.targetHeight) {
+ //Bypass the height resizer pass:
+ this.resizeHeight = this.bypassResizer;
+ }
+ else {
+ //Setup the height resizer pass:
+ this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight;
+ if (this.ratioWeightHeightPass < 1 && this.interpolationPass) {
+ this.initializeSecondPassBuffers(true);
+ this.resizeHeight = this.resizeHeightInterpolated;
+ }
+ else {
+ this.initializeSecondPassBuffers(false);
+ this.resizeHeight = (this.colorChannels == 4) ? this.resizeHeightRGBA : this.resizeHeightRGB;
+ }
+ }
+}
+Resize.prototype.resizeWidthRGB = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var line = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 2;
+ var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 2;
+ var output = this.outputWidthWorkBench;
+ var outputBuffer = this.widthBuffer;
+ do {
+ for (line = 0; line < this.originalHeightMultipliedByChannels;) {
+ output[line++] = 0;
+ output[line++] = 0;
+ output[line++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset] * amountToNext;
+ }
+ currentPosition = actualPosition = actualPosition + 3;
+ weight -= amountToNext;
+ }
+ else {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels);
+ for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) {
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset] = output[line++] * ratioWeightDivisor;
+ }
+ outputOffset += 3;
+ } while (outputOffset < this.targetWidthMultipliedByChannels);
+ return outputBuffer;
+}
+Resize.prototype.resizeWidthInterpolatedRGB = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var weight = 0;
+ var finalOffset = 0;
+ var pixelOffset = 0;
+ var firstWeight = 0;
+ var secondWeight = 0;
+ var outputBuffer = this.widthBuffer;
+ //Handle for only one interpolation input being valid for start calculation:
+ for (var targetPosition = 0; weight < 1/3; targetPosition += 3, weight += ratioWeight) {
+ for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ }
+ }
+ //Adjust for overshoot of the last pass's counter:
+ weight -= 1/3;
+ for (var interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 3, weight += ratioWeight) {
+ //Calculate weightings:
+ secondWeight = weight % 1;
+ firstWeight = 1 - secondWeight;
+ //Interpolate:
+ for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 3; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 3] * secondWeight);
+ outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight);
+ outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight);
+ }
+ }
+ //Handle for only one interpolation input being valid for end calculation:
+ for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - 3; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 3) {
+ for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ }
+ }
+ return outputBuffer;
+}
+Resize.prototype.resizeWidthRGBA = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var line = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 3;
+ var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 3;
+ var output = this.outputWidthWorkBench;
+ var outputBuffer = this.widthBuffer;
+ do {
+ for (line = 0; line < this.originalHeightMultipliedByChannels;) {
+ output[line++] = 0;
+ output[line++] = 0;
+ output[line++] = 0;
+ output[line++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset] * amountToNext;
+ }
+ currentPosition = actualPosition = actualPosition + 4;
+ weight -= amountToNext;
+ }
+ else {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels);
+ for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) {
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset] = output[line++] * ratioWeightDivisor;
+ }
+ outputOffset += 4;
+ } while (outputOffset < this.targetWidthMultipliedByChannels);
+ return outputBuffer;
+}
+Resize.prototype.resizeWidthInterpolatedRGBA = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var weight = 0;
+ var finalOffset = 0;
+ var pixelOffset = 0;
+ var firstWeight = 0;
+ var secondWeight = 0;
+ var outputBuffer = this.widthBuffer;
+ //Handle for only one interpolation input being valid for start calculation:
+ for (var targetPosition = 0; weight < 1/3; targetPosition += 4, weight += ratioWeight) {
+ for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
+ }
+ }
+ //Adjust for overshoot of the last pass's counter:
+ weight -= 1/3;
+ for (var interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 4, weight += ratioWeight) {
+ //Calculate weightings:
+ secondWeight = weight % 1;
+ firstWeight = 1 - secondWeight;
+ //Interpolate:
+ for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 4; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight);
+ outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight);
+ outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 6] * secondWeight);
+ outputBuffer[finalOffset + 3] = (buffer[pixelOffset + 3] * firstWeight) + (buffer[pixelOffset + 7] * secondWeight);
+ }
+ }
+ //Handle for only one interpolation input being valid for end calculation:
+ for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - 4; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 4) {
+ for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
+ }
+ }
+ return outputBuffer;
+}
+Resize.prototype.resizeHeightRGB = function (buffer) {
+ var ratioWeight = this.ratioWeightHeightPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var output = this.outputHeightWorkBench;
+ var outputBuffer = this.heightBuffer;
+ do {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ }
+ currentPosition = actualPosition;
+ weight -= amountToNext;
+ }
+ else {
+ for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.widthPassResultSize);
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ }
+ } while (outputOffset < this.finalResultSize);
+ return outputBuffer;
+}
+Resize.prototype.resizeHeightInterpolated = function (buffer) {
+ var ratioWeight = this.ratioWeightHeightPass;
+ var weight = 0;
+ var finalOffset = 0;
+ var pixelOffset = 0;
+ var pixelOffsetAccumulated = 0;
+ var pixelOffsetAccumulated2 = 0;
+ var firstWeight = 0;
+ var secondWeight = 0;
+ var outputBuffer = this.heightBuffer;
+ //Handle for only one interpolation input being valid for start calculation:
+ for (; weight < 1/3; weight += ratioWeight) {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]);
+ }
+ }
+ //Adjust for overshoot of the last pass's counter:
+ weight -= 1/3;
+ for (var interpolationHeightSourceReadStop = this.heightOriginal - 1; weight < interpolationHeightSourceReadStop; weight += ratioWeight) {
+ //Calculate weightings:
+ secondWeight = weight % 1;
+ firstWeight = 1 - secondWeight;
+ //Interpolate:
+ pixelOffsetAccumulated = Math.floor(weight) * this.targetWidthMultipliedByChannels;
+ pixelOffsetAccumulated2 = pixelOffsetAccumulated + this.targetWidthMultipliedByChannels;
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) {
+ outputBuffer[finalOffset++] = Math.round((buffer[pixelOffsetAccumulated++] * firstWeight) + (buffer[pixelOffsetAccumulated2++] * secondWeight));
+ }
+ }
+ //Handle for only one interpolation input being valid for end calculation:
+ while (finalOffset < this.finalResultSize) {
+ for (pixelOffset = 0, pixelOffsetAccumulated = interpolationHeightSourceReadStop * this.targetWidthMultipliedByChannels; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) {
+ outputBuffer[finalOffset++] = Math.round(buffer[pixelOffsetAccumulated++]);
+ }
+ }
+ return outputBuffer;
+}
+Resize.prototype.resizeHeightRGBA = function (buffer) {
+ var ratioWeight = this.ratioWeightHeightPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var output = this.outputHeightWorkBench;
+ var outputBuffer = this.heightBuffer;
+ do {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ }
+ currentPosition = actualPosition;
+ weight -= amountToNext;
+ }
+ else {
+ for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.widthPassResultSize);
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ }
+ } while (outputOffset < this.finalResultSize);
+ return outputBuffer;
+}
+Resize.prototype.resize = function (buffer) {
+ if (this.useWebWorker) {
+ this.worker.postMessage(["resize", buffer]);
+ }
+ else {
+ this.resizeCallback(this.resizeHeight(this.resizeWidth(buffer)));
+ }
+}
+Resize.prototype.bypassResizer = function (buffer) {
+ //Just return the buffer passsed:
+ return buffer;
+}
+Resize.prototype.initializeFirstPassBuffers = function (BILINEARAlgo) {
+ //Initialize the internal width pass buffers:
+ this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize);
+ if (!BILINEARAlgo) {
+ this.outputWidthWorkBench = this.generateFloatBuffer(this.originalHeightMultipliedByChannels);
+ }
+}
+Resize.prototype.initializeSecondPassBuffers = function (BILINEARAlgo) {
+ //Initialize the internal height pass buffers:
+ this.heightBuffer = this.generateUint8Buffer(this.finalResultSize);
+ if (!BILINEARAlgo) {
+ this.outputHeightWorkBench = this.generateFloatBuffer(this.targetWidthMultipliedByChannels);
+ }
+}
+Resize.prototype.generateFloatBuffer = function (bufferLength) {
+ //Generate a float32 typed array buffer:
+ try {
+ return new Float32Array(bufferLength);
+ }
+ catch (error) {
+ return [];
+ }
+}
+Resize.prototype.generateUint8Buffer = function (bufferLength) {
+ //Generate a uint8 typed array buffer:
+ try {
+ return new Uint8Array(bufferLength);
+ }
+ catch (error) {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/lib/resize/resizeWorker.js b/lib/resize/resizeWorker.js
new file mode 100644
index 0000000..0a99db3
--- /dev/null
+++ b/lib/resize/resizeWorker.js
@@ -0,0 +1,429 @@
+//JavaScript Image Resizer (c) 2012 - Grant Galitz
+var resizeWorker = null;
+self.onmessage = function (event) {
+ switch (event.data[0]) {
+ case "setup":
+ resizeWorker = new Resize(event.data[1], event.data[2], event.data[3], event.data[4], event.data[5], event.data[6]);
+ break;
+ case "resize":
+ resizeWorker.resize(event.data[1]);
+ }
+}
+function Resize(widthOriginal, heightOriginal, targetWidth, targetHeight, colorChannels, interpolationPass) {
+ this.widthOriginal = widthOriginal;
+ this.heightOriginal = heightOriginal;
+ this.targetWidth = targetWidth;
+ this.targetHeight = targetHeight;
+ this.colorChannels = colorChannels;
+ this.interpolationPass = !!interpolationPass;
+ this.targetWidthMultipliedByChannels = this.targetWidth * this.colorChannels;
+ this.originalWidthMultipliedByChannels = this.widthOriginal * this.colorChannels;
+ this.originalHeightMultipliedByChannels = this.heightOriginal * this.colorChannels;
+ this.widthPassResultSize = this.targetWidthMultipliedByChannels * this.heightOriginal;
+ this.finalResultSize = this.targetWidthMultipliedByChannels * this.targetHeight;
+ this.initialize();
+}
+Resize.prototype.initialize = function () {
+ //Perform some checks:
+ if (this.widthOriginal > 0 && this.heightOriginal > 0 && this.targetWidth > 0 && this.targetHeight > 0) {
+ this.configurePasses();
+ }
+ else {
+ throw(new Error("Invalid settings specified for the resizer."));
+ }
+}
+Resize.prototype.configurePasses = function () {
+ if (this.widthOriginal == this.targetWidth) {
+ //Bypass the width resizer pass:
+ this.resizeWidth = this.bypassResizer;
+ }
+ else {
+ //Setup the width resizer pass:
+ this.ratioWeightWidthPass = this.widthOriginal / this.targetWidth;
+ if (this.ratioWeightWidthPass < 1 && this.interpolationPass) {
+ this.initializeFirstPassBuffers(true);
+ this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthInterpolatedRGBA : this.resizeWidthInterpolatedRGB;
+ }
+ else {
+ this.initializeFirstPassBuffers(false);
+ this.resizeWidth = (this.colorChannels == 4) ? this.resizeWidthRGBA : this.resizeWidthRGB;
+ }
+ }
+ if (this.heightOriginal == this.targetHeight) {
+ //Bypass the height resizer pass:
+ this.resizeHeight = this.bypassResizer;
+ }
+ else {
+ //Setup the height resizer pass:
+ this.ratioWeightHeightPass = this.heightOriginal / this.targetHeight;
+ if (this.ratioWeightHeightPass < 1 && this.interpolationPass) {
+ this.initializeSecondPassBuffers(true);
+ this.resizeHeight = this.resizeHeightInterpolated;
+ }
+ else {
+ this.initializeSecondPassBuffers(false);
+ this.resizeHeight = (this.colorChannels == 4) ? this.resizeHeightRGBA : this.resizeHeightRGB;
+ }
+ }
+}
+Resize.prototype.resizeWidthRGB = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var line = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 2;
+ var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 2;
+ var output = this.outputWidthWorkBench;
+ var outputBuffer = this.widthBuffer;
+ do {
+ for (line = 0; line < this.originalHeightMultipliedByChannels;) {
+ output[line++] = 0;
+ output[line++] = 0;
+ output[line++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset] * amountToNext;
+ }
+ currentPosition = actualPosition = actualPosition + 3;
+ weight -= amountToNext;
+ }
+ else {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels);
+ for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) {
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset] = output[line++] * ratioWeightDivisor;
+ }
+ outputOffset += 3;
+ } while (outputOffset < this.targetWidthMultipliedByChannels);
+ return outputBuffer;
+}
+Resize.prototype.resizeWidthInterpolatedRGB = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var weight = 0;
+ var finalOffset = 0;
+ var pixelOffset = 0;
+ var firstWeight = 0;
+ var secondWeight = 0;
+ var outputBuffer = this.widthBuffer;
+ //Handle for only one interpolation input being valid for start calculation:
+ for (var targetPosition = 0; weight < 1/3; targetPosition += 3, weight += ratioWeight) {
+ for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ }
+ }
+ //Adjust for overshoot of the last pass's counter:
+ weight -= 1/3;
+ for (var interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 3, weight += ratioWeight) {
+ //Calculate weightings:
+ secondWeight = weight % 1;
+ firstWeight = 1 - secondWeight;
+ //Interpolate:
+ for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 3; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 3] * secondWeight);
+ outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight);
+ outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight);
+ }
+ }
+ //Handle for only one interpolation input being valid for end calculation:
+ for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - 3; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 3) {
+ for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ }
+ }
+ return outputBuffer;
+}
+Resize.prototype.resizeWidthRGBA = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var line = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var nextLineOffsetOriginalWidth = this.originalWidthMultipliedByChannels - 3;
+ var nextLineOffsetTargetWidth = this.targetWidthMultipliedByChannels - 3;
+ var output = this.outputWidthWorkBench;
+ var outputBuffer = this.widthBuffer;
+ do {
+ for (line = 0; line < this.originalHeightMultipliedByChannels;) {
+ output[line++] = 0;
+ output[line++] = 0;
+ output[line++] = 0;
+ output[line++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset++] * amountToNext;
+ output[line++] += buffer[pixelOffset] * amountToNext;
+ }
+ currentPosition = actualPosition = actualPosition + 4;
+ weight -= amountToNext;
+ }
+ else {
+ for (line = 0, pixelOffset = actualPosition; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetOriginalWidth) {
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset++] * weight;
+ output[line++] += buffer[pixelOffset] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.originalWidthMultipliedByChannels);
+ for (line = 0, pixelOffset = outputOffset; line < this.originalHeightMultipliedByChannels; pixelOffset += nextLineOffsetTargetWidth) {
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset++] = output[line++] * ratioWeightDivisor;
+ outputBuffer[pixelOffset] = output[line++] * ratioWeightDivisor;
+ }
+ outputOffset += 4;
+ } while (outputOffset < this.targetWidthMultipliedByChannels);
+ return outputBuffer;
+}
+Resize.prototype.resizeWidthInterpolatedRGBA = function (buffer) {
+ var ratioWeight = this.ratioWeightWidthPass;
+ var weight = 0;
+ var finalOffset = 0;
+ var pixelOffset = 0;
+ var firstWeight = 0;
+ var secondWeight = 0;
+ var outputBuffer = this.widthBuffer;
+ //Handle for only one interpolation input being valid for start calculation:
+ for (var targetPosition = 0; weight < 1/3; targetPosition += 4, weight += ratioWeight) {
+ for (finalOffset = targetPosition, pixelOffset = 0; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
+ }
+ }
+ //Adjust for overshoot of the last pass's counter:
+ weight -= 1/3;
+ for (var interpolationWidthSourceReadStop = this.widthOriginal - 1; weight < interpolationWidthSourceReadStop; targetPosition += 4, weight += ratioWeight) {
+ //Calculate weightings:
+ secondWeight = weight % 1;
+ firstWeight = 1 - secondWeight;
+ //Interpolate:
+ for (finalOffset = targetPosition, pixelOffset = Math.floor(weight) * 4; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = (buffer[pixelOffset] * firstWeight) + (buffer[pixelOffset + 4] * secondWeight);
+ outputBuffer[finalOffset + 1] = (buffer[pixelOffset + 1] * firstWeight) + (buffer[pixelOffset + 5] * secondWeight);
+ outputBuffer[finalOffset + 2] = (buffer[pixelOffset + 2] * firstWeight) + (buffer[pixelOffset + 6] * secondWeight);
+ outputBuffer[finalOffset + 3] = (buffer[pixelOffset + 3] * firstWeight) + (buffer[pixelOffset + 7] * secondWeight);
+ }
+ }
+ //Handle for only one interpolation input being valid for end calculation:
+ for (interpolationWidthSourceReadStop = this.originalWidthMultipliedByChannels - 4; targetPosition < this.targetWidthMultipliedByChannels; targetPosition += 4) {
+ for (finalOffset = targetPosition, pixelOffset = interpolationWidthSourceReadStop; finalOffset < this.widthPassResultSize; pixelOffset += this.originalWidthMultipliedByChannels, finalOffset += this.targetWidthMultipliedByChannels) {
+ outputBuffer[finalOffset] = buffer[pixelOffset];
+ outputBuffer[finalOffset + 1] = buffer[pixelOffset + 1];
+ outputBuffer[finalOffset + 2] = buffer[pixelOffset + 2];
+ outputBuffer[finalOffset + 3] = buffer[pixelOffset + 3];
+ }
+ }
+ return outputBuffer;
+}
+Resize.prototype.resizeHeightRGB = function (buffer) {
+ var ratioWeight = this.ratioWeightHeightPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var output = this.outputHeightWorkBench;
+ var outputBuffer = this.heightBuffer;
+ do {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ }
+ currentPosition = actualPosition;
+ weight -= amountToNext;
+ }
+ else {
+ for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.widthPassResultSize);
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ }
+ } while (outputOffset < this.finalResultSize);
+ return outputBuffer;
+}
+Resize.prototype.resizeHeightInterpolated = function (buffer) {
+ var ratioWeight = this.ratioWeightHeightPass;
+ var weight = 0;
+ var finalOffset = 0;
+ var pixelOffset = 0;
+ var pixelOffsetAccumulated = 0;
+ var pixelOffsetAccumulated2 = 0;
+ var firstWeight = 0;
+ var secondWeight = 0;
+ var outputBuffer = this.heightBuffer;
+ //Handle for only one interpolation input being valid for start calculation:
+ for (; weight < 1/3; weight += ratioWeight) {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ outputBuffer[finalOffset++] = Math.round(buffer[pixelOffset++]);
+ }
+ }
+ //Adjust for overshoot of the last pass's counter:
+ weight -= 1/3;
+ for (var interpolationHeightSourceReadStop = this.heightOriginal - 1; weight < interpolationHeightSourceReadStop; weight += ratioWeight) {
+ //Calculate weightings:
+ secondWeight = weight % 1;
+ firstWeight = 1 - secondWeight;
+ //Interpolate:
+ pixelOffsetAccumulated = Math.floor(weight) * this.targetWidthMultipliedByChannels;
+ pixelOffsetAccumulated2 = pixelOffsetAccumulated + this.targetWidthMultipliedByChannels;
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) {
+ outputBuffer[finalOffset++] = Math.round((buffer[pixelOffsetAccumulated++] * firstWeight) + (buffer[pixelOffsetAccumulated2++] * secondWeight));
+ }
+ }
+ //Handle for only one interpolation input being valid for end calculation:
+ while (finalOffset < this.finalResultSize) {
+ for (pixelOffset = 0, pixelOffsetAccumulated = interpolationHeightSourceReadStop * this.targetWidthMultipliedByChannels; pixelOffset < this.targetWidthMultipliedByChannels; ++pixelOffset) {
+ outputBuffer[finalOffset++] = Math.round(buffer[pixelOffsetAccumulated++]);
+ }
+ }
+ return outputBuffer;
+}
+Resize.prototype.resizeHeightRGBA = function (buffer) {
+ var ratioWeight = this.ratioWeightHeightPass;
+ var ratioWeightDivisor = 1 / ratioWeight;
+ var weight = 0;
+ var amountToNext = 0;
+ var actualPosition = 0;
+ var currentPosition = 0;
+ var pixelOffset = 0;
+ var outputOffset = 0;
+ var output = this.outputHeightWorkBench;
+ var outputBuffer = this.heightBuffer;
+ do {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ output[pixelOffset++] = 0;
+ }
+ weight = ratioWeight;
+ do {
+ amountToNext = 1 + actualPosition - currentPosition;
+ if (weight >= amountToNext) {
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ output[pixelOffset++] += buffer[actualPosition++] * amountToNext;
+ }
+ currentPosition = actualPosition;
+ weight -= amountToNext;
+ }
+ else {
+ for (pixelOffset = 0, amountToNext = actualPosition; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ output[pixelOffset++] += buffer[amountToNext++] * weight;
+ }
+ currentPosition += weight;
+ break;
+ }
+ } while (weight > 0 && actualPosition < this.widthPassResultSize);
+ for (pixelOffset = 0; pixelOffset < this.targetWidthMultipliedByChannels;) {
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ outputBuffer[outputOffset++] = Math.round(output[pixelOffset++] * ratioWeightDivisor);
+ }
+ } while (outputOffset < this.finalResultSize);
+ return outputBuffer;
+}
+Resize.prototype.resize = function (buffer) {
+ self.postMessage(this.resizeHeight(this.resizeWidth(buffer)));
+}
+Resize.prototype.bypassResizer = function (buffer) {
+ //Just return the buffer passsed:
+ return buffer;
+}
+Resize.prototype.initializeFirstPassBuffers = function (BILINEARAlgo) {
+ //Initialize the internal width pass buffers:
+ this.widthBuffer = this.generateFloatBuffer(this.widthPassResultSize);
+ if (!BILINEARAlgo) {
+ this.outputWidthWorkBench = this.generateFloatBuffer(this.originalHeightMultipliedByChannels);
+ }
+}
+Resize.prototype.initializeSecondPassBuffers = function (BILINEARAlgo) {
+ //Initialize the internal height pass buffers:
+ this.heightBuffer = this.generateUint8Buffer(this.finalResultSize);
+ if (!BILINEARAlgo) {
+ this.outputHeightWorkBench = this.generateFloatBuffer(this.targetWidthMultipliedByChannels);
+ }
+}
+Resize.prototype.generateFloatBuffer = function (bufferLength) {
+ //Generate a float32 typed array buffer:
+ try {
+ return new Float32Array(bufferLength);
+ }
+ catch (error) {
+ return [];
+ }
+}
+Resize.prototype.generateUint8Buffer = function (bufferLength) {
+ //Generate a uint8 typed array buffer:
+ try {
+ return new Uint8Array(bufferLength);
+ }
+ catch (error) {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/manifest.webapp b/manifest.webapp
index 6cf1199..ceda86e 100644
--- a/manifest.webapp
+++ b/manifest.webapp
@@ -1,6 +1,6 @@
{
"name": "Podcast",
- "version": "0.0.1",
+ "version": "0.1.0",
"description": "Podcast Manager for Firefox OS",
"icons": {
"128": "/blaIcon128.png"
@@ -10,6 +10,7 @@
"name": "Colin Frei",
"url": "http://colinfrei.com"
},
+ "type": "privileged",
"permissions": {
"storage": { "description": "Allow Podcast to store podcast information for offline use" },
"systemXHR": { "description": "Allow Podcast to download podcast information" },
diff --git a/package.manifest b/package.manifest
index 5a2d462..ce99da0 100644
--- a/package.manifest
+++ b/package.manifest
@@ -1,5 +1,5 @@
{
"name": "Podcast",
- "package_path": "http://labs.colinfrei.com/package.zip",
- "version": "1.0"
+ "package_path": "http://192.168.100.37/package.zip",
+ "version": "1.1"
}
\ No newline at end of file
diff --git a/partials/feed.html b/partials/feed.html
index b6902cb..0de4b44 100644
--- a/partials/feed.html
+++ b/partials/feed.html
@@ -5,9 +5,9 @@
{{ feed.author }}
-
Delete Feed
+
Delete Feed
Number of Items to keep in Queue
-
+
1
2
3
diff --git a/partials/importGoogle.html b/partials/importGoogle.html
new file mode 100644
index 0000000..5cd65c9
--- /dev/null
+++ b/partials/importGoogle.html
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/partials/info.html b/partials/info.html
index 6e36639..7e99c38 100644
--- a/partials/info.html
+++ b/partials/info.html
@@ -5,10 +5,9 @@
The following libraries are used to build this app:
- TODO: link all of that
\ No newline at end of file
diff --git a/partials/listFeeds.html b/partials/listFeeds.html
index 0fa9504..a6cc95a 100644
--- a/partials/listFeeds.html
+++ b/partials/listFeeds.html
@@ -3,7 +3,5 @@
-
- {{feed.id}}
- {{feed.title}}
+
{{feed.title}}
\ No newline at end of file
diff --git a/partials/settings.html b/partials/settings.html
index a6f2ea1..6ca6835 100644
--- a/partials/settings.html
+++ b/partials/settings.html
@@ -1,14 +1,13 @@
Refresh Subscriptions:
-
+
Only Download on Wi-Fi
-
+
Download Files
- Load Fixtures
-
-
-Install!
+ Load Fixtures
+ Import Feeds from Google
+
\ No newline at end of file
diff --git a/scripts/e2e-test.bat b/scripts/e2e-test.bat
new file mode 100644
index 0000000..c89a708
--- /dev/null
+++ b/scripts/e2e-test.bat
@@ -0,0 +1,11 @@
+@echo off
+
+REM Windows script for running e2e tests
+REM You have to run server and capture some browser first
+REM
+REM Requirements:
+REM - NodeJS (http://nodejs.org/)
+REM - Testacular (npm install -g testacular)
+
+set BASE_DIR=%~dp0
+testacular start "%BASE_DIR%\..\config\testacular-e2e.conf.js" %*
diff --git a/scripts/e2e-test.sh b/scripts/e2e-test.sh
new file mode 100755
index 0000000..e887c34
--- /dev/null
+++ b/scripts/e2e-test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+BASE_DIR=`dirname $0`
+
+echo ""
+echo "Starting Testacular Server (http://vojtajina.github.com/testacular)"
+echo "-------------------------------------------------------------------"
+
+testacular start $BASE_DIR/../config/testacular-e2e.conf.js $*
diff --git a/scripts/test.bat b/scripts/test.bat
new file mode 100644
index 0000000..000242f
--- /dev/null
+++ b/scripts/test.bat
@@ -0,0 +1,11 @@
+@echo off
+
+REM Windows script for running unit tests
+REM You have to run server and capture some browser first
+REM
+REM Requirements:
+REM - NodeJS (http://nodejs.org/)
+REM - Testacular (npm install -g testacular)
+
+set BASE_DIR=%~dp0
+testacular start "%BASE_DIR%\..\config\testacular.conf.js" %*
diff --git a/scripts/test.sh b/scripts/test.sh
new file mode 100755
index 0000000..4c37cde
--- /dev/null
+++ b/scripts/test.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+BASE_DIR=`dirname $0`
+
+echo ""
+echo "Starting Testacular Server (http://vojtajina.github.com/testacular)"
+echo "-------------------------------------------------------------------"
+
+testacular start $BASE_DIR/../config/testacular.conf.js $*
diff --git a/scripts/watchr.rb b/scripts/watchr.rb
new file mode 100755
index 0000000..89ef656
--- /dev/null
+++ b/scripts/watchr.rb
@@ -0,0 +1,19 @@
+#!/usr/bin/env watchr
+
+# config file for watchr http://github.com/mynyml/watchr
+# install: gem install watchr
+# run: watch watchr.rb
+# note: make sure that you have jstd server running (server.sh) and a browser captured
+
+log_file = File.expand_path(File.dirname(__FILE__) + '/../logs/jstd.log')
+
+`cd ..`
+`touch #{log_file}`
+
+puts "String watchr... log file: #{log_file}"
+
+watch( '(app/js|test/unit)' ) do
+ `echo "\n\ntest run started @ \`date\`" > #{log_file}`
+ `scripts/test.sh &> #{log_file}`
+end
+
diff --git a/scripts/web-server.js b/scripts/web-server.js
new file mode 100755
index 0000000..3f74441
--- /dev/null
+++ b/scripts/web-server.js
@@ -0,0 +1,244 @@
+#!/usr/bin/env node
+
+var util = require('util'),
+ http = require('http'),
+ fs = require('fs'),
+ url = require('url'),
+ events = require('events');
+
+var DEFAULT_PORT = 8000;
+
+function main(argv) {
+ new HttpServer({
+ 'GET': createServlet(StaticServlet),
+ 'HEAD': createServlet(StaticServlet)
+ }).start(Number(argv[2]) || DEFAULT_PORT);
+}
+
+function escapeHtml(value) {
+ return value.toString().
+ replace('<', '<').
+ replace('>', '>').
+ replace('"', '"');
+}
+
+function createServlet(Class) {
+ var servlet = new Class();
+ return servlet.handleRequest.bind(servlet);
+}
+
+/**
+ * An Http server implementation that uses a map of methods to decide
+ * action routing.
+ *
+ * @param {Object} Map of method => Handler function
+ */
+function HttpServer(handlers) {
+ this.handlers = handlers;
+ this.server = http.createServer(this.handleRequest_.bind(this));
+}
+
+HttpServer.prototype.start = function(port) {
+ this.port = port;
+ this.server.listen(port);
+ util.puts('Http Server running at http://localhost:' + port + '/');
+};
+
+HttpServer.prototype.parseUrl_ = function(urlString) {
+ var parsed = url.parse(urlString);
+ parsed.pathname = url.resolve('/', parsed.pathname);
+ return url.parse(url.format(parsed), true);
+};
+
+HttpServer.prototype.handleRequest_ = function(req, res) {
+ var logEntry = req.method + ' ' + req.url;
+ if (req.headers['user-agent']) {
+ logEntry += ' ' + req.headers['user-agent'];
+ }
+ util.puts(logEntry);
+ req.url = this.parseUrl_(req.url);
+ var handler = this.handlers[req.method];
+ if (!handler) {
+ res.writeHead(501);
+ res.end();
+ } else {
+ handler.call(this, req, res);
+ }
+};
+
+/**
+ * Handles static content.
+ */
+function StaticServlet() {}
+
+StaticServlet.MimeMap = {
+ 'txt': 'text/plain',
+ 'html': 'text/html',
+ 'css': 'text/css',
+ 'xml': 'application/xml',
+ 'json': 'application/json',
+ 'js': 'application/javascript',
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'gif': 'image/gif',
+ 'png': 'image/png',
+ 'svg': 'image/svg+xml'
+};
+
+StaticServlet.prototype.handleRequest = function(req, res) {
+ var self = this;
+ var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
+ return String.fromCharCode(parseInt(hex, 16));
+ });
+ var parts = path.split('/');
+ if (parts[parts.length-1].charAt(0) === '.')
+ return self.sendForbidden_(req, res, path);
+ fs.stat(path, function(err, stat) {
+ if (err)
+ return self.sendMissing_(req, res, path);
+ if (stat.isDirectory())
+ return self.sendDirectory_(req, res, path);
+ return self.sendFile_(req, res, path);
+ });
+}
+
+StaticServlet.prototype.sendError_ = function(req, res, error) {
+ res.writeHead(500, {
+ 'Content-Type': 'text/html'
+ });
+ res.write('\n');
+ res.write('
Internal Server Error \n');
+ res.write('
Internal Server Error ');
+ res.write('
' + escapeHtml(util.inspect(error)) + ' ');
+ util.puts('500 Internal Server Error');
+ util.puts(util.inspect(error));
+};
+
+StaticServlet.prototype.sendMissing_ = function(req, res, path) {
+ path = path.substring(1);
+ res.writeHead(404, {
+ 'Content-Type': 'text/html'
+ });
+ res.write('\n');
+ res.write('
404 Not Found \n');
+ res.write('
Not Found ');
+ res.write(
+ '
The requested URL ' +
+ escapeHtml(path) +
+ ' was not found on this server.
'
+ );
+ res.end();
+ util.puts('404 Not Found: ' + path);
+};
+
+StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
+ path = path.substring(1);
+ res.writeHead(403, {
+ 'Content-Type': 'text/html'
+ });
+ res.write('\n');
+ res.write('
403 Forbidden \n');
+ res.write('
Forbidden ');
+ res.write(
+ '
You do not have permission to access ' +
+ escapeHtml(path) + ' on this server.
'
+ );
+ res.end();
+ util.puts('403 Forbidden: ' + path);
+};
+
+StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
+ res.writeHead(301, {
+ 'Content-Type': 'text/html',
+ 'Location': redirectUrl
+ });
+ res.write('\n');
+ res.write('
301 Moved Permanently \n');
+ res.write('
Moved Permanently ');
+ res.write(
+ '
The document has moved here .
'
+ );
+ res.end();
+ util.puts('301 Moved Permanently: ' + redirectUrl);
+};
+
+StaticServlet.prototype.sendFile_ = function(req, res, path) {
+ var self = this;
+ var file = fs.createReadStream(path);
+ res.writeHead(200, {
+ 'Content-Type': StaticServlet.
+ MimeMap[path.split('.').pop()] || 'text/plain'
+ });
+ if (req.method === 'HEAD') {
+ res.end();
+ } else {
+ file.on('data', res.write.bind(res));
+ file.on('close', function() {
+ res.end();
+ });
+ file.on('error', function(error) {
+ self.sendError_(req, res, error);
+ });
+ }
+};
+
+StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
+ var self = this;
+ if (path.match(/[^\/]$/)) {
+ req.url.pathname += '/';
+ var redirectUrl = url.format(url.parse(url.format(req.url)));
+ return self.sendRedirect_(req, res, redirectUrl);
+ }
+ fs.readdir(path, function(err, files) {
+ if (err)
+ return self.sendError_(req, res, error);
+
+ if (!files.length)
+ return self.writeDirectoryIndex_(req, res, path, []);
+
+ var remaining = files.length;
+ files.forEach(function(fileName, index) {
+ fs.stat(path + '/' + fileName, function(err, stat) {
+ if (err)
+ return self.sendError_(req, res, err);
+ if (stat.isDirectory()) {
+ files[index] = fileName + '/';
+ }
+ if (!(--remaining))
+ return self.writeDirectoryIndex_(req, res, path, files);
+ });
+ });
+ });
+};
+
+StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
+ path = path.substring(1);
+ res.writeHead(200, {
+ 'Content-Type': 'text/html'
+ });
+ if (req.method === 'HEAD') {
+ res.end();
+ return;
+ }
+ res.write('\n');
+ res.write('
' + escapeHtml(path) + ' \n');
+ res.write('\n');
+ res.write('
Directory: ' + escapeHtml(path) + ' ');
+ res.write('
');
+ files.forEach(function(fileName) {
+ if (fileName.charAt(0) !== '.') {
+ res.write('' +
+ escapeHtml(fileName) + ' ');
+ }
+ });
+ res.write(' ');
+ res.end();
+};
+
+// Must be last,
+main(process.argv);
diff --git a/test/fixtures/_setup.js b/test/fixtures/_setup.js
new file mode 100644
index 0000000..cb60b7c
--- /dev/null
+++ b/test/fixtures/_setup.js
@@ -0,0 +1 @@
+var testFixtures = {};
\ No newline at end of file
diff --git a/test/fixtures/feedXml_xml.js b/test/fixtures/feedXml_xml.js
new file mode 100644
index 0000000..c10a249
--- /dev/null
+++ b/test/fixtures/feedXml_xml.js
@@ -0,0 +1,6 @@
+testFixtures.feedXml = '
Example Example Description http://www.srf.ch/sendungen/espressode Sat, 02 Mar 2013 21:53:12 +0100 Generator foo@example.com (Foo) Copyright: Example 15 Example Author Subtitle Example Description Schweizer Radio und Fernsehen (SRF) srf@srf.ch (SRF) Schweiz, Politik, Wirtschaft, Nachrichten, Radio, Information, SRF, Sport, Gesellschaft, International, Kultur no http://www.srf.ch/sendungen/espressohttp://epg.drs.ch/asset/image/audio/ad27344a-ec61-4f5d-80c6-28eadc689b1d/PODCAST.jpg Espresso ' +
+ 'Example Item 1 Example Description 1 http://example.org/podcast/1.mp3 Schweizer Radio und Fernsehen (SRF) Example Item 1 Example Description 1 no 00:12:41 srf@srf.ch (SRF) Fri, 01 Mar 2013 09:00:00 +0100 ' +
+ 'Example Item 2 Example Description 2 http://example.org/podcast/2.mp3 Schweizer Radio und Fernsehen (SRF) Example Item 2 Example Description 2 no 00:13:25 srf@srf.ch (SRF) Thu, 28 Feb 2013 09:00:00 +0100 ' +
+ 'Example Item 3 Example Description 3 http://example.org/podcast/3.mp3 Schweizer Radio und Fernsehen (SRF) Example Item 3 Example Description 3 no 00:13:59 srf@srf.ch (SRF) Wed, 27 Feb 2013 09:00:00 +0100 ' +
+ 'Example Item 4 Example Description 4 http://example.org/podcast/4.mp3 Schweizer Radio und Fernsehen (SRF) Example Item 4 Example Description 4 no 00:09:29 srf@srf.ch (SRF) Tue, 26 Feb 2013 09:00:00 +0100 ' +
+ '';
\ No newline at end of file
diff --git a/test/fixtures/googleReaderSubscriptions_json.js b/test/fixtures/googleReaderSubscriptions_json.js
new file mode 100644
index 0000000..0654c86
--- /dev/null
+++ b/test/fixtures/googleReaderSubscriptions_json.js
@@ -0,0 +1,53 @@
+testFixtures.googleReaderSubscriptions = {
+ "subscriptions": [{
+ "id": "feed/http://feeds.5by5.tv/b2w-afterdark",
+ "title": "Back to Work with After Dark Post-Shows",
+ "categories": [{
+ "id": "user/17796086709166896494/label/Listen Subscriptions",
+ "label": "Listen Subscriptions"
+ }],
+ "sortid": "A22E8D57",
+ "firstitemmsec": "1349354159187",
+ "htmlUrl": "http://5by5.tv/b2w"
+ }, {
+ "id": "feed/http://pod.drs.ch/heutemorgen_mpx.xml",
+ "title": "HeuteMorgen",
+ "categories": [{
+ "id": "user/17796086709166896494/label/Listen Subscriptions",
+ "label": "Listen Subscriptions"
+ }],
+ "sortid": "37DCFED2",
+ "firstitemmsec": "1317447602448",
+ "htmlUrl": "http://www.srf.ch/sendungen/heute-aktuell"
+ }, {
+ "id": "feed/http://feeds.feedburner.com/html5rocks",
+ "title": "HTML5Rocks",
+ "categories": [],
+ "sortid": "B9FAB293",
+ "firstitemmsec": "1329860143802",
+ "htmlUrl": "http://pipes.yahoo.com/pipes/pipe.info?_id\u003d647030be6aceb6d005c3775a1c19401c"
+ }, {
+ "id": "feed/http://feeds.liip.ch/liip_news_de",
+ "title": "Liip AG",
+ "categories": [],
+ "sortid": "4B59A186",
+ "firstitemmsec": "1297075725149"
+ }, {
+ "id": "feed/http://feeds.liip.ch/liip_blog",
+ "title": "Liip Blog",
+ "categories": [],
+ "sortid": "DE584526",
+ "firstitemmsec": "1298540934688",
+ "htmlUrl": "http://blog.liip.ch/"
+ }, {
+ "id": "feed/http://pod.drs.ch/mailbox_mpx.xml",
+ "title": "Mailbox",
+ "categories": [{
+ "id": "user/17796086709166896494/label/Listen Subscriptions",
+ "label": "Listen Subscriptions"
+ }],
+ "sortid": "BFB2E757",
+ "firstitemmsec": "1314704877277",
+ "htmlUrl": "http://www.srf.ch/podcasts"
+ }]
+};
\ No newline at end of file
diff --git a/test/lib/angular/angular-mocks.js b/test/lib/angular/angular-mocks.js
new file mode 100644
index 0000000..108b448
--- /dev/null
+++ b/test/lib/angular/angular-mocks.js
@@ -0,0 +1,1790 @@
+/**
+ * @license AngularJS v1.1.2
+ * (c) 2010-2012 Google, Inc. http://angularjs.org
+ * License: MIT
+ *
+ * TODO(vojta): wrap whole file into closure during build
+ */
+
+/**
+ * @ngdoc overview
+ * @name angular.mock
+ * @description
+ *
+ * Namespace from 'angular-mocks.js' which contains testing related code.
+ */
+angular.mock = {};
+
+/**
+ * ! This is a private undocumented service !
+ *
+ * @name ngMock.$browser
+ *
+ * @description
+ * This service is a mock implementation of {@link ng.$browser}. It provides fake
+ * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr,
+ * cookies, etc...
+ *
+ * The api of this service is the same as that of the real {@link ng.$browser $browser}, except
+ * that there are several helper methods available which can be used in tests.
+ */
+angular.mock.$BrowserProvider = function() {
+ this.$get = function(){
+ return new angular.mock.$Browser();
+ };
+};
+
+angular.mock.$Browser = function() {
+ var self = this;
+
+ this.isMock = true;
+ self.$$url = "http://server/";
+ self.$$lastUrl = self.$$url; // used by url polling fn
+ self.pollFns = [];
+
+ // TODO(vojta): remove this temporary api
+ self.$$completeOutstandingRequest = angular.noop;
+ self.$$incOutstandingRequestCount = angular.noop;
+
+
+ // register url polling fn
+
+ self.onUrlChange = function(listener) {
+ self.pollFns.push(
+ function() {
+ if (self.$$lastUrl != self.$$url) {
+ self.$$lastUrl = self.$$url;
+ listener(self.$$url);
+ }
+ }
+ );
+
+ return listener;
+ };
+
+ self.cookieHash = {};
+ self.lastCookieHash = {};
+ self.deferredFns = [];
+ self.deferredNextId = 0;
+
+ self.defer = function(fn, delay) {
+ delay = delay || 0;
+ self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId});
+ self.deferredFns.sort(function(a,b){ return a.time - b.time;});
+ return self.deferredNextId++;
+ };
+
+
+ self.defer.now = 0;
+
+
+ self.defer.cancel = function(deferId) {
+ var fnIndex;
+
+ angular.forEach(self.deferredFns, function(fn, index) {
+ if (fn.id === deferId) fnIndex = index;
+ });
+
+ if (fnIndex !== undefined) {
+ self.deferredFns.splice(fnIndex, 1);
+ return true;
+ }
+
+ return false;
+ };
+
+
+ /**
+ * @name ngMock.$browser#defer.flush
+ * @methodOf ngMock.$browser
+ *
+ * @description
+ * Flushes all pending requests and executes the defer callbacks.
+ *
+ * @param {number=} number of milliseconds to flush. See {@link #defer.now}
+ */
+ self.defer.flush = function(delay) {
+ if (angular.isDefined(delay)) {
+ self.defer.now += delay;
+ } else {
+ if (self.deferredFns.length) {
+ self.defer.now = self.deferredFns[self.deferredFns.length-1].time;
+ } else {
+ throw Error('No deferred tasks to be flushed');
+ }
+ }
+
+ while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) {
+ self.deferredFns.shift().fn();
+ }
+ };
+ /**
+ * @name ngMock.$browser#defer.now
+ * @propertyOf ngMock.$browser
+ *
+ * @description
+ * Current milliseconds mock time.
+ */
+
+ self.$$baseHref = '';
+ self.baseHref = function() {
+ return this.$$baseHref;
+ };
+};
+angular.mock.$Browser.prototype = {
+
+ /**
+ * @name ngMock.$browser#poll
+ * @methodOf ngMock.$browser
+ *
+ * @description
+ * run all fns in pollFns
+ */
+ poll: function poll() {
+ angular.forEach(this.pollFns, function(pollFn){
+ pollFn();
+ });
+ },
+
+ addPollFn: function(pollFn) {
+ this.pollFns.push(pollFn);
+ return pollFn;
+ },
+
+ url: function(url, replace) {
+ if (url) {
+ this.$$url = url;
+ return this;
+ }
+
+ return this.$$url;
+ },
+
+ cookies: function(name, value) {
+ if (name) {
+ if (value == undefined) {
+ delete this.cookieHash[name];
+ } else {
+ if (angular.isString(value) && //strings only
+ value.length <= 4096) { //strict cookie storage limits
+ this.cookieHash[name] = value;
+ }
+ }
+ } else {
+ if (!angular.equals(this.cookieHash, this.lastCookieHash)) {
+ this.lastCookieHash = angular.copy(this.cookieHash);
+ this.cookieHash = angular.copy(this.cookieHash);
+ }
+ return this.cookieHash;
+ }
+ },
+
+ notifyWhenNoOutstandingRequests: function(fn) {
+ fn();
+ }
+};
+
+
+/**
+ * @ngdoc object
+ * @name ngMock.$exceptionHandlerProvider
+ *
+ * @description
+ * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors passed
+ * into the `$exceptionHandler`.
+ */
+
+/**
+ * @ngdoc object
+ * @name ngMock.$exceptionHandler
+ *
+ * @description
+ * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed
+ * into it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration
+ * information.
+ *
+ *
+ *
+ * describe('$exceptionHandlerProvider', function() {
+ *
+ * it('should capture log messages and exceptions', function() {
+ *
+ * module(function($exceptionHandlerProvider) {
+ * $exceptionHandlerProvider.mode('log');
+ * });
+ *
+ * inject(function($log, $exceptionHandler, $timeout) {
+ * $timeout(function() { $log.log(1); });
+ * $timeout(function() { $log.log(2); throw 'banana peel'; });
+ * $timeout(function() { $log.log(3); });
+ * expect($exceptionHandler.errors).toEqual([]);
+ * expect($log.assertEmpty());
+ * $timeout.flush();
+ * expect($exceptionHandler.errors).toEqual(['banana peel']);
+ * expect($log.log.logs).toEqual([[1], [2], [3]]);
+ * });
+ * });
+ * });
+ *
+ */
+
+angular.mock.$ExceptionHandlerProvider = function() {
+ var handler;
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$exceptionHandlerProvider#mode
+ * @methodOf ngMock.$exceptionHandlerProvider
+ *
+ * @description
+ * Sets the logging mode.
+ *
+ * @param {string} mode Mode of operation, defaults to `rethrow`.
+ *
+ * - `rethrow`: If any errors are are passed into the handler in tests, it typically
+ * means that there is a bug in the application or test, so this mock will
+ * make these tests fail.
+ * - `log`: Sometimes it is desirable to test that an error is throw, for this case the `log` mode stores an
+ * array of errors in `$exceptionHandler.errors`, to allow later assertion of them.
+ * See {@link ngMock.$log#assertEmpty assertEmpty()} and
+ * {@link ngMock.$log#reset reset()}
+ */
+ this.mode = function(mode) {
+ switch(mode) {
+ case 'rethrow':
+ handler = function(e) {
+ throw e;
+ };
+ break;
+ case 'log':
+ var errors = [];
+
+ handler = function(e) {
+ if (arguments.length == 1) {
+ errors.push(e);
+ } else {
+ errors.push([].slice.call(arguments, 0));
+ }
+ };
+
+ handler.errors = errors;
+ break;
+ default:
+ throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
+ }
+ };
+
+ this.$get = function() {
+ return handler;
+ };
+
+ this.mode('rethrow');
+};
+
+
+/**
+ * @ngdoc service
+ * @name ngMock.$log
+ *
+ * @description
+ * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays
+ * (one array per logging level). These arrays are exposed as `logs` property of each of the
+ * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`.
+ *
+ */
+angular.mock.$LogProvider = function() {
+
+ function concat(array1, array2, index) {
+ return array1.concat(Array.prototype.slice.call(array2, index));
+ }
+
+
+ this.$get = function () {
+ var $log = {
+ log: function() { $log.log.logs.push(concat([], arguments, 0)); },
+ warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
+ info: function() { $log.info.logs.push(concat([], arguments, 0)); },
+ error: function() { $log.error.logs.push(concat([], arguments, 0)); }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$log#reset
+ * @methodOf ngMock.$log
+ *
+ * @description
+ * Reset all of the logging arrays to empty.
+ */
+ $log.reset = function () {
+ /**
+ * @ngdoc property
+ * @name ngMock.$log#log.logs
+ * @propertyOf ngMock.$log
+ *
+ * @description
+ * Array of logged messages.
+ */
+ $log.log.logs = [];
+ /**
+ * @ngdoc property
+ * @name ngMock.$log#warn.logs
+ * @propertyOf ngMock.$log
+ *
+ * @description
+ * Array of logged messages.
+ */
+ $log.warn.logs = [];
+ /**
+ * @ngdoc property
+ * @name ngMock.$log#info.logs
+ * @propertyOf ngMock.$log
+ *
+ * @description
+ * Array of logged messages.
+ */
+ $log.info.logs = [];
+ /**
+ * @ngdoc property
+ * @name ngMock.$log#error.logs
+ * @propertyOf ngMock.$log
+ *
+ * @description
+ * Array of logged messages.
+ */
+ $log.error.logs = [];
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$log#assertEmpty
+ * @methodOf ngMock.$log
+ *
+ * @description
+ * Assert that the all of the logging methods have no logged messages. If messages present, an exception is thrown.
+ */
+ $log.assertEmpty = function() {
+ var errors = [];
+ angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) {
+ angular.forEach($log[logLevel].logs, function(log) {
+ angular.forEach(log, function (logItem) {
+ errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + (logItem.stack || ''));
+ });
+ });
+ });
+ if (errors.length) {
+ errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " +
+ "log message was not checked and removed:");
+ errors.push('');
+ throw new Error(errors.join('\n---------\n'));
+ }
+ };
+
+ $log.reset();
+ return $log;
+ };
+};
+
+
+(function() {
+ var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/;
+
+ function jsonStringToDate(string){
+ var match;
+ if (match = string.match(R_ISO8061_STR)) {
+ var date = new Date(0),
+ tzHour = 0,
+ tzMin = 0;
+ if (match[9]) {
+ tzHour = int(match[9] + match[10]);
+ tzMin = int(match[9] + match[11]);
+ }
+ date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3]));
+ date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
+ return date;
+ }
+ return string;
+ }
+
+ function int(str) {
+ return parseInt(str, 10);
+ }
+
+ function padNumber(num, digits, trim) {
+ var neg = '';
+ if (num < 0) {
+ neg = '-';
+ num = -num;
+ }
+ num = '' + num;
+ while(num.length < digits) num = '0' + num;
+ if (trim)
+ num = num.substr(num.length - digits);
+ return neg + num;
+ }
+
+
+ /**
+ * @ngdoc object
+ * @name angular.mock.TzDate
+ * @description
+ *
+ * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`.
+ *
+ * Mock of the Date type which has its timezone specified via constroctor arg.
+ *
+ * The main purpose is to create Date-like instances with timezone fixed to the specified timezone
+ * offset, so that we can test code that depends on local timezone settings without dependency on
+ * the time zone settings of the machine where the code is running.
+ *
+ * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored)
+ * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC*
+ *
+ * @example
+ * !!!! WARNING !!!!!
+ * This is not a complete Date object so only methods that were implemented can be called safely.
+ * To make matters worse, TzDate instances inherit stuff from Date via a prototype.
+ *
+ * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is
+ * incomplete we might be missing some non-standard methods. This can result in errors like:
+ * "Date.prototype.foo called on incompatible Object".
+ *
+ *
+ * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z');
+ * newYearInBratislava.getTimezoneOffset() => -60;
+ * newYearInBratislava.getFullYear() => 2010;
+ * newYearInBratislava.getMonth() => 0;
+ * newYearInBratislava.getDate() => 1;
+ * newYearInBratislava.getHours() => 0;
+ * newYearInBratislava.getMinutes() => 0;
+ *
+ *
+ */
+ angular.mock.TzDate = function (offset, timestamp) {
+ var self = new Date(0);
+ if (angular.isString(timestamp)) {
+ var tsStr = timestamp;
+
+ self.origDate = jsonStringToDate(timestamp);
+
+ timestamp = self.origDate.getTime();
+ if (isNaN(timestamp))
+ throw {
+ name: "Illegal Argument",
+ message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string"
+ };
+ } else {
+ self.origDate = new Date(timestamp);
+ }
+
+ var localOffset = new Date(timestamp).getTimezoneOffset();
+ self.offsetDiff = localOffset*60*1000 - offset*1000*60*60;
+ self.date = new Date(timestamp + self.offsetDiff);
+
+ self.getTime = function() {
+ return self.date.getTime() - self.offsetDiff;
+ };
+
+ self.toLocaleDateString = function() {
+ return self.date.toLocaleDateString();
+ };
+
+ self.getFullYear = function() {
+ return self.date.getFullYear();
+ };
+
+ self.getMonth = function() {
+ return self.date.getMonth();
+ };
+
+ self.getDate = function() {
+ return self.date.getDate();
+ };
+
+ self.getHours = function() {
+ return self.date.getHours();
+ };
+
+ self.getMinutes = function() {
+ return self.date.getMinutes();
+ };
+
+ self.getSeconds = function() {
+ return self.date.getSeconds();
+ };
+
+ self.getTimezoneOffset = function() {
+ return offset * 60;
+ };
+
+ self.getUTCFullYear = function() {
+ return self.origDate.getUTCFullYear();
+ };
+
+ self.getUTCMonth = function() {
+ return self.origDate.getUTCMonth();
+ };
+
+ self.getUTCDate = function() {
+ return self.origDate.getUTCDate();
+ };
+
+ self.getUTCHours = function() {
+ return self.origDate.getUTCHours();
+ };
+
+ self.getUTCMinutes = function() {
+ return self.origDate.getUTCMinutes();
+ };
+
+ self.getUTCSeconds = function() {
+ return self.origDate.getUTCSeconds();
+ };
+
+ self.getUTCMilliseconds = function() {
+ return self.origDate.getUTCMilliseconds();
+ };
+
+ self.getDay = function() {
+ return self.date.getDay();
+ };
+
+ // provide this method only on browsers that already have it
+ if (self.toISOString) {
+ self.toISOString = function() {
+ return padNumber(self.origDate.getUTCFullYear(), 4) + '-' +
+ padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' +
+ padNumber(self.origDate.getUTCDate(), 2) + 'T' +
+ padNumber(self.origDate.getUTCHours(), 2) + ':' +
+ padNumber(self.origDate.getUTCMinutes(), 2) + ':' +
+ padNumber(self.origDate.getUTCSeconds(), 2) + '.' +
+ padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'
+ }
+ }
+
+ //hide all methods not implemented in this mock that the Date prototype exposes
+ var unimplementedMethods = ['getMilliseconds', 'getUTCDay',
+ 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds',
+ 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear',
+ 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds',
+ 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString',
+ 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf'];
+
+ angular.forEach(unimplementedMethods, function(methodName) {
+ self[methodName] = function() {
+ throw Error("Method '" + methodName + "' is not implemented in the TzDate mock");
+ };
+ });
+
+ return self;
+ };
+
+ //make "tzDateInstance instanceof Date" return true
+ angular.mock.TzDate.prototype = Date.prototype;
+})();
+
+
+/**
+ * @ngdoc function
+ * @name angular.mock.dump
+ * @description
+ *
+ * *NOTE*: this is not an injectable instance, just a globally available function.
+ *
+ * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for debugging.
+ *
+ * This method is also available on window, where it can be used to display objects on debug console.
+ *
+ * @param {*} object - any object to turn into string.
+ * @return {string} a serialized string of the argument
+ */
+angular.mock.dump = function(object) {
+ return serialize(object);
+
+ function serialize(object) {
+ var out;
+
+ if (angular.isElement(object)) {
+ object = angular.element(object);
+ out = angular.element('
');
+ angular.forEach(object, function(element) {
+ out.append(angular.element(element).clone());
+ });
+ out = out.html();
+ } else if (angular.isArray(object)) {
+ out = [];
+ angular.forEach(object, function(o) {
+ out.push(serialize(o));
+ });
+ out = '[ ' + out.join(', ') + ' ]';
+ } else if (angular.isObject(object)) {
+ if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) {
+ out = serializeScope(object);
+ } else if (object instanceof Error) {
+ out = object.stack || ('' + object.name + ': ' + object.message);
+ } else {
+ out = angular.toJson(object, true);
+ }
+ } else {
+ out = String(object);
+ }
+
+ return out;
+ }
+
+ function serializeScope(scope, offset) {
+ offset = offset || ' ';
+ var log = [offset + 'Scope(' + scope.$id + '): {'];
+ for ( var key in scope ) {
+ if (scope.hasOwnProperty(key) && !key.match(/^(\$|this)/)) {
+ log.push(' ' + key + ': ' + angular.toJson(scope[key]));
+ }
+ }
+ var child = scope.$$childHead;
+ while(child) {
+ log.push(serializeScope(child, offset + ' '));
+ child = child.$$nextSibling;
+ }
+ log.push('}');
+ return log.join('\n' + offset);
+ }
+};
+
+/**
+ * @ngdoc object
+ * @name ngMock.$httpBackend
+ * @description
+ * Fake HTTP backend implementation suitable for unit testing application that use the
+ * {@link ng.$http $http service}.
+ *
+ * *Note*: For fake http backend implementation suitable for end-to-end testing or backend-less
+ * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}.
+ *
+ * During unit testing, we want our unit tests to run quickly and have no external dependencies so
+ * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or
+ * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is
+ * to verify whether a certain request has been sent or not, or alternatively just let the
+ * application make requests, respond with pre-trained responses and assert that the end result is
+ * what we expect it to be.
+ *
+ * This mock implementation can be used to respond with static or dynamic responses via the
+ * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc).
+ *
+ * When an Angular application needs some data from a server, it calls the $http service, which
+ * sends the request to a real server using $httpBackend service. With dependency injection, it is
+ * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify
+ * the requests and respond with some testing data without sending a request to real server.
+ *
+ * There are two ways to specify what test data should be returned as http responses by the mock
+ * backend when the code under test makes http requests:
+ *
+ * - `$httpBackend.expect` - specifies a request expectation
+ * - `$httpBackend.when` - specifies a backend definition
+ *
+ *
+ * # Request Expectations vs Backend Definitions
+ *
+ * Request expectations provide a way to make assertions about requests made by the application and
+ * to define responses for those requests. The test will fail if the expected requests are not made
+ * or they are made in the wrong order.
+ *
+ * Backend definitions allow you to define a fake backend for your application which doesn't assert
+ * if a particular request was made or not, it just returns a trained response if a request is made.
+ * The test will pass whether or not the request gets made during testing.
+ *
+ *
+ *
+ * Request expectations Backend definitions
+ *
+ * Syntax
+ * .expect(...).respond(...)
+ * .when(...).respond(...)
+ *
+ *
+ * Typical usage
+ * strict unit tests
+ * loose (black-box) unit testing
+ *
+ *
+ * Fulfills multiple requests
+ * NO
+ * YES
+ *
+ *
+ * Order of requests matters
+ * YES
+ * NO
+ *
+ *
+ * Request required
+ * YES
+ * NO
+ *
+ *
+ * Response required
+ * optional (see below)
+ * YES
+ *
+ *
+ *
+ * In cases where both backend definitions and request expectations are specified during unit
+ * testing, the request expectations are evaluated first.
+ *
+ * If a request expectation has no response specified, the algorithm will search your backend
+ * definitions for an appropriate response.
+ *
+ * If a request didn't match any expectation or if the expectation doesn't have the response
+ * defined, the backend definitions are evaluated in sequential order to see if any of them match
+ * the request. The response from the first matched definition is returned.
+ *
+ *
+ * # Flushing HTTP requests
+ *
+ * The $httpBackend used in production, always responds to requests with responses asynchronously.
+ * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are
+ * hard to write, follow and maintain. At the same time the testing mock, can't respond
+ * synchronously because that would change the execution of the code under test. For this reason the
+ * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending
+ * requests and thus preserving the async api of the backend, while allowing the test to execute
+ * synchronously.
+ *
+ *
+ * # Unit testing with mock $httpBackend
+ *
+ *
+ // controller
+ function MyController($scope, $http) {
+ $http.get('/auth.py').success(function(data) {
+ $scope.user = data;
+ });
+
+ this.saveMessage = function(message) {
+ $scope.status = 'Saving...';
+ $http.post('/add-msg.py', message).success(function(response) {
+ $scope.status = '';
+ }).error(function() {
+ $scope.status = 'ERROR!';
+ });
+ };
+ }
+
+ // testing controller
+ var $httpBackend;
+
+ beforeEach(inject(function($injector) {
+ $httpBackend = $injector.get('$httpBackend');
+
+ // backend definition common for all tests
+ $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
+ }));
+
+
+ afterEach(function() {
+ $httpBackend.verifyNoOutstandingExpectation();
+ $httpBackend.verifyNoOutstandingRequest();
+ });
+
+
+ it('should fetch authentication token', function() {
+ $httpBackend.expectGET('/auth.py');
+ var controller = scope.$new(MyController);
+ $httpBackend.flush();
+ });
+
+
+ it('should send msg to server', function() {
+ // now you don’t care about the authentication, but
+ // the controller will still send the request and
+ // $httpBackend will respond without you having to
+ // specify the expectation and response for this request
+ $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, '');
+
+ var controller = scope.$new(MyController);
+ $httpBackend.flush();
+ controller.saveMessage('message content');
+ expect(controller.status).toBe('Saving...');
+ $httpBackend.flush();
+ expect(controller.status).toBe('');
+ });
+
+
+ it('should send auth header', function() {
+ $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) {
+ // check if the header was send, if it wasn't the expectation won't
+ // match the request and the test will fail
+ return headers['Authorization'] == 'xxx';
+ }).respond(201, '');
+
+ var controller = scope.$new(MyController);
+ controller.saveMessage('whatever');
+ $httpBackend.flush();
+ });
+
+ */
+angular.mock.$HttpBackendProvider = function() {
+ this.$get = [createHttpBackendMock];
+};
+
+/**
+ * General factory function for $httpBackend mock.
+ * Returns instance for unit testing (when no arguments specified):
+ * - passing through is disabled
+ * - auto flushing is disabled
+ *
+ * Returns instance for e2e testing (when `$delegate` and `$browser` specified):
+ * - passing through (delegating request to real backend) is enabled
+ * - auto flushing is enabled
+ *
+ * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified)
+ * @param {Object=} $browser Auto-flushing enabled if specified
+ * @return {Object} Instance of $httpBackend mock
+ */
+function createHttpBackendMock($delegate, $browser) {
+ var definitions = [],
+ expectations = [],
+ responses = [],
+ responsesPush = angular.bind(responses, responses.push);
+
+ function createResponse(status, data, headers) {
+ if (angular.isFunction(status)) return status;
+
+ return function() {
+ return angular.isNumber(status)
+ ? [status, data, headers]
+ : [200, status, data];
+ };
+ }
+
+ // TODO(vojta): change params to: method, url, data, headers, callback
+ function $httpBackend(method, url, data, callback, headers) {
+ var xhr = new MockXhr(),
+ expectation = expectations[0],
+ wasExpected = false;
+
+ function prettyPrint(data) {
+ return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
+ ? data
+ : angular.toJson(data);
+ }
+
+ if (expectation && expectation.match(method, url)) {
+ if (!expectation.matchData(data))
+ throw Error('Expected ' + expectation + ' with different data\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
+
+ if (!expectation.matchHeaders(headers))
+ throw Error('Expected ' + expectation + ' with different headers\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' +
+ prettyPrint(headers));
+
+ expectations.shift();
+
+ if (expectation.response) {
+ responses.push(function() {
+ var response = expectation.response(method, url, data, headers);
+ xhr.$$respHeaders = response[2];
+ callback(response[0], response[1], xhr.getAllResponseHeaders());
+ });
+ return;
+ }
+ wasExpected = true;
+ }
+
+ var i = -1, definition;
+ while ((definition = definitions[++i])) {
+ if (definition.match(method, url, data, headers || {})) {
+ if (definition.response) {
+ // if $browser specified, we do auto flush all requests
+ ($browser ? $browser.defer : responsesPush)(function() {
+ var response = definition.response(method, url, data, headers);
+ xhr.$$respHeaders = response[2];
+ callback(response[0], response[1], xhr.getAllResponseHeaders());
+ });
+ } else if (definition.passThrough) {
+ $delegate(method, url, data, callback, headers);
+ } else throw Error('No response defined !');
+ return;
+ }
+ }
+ throw wasExpected ?
+ Error('No response defined !') :
+ Error('Unexpected request: ' + method + ' ' + url + '\n' +
+ (expectation ? 'Expected ' + expectation : 'No more request expected'));
+ }
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#when
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition.
+ *
+ * @param {string} method HTTP method.
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ *
+ * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
+ * – The respond method takes a set of static data to be returned or a function that can return
+ * an array containing response status (number), response data (string) and response headers
+ * (Object).
+ */
+ $httpBackend.when = function(method, url, data, headers) {
+ var definition = new MockHttpExpectation(method, url, data, headers),
+ chain = {
+ respond: function(status, data, headers) {
+ definition.response = createResponse(status, data, headers);
+ }
+ };
+
+ if ($browser) {
+ chain.passThrough = function() {
+ definition.passThrough = true;
+ };
+ }
+
+ definitions.push(definition);
+ return chain;
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenGET
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for GET requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenHEAD
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for HEAD requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenDELETE
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for DELETE requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenPOST
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for POST requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenPUT
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for PUT requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#whenJSONP
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new backend definition for JSONP requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+ createShortMethods('when');
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expect
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation.
+ *
+ * @param {string} method HTTP method.
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current expectation.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ *
+ * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
+ * – The respond method takes a set of static data to be returned or a function that can return
+ * an array containing response status (number), response data (string) and response headers
+ * (Object).
+ */
+ $httpBackend.expect = function(method, url, data, headers) {
+ var expectation = new MockHttpExpectation(method, url, data, headers);
+ expectations.push(expectation);
+ return {
+ respond: function(status, data, headers) {
+ expectation.response = createResponse(status, data, headers);
+ }
+ };
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectGET
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for GET requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled. See #expect for more info.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectHEAD
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for HEAD requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectDELETE
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for DELETE requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectPOST
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for POST requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectPUT
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for PUT requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectPATCH
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for PATCH requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {Object=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#expectJSONP
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Creates a new request expectation for JSONP requests. For more info see `expect()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * request is handled.
+ */
+ createShortMethods('expect');
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#flush
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Flushes all pending requests using the trained responses.
+ *
+ * @param {number=} count Number of responses to flush (in the order they arrived). If undefined,
+ * all pending requests will be flushed. If there are no pending requests when the flush method
+ * is called an exception is thrown (as this typically a sign of programming error).
+ */
+ $httpBackend.flush = function(count) {
+ if (!responses.length) throw Error('No pending request to flush !');
+
+ if (angular.isDefined(count)) {
+ while (count--) {
+ if (!responses.length) throw Error('No more pending request to flush !');
+ responses.shift()();
+ }
+ } else {
+ while (responses.length) {
+ responses.shift()();
+ }
+ }
+ $httpBackend.verifyNoOutstandingExpectation();
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#verifyNoOutstandingExpectation
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Verifies that all of the requests defined via the `expect` api were made. If any of the
+ * requests were not made, verifyNoOutstandingExpectation throws an exception.
+ *
+ * Typically, you would call this method following each test case that asserts requests using an
+ * "afterEach" clause.
+ *
+ *
+ * afterEach($httpBackend.verifyExpectations);
+ *
+ */
+ $httpBackend.verifyNoOutstandingExpectation = function() {
+ if (expectations.length) {
+ throw Error('Unsatisfied requests: ' + expectations.join(', '));
+ }
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#verifyNoOutstandingRequest
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Verifies that there are no outstanding requests that need to be flushed.
+ *
+ * Typically, you would call this method following each test case that asserts requests using an
+ * "afterEach" clause.
+ *
+ *
+ * afterEach($httpBackend.verifyNoOutstandingRequest);
+ *
+ */
+ $httpBackend.verifyNoOutstandingRequest = function() {
+ if (responses.length) {
+ throw Error('Unflushed requests: ' + responses.length);
+ }
+ };
+
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$httpBackend#resetExpectations
+ * @methodOf ngMock.$httpBackend
+ * @description
+ * Resets all request expectations, but preserves all backend definitions. Typically, you would
+ * call resetExpectations during a multiple-phase test when you want to reuse the same instance of
+ * $httpBackend mock.
+ */
+ $httpBackend.resetExpectations = function() {
+ expectations.length = 0;
+ responses.length = 0;
+ };
+
+ return $httpBackend;
+
+
+ function createShortMethods(prefix) {
+ angular.forEach(['GET', 'DELETE', 'JSONP'], function(method) {
+ $httpBackend[prefix + method] = function(url, headers) {
+ return $httpBackend[prefix](method, url, undefined, headers)
+ }
+ });
+
+ angular.forEach(['PUT', 'POST', 'PATCH'], function(method) {
+ $httpBackend[prefix + method] = function(url, data, headers) {
+ return $httpBackend[prefix](method, url, data, headers)
+ }
+ });
+ }
+}
+
+function MockHttpExpectation(method, url, data, headers) {
+
+ this.data = data;
+ this.headers = headers;
+
+ this.match = function(m, u, d, h) {
+ if (method != m) return false;
+ if (!this.matchUrl(u)) return false;
+ if (angular.isDefined(d) && !this.matchData(d)) return false;
+ if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
+ return true;
+ };
+
+ this.matchUrl = function(u) {
+ if (!url) return true;
+ if (angular.isFunction(url.test)) return url.test(u);
+ return url == u;
+ };
+
+ this.matchHeaders = function(h) {
+ if (angular.isUndefined(headers)) return true;
+ if (angular.isFunction(headers)) return headers(h);
+ return angular.equals(headers, h);
+ };
+
+ this.matchData = function(d) {
+ if (angular.isUndefined(data)) return true;
+ if (data && angular.isFunction(data.test)) return data.test(d);
+ if (data && !angular.isString(data)) return angular.toJson(data) == d;
+ return data == d;
+ };
+
+ this.toString = function() {
+ return method + ' ' + url;
+ };
+}
+
+function MockXhr() {
+
+ // hack for testing $http, $httpBackend
+ MockXhr.$$lastInstance = this;
+
+ this.open = function(method, url, async) {
+ this.$$method = method;
+ this.$$url = url;
+ this.$$async = async;
+ this.$$reqHeaders = {};
+ this.$$respHeaders = {};
+ };
+
+ this.send = function(data) {
+ this.$$data = data;
+ };
+
+ this.setRequestHeader = function(key, value) {
+ this.$$reqHeaders[key] = value;
+ };
+
+ this.getResponseHeader = function(name) {
+ // the lookup must be case insensitive, that's why we try two quick lookups and full scan at last
+ var header = this.$$respHeaders[name];
+ if (header) return header;
+
+ name = angular.lowercase(name);
+ header = this.$$respHeaders[name];
+ if (header) return header;
+
+ header = undefined;
+ angular.forEach(this.$$respHeaders, function(headerVal, headerName) {
+ if (!header && angular.lowercase(headerName) == name) header = headerVal;
+ });
+ return header;
+ };
+
+ this.getAllResponseHeaders = function() {
+ var lines = [];
+
+ angular.forEach(this.$$respHeaders, function(value, key) {
+ lines.push(key + ': ' + value);
+ });
+ return lines.join('\n');
+ };
+
+ this.abort = angular.noop;
+}
+
+
+/**
+ * @ngdoc function
+ * @name ngMock.$timeout
+ * @description
+ *
+ * This service is just a simple decorator for {@link ng.$timeout $timeout} service
+ * that adds a "flush" and "verifyNoPendingTasks" methods.
+ */
+
+angular.mock.$TimeoutDecorator = function($delegate, $browser) {
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$timeout#flush
+ * @methodOf ngMock.$timeout
+ * @description
+ *
+ * Flushes the queue of pending tasks.
+ */
+ $delegate.flush = function() {
+ $browser.defer.flush();
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngMock.$timeout#verifyNoPendingTasks
+ * @methodOf ngMock.$timeout
+ * @description
+ *
+ * Verifies that there are no pending tasks that need to be flushed.
+ */
+ $delegate.verifyNoPendingTasks = function() {
+ if ($browser.deferredFns.length) {
+ throw Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' +
+ formatPendingTasksAsString($browser.deferredFns));
+ }
+ };
+
+ function formatPendingTasksAsString(tasks) {
+ var result = [];
+ angular.forEach(tasks, function(task) {
+ result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}');
+ });
+
+ return result.join(', ');
+ }
+
+ return $delegate;
+};
+
+/**
+ *
+ */
+angular.mock.$RootElementProvider = function() {
+ this.$get = function() {
+ return angular.element('
');
+ }
+};
+
+/**
+ * @ngdoc overview
+ * @name ngMock
+ * @description
+ *
+ * The `ngMock` is an angular module which is used with `ng` module and adds unit-test configuration as well as useful
+ * mocks to the {@link AUTO.$injector $injector}.
+ */
+angular.module('ngMock', ['ng']).provider({
+ $browser: angular.mock.$BrowserProvider,
+ $exceptionHandler: angular.mock.$ExceptionHandlerProvider,
+ $log: angular.mock.$LogProvider,
+ $httpBackend: angular.mock.$HttpBackendProvider,
+ $rootElement: angular.mock.$RootElementProvider
+}).config(function($provide) {
+ $provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
+ });
+
+/**
+ * @ngdoc overview
+ * @name ngMockE2E
+ * @description
+ *
+ * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing.
+ * Currently there is only one mock present in this module -
+ * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock.
+ */
+angular.module('ngMockE2E', ['ng']).config(function($provide) {
+ $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator);
+});
+
+/**
+ * @ngdoc object
+ * @name ngMockE2E.$httpBackend
+ * @description
+ * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of
+ * applications that use the {@link ng.$http $http service}.
+ *
+ * *Note*: For fake http backend implementation suitable for unit testing please see
+ * {@link ngMock.$httpBackend unit-testing $httpBackend mock}.
+ *
+ * This implementation can be used to respond with static or dynamic responses via the `when` api
+ * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the
+ * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch
+ * templates from a webserver).
+ *
+ * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application
+ * is being developed with the real backend api replaced with a mock, it is often desirable for
+ * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch
+ * templates or static files from the webserver). To configure the backend with this behavior
+ * use the `passThrough` request handler of `when` instead of `respond`.
+ *
+ * Additionally, we don't want to manually have to flush mocked out requests like we do during unit
+ * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests
+ * automatically, closely simulating the behavior of the XMLHttpRequest object.
+ *
+ * To setup the application to run with this http backend, you have to create a module that depends
+ * on the `ngMockE2E` and your application modules and defines the fake backend:
+ *
+ *
+ * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']);
+ * myAppDev.run(function($httpBackend) {
+ * phones = [{name: 'phone1'}, {name: 'phone2'}];
+ *
+ * // returns the current list of phones
+ * $httpBackend.whenGET('/phones').respond(phones);
+ *
+ * // adds a new phone to the phones array
+ * $httpBackend.whenPOST('/phones').respond(function(method, url, data) {
+ * phones.push(angular.fromJSON(data));
+ * });
+ * $httpBackend.whenGET(/^\/templates\//).passThrough();
+ * //...
+ * });
+ *
+ *
+ * Afterwards, bootstrap your app with this new module.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#when
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition.
+ *
+ * @param {string} method HTTP method.
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
+ * object and returns true if the headers match the current definition.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ *
+ * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}`
+ * – The respond method takes a set of static data to be returned or a function that can return
+ * an array containing response status (number), response data (string) and response headers
+ * (Object).
+ * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough`
+ * handler, will be pass through to the real backend (an XHR request will be made to the
+ * server.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenGET
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for GET requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenHEAD
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for HEAD requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenDELETE
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for DELETE requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenPOST
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for POST requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenPUT
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for PUT requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenPATCH
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for PATCH requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @param {(string|RegExp)=} data HTTP request body.
+ * @param {(Object|function(Object))=} headers HTTP headers.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+
+/**
+ * @ngdoc method
+ * @name ngMockE2E.$httpBackend#whenJSONP
+ * @methodOf ngMockE2E.$httpBackend
+ * @description
+ * Creates a new backend definition for JSONP requests. For more info see `when()`.
+ *
+ * @param {string|RegExp} url HTTP url.
+ * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that
+ * control how a matched request is handled.
+ */
+angular.mock.e2e = {};
+angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock];
+
+
+angular.mock.clearDataCache = function() {
+ var key,
+ cache = angular.element.cache;
+
+ for(key in cache) {
+ if (cache.hasOwnProperty(key)) {
+ var handle = cache[key].handle;
+
+ handle && angular.element(handle.elem).unbind();
+ delete cache[key];
+ }
+ }
+};
+
+
+window.jstestdriver && (function(window) {
+ /**
+ * Global method to output any number of objects into JSTD console. Useful for debugging.
+ */
+ window.dump = function() {
+ var args = [];
+ angular.forEach(arguments, function(arg) {
+ args.push(angular.mock.dump(arg));
+ });
+ jstestdriver.console.log.apply(jstestdriver.console, args);
+ if (window.console) {
+ window.console.log.apply(window.console, args);
+ }
+ };
+})(window);
+
+
+(window.jasmine || window.mocha) && (function(window) {
+
+ var currentSpec = null;
+
+ beforeEach(function() {
+ currentSpec = this;
+ });
+
+ afterEach(function() {
+ var injector = currentSpec.$injector;
+
+ currentSpec.$injector = null;
+ currentSpec.$modules = null;
+ currentSpec = null;
+
+ if (injector) {
+ injector.get('$rootElement').unbind();
+ injector.get('$browser').pollFns.length = 0;
+ }
+
+ angular.mock.clearDataCache();
+
+ // clean up jquery's fragment cache
+ angular.forEach(angular.element.fragments, function(val, key) {
+ delete angular.element.fragments[key];
+ });
+
+ MockXhr.$$lastInstance = null;
+
+ angular.forEach(angular.callbacks, function(val, key) {
+ delete angular.callbacks[key];
+ });
+ angular.callbacks.counter = 0;
+ });
+
+ function isSpecRunning() {
+ return currentSpec && currentSpec.queue.running;
+ }
+
+ /**
+ * @ngdoc function
+ * @name angular.mock.module
+ * @description
+ *
+ * *NOTE*: This is function is also published on window for easy access.
+ * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}.
+ *
+ * This function registers a module configuration code. It collects the configuration information
+ * which will be used when the injector is created by {@link angular.mock.inject inject}.
+ *
+ * See {@link angular.mock.inject inject} for usage example
+ *
+ * @param {...(string|Function)} fns any number of modules which are represented as string
+ * aliases or as anonymous module initialization functions. The modules are used to
+ * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded.
+ */
+ window.module = angular.mock.module = function() {
+ var moduleFns = Array.prototype.slice.call(arguments, 0);
+ return isSpecRunning() ? workFn() : workFn;
+ /////////////////////
+ function workFn() {
+ if (currentSpec.$injector) {
+ throw Error('Injector already created, can not register a module!');
+ } else {
+ var modules = currentSpec.$modules || (currentSpec.$modules = []);
+ angular.forEach(moduleFns, function(module) {
+ modules.push(module);
+ });
+ }
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name angular.mock.inject
+ * @description
+ *
+ * *NOTE*: This is function is also published on window for easy access.
+ * *NOTE*: Only available with {@link http://pivotal.github.com/jasmine/ jasmine}.
+ *
+ * The inject function wraps a function into an injectable function. The inject() creates new
+ * instance of {@link AUTO.$injector $injector} per test, which is then used for
+ * resolving references.
+ *
+ * See also {@link angular.mock.module module}
+ *
+ * Example of what a typical jasmine tests looks like with the inject method.
+ *
+ *
+ * angular.module('myApplicationModule', [])
+ * .value('mode', 'app')
+ * .value('version', 'v1.0.1');
+ *
+ *
+ * describe('MyApp', function() {
+ *
+ * // You need to load modules that you want to test,
+ * // it loads only the "ng" module by default.
+ * beforeEach(module('myApplicationModule'));
+ *
+ *
+ * // inject() is used to inject arguments of all given functions
+ * it('should provide a version', inject(function(mode, version) {
+ * expect(version).toEqual('v1.0.1');
+ * expect(mode).toEqual('app');
+ * }));
+ *
+ *
+ * // The inject and module method can also be used inside of the it or beforeEach
+ * it('should override a version and test the new version is injected', function() {
+ * // module() takes functions or strings (module aliases)
+ * module(function($provide) {
+ * $provide.value('version', 'overridden'); // override version here
+ * });
+ *
+ * inject(function(version) {
+ * expect(version).toEqual('overridden');
+ * });
+ * ));
+ * });
+ *
+ *
+ *
+ * @param {...Function} fns any number of functions which will be injected using the injector.
+ */
+ window.inject = angular.mock.inject = function() {
+ var blockFns = Array.prototype.slice.call(arguments, 0);
+ var errorForStack = new Error('Declaration Location');
+ return isSpecRunning() ? workFn() : workFn;
+ /////////////////////
+ function workFn() {
+ var modules = currentSpec.$modules || [];
+
+ modules.unshift('ngMock');
+ modules.unshift('ng');
+ var injector = currentSpec.$injector;
+ if (!injector) {
+ injector = currentSpec.$injector = angular.injector(modules);
+ }
+ for(var i = 0, ii = blockFns.length; i < ii; i++) {
+ try {
+ injector.invoke(blockFns[i] || angular.noop, this);
+ } catch (e) {
+ if(e.stack) e.stack += '\n' + errorForStack.stack;
+ throw e;
+ } finally {
+ errorForStack = null;
+ }
+ }
+ }
+ };
+})(window);
\ No newline at end of file
diff --git a/test/lib/angular/angular-scenario.js b/test/lib/angular/angular-scenario.js
new file mode 100644
index 0000000..a96cab1
--- /dev/null
+++ b/test/lib/angular/angular-scenario.js
@@ -0,0 +1,26407 @@
+/*!
+ * jQuery JavaScript Library v1.8.2
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Thu Sep 20 2012 21:13:05 GMT-0400 (Eastern Daylight Time)
+ */
+(function( window, undefined ) {
+ 'use strict';
+ var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+ navigator = window.navigator,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Save a reference to some core methods
+ core_push = Array.prototype.push,
+ core_slice = Array.prototype.slice,
+ core_indexOf = Array.prototype.indexOf,
+ core_toString = Object.prototype.toString,
+ core_hasOwn = Object.prototype.hasOwnProperty,
+ core_trim = String.prototype.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+ // Used for detecting and trimming whitespace
+ core_rnotwhite = /\S/,
+ core_rspace = /\s+/,
+
+ // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over
to avoid XSS via location.hash (#9521)
+ rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ },
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+ jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+ // scripts is true for back-compat
+ selector = jQuery.parseHTML( match[1], doc, true );
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ this.attr.call( selector, context, true );
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.8.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ),
+ "slice", core_slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+ };
+
+// Give the init function the jQuery prototype for later instantiation
+ jQuery.fn.init.prototype = jQuery.fn;
+
+ jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+ };
+
+ jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ core_toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // scripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, scripts ) {
+ var parsed;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ scripts = context;
+ context = 0;
+ }
+ context = context || document;
+
+ // Single tag
+ if ( (parsed = rsingleTag.exec( data )) ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+ return jQuery.merge( [],
+ (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+ },
+
+ parseJSON: function( data ) {
+ if ( !data || typeof data !== "string") {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && core_rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var name,
+ i = 0,
+ length = obj.length,
+ isObj = length === undefined || jQuery.isFunction( obj );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.apply( obj[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( obj[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var type,
+ ret = results || [];
+
+ if ( arr != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ type = jQuery.type( arr );
+
+ if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+ core_push.call( ret, arr );
+ } else {
+ jQuery.merge( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key,
+ ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+ });
+
+ jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready, 1 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+ };
+
+// Populate the class2type map
+ jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+ });
+
+// All jQuery objects should point back to these
+ rootjQuery = jQuery(document);
+// String to Object options format cache
+ var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+ function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.split( core_rspace ), function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+ }
+
+ /*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+ jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) {
+ list.push( arg );
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+ };
+ jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+ });
+ jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ eventName,
+ i,
+ isSupported,
+ clickFn,
+ div = document.createElement("div");
+
+ // Preliminary tests
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " a ";
+
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+
+ // Can't get basic test support
+ if ( !all || !all.length ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: ( document.compatMode === "CSS1Compat" ),
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", clickFn = function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent("onclick");
+ div.detachEvent( "onclick", clickFn );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ input.setAttribute( "checked", "checked" );
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: true,
+ change: true,
+ focusin: true
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, div, tds, marginDiv,
+ divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // NOTE: To any future maintainer, we've window.getComputedStyle
+ // because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = document.createElement("div");
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "
";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ container.style.zoom = 1;
+ }
+
+ // Null elements to avoid leaks in IE
+ body.removeChild( container );
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ fragment.removeChild( div );
+ all = a = select = opt = input = fragment = div = null;
+
+ return support;
+ })();
+ var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+ jQuery.extend({
+ cache: {},
+
+ deletedIds: [],
+
+ // Remove at next major release (1.9/2.0)
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+ });
+
+ jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( !name.indexOf( "data-" ) ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+ });
+
+ function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+ }
+
+// checks a cache object for emptiness
+ function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
+ }
+ });
+
+ jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+ });
+ var nodeHook, boolHook, fixSpecified,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea|)$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute;
+
+ jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var removes, className, elem, c, cl, i, l;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+ if ( (value && typeof value === "string") || value === undefined ) {
+ removes = ( value || "" ).split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+ if ( elem.nodeType === 1 && elem.className ) {
+
+ className = (" " + elem.className + " ").replace( rclass, " " );
+
+ // loop over each item in the removal list
+ for ( c = 0, cl = removes.length; c < cl; c++ ) {
+ // Remove until there is nothing to remove,
+ while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
+ className = className.replace( " " + removes[ c ] + " " , " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( className ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( core_rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+ });
+
+ jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+ attrFn: {},
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+
+ attrNames = value.split( core_rspace );
+
+ for ( ; i < attrNames.length; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+ });
+
+// Hook for boolean attributes
+ boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+ };
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+ if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.value = value + "" );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+ }
+
+
+// Some attributes require a special call on IE
+ if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+ }
+
+ if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+ }
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+ if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+// IE6/7 call enctype encoding
+ if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+ }
+
+// Radios and checkboxes getter/setter
+ if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+ }
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+ });
+ var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+ /*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+ jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var t, tns, type, origType, namespaces, origCount,
+ j, events, special, eventType, handleObj,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, "events", true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+ type = event.type || event,
+ namespaces = [];
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ for ( old = elem; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old === (elem.ownerDocument || document) ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
+ handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = core_slice.call( arguments ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [];
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ selMatch = {};
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+ };
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+ jQuery.event.handle = jQuery.event.dispatch;
+
+ jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8 –
+ // detachEvent needed property on element, by name of that event, to properly expose it to GC
+ if ( typeof elem[ name ] === "undefined" ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+ jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+ };
+
+ function returnFalse() {
+ return false;
+ }
+ function returnTrue() {
+ return true;
+ }
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+ jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+ };
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+ jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+ }, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+ });
+
+// IE submit delegation
+ if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !jQuery._data( form, "_submit_attached" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ jQuery._data( form, "_submit_attached", true );
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+ }
+
+// IE change delegation and checkbox/radio fix
+ if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ }
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event, true );
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ jQuery._data( elem, "_change_attached", true );
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+ }
+
+// Create "bubbling" focus and blur events
+ if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+ }
+
+ jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+ });
+
+ jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+ });
+ /*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://sizzlejs.com/
+ */
+ (function( window, undefined ) {
+
+ var cachedruns,
+ assertGetIdNotName,
+ Expr,
+ getText,
+ isXML,
+ contains,
+ compile,
+ sortOrder,
+ hasDuplicate,
+ outermostContext,
+
+ baseHasDuplicate = true,
+ strundefined = "undefined",
+
+ expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+ Token = String,
+ document = window.document,
+ docElem = document.documentElement,
+ dirruns = 0,
+ done = 0,
+ pop = [].pop,
+ push = [].push,
+ slice = [].slice,
+ // Use a stripped-down indexOf if a native one is unavailable
+ indexOf = [].indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // Augment a function for special use by Sizzle
+ markFunction = function( fn, value ) {
+ fn[ expando ] = value == null || value;
+ return fn;
+ },
+
+ createCache = function() {
+ var cache = {},
+ keys = [];
+
+ return markFunction(function( key, value ) {
+ // Only keep the most recent entries
+ if ( keys.push( key ) > Expr.cacheLength ) {
+ delete cache[ keys.shift() ];
+ }
+
+ return (cache[ key ] = value);
+ }, cache );
+ },
+
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+
+ // Regex
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ operators = "([*^$|!~]?=)",
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // Prefer arguments not in parens/brackets,
+ // then attribute selectors and non-pseudos (denoted by :),
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",
+
+ // For matchExpr.POS and matchExpr.needsContext
+ pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
+ rpseudo = new RegExp( pseudos ),
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+ rnot = /^:not/,
+ rsibling = /[\x20\t\r\n\f]*[+~]/,
+ rendsWithNot = /:not\($/,
+
+ rheader = /h\d/i,
+ rinputs = /input|select|textarea|button/i,
+
+ rbackslash = /\\(?!\\)/g,
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "POS": new RegExp( pos, "i" ),
+ "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ // For use in libraries implementing .is()
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+ },
+
+ // Support
+
+ // Used for testing something on an element
+ assert = function( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // release memory in IE
+ div = null;
+ }
+ },
+
+ // Check if getElementsByTagName("*") returns only elements
+ assertTagNameNoComments = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ }),
+
+ // Check if getAttribute returns normalized href attributes
+ assertHrefNotNormalized = assert(function( div ) {
+ div.innerHTML = " ";
+ return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+ div.firstChild.getAttribute("href") === "#";
+ }),
+
+ // Check if attributes should be retrieved by attribute nodes
+ assertAttributes = assert(function( div ) {
+ div.innerHTML = " ";
+ var type = typeof div.lastChild.getAttribute("multiple");
+ // IE8 returns a string for some attributes even when not present
+ return type !== "boolean" && type !== "string";
+ }),
+
+ // Check if getElementsByClassName can be trusted
+ assertUsableClassName = assert(function( div ) {
+ // Opera can't find a second classname (in 9.6)
+ div.innerHTML = "
";
+ if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
+ return false;
+ }
+
+ // Safari 3.2 caches class attributes and doesn't catch changes
+ div.lastChild.className = "e";
+ return div.getElementsByClassName("e").length === 2;
+ }),
+
+ // Check if getElementById returns elements by name
+ // Check if getElementsByName privileges form controls or returns elements by ID
+ assertUsableName = assert(function( div ) {
+ // Inject content
+ div.id = expando + 0;
+ div.innerHTML = "
";
+ docElem.insertBefore( div, docElem.firstChild );
+
+ // Test
+ var pass = document.getElementsByName &&
+ // buggy browsers will return fewer than the correct 2
+ document.getElementsByName( expando ).length === 2 +
+ // buggy browsers will return more than the correct 0
+ document.getElementsByName( expando + 0 ).length;
+ assertGetIdNotName = !document.getElementById( expando );
+
+ // Cleanup
+ docElem.removeChild( div );
+
+ return pass;
+ });
+
+// If slice is not available, provide a backup
+ try {
+ slice.call( docElem.childNodes, 0 )[0].nodeType;
+ } catch ( e ) {
+ slice = function( i ) {
+ var elem,
+ results = [];
+ for ( ; (elem = this[i]); i++ ) {
+ results.push( elem );
+ }
+ return results;
+ };
+ }
+
+ function Sizzle( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+ var match, elem, xml, m,
+ nodeType = context.nodeType;
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( nodeType !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ xml = isXML( context );
+
+ if ( !xml && !seed ) {
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+ push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+ return results;
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );
+ }
+
+ Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+ };
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+
+// Returns a function to use in pseudos for input types
+ function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+ }
+
+// Returns a function to use in pseudos for buttons
+ function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+ }
+
+// Returns a function to use in pseudos for positionals
+ function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+ }
+
+ /**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+ getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ }
+ return ret;
+ };
+
+ isXML = 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).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+ };
+
+// Element contains another
+ contains = Sizzle.contains = docElem.contains ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+ } :
+ docElem.compareDocumentPosition ?
+ function( a, b ) {
+ return b && !!( a.compareDocumentPosition( b ) & 16 );
+ } :
+ function( a, b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ Sizzle.attr = function( elem, name ) {
+ var val,
+ xml = isXML( elem );
+
+ if ( !xml ) {
+ name = name.toLowerCase();
+ }
+ if ( (val = Expr.attrHandle[ name ]) ) {
+ return val( elem );
+ }
+ if ( xml || assertAttributes ) {
+ return elem.getAttribute( name );
+ }
+ val = elem.getAttributeNode( name );
+ return val ?
+ typeof elem[ name ] === "boolean" ?
+ elem[ name ] ? name : null :
+ val.specified ? val.value : null :
+ null;
+ };
+
+ Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ // IE6/7 return a modified href
+ attrHandle: assertHrefNotNormalized ?
+ {} :
+ {
+ "href": function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ },
+ "type": function( elem ) {
+ return elem.getAttribute("type");
+ }
+ },
+
+ find: {
+ "ID": assertGetIdNotName ?
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ } :
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ },
+
+ "TAG": assertTagNameNoComments ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ var elem,
+ tmp = [],
+ i = 0;
+
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ },
+
+ "NAME": assertUsableName && function( tag, context ) {
+ if ( typeof context.getElementsByName !== strundefined ) {
+ return context.getElementsByName( name );
+ }
+ },
+
+ "CLASS": assertUsableClassName && function( className, context, xml ) {
+ if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+ return context.getElementsByClassName( className );
+ }
+ }
+ },
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( rbackslash, "" );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 3 xn-component of xn+y argument ([+-]?\d*n|)
+ 4 sign of xn-component
+ 5 x of xn-component
+ 6 sign of y-component
+ 7 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1] === "nth" ) {
+ // nth-child requires argument
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+ match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var unquoted, excess;
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ if ( match[3] ) {
+ match[2] = match[3];
+ } else if ( (unquoted = match[4]) ) {
+ // Only check arguments that contain a pseudo
+ if ( rpseudo.test(unquoted) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ unquoted = unquoted.slice( 0, excess );
+ match[0] = match[0].slice( 0, excess );
+ }
+ match[2] = unquoted;
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+ "ID": assertGetIdNotName ?
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ return elem.getAttribute("id") === id;
+ };
+ } :
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === id;
+ };
+ },
+
+ "TAG": function( nodeName ) {
+ if ( nodeName === "*" ) {
+ return function() { return true; };
+ }
+ nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+ return function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ expando ][ className ];
+ if ( !pattern ) {
+ pattern = classCache( className, new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)") );
+ }
+ return function( elem ) {
+ return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+ };
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem, context ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, argument, first, last ) {
+
+ if ( type === "nth" ) {
+ return function( elem ) {
+ var node, diff,
+ parent = elem.parentNode;
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ if ( parent ) {
+ diff = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ diff++;
+ if ( elem === node ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset (or cast to NaN), then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ };
+ }
+
+ return function( elem ) {
+ var 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;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ var nodeType;
+ elem = elem.firstChild;
+ while ( elem ) {
+ if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+ return false;
+ }
+ elem = elem.nextSibling;
+ }
+ return true;
+ },
+
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "text": function( elem ) {
+ var type, attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ (type = elem.type) === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+ },
+
+ // Input types
+ "radio": createInputPseudo("radio"),
+ "checkbox": createInputPseudo("checkbox"),
+ "file": createInputPseudo("file"),
+ "password": createInputPseudo("password"),
+ "image": createInputPseudo("image"),
+
+ "submit": createButtonPseudo("submit"),
+ "reset": createButtonPseudo("reset"),
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "focus": function( elem ) {
+ var doc = elem.ownerDocument;
+ return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);
+ },
+
+ "active": function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ },
+
+ // Positional types
+ "first": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = 0; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = 1; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+ };
+
+ function siblingCheck( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+ }
+
+ sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+ a.compareDocumentPosition :
+ a.compareDocumentPosition(b) & 4
+ ) ? -1 : 1;
+ } :
+ function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+// Always assume the presence of duplicates if sort doesn't
+// pass them to our comparison function (as in Google Chrome).
+ [0, 0].sort( sortOrder );
+ baseHasDuplicate = !hasDuplicate;
+
+// Document sorting and removing duplicates
+ Sizzle.uniqueSort = function( results ) {
+ var elem,
+ i = 1;
+
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+
+ return results;
+ };
+
+ Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+ };
+
+ function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type, soFar, groups, preFilters,
+ cached = tokenCache[ expando ][ selector ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ soFar = soFar.slice( match[0].length );
+ }
+ groups.push( tokens = [] );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+
+ // Cast descendant combinators to space
+ matched.type = match[0].replace( rtrim, " " );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ // The last two arguments here are (context, xml) for backCompat
+ (match = preFilters[ type ]( match, document, true ))) ) {
+
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+ matched.type = type;
+ matched.matches = match;
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+ }
+
+ function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && combinator.dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( !xml ) {
+ var cache,
+ dirkey = dirruns + " " + doneName + " ",
+ cachedkey = dirkey + cachedruns;
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( (cache = elem[ expando ]) === cachedkey ) {
+ return elem.sizset;
+ } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+ if ( elem.sizset ) {
+ return elem;
+ }
+ } else {
+ elem[ expando ] = cachedkey;
+ if ( matcher( elem, context, xml ) ) {
+ elem.sizset = true;
+ return elem;
+ }
+ elem.sizset = false;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( matcher( elem, context, xml ) ) {
+ return elem;
+ }
+ }
+ }
+ }
+ };
+ }
+
+ function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+ }
+
+ function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+ }
+
+ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ // Positional selectors apply to seed elements, so it is invalid to follow them with relative ones
+ if ( seed && postFinder ) {
+ return;
+ }
+
+ var i, elem, postFilterIn,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [], seed ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ postFilterIn = condense( matcherOut, postMap );
+ postFilter( postFilterIn, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = postFilterIn.length;
+ while ( i-- ) {
+ if ( (elem = postFilterIn[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ // Keep seed and results synchronized
+ if ( seed ) {
+ // Ignore postFinder because it can't coexist with seed
+ i = preFilter && matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ seed[ preMap[i] ] = !(results[ preMap[i] ] = elem);
+ }
+ }
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+ }
+
+ function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ // The concatenated values are (context, xml) for backCompat
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && tokens.join("")
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+ }
+
+ function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, expandContext ) {
+ var elem, j, matcher,
+ setMatched = [],
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ outermost = expandContext != null,
+ contextBackup = outermostContext,
+ // We must always have either seed elements or context
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+ // Nested matchers should use non-integer dirruns
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = superMatcher.el;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ cachedruns = ++superMatcher.el;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ for ( j = 0; (matcher = setMatchers[j]); j++ ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ superMatcher.el = 0;
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+ }
+
+ compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ expando ][ selector ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+ };
+
+ function multipleContexts( selector, contexts, results, seed ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results, seed );
+ }
+ return results;
+ }
+
+ function select( selector, context, results, seed, xml ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector ),
+ j = match.length;
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ context.nodeType === 9 && !xml &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( rbackslash, "" ),
+ rsibling.test( tokens[0].type ) && context.parentNode || context,
+ xml
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && tokens.join("");
+ if ( !selector ) {
+ push.apply( results, slice.call( seed, 0 ) );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ xml,
+ results,
+ rsibling.test( selector )
+ );
+ return results;
+ }
+
+ if ( document.querySelectorAll ) {
+ (function() {
+ var disconnectedMatch,
+ oldSelect = select,
+ rescape = /'|\\/g,
+ rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+
+ // qSa(:focus) reports false when true (Chrome 21),
+ // A support test would require too much code (would include document ready)
+ rbuggyQSA = [":focus"],
+
+ // matchesSelector(:focus) reports false when true (Chrome 21),
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ // A support test would require too much code (would include document ready)
+ // just skip matchesSelector for :active
+ rbuggyMatches = [ ":active", ":focus" ],
+ matches = docElem.matchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.webkitMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector;
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explictly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = " ";
+
+ // IE8 - Some boolean attributes are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here (do not put tests after this one)
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Opera 10-12/IE9 - ^= $= *= and empty values
+ // Should not select anything
+ div.innerHTML = "
";
+ if ( div.querySelectorAll("[test^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here (do not put tests after this one)
+ div.innerHTML = " ";
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push(":enabled", ":disabled");
+ }
+ });
+
+ // rbuggyQSA always contains :focus, so no need for a length check
+ rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );
+
+ select = function( selector, context, results, seed, xml ) {
+ // Only use querySelectorAll when not filtering,
+ // when this is not xml,
+ // and when no QSA bugs apply
+ if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ var groups, i,
+ old = true,
+ nid = expando,
+ newContext = context,
+ newSelector = context.nodeType === 9 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + groups[i].join("");
+ }
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results, slice.call( newContext.querySelectorAll(
+ newSelector
+ ), 0 ) );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+
+ return oldSelect( selector, context, results, seed, xml );
+ };
+
+ if ( matches ) {
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ try {
+ matches.call( div, "[test!='']:sizzle" );
+ rbuggyMatches.push( "!=", pseudos );
+ } catch ( e ) {}
+ });
+
+ // rbuggyMatches always contains :active and :focus, so no need for a length check
+ rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyMatches always contains :active, so no need for an existence check
+ if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) {
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+ }
+ })();
+ }
+
+// Deprecated
+ Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Back-compat
+ function setFilters() {}
+ Expr.filters = setFilters.prototype = Expr.pseudos;
+ Expr.setFilters = new setFilters();
+
+// Override sizzle attribute retrieval
+ Sizzle.attr = jQuery.attr;
+ jQuery.find = Sizzle;
+ jQuery.expr = Sizzle.selectors;
+ jQuery.expr[":"] = jQuery.expr.pseudos;
+ jQuery.unique = Sizzle.uniqueSort;
+ jQuery.text = Sizzle.getText;
+ jQuery.isXMLDoc = Sizzle.isXML;
+ jQuery.contains = Sizzle.contains;
+
+
+ })( window );
+ var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ rneedsContext = jQuery.expr.match.needsContext,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+ jQuery.fn.extend({
+ find: function( selector ) {
+ var i, l, length, n, r, ret,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ ret = this.pushStack( "", "find", selector );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ rneedsContext.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ ret = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+ });
+
+ jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+ function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+ }
+
+ function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+ }
+
+ jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
+ }
+ }, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( this.length > 1 && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
+ };
+ });
+
+ jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+ });
+
+// Implement the identical functionality for filter and not
+ function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+ }
+ function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+ }
+
+ var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rtbody = / ]", "i"),
+ rcheckableType = /^(?:checkbox|radio)$/,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*\s*$/g,
+ wrapMap = {
+ option: [ 1, "", " " ],
+ legend: [ 1, "", " " ],
+ thead: [ 1, "" ],
+ tr: [ 2, "" ],
+ td: [ 3, "" ],
+ col: [ 2, "" ],
+ area: [ 1, "", " " ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document ),
+ fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+ wrapMap.optgroup = wrapMap.option;
+ wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+ wrapMap.th = wrapMap.td;
+
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
+ if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "X", "
" ];
+ }
+
+ jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
+ }
+ },
+
+ after: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1>$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( !isDisconnected( this[0] ) ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ }
+
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+
+ // Flatten any nested arrays
+ args = [].concat.apply( [], args );
+
+ var results, first, fragment, iNoClone,
+ i = 0,
+ value = args[0],
+ scripts = [],
+ l = this.length;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call( this, i, table ? self.html() : undefined );
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ results = jQuery.buildFragment( args, this, scripts );
+ fragment = results.fragment;
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ // Fragments from the fragment cache must always be cloned and never used in place.
+ for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
+ callback.call(
+ table && jQuery.nodeName( this[i], "table" ) ?
+ findOrAppend( this[i], "tbody" ) :
+ this[i],
+ i === iNoClone ?
+ fragment :
+ jQuery.clone( fragment, true, true )
+ );
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ if ( jQuery.ajax ) {
+ jQuery.ajax({
+ url: elem.src,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ } else {
+ jQuery.error("no ajax");
+ }
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+ });
+
+ function findOrAppend( elem, tag ) {
+ return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
+ }
+
+ function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+ }
+
+ function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ if ( nodeName === "object" ) {
+ // IE6-10 improperly clones children of object elements using classid.
+ // IE10 throws NoModificationAllowedError if parent is null, #12132.
+ if ( dest.parentNode ) {
+ dest.outerHTML = src.outerHTML;
+ }
+
+ // This path appears unavoidable for IE9. When cloning an object
+ // element in IE9, the outerHTML strategy above is not sufficient.
+ // If the src has innerHTML and the destination does not,
+ // copy the src.innerHTML into the dest.innerHTML. #10324
+ if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+ dest.innerHTML = src.innerHTML;
+ }
+
+ } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+ }
+
+ jQuery.buildFragment = function( args, context, scripts ) {
+ var fragment, cacheable, cachehit,
+ first = args[ 0 ];
+
+ // Set context from what may come in as undefined or a jQuery collection or a node
+ // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 &
+ // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception
+ context = context || document;
+ context = !context.nodeType && context[0] || context;
+ context = context.ownerDocument || context;
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put or elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ // Mark cacheable and look for a hit
+ cacheable = true;
+ fragment = jQuery.fragments[ first ];
+ cachehit = fragment !== undefined;
+ }
+
+ if ( !fragment ) {
+ fragment = context.createDocumentFragment();
+ jQuery.clean( args, context, fragment, scripts );
+
+ // Update the cache, but only store false
+ // unless this is a second parsing of the same content
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cachehit && fragment;
+ }
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+ };
+
+ jQuery.fragments = {};
+
+ jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+ }, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ i = 0,
+ ret = [],
+ insert = jQuery( selector ),
+ l = insert.length,
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+ } else {
+ for ( ; i < l; i++ ) {
+ elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+ });
+
+ function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+ }
+
+// Used in clean, fixes the defaultChecked property
+ function fixDefaultChecked( elem ) {
+ if ( rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+ }
+
+ jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ clone;
+
+ if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+ safe = context === document && safeFragment,
+ ret = [];
+
+ // Ensure that context is a document
+ if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+ context = document;
+ }
+
+ // Use the already-created safe fragment if context permits
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Ensure a safe container in which to render the html
+ safe = safe || createSafeFragment( context );
+ div = context.createElement("div");
+ safe.appendChild( div );
+
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1>$2>");
+
+ // Go to html and back, then peel off extra wrappers
+ tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ depth = wrap[0];
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a , *may* have spurious
+ hasBody = rtbody.test(elem);
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare or
+ wrap[1] === "" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Take out of fragment container (we need a fresh div each time)
+ div.parentNode.removeChild( div );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ jQuery.merge( ret, elem );
+ }
+ }
+
+ // Fix #11356: Clear elements from safeFragment
+ if ( div ) {
+ elem = div = safe = null;
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !jQuery.support.appendChecked ) {
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+ }
+ }
+
+ // Append elements to a provided document fragment
+ if ( fragment ) {
+ // Special handling of each script element
+ handleScript = function( elem ) {
+ // Check if we consider it executable
+ if ( !elem.type || rscriptType.test( elem.type ) ) {
+ // Detach the script and store it in the scripts array (if provided) or the fragment
+ // Return truthy to indicate that it has been handled
+ return scripts ?
+ scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+ fragment.appendChild( elem );
+ }
+ };
+
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ // Check if we're done after handling an executable script
+ if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+ // Append to fragment and handle embedded scripts
+ fragment.appendChild( elem );
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+ jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+ // Splice the scripts into ret after their former ancestor and advance our index beyond them
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ i += jsTags.length;
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems, /* internal */ acceptData ) {
+ var data, id, elem, type,
+ i = 0,
+ internalKey = jQuery.expando,
+ cache = jQuery.cache,
+ deleteExpando = jQuery.support.deleteExpando,
+ special = jQuery.event.special;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( deleteExpando ) {
+ delete elem[ internalKey ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ jQuery.deletedIds.push( id );
+ }
+ }
+ }
+ }
+ }
+ });
+// Limit scope pollution from any deprecated API
+ (function() {
+
+ var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+ jQuery.uaMatch = function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ };
+
+ matched = jQuery.uaMatch( navigator.userAgent );
+ browser = {};
+
+ if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+ }
+
+// Chrome is Webkit, but Webkit is also Safari.
+ if ( browser.chrome ) {
+ browser.webkit = true;
+ } else if ( browser.webkit ) {
+ browser.safari = true;
+ }
+
+ jQuery.browser = browser;
+
+ jQuery.sub = function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+ };
+
+ })();
+ var curCSS, iframe, iframeDoc,
+ ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ rposition = /^(top|right|bottom|left)$/,
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rmargin = /^margin/,
+ rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+ rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+ rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+ elemdisplay = {},
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+
+ eventsToggle = jQuery.fn.toggle;
+
+// return a css property mapped to a potentially vendor prefixed property
+ function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+ }
+
+ function isHidden( elem, el ) {
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+ }
+
+ function showHide( elements, show ) {
+ var elem, display,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ values[ index ] = jQuery._data( elem, "olddisplay" );
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && elem.style.display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ display = curCSS( elem, "display" );
+
+ if ( !values[ index ] && display !== "none" ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+ }
+
+ jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state, fn2 ) {
+ var bool = typeof state === "boolean";
+
+ if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+ return eventsToggle.apply( this, arguments );
+ }
+
+ return this.each(function() {
+ if ( bool ? state : isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+ });
+
+ jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, numeric, extra ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( numeric || extra !== undefined ) {
+ num = parseFloat( val );
+ return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+ });
+
+// NOTE: To any future maintainer, we've window.getComputedStyle
+// because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ curCSS = function( elem, name ) {
+ var ret, width, minWidth, maxWidth,
+ computed = window.getComputedStyle( elem, null ),
+ style = elem.style;
+
+ if ( computed ) {
+
+ ret = computed[ name ];
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+ };
+ } else if ( document.documentElement.currentStyle ) {
+ curCSS = function( elem, name ) {
+ var left, rsLeft,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ // but not position css attributes, as those are proportional to the parent element instead
+ // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+ if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+ }
+
+ function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+ }
+
+ function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ // we use jQuery.css instead of curCSS here
+ // because of the reliableMarginRight CSS hook!
+ val += jQuery.css( elem, extra + cssExpand[ i ], true );
+ }
+
+ // From this point on we use curCSS for maximum performance (relevant in animations)
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val;
+ }
+
+ function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ valueIsBorderBox = true,
+ isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox
+ )
+ ) + "px";
+ }
+
+
+// Try to determine the default display value of an element
+ function css_defaultDisplay( nodeName ) {
+ if ( elemdisplay[ nodeName ] ) {
+ return elemdisplay[ nodeName ];
+ }
+
+ var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+ display = elem.css("display");
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // Use the already-created iframe if possible
+ iframe = document.body.appendChild(
+ iframe || jQuery.extend( document.createElement("iframe"), {
+ frameBorder: 0,
+ width: 0,
+ height: 0
+ })
+ );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write("");
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+ display = curCSS( elem, "display" );
+ document.body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+
+ return display;
+ }
+
+ jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ } else {
+ return getWidthOrHeight( elem, name, extra );
+ }
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+ ) : 0
+ );
+ }
+ };
+ });
+
+ if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+ style.removeAttribute ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+ }
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+ jQuery(function() {
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "marginRight" );
+ }
+ });
+ }
+ };
+ }
+
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+ jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ var ret = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+ }
+ }
+ };
+ });
+ }
+
+ });
+
+ if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+ }
+
+// These hooks are used by animate to expand properties
+ jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+ }, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+ });
+ var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ rselectTextarea = /^(?:select|textarea)/i;
+
+ jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+ });
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+ jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+ };
+
+ function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+ }
+ var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /
+
+
+
+ it('should auto compile', function() {
+ expect(element('div[compile]').text()).toBe('Hello Angular');
+ input('html').enter('{{name}}!');
+ expect(element('div[compile]').text()).toBe('Angular!');
+ });
+
+
+
+ *
+ *
+ * @param {string|DOMElement} element Element or HTML string to compile into a template function.
+ * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives.
+ * @param {number} maxPriority only apply directives lower then given priority (Only effects the
+ * root element(s), not their children)
+ * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template
+ * (a DOM element/tree) to a scope. Where:
+ *
+ * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to.
+ * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
+ * `template` and call the `cloneAttachFn` function allowing the caller to attach the
+ * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
+ * called as: `cloneAttachFn(clonedElement, scope)` where:
+ *
+ * * `clonedElement` - is a clone of the original `element` passed into the compiler.
+ * * `scope` - is the current scope with which the linking function is working with.
+ *
+ * Calling the linking function returns the element of the template. It is either the original element
+ * passed in, or the clone of the element if the `cloneAttachFn` is provided.
+ *
+ * After linking the view is not updated until after a call to $digest which typically is done by
+ * Angular automatically.
+ *
+ * If you need access to the bound view, there are two ways to do it:
+ *
+ * - If you are not asking the linking function to clone the template, create the DOM element(s)
+ * before you send them to the compiler and keep this reference around.
+ *
+ * var element = $compile('{{total}}
')(scope);
+ *
+ *
+ * - if on the other hand, you need the element to be cloned, the view reference from the original
+ * example would not point to the clone, but rather to the original template that was cloned. In
+ * this case, you can access the clone via the cloneAttachFn:
+ *
+ * var templateHTML = angular.element('{{total}}
'),
+ * scope = ....;
+ *
+ * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) {
+ * //attach the clone to DOM document at the right place
+ * });
+ *
+ * //now we have reference to the cloned DOM via `clone`
+ *
+ *
+ *
+ * For information on how the compiler works, see the
+ * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
+ */
+
+
+ /**
+ * @ngdoc service
+ * @name ng.$compileProvider
+ * @function
+ *
+ * @description
+ */
+ $CompileProvider.$inject = ['$provide'];
+ function $CompileProvider($provide) {
+ var hasDirectives = {},
+ Suffix = 'Directive',
+ COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
+ CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
+ MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ';
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$compileProvider#directive
+ * @methodOf ng.$compileProvider
+ * @function
+ *
+ * @description
+ * Register a new directives with the compiler.
+ *
+ * @param {string} name Name of the directive in camel-case. (ie ngBind
which will match as
+ * ng-bind
).
+ * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more
+ * info.
+ * @returns {ng.$compileProvider} Self for chaining.
+ */
+ this.directive = function registerDirective(name, directiveFactory) {
+ if (isString(name)) {
+ assertArg(directiveFactory, 'directive');
+ if (!hasDirectives.hasOwnProperty(name)) {
+ hasDirectives[name] = [];
+ $provide.factory(name + Suffix, ['$injector', '$exceptionHandler',
+ function($injector, $exceptionHandler) {
+ var directives = [];
+ forEach(hasDirectives[name], function(directiveFactory) {
+ try {
+ var directive = $injector.invoke(directiveFactory);
+ if (isFunction(directive)) {
+ directive = { compile: valueFn(directive) };
+ } else if (!directive.compile && directive.link) {
+ directive.compile = valueFn(directive.link);
+ }
+ directive.priority = directive.priority || 0;
+ directive.name = directive.name || name;
+ directive.require = directive.require || (directive.controller && directive.name);
+ directive.restrict = directive.restrict || 'A';
+ directives.push(directive);
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ return directives;
+ }]);
+ }
+ hasDirectives[name].push(directiveFactory);
+ } else {
+ forEach(name, reverseParams(registerDirective));
+ }
+ return this;
+ };
+
+
+ this.$get = [
+ '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
+ '$controller', '$rootScope',
+ function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
+ $controller, $rootScope) {
+
+ var Attributes = function(element, attr) {
+ this.$$element = element;
+ this.$attr = attr || {};
+ };
+
+ Attributes.prototype = {
+ $normalize: directiveNormalize,
+
+
+ /**
+ * Set a normalized attribute on the element in a way such that all directives
+ * can share the attribute. This function properly handles boolean attributes.
+ * @param {string} key Normalized key. (ie ngAttribute)
+ * @param {string|boolean} value The value to set. If `null` attribute will be deleted.
+ * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute.
+ * Defaults to true.
+ * @param {string=} attrName Optional none normalized name. Defaults to key.
+ */
+ $set: function(key, value, writeAttr, attrName) {
+ var booleanKey = getBooleanAttrName(this.$$element[0], key),
+ $$observers = this.$$observers;
+
+ if (booleanKey) {
+ this.$$element.prop(key, value);
+ attrName = booleanKey;
+ }
+
+ this[key] = value;
+
+ // translate normalized key to actual key
+ if (attrName) {
+ this.$attr[key] = attrName;
+ } else {
+ attrName = this.$attr[key];
+ if (!attrName) {
+ this.$attr[key] = attrName = snake_case(key, '-');
+ }
+ }
+
+ if (writeAttr !== false) {
+ if (value === null || value === undefined) {
+ this.$$element.removeAttr(attrName);
+ } else {
+ this.$$element.attr(attrName, value);
+ }
+ }
+
+ // fire observers
+ $$observers && forEach($$observers[key], function(fn) {
+ try {
+ fn(value);
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ },
+
+
+ /**
+ * Observe an interpolated attribute.
+ * The observer will never be called, if given attribute is not interpolated.
+ *
+ * @param {string} key Normalized key. (ie ngAttribute) .
+ * @param {function(*)} fn Function that will be called whenever the attribute value changes.
+ * @returns {function(*)} the `fn` Function passed in.
+ */
+ $observe: function(key, fn) {
+ var attrs = this,
+ $$observers = (attrs.$$observers || (attrs.$$observers = {})),
+ listeners = ($$observers[key] || ($$observers[key] = []));
+
+ listeners.push(fn);
+ $rootScope.$evalAsync(function() {
+ if (!listeners.$$inter) {
+ // no one registered attribute interpolation function, so lets call it manually
+ fn(attrs[key]);
+ }
+ });
+ return fn;
+ }
+ };
+
+ var startSymbol = $interpolate.startSymbol(),
+ endSymbol = $interpolate.endSymbol(),
+ denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
+ ? identity
+ : function denormalizeTemplate(template) {
+ return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
+ };
+
+
+ return compile;
+
+ //================================
+
+ function compile($compileNodes, transcludeFn, maxPriority) {
+ if (!($compileNodes instanceof jqLite)) {
+ // jquery always rewraps, where as we need to preserve the original selector so that we can modify it.
+ $compileNodes = jqLite($compileNodes);
+ }
+ // We can not compile top level text elements since text nodes can be merged and we will
+ // not be able to attach scope data to them, so we will wrap them in
+ forEach($compileNodes, function(node, index){
+ if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) {
+ $compileNodes[index] = jqLite(node).wrap(' ').parent()[0];
+ }
+ });
+ var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority);
+ return function publicLinkFn(scope, cloneConnectFn){
+ assertArg(scope, 'scope');
+ // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
+ // and sometimes changes the structure of the DOM.
+ var $linkNode = cloneConnectFn
+ ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!!
+ : $compileNodes;
+ $linkNode.data('$scope', scope);
+ safeAddClass($linkNode, 'ng-scope');
+ if (cloneConnectFn) cloneConnectFn($linkNode, scope);
+ if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
+ return $linkNode;
+ };
+ }
+
+ function wrongMode(localName, mode) {
+ throw Error("Unsupported '" + mode + "' for '" + localName + "'.");
+ }
+
+ function safeAddClass($element, className) {
+ try {
+ $element.addClass(className);
+ } catch(e) {
+ // ignore, since it means that we are trying to set class on
+ // SVG element, where class name is read-only.
+ }
+ }
+
+ /**
+ * Compile function matches each node in nodeList against the directives. Once all directives
+ * for a particular node are collected their compile functions are executed. The compile
+ * functions return values - the linking functions - are combined into a composite linking
+ * function, which is the a linking function for the node.
+ *
+ * @param {NodeList} nodeList an array of nodes to compile
+ * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
+ * scope argument is auto-generated to the new child of the transcluded parent scope.
+ * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then the
+ * rootElement must be set the jqLite collection of the compile root. This is
+ * needed so that the jqLite collection items can be replaced with widgets.
+ * @param {number=} max directive priority
+ * @returns {?function} A composite linking function of all of the matched directives or null.
+ */
+ function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority) {
+ var linkFns = [],
+ nodeLinkFn, childLinkFn, directives, attrs, linkFnFound;
+
+ for(var i = 0; i < nodeList.length; i++) {
+ attrs = new Attributes();
+
+ // we must always refer to nodeList[i] since the nodes can be replaced underneath us.
+ directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
+
+ nodeLinkFn = (directives.length)
+ ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement)
+ : null;
+
+ childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !nodeList[i].childNodes.length)
+ ? null
+ : compileNodes(nodeList[i].childNodes,
+ nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
+
+ linkFns.push(nodeLinkFn);
+ linkFns.push(childLinkFn);
+ linkFnFound = (linkFnFound || nodeLinkFn || childLinkFn);
+ }
+
+ // return a linking function if we have found anything, null otherwise
+ return linkFnFound ? compositeLinkFn : null;
+
+ function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
+ var nodeLinkFn, childLinkFn, node, childScope, childTranscludeFn, i, ii, n;
+
+ // copy nodeList so that linking doesn't break due to live list updates.
+ var stableNodeList = [];
+ for (i = 0, ii = nodeList.length; i < ii; i++) {
+ stableNodeList.push(nodeList[i]);
+ }
+
+ for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) {
+ node = stableNodeList[n];
+ nodeLinkFn = linkFns[i++];
+ childLinkFn = linkFns[i++];
+
+ if (nodeLinkFn) {
+ if (nodeLinkFn.scope) {
+ childScope = scope.$new(isObject(nodeLinkFn.scope));
+ jqLite(node).data('$scope', childScope);
+ } else {
+ childScope = scope;
+ }
+ childTranscludeFn = nodeLinkFn.transclude;
+ if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
+ nodeLinkFn(childLinkFn, childScope, node, $rootElement,
+ (function(transcludeFn) {
+ return function(cloneFn) {
+ var transcludeScope = scope.$new();
+
+ return transcludeFn(transcludeScope, cloneFn).
+ bind('$destroy', bind(transcludeScope, transcludeScope.$destroy));
+ };
+ })(childTranscludeFn || transcludeFn)
+ );
+ } else {
+ nodeLinkFn(childLinkFn, childScope, node, undefined, boundTranscludeFn);
+ }
+ } else if (childLinkFn) {
+ childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Looks for directives on the given node and adds them to the directive collection which is
+ * sorted.
+ *
+ * @param node Node to search.
+ * @param directives An array to which the directives are added to. This array is sorted before
+ * the function returns.
+ * @param attrs The shared attrs object which is used to populate the normalized attributes.
+ * @param {number=} maxPriority Max directive priority.
+ */
+ function collectDirectives(node, directives, attrs, maxPriority) {
+ var nodeType = node.nodeType,
+ attrsMap = attrs.$attr,
+ match,
+ className;
+
+ switch(nodeType) {
+ case 1: /* Element */
+ // use the node name:
+ addDirective(directives,
+ directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);
+
+ // iterate over the attributes
+ for (var attr, name, nName, value, nAttrs = node.attributes,
+ j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
+ attr = nAttrs[j];
+ if (attr.specified) {
+ name = attr.name;
+ nName = directiveNormalize(name.toLowerCase());
+ attrsMap[nName] = name;
+ attrs[nName] = value = trim((msie && name == 'href')
+ ? decodeURIComponent(node.getAttribute(name, 2))
+ : attr.value);
+ if (getBooleanAttrName(node, nName)) {
+ attrs[nName] = true; // presence means true
+ }
+ addAttrInterpolateDirective(node, directives, value, nName);
+ addDirective(directives, nName, 'A', maxPriority);
+ }
+ }
+
+ // use class as directive
+ className = node.className;
+ if (isString(className) && className !== '') {
+ while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
+ nName = directiveNormalize(match[2]);
+ if (addDirective(directives, nName, 'C', maxPriority)) {
+ attrs[nName] = trim(match[3]);
+ }
+ className = className.substr(match.index + match[0].length);
+ }
+ }
+ break;
+ case 3: /* Text Node */
+ addTextInterpolateDirective(directives, node.nodeValue);
+ break;
+ case 8: /* Comment */
+ try {
+ match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
+ if (match) {
+ nName = directiveNormalize(match[1]);
+ if (addDirective(directives, nName, 'M', maxPriority)) {
+ attrs[nName] = trim(match[2]);
+ }
+ }
+ } catch (e) {
+ // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value.
+ // Just ignore it and continue. (Can't seem to reproduce in test case.)
+ }
+ break;
+ }
+
+ directives.sort(byPriority);
+ return directives;
+ }
+
+
+ /**
+ * Once the directives have been collected their compile functions is executed. This method
+ * is responsible for inlining directive templates as well as terminating the application
+ * of the directives if the terminal directive has been reached..
+ *
+ * @param {Array} directives Array of collected directives to execute their compile function.
+ * this needs to be pre-sorted by priority order.
+ * @param {Node} compileNode The raw DOM node to apply the compile functions to
+ * @param {Object} templateAttrs The shared attribute function
+ * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the
+ * scope argument is auto-generated to the new child of the transcluded parent scope.
+ * @param {DOMElement} $rootElement If we are working on the root of the compile tree then this
+ * argument has the root jqLite array so that we can replace widgets on it.
+ * @returns linkFn
+ */
+ function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, $rootElement) {
+ var terminalPriority = -Number.MAX_VALUE,
+ preLinkFns = [],
+ postLinkFns = [],
+ newScopeDirective = null,
+ newIsolateScopeDirective = null,
+ templateDirective = null,
+ $compileNode = templateAttrs.$$element = jqLite(compileNode),
+ directive,
+ directiveName,
+ $template,
+ transcludeDirective,
+ childTranscludeFn = transcludeFn,
+ controllerDirectives,
+ linkFn,
+ directiveValue;
+
+ // executes all directives on the current element
+ for(var i = 0, ii = directives.length; i < ii; i++) {
+ directive = directives[i];
+ $template = undefined;
+
+ if (terminalPriority > directive.priority) {
+ break; // prevent further processing of directives
+ }
+
+ if (directiveValue = directive.scope) {
+ assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode);
+ if (isObject(directiveValue)) {
+ safeAddClass($compileNode, 'ng-isolate-scope');
+ newIsolateScopeDirective = directive;
+ }
+ safeAddClass($compileNode, 'ng-scope');
+ newScopeDirective = newScopeDirective || directive;
+ }
+
+ directiveName = directive.name;
+
+ if (directiveValue = directive.controller) {
+ controllerDirectives = controllerDirectives || {};
+ assertNoDuplicate("'" + directiveName + "' controller",
+ controllerDirectives[directiveName], directive, $compileNode);
+ controllerDirectives[directiveName] = directive;
+ }
+
+ if (directiveValue = directive.transclude) {
+ assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode);
+ transcludeDirective = directive;
+ terminalPriority = directive.priority;
+ if (directiveValue == 'element') {
+ $template = jqLite(compileNode);
+ $compileNode = templateAttrs.$$element =
+ jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' '));
+ compileNode = $compileNode[0];
+ replaceWith($rootElement, jqLite($template[0]), compileNode);
+ childTranscludeFn = compile($template, transcludeFn, terminalPriority);
+ } else {
+ $template = jqLite(JQLiteClone(compileNode)).contents();
+ $compileNode.html(''); // clear contents
+ childTranscludeFn = compile($template, transcludeFn);
+ }
+ }
+
+ if ((directiveValue = directive.template)) {
+ assertNoDuplicate('template', templateDirective, directive, $compileNode);
+ templateDirective = directive;
+ directiveValue = denormalizeTemplate(directiveValue);
+
+ if (directive.replace) {
+ $template = jqLite('' +
+ trim(directiveValue) +
+ '
').contents();
+ compileNode = $template[0];
+
+ if ($template.length != 1 || compileNode.nodeType !== 1) {
+ throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue);
+ }
+
+ replaceWith($rootElement, $compileNode, compileNode);
+
+ var newTemplateAttrs = {$attr: {}};
+
+ // combine directives from the original node and from the template:
+ // - take the array of directives for this element
+ // - split it into two parts, those that were already applied and those that weren't
+ // - collect directives from the template, add them to the second group and sort them
+ // - append the second group with new directives to the first group
+ directives = directives.concat(
+ collectDirectives(
+ compileNode,
+ directives.splice(i + 1, directives.length - (i + 1)),
+ newTemplateAttrs
+ )
+ );
+ mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
+
+ ii = directives.length;
+ } else {
+ $compileNode.html(directiveValue);
+ }
+ }
+
+ if (directive.templateUrl) {
+ assertNoDuplicate('template', templateDirective, directive, $compileNode);
+ templateDirective = directive;
+ nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i),
+ nodeLinkFn, $compileNode, templateAttrs, $rootElement, directive.replace,
+ childTranscludeFn);
+ ii = directives.length;
+ } else if (directive.compile) {
+ try {
+ linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn);
+ if (isFunction(linkFn)) {
+ addLinkFns(null, linkFn);
+ } else if (linkFn) {
+ addLinkFns(linkFn.pre, linkFn.post);
+ }
+ } catch (e) {
+ $exceptionHandler(e, startingTag($compileNode));
+ }
+ }
+
+ if (directive.terminal) {
+ nodeLinkFn.terminal = true;
+ terminalPriority = Math.max(terminalPriority, directive.priority);
+ }
+
+ }
+
+ nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope;
+ nodeLinkFn.transclude = transcludeDirective && childTranscludeFn;
+
+ // might be normal or delayed nodeLinkFn depending on if templateUrl is present
+ return nodeLinkFn;
+
+ ////////////////////
+
+ function addLinkFns(pre, post) {
+ if (pre) {
+ pre.require = directive.require;
+ preLinkFns.push(pre);
+ }
+ if (post) {
+ post.require = directive.require;
+ postLinkFns.push(post);
+ }
+ }
+
+
+ function getControllers(require, $element) {
+ var value, retrievalMethod = 'data', optional = false;
+ if (isString(require)) {
+ while((value = require.charAt(0)) == '^' || value == '?') {
+ require = require.substr(1);
+ if (value == '^') {
+ retrievalMethod = 'inheritedData';
+ }
+ optional = optional || value == '?';
+ }
+ value = $element[retrievalMethod]('$' + require + 'Controller');
+ if (!value && !optional) {
+ throw Error("No controller: " + require);
+ }
+ return value;
+ } else if (isArray(require)) {
+ value = [];
+ forEach(require, function(require) {
+ value.push(getControllers(require, $element));
+ });
+ }
+ return value;
+ }
+
+
+ function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) {
+ var attrs, $element, i, ii, linkFn, controller;
+
+ if (compileNode === linkNode) {
+ attrs = templateAttrs;
+ } else {
+ attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr));
+ }
+ $element = attrs.$$element;
+
+ if (newIsolateScopeDirective) {
+ var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/;
+
+ var parentScope = scope.$parent || scope;
+
+ forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
+ var match = definiton.match(LOCAL_REGEXP) || [],
+ attrName = match[2]|| scopeName,
+ mode = match[1], // @, =, or &
+ lastValue,
+ parentGet, parentSet;
+
+ switch (mode) {
+
+ case '@': {
+ attrs.$observe(attrName, function(value) {
+ scope[scopeName] = value;
+ });
+ attrs.$$observers[attrName].$$scope = parentScope;
+ break;
+ }
+
+ case '=': {
+ parentGet = $parse(attrs[attrName]);
+ parentSet = parentGet.assign || function() {
+ // reset the change, or we will throw this exception on every $digest
+ lastValue = scope[scopeName] = parentGet(parentScope);
+ throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] +
+ ' (directive: ' + newIsolateScopeDirective.name + ')');
+ };
+ lastValue = scope[scopeName] = parentGet(parentScope);
+ scope.$watch(function parentValueWatch() {
+ var parentValue = parentGet(parentScope);
+
+ if (parentValue !== scope[scopeName]) {
+ // we are out of sync and need to copy
+ if (parentValue !== lastValue) {
+ // parent changed and it has precedence
+ lastValue = scope[scopeName] = parentValue;
+ } else {
+ // if the parent can be assigned then do so
+ parentSet(parentScope, parentValue = lastValue = scope[scopeName]);
+ }
+ }
+ return parentValue;
+ });
+ break;
+ }
+
+ case '&': {
+ parentGet = $parse(attrs[attrName]);
+ scope[scopeName] = function(locals) {
+ return parentGet(parentScope, locals);
+ }
+ break;
+ }
+
+ default: {
+ throw Error('Invalid isolate scope definition for directive ' +
+ newIsolateScopeDirective.name + ': ' + definiton);
+ }
+ }
+ });
+ }
+
+ if (controllerDirectives) {
+ forEach(controllerDirectives, function(directive) {
+ var locals = {
+ $scope: scope,
+ $element: $element,
+ $attrs: attrs,
+ $transclude: boundTranscludeFn
+ };
+
+ controller = directive.controller;
+ if (controller == '@') {
+ controller = attrs[directive.name];
+ }
+
+ $element.data(
+ '$' + directive.name + 'Controller',
+ $controller(controller, locals));
+ });
+ }
+
+ // PRELINKING
+ for(i = 0, ii = preLinkFns.length; i < ii; i++) {
+ try {
+ linkFn = preLinkFns[i];
+ linkFn(scope, $element, attrs,
+ linkFn.require && getControllers(linkFn.require, $element));
+ } catch (e) {
+ $exceptionHandler(e, startingTag($element));
+ }
+ }
+
+ // RECURSION
+ childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
+
+ // POSTLINKING
+ for(i = 0, ii = postLinkFns.length; i < ii; i++) {
+ try {
+ linkFn = postLinkFns[i];
+ linkFn(scope, $element, attrs,
+ linkFn.require && getControllers(linkFn.require, $element));
+ } catch (e) {
+ $exceptionHandler(e, startingTag($element));
+ }
+ }
+ }
+ }
+
+
+ /**
+ * looks up the directive and decorates it with exception handling and proper parameters. We
+ * call this the boundDirective.
+ *
+ * @param {string} name name of the directive to look up.
+ * @param {string} location The directive must be found in specific format.
+ * String containing any of theses characters:
+ *
+ * * `E`: element name
+ * * `A': attribute
+ * * `C`: class
+ * * `M`: comment
+ * @returns true if directive was added.
+ */
+ function addDirective(tDirectives, name, location, maxPriority) {
+ var match = false;
+ if (hasDirectives.hasOwnProperty(name)) {
+ for(var directive, directives = $injector.get(name + Suffix),
+ i = 0, ii = directives.length; i directive.priority) &&
+ directive.restrict.indexOf(location) != -1) {
+ tDirectives.push(directive);
+ match = true;
+ }
+ } catch(e) { $exceptionHandler(e); }
+ }
+ }
+ return match;
+ }
+
+
+ /**
+ * When the element is replaced with HTML template then the new attributes
+ * on the template need to be merged with the existing attributes in the DOM.
+ * The desired effect is to have both of the attributes present.
+ *
+ * @param {object} dst destination attributes (original DOM)
+ * @param {object} src source attributes (from the directive template)
+ */
+ function mergeTemplateAttributes(dst, src) {
+ var srcAttr = src.$attr,
+ dstAttr = dst.$attr,
+ $element = dst.$$element;
+
+ // reapply the old attributes to the new element
+ forEach(dst, function(value, key) {
+ if (key.charAt(0) != '$') {
+ if (src[key]) {
+ value += (key === 'style' ? ';' : ' ') + src[key];
+ }
+ dst.$set(key, value, true, srcAttr[key]);
+ }
+ });
+
+ // copy the new attributes on the old attrs object
+ forEach(src, function(value, key) {
+ if (key == 'class') {
+ safeAddClass($element, value);
+ dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value;
+ } else if (key == 'style') {
+ $element.attr('style', $element.attr('style') + ';' + value);
+ } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) {
+ dst[key] = value;
+ dstAttr[key] = srcAttr[key];
+ }
+ });
+ }
+
+
+ function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs,
+ $rootElement, replace, childTranscludeFn) {
+ var linkQueue = [],
+ afterTemplateNodeLinkFn,
+ afterTemplateChildLinkFn,
+ beforeTemplateCompileNode = $compileNode[0],
+ origAsyncDirective = directives.shift(),
+ // The fact that we have to copy and patch the directive seems wrong!
+ derivedSyncDirective = extend({}, origAsyncDirective, {
+ controller: null, templateUrl: null, transclude: null, scope: null
+ });
+
+ $compileNode.html('');
+
+ $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}).
+ success(function(content) {
+ var compileNode, tempTemplateAttrs, $template;
+
+ content = denormalizeTemplate(content);
+
+ if (replace) {
+ $template = jqLite('' + trim(content) + '
').contents();
+ compileNode = $template[0];
+
+ if ($template.length != 1 || compileNode.nodeType !== 1) {
+ throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content);
+ }
+
+ tempTemplateAttrs = {$attr: {}};
+ replaceWith($rootElement, $compileNode, compileNode);
+ collectDirectives(compileNode, directives, tempTemplateAttrs);
+ mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
+ } else {
+ compileNode = beforeTemplateCompileNode;
+ $compileNode.html(content);
+ }
+
+ directives.unshift(derivedSyncDirective);
+ afterTemplateNodeLinkFn = applyDirectivesToNode(directives, $compileNode, tAttrs, childTranscludeFn);
+ afterTemplateChildLinkFn = compileNodes($compileNode.contents(), childTranscludeFn);
+
+
+ while(linkQueue.length) {
+ var controller = linkQueue.pop(),
+ linkRootElement = linkQueue.pop(),
+ beforeTemplateLinkNode = linkQueue.pop(),
+ scope = linkQueue.pop(),
+ linkNode = compileNode;
+
+ if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
+ // it was cloned therefore we have to clone as well.
+ linkNode = JQLiteClone(compileNode);
+ replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode);
+ }
+
+ afterTemplateNodeLinkFn(function() {
+ beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller);
+ }, scope, linkNode, $rootElement, controller);
+ }
+ linkQueue = null;
+ }).
+ error(function(response, code, headers, config) {
+ throw Error('Failed to load template: ' + config.url);
+ });
+
+ return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) {
+ if (linkQueue) {
+ linkQueue.push(scope);
+ linkQueue.push(node);
+ linkQueue.push(rootElement);
+ linkQueue.push(controller);
+ } else {
+ afterTemplateNodeLinkFn(function() {
+ beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller);
+ }, scope, node, rootElement, controller);
+ }
+ };
+ }
+
+
+ /**
+ * Sorting function for bound directives.
+ */
+ function byPriority(a, b) {
+ return b.priority - a.priority;
+ }
+
+
+ function assertNoDuplicate(what, previousDirective, directive, element) {
+ if (previousDirective) {
+ throw Error('Multiple directives [' + previousDirective.name + ', ' +
+ directive.name + '] asking for ' + what + ' on: ' + startingTag(element));
+ }
+ }
+
+
+ function addTextInterpolateDirective(directives, text) {
+ var interpolateFn = $interpolate(text, true);
+ if (interpolateFn) {
+ directives.push({
+ priority: 0,
+ compile: valueFn(function textInterpolateLinkFn(scope, node) {
+ var parent = node.parent(),
+ bindings = parent.data('$binding') || [];
+ bindings.push(interpolateFn);
+ safeAddClass(parent.data('$binding', bindings), 'ng-binding');
+ scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
+ node[0].nodeValue = value;
+ });
+ })
+ });
+ }
+ }
+
+
+ function addAttrInterpolateDirective(node, directives, value, name) {
+ var interpolateFn = $interpolate(value, true);
+
+
+ // no interpolation found -> ignore
+ if (!interpolateFn) return;
+
+ directives.push({
+ priority: 100,
+ compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) {
+ var $$observers = (attr.$$observers || (attr.$$observers = {}));
+
+ if (name === 'class') {
+ // we need to interpolate classes again, in the case the element was replaced
+ // and therefore the two class attrs got merged - we want to interpolate the result
+ interpolateFn = $interpolate(attr[name], true);
+ }
+
+ attr[name] = undefined;
+ ($$observers[name] || ($$observers[name] = [])).$$inter = true;
+ (attr.$$observers && attr.$$observers[name].$$scope || scope).
+ $watch(interpolateFn, function interpolateFnWatchAction(value) {
+ attr.$set(name, value);
+ });
+ })
+ });
+ }
+
+
+ /**
+ * This is a special jqLite.replaceWith, which can replace items which
+ * have no parents, provided that the containing jqLite collection is provided.
+ *
+ * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes
+ * in the root of the tree.
+ * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell,
+ * but replace its DOM node reference.
+ * @param {Node} newNode The new DOM node.
+ */
+ function replaceWith($rootElement, $element, newNode) {
+ var oldNode = $element[0],
+ parent = oldNode.parentNode,
+ i, ii;
+
+ if ($rootElement) {
+ for(i = 0, ii = $rootElement.length; i < ii; i++) {
+ if ($rootElement[i] == oldNode) {
+ $rootElement[i] = newNode;
+ break;
+ }
+ }
+ }
+
+ if (parent) {
+ parent.replaceChild(newNode, oldNode);
+ }
+
+ newNode[jqLite.expando] = oldNode[jqLite.expando];
+ $element[0] = newNode;
+ }
+ }];
+ }
+
+ var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i;
+ /**
+ * Converts all accepted directives format into proper directive name.
+ * All of these will become 'myDirective':
+ * my:DiRective
+ * my-directive
+ * x-my-directive
+ * data-my:directive
+ *
+ * Also there is special case for Moz prefix starting with upper case letter.
+ * @param name Name to normalize
+ */
+ function directiveNormalize(name) {
+ return camelCase(name.replace(PREFIX_REGEXP, ''));
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$compile.directive.Attributes
+ * @description
+ *
+ * A shared object between directive compile / linking functions which contains normalized DOM element
+ * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed
+ * since all of these are treated as equivalent in Angular:
+ *
+ *
+ */
+
+ /**
+ * @ngdoc property
+ * @name ng.$compile.directive.Attributes#$attr
+ * @propertyOf ng.$compile.directive.Attributes
+ * @returns {object} A map of DOM element attribute names to the normalized name. This is
+ * needed to do reverse lookup from normalized name back to actual name.
+ */
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$compile.directive.Attributes#$set
+ * @methodOf ng.$compile.directive.Attributes
+ * @function
+ *
+ * @description
+ * Set DOM element attribute value.
+ *
+ *
+ * @param {string} name Normalized element attribute name of the property to modify. The name is
+ * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr}
+ * property to the original name.
+ * @param {string} value Value to set the attribute to.
+ */
+
+
+
+ /**
+ * Closure compiler type information
+ */
+
+ function nodesetLinkingFn(
+ /* angular.Scope */ scope,
+ /* NodeList */ nodeList,
+ /* Element */ rootElement,
+ /* function(Function) */ boundTranscludeFn
+ ){}
+
+ function directiveLinkingFn(
+ /* nodesetLinkingFn */ nodesetLinkingFn,
+ /* angular.Scope */ scope,
+ /* Node */ node,
+ /* Element */ rootElement,
+ /* function(Function) */ boundTranscludeFn
+ ){}
+
+ /**
+ * @ngdoc object
+ * @name ng.$controllerProvider
+ * @description
+ * The {@link ng.$controller $controller service} is used by Angular to create new
+ * controllers.
+ *
+ * This provider allows controller registration via the
+ * {@link ng.$controllerProvider#register register} method.
+ */
+ function $ControllerProvider() {
+ var controllers = {};
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$controllerProvider#register
+ * @methodOf ng.$controllerProvider
+ * @param {string} name Controller name
+ * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI
+ * annotations in the array notation).
+ */
+ this.register = function(name, constructor) {
+ if (isObject(name)) {
+ extend(controllers, name)
+ } else {
+ controllers[name] = constructor;
+ }
+ };
+
+
+ this.$get = ['$injector', '$window', function($injector, $window) {
+
+ /**
+ * @ngdoc function
+ * @name ng.$controller
+ * @requires $injector
+ *
+ * @param {Function|string} constructor If called with a function then it's considered to be the
+ * controller constructor function. Otherwise it's considered to be a string which is used
+ * to retrieve the controller constructor using the following steps:
+ *
+ * * check if a controller with given name is registered via `$controllerProvider`
+ * * check if evaluating the string on the current scope returns a constructor
+ * * check `window[constructor]` on the global `window` object
+ *
+ * @param {Object} locals Injection locals for Controller.
+ * @return {Object} Instance of given controller.
+ *
+ * @description
+ * `$controller` service is responsible for instantiating controllers.
+ *
+ * It's just simple call to {@link AUTO.$injector $injector}, but extracted into
+ * a service, so that one can override this service with {@link https://gist.github.com/1649788
+ * BC version}.
+ */
+ return function(constructor, locals) {
+ if(isString(constructor)) {
+ var name = constructor;
+ constructor = controllers.hasOwnProperty(name)
+ ? controllers[name]
+ : getter(locals.$scope, name, true) || getter($window, name, true);
+
+ assertArgFn(constructor, name, true);
+ }
+
+ return $injector.instantiate(constructor, locals);
+ };
+ }];
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$document
+ * @requires $window
+ *
+ * @description
+ * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
+ * element.
+ */
+ function $DocumentProvider(){
+ this.$get = ['$window', function(window){
+ return jqLite(window.document);
+ }];
+ }
+
+ /**
+ * @ngdoc function
+ * @name ng.$exceptionHandler
+ * @requires $log
+ *
+ * @description
+ * Any uncaught exception in angular expressions is delegated to this service.
+ * The default implementation simply delegates to `$log.error` which logs it into
+ * the browser console.
+ *
+ * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
+ * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
+ *
+ * @param {Error} exception Exception associated with the error.
+ * @param {string=} cause optional information about the context in which
+ * the error was thrown.
+ *
+ */
+ function $ExceptionHandlerProvider() {
+ this.$get = ['$log', function($log){
+ return function(exception, cause) {
+ $log.error.apply($log, arguments);
+ };
+ }];
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$interpolateProvider
+ * @function
+ *
+ * @description
+ *
+ * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
+ */
+ function $InterpolateProvider() {
+ var startSymbol = '{{';
+ var endSymbol = '}}';
+
+ /**
+ * @ngdoc method
+ * @name ng.$interpolateProvider#startSymbol
+ * @methodOf ng.$interpolateProvider
+ * @description
+ * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
+ *
+ * @param {string=} value new value to set the starting symbol to.
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ */
+ this.startSymbol = function(value){
+ if (value) {
+ startSymbol = value;
+ return this;
+ } else {
+ return startSymbol;
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ng.$interpolateProvider#endSymbol
+ * @methodOf ng.$interpolateProvider
+ * @description
+ * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
+ *
+ * @param {string=} value new value to set the ending symbol to.
+ * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
+ */
+ this.endSymbol = function(value){
+ if (value) {
+ endSymbol = value;
+ return this;
+ } else {
+ return endSymbol;
+ }
+ };
+
+
+ this.$get = ['$parse', '$exceptionHandler', function($parse, $exceptionHandler) {
+ var startSymbolLength = startSymbol.length,
+ endSymbolLength = endSymbol.length;
+
+ /**
+ * @ngdoc function
+ * @name ng.$interpolate
+ * @function
+ *
+ * @requires $parse
+ *
+ * @description
+ *
+ * Compiles a string with markup into an interpolation function. This service is used by the
+ * HTML {@link ng.$compile $compile} service for data binding. See
+ * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
+ * interpolation markup.
+ *
+ *
+
+ var $interpolate = ...; // injected
+ var exp = $interpolate('Hello {{name}}!');
+ expect(exp({name:'Angular'}).toEqual('Hello Angular!');
+
+ *
+ *
+ * @param {string} text The text with markup to interpolate.
+ * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
+ * embedded expression in order to return an interpolation function. Strings with no
+ * embedded expression will return null for the interpolation function.
+ * @returns {function(context)} an interpolation function which is used to compute the interpolated
+ * string. The function has these parameters:
+ *
+ * * `context`: an object against which any expressions embedded in the strings are evaluated
+ * against.
+ *
+ */
+ function $interpolate(text, mustHaveExpression) {
+ var startIndex,
+ endIndex,
+ index = 0,
+ parts = [],
+ length = text.length,
+ hasInterpolation = false,
+ fn,
+ exp,
+ concat = [];
+
+ while(index < length) {
+ if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) &&
+ ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) {
+ (index != startIndex) && parts.push(text.substring(index, startIndex));
+ parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex)));
+ fn.exp = exp;
+ index = endIndex + endSymbolLength;
+ hasInterpolation = true;
+ } else {
+ // we did not find anything, so we have to add the remainder to the parts array
+ (index != length) && parts.push(text.substring(index));
+ index = length;
+ }
+ }
+
+ if (!(length = parts.length)) {
+ // we added, nothing, must have been an empty string.
+ parts.push('');
+ length = 1;
+ }
+
+ if (!mustHaveExpression || hasInterpolation) {
+ concat.length = length;
+ fn = function(context) {
+ try {
+ for(var i = 0, ii = length, part; i html5 url
+ } else {
+ return composeProtocolHostPort(match.protocol, match.host, match.port) +
+ pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length);
+ }
+ }
+
+
+ function convertToHashbangUrl(url, basePath, hashPrefix) {
+ var match = matchUrl(url);
+
+ // already hashbang url
+ if (decodeURIComponent(match.path) == basePath) {
+ return url;
+ // convert html5 url -> hashbang url
+ } else {
+ var search = match.search && '?' + match.search || '',
+ hash = match.hash && '#' + match.hash || '',
+ pathPrefix = pathPrefixFromBase(basePath),
+ path = match.path.substr(pathPrefix.length);
+
+ if (match.path.indexOf(pathPrefix) !== 0) {
+ throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !');
+ }
+
+ return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath +
+ '#' + hashPrefix + path + search + hash;
+ }
+ }
+
+
+ /**
+ * LocationUrl represents an url
+ * This object is exposed as $location service when HTML5 mode is enabled and supported
+ *
+ * @constructor
+ * @param {string} url HTML5 url
+ * @param {string} pathPrefix
+ */
+ function LocationUrl(url, pathPrefix, appBaseUrl) {
+ pathPrefix = pathPrefix || '';
+
+ /**
+ * Parse given html5 (regular) url string into properties
+ * @param {string} newAbsoluteUrl HTML5 url
+ * @private
+ */
+ this.$$parse = function(newAbsoluteUrl) {
+ var match = matchUrl(newAbsoluteUrl, this);
+
+ if (match.path.indexOf(pathPrefix) !== 0) {
+ throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !');
+ }
+
+ this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length));
+ this.$$search = parseKeyValue(match.search);
+ this.$$hash = match.hash && decodeURIComponent(match.hash) || '';
+
+ this.$$compose();
+ };
+
+ /**
+ * Compose url and update `absUrl` property
+ * @private
+ */
+ this.$$compose = function() {
+ var search = toKeyValue(this.$$search),
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
+
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
+ this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
+ pathPrefix + this.$$url;
+ };
+
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return absoluteLinkUrl;
+ }
+ }
+
+
+ this.$$parse(url);
+ }
+
+
+ /**
+ * LocationHashbangUrl represents url
+ * This object is exposed as $location service when html5 history api is disabled or not supported
+ *
+ * @constructor
+ * @param {string} url Legacy url
+ * @param {string} hashPrefix Prefix for hash part (containing path and search)
+ */
+ function LocationHashbangUrl(url, hashPrefix, appBaseUrl) {
+ var basePath;
+
+ /**
+ * Parse given hashbang url into properties
+ * @param {string} url Hashbang url
+ * @private
+ */
+ this.$$parse = function(url) {
+ var match = matchUrl(url, this);
+
+
+ if (match.hash && match.hash.indexOf(hashPrefix) !== 0) {
+ throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !');
+ }
+
+ basePath = match.path + (match.search ? '?' + match.search : '');
+ match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length));
+ if (match[1]) {
+ this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]);
+ } else {
+ this.$$path = '';
+ }
+
+ this.$$search = parseKeyValue(match[3]);
+ this.$$hash = match[5] && decodeURIComponent(match[5]) || '';
+
+ this.$$compose();
+ };
+
+ /**
+ * Compose hashbang url and update `absUrl` property
+ * @private
+ */
+ this.$$compose = function() {
+ var search = toKeyValue(this.$$search),
+ hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
+
+ this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
+ this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) +
+ basePath + (this.$$url ? '#' + hashPrefix + this.$$url : '');
+ };
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return absoluteLinkUrl;
+ }
+ }
+
+
+ this.$$parse(url);
+ }
+
+
+ LocationUrl.prototype = {
+
+ /**
+ * Has any change been replacing ?
+ * @private
+ */
+ $$replace: false,
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#absUrl
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return full url representation with all segments encoded according to rules specified in
+ * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}.
+ *
+ * @return {string} full url
+ */
+ absUrl: locationGetter('$$absUrl'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#url
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return url (e.g. `/path?a=b#hash`) when called without any parameter.
+ *
+ * Change path, search and hash, when called with parameter and return `$location`.
+ *
+ * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
+ * @return {string} url
+ */
+ url: function(url, replace) {
+ if (isUndefined(url))
+ return this.$$url;
+
+ var match = PATH_MATCH.exec(url);
+ if (match[1]) this.path(decodeURIComponent(match[1]));
+ if (match[2] || match[1]) this.search(match[3] || '');
+ this.hash(match[5] || '', replace);
+
+ return this;
+ },
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#protocol
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return protocol of current url.
+ *
+ * @return {string} protocol of current url
+ */
+ protocol: locationGetter('$$protocol'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#host
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return host of current url.
+ *
+ * @return {string} host of current url.
+ */
+ host: locationGetter('$$host'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#port
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter only.
+ *
+ * Return port of current url.
+ *
+ * @return {Number} port
+ */
+ port: locationGetter('$$port'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#path
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return path of current url when called without any parameter.
+ *
+ * Change path when called with parameter and return `$location`.
+ *
+ * Note: Path should always begin with forward slash (/), this method will add the forward slash
+ * if it is missing.
+ *
+ * @param {string=} path New path
+ * @return {string} path
+ */
+ path: locationGetterSetter('$$path', function(path) {
+ return path.charAt(0) == '/' ? path : '/' + path;
+ }),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#search
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return search part (as object) of current url when called without any parameter.
+ *
+ * Change search part when called with parameter and return `$location`.
+ *
+ * @param {string|object=} search New search params - string or hash object
+ * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a
+ * single search parameter. If the value is `null`, the parameter will be deleted.
+ *
+ * @return {string} search
+ */
+ search: function(search, paramValue) {
+ if (isUndefined(search))
+ return this.$$search;
+
+ if (isDefined(paramValue)) {
+ if (paramValue === null) {
+ delete this.$$search[search];
+ } else {
+ this.$$search[search] = paramValue;
+ }
+ } else {
+ this.$$search = isString(search) ? parseKeyValue(search) : search;
+ }
+
+ this.$$compose();
+ return this;
+ },
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#hash
+ * @methodOf ng.$location
+ *
+ * @description
+ * This method is getter / setter.
+ *
+ * Return hash fragment when called without any parameter.
+ *
+ * Change hash fragment when called with parameter and return `$location`.
+ *
+ * @param {string=} hash New hash fragment
+ * @return {string} hash
+ */
+ hash: locationGetterSetter('$$hash', identity),
+
+ /**
+ * @ngdoc method
+ * @name ng.$location#replace
+ * @methodOf ng.$location
+ *
+ * @description
+ * If called, all changes to $location during current `$digest` will be replacing current history
+ * record, instead of adding new one.
+ */
+ replace: function() {
+ this.$$replace = true;
+ return this;
+ }
+ };
+
+ LocationHashbangUrl.prototype = inherit(LocationUrl.prototype);
+
+ function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) {
+ LocationHashbangUrl.apply(this, arguments);
+
+
+ this.$$rewriteAppUrl = function(absoluteLinkUrl) {
+ if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) {
+ return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length);
+ }
+ }
+ }
+
+ LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype);
+
+ function locationGetter(property) {
+ return function() {
+ return this[property];
+ };
+ }
+
+
+ function locationGetterSetter(property, preprocess) {
+ return function(value) {
+ if (isUndefined(value))
+ return this[property];
+
+ this[property] = preprocess(value);
+ this.$$compose();
+
+ return this;
+ };
+ }
+
+
+ /**
+ * @ngdoc object
+ * @name ng.$location
+ *
+ * @requires $browser
+ * @requires $sniffer
+ * @requires $rootElement
+ *
+ * @description
+ * The $location service parses the URL in the browser address bar (based on the
+ * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL
+ * available to your application. Changes to the URL in the address bar are reflected into
+ * $location service and changes to $location are reflected into the browser address bar.
+ *
+ * **The $location service:**
+ *
+ * - Exposes the current URL in the browser address bar, so you can
+ * - Watch and observe the URL.
+ * - Change the URL.
+ * - Synchronizes the URL with the browser when the user
+ * - Changes the address bar.
+ * - Clicks the back or forward button (or clicks a History link).
+ * - Clicks on a link.
+ * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
+ *
+ * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular
+ * Services: Using $location}
+ */
+
+ /**
+ * @ngdoc object
+ * @name ng.$locationProvider
+ * @description
+ * Use the `$locationProvider` to configure how the application deep linking paths are stored.
+ */
+ function $LocationProvider(){
+ var hashPrefix = '',
+ html5Mode = false;
+
+ /**
+ * @ngdoc property
+ * @name ng.$locationProvider#hashPrefix
+ * @methodOf ng.$locationProvider
+ * @description
+ * @param {string=} prefix Prefix for hash part (containing path and search)
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.hashPrefix = function(prefix) {
+ if (isDefined(prefix)) {
+ hashPrefix = prefix;
+ return this;
+ } else {
+ return hashPrefix;
+ }
+ };
+
+ /**
+ * @ngdoc property
+ * @name ng.$locationProvider#html5Mode
+ * @methodOf ng.$locationProvider
+ * @description
+ * @param {string=} mode Use HTML5 strategy if available.
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.html5Mode = function(mode) {
+ if (isDefined(mode)) {
+ html5Mode = mode;
+ return this;
+ } else {
+ return html5Mode;
+ }
+ };
+
+ this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement',
+ function( $rootScope, $browser, $sniffer, $rootElement) {
+ var $location,
+ basePath,
+ pathPrefix,
+ initUrl = $browser.url(),
+ initUrlParts = matchUrl(initUrl),
+ appBaseUrl;
+
+ if (html5Mode) {
+ basePath = $browser.baseHref() || '/';
+ pathPrefix = pathPrefixFromBase(basePath);
+ appBaseUrl =
+ composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
+ pathPrefix + '/';
+
+ if ($sniffer.history) {
+ $location = new LocationUrl(
+ convertToHtml5Url(initUrl, basePath, hashPrefix),
+ pathPrefix, appBaseUrl);
+ } else {
+ $location = new LocationHashbangInHtml5Url(
+ convertToHashbangUrl(initUrl, basePath, hashPrefix),
+ hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1));
+ }
+ } else {
+ appBaseUrl =
+ composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) +
+ (initUrlParts.path || '') +
+ (initUrlParts.search ? ('?' + initUrlParts.search) : '') +
+ '#' + hashPrefix + '/';
+
+ $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl);
+ }
+
+ $rootElement.bind('click', function(event) {
+ // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
+ // currently we open nice url link and redirect then
+
+ if (event.ctrlKey || event.metaKey || event.which == 2) return;
+
+ var elm = jqLite(event.target);
+
+ // traverse the DOM up to find first A tag
+ while (lowercase(elm[0].nodeName) !== 'a') {
+ // ignore rewriting if no A tag (reached root element, or no parent - removed from document)
+ if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return;
+ }
+
+ var absHref = elm.prop('href'),
+ rewrittenUrl = $location.$$rewriteAppUrl(absHref);
+
+ if (absHref && !elm.attr('target') && rewrittenUrl) {
+ // update location manually
+ $location.$$parse(rewrittenUrl);
+ $rootScope.$apply();
+ event.preventDefault();
+ // hack to work around FF6 bug 684208 when scenario runner clicks on links
+ window.angular['ff-684208-preventDefault'] = true;
+ }
+ });
+
+
+ // rewrite hashbang url <> html5 url
+ if ($location.absUrl() != initUrl) {
+ $browser.url($location.absUrl(), true);
+ }
+
+ // update $location when $browser url changes
+ $browser.onUrlChange(function(newUrl) {
+ if ($location.absUrl() != newUrl) {
+ $rootScope.$evalAsync(function() {
+ var oldUrl = $location.absUrl();
+
+ $location.$$parse(newUrl);
+ afterLocationChange(oldUrl);
+ });
+ if (!$rootScope.$$phase) $rootScope.$digest();
+ }
+ });
+
+ // update browser
+ var changeCounter = 0;
+ $rootScope.$watch(function $locationWatch() {
+ var oldUrl = $browser.url();
+ var currentReplace = $location.$$replace;
+
+ if (!changeCounter || oldUrl != $location.absUrl()) {
+ changeCounter++;
+ $rootScope.$evalAsync(function() {
+ if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl).
+ defaultPrevented) {
+ $location.$$parse(oldUrl);
+ } else {
+ $browser.url($location.absUrl(), currentReplace);
+ afterLocationChange(oldUrl);
+ }
+ });
+ }
+ $location.$$replace = false;
+
+ return changeCounter;
+ });
+
+ return $location;
+
+ function afterLocationChange(oldUrl) {
+ $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl);
+ }
+ }];
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$log
+ * @requires $window
+ *
+ * @description
+ * Simple service for logging. Default implementation writes the message
+ * into the browser's console (if present).
+ *
+ * The main purpose of this service is to simplify debugging and troubleshooting.
+ *
+ * @example
+
+
+ function LogCtrl($scope, $log) {
+ $scope.$log = $log;
+ $scope.message = 'Hello World!';
+ }
+
+
+
+
Reload this page with open console, enter text and hit the log button...
+ Message:
+
+
log
+
warn
+
info
+
error
+
+
+
+ */
+
+ /**
+ * @ngdoc object
+ * @name ng.$logProvider
+ * @description
+ * Use the `$logProvider` to configure how the application logs messages
+ */
+ function $LogProvider(){
+ var debug = true,
+ self = this;
+
+ /**
+ * @ngdoc property
+ * @name ng.$logProvider#debugEnabled
+ * @methodOf ng.$logProvider
+ * @description
+ * @param {string=} flag enable or disable debug level messages
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ */
+ this.debugEnabled = function(flag) {
+ if (isDefined(flag)) {
+ debug = flag;
+ return this;
+ } else {
+ return debug;
+ }
+ };
+
+ this.$get = ['$window', function($window){
+ return {
+ /**
+ * @ngdoc method
+ * @name ng.$log#log
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write a log message
+ */
+ log: consoleLog('log'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#warn
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write a warning message
+ */
+ warn: consoleLog('warn'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#info
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write an information message
+ */
+ info: consoleLog('info'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#error
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write an error message
+ */
+ error: consoleLog('error'),
+
+ /**
+ * @ngdoc method
+ * @name ng.$log#debug
+ * @methodOf ng.$log
+ *
+ * @description
+ * Write a debug message
+ */
+ debug: (function () {
+ var fn = consoleLog('debug');
+
+ return function() {
+ if (debug) {
+ fn.apply(self, arguments);
+ }
+ }
+ }())
+ };
+
+ function formatError(arg) {
+ if (arg instanceof Error) {
+ if (arg.stack) {
+ arg = (arg.message && arg.stack.indexOf(arg.message) === -1)
+ ? 'Error: ' + arg.message + '\n' + arg.stack
+ : arg.stack;
+ } else if (arg.sourceURL) {
+ arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
+ }
+ }
+ return arg;
+ }
+
+ function consoleLog(type) {
+ var console = $window.console || {},
+ logFn = console[type] || console.log || noop;
+
+ if (logFn.apply) {
+ return function() {
+ var args = [];
+ forEach(arguments, function(arg) {
+ args.push(formatError(arg));
+ });
+ return logFn.apply(console, args);
+ };
+ }
+
+ // we are IE which either doesn't have window.console => this is noop and we do nothing,
+ // or we are IE where console.log doesn't have apply so we log at least first 2 args
+ return function(arg1, arg2) {
+ logFn(arg1, arg2);
+ }
+ }
+ }];
+ }
+
+ var OPERATORS = {
+ 'null':function(){return null;},
+ 'true':function(){return true;},
+ 'false':function(){return false;},
+ undefined:noop,
+ '+':function(self, locals, a,b){
+ a=a(self, locals); b=b(self, locals);
+ if (isDefined(a)) {
+ if (isDefined(b)) {
+ return a + b;
+ }
+ return a;
+ }
+ return isDefined(b)?b:undefined;},
+ '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
+ '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
+ '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
+ '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
+ '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
+ '=':noop,
+ '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);},
+ '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);},
+ '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
+ '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
+ '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
+ '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
+ '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
+ '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
+ '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
+ '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
+// '|':function(self, locals, a,b){return a|b;},
+ '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
+ '!':function(self, locals, a){return !a(self, locals);}
+ };
+ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
+
+ function lex(text, csp){
+ var tokens = [],
+ token,
+ index = 0,
+ json = [],
+ ch,
+ lastCh = ':'; // can start regexp
+
+ while (index < text.length) {
+ ch = text.charAt(index);
+ if (is('"\'')) {
+ readString(ch);
+ } else if (isNumber(ch) || is('.') && isNumber(peek())) {
+ readNumber();
+ } else if (isIdent(ch)) {
+ readIdent();
+ // identifiers can only be if the preceding char was a { or ,
+ if (was('{,') && json[0]=='{' &&
+ (token=tokens[tokens.length-1])) {
+ token.json = token.text.indexOf('.') == -1;
+ }
+ } else if (is('(){}[].,;:')) {
+ tokens.push({
+ index:index,
+ text:ch,
+ json:(was(':[,') && is('{[')) || is('}]:,')
+ });
+ if (is('{[')) json.unshift(ch);
+ if (is('}]')) json.shift();
+ index++;
+ } else if (isWhitespace(ch)) {
+ index++;
+ continue;
+ } else {
+ var ch2 = ch + peek(),
+ ch3 = ch2 + peek(2),
+ fn = OPERATORS[ch],
+ fn2 = OPERATORS[ch2],
+ fn3 = OPERATORS[ch3];
+ if (fn3) {
+ tokens.push({index:index, text:ch3, fn:fn3});
+ index += 3;
+ } else if (fn2) {
+ tokens.push({index:index, text:ch2, fn:fn2});
+ index += 2;
+ } else if (fn) {
+ tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')});
+ index += 1;
+ } else {
+ throwError("Unexpected next character ", index, index+1);
+ }
+ }
+ lastCh = ch;
+ }
+ return tokens;
+
+ function is(chars) {
+ return chars.indexOf(ch) != -1;
+ }
+
+ function was(chars) {
+ return chars.indexOf(lastCh) != -1;
+ }
+
+ function peek(i) {
+ var num = i || 1;
+ return index + num < text.length ? text.charAt(index + num) : false;
+ }
+ function isNumber(ch) {
+ return '0' <= ch && ch <= '9';
+ }
+ function isWhitespace(ch) {
+ return ch == ' ' || ch == '\r' || ch == '\t' ||
+ ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0
+ }
+ function isIdent(ch) {
+ return 'a' <= ch && ch <= 'z' ||
+ 'A' <= ch && ch <= 'Z' ||
+ '_' == ch || ch == '$';
+ }
+ function isExpOperator(ch) {
+ return ch == '-' || ch == '+' || isNumber(ch);
+ }
+
+ function throwError(error, start, end) {
+ end = end || index;
+ throw Error("Lexer Error: " + error + " at column" +
+ (isDefined(start)
+ ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]"
+ : " " + end) +
+ " in expression [" + text + "].");
+ }
+
+ function readNumber() {
+ var number = "";
+ var start = index;
+ while (index < text.length) {
+ var ch = lowercase(text.charAt(index));
+ if (ch == '.' || isNumber(ch)) {
+ number += ch;
+ } else {
+ var peekCh = peek();
+ if (ch == 'e' && isExpOperator(peekCh)) {
+ number += ch;
+ } else if (isExpOperator(ch) &&
+ peekCh && isNumber(peekCh) &&
+ number.charAt(number.length - 1) == 'e') {
+ number += ch;
+ } else if (isExpOperator(ch) &&
+ (!peekCh || !isNumber(peekCh)) &&
+ number.charAt(number.length - 1) == 'e') {
+ throwError('Invalid exponent');
+ } else {
+ break;
+ }
+ }
+ index++;
+ }
+ number = 1 * number;
+ tokens.push({index:start, text:number, json:true,
+ fn:function() {return number;}});
+ }
+ function readIdent() {
+ var ident = "",
+ start = index,
+ lastDot, peekIndex, methodName;
+
+ while (index < text.length) {
+ var ch = text.charAt(index);
+ if (ch == '.' || isIdent(ch) || isNumber(ch)) {
+ if (ch == '.') lastDot = index;
+ ident += ch;
+ } else {
+ break;
+ }
+ index++;
+ }
+
+ //check if this is not a method invocation and if it is back out to last dot
+ if (lastDot) {
+ peekIndex = index;
+ while(peekIndex < text.length) {
+ var ch = text.charAt(peekIndex);
+ if (ch == '(') {
+ methodName = ident.substr(lastDot - start + 1);
+ ident = ident.substr(0, lastDot - start);
+ index = peekIndex;
+ break;
+ }
+ if(isWhitespace(ch)) {
+ peekIndex++;
+ } else {
+ break;
+ }
+ }
+ }
+
+
+ var token = {
+ index:start,
+ text:ident
+ };
+
+ if (OPERATORS.hasOwnProperty(ident)) {
+ token.fn = token.json = OPERATORS[ident];
+ } else {
+ var getter = getterFn(ident, csp);
+ token.fn = extend(function(self, locals) {
+ return (getter(self, locals));
+ }, {
+ assign: function(self, value) {
+ return setter(self, ident, value);
+ }
+ });
+ }
+
+ tokens.push(token);
+
+ if (methodName) {
+ tokens.push({
+ index:lastDot,
+ text: '.',
+ json: false
+ });
+ tokens.push({
+ index: lastDot + 1,
+ text: methodName,
+ json: false
+ });
+ }
+ }
+
+ function readString(quote) {
+ var start = index;
+ index++;
+ var string = "";
+ var rawString = quote;
+ var escape = false;
+ while (index < text.length) {
+ var ch = text.charAt(index);
+ rawString += ch;
+ if (escape) {
+ if (ch == 'u') {
+ var hex = text.substring(index + 1, index + 5);
+ if (!hex.match(/[\da-f]{4}/i))
+ throwError( "Invalid unicode escape [\\u" + hex + "]");
+ index += 4;
+ string += String.fromCharCode(parseInt(hex, 16));
+ } else {
+ var rep = ESCAPE[ch];
+ if (rep) {
+ string += rep;
+ } else {
+ string += ch;
+ }
+ }
+ escape = false;
+ } else if (ch == '\\') {
+ escape = true;
+ } else if (ch == quote) {
+ index++;
+ tokens.push({
+ index:start,
+ text:rawString,
+ string:string,
+ json:true,
+ fn:function() { return string; }
+ });
+ return;
+ } else {
+ string += ch;
+ }
+ index++;
+ }
+ throwError("Unterminated quote", start);
+ }
+ }
+
+/////////////////////////////////////////
+
+ function parser(text, json, $filter, csp){
+ var ZERO = valueFn(0),
+ value,
+ tokens = lex(text, csp),
+ assignment = _assignment,
+ functionCall = _functionCall,
+ fieldAccess = _fieldAccess,
+ objectIndex = _objectIndex,
+ filterChain = _filterChain;
+
+ if(json){
+ // The extra level of aliasing is here, just in case the lexer misses something, so that
+ // we prevent any accidental execution in JSON.
+ assignment = logicalOR;
+ functionCall =
+ fieldAccess =
+ objectIndex =
+ filterChain =
+ function() { throwError("is not valid json", {text:text, index:0}); };
+ value = primary();
+ } else {
+ value = statements();
+ }
+ if (tokens.length !== 0) {
+ throwError("is an unexpected token", tokens[0]);
+ }
+ return value;
+
+ ///////////////////////////////////
+ function throwError(msg, token) {
+ throw Error("Syntax Error: Token '" + token.text +
+ "' " + msg + " at column " +
+ (token.index + 1) + " of the expression [" +
+ text + "] starting at [" + text.substring(token.index) + "].");
+ }
+
+ function peekToken() {
+ if (tokens.length === 0)
+ throw Error("Unexpected end of expression: " + text);
+ return tokens[0];
+ }
+
+ function peek(e1, e2, e3, e4) {
+ if (tokens.length > 0) {
+ var token = tokens[0];
+ var t = token.text;
+ if (t==e1 || t==e2 || t==e3 || t==e4 ||
+ (!e1 && !e2 && !e3 && !e4)) {
+ return token;
+ }
+ }
+ return false;
+ }
+
+ function expect(e1, e2, e3, e4){
+ var token = peek(e1, e2, e3, e4);
+ if (token) {
+ if (json && !token.json) {
+ throwError("is not valid json", token);
+ }
+ tokens.shift();
+ return token;
+ }
+ return false;
+ }
+
+ function consume(e1){
+ if (!expect(e1)) {
+ throwError("is unexpected, expecting [" + e1 + "]", peek());
+ }
+ }
+
+ function unaryFn(fn, right) {
+ return function(self, locals) {
+ return fn(self, locals, right);
+ };
+ }
+
+ function binaryFn(left, fn, right) {
+ return function(self, locals) {
+ return fn(self, locals, left, right);
+ };
+ }
+
+ function statements() {
+ var statements = [];
+ while(true) {
+ if (tokens.length > 0 && !peek('}', ')', ';', ']'))
+ statements.push(filterChain());
+ if (!expect(';')) {
+ // optimize for the common case where there is only one statement.
+ // TODO(size): maybe we should not support multiple statements?
+ return statements.length == 1
+ ? statements[0]
+ : function(self, locals){
+ var value;
+ for ( var i = 0; i < statements.length; i++) {
+ var statement = statements[i];
+ if (statement)
+ value = statement(self, locals);
+ }
+ return value;
+ };
+ }
+ }
+ }
+
+ function _filterChain() {
+ var left = expression();
+ var token;
+ while(true) {
+ if ((token = expect('|'))) {
+ left = binaryFn(left, token.fn, filter());
+ } else {
+ return left;
+ }
+ }
+ }
+
+ function filter() {
+ var token = expect();
+ var fn = $filter(token.text);
+ var argsFn = [];
+ while(true) {
+ if ((token = expect(':'))) {
+ argsFn.push(expression());
+ } else {
+ var fnInvoke = function(self, locals, input){
+ var args = [input];
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self, locals));
+ }
+ return fn.apply(self, args);
+ };
+ return function() {
+ return fnInvoke;
+ };
+ }
+ }
+ }
+
+ function expression() {
+ return assignment();
+ }
+
+ function _assignment() {
+ var left = logicalOR();
+ var right;
+ var token;
+ if ((token = expect('='))) {
+ if (!left.assign) {
+ throwError("implies assignment but [" +
+ text.substring(0, token.index) + "] can not be assigned to", token);
+ }
+ right = logicalOR();
+ return function(self, locals){
+ return left.assign(self, right(self, locals), locals);
+ };
+ } else {
+ return left;
+ }
+ }
+
+ function logicalOR() {
+ var left = logicalAND();
+ var token;
+ while(true) {
+ if ((token = expect('||'))) {
+ left = binaryFn(left, token.fn, logicalAND());
+ } else {
+ return left;
+ }
+ }
+ }
+
+ function logicalAND() {
+ var left = equality();
+ var token;
+ if ((token = expect('&&'))) {
+ left = binaryFn(left, token.fn, logicalAND());
+ }
+ return left;
+ }
+
+ function equality() {
+ var left = relational();
+ var token;
+ if ((token = expect('==','!=','===','!=='))) {
+ left = binaryFn(left, token.fn, equality());
+ }
+ return left;
+ }
+
+ function relational() {
+ var left = additive();
+ var token;
+ if ((token = expect('<', '>', '<=', '>='))) {
+ left = binaryFn(left, token.fn, relational());
+ }
+ return left;
+ }
+
+ function additive() {
+ var left = multiplicative();
+ var token;
+ while ((token = expect('+','-'))) {
+ left = binaryFn(left, token.fn, multiplicative());
+ }
+ return left;
+ }
+
+ function multiplicative() {
+ var left = unary();
+ var token;
+ while ((token = expect('*','/','%'))) {
+ left = binaryFn(left, token.fn, unary());
+ }
+ return left;
+ }
+
+ function unary() {
+ var token;
+ if (expect('+')) {
+ return primary();
+ } else if ((token = expect('-'))) {
+ return binaryFn(ZERO, token.fn, unary());
+ } else if ((token = expect('!'))) {
+ return unaryFn(token.fn, unary());
+ } else {
+ return primary();
+ }
+ }
+
+
+ function primary() {
+ var primary;
+ if (expect('(')) {
+ primary = filterChain();
+ consume(')');
+ } else if (expect('[')) {
+ primary = arrayDeclaration();
+ } else if (expect('{')) {
+ primary = object();
+ } else {
+ var token = expect();
+ primary = token.fn;
+ if (!primary) {
+ throwError("not a primary expression", token);
+ }
+ }
+
+ var next, context;
+ while ((next = expect('(', '[', '.'))) {
+ if (next.text === '(') {
+ primary = functionCall(primary, context);
+ context = null;
+ } else if (next.text === '[') {
+ context = primary;
+ primary = objectIndex(primary);
+ } else if (next.text === '.') {
+ context = primary;
+ primary = fieldAccess(primary);
+ } else {
+ throwError("IMPOSSIBLE");
+ }
+ }
+ return primary;
+ }
+
+ function _fieldAccess(object) {
+ var field = expect().text;
+ var getter = getterFn(field, csp);
+ return extend(
+ function(self, locals) {
+ return getter(object(self, locals), locals);
+ },
+ {
+ assign:function(self, value, locals) {
+ return setter(object(self, locals), field, value);
+ }
+ }
+ );
+ }
+
+ function _objectIndex(obj) {
+ var indexFn = expression();
+ consume(']');
+ return extend(
+ function(self, locals){
+ var o = obj(self, locals),
+ i = indexFn(self, locals),
+ v, p;
+
+ if (!o) return undefined;
+ v = o[i];
+ if (v && v.then) {
+ p = v;
+ if (!('$$v' in v)) {
+ p.$$v = undefined;
+ p.then(function(val) { p.$$v = val; });
+ }
+ v = v.$$v;
+ }
+ return v;
+ }, {
+ assign:function(self, value, locals){
+ return obj(self, locals)[indexFn(self, locals)] = value;
+ }
+ });
+ }
+
+ function _functionCall(fn, contextGetter) {
+ var argsFn = [];
+ if (peekToken().text != ')') {
+ do {
+ argsFn.push(expression());
+ } while (expect(','));
+ }
+ consume(')');
+ return function(self, locals){
+ var args = [],
+ context = contextGetter ? contextGetter(self, locals) : self;
+
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self, locals));
+ }
+ var fnPtr = fn(self, locals) || noop;
+ // IE stupidity!
+ return fnPtr.apply
+ ? fnPtr.apply(context, args)
+ : fnPtr(args[0], args[1], args[2], args[3], args[4]);
+ };
+ }
+
+ // This is used with json array declaration
+ function arrayDeclaration () {
+ var elementFns = [];
+ if (peekToken().text != ']') {
+ do {
+ elementFns.push(expression());
+ } while (expect(','));
+ }
+ consume(']');
+ return function(self, locals){
+ var array = [];
+ for ( var i = 0; i < elementFns.length; i++) {
+ array.push(elementFns[i](self, locals));
+ }
+ return array;
+ };
+ }
+
+ function object () {
+ var keyValues = [];
+ if (peekToken().text != '}') {
+ do {
+ var token = expect(),
+ key = token.string || token.text;
+ consume(":");
+ var value = expression();
+ keyValues.push({key:key, value:value});
+ } while (expect(','));
+ }
+ consume('}');
+ return function(self, locals){
+ var object = {};
+ for ( var i = 0; i < keyValues.length; i++) {
+ var keyValue = keyValues[i];
+ var value = keyValue.value(self, locals);
+ object[keyValue.key] = value;
+ }
+ return object;
+ };
+ }
+ }
+
+//////////////////////////////////////////////////
+// Parser helper functions
+//////////////////////////////////////////////////
+
+ function setter(obj, path, setValue) {
+ var element = path.split('.');
+ for (var i = 0; element.length > 1; i++) {
+ var key = element.shift();
+ var propertyObj = obj[key];
+ if (!propertyObj) {
+ propertyObj = {};
+ obj[key] = propertyObj;
+ }
+ obj = propertyObj;
+ }
+ obj[element.shift()] = setValue;
+ return setValue;
+ }
+
+ /**
+ * Return the value accesible from the object by path. Any undefined traversals are ignored
+ * @param {Object} obj starting object
+ * @param {string} path path to traverse
+ * @param {boolean=true} bindFnToScope
+ * @returns value as accesbile by path
+ */
+//TODO(misko): this function needs to be removed
+ function getter(obj, path, bindFnToScope) {
+ if (!path) return obj;
+ var keys = path.split('.');
+ var key;
+ var lastInstance = obj;
+ var len = keys.length;
+
+ for (var i = 0; i < len; i++) {
+ key = keys[i];
+ if (obj) {
+ obj = (lastInstance = obj)[key];
+ }
+ }
+ if (!bindFnToScope && isFunction(obj)) {
+ return bind(lastInstance, obj);
+ }
+ return obj;
+ }
+
+ var getterFnCache = {};
+
+ /**
+ * Implementation of the "Black Hole" variant from:
+ * - http://jsperf.com/angularjs-parse-getter/4
+ * - http://jsperf.com/path-evaluation-simplified/7
+ */
+ function cspSafeGetterFn(key0, key1, key2, key3, key4) {
+ return function(scope, locals) {
+ var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope,
+ promise;
+
+ if (pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key0];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key1 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key1];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key2 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key2];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key3 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key3];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ if (!key4 || pathVal === null || pathVal === undefined) return pathVal;
+
+ pathVal = pathVal[key4];
+ if (pathVal && pathVal.then) {
+ if (!("$$v" in pathVal)) {
+ promise = pathVal;
+ promise.$$v = undefined;
+ promise.then(function(val) { promise.$$v = val; });
+ }
+ pathVal = pathVal.$$v;
+ }
+ return pathVal;
+ };
+ };
+
+ function getterFn(path, csp) {
+ if (getterFnCache.hasOwnProperty(path)) {
+ return getterFnCache[path];
+ }
+
+ var pathKeys = path.split('.'),
+ pathKeysLength = pathKeys.length,
+ fn;
+
+ if (csp) {
+ fn = (pathKeysLength < 6)
+ ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4])
+ : function(scope, locals) {
+ var i = 0, val
+ do {
+ val = cspSafeGetterFn(
+ pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++]
+ )(scope, locals);
+
+ locals = undefined; // clear after first iteration
+ scope = val;
+ } while (i < pathKeysLength);
+ return val;
+ }
+ } else {
+ var code = 'var l, fn, p;\n';
+ forEach(pathKeys, function(key, index) {
+ code += 'if(s === null || s === undefined) return s;\n' +
+ 'l=s;\n' +
+ 's='+ (index
+ // we simply dereference 's' on any .dot notation
+ ? 's'
+ // but if we are first then we check locals first, and if so read it first
+ : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
+ 'if (s && s.then) {\n' +
+ ' if (!("$$v" in s)) {\n' +
+ ' p=s;\n' +
+ ' p.$$v = undefined;\n' +
+ ' p.then(function(v) {p.$$v=v;});\n' +
+ '}\n' +
+ ' s=s.$$v\n' +
+ '}\n';
+ });
+ code += 'return s;';
+ fn = Function('s', 'k', code); // s=scope, k=locals
+ fn.toString = function() { return code; };
+ }
+
+ return getterFnCache[path] = fn;
+ }
+
+///////////////////////////////////
+
+ /**
+ * @ngdoc function
+ * @name ng.$parse
+ * @function
+ *
+ * @description
+ *
+ * Converts Angular {@link guide/expression expression} into a function.
+ *
+ *
+ * var getter = $parse('user.name');
+ * var setter = getter.assign;
+ * var context = {user:{name:'angular'}};
+ * var locals = {user:{name:'local'}};
+ *
+ * expect(getter(context)).toEqual('angular');
+ * setter(context, 'newValue');
+ * expect(context.user.name).toEqual('newValue');
+ * expect(getter(context, locals)).toEqual('local');
+ *
+ *
+ *
+ * @param {string} expression String expression to compile.
+ * @returns {function(context, locals)} a function which represents the compiled expression:
+ *
+ * * `context`: an object against which any expressions embedded in the strings are evaluated
+ * against (Topically a scope object).
+ * * `locals`: local variables context object, useful for overriding values in `context`.
+ *
+ * The return function also has an `assign` property, if the expression is assignable, which
+ * allows one to set values to expressions.
+ *
+ */
+ function $ParseProvider() {
+ var cache = {};
+ this.$get = ['$filter', '$sniffer', function($filter, $sniffer) {
+ return function(exp) {
+ switch(typeof exp) {
+ case 'string':
+ return cache.hasOwnProperty(exp)
+ ? cache[exp]
+ : cache[exp] = parser(exp, false, $filter, $sniffer.csp);
+ case 'function':
+ return exp;
+ default:
+ return noop;
+ }
+ };
+ }];
+ }
+
+ /**
+ * @ngdoc service
+ * @name ng.$q
+ * @requires $rootScope
+ *
+ * @description
+ * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q).
+ *
+ * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an
+ * interface for interacting with an object that represents the result of an action that is
+ * performed asynchronously, and may or may not be finished at any given point in time.
+ *
+ * From the perspective of dealing with error handling, deferred and promise apis are to
+ * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming.
+ *
+ *
+ * // for the purpose of this example let's assume that variables `$q` and `scope` are
+ * // available in the current lexical scope (they could have been injected or passed in).
+ *
+ * function asyncGreet(name) {
+ * var deferred = $q.defer();
+ *
+ * setTimeout(function() {
+ * // since this fn executes async in a future turn of the event loop, we need to wrap
+ * // our code into an $apply call so that the model changes are properly observed.
+ * scope.$apply(function() {
+ * if (okToGreet(name)) {
+ * deferred.resolve('Hello, ' + name + '!');
+ * } else {
+ * deferred.reject('Greeting ' + name + ' is not allowed.');
+ * }
+ * });
+ * }, 1000);
+ *
+ * return deferred.promise;
+ * }
+ *
+ * var promise = asyncGreet('Robin Hood');
+ * promise.then(function(greeting) {
+ * alert('Success: ' + greeting);
+ * }, function(reason) {
+ * alert('Failed: ' + reason);
+ * });
+ *
+ *
+ * At first it might not be obvious why this extra complexity is worth the trouble. The payoff
+ * comes in the way of
+ * [guarantees that promise and deferred apis make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md).
+ *
+ * Additionally the promise api allows for composition that is very hard to do with the
+ * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach.
+ * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the
+ * section on serial or parallel joining of promises.
+ *
+ *
+ * # The Deferred API
+ *
+ * A new instance of deferred is constructed by calling `$q.defer()`.
+ *
+ * The purpose of the deferred object is to expose the associated Promise instance as well as apis
+ * that can be used for signaling the successful or unsuccessful completion of the task.
+ *
+ * **Methods**
+ *
+ * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection
+ * constructed via `$q.reject`, the promise will be rejected instead.
+ * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to
+ * resolving it with a rejection constructed via `$q.reject`.
+ *
+ * **Properties**
+ *
+ * - promise – `{Promise}` – promise object associated with this deferred.
+ *
+ *
+ * # The Promise API
+ *
+ * A new promise instance is created when a deferred instance is created and can be retrieved by
+ * calling `deferred.promise`.
+ *
+ * The purpose of the promise object is to allow for interested parties to get access to the result
+ * of the deferred task when it completes.
+ *
+ * **Methods**
+ *
+ * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved
+ * or rejected calls one of the success or error callbacks asynchronously as soon as the result
+ * is available. The callbacks are called with a single argument the result or rejection reason.
+ *
+ * This method *returns a new promise* which is resolved or rejected via the return value of the
+ * `successCallback` or `errorCallback`.
+ *
+ *
+ * # Chaining promises
+ *
+ * Because calling `then` api of a promise returns a new derived promise, it is easily possible
+ * to create a chain of promises:
+ *
+ *
+ * promiseB = promiseA.then(function(result) {
+ * return result + 1;
+ * });
+ *
+ * // promiseB will be resolved immediately after promiseA is resolved and it's value will be
+ * // the result of promiseA incremented by 1
+ *
+ *
+ * It is possible to create chains of any length and since a promise can be resolved with another
+ * promise (which will defer its resolution further), it is possible to pause/defer resolution of
+ * the promises at any point in the chain. This makes it possible to implement powerful apis like
+ * $http's response interceptors.
+ *
+ *
+ * # Differences between Kris Kowal's Q and $q
+ *
+ * There are three main differences:
+ *
+ * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation
+ * mechanism in angular, which means faster propagation of resolution or rejection into your
+ * models and avoiding unnecessary browser repaints, which would result in flickering UI.
+ * - $q promises are recognized by the templating engine in angular, which means that in templates
+ * you can treat promises attached to a scope as if they were the resulting values.
+ * - Q has many more features that $q, but that comes at a cost of bytes. $q is tiny, but contains
+ * all the important functionality needed for common async tasks.
+ *
+ * # Testing
+ *
+ *
+ * it('should simulate promise', inject(function($q, $rootSCope) {
+ * var deferred = $q.defer();
+ * var promise = deferred.promise;
+ * var resolvedValue;
+ *
+ * promise.then(function(value) { resolvedValue = value; });
+ * expect(resolvedValue).toBeUndefined();
+ *
+ * // Simulate resolving of promise
+ * defered.resolve(123);
+ * // Note that the 'then' function does not get called synchronously.
+ * // This is because we want the promise API to always be async, whether or not
+ * // it got called synchronously or asynchronously.
+ * expect(resolvedValue).toBeUndefined();
+ *
+ * // Propagate promise resolution to 'then' functions using $apply().
+ * $rootScope.$apply();
+ * expect(resolvedValue).toEqual(123);
+ * });
+ *
+ */
+ function $QProvider() {
+
+ this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) {
+ return qFactory(function(callback) {
+ $rootScope.$evalAsync(callback);
+ }, $exceptionHandler);
+ }];
+ }
+
+
+ /**
+ * Constructs a promise manager.
+ *
+ * @param {function(function)} nextTick Function for executing functions in the next turn.
+ * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for
+ * debugging purposes.
+ * @returns {object} Promise manager.
+ */
+ function qFactory(nextTick, exceptionHandler) {
+
+ /**
+ * @ngdoc
+ * @name ng.$q#defer
+ * @methodOf ng.$q
+ * @description
+ * Creates a `Deferred` object which represents a task which will finish in the future.
+ *
+ * @returns {Deferred} Returns a new instance of deferred.
+ */
+ var defer = function() {
+ var pending = [],
+ value, deferred;
+
+ deferred = {
+
+ resolve: function(val) {
+ if (pending) {
+ var callbacks = pending;
+ pending = undefined;
+ value = ref(val);
+
+ if (callbacks.length) {
+ nextTick(function() {
+ var callback;
+ for (var i = 0, ii = callbacks.length; i < ii; i++) {
+ callback = callbacks[i];
+ value.then(callback[0], callback[1]);
+ }
+ });
+ }
+ }
+ },
+
+
+ reject: function(reason) {
+ deferred.resolve(reject(reason));
+ },
+
+
+ promise: {
+ then: function(callback, errback) {
+ var result = defer();
+
+ var wrappedCallback = function(value) {
+ try {
+ result.resolve((callback || defaultCallback)(value));
+ } catch(e) {
+ exceptionHandler(e);
+ result.reject(e);
+ }
+ };
+
+ var wrappedErrback = function(reason) {
+ try {
+ result.resolve((errback || defaultErrback)(reason));
+ } catch(e) {
+ exceptionHandler(e);
+ result.reject(e);
+ }
+ };
+
+ if (pending) {
+ pending.push([wrappedCallback, wrappedErrback]);
+ } else {
+ value.then(wrappedCallback, wrappedErrback);
+ }
+
+ return result.promise;
+ }
+ }
+ };
+
+ return deferred;
+ };
+
+
+ var ref = function(value) {
+ if (value && value.then) return value;
+ return {
+ then: function(callback) {
+ var result = defer();
+ nextTick(function() {
+ result.resolve(callback(value));
+ });
+ return result.promise;
+ }
+ };
+ };
+
+
+ /**
+ * @ngdoc
+ * @name ng.$q#reject
+ * @methodOf ng.$q
+ * @description
+ * Creates a promise that is resolved as rejected with the specified `reason`. This api should be
+ * used to forward rejection in a chain of promises. If you are dealing with the last promise in
+ * a promise chain, you don't need to worry about it.
+ *
+ * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of
+ * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via
+ * a promise error callback and you want to forward the error to the promise derived from the
+ * current promise, you have to "rethrow" the error by returning a rejection constructed via
+ * `reject`.
+ *
+ *
+ * promiseB = promiseA.then(function(result) {
+ * // success: do something and resolve promiseB
+ * // with the old or a new result
+ * return result;
+ * }, function(reason) {
+ * // error: handle the error if possible and
+ * // resolve promiseB with newPromiseOrValue,
+ * // otherwise forward the rejection to promiseB
+ * if (canHandle(reason)) {
+ * // handle the error and recover
+ * return newPromiseOrValue;
+ * }
+ * return $q.reject(reason);
+ * });
+ *
+ *
+ * @param {*} reason Constant, message, exception or an object representing the rejection reason.
+ * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`.
+ */
+ var reject = function(reason) {
+ return {
+ then: function(callback, errback) {
+ var result = defer();
+ nextTick(function() {
+ result.resolve((errback || defaultErrback)(reason));
+ });
+ return result.promise;
+ }
+ };
+ };
+
+
+ /**
+ * @ngdoc
+ * @name ng.$q#when
+ * @methodOf ng.$q
+ * @description
+ * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise.
+ * This is useful when you are dealing with on object that might or might not be a promise, or if
+ * the promise comes from a source that can't be trusted.
+ *
+ * @param {*} value Value or a promise
+ * @returns {Promise} Returns a single promise that will be resolved with an array of values,
+ * each value coresponding to the promise at the same index in the `promises` array. If any of
+ * the promises is resolved with a rejection, this resulting promise will be resolved with the
+ * same rejection.
+ */
+ var when = function(value, callback, errback) {
+ var result = defer(),
+ done;
+
+ var wrappedCallback = function(value) {
+ try {
+ return (callback || defaultCallback)(value);
+ } catch (e) {
+ exceptionHandler(e);
+ return reject(e);
+ }
+ };
+
+ var wrappedErrback = function(reason) {
+ try {
+ return (errback || defaultErrback)(reason);
+ } catch (e) {
+ exceptionHandler(e);
+ return reject(e);
+ }
+ };
+
+ nextTick(function() {
+ ref(value).then(function(value) {
+ if (done) return;
+ done = true;
+ result.resolve(ref(value).then(wrappedCallback, wrappedErrback));
+ }, function(reason) {
+ if (done) return;
+ done = true;
+ result.resolve(wrappedErrback(reason));
+ });
+ });
+
+ return result.promise;
+ };
+
+
+ function defaultCallback(value) {
+ return value;
+ }
+
+
+ function defaultErrback(reason) {
+ return reject(reason);
+ }
+
+
+ /**
+ * @ngdoc
+ * @name ng.$q#all
+ * @methodOf ng.$q
+ * @description
+ * Combines multiple promises into a single promise that is resolved when all of the input
+ * promises are resolved.
+ *
+ * @param {Array.} promises An array of promises.
+ * @returns {Promise} Returns a single promise that will be resolved with an array of values,
+ * each value coresponding to the promise at the same index in the `promises` array. If any of
+ * the promises is resolved with a rejection, this resulting promise will be resolved with the
+ * same rejection.
+ */
+ function all(promises) {
+ var deferred = defer(),
+ counter = promises.length,
+ results = [];
+
+ if (counter) {
+ forEach(promises, function(promise, index) {
+ ref(promise).then(function(value) {
+ if (index in results) return;
+ results[index] = value;
+ if (!(--counter)) deferred.resolve(results);
+ }, function(reason) {
+ if (index in results) return;
+ deferred.reject(reason);
+ });
+ });
+ } else {
+ deferred.resolve(results);
+ }
+
+ return deferred.promise;
+ }
+
+ return {
+ defer: defer,
+ reject: reject,
+ when: when,
+ all: all
+ };
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$routeProvider
+ * @function
+ *
+ * @description
+ *
+ * Used for configuring routes. See {@link ng.$route $route} for an example.
+ */
+ function $RouteProvider(){
+ var routes = {};
+
+ /**
+ * @ngdoc method
+ * @name ng.$routeProvider#when
+ * @methodOf ng.$routeProvider
+ *
+ * @param {string} path Route path (matched against `$location.path`). If `$location.path`
+ * contains redundant trailing slash or is missing one, the route will still match and the
+ * `$location.path` will be updated to add or drop the trailing slash to exactly match the
+ * route definition.
+ *
+ * `path` can contain named groups starting with a colon (`:name`). All characters up to the
+ * next slash are matched and stored in `$routeParams` under the given `name` when the route
+ * matches.
+ *
+ * @param {Object} route Mapping information to be assigned to `$route.current` on route
+ * match.
+ *
+ * Object properties:
+ *
+ * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly
+ * created scope or the name of a {@link angular.Module#controller registered controller}
+ * if passed as a string.
+ * - `template` – `{string=|function()=}` – html template as a string or function that returns
+ * an html template as a string which should be used by {@link ng.directive:ngView ngView} or
+ * {@link ng.directive:ngInclude ngInclude} directives.
+ * This property takes precedence over `templateUrl`.
+ *
+ * If `template` is a function, it will be called with the following parameters:
+ *
+ * - `{Array.}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route
+ *
+ * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
+ * template that should be used by {@link ng.directive:ngView ngView}.
+ *
+ * If `templateUrl` is a function, it will be called with the following parameters:
+ *
+ * - `{Array.}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route
+ *
+ * - `resolve` - `{Object.=}` - An optional map of dependencies which should
+ * be injected into the controller. If any of these dependencies are promises, they will be
+ * resolved and converted to a value before the controller is instantiated and the
+ * `$routeChangeSuccess` event is fired. The map object is:
+ *
+ * - `key` – `{string}`: a name of a dependency to be injected into the controller.
+ * - `factory` - `{string|function}`: If `string` then it is an alias for a service.
+ * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
+ * and the return value is treated as the dependency. If the result is a promise, it is resolved
+ * before its value is injected into the controller.
+ *
+ * - `redirectTo` – {(string|function())=} – value to update
+ * {@link ng.$location $location} path with and trigger route redirection.
+ *
+ * If `redirectTo` is a function, it will be called with the following parameters:
+ *
+ * - `{Object.}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route templateUrl.
+ * - `{string}` - current `$location.path()`
+ * - `{Object}` - current `$location.search()`
+ *
+ * The custom `redirectTo` function is expected to return a string which will be used
+ * to update `$location.path()` and `$location.search()`.
+ *
+ * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
+ * changes.
+ *
+ * If the option is set to `false` and url in the browser changes, then
+ * `$routeUpdate` event is broadcasted on the root scope.
+ *
+ * @returns {Object} self
+ *
+ * @description
+ * Adds a new route definition to the `$route` service.
+ */
+ this.when = function(path, route) {
+ routes[path] = extend({reloadOnSearch: true}, route);
+
+ // create redirection for trailing slashes
+ if (path) {
+ var redirectPath = (path[path.length-1] == '/')
+ ? path.substr(0, path.length-1)
+ : path +'/';
+
+ routes[redirectPath] = {redirectTo: path};
+ }
+
+ return this;
+ };
+
+ /**
+ * @ngdoc method
+ * @name ng.$routeProvider#otherwise
+ * @methodOf ng.$routeProvider
+ *
+ * @description
+ * Sets route definition that will be used on route change when no other route definition
+ * is matched.
+ *
+ * @param {Object} params Mapping information to be assigned to `$route.current`.
+ * @returns {Object} self
+ */
+ this.otherwise = function(params) {
+ this.when(null, params);
+ return this;
+ };
+
+
+ this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache',
+ function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) {
+
+ /**
+ * @ngdoc object
+ * @name ng.$route
+ * @requires $location
+ * @requires $routeParams
+ *
+ * @property {Object} current Reference to the current route definition.
+ * The route definition contains:
+ *
+ * - `controller`: The controller constructor as define in route definition.
+ * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
+ * controller instantiation. The `locals` contain
+ * the resolved values of the `resolve` map. Additionally the `locals` also contain:
+ *
+ * - `$scope` - The current route scope.
+ * - `$template` - The current route template HTML.
+ *
+ * @property {Array.} routes Array of all configured routes.
+ *
+ * @description
+ * Is used for deep-linking URLs to controllers and views (HTML partials).
+ * It watches `$location.url()` and tries to map the path to an existing route definition.
+ *
+ * You can define routes through {@link ng.$routeProvider $routeProvider}'s API.
+ *
+ * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView}
+ * directive and the {@link ng.$routeParams $routeParams} service.
+ *
+ * @example
+ This example shows how changing the URL hash causes the `$route` to match a route against the
+ URL, and the `ngView` pulls in the partial.
+
+ Note that this example is using {@link ng.directive:script inlined templates}
+ to get it working on jsfiddle as well.
+
+
+
+
+ Choose:
+
Moby |
+
Moby: Ch1 |
+
Gatsby |
+
Gatsby: Ch4 |
+
Scarlet Letter
+
+
+
+
+
$location.path() = {{$location.path()}}
+
$route.current.templateUrl = {{$route.current.templateUrl}}
+
$route.current.params = {{$route.current.params}}
+
$route.current.scope.name = {{$route.current.scope.name}}
+
$routeParams = {{$routeParams}}
+
+
+
+
+ controller: {{name}}
+ Book Id: {{params.bookId}}
+
+
+
+ controller: {{name}}
+ Book Id: {{params.bookId}}
+ Chapter Id: {{params.chapterId}}
+
+
+
+ angular.module('ngView', [], function($routeProvider, $locationProvider) {
+ $routeProvider.when('/Book/:bookId', {
+ templateUrl: 'book.html',
+ controller: BookCntl,
+ resolve: {
+ // I will cause a 1 second delay
+ delay: function($q, $timeout) {
+ var delay = $q.defer();
+ $timeout(delay.resolve, 1000);
+ return delay.promise;
+ }
+ }
+ });
+ $routeProvider.when('/Book/:bookId/ch/:chapterId', {
+ templateUrl: 'chapter.html',
+ controller: ChapterCntl
+ });
+
+ // configure html5 to get links working on jsfiddle
+ $locationProvider.html5Mode(true);
+ });
+
+ function MainCntl($scope, $route, $routeParams, $location) {
+ $scope.$route = $route;
+ $scope.$location = $location;
+ $scope.$routeParams = $routeParams;
+ }
+
+ function BookCntl($scope, $routeParams) {
+ $scope.name = "BookCntl";
+ $scope.params = $routeParams;
+ }
+
+ function ChapterCntl($scope, $routeParams) {
+ $scope.name = "ChapterCntl";
+ $scope.params = $routeParams;
+ }
+
+
+
+ it('should load and compile correct template', function() {
+ element('a:contains("Moby: Ch1")').click();
+ var content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: ChapterCntl/);
+ expect(content).toMatch(/Book Id\: Moby/);
+ expect(content).toMatch(/Chapter Id\: 1/);
+
+ element('a:contains("Scarlet")').click();
+ sleep(2); // promises are not part of scenario waiting
+ content = element('.doc-example-live [ng-view]').text();
+ expect(content).toMatch(/controller\: BookCntl/);
+ expect(content).toMatch(/Book Id\: Scarlet/);
+ });
+
+
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeChangeStart
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted before a route change. At this point the route services starts
+ * resolving all of the dependencies needed for the route change to occurs.
+ * Typically this involves fetching the view template as well as any dependencies
+ * defined in `resolve` route property. Once all of the dependencies are resolved
+ * `$routeChangeSuccess` is fired.
+ *
+ * @param {Route} next Future route information.
+ * @param {Route} current Current route information.
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeChangeSuccess
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted after a route dependencies are resolved.
+ * {@link ng.directive:ngView ngView} listens for the directive
+ * to instantiate the controller and render the view.
+ *
+ * @param {Route} current Current route information.
+ * @param {Route} previous Previous route information.
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeChangeError
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted if any of the resolve promises are rejected.
+ *
+ * @param {Route} current Current route information.
+ * @param {Route} previous Previous route information.
+ * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
+ */
+
+ /**
+ * @ngdoc event
+ * @name ng.$route#$routeUpdate
+ * @eventOf ng.$route
+ * @eventType broadcast on root scope
+ * @description
+ *
+ * The `reloadOnSearch` property has been set to false, and we are reusing the same
+ * instance of the Controller.
+ */
+
+ var forceReload = false,
+ $route = {
+ routes: routes,
+
+ /**
+ * @ngdoc method
+ * @name ng.$route#reload
+ * @methodOf ng.$route
+ *
+ * @description
+ * Causes `$route` service to reload the current route even if
+ * {@link ng.$location $location} hasn't changed.
+ *
+ * As a result of that, {@link ng.directive:ngView ngView}
+ * creates new scope, reinstantiates the controller.
+ */
+ reload: function() {
+ forceReload = true;
+ $rootScope.$evalAsync(updateRoute);
+ }
+ };
+
+ $rootScope.$on('$locationChangeSuccess', updateRoute);
+
+ return $route;
+
+ /////////////////////////////////////////////////////
+
+ /**
+ * @param on {string} current url
+ * @param when {string} route when template to match the url against
+ * @return {?Object}
+ */
+ function switchRouteMatcher(on, when) {
+ // TODO(i): this code is convoluted and inefficient, we should construct the route matching
+ // regex only once and then reuse it
+
+ // Escape regexp special characters.
+ when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$';
+ var regex = '',
+ params = [],
+ dst = {};
+
+ var re = /:(\w+)/g,
+ paramMatch,
+ lastMatchedIndex = 0;
+
+ while ((paramMatch = re.exec(when)) !== null) {
+ // Find each :param in `when` and replace it with a capturing group.
+ // Append all other sections of when unchanged.
+ regex += when.slice(lastMatchedIndex, paramMatch.index);
+ regex += '([^\\/]*)';
+ params.push(paramMatch[1]);
+ lastMatchedIndex = re.lastIndex;
+ }
+ // Append trailing path part.
+ regex += when.substr(lastMatchedIndex);
+
+ var match = on.match(new RegExp(regex));
+ if (match) {
+ forEach(params, function(name, index) {
+ dst[name] = match[index + 1];
+ });
+ }
+ return match ? dst : null;
+ }
+
+ function updateRoute() {
+ var next = parseRoute(),
+ last = $route.current;
+
+ if (next && last && next.$route === last.$route
+ && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
+ last.params = next.params;
+ copy(last.params, $routeParams);
+ $rootScope.$broadcast('$routeUpdate', last);
+ } else if (next || last) {
+ forceReload = false;
+ $rootScope.$broadcast('$routeChangeStart', next, last);
+ $route.current = next;
+ if (next) {
+ if (next.redirectTo) {
+ if (isString(next.redirectTo)) {
+ $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
+ .replace();
+ } else {
+ $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
+ .replace();
+ }
+ }
+ }
+
+ $q.when(next).
+ then(function() {
+ if (next) {
+ var keys = [],
+ values = [],
+ template;
+
+ forEach(next.resolve || {}, function(value, key) {
+ keys.push(key);
+ values.push(isString(value) ? $injector.get(value) : $injector.invoke(value));
+ });
+ if (isDefined(template = next.template)) {
+ if (isFunction(template)) {
+ template = template(next.params);
+ }
+ } else if (isDefined(template = next.templateUrl)) {
+ if (isFunction(template)) {
+ template = template(next.params);
+ }
+ if (isDefined(template)) {
+ next.loadedTemplateUrl = template;
+ template = $http.get(template, {cache: $templateCache}).
+ then(function(response) { return response.data; });
+ }
+ }
+ if (isDefined(template)) {
+ keys.push('$template');
+ values.push(template);
+ }
+ return $q.all(values).then(function(values) {
+ var locals = {};
+ forEach(values, function(value, index) {
+ locals[keys[index]] = value;
+ });
+ return locals;
+ });
+ }
+ }).
+ // after route change
+ then(function(locals) {
+ if (next == $route.current) {
+ if (next) {
+ next.locals = locals;
+ copy(next.params, $routeParams);
+ }
+ $rootScope.$broadcast('$routeChangeSuccess', next, last);
+ }
+ }, function(error) {
+ if (next == $route.current) {
+ $rootScope.$broadcast('$routeChangeError', next, last, error);
+ }
+ });
+ }
+ }
+
+
+ /**
+ * @returns the current active route, by matching it against the URL
+ */
+ function parseRoute() {
+ // Match a route
+ var params, match;
+ forEach(routes, function(route, path) {
+ if (!match && (params = switchRouteMatcher($location.path(), path))) {
+ match = inherit(route, {
+ params: extend({}, $location.search(), params),
+ pathParams: params});
+ match.$route = route;
+ }
+ });
+ // No route matched; fallback to "otherwise" route
+ return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
+ }
+
+ /**
+ * @returns interpolation of the redirect path with the parametrs
+ */
+ function interpolate(string, params) {
+ var result = [];
+ forEach((string||'').split(':'), function(segment, i) {
+ if (i == 0) {
+ result.push(segment);
+ } else {
+ var segmentMatch = segment.match(/(\w+)(.*)/);
+ var key = segmentMatch[1];
+ result.push(params[key]);
+ result.push(segmentMatch[2] || '');
+ delete params[key];
+ }
+ });
+ return result.join('');
+ }
+ }];
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$routeParams
+ * @requires $route
+ *
+ * @description
+ * Current set of route parameters. The route parameters are a combination of the
+ * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters
+ * are extracted when the {@link ng.$route $route} path is matched.
+ *
+ * In case of parameter name collision, `path` params take precedence over `search` params.
+ *
+ * The service guarantees that the identity of the `$routeParams` object will remain unchanged
+ * (but its properties will likely change) even when a route change occurs.
+ *
+ * @example
+ *
+ * // Given:
+ * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
+ * // Route: /Chapter/:chapterId/Section/:sectionId
+ * //
+ * // Then
+ * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
+ *
+ */
+ function $RouteParamsProvider() {
+ this.$get = valueFn({});
+ }
+
+ /**
+ * DESIGN NOTES
+ *
+ * The design decisions behind the scope ware heavily favored for speed and memory consumption.
+ *
+ * The typical use of scope is to watch the expressions, which most of the time return the same
+ * value as last time so we optimize the operation.
+ *
+ * Closures construction is expensive from speed as well as memory:
+ * - no closures, instead ups prototypical inheritance for API
+ * - Internal state needs to be stored on scope directly, which means that private state is
+ * exposed as $$____ properties
+ *
+ * Loop operations are optimized by using while(count--) { ... }
+ * - this means that in order to keep the same order of execution as addition we have to add
+ * items to the array at the begging (shift) instead of at the end (push)
+ *
+ * Child scopes are created and removed often
+ * - Using array would be slow since inserts in meddle are expensive so we use linked list
+ *
+ * There are few watches then a lot of observers. This is why you don't want the observer to be
+ * implemented in the same way as watch. Watch requires return of initialization function which
+ * are expensive to construct.
+ */
+
+
+ /**
+ * @ngdoc object
+ * @name ng.$rootScopeProvider
+ * @description
+ *
+ * Provider for the $rootScope service.
+ */
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScopeProvider#digestTtl
+ * @methodOf ng.$rootScopeProvider
+ * @description
+ *
+ * Sets the number of digest iteration the scope should attempt to execute before giving up and
+ * assuming that the model is unstable.
+ *
+ * The current default is 10 iterations.
+ *
+ * @param {number} limit The number of digest iterations.
+ */
+
+
+ /**
+ * @ngdoc object
+ * @name ng.$rootScope
+ * @description
+ *
+ * Every application has a single root {@link ng.$rootScope.Scope scope}.
+ * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide
+ * event processing life-cycle. See {@link guide/scope developer guide on scopes}.
+ */
+ function $RootScopeProvider(){
+ var TTL = 10;
+
+ this.digestTtl = function(value) {
+ if (arguments.length) {
+ TTL = value;
+ }
+ return TTL;
+ };
+
+ this.$get = ['$injector', '$exceptionHandler', '$parse',
+ function( $injector, $exceptionHandler, $parse) {
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope
+ *
+ * @description
+ * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
+ * {@link AUTO.$injector $injector}. Child scopes are created using the
+ * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
+ * compiled HTML template is executed.)
+ *
+ * Here is a simple scope snippet to show how you can interact with the scope.
+ *
+ angular.injector(['ng']).invoke(function($rootScope) {
+ var scope = $rootScope.$new();
+ scope.salutation = 'Hello';
+ scope.name = 'World';
+
+ expect(scope.greeting).toEqual(undefined);
+
+ scope.$watch('name', function() {
+ scope.greeting = scope.salutation + ' ' + scope.name + '!';
+ }); // initialize the watch
+
+ expect(scope.greeting).toEqual(undefined);
+ scope.name = 'Misko';
+ // still old value, since watches have not been called yet
+ expect(scope.greeting).toEqual(undefined);
+
+ scope.$digest(); // fire all the watches
+ expect(scope.greeting).toEqual('Hello Misko!');
+ });
+ *
+ *
+ * # Inheritance
+ * A scope can inherit from a parent scope, as in this example:
+ *
+ var parent = $rootScope;
+ var child = parent.$new();
+
+ parent.salutation = "Hello";
+ child.name = "World";
+ expect(child.salutation).toEqual('Hello');
+
+ child.salutation = "Welcome";
+ expect(child.salutation).toEqual('Welcome');
+ expect(parent.salutation).toEqual('Hello');
+ *
+ *
+ *
+ * @param {Object.=} providers Map of service factory which need to be provided
+ * for the current scope. Defaults to {@link ng}.
+ * @param {Object.=} instanceCache Provides pre-instantiated services which should
+ * append/override services provided by `providers`. This is handy when unit-testing and having
+ * the need to override a default service.
+ * @returns {Object} Newly created scope.
+ *
+ */
+ function Scope() {
+ this.$id = nextUid();
+ this.$$phase = this.$parent = this.$$watchers =
+ this.$$nextSibling = this.$$prevSibling =
+ this.$$childHead = this.$$childTail = null;
+ this['this'] = this.$root = this;
+ this.$$destroyed = false;
+ this.$$asyncQueue = [];
+ this.$$listeners = {};
+ }
+
+ /**
+ * @ngdoc property
+ * @name ng.$rootScope.Scope#$id
+ * @propertyOf ng.$rootScope.Scope
+ * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
+ * debugging.
+ */
+
+
+ Scope.prototype = {
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$new
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Creates a new child {@link ng.$rootScope.Scope scope}.
+ *
+ * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and
+ * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope
+ * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}.
+ *
+ * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for
+ * the scope and its child scopes to be permanently detached from the parent and thus stop
+ * participating in model change detection and listener notification by invoking.
+ *
+ * @param {boolean} isolate if true then the scope does not prototypically inherit from the
+ * parent scope. The scope is isolated, as it can not see parent scope properties.
+ * When creating widgets it is useful for the widget to not accidentally read parent
+ * state.
+ *
+ * @returns {Object} The newly created child scope.
+ *
+ */
+ $new: function(isolate) {
+ var Child,
+ child;
+
+ if (isFunction(isolate)) {
+ // TODO: remove at some point
+ throw Error('API-CHANGE: Use $controller to instantiate controllers.');
+ }
+ if (isolate) {
+ child = new Scope();
+ child.$root = this.$root;
+ } else {
+ Child = function() {}; // should be anonymous; This is so that when the minifier munges
+ // the name it does not become random set of chars. These will then show up as class
+ // name in the debugger.
+ Child.prototype = this;
+ child = new Child();
+ child.$id = nextUid();
+ }
+ child['this'] = child;
+ child.$$listeners = {};
+ child.$parent = this;
+ child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null;
+ child.$$prevSibling = this.$$childTail;
+ if (this.$$childHead) {
+ this.$$childTail.$$nextSibling = child;
+ this.$$childTail = child;
+ } else {
+ this.$$childHead = this.$$childTail = child;
+ }
+ return child;
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$watch
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
+ *
+ * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and
+ * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()}
+ * reruns when it detects changes the `watchExpression` can execute multiple times per
+ * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.)
+ * - The `listener` is called only when the value from the current `watchExpression` and the
+ * previous call to `watchExpression` are not equal (with the exception of the initial run,
+ * see below). The inequality is determined according to
+ * {@link angular.equals} function. To save the value of the object for later comparison, the
+ * {@link angular.copy} function is used. It also means that watching complex options will
+ * have adverse memory and performance implications.
+ * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
+ * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
+ * limit is 10 to prevent an infinite loop deadlock.
+ *
+ *
+ * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
+ * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
+ * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is
+ * detected, be prepared for multiple calls to your listener.)
+ *
+ * After a watcher is registered with the scope, the `listener` fn is called asynchronously
+ * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
+ * watcher. In rare cases, this is undesirable because the listener is called when the result
+ * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
+ * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the
+ * listener was called due to initialization.
+ *
+ *
+ * # Example
+ *
+ // let's assume that scope was dependency injected as the $rootScope
+ var scope = $rootScope;
+ scope.name = 'misko';
+ scope.counter = 0;
+
+ expect(scope.counter).toEqual(0);
+ scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
+ expect(scope.counter).toEqual(0);
+
+ scope.$digest();
+ // no variable change
+ expect(scope.counter).toEqual(0);
+
+ scope.name = 'adam';
+ scope.$digest();
+ expect(scope.counter).toEqual(1);
+ *
+ *
+ *
+ *
+ * @param {(function()|string)} watchExpression Expression that is evaluated on each
+ * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a
+ * call to the `listener`.
+ *
+ * - `string`: Evaluated as {@link guide/expression expression}
+ * - `function(scope)`: called with current `scope` as a parameter.
+ * @param {(function()|string)=} listener Callback called whenever the return value of
+ * the `watchExpression` changes.
+ *
+ * - `string`: Evaluated as {@link guide/expression expression}
+ * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters.
+ *
+ * @param {boolean=} objectEquality Compare object for equality rather than for reference.
+ * @returns {function()} Returns a deregistration function for this listener.
+ */
+ $watch: function(watchExp, listener, objectEquality) {
+ var scope = this,
+ get = compileToFn(watchExp, 'watch'),
+ array = scope.$$watchers,
+ watcher = {
+ fn: listener,
+ last: initWatchVal,
+ get: get,
+ exp: watchExp,
+ eq: !!objectEquality
+ };
+
+ // in the case user pass string, we need to compile it, do we really need this ?
+ if (!isFunction(listener)) {
+ var listenFn = compileToFn(listener || noop, 'listener');
+ watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
+ }
+
+ if (!array) {
+ array = scope.$$watchers = [];
+ }
+ // we use unshift since we use a while loop in $digest for speed.
+ // the while loop reads in reverse order.
+ array.unshift(watcher);
+
+ return function() {
+ arrayRemove(array, watcher);
+ };
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$digest
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Process all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children.
+ * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the
+ * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are
+ * firing. This means that it is possible to get into an infinite loop. This function will throw
+ * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10.
+ *
+ * Usually you don't call `$digest()` directly in
+ * {@link ng.directive:ngController controllers} or in
+ * {@link ng.$compileProvider#directive directives}.
+ * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a
+ * {@link ng.$compileProvider#directive directives}) will force a `$digest()`.
+ *
+ * If you want to be notified whenever `$digest()` is called,
+ * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()}
+ * with no `listener`.
+ *
+ * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
+ * life-cycle.
+ *
+ * # Example
+ *
+ var scope = ...;
+ scope.name = 'misko';
+ scope.counter = 0;
+
+ expect(scope.counter).toEqual(0);
+ scope.$watch('name', function(newValue, oldValue) {
+ scope.counter = scope.counter + 1;
+ });
+ expect(scope.counter).toEqual(0);
+
+ scope.$digest();
+ // no variable change
+ expect(scope.counter).toEqual(0);
+
+ scope.name = 'adam';
+ scope.$digest();
+ expect(scope.counter).toEqual(1);
+ *
+ *
+ */
+ $digest: function() {
+ var watch, value, last,
+ watchers,
+ asyncQueue = this.$$asyncQueue,
+ length,
+ dirty, ttl = TTL,
+ next, current, target = this,
+ watchLog = [],
+ logIdx, logMsg;
+
+ beginPhase('$digest');
+
+ do { // "while dirty" loop
+ dirty = false;
+ current = target;
+
+ while(asyncQueue.length) {
+ try {
+ current.$eval(asyncQueue.shift());
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+
+ do { // "traverse the scopes" loop
+ if ((watchers = current.$$watchers)) {
+ // process our watches
+ length = watchers.length;
+ while (length--) {
+ try {
+ watch = watchers[length];
+ // Most common watches are on primitives, in which case we can short
+ // circuit it with === operator, only when === fails do we use .equals
+ if ((value = watch.get(current)) !== (last = watch.last) &&
+ !(watch.eq
+ ? equals(value, last)
+ : (typeof value == 'number' && typeof last == 'number'
+ && isNaN(value) && isNaN(last)))) {
+ dirty = true;
+ watch.last = watch.eq ? copy(value) : value;
+ watch.fn(value, ((last === initWatchVal) ? value : last), current);
+ if (ttl < 5) {
+ logIdx = 4 - ttl;
+ if (!watchLog[logIdx]) watchLog[logIdx] = [];
+ logMsg = (isFunction(watch.exp))
+ ? 'fn: ' + (watch.exp.name || watch.exp.toString())
+ : watch.exp;
+ logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
+ watchLog[logIdx].push(logMsg);
+ }
+ }
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+ }
+
+ // Insanity Warning: scope depth-first traversal
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
+ // this piece should be kept in sync with the traversal in $broadcast
+ if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
+ while(current !== target && !(next = current.$$nextSibling)) {
+ current = current.$parent;
+ }
+ }
+ } while ((current = next));
+
+ if(dirty && !(ttl--)) {
+ clearPhase();
+ throw Error(TTL + ' $digest() iterations reached. Aborting!\n' +
+ 'Watchers fired in the last 5 iterations: ' + toJson(watchLog));
+ }
+ } while (dirty || asyncQueue.length);
+
+ clearPhase();
+ },
+
+
+ /**
+ * @ngdoc event
+ * @name ng.$rootScope.Scope#$destroy
+ * @eventOf ng.$rootScope.Scope
+ * @eventType broadcast on scope being destroyed
+ *
+ * @description
+ * Broadcasted when a scope and its children are being destroyed.
+ */
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$destroy
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Removes the current scope (and all of its children) from the parent scope. Removal implies
+ * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer
+ * propagate to the current scope and its children. Removal also implies that the current
+ * scope is eligible for garbage collection.
+ *
+ * The `$destroy()` is usually used by directives such as
+ * {@link ng.directive:ngRepeat ngRepeat} for managing the
+ * unrolling of the loop.
+ *
+ * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope.
+ * Application code can register a `$destroy` event handler that will give it chance to
+ * perform any necessary cleanup.
+ */
+ $destroy: function() {
+ // we can't destroy the root scope or a scope that has been already destroyed
+ if ($rootScope == this || this.$$destroyed) return;
+ var parent = this.$parent;
+
+ this.$broadcast('$destroy');
+ this.$$destroyed = true;
+
+ if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
+ if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
+ if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
+ if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
+
+ // This is bogus code that works around Chrome's GC leak
+ // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451
+ this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead =
+ this.$$childTail = null;
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$eval
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Executes the `expression` on the current scope returning the result. Any exceptions in the
+ * expression are propagated (uncaught). This is useful when evaluating Angular expressions.
+ *
+ * # Example
+ *
+ var scope = ng.$rootScope.Scope();
+ scope.a = 1;
+ scope.b = 2;
+
+ expect(scope.$eval('a+b')).toEqual(3);
+ expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
+ *
+ *
+ * @param {(string|function())=} expression An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
+ * - `function(scope)`: execute the function with the current `scope` parameter.
+ *
+ * @returns {*} The result of evaluating the expression.
+ */
+ $eval: function(expr, locals) {
+ return $parse(expr)(this, locals);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$evalAsync
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Executes the expression on the current scope at a later point in time.
+ *
+ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
+ *
+ * - it will execute in the current script execution context (before any DOM rendering).
+ * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after
+ * `expression` execution.
+ *
+ * Any exceptions from the execution of the expression are forwarded to the
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {(string|function())=} expression An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
+ * - `function(scope)`: execute the function with the current `scope` parameter.
+ *
+ */
+ $evalAsync: function(expr) {
+ this.$$asyncQueue.push(expr);
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$apply
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * `$apply()` is used to execute an expression in angular from outside of the angular framework.
+ * (For example from browser DOM events, setTimeout, XHR or third party libraries).
+ * Because we are calling into the angular framework we need to perform proper scope life-cycle
+ * of {@link ng.$exceptionHandler exception handling},
+ * {@link ng.$rootScope.Scope#$digest executing watches}.
+ *
+ * ## Life cycle
+ *
+ * # Pseudo-Code of `$apply()`
+ *
+ function $apply(expr) {
+ try {
+ return $eval(expr);
+ } catch (e) {
+ $exceptionHandler(e);
+ } finally {
+ $root.$digest();
+ }
+ }
+ *
+ *
+ *
+ * Scope's `$apply()` method transitions through the following stages:
+ *
+ * 1. The {@link guide/expression expression} is executed using the
+ * {@link ng.$rootScope.Scope#$eval $eval()} method.
+ * 2. Any exceptions from the execution of the expression are forwarded to the
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
+ * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression
+ * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method.
+ *
+ *
+ * @param {(string|function())=} exp An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/expression expression}.
+ * - `function(scope)`: execute the function with current `scope` parameter.
+ *
+ * @returns {*} The result of evaluating the expression.
+ */
+ $apply: function(expr) {
+ try {
+ beginPhase('$apply');
+ return this.$eval(expr);
+ } catch (e) {
+ $exceptionHandler(e);
+ } finally {
+ clearPhase();
+ try {
+ $rootScope.$digest();
+ } catch (e) {
+ $exceptionHandler(e);
+ throw e;
+ }
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$on
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of
+ * event life cycle.
+ *
+ * @param {string} name Event name to listen on.
+ * @param {function(event)} listener Function to call when the event is emitted.
+ * @returns {function()} Returns a deregistration function for this listener.
+ *
+ * The event listener function format is: `function(event, args...)`. The `event` object
+ * passed into the listener has the following attributes:
+ *
+ * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
+ * - `currentScope` - `{Scope}`: the current scope which is handling the event.
+ * - `name` - `{string}`: Name of the event.
+ * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event
+ * propagation (available only for events that were `$emit`-ed).
+ * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true.
+ * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
+ */
+ $on: function(name, listener) {
+ var namedListeners = this.$$listeners[name];
+ if (!namedListeners) {
+ this.$$listeners[name] = namedListeners = [];
+ }
+ namedListeners.push(listener);
+
+ return function() {
+ namedListeners[indexOf(namedListeners, listener)] = null;
+ };
+ },
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$rootScope.Scope#$emit
+ * @methodOf ng.$rootScope.Scope
+ * @function
+ *
+ * @description
+ * Dispatches an event `name` upwards through the scope hierarchy notifying the
+ * registered {@link ng.$rootScope.Scope#$on} listeners.
+ *
+ * The event life cycle starts at the scope on which `$emit` was called. All
+ * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified.
+ * Afterwards, the event traverses upwards toward the root scope and calls all registered
+ * listeners along the way. The event will stop propagating if one of the listeners cancels it.
+ *
+ * Any exception emmited from the {@link ng.$rootScope.Scope#$on listeners} will be passed
+ * onto the {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {string} name Event name to emit.
+ * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
+ * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on}
+ */
+ $emit: function(name, args) {
+ var empty = [],
+ namedListeners,
+ scope = this,
+ stopPropagation = false,
+ event = {
+ name: name,
+ targetScope: scope,
+ stopPropagation: function() {stopPropagation = true;},
+ preventDefault: function() {
+ event.defaultPrevented = true;
+ },
+ defaultPrevented: false
+ },
+ listenerArgs = concat([event], arguments, 1),
+ i, length;
+
+ do {
+ namedListeners = scope.$$listeners[name] || empty;
+ event.currentScope = scope;
+ for (i=0, length=namedListeners.length; i 7),
+ hasEvent: function(event) {
+ // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
+ // it. In particular the event is not fired when backspace or delete key are pressed or
+ // when cut operation is performed.
+ if (event == 'input' && msie == 9) return false;
+
+ if (isUndefined(eventSupport[event])) {
+ var divElm = document.createElement('div');
+ eventSupport[event] = 'on' + event in divElm;
+ }
+
+ return eventSupport[event];
+ },
+ csp: document.securityPolicy ? document.securityPolicy.isActive : false
+ };
+ }];
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$window
+ *
+ * @description
+ * A reference to the browser's `window` object. While `window`
+ * is globally available in JavaScript, it causes testability problems, because
+ * it is a global variable. In angular we always refer to it through the
+ * `$window` service, so it may be overriden, removed or mocked for testing.
+ *
+ * All expressions are evaluated with respect to current scope so they don't
+ * suffer from window globality.
+ *
+ * @example
+
+
+
+ ALERT
+
+
+
+
+ */
+ function $WindowProvider(){
+ this.$get = valueFn(window);
+ }
+
+ /**
+ * Parse headers into key value object
+ *
+ * @param {string} headers Raw headers as a string
+ * @returns {Object} Parsed headers as key value object
+ */
+ function parseHeaders(headers) {
+ var parsed = {}, key, val, i;
+
+ if (!headers) return parsed;
+
+ forEach(headers.split('\n'), function(line) {
+ i = line.indexOf(':');
+ key = lowercase(trim(line.substr(0, i)));
+ val = trim(line.substr(i + 1));
+
+ if (key) {
+ if (parsed[key]) {
+ parsed[key] += ', ' + val;
+ } else {
+ parsed[key] = val;
+ }
+ }
+ });
+
+ return parsed;
+ }
+
+
+ var IS_SAME_DOMAIN_URL_MATCH = /^(([^:]+):)?\/\/(\w+:{0,1}\w*@)?([\w\.-]*)?(:([0-9]+))?(.*)$/;
+
+
+ /**
+ * Parse a request and location URL and determine whether this is a same-domain request.
+ *
+ * @param {string} requestUrl The url of the request.
+ * @param {string} locationUrl The current browser location url.
+ * @returns {boolean} Whether the request is for the same domain.
+ */
+ function isSameDomain(requestUrl, locationUrl) {
+ var match = IS_SAME_DOMAIN_URL_MATCH.exec(requestUrl);
+ // if requestUrl is relative, the regex does not match.
+ if (match == null) return true;
+
+ var domain1 = {
+ protocol: match[2],
+ host: match[4],
+ port: int(match[6]) || DEFAULT_PORTS[match[2]] || null,
+ // IE8 sets unmatched groups to '' instead of undefined.
+ relativeProtocol: match[2] === undefined || match[2] === ''
+ };
+
+ match = URL_MATCH.exec(locationUrl);
+ var domain2 = {
+ protocol: match[1],
+ host: match[3],
+ port: int(match[5]) || DEFAULT_PORTS[match[1]] || null
+ };
+
+ return (domain1.protocol == domain2.protocol || domain1.relativeProtocol) &&
+ domain1.host == domain2.host &&
+ (domain1.port == domain2.port || (domain1.relativeProtocol &&
+ domain2.port == DEFAULT_PORTS[domain2.protocol]));
+ }
+
+
+ /**
+ * Returns a function that provides access to parsed headers.
+ *
+ * Headers are lazy parsed when first requested.
+ * @see parseHeaders
+ *
+ * @param {(string|Object)} headers Headers to provide access to.
+ * @returns {function(string=)} Returns a getter function which if called with:
+ *
+ * - if called with single an argument returns a single header value or null
+ * - if called with no arguments returns an object containing all headers.
+ */
+ function headersGetter(headers) {
+ var headersObj = isObject(headers) ? headers : undefined;
+
+ return function(name) {
+ if (!headersObj) headersObj = parseHeaders(headers);
+
+ if (name) {
+ return headersObj[lowercase(name)] || null;
+ }
+
+ return headersObj;
+ };
+ }
+
+
+ /**
+ * Chain all given functions
+ *
+ * This function is used for both request and response transforming
+ *
+ * @param {*} data Data to transform.
+ * @param {function(string=)} headers Http headers getter fn.
+ * @param {(function|Array.)} fns Function or an array of functions.
+ * @returns {*} Transformed data.
+ */
+ function transformData(data, headers, fns) {
+ if (isFunction(fns))
+ return fns(data, headers);
+
+ forEach(fns, function(fn) {
+ data = fn(data, headers);
+ });
+
+ return data;
+ }
+
+
+ function isSuccess(status) {
+ return 200 <= status && status < 300;
+ }
+
+
+ function $HttpProvider() {
+ var JSON_START = /^\s*(\[|\{[^\{])/,
+ JSON_END = /[\}\]]\s*$/,
+ PROTECTION_PREFIX = /^\)\]\}',?\n/;
+
+ var defaults = this.defaults = {
+ // transform incoming response data
+ transformResponse: [function(data) {
+ if (isString(data)) {
+ // strip json vulnerability protection prefix
+ data = data.replace(PROTECTION_PREFIX, '');
+ if (JSON_START.test(data) && JSON_END.test(data))
+ data = fromJson(data, true);
+ }
+ return data;
+ }],
+
+ // transform outgoing request data
+ transformRequest: [function(d) {
+ return isObject(d) && !isFile(d) ? toJson(d) : d;
+ }],
+
+ // default headers
+ headers: {
+ common: {
+ 'Accept': 'application/json, text/plain, */*'
+ },
+ post: {'Content-Type': 'application/json;charset=utf-8'},
+ put: {'Content-Type': 'application/json;charset=utf-8'}
+ }
+ };
+
+ var providerResponseInterceptors = this.responseInterceptors = [];
+
+ this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
+ function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
+
+ var defaultCache = $cacheFactory('$http'),
+ responseInterceptors = [];
+
+ forEach(providerResponseInterceptors, function(interceptor) {
+ responseInterceptors.push(
+ isString(interceptor)
+ ? $injector.get(interceptor)
+ : $injector.invoke(interceptor)
+ );
+ });
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$http
+ * @requires $httpBackend
+ * @requires $browser
+ * @requires $cacheFactory
+ * @requires $rootScope
+ * @requires $q
+ * @requires $injector
+ *
+ * @description
+ * The `$http` service is a core Angular service that facilitates communication with the remote
+ * HTTP servers via browser's {@link https://developer.mozilla.org/en/xmlhttprequest
+ * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
+ *
+ * For unit testing applications that use `$http` service, see
+ * {@link ngMock.$httpBackend $httpBackend mock}.
+ *
+ * For a higher level of abstraction, please check out the {@link ngResource.$resource
+ * $resource} service.
+ *
+ * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
+ * the $q service. While for simple usage patters this doesn't matter much, for advanced usage,
+ * it is important to familiarize yourself with these apis and guarantees they provide.
+ *
+ *
+ * # General usage
+ * The `$http` service is a function which takes a single argument — a configuration object —
+ * that is used to generate an http request and returns a {@link ng.$q promise}
+ * with two $http specific methods: `success` and `error`.
+ *
+ *
+ * $http({method: 'GET', url: '/someUrl'}).
+ * success(function(data, status, headers, config) {
+ * // this callback will be called asynchronously
+ * // when the response is available
+ * }).
+ * error(function(data, status, headers, config) {
+ * // called asynchronously if an error occurs
+ * // or server returns response with an error status.
+ * });
+ *
+ *
+ * Since the returned value of calling the $http function is a Promise object, you can also use
+ * the `then` method to register callbacks, and these callbacks will receive a single argument –
+ * an object representing the response. See the api signature and type info below for more
+ * details.
+ *
+ * A response status code that falls in the [200, 300) range is considered a success status and
+ * will result in the success callback being called. Note that if the response is a redirect,
+ * XMLHttpRequest will transparently follow it, meaning that the error callback will not be
+ * called for such responses.
+ *
+ * # Shortcut methods
+ *
+ * Since all invocation of the $http service require definition of the http method and url and
+ * POST and PUT requests require response body/data to be provided as well, shortcut methods
+ * were created to simplify using the api:
+ *
+ *
+ * $http.get('/someUrl').success(successCallback);
+ * $http.post('/someUrl', data).success(successCallback);
+ *
+ *
+ * Complete list of shortcut methods:
+ *
+ * - {@link ng.$http#get $http.get}
+ * - {@link ng.$http#head $http.head}
+ * - {@link ng.$http#post $http.post}
+ * - {@link ng.$http#put $http.put}
+ * - {@link ng.$http#delete $http.delete}
+ * - {@link ng.$http#jsonp $http.jsonp}
+ *
+ *
+ * # Setting HTTP Headers
+ *
+ * The $http service will automatically add certain http headers to all requests. These defaults
+ * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
+ * object, which currently contains this default configuration:
+ *
+ * - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
+ * - `Accept: application/json, text/plain, * / *`
+ * - `$httpProvider.defaults.headers.post`: (header defaults for HTTP POST requests)
+ * - `Content-Type: application/json`
+ * - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests)
+ * - `Content-Type: application/json`
+ *
+ * To add or overwrite these defaults, simply add or remove a property from this configuration
+ * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
+ * with name equal to the lower-cased http method name, e.g.
+ * `$httpProvider.defaults.headers.get['My-Header']='value'`.
+ *
+ * Additionally, the defaults can be set at runtime via the `$http.defaults` object in a similar
+ * fassion as described above.
+ *
+ *
+ * # Transforming Requests and Responses
+ *
+ * Both requests and responses can be transformed using transform functions. By default, Angular
+ * applies these transformations:
+ *
+ * Request transformations:
+ *
+ * - if the `data` property of the request config object contains an object, serialize it into
+ * JSON format.
+ *
+ * Response transformations:
+ *
+ * - if XSRF prefix is detected, strip it (see Security Considerations section below)
+ * - if json response is detected, deserialize it using a JSON parser
+ *
+ * To override these transformation locally, specify transform functions as `transformRequest`
+ * and/or `transformResponse` properties of the config object. To globally override the default
+ * transforms, override the `$httpProvider.defaults.transformRequest` and
+ * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`.
+ *
+ *
+ * # Caching
+ *
+ * To enable caching set the configuration property `cache` to `true`. When the cache is
+ * enabled, `$http` stores the response from the server in local cache. Next time the
+ * response is served from the cache without sending a request to the server.
+ *
+ * Note that even if the response is served from cache, delivery of the data is asynchronous in
+ * the same way that real requests are.
+ *
+ * If there are multiple GET requests for the same url that should be cached using the same
+ * cache, but the cache is not populated yet, only one request to the server will be made and
+ * the remaining requests will be fulfilled using the response for the first request.
+ *
+ *
+ * # Response interceptors
+ *
+ * Before you start creating interceptors, be sure to understand the
+ * {@link ng.$q $q and deferred/promise APIs}.
+ *
+ * For purposes of global error handling, authentication or any kind of synchronous or
+ * asynchronous preprocessing of received responses, it is desirable to be able to intercept
+ * responses for http requests before they are handed over to the application code that
+ * initiated these requests. The response interceptors leverage the {@link ng.$q
+ * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
+ *
+ * The interceptors are service factories that are registered with the $httpProvider by
+ * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
+ * injected with dependencies (if specified) and returns the interceptor — a function that
+ * takes a {@link ng.$q promise} and returns the original or a new promise.
+ *
+ *
+ * // register the interceptor as a service
+ * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
+ * return function(promise) {
+ * return promise.then(function(response) {
+ * // do something on success
+ * }, function(response) {
+ * // do something on error
+ * if (canRecover(response)) {
+ * return responseOrNewPromise
+ * }
+ * return $q.reject(response);
+ * });
+ * }
+ * });
+ *
+ * $httpProvider.responseInterceptors.push('myHttpInterceptor');
+ *
+ *
+ * // register the interceptor via an anonymous factory
+ * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
+ * return function(promise) {
+ * // same as above
+ * }
+ * });
+ *
+ *
+ *
+ * # Security Considerations
+ *
+ * When designing web applications, consider security threats from:
+ *
+ * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
+ * JSON Vulnerability}
+ * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}
+ *
+ * Both server and the client must cooperate in order to eliminate these threats. Angular comes
+ * pre-configured with strategies that address these issues, but for this to work backend server
+ * cooperation is required.
+ *
+ * ## JSON Vulnerability Protection
+ *
+ * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
+ * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into
+ * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To
+ * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
+ * Angular will automatically strip the prefix before processing it as JSON.
+ *
+ * For example if your server needs to return:
+ *
+ * ['one','two']
+ *
+ *
+ * which is vulnerable to attack, your server can return:
+ *
+ * )]}',
+ * ['one','two']
+ *
+ *
+ * Angular will strip the prefix, before processing the JSON.
+ *
+ *
+ * ## Cross Site Request Forgery (XSRF) Protection
+ *
+ * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which
+ * an unauthorized site can gain your user's private data. Angular provides following mechanism
+ * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
+ * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
+ * runs on your domain could read the cookie, your server can be assured that the XHR came from
+ * JavaScript running on your domain. The header will not be set for cross-domain requests.
+ *
+ * To take advantage of this, your server needs to set a token in a JavaScript readable session
+ * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the
+ * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure
+ * that only JavaScript running on your domain could have read the token. The token must be
+ * unique for each user and must be verifiable by the server (to prevent the JavaScript making
+ * up its own tokens). We recommend that the token is a digest of your site's authentication
+ * cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
+ *
+ *
+ * @param {object} config Object describing the request to be made and how it should be
+ * processed. The object has following properties:
+ *
+ * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
+ * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
+ * - **params** – `{Object.}` – Map of strings or objects which will be turned to
+ * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified.
+ * - **data** – `{string|Object}` – Data to be sent as the request message data.
+ * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server.
+ * - **transformRequest** – `{function(data, headersGetter)|Array.}` –
+ * transform function or an array of such functions. The transform function takes the http
+ * request body and headers and returns its transformed (typically serialized) version.
+ * - **transformResponse** – `{function(data, headersGetter)|Array.}` –
+ * transform function or an array of such functions. The transform function takes the http
+ * response body and headers and returns its transformed (typically deserialized) version.
+ * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
+ * GET request, otherwise if a cache instance built with
+ * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
+ * caching.
+ * - **timeout** – `{number}` – timeout in milliseconds.
+ * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
+ * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
+ * requests with credentials} for more information.
+ * - **responseType** - `{string}` - see {@link
+ * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
+ *
+ * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
+ * standard `then` method and two http specific methods: `success` and `error`. The `then`
+ * method takes two arguments a success and an error callback which will be called with a
+ * response object. The `success` and `error` methods take a single argument - a function that
+ * will be called when the request succeeds or fails respectively. The arguments passed into
+ * these functions are destructured representation of the response object passed into the
+ * `then` method. The response object has these properties:
+ *
+ * - **data** – `{string|Object}` – The response body transformed with the transform functions.
+ * - **status** – `{number}` – HTTP status code of the response.
+ * - **headers** – `{function([headerName])}` – Header getter function.
+ * - **config** – `{Object}` – The configuration object that was used to generate the request.
+ *
+ * @property {Array.} pendingRequests Array of config objects for currently pending
+ * requests. This is primarily meant to be used for debugging purposes.
+ *
+ *
+ * @example
+
+
+
+
+ GET
+ JSONP
+
+
+
fetch
+
Sample GET
+
Sample JSONP
+
Invalid JSONP
+
http status code: {{status}}
+
http response data: {{data}}
+
+
+
+ function FetchCtrl($scope, $http, $templateCache) {
+ $scope.method = 'GET';
+ $scope.url = 'http-hello.html';
+
+ $scope.fetch = function() {
+ $scope.code = null;
+ $scope.response = null;
+
+ $http({method: $scope.method, url: $scope.url, cache: $templateCache}).
+ success(function(data, status) {
+ $scope.status = status;
+ $scope.data = data;
+ }).
+ error(function(data, status) {
+ $scope.data = data || "Request failed";
+ $scope.status = status;
+ });
+ };
+
+ $scope.updateModel = function(method, url) {
+ $scope.method = method;
+ $scope.url = url;
+ };
+ }
+
+
+ Hello, $http!
+
+
+ it('should make an xhr GET request', function() {
+ element(':button:contains("Sample GET")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Hello, \$http!/);
+ });
+
+ it('should make a JSONP request to angularjs.org', function() {
+ element(':button:contains("Sample JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('200');
+ expect(binding('data')).toMatch(/Super Hero!/);
+ });
+
+ it('should make JSONP request to invalid URL and invoke the error handler',
+ function() {
+ element(':button:contains("Invalid JSONP")').click();
+ element(':button:contains("fetch")').click();
+ expect(binding('status')).toBe('0');
+ expect(binding('data')).toBe('Request failed');
+ });
+
+
+ */
+ function $http(config) {
+ config.method = uppercase(config.method);
+
+ var reqTransformFn = config.transformRequest || defaults.transformRequest,
+ respTransformFn = config.transformResponse || defaults.transformResponse,
+ defHeaders = defaults.headers,
+ xsrfToken = isSameDomain(config.url, $browser.url()) ?
+ $browser.cookies()['XSRF-TOKEN'] : undefined,
+ reqHeaders = extend({'X-XSRF-TOKEN': xsrfToken},
+ defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
+ reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
+ promise;
+
+ // strip content-type if data is undefined
+ if (isUndefined(config.data)) {
+ delete reqHeaders['Content-Type'];
+ }
+
+ if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
+ config.withCredentials = defaults.withCredentials;
+ }
+
+ // send request
+ promise = sendReq(config, reqData, reqHeaders);
+
+
+ // transform future response
+ promise = promise.then(transformResponse, transformResponse);
+
+ // apply interceptors
+ forEach(responseInterceptors, function(interceptor) {
+ promise = interceptor(promise);
+ });
+
+ promise.success = function(fn) {
+ promise.then(function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
+ };
+
+ promise.error = function(fn) {
+ promise.then(null, function(response) {
+ fn(response.data, response.status, response.headers, config);
+ });
+ return promise;
+ };
+
+ return promise;
+
+ function transformResponse(response) {
+ // make a copy since the response must be cacheable
+ var resp = extend({}, response, {
+ data: transformData(response.data, response.headers, respTransformFn)
+ });
+ return (isSuccess(response.status))
+ ? resp
+ : $q.reject(resp);
+ }
+ }
+
+ $http.pendingRequests = [];
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#get
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `GET` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#delete
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `DELETE` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#head
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `HEAD` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#jsonp
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `JSONP` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request.
+ * Should contain `JSON_CALLBACK` string.
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+ createShortMethods('get', 'delete', 'head', 'jsonp');
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#post
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `POST` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+
+ /**
+ * @ngdoc method
+ * @name ng.$http#put
+ * @methodOf ng.$http
+ *
+ * @description
+ * Shortcut method to perform `PUT` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {HttpPromise} Future object
+ */
+ createShortMethodsWithData('post', 'put');
+
+ /**
+ * @ngdoc property
+ * @name ng.$http#defaults
+ * @propertyOf ng.$http
+ *
+ * @description
+ * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
+ * default headers, withCredentials as well as request and response transformations.
+ *
+ * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
+ */
+ $http.defaults = defaults;
+
+
+ return $http;
+
+
+ function createShortMethods(names) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url
+ }));
+ };
+ });
+ }
+
+
+ function createShortMethodsWithData(name) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, data, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url,
+ data: data
+ }));
+ };
+ });
+ }
+
+
+ /**
+ * Makes the request
+ *
+ * !!! ACCESSES CLOSURE VARS:
+ * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
+ */
+ function sendReq(config, reqData, reqHeaders) {
+ var deferred = $q.defer(),
+ promise = deferred.promise,
+ cache,
+ cachedResp,
+ url = buildUrl(config.url, config.params);
+
+ $http.pendingRequests.push(config);
+ promise.then(removePendingReq, removePendingReq);
+
+
+ if (config.cache && config.method == 'GET') {
+ cache = isObject(config.cache) ? config.cache : defaultCache;
+ }
+
+ if (cache) {
+ cachedResp = cache.get(url);
+ if (cachedResp) {
+ if (cachedResp.then) {
+ // cached request has already been sent, but there is no response yet
+ cachedResp.then(removePendingReq, removePendingReq);
+ return cachedResp;
+ } else {
+ // serving from cache
+ if (isArray(cachedResp)) {
+ resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
+ } else {
+ resolvePromise(cachedResp, 200, {});
+ }
+ }
+ } else {
+ // put the promise for the non-transformed response into cache as a placeholder
+ cache.put(url, promise);
+ }
+ }
+
+ // if we won't have the response in cache, send the request to the backend
+ if (!cachedResp) {
+ $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
+ config.withCredentials, config.responseType);
+ }
+
+ return promise;
+
+
+ /**
+ * Callback registered to $httpBackend():
+ * - caches the response if desired
+ * - resolves the raw $http promise
+ * - calls $apply
+ */
+ function done(status, response, headersString) {
+ if (cache) {
+ if (isSuccess(status)) {
+ cache.put(url, [status, response, parseHeaders(headersString)]);
+ } else {
+ // remove promise from the cache
+ cache.remove(url);
+ }
+ }
+
+ resolvePromise(response, status, headersString);
+ $rootScope.$apply();
+ }
+
+
+ /**
+ * Resolves the raw $http promise.
+ */
+ function resolvePromise(response, status, headers) {
+ // normalize internal statuses to 0
+ status = Math.max(status, 0);
+
+ (isSuccess(status) ? deferred.resolve : deferred.reject)({
+ data: response,
+ status: status,
+ headers: headersGetter(headers),
+ config: config
+ });
+ }
+
+
+ function removePendingReq() {
+ var idx = indexOf($http.pendingRequests, config);
+ if (idx !== -1) $http.pendingRequests.splice(idx, 1);
+ }
+ }
+
+
+ function buildUrl(url, params) {
+ if (!params) return url;
+ var parts = [];
+ forEachSorted(params, function(value, key) {
+ if (value == null || value == undefined) return;
+ if (!isArray(value)) value = [value];
+
+ forEach(value, function(v) {
+ if (isObject(v)) {
+ v = toJson(v);
+ }
+ parts.push(encodeURIComponent(key) + '=' +
+ encodeURIComponent(v));
+ });
+ });
+ return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
+ }
+
+
+ }];
+ }
+ var XHR = window.XMLHttpRequest || function() {
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
+ throw new Error("This browser does not support XMLHttpRequest.");
+ };
+
+
+ /**
+ * @ngdoc object
+ * @name ng.$httpBackend
+ * @requires $browser
+ * @requires $window
+ * @requires $document
+ *
+ * @description
+ * HTTP backend used by the {@link ng.$http service} that delegates to
+ * XMLHttpRequest object or JSONP and deals with browser incompatibilities.
+ *
+ * You should never need to use this service directly, instead use the higher-level abstractions:
+ * {@link ng.$http $http} or {@link ngResource.$resource $resource}.
+ *
+ * During testing this implementation is swapped with {@link ngMock.$httpBackend mock
+ * $httpBackend} which can be trained with responses.
+ */
+ function $HttpBackendProvider() {
+ this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) {
+ return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks,
+ $document[0], $window.location.protocol.replace(':', ''));
+ }];
+ }
+
+ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
+ // TODO(vojta): fix the signature
+ return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
+ $browser.$$incOutstandingRequestCount();
+ url = url || $browser.url();
+
+ if (lowercase(method) == 'jsonp') {
+ var callbackId = '_' + (callbacks.counter++).toString(36);
+ callbacks[callbackId] = function(data) {
+ callbacks[callbackId].data = data;
+ };
+
+ jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
+ function() {
+ if (callbacks[callbackId].data) {
+ completeRequest(callback, 200, callbacks[callbackId].data);
+ } else {
+ completeRequest(callback, -2);
+ }
+ delete callbacks[callbackId];
+ });
+ } else {
+ var xhr = new XHR();
+ xhr.open(method, url, true);
+ forEach(headers, function(value, key) {
+ if (value) xhr.setRequestHeader(key, value);
+ });
+
+ var status;
+
+ // In IE6 and 7, this might be called synchronously when xhr.send below is called and the
+ // response is in the cache. the promise api will ensure that to the app code the api is
+ // always async
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ completeRequest(callback, status || xhr.status, xhr.response || xhr.responseText,
+ xhr.getAllResponseHeaders());
+ }
+ };
+
+ if (withCredentials) {
+ xhr.withCredentials = true;
+ }
+
+ if (responseType) {
+ xhr.responseType = responseType;
+ }
+
+ xhr.send(post || '');
+
+ if (timeout > 0) {
+ $browserDefer(function() {
+ status = -1;
+ xhr.abort();
+ }, timeout);
+ }
+ }
+
+
+ function completeRequest(callback, status, response, headersString) {
+ // URL_MATCH is defined in src/service/location.js
+ var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1];
+
+ // fix status code for file protocol (it's always 0)
+ status = (protocol == 'file') ? (response ? 200 : 404) : status;
+
+ // normalize IE bug (http://bugs.jquery.com/ticket/1450)
+ status = status == 1223 ? 204 : status;
+
+ callback(status, response, headersString);
+ $browser.$$completeOutstandingRequest(noop);
+ }
+ };
+
+ function jsonpReq(url, done) {
+ // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
+ // - fetches local scripts via XHR and evals them
+ // - adds and immediately removes script elements from the document
+ var script = rawDocument.createElement('script'),
+ doneWrapper = function() {
+ rawDocument.body.removeChild(script);
+ if (done) done();
+ };
+
+ script.type = 'text/javascript';
+ script.src = url;
+
+ if (msie) {
+ script.onreadystatechange = function() {
+ if (/loaded|complete/.test(script.readyState)) doneWrapper();
+ };
+ } else {
+ script.onload = script.onerror = doneWrapper;
+ }
+
+ rawDocument.body.appendChild(script);
+ }
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$locale
+ *
+ * @description
+ * $locale service provides localization rules for various Angular components. As of right now the
+ * only public api is:
+ *
+ * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`)
+ */
+ function $LocaleProvider(){
+ this.$get = function() {
+ return {
+ id: 'en-us',
+
+ NUMBER_FORMATS: {
+ DECIMAL_SEP: '.',
+ GROUP_SEP: ',',
+ PATTERNS: [
+ { // Decimal Pattern
+ minInt: 1,
+ minFrac: 0,
+ maxFrac: 3,
+ posPre: '',
+ posSuf: '',
+ negPre: '-',
+ negSuf: '',
+ gSize: 3,
+ lgSize: 3
+ },{ //Currency Pattern
+ minInt: 1,
+ minFrac: 2,
+ maxFrac: 2,
+ posPre: '\u00A4',
+ posSuf: '',
+ negPre: '(\u00A4',
+ negSuf: ')',
+ gSize: 3,
+ lgSize: 3
+ }
+ ],
+ CURRENCY_SYM: '$'
+ },
+
+ DATETIME_FORMATS: {
+ MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
+ .split(','),
+ SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
+ DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
+ SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
+ AMPMS: ['AM','PM'],
+ medium: 'MMM d, y h:mm:ss a',
+ short: 'M/d/yy h:mm a',
+ fullDate: 'EEEE, MMMM d, y',
+ longDate: 'MMMM d, y',
+ mediumDate: 'MMM d, y',
+ shortDate: 'M/d/yy',
+ mediumTime: 'h:mm:ss a',
+ shortTime: 'h:mm a'
+ },
+
+ pluralCat: function(num) {
+ if (num === 1) {
+ return 'one';
+ }
+ return 'other';
+ }
+ };
+ };
+ }
+
+ function $TimeoutProvider() {
+ this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
+ function($rootScope, $browser, $q, $exceptionHandler) {
+ var deferreds = {};
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$timeout
+ * @requires $browser
+ *
+ * @description
+ * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
+ * block and delegates any exceptions to
+ * {@link ng.$exceptionHandler $exceptionHandler} service.
+ *
+ * The return value of registering a timeout function is a promise which will be resolved when
+ * the timeout is reached and the timeout function is executed.
+ *
+ * To cancel a the timeout request, call `$timeout.cancel(promise)`.
+ *
+ * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
+ * synchronously flush the queue of deferred functions.
+ *
+ * @param {function()} fn A function, who's execution should be delayed.
+ * @param {number=} [delay=0] Delay in milliseconds.
+ * @param {boolean=} [invokeApply=true] If set to false skips model dirty checking, otherwise
+ * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
+ * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
+ * promise will be resolved with is the return value of the `fn` function.
+ */
+ function timeout(fn, delay, invokeApply) {
+ var deferred = $q.defer(),
+ promise = deferred.promise,
+ skipApply = (isDefined(invokeApply) && !invokeApply),
+ timeoutId, cleanup;
+
+ timeoutId = $browser.defer(function() {
+ try {
+ deferred.resolve(fn());
+ } catch(e) {
+ deferred.reject(e);
+ $exceptionHandler(e);
+ }
+
+ if (!skipApply) $rootScope.$apply();
+ }, delay);
+
+ cleanup = function() {
+ delete deferreds[promise.$$timeoutId];
+ };
+
+ promise.$$timeoutId = timeoutId;
+ deferreds[timeoutId] = deferred;
+ promise.then(cleanup, cleanup);
+
+ return promise;
+ }
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$timeout#cancel
+ * @methodOf ng.$timeout
+ *
+ * @description
+ * Cancels a task associated with the `promise`. As a result of this the promise will be
+ * resolved with a rejection.
+ *
+ * @param {Promise=} promise Promise returned by the `$timeout` function.
+ * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully
+ * canceled.
+ */
+ timeout.cancel = function(promise) {
+ if (promise && promise.$$timeoutId in deferreds) {
+ deferreds[promise.$$timeoutId].reject('canceled');
+ return $browser.defer.cancel(promise.$$timeoutId);
+ }
+ return false;
+ };
+
+ return timeout;
+ }];
+ }
+
+ /**
+ * @ngdoc object
+ * @name ng.$filterProvider
+ * @description
+ *
+ * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To
+ * achieve this a filter definition consists of a factory function which is annotated with dependencies and is
+ * responsible for creating a the filter function.
+ *
+ *
+ * // Filter registration
+ * function MyModule($provide, $filterProvider) {
+ * // create a service to demonstrate injection (not always needed)
+ * $provide.value('greet', function(name){
+ * return 'Hello ' + name + '!';
+ * });
+ *
+ * // register a filter factory which uses the
+ * // greet service to demonstrate DI.
+ * $filterProvider.register('greet', function(greet){
+ * // return the filter function which uses the greet service
+ * // to generate salutation
+ * return function(text) {
+ * // filters need to be forgiving so check input validity
+ * return text && greet(text) || text;
+ * };
+ * });
+ * }
+ *
+ *
+ * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`.
+ *
+ * it('should be the same instance', inject(
+ * function($filterProvider) {
+ * $filterProvider.register('reverse', function(){
+ * return ...;
+ * });
+ * },
+ * function($filter, reverseFilter) {
+ * expect($filter('reverse')).toBe(reverseFilter);
+ * });
+ *
+ *
+ *
+ * For more information about how angular filters work, and how to create your own filters, see
+ * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer
+ * Guide.
+ */
+ /**
+ * @ngdoc method
+ * @name ng.$filterProvider#register
+ * @methodOf ng.$filterProvider
+ * @description
+ * Register filter factory function.
+ *
+ * @param {String} name Name of the filter.
+ * @param {function} fn The filter factory function which is injectable.
+ */
+
+
+ /**
+ * @ngdoc function
+ * @name ng.$filter
+ * @function
+ * @description
+ * Filters are used for formatting data displayed to the user.
+ *
+ * The general syntax in templates is as follows:
+ *
+ * {{ expression | [ filter_name ] }}
+ *
+ * @param {String} name Name of the filter function to retrieve
+ * @return {Function} the filter function
+ */
+ $FilterProvider.$inject = ['$provide'];
+ function $FilterProvider($provide) {
+ var suffix = 'Filter';
+
+ function register(name, factory) {
+ return $provide.factory(name + suffix, factory);
+ }
+ this.register = register;
+
+ this.$get = ['$injector', function($injector) {
+ return function(name) {
+ return $injector.get(name + suffix);
+ }
+ }];
+
+ ////////////////////////////////////////
+
+ register('currency', currencyFilter);
+ register('date', dateFilter);
+ register('filter', filterFilter);
+ register('json', jsonFilter);
+ register('limitTo', limitToFilter);
+ register('lowercase', lowercaseFilter);
+ register('number', numberFilter);
+ register('orderBy', orderByFilter);
+ register('uppercase', uppercaseFilter);
+ }
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:filter
+ * @function
+ *
+ * @description
+ * Selects a subset of items from `array` and returns it as a new array.
+ *
+ * Note: This function is used to augment the `Array` type in Angular expressions. See
+ * {@link ng.$filter} for more information about Angular arrays.
+ *
+ * @param {Array} array The source array.
+ * @param {string|Object|function()} expression The predicate to be used for selecting items from
+ * `array`.
+ *
+ * Can be one of:
+ *
+ * - `string`: Predicate that results in a substring match using the value of `expression`
+ * string. All strings or objects with string properties in `array` that contain this string
+ * will be returned. The predicate can be negated by prefixing the string with `!`.
+ *
+ * - `Object`: A pattern object can be used to filter specific properties on objects contained
+ * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
+ * which have property `name` containing "M" and property `phone` containing "1". A special
+ * property name `$` can be used (as in `{$:"text"}`) to accept a match against any
+ * property of the object. That's equivalent to the simple substring match with a `string`
+ * as described above.
+ *
+ * - `function`: A predicate function can be used to write arbitrary filters. The function is
+ * called for each element of `array`. The final result is an array of those elements that
+ * the predicate returned true for.
+ *
+ * @example
+
+
+
+
+ Search:
+
+ Name Phone
+
+ {{friend.name}}
+ {{friend.phone}}
+
+
+
+ Any:
+ Name only
+ Phone only
+
+ Name Phone
+
+ {{friend.name}}
+ {{friend.phone}}
+
+
+
+
+ it('should search across all fields when filtering with a string', function() {
+ input('searchText').enter('m');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Adam']);
+
+ input('searchText').enter('76');
+ expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['John', 'Julie']);
+ });
+
+ it('should search in specific fields when filtering with a predicate object', function() {
+ input('search.$').enter('i');
+ expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Mike', 'Julie']);
+ });
+
+
+ */
+ function filterFilter() {
+ return function(array, expression) {
+ if (!(array instanceof Array)) return array;
+ var predicates = [];
+ predicates.check = function(value) {
+ for (var j = 0; j < predicates.length; j++) {
+ if(!predicates[j](value)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ var search = function(obj, text){
+ if (text.charAt(0) === '!') {
+ return !search(obj, text.substr(1));
+ }
+ switch (typeof obj) {
+ case "boolean":
+ case "number":
+ case "string":
+ return ('' + obj).toLowerCase().indexOf(text) > -1;
+ case "object":
+ for ( var objKey in obj) {
+ if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
+ return true;
+ }
+ }
+ return false;
+ case "array":
+ for ( var i = 0; i < obj.length; i++) {
+ if (search(obj[i], text)) {
+ return true;
+ }
+ }
+ return false;
+ default:
+ return false;
+ }
+ };
+ switch (typeof expression) {
+ case "boolean":
+ case "number":
+ case "string":
+ expression = {$:expression};
+ case "object":
+ for (var key in expression) {
+ if (key == '$') {
+ (function() {
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(value, text);
+ });
+ })();
+ } else {
+ (function() {
+ var path = key;
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(getter(value, path), text);
+ });
+ })();
+ }
+ }
+ break;
+ case 'function':
+ predicates.push(expression);
+ break;
+ default:
+ return array;
+ }
+ var filtered = [];
+ for ( var j = 0; j < array.length; j++) {
+ var value = array[j];
+ if (predicates.check(value)) {
+ filtered.push(value);
+ }
+ }
+ return filtered;
+ }
+ }
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:currency
+ * @function
+ *
+ * @description
+ * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
+ * symbol for current locale is used.
+ *
+ * @param {number} amount Input to filter.
+ * @param {string=} symbol Currency symbol or identifier to be displayed.
+ * @returns {string} Formatted number.
+ *
+ *
+ * @example
+
+
+
+
+
+ default currency symbol ($): {{amount | currency}}
+ custom currency identifier (USD$): {{amount | currency:"USD$"}}
+
+
+
+ it('should init with 1234.56', function() {
+ expect(binding('amount | currency')).toBe('$1,234.56');
+ expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56');
+ });
+ it('should update', function() {
+ input('amount').enter('-1234');
+ expect(binding('amount | currency')).toBe('($1,234.00)');
+ expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)');
+ });
+
+
+ */
+ currencyFilter.$inject = ['$locale'];
+ function currencyFilter($locale) {
+ var formats = $locale.NUMBER_FORMATS;
+ return function(amount, currencySymbol){
+ if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
+ return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
+ replace(/\u00A4/g, currencySymbol);
+ };
+ }
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:number
+ * @function
+ *
+ * @description
+ * Formats a number as text.
+ *
+ * If the input is not a number an empty string is returned.
+ *
+ * @param {number|string} number Number to format.
+ * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to.
+ * @returns {string} Number rounded to decimalPlaces and places a “,†after each third digit.
+ *
+ * @example
+
+
+
+
+ Enter number:
+ Default formatting: {{val | number}}
+ No fractions: {{val | number:0}}
+ Negative number: {{-val | number:4}}
+
+
+
+ it('should format numbers', function() {
+ expect(binding('val | number')).toBe('1,234.568');
+ expect(binding('val | number:0')).toBe('1,235');
+ expect(binding('-val | number:4')).toBe('-1,234.5679');
+ });
+
+ it('should update', function() {
+ input('val').enter('3374.333');
+ expect(binding('val | number')).toBe('3,374.333');
+ expect(binding('val | number:0')).toBe('3,374');
+ expect(binding('-val | number:4')).toBe('-3,374.3330');
+ });
+
+
+ */
+
+
+ numberFilter.$inject = ['$locale'];
+ function numberFilter($locale) {
+ var formats = $locale.NUMBER_FORMATS;
+ return function(number, fractionSize) {
+ return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
+ fractionSize);
+ };
+ }
+
+ var DECIMAL_SEP = '.';
+ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
+ if (isNaN(number) || !isFinite(number)) return '';
+
+ var isNegative = number < 0;
+ number = Math.abs(number);
+ var numStr = number + '',
+ formatedText = '',
+ parts = [];
+
+ var hasExponent = false;
+ if (numStr.indexOf('e') !== -1) {
+ var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
+ if (match && match[2] == '-' && match[3] > fractionSize + 1) {
+ numStr = '0';
+ } else {
+ formatedText = numStr;
+ hasExponent = true;
+ }
+ }
+
+ if (!hasExponent) {
+ var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
+
+ // determine fractionSize if it is not specified
+ if (isUndefined(fractionSize)) {
+ fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
+ }
+
+ var pow = Math.pow(10, fractionSize);
+ number = Math.round(number * pow) / pow;
+ var fraction = ('' + number).split(DECIMAL_SEP);
+ var whole = fraction[0];
+ fraction = fraction[1] || '';
+
+ var pos = 0,
+ lgroup = pattern.lgSize,
+ group = pattern.gSize;
+
+ if (whole.length >= (lgroup + group)) {
+ pos = whole.length - lgroup;
+ for (var i = 0; i < pos; i++) {
+ if ((pos - i)%group === 0 && i !== 0) {
+ formatedText += groupSep;
+ }
+ formatedText += whole.charAt(i);
+ }
+ }
+
+ for (i = pos; i < whole.length; i++) {
+ if ((whole.length - i)%lgroup === 0 && i !== 0) {
+ formatedText += groupSep;
+ }
+ formatedText += whole.charAt(i);
+ }
+
+ // format fraction part.
+ while(fraction.length < fractionSize) {
+ fraction += '0';
+ }
+
+ if (fractionSize) formatedText += decimalSep + fraction.substr(0, fractionSize);
+ }
+
+ parts.push(isNegative ? pattern.negPre : pattern.posPre);
+ parts.push(formatedText);
+ parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
+ return parts.join('');
+ }
+
+ function padNumber(num, digits, trim) {
+ var neg = '';
+ if (num < 0) {
+ neg = '-';
+ num = -num;
+ }
+ num = '' + num;
+ while(num.length < digits) num = '0' + num;
+ if (trim)
+ num = num.substr(num.length - digits);
+ return neg + num;
+ }
+
+
+ function dateGetter(name, size, offset, trim) {
+ return function(date) {
+ var value = date['get' + name]();
+ if (offset > 0 || value > -offset)
+ value += offset;
+ if (value === 0 && offset == -12 ) value = 12;
+ return padNumber(value, size, trim);
+ };
+ }
+
+ function dateStrGetter(name, shortForm) {
+ return function(date, formats) {
+ var value = date['get' + name]();
+ var get = uppercase(shortForm ? ('SHORT' + name) : name);
+
+ return formats[get][value];
+ };
+ }
+
+ function timeZoneGetter(date) {
+ var offset = date.getTimezoneOffset();
+ return padNumber(offset / 60, 2) + padNumber(Math.abs(offset % 60), 2);
+ }
+
+ function ampmGetter(date, formats) {
+ return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
+ }
+
+ var DATE_FORMATS = {
+ yyyy: dateGetter('FullYear', 4),
+ yy: dateGetter('FullYear', 2, 0, true),
+ y: dateGetter('FullYear', 1),
+ MMMM: dateStrGetter('Month'),
+ MMM: dateStrGetter('Month', true),
+ MM: dateGetter('Month', 2, 1),
+ M: dateGetter('Month', 1, 1),
+ dd: dateGetter('Date', 2),
+ d: dateGetter('Date', 1),
+ HH: dateGetter('Hours', 2),
+ H: dateGetter('Hours', 1),
+ hh: dateGetter('Hours', 2, -12),
+ h: dateGetter('Hours', 1, -12),
+ mm: dateGetter('Minutes', 2),
+ m: dateGetter('Minutes', 1),
+ ss: dateGetter('Seconds', 2),
+ s: dateGetter('Seconds', 1),
+ EEEE: dateStrGetter('Day'),
+ EEE: dateStrGetter('Day', true),
+ a: ampmGetter,
+ Z: timeZoneGetter
+ };
+
+ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
+ NUMBER_STRING = /^\d+$/;
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:date
+ * @function
+ *
+ * @description
+ * Formats `date` to a string based on the requested `format`.
+ *
+ * `format` string can be composed of the following elements:
+ *
+ * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010)
+ * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10)
+ * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199)
+ * * `'MMMM'`: Month in year (January-December)
+ * * `'MMM'`: Month in year (Jan-Dec)
+ * * `'MM'`: Month in year, padded (01-12)
+ * * `'M'`: Month in year (1-12)
+ * * `'dd'`: Day in month, padded (01-31)
+ * * `'d'`: Day in month (1-31)
+ * * `'EEEE'`: Day in Week,(Sunday-Saturday)
+ * * `'EEE'`: Day in Week, (Sun-Sat)
+ * * `'HH'`: Hour in day, padded (00-23)
+ * * `'H'`: Hour in day (0-23)
+ * * `'hh'`: Hour in am/pm, padded (01-12)
+ * * `'h'`: Hour in am/pm, (1-12)
+ * * `'mm'`: Minute in hour, padded (00-59)
+ * * `'m'`: Minute in hour (0-59)
+ * * `'ss'`: Second in minute, padded (00-59)
+ * * `'s'`: Second in minute (0-59)
+ * * `'a'`: am/pm marker
+ * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-1200)
+ *
+ * `format` string can also be one of the following predefined
+ * {@link guide/i18n localizable formats}:
+ *
+ * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
+ * (e.g. Sep 3, 2010 12:05:08 pm)
+ * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
+ * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
+ * (e.g. Friday, September 3, 2010)
+ * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010
+ * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
+ * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
+ * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
+ * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
+ *
+ * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
+ * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
+ * (e.g. `"h o''clock"`).
+ *
+ * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
+ * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and it's
+ * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ).
+ * @param {string=} format Formatting rules (see Description). If not specified,
+ * `mediumDate` is used.
+ * @returns {string} Formatted string or the input if input is not recognized as date/millis.
+ *
+ * @example
+
+
+ {{1288323623006 | date:'medium'}} :
+ {{1288323623006 | date:'medium'}}
+ {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}} :
+ {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
+ {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}} :
+ {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
+
+
+ it('should format date', function() {
+ expect(binding("1288323623006 | date:'medium'")).
+ toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
+ expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).
+ toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} \-?\d{4}/);
+ expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).
+ toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
+ });
+
+
+ */
+ dateFilter.$inject = ['$locale'];
+ function dateFilter($locale) {
+
+
+ var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;
+ // 1 2 3 4 5 6 7 8 9 10 11
+ function jsonStringToDate(string) {
+ var match;
+ if (match = string.match(R_ISO8601_STR)) {
+ var date = new Date(0),
+ tzHour = 0,
+ tzMin = 0,
+ dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear,
+ timeSetter = match[8] ? date.setUTCHours : date.setHours;
+
+ if (match[9]) {
+ tzHour = int(match[9] + match[10]);
+ tzMin = int(match[9] + match[11]);
+ }
+ dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
+ timeSetter.call(date, int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0));
+ return date;
+ }
+ return string;
+ }
+
+
+ return function(date, format) {
+ var text = '',
+ parts = [],
+ fn, match;
+
+ format = format || 'mediumDate';
+ format = $locale.DATETIME_FORMATS[format] || format;
+ if (isString(date)) {
+ if (NUMBER_STRING.test(date)) {
+ date = int(date);
+ } else {
+ date = jsonStringToDate(date);
+ }
+ }
+
+ if (isNumber(date)) {
+ date = new Date(date);
+ }
+
+ if (!isDate(date)) {
+ return date;
+ }
+
+ while(format) {
+ match = DATE_FORMATS_SPLIT.exec(format);
+ if (match) {
+ parts = concat(parts, match, 1);
+ format = parts.pop();
+ } else {
+ parts.push(format);
+ format = null;
+ }
+ }
+
+ forEach(parts, function(value){
+ fn = DATE_FORMATS[value];
+ text += fn ? fn(date, $locale.DATETIME_FORMATS)
+ : value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
+ });
+
+ return text;
+ };
+ }
+
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:json
+ * @function
+ *
+ * @description
+ * Allows you to convert a JavaScript object into JSON string.
+ *
+ * This filter is mostly useful for debugging. When using the double curly {{value}} notation
+ * the binding is automatically converted to JSON.
+ *
+ * @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
+ * @returns {string} JSON string.
+ *
+ *
+ * @example:
+
+
+ {{ {'name':'value'} | json }}
+
+
+ it('should jsonify filtered objects', function() {
+ expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/);
+ });
+
+
+ *
+ */
+ function jsonFilter() {
+ return function(object) {
+ return toJson(object, true);
+ };
+ }
+
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:lowercase
+ * @function
+ * @description
+ * Converts string to lowercase.
+ * @see angular.lowercase
+ */
+ var lowercaseFilter = valueFn(lowercase);
+
+
+ /**
+ * @ngdoc filter
+ * @name ng.filter:uppercase
+ * @function
+ * @description
+ * Converts string to uppercase.
+ * @see angular.uppercase
+ */
+ var uppercaseFilter = valueFn(uppercase);
+
+ /**
+ * @ngdoc function
+ * @name ng.filter:limitTo
+ * @function
+ *
+ * @description
+ * Creates a new array or string containing only a specified number of elements. The elements
+ * are taken from either the beginning or the end of the source array or string, as specified by
+ * the value and sign (positive or negative) of `limit`.
+ *
+ * Note: This function is used to augment the `Array` type in Angular expressions. See
+ * {@link ng.$filter} for more information about Angular arrays.
+ *
+ * @param {Array|string} input Source array or string to be limited.
+ * @param {string|number} limit The length of the returned array or string. If the `limit` number
+ * is positive, `limit` number of items from the beginning of the source array/string are copied.
+ * If the number is negative, `limit` number of items from the end of the source array/string
+ * are copied. The `limit` will be trimmed if it exceeds `array.length`
+ * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
+ * had less than `limit` elements.
+ *
+ * @example
+
+
+
+
+ Limit {{numbers}} to:
+
Output numbers: {{ numbers | limitTo:numLimit }}
+ Limit {{letters}} to:
+
Output letters: {{ letters | limitTo:letterLimit }}
+
+
+
+ it('should limit the number array to first three items', function() {
+ expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3');
+ expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3');
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('abc');
+ });
+
+ it('should update the output when -3 is entered', function() {
+ input('numLimit').enter(-3);
+ input('letterLimit').enter(-3);
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('ghi');
+ });
+
+ it('should not exceed the maximum size of input array', function() {
+ input('numLimit').enter(100);
+ input('letterLimit').enter(100);
+ expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]');
+ expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi');
+ });
+
+
+ */
+ function limitToFilter(){
+ return function(input, limit) {
+ if (!isArray(input) && !isString(input)) return input;
+
+ limit = int(limit);
+
+ if (isString(input)) {
+ //NaN check on limit
+ if (limit) {
+ return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
+ } else {
+ return "";
+ }
+ }
+
+ var out = [],
+ i, n;
+
+ // if abs(limit) exceeds maximum length, trim it
+ if (limit > input.length)
+ limit = input.length;
+ else if (limit < -input.length)
+ limit = -input.length;
+
+ if (limit > 0) {
+ i = 0;
+ n = limit;
+ } else {
+ i = input.length + limit;
+ n = input.length;
+ }
+
+ for (; i} expression A predicate to be
+ * used by the comparator to determine the order of elements.
+ *
+ * Can be one of:
+ *
+ * - `function`: Getter function. The result of this function will be sorted using the
+ * `<`, `=`, `>` operator.
+ * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
+ * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
+ * ascending or descending sort order (for example, +name or -name).
+ * - `Array`: An array of function or string predicates. The first predicate in the array
+ * is used for sorting, but when two items are equivalent, the next predicate is used.
+ *
+ * @param {boolean=} reverse Reverse the order the array.
+ * @returns {Array} Sorted copy of the source array.
+ *
+ * @example
+
+
+
+
+
Sorting predicate = {{predicate}}; reverse = {{reverse}}
+
+ [
unsorted ]
+
+
+
+
+ it('should be reverse ordered by aged', function() {
+ expect(binding('predicate')).toBe('-age');
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '29', '21', '19', '10']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']);
+ });
+
+ it('should reorder the table when user selects different predicate', function() {
+ element('.doc-example-live a:contains("Name")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.age')).
+ toEqual(['35', '10', '29', '19', '21']);
+
+ element('.doc-example-live a:contains("Phone")').click();
+ expect(repeater('table.friend', 'friend in friends').column('friend.phone')).
+ toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']);
+ expect(repeater('table.friend', 'friend in friends').column('friend.name')).
+ toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']);
+ });
+
+
+ */
+ orderByFilter.$inject = ['$parse'];
+ function orderByFilter($parse){
+ return function(array, sortPredicate, reverseOrder) {
+ if (!(array instanceof Array)) return array;
+ if (!sortPredicate) return array;
+ sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
+ sortPredicate = map(sortPredicate, function(predicate){
+ var descending = false, get = predicate || identity;
+ if (isString(predicate)) {
+ if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
+ descending = predicate.charAt(0) == '-';
+ predicate = predicate.substring(1);
+ }
+ get = $parse(predicate);
+ }
+ return reverseComparator(function(a,b){
+ return compare(get(a),get(b));
+ }, descending);
+ });
+ var arrayCopy = [];
+ for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
+ return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
+
+ function comparator(o1, o2){
+ for ( var i = 0; i < sortPredicate.length; i++) {
+ var comp = sortPredicate[i](o1, o2);
+ if (comp !== 0) return comp;
+ }
+ return 0;
+ }
+ function reverseComparator(comp, descending) {
+ return toBoolean(descending)
+ ? function(a,b){return comp(b,a);}
+ : comp;
+ }
+ function compare(v1, v2){
+ var t1 = typeof v1;
+ var t2 = typeof v2;
+ if (t1 == t2) {
+ if (t1 == "string") v1 = v1.toLowerCase();
+ if (t1 == "string") v2 = v2.toLowerCase();
+ if (v1 === v2) return 0;
+ return v1 < v2 ? -1 : 1;
+ } else {
+ return t1 < t2 ? -1 : 1;
+ }
+ }
+ }
+ }
+
+ function ngDirective(directive) {
+ if (isFunction(directive)) {
+ directive = {
+ link: directive
+ }
+ }
+ directive.restrict = directive.restrict || 'AC';
+ return valueFn(directive);
+ }
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:a
+ * @restrict E
+ *
+ * @description
+ * Modifies the default behavior of html A tag, so that the default action is prevented when href
+ * attribute is empty.
+ *
+ * The reasoning for this change is to allow easy creation of action links with `ngClick` directive
+ * without changing the location or causing page reloads, e.g.:
+ * Save
+ */
+ var htmlAnchorDirective = valueFn({
+ restrict: 'E',
+ compile: function(element, attr) {
+ // turn link into a link in IE
+ // but only if it doesn't have name attribute, in which case it's an anchor
+ if (!attr.href) {
+ attr.$set('href', '');
+ }
+
+ return function(scope, element) {
+ element.bind('click', function(event){
+ // if we have no href url, then don't navigate anywhere.
+ if (!element.attr('href')) {
+ event.preventDefault();
+ }
+ });
+ }
+ }
+ });
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngHref
+ * @restrict A
+ *
+ * @description
+ * Using Angular markup like {{hash}} in an href attribute makes
+ * the page open to a wrong URL, if the user clicks that link before
+ * angular has a chance to replace the {{hash}} with actual URL, the
+ * link will be broken and will most likely return a 404 error.
+ * The `ngHref` directive solves this problem.
+ *
+ * The buggy way to write it:
+ *
+ *
+ *
+ *
+ * The correct way to write it:
+ *
+ *
+ *
+ *
+ * @element A
+ * @param {template} ngHref any string which can contain `{{}}` markup.
+ *
+ * @example
+ * This example uses `link` variable inside `href` attribute:
+
+
+
+ link 1 (link, don't reload)
+ link 2 (link, don't reload)
+ link 3 (link, reload!)
+ anchor (link, don't reload)
+ anchor (no link)
+ link (link, change location)
+
+
+ it('should execute ng-click but not reload when href without value', function() {
+ element('#link-1').click();
+ expect(input('value').val()).toEqual('1');
+ expect(element('#link-1').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click but not reload when href empty string', function() {
+ element('#link-2').click();
+ expect(input('value').val()).toEqual('2');
+ expect(element('#link-2').attr('href')).toBe("");
+ });
+
+ it('should execute ng-click and change url when ng-href specified', function() {
+ expect(element('#link-3').attr('href')).toBe("/123");
+
+ element('#link-3').click();
+ expect(browser().window().path()).toEqual('/123');
+ });
+
+ it('should execute ng-click but not reload when href empty string and name specified', function() {
+ element('#link-4').click();
+ expect(input('value').val()).toEqual('4');
+ expect(element('#link-4').attr('href')).toBe('');
+ });
+
+ it('should execute ng-click but not reload when no href but name specified', function() {
+ element('#link-5').click();
+ expect(input('value').val()).toEqual('5');
+ expect(element('#link-5').attr('href')).toBe('');
+ });
+
+ it('should only change url when only ng-href', function() {
+ input('value').enter('6');
+ expect(element('#link-6').attr('href')).toBe('6');
+
+ element('#link-6').click();
+ expect(browser().location().url()).toEqual('/6');
+ });
+
+
+ */
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngSrc
+ * @restrict A
+ *
+ * @description
+ * Using Angular markup like `{{hash}}` in a `src` attribute doesn't
+ * work right: The browser will fetch from the URL with the literal
+ * text `{{hash}}` until Angular replaces the expression inside
+ * `{{hash}}`. The `ngSrc` directive solves this problem.
+ *
+ * The buggy way to write it:
+ *
+ *
+ *
+ *
+ * The correct way to write it:
+ *
+ *
+ *
+ *
+ * @element IMG
+ * @param {template} ngSrc any string which can contain `{{}}` markup.
+ */
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngDisabled
+ * @restrict A
+ *
+ * @description
+ *
+ * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
+ *
+ *
+ * Disabled
+ *
+ *
+ *
+ * The HTML specs do not require browsers to preserve the special attributes such as disabled.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngDisabled` directive.
+ *
+ * @example
+
+
+ Click me to toggle:
+ Button
+
+
+ it('should toggle button', function() {
+ expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy();
+ });
+
+
+ *
+ * @element INPUT
+ * @param {expression} ngDisabled Angular expression that will be evaluated.
+ */
+
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngChecked
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as checked.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngChecked` directive.
+ * @example
+
+
+ Check me to check both:
+
+
+
+ it('should check both checkBoxes', function() {
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy();
+ input('master').check();
+ expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy();
+ });
+
+
+ *
+ * @element INPUT
+ * @param {expression} ngChecked Angular expression that will be evaluated.
+ */
+
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngMultiple
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as multiple.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngMultiple` directive.
+ *
+ * @example
+
+
+ Check me check multiple:
+
+ Misko
+ Igor
+ Vojta
+ Di
+
+
+
+ it('should toggle multiple', function() {
+ expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy();
+ });
+
+
+ *
+ * @element SELECT
+ * @param {expression} ngMultiple Angular expression that will be evaluated.
+ */
+
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngReadonly
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as readonly.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngReadonly` directive.
+ * @example
+
+
+ Check me to make text readonly:
+
+
+
+ it('should toggle readonly attr', function() {
+ expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy();
+ input('checked').check();
+ expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy();
+ });
+
+
+ *
+ * @element INPUT
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngSelected
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as selected.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduced the `ngSelected` directive.
+ * @example
+
+
+ Check me to select:
+
+ Hello!
+ Greetings!
+
+
+
+ it('should select Greetings!', function() {
+ expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy();
+ input('selected').check();
+ expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy();
+ });
+
+
+ *
+ * @element OPTION
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngOpen
+ * @restrict A
+ *
+ * @description
+ * The HTML specs do not require browsers to preserve the special attributes such as open.
+ * (The presence of them means true and absence means false)
+ * This prevents the angular compiler from correctly retrieving the binding expression.
+ * To solve this problem, we introduce the `ngOpen` directive.
+ *
+ * @example
+
+
+ Check me check multiple:
+
+ Show/Hide me
+
+
+
+ it('should toggle open', function() {
+ expect(element('#details').prop('open')).toBeFalsy();
+ input('open').check();
+ expect(element('#details').prop('open')).toBeTruthy();
+ });
+
+
+ *
+ * @element DETAILS
+ * @param {string} expression Angular expression that will be evaluated.
+ */
+
+ var ngAttributeAliasDirectives = {};
+
+
+// boolean attrs are evaluated
+ forEach(BOOLEAN_ATTR, function(propName, attrName) {
+ var normalized = directiveNormalize('ng-' + attrName);
+ ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ priority: 100,
+ compile: function() {
+ return function(scope, element, attr) {
+ scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
+ attr.$set(attrName, !!value);
+ });
+ };
+ }
+ };
+ };
+ });
+
+
+// ng-src, ng-href are interpolated
+ forEach(['src', 'href'], function(attrName) {
+ var normalized = directiveNormalize('ng-' + attrName);
+ ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ priority: 99, // it needs to run after the attributes are interpolated
+ link: function(scope, element, attr) {
+ attr.$observe(normalized, function(value) {
+ if (!value)
+ return;
+
+ attr.$set(attrName, value);
+
+ // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
+ // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
+ // to set the property as well to achieve the desired effect
+ if (msie) element.prop(attrName, value);
+ });
+ }
+ };
+ };
+ });
+
+ var nullFormCtrl = {
+ $addControl: noop,
+ $removeControl: noop,
+ $setValidity: noop,
+ $setDirty: noop,
+ $setPristine: noop
+ };
+
+ /**
+ * @ngdoc object
+ * @name ng.directive:form.FormController
+ *
+ * @property {boolean} $pristine True if user has not interacted with the form yet.
+ * @property {boolean} $dirty True if user has already interacted with the form.
+ * @property {boolean} $valid True if all of the containing forms and controls are valid.
+ * @property {boolean} $invalid True if at least one containing control or form is invalid.
+ *
+ * @property {Object} $error Is an object hash, containing references to all invalid controls or
+ * forms, where:
+ *
+ * - keys are validation tokens (error names) — such as `required`, `url` or `email`),
+ * - values are arrays of controls or forms that are invalid with given error.
+ *
+ * @description
+ * `FormController` keeps track of all its controls and nested forms as well as state of them,
+ * such as being valid/invalid or dirty/pristine.
+ *
+ * Each {@link ng.directive:form form} directive creates an instance
+ * of `FormController`.
+ *
+ */
+//asks for $scope to fool the BC controller module
+ FormController.$inject = ['$element', '$attrs', '$scope'];
+ function FormController(element, attrs) {
+ var form = this,
+ parentForm = element.parent().controller('form') || nullFormCtrl,
+ invalidCount = 0, // used to easily determine if we are valid
+ errors = form.$error = {},
+ controls = [];
+
+ // init state
+ form.$name = attrs.name;
+ form.$dirty = false;
+ form.$pristine = true;
+ form.$valid = true;
+ form.$invalid = false;
+
+ parentForm.$addControl(form);
+
+ // Setup initial state of the control
+ element.addClass(PRISTINE_CLASS);
+ toggleValidCss(true);
+
+ // convenience method for easy toggling of classes
+ function toggleValidCss(isValid, validationErrorKey) {
+ validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
+ element.
+ removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
+ addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
+ }
+
+ form.$addControl = function(control) {
+ controls.push(control);
+
+ if (control.$name && !form.hasOwnProperty(control.$name)) {
+ form[control.$name] = control;
+ }
+ };
+
+ form.$removeControl = function(control) {
+ if (control.$name && form[control.$name] === control) {
+ delete form[control.$name];
+ }
+ forEach(errors, function(queue, validationToken) {
+ form.$setValidity(validationToken, true, control);
+ });
+
+ arrayRemove(controls, control);
+ };
+
+ form.$setValidity = function(validationToken, isValid, control) {
+ var queue = errors[validationToken];
+
+ if (isValid) {
+ if (queue) {
+ arrayRemove(queue, control);
+ if (!queue.length) {
+ invalidCount--;
+ if (!invalidCount) {
+ toggleValidCss(isValid);
+ form.$valid = true;
+ form.$invalid = false;
+ }
+ errors[validationToken] = false;
+ toggleValidCss(true, validationToken);
+ parentForm.$setValidity(validationToken, true, form);
+ }
+ }
+
+ } else {
+ if (!invalidCount) {
+ toggleValidCss(isValid);
+ }
+ if (queue) {
+ if (includes(queue, control)) return;
+ } else {
+ errors[validationToken] = queue = [];
+ invalidCount++;
+ toggleValidCss(false, validationToken);
+ parentForm.$setValidity(validationToken, false, form);
+ }
+ queue.push(control);
+
+ form.$valid = false;
+ form.$invalid = true;
+ }
+ };
+
+ form.$setDirty = function() {
+ element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ form.$dirty = true;
+ form.$pristine = false;
+ parentForm.$setDirty();
+ };
+
+ /**
+ * @ngdoc function
+ * @name ng.directive:form.FormController#$setPristine
+ * @methodOf ng.directive:form.FormController
+ *
+ * @description
+ * Sets the form to its pristine state.
+ *
+ * This method can be called to remove the 'ng-dirty' class and set the form to its pristine
+ * state (ng-pristine class). This method will also propagate to all the controls contained
+ * in this form.
+ *
+ * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
+ * saving or resetting it.
+ */
+ form.$setPristine = function () {
+ element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
+ form.$dirty = false;
+ form.$pristine = true;
+ forEach(controls, function(control) {
+ control.$setPristine();
+ });
+ };
+ }
+
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:ngForm
+ * @restrict EAC
+ *
+ * @description
+ * Nestable alias of {@link ng.directive:form `form`} directive. HTML
+ * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
+ * sub-group of controls needs to be determined.
+ *
+ * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into
+ * related scope, under this name.
+ *
+ */
+
+ /**
+ * @ngdoc directive
+ * @name ng.directive:form
+ * @restrict E
+ *
+ * @description
+ * Directive that instantiates
+ * {@link ng.directive:form.FormController FormController}.
+ *
+ * If `name` attribute is specified, the form controller is published onto the current scope under
+ * this name.
+ *
+ * # Alias: {@link ng.directive:ngForm `ngForm`}
+ *
+ * In angular forms can be nested. This means that the outer form is valid when all of the child
+ * forms are valid as well. However browsers do not allow nesting of `