/**
* @license AngularJS v1.0.0rc1
* (c) 2010-2012 AngularJS http://angularjs.org
* License: MIT
*/
'use strict';
(function(window, document, undefined){
////////////////////////////////////
if (typeof document.getAttribute == $undefined)
document.getAttribute = function() {};
/**
* @ngdoc function
* @name angular.lowercase
* @function
*
* @description Converts the specified string to lowercase.
* @param {string} string String to be converted to lowercase.
* @returns {string} Lowercased string.
*/
var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
/**
* @ngdoc function
* @name angular.uppercase
* @function
*
* @description Converts the specified string to uppercase.
* @param {string} string String to be converted to uppercase.
* @returns {string} Uppercased string.
*/
var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
var manualLowercase = function(s) {
return isString(s)
? s.replace(/[A-Z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) | 32);})
: s;
};
var manualUppercase = function(s) {
return isString(s)
? s.replace(/[a-z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) & ~32);})
: s;
};
// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
// with correct but slower alternatives.
if ('i' !== 'I'.toLowerCase()) {
lowercase = manualLowercase;
uppercase = manualUppercase;
}
function fromCharCode(code) {return String.fromCharCode(code);}
var $boolean = 'boolean',
$console = 'console',
$length = 'length',
$name = 'name',
$object = 'object',
$string = 'string',
$undefined = 'undefined',
Error = window.Error,
/** holds major version number for IE or NaN for real browsers */
msie = parseInt((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1], 10),
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
push = [].push,
toString = Object.prototype.toString,
/** @name angular */
angular = window.angular || (window.angular = {}),
angularModule,
/** @name angular.module.ng */
nodeName_,
uid = ['0', '0', '0'],
DATE_ISOSTRING_LN = 24;
/**
* @ngdoc function
* @name angular.forEach
* @function
*
* @description
* Invokes the `iterator` function once for each item in `obj` collection, which can be either an
* object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
* is the value of an object property or an array element and `key` is the object property key or
* array element index. Specifying a `context` for the function is optional.
*
* Note: this function was previously known as `angular.foreach`.
*
*
* @param {Object|Array} obj Object to iterate over.
* @param {Function} iterator Iterator function.
* @param {Object=} context Object to become context (`this`) for the iterator function.
* @returns {Object|Array} Reference to `obj`.
*/
function forEach(obj, iterator, context) {
var key;
if (obj) {
if (isFunction(obj)){
for (key in obj) {
if (key != 'prototype' && key != $length && key != $name && obj.hasOwnProperty(key)) {
iterator.call(context, obj[key], key);
}
}
} else if (obj.forEach && obj.forEach !== forEach) {
obj.forEach(iterator, context);
} else if (isObject(obj) && isNumber(obj.length)) {
for (key = 0; key < obj.length; key++)
iterator.call(context, obj[key], key);
} else {
for (key in obj)
iterator.call(context, obj[key], key);
}
}
return obj;
}
function sortedKeys(obj) {
var keys = [];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
keys.push(key);
}
}
return keys.sort();
}
function forEachSorted(obj, iterator, context) {
var keys = sortedKeys(obj)
for ( var i = 0; i < keys.length; i++) {
iterator.call(context, obj[keys[i]], keys[i]);
}
return keys;
}
/**
* when using forEach the params are value, key, but it is often useful to have key, value.
* @param {function(string, *)} iteratorFn
* @returns {function(*, string)}
*/
function reverseParams(iteratorFn) {
return function(value, key) { iteratorFn(key, value) };
}
/**
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
* characters such as '012ABC'. The reason why we are not using simply a number counter is that
* the number string gets longer over time, and it can also overflow, where as the the nextId
* will grow much slower, it is a string, and it will never overflow.
*
* @returns an unique alpha-numeric string
*/
function nextUid() {
var index = uid.length;
var digit;
while(index) {
index--;
digit = uid[index].charCodeAt(0);
if (digit == 57 /*'9'*/) {
uid[index] = 'A';
return uid.join('');
}
if (digit == 90 /*'Z'*/) {
uid[index] = '0';
} else {
uid[index] = String.fromCharCode(digit + 1);
return uid.join('');
}
}
uid.unshift('0');
return uid.join('');
}
/**
* @ngdoc function
* @name angular.extend
* @function
*
* @description
* Extends the destination object `dst` by copying all of the properties from the `src` object(s)
* to `dst`. You can specify multiple `src` objects.
*
* @param {Object} dst Destination object.
* @param {...Object} src Source object(s).
*/
function extend(dst) {
forEach(arguments, function(obj){
if (obj !== dst) {
forEach(obj, function(value, key){
dst[key] = value;
});
}
});
return dst;
}
function inherit(parent, extra) {
return extend(new (extend(function() {}, {prototype:parent}))(), extra);
}
/**
* @ngdoc function
* @name angular.noop
* @function
*
* @description
* A function that performs no operations. This function can be useful when writing code in the
* functional style.
function foo(callback) {
var result = calculateResult();
(callback || angular.noop)(result);
}
*/
function noop() {}
noop.$inject = [];
/**
* @ngdoc function
* @name angular.identity
* @function
*
* @description
* A function that returns its first argument. This function is useful when writing code in the
* functional style.
*
function transformer(transformationFn, value) {
return (transformationFn || identity)(value);
};
*/
function identity($) {return $;}
identity.$inject = [];
function valueFn(value) {return function() {return value;};}
/**
* @ngdoc function
* @name angular.isUndefined
* @function
*
* @description
* Determines if a reference is undefined.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is undefined.
*/
function isUndefined(value){return typeof value == $undefined;}
/**
* @ngdoc function
* @name angular.isDefined
* @function
*
* @description
* Determines if a reference is defined.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is defined.
*/
function isDefined(value){return typeof value != $undefined;}
/**
* @ngdoc function
* @name angular.isObject
* @function
*
* @description
* Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
* considered to be objects.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Object` but not `null`.
*/
function isObject(value){return value!=null && typeof value == $object;}
/**
* @ngdoc function
* @name angular.isString
* @function
*
* @description
* Determines if a reference is a `String`.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `String`.
*/
function isString(value){return typeof value == $string;}
/**
* @ngdoc function
* @name angular.isNumber
* @function
*
* @description
* Determines if a reference is a `Number`.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
function isNumber(value){return typeof value == 'number';}
/**
* @ngdoc function
* @name angular.isDate
* @function
*
* @description
* Determines if a value is a date.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Date`.
*/
function isDate(value){
return toString.apply(value) == '[object Date]';
}
/**
* @ngdoc function
* @name angular.isArray
* @function
*
* @description
* Determines if a reference is an `Array`.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Array`.
*/
function isArray(value) {
return toString.apply(value) == '[object Array]';
}
/**
* @ngdoc function
* @name angular.isFunction
* @function
*
* @description
* Determines if a reference is a `Function`.
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Function`.
*/
function isFunction(value){return typeof value == 'function';}
/**
* Checks if `obj` is a window object.
*
* @private
* @param {*} obj Object to check
* @returns {boolean} True if `obj` is a window obj.
*/
function isWindow(obj) {
return obj && obj.document && obj.location && obj.alert && obj.setInterval;
}
function isScope(obj) {
return obj && obj.$evalAsync && obj.$watch;
}
function isFile(obj) {
return toString.apply(obj) === '[object File]';
}
function isBoolean(value) {return typeof value == $boolean;}
function isTextNode(node) {return nodeName_(node) == '#text';}
function trim(value) {
return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value;
}
/**
* @ngdoc function
* @name angular.isElement
* @function
*
* @description
* Determines if a reference is a DOM element (or wrapped jQuery element).
*
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
*/
function isElement(node) {
return node &&
(node.nodeName // we are a direct element
|| (node.bind && node.find)); // we have a bind and find method part of jQuery API
}
/**
* @param str 'key1,key2,...'
* @returns {object} in the form of {key1:true, key2:true, ...}
*/
function makeMap(str){
var obj = {}, items = str.split(","), i;
for ( i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
return obj;
}
/**
* HTML class which is the only class which can be used in ng-bind to inline HTML for security
* reasons.
*
* @constructor
* @param html raw (unsafe) html
* @param {string=} option If set to 'usafe', get method will return raw (unsafe/unsanitized) html
*/
function HTML(html, option) {
this.html = html;
this.get = lowercase(option) == 'unsafe'
? valueFn(html)
: function htmlSanitize() {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf));
return buf.join('');
};
}
if (msie < 9) {
nodeName_ = function(element) {
element = element.nodeName ? element : element[0];
return (element.scopeName && element.scopeName != 'HTML')
? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName;
};
} else {
nodeName_ = function(element) {
return element.nodeName ? element.nodeName : element[0].nodeName;
};
}
function isVisible(element) {
var rect = element[0].getBoundingClientRect(),
width = (rect.width || (rect.right||0 - rect.left||0)),
height = (rect.height || (rect.bottom||0 - rect.top||0));
return width>0 && height>0;
}
function map(obj, iterator, context) {
var results = [];
forEach(obj, function(value, index, list) {
results.push(iterator.call(context, value, index, list));
});
return results;
}
/**
* @description
* Determines the number of elements in an array, the number of properties an object has, or
* the length of a string.
*
* Note: This function is used to augment the Object type in Angular expressions. See
* {@link angular.Object} for more information about Angular arrays.
*
* @param {Object|Array|string} obj Object, array, or string to inspect.
* @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
* @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
*/
function size(obj, ownPropsOnly) {
var size = 0, key;
if (isArray(obj) || isString(obj)) {
return obj.length;
} else if (isObject(obj)){
for (key in obj)
if (!ownPropsOnly || obj.hasOwnProperty(key))
size++;
}
return size;
}
function includes(array, obj) {
return indexOf(array, obj) != -1;
}
function indexOf(array, obj) {
if (array.indexOf) return array.indexOf(obj);
for ( var i = 0; i < array.length; i++) {
if (obj === array[i]) return i;
}
return -1;
}
function arrayRemove(array, value) {
var index = indexOf(array, value);
if (index >=0)
array.splice(index, 1);
return value;
}
function isLeafNode (node) {
if (node) {
switch (node.nodeName) {
case "OPTION":
case "PRE":
case "TITLE":
return true;
}
}
return false;
}
/**
* @ngdoc function
* @name angular.copy
* @function
*
* @description
* Creates a deep copy of `source`, which should be an object or an array.
*
* * If no destination is supplied, a copy of the object or array is created.
* * If a destination is provided, all of its elements (for array) or properties (for objects)
* are deleted and then all elements/properties from the source are copied to it.
* * If `source` is not an object or array, `source` is returned.
*
* Note: this function is used to augment the Object type in Angular expressions. See
* {@link angular.module.ng.$filter} for more information about Angular arrays.
*
* @param {*} source The source that will be used to make a copy.
* Can be any type, including primitives, `null`, and `undefined`.
* @param {(Object|Array)=} destination Destination into which the source is copied. If
* provided, must be of the same type as `source`.
* @returns {*} The copy or updated `destination`, if `destination` was specified.
*/
function copy(source, destination){
if (isWindow(source) || isScope(source)) throw Error("Can't copy Window or Scope");
if (!destination) {
destination = source;
if (source) {
if (isArray(source)) {
destination = copy(source, []);
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isObject(source)) {
destination = copy(source, {});
}
}
} else {
if (source === destination) throw Error("Can't copy equivalent objects or arrays");
if (isArray(source)) {
while(destination.length) {
destination.pop();
}
for ( var i = 0; i < source.length; i++) {
destination.push(copy(source[i]));
}
} else {
forEach(destination, function(value, key){
delete destination[key];
});
for ( var key in source) {
destination[key] = copy(source[key]);
}
}
}
return destination;
}
/**
* Create a shallow copy of an object
* @param src
*/
function shallowCopy(src) {
var dst = {},
key;
for(key in src) {
if (src.hasOwnProperty(key)) {
dst[key] = src[key];
}
}
return dst;
}
/**
* @ngdoc function
* @name angular.equals
* @function
*
* @description
* Determines if two objects or two values are equivalent. Supports value types, arrays and
* objects.
*
* Two objects or values are considered equivalent if at least one of the following is true:
*
* * Both objects or values pass `===` comparison.
* * Both objects or values are of the same type and all of their properties pass `===` comparison.
* * Both values are NaN. (In JavasScript, NaN == NaN => false. But we consider two NaN as equal)
*
* During a property comparision, properties of `function` type and properties with names
* that begin with `$` are ignored.
*
* Scope and DOMWindow objects are being compared only be identify (`===`).
*
* @param {*} o1 Object or value to compare.
* @param {*} o2 Object or value to compare.
* @returns {boolean} True if arguments are equal.
*/
function equals(o1, o2) {
if (o1 === o2) return true;
if (o1 === null || o2 === null) return false;
if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
if (t1 == t2) {
if (t1 == 'object') {
if (isArray(o1)) {
if ((length = o1.length) == o2.length) {
for(key=0; key 2 ? sliceArgs(arguments, 2) : [];
if (isFunction(fn) && !(fn instanceof RegExp)) {
return curryArgs.length
? function() {
return arguments.length
? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))
: fn.apply(self, curryArgs);
}
: function() {
return arguments.length
? fn.apply(self, arguments)
: fn.call(self);
};
} else {
// in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
return fn;
}
}
function toBoolean(value) {
if (value && value.length !== 0) {
var v = lowercase("" + value);
value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]');
} else {
value = false;
}
return value;
}
/**
* @returns {string} Returns the string representation of the element.
*/
function startingTag(element) {
element = jqLite(element).clone();
try {
// turns out IE does not let you set .html() on elements which
// are not allowed to have children. So we just ignore it.
element.html('');
} catch(e) {};
return jqLite('
').append(element).html().match(/^(<[^>]+>)/)[1];
}
/////////////////////////////////////////////////
/**
* Parses an escaped url query string into key-value pairs.
* @returns Object.<(string|boolean)>
*/
function parseKeyValue(/**string*/keyValue) {
var obj = {}, key_value, key;
forEach((keyValue || "").split('&'), function(keyValue){
if (keyValue) {
key_value = keyValue.split('=');
key = decodeURIComponent(key_value[0]);
obj[key] = isDefined(key_value[1]) ? decodeURIComponent(key_value[1]) : true;
}
});
return obj;
}
function toKeyValue(obj) {
var parts = [];
forEach(obj, function(value, key) {
parts.push(encodeUriQuery(key, true) + (value === true ? '' : '=' + encodeUriQuery(value, true)));
});
return parts.length ? parts.join('&') : '';
}
/**
* We need our custom mehtod because encodeURIComponent is too agressive and doesn't follow
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
* segments:
* segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* pct-encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriSegment(val) {
return encodeUriQuery(val, true).
replace(/%26/gi, '&').
replace(/%3D/gi, '=').
replace(/%2B/gi, '+');
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a custom
* method becuase encodeURIComponent is too agressive and encodes stuff that doesn't have to be
* encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace((pctEncodeSpaces ? null : /%20/g), '+');
}
/**
* @ngdoc directive
* @name angular.module.ng.$compileProvider.directive.ng-app
*
* @element ANY
* @param {angular.Module} ng-app on optional application
* {@link angular.module module} name to load.
*
* @description
*
* Use this directive to auto-bootstrap on application. Only
* one directive can be used per HTML document. The directive
* designates the root of the application and is typically placed
* ot the root of the page.
*
* In the example below if the `ng-app` directive would not be placed
* on the `html` element then the document would not be compiled
* and the `{{ 1+2 }}` would not be resolved to `3`.
*
* `ng-app` is the easiest way to bootstrap an application.
*
I can add: 1 + 2 = {{ 1+2 }}
*
*/
function angularInit(element, bootstrap) {
var elements = [element],
appElement,
module,
names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
function append(element) {
element && elements.push(element);
}
forEach(names, function(name) {
names[name] = true;
append(document.getElementById(name));
name = name.replace(':', '\\:');
if (element.querySelectorAll) {
forEach(element.querySelectorAll('.' + name), append);
forEach(element.querySelectorAll('.' + name + '\\:'), append);
forEach(element.querySelectorAll('[' + name + ']'), append);
};
});
forEach(elements, function(element) {
if (!appElement) {
var className = ' ' + element.className + ' ';
var match = NG_APP_CLASS_REGEXP.exec(className);
if (match) {
appElement = element;
module = (match[2] || '').replace(/\s+/g, ',');
} else {
forEach(element.attributes, function(attr) {
if (!appElement && names[attr.name]) {
appElement = element;
module = attr.value;
}
});
}
}
});
if (appElement) {
bootstrap(appElement, module ? [module] : []);
}
}
/**
* @ngdoc function
* @name angular.bootstrap
* @description
* Use this function to manually start up angular application.
*
* See: {@link guide/dev_guide.bootstrap.manual_bootstrap Bootstrap}
*
* @param {Element} element DOM element which is the root of angular application.
* @param {Array=} modules an array of module declarations. See: {@link angular.module modules}
* @param {angular.module.auta.$injector} the injector;
*/
function bootstrap(element, modules) {
element = jqLite(element);
modules = modules || [];
modules.unshift('ng');
var injector = createInjector(modules);
injector.invoke(
['$rootScope', '$compile', '$injector', function(scope, compile, injector){
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
return injector;
}
var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator){
separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
return (pos ? separator : '') + letter.toLowerCase();
});
}
function bindJQuery() {
// bind to jQuery if present;
jQuery = window.jQuery;
// reset to jQuery or default to us.
if (jQuery) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
JQLitePatchJQueryRemove('remove', true);
JQLitePatchJQueryRemove('empty');
JQLitePatchJQueryRemove('html');
} else {
jqLite = jqLiteWrap;
}
angular.element = jqLite;
}
/**
* throw error of the argument is falsy.
*/
function assertArg(arg, name, reason) {
if (!arg) {
var error = new Error("Argument '" + (name||'?') + "' is " +
(reason || "required"));
throw error;
}
return arg;
}
function assertArgFn(arg, name) {
assertArg(isFunction(arg), name, 'not a function, got ' +
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
return arg;
}
/**
* @ngdoc interface
* @name angular.Module
* @description
*
* Interface for configuring angular {@link angular.module modules}.
*/
function setupModuleLoader(window) {
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
return ensure(ensure(window, 'angular', Object), 'module', function() {
/** @type {Object.} */
var modules = {};
/**
* @ngdoc function
* @name angular.module
* @description
*
* The `angular.module` is a global place for creating and registering Angular modules. All
* modules (angular core or 3rd party) that should be available to an application must be
* registered using this mechanism.
*
*
* # Module
*
* A module is a collocation of services, directives, filters, and configure information. Module
* is used to configure the {@link angular.module.AUTO.$injector $injector}.
*
*
* // Create a new module
* var myModule = angular.module('myModule', []);
*
* // register a new service
* myModule.value('appName', 'MyCoolApp');
*
* // configure existing services inside initialization blocks.
* myModule.config(function($locationProvider) {
* // Configure existing providers
* $locationProvider.hashPrefix('!');
* });
*
*
* Then you can create an injector and load your modules like this:
*
*
* var injector = angular.injector(['ng', 'MyModule'])
*
*
* However it's more likely that you'll just use
* {@link angular.module.ng.$compileProvider.directive.ng-app ng-app} or
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
* @param {Array.=} requires If specified then new module is being created. If unspecified then the
* the module is being retrieved for further configuration.
* @param {Function} configFn Option configuration function for the module. Same as
* {@link angular.Module#config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
*/
return function module(name, requires, configFn) {
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
if (!requires) {
throw Error('No module: ' + name);
}
/** @type {!Array.>} */
var invokeQueue = [];
/** @type {!Array.} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke');
/** @type {angular.Module} */
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_runBlocks: runBlocks,
/**
* @ngdoc property
* @name angular.Module#requires
* @propertyOf angular.Module
* @returns {Array.} List of module names which must be loaded before this module.
* @description
* Holds the list of modules which the injector will load before the current module is loaded.
*/
requires: requires,
/**
* @ngdoc property
* @name angular.Module#name
* @propertyOf angular.Module
* @returns {string} Name of the module.
* @description
*/
name: name,
/**
* @ngdoc method
* @name angular.Module#provider
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} providerType Construction function for creating new instance of the service.
* @description
* See {@link angular.module.AUTO.$provide#provider $provide.provider()}.
*/
provider: invokeLater('$provide', 'provider'),
/**
* @ngdoc method
* @name angular.Module#factory
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} providerFunction Function for creating new instance of the service.
* @description
* See {@link angular.module.AUTO.$provide#factory $provide.factory()}.
*/
factory: invokeLater('$provide', 'factory'),
/**
* @ngdoc method
* @name angular.Module#service
* @methodOf angular.Module
* @param {string} name service name
* @param {Function} constructor A constructor function that will be instantiated.
* @description
* See {@link angular.module.AUTO.$provide#service $provide.service()}.
*/
service: invokeLater('$provide', 'service'),
/**
* @ngdoc method
* @name angular.Module#value
* @methodOf angular.Module
* @param {string} name service name
* @param {*} object Service instance object.
* @description
* See {@link angular.module.AUTO.$provide#value $provide.value()}.
*/
value: invokeLater('$provide', 'value'),
/**
* @ngdoc method
* @name angular.Module#constant
* @methodOf angular.Module
* @param {string} name constant name
* @param {*} object Constant value.
* @description
* Because the constant are fixed, they get applied before other provide methods.
* See {@link angular.module.AUTO.$provide#constant $provide.constant()}.
*/
constant: invokeLater('$provide', 'constant', 'unshift'),
/**
* @ngdoc method
* @name angular.Module#filter
* @methodOf angular.Module
* @param {string} name filter name
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link angular.module.ng.$filterProvider#register $filterProvider.register()}.
*/
filter: invokeLater('$filterProvider', 'register'),
/**
* @ngdoc method
* @name angular.Module#directive
* @methodOf angular.Module
* @param {string} name directive name
* @param {Function} directiveFactory Factory function for creating new instance of
* directives.
* @description
* See {@link angular.module.ng.$compileProvider.directive $compileProvider.directive()}.
*/
directive: invokeLater('$compileProvider', 'directive'),
/**
* @ngdoc method
* @name angular.Module#config
* @methodOf angular.Module
* @param {Function} configFn Execute this function on module load. Useful for service
* configuration.
* @description
* Use this method to register work which needs to be performed on module loading.
*/
config: config,
/**
* @ngdoc method
* @name angular.Module#run
* @methodOf angular.Module
* @param {Function} initializationFn Execute this function after injector creation.
* Useful for application initialization.
* @description
* Use this method to register work which needs to be performed when the injector with
* with the current module is finished loading.
*/
run: function(block) {
runBlocks.push(block);
return this;
}
};
if (configFn) {
config(configFn);
}
return moduleInstance;
/**
* @param {string} provider
* @param {string} method
* @param {String=} insertMethod
* @returns {angular.Module}
*/
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
}
}
});
};
});
}
/**
* @ngdoc property
* @name angular.version
* @description
* An object that contains information about the current AngularJS version. This object has the
* following properties:
*
* - `full` – `{string}` – Full version string, such as "0.9.18".
* - `major` – `{number}` – Major version number, such as "0".
* - `minor` – `{number}` – Minor version number, such as "9".
* - `dot` – `{number}` – Dot version number, such as "18".
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
full: '1.0.0rc1', // all of these placeholder strings will be replaced by rake's
major: 1, // compile task
minor: 0,
dot: 0,
codeName: 'moiré-vision'
};
function publishExternalAPI(angular){
extend(angular, {
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'equals': equals,
'element': jqLite,
'forEach': forEach,
'injector': createInjector,
'noop':noop,
'bind':bind,
'toJson': toJson,
'fromJson': fromJson,
'identity':identity,
'isUndefined': isUndefined,
'isDefined': isDefined,
'isString': isString,
'isFunction': isFunction,
'isObject': isObject,
'isNumber': isNumber,
'isElement': isElement,
'isArray': isArray,
'version': version,
'isDate': isDate,
'lowercase': lowercase,
'uppercase': uppercase,
'callbacks': {counter: 0}
});
angularModule = setupModuleLoader(window);
try {
angularModule('ngLocale');
} catch (e) {
angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
}
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
$provide.provider('$compile', $CompileProvider).
directive({
a: htmlAnchorDirective,
input: inputDirective,
textarea: inputDirective,
form: formDirective,
script: scriptDirective,
select: selectDirective,
style: styleDirective,
option: optionDirective,
ngBind: ngBindDirective,
ngBindHtml: ngBindHtmlDirective,
ngBindHtmlUnsafe: ngBindHtmlUnsafeDirective,
ngBindTemplate: ngBindTemplateDirective,
ngBindAttr: ngBindAttrDirective,
ngClass: ngClassDirective,
ngClassEven: ngClassEvenDirective,
ngClassOdd: ngClassOddDirective,
ngCloak: ngCloakDirective,
ngController: ngControllerDirective,
ngForm: ngFormDirective,
ngHide: ngHideDirective,
ngInclude: ngIncludeDirective,
ngInit: ngInitDirective,
ngNonBindable: ngNonBindableDirective,
ngPluralize: ngPluralizeDirective,
ngRepeat: ngRepeatDirective,
ngShow: ngShowDirective,
ngSubmit: ngSubmitDirective,
ngStyle: ngStyleDirective,
ngSwitch: ngSwitchDirective,
ngSwitchWhen: ngSwitchWhenDirective,
ngSwitchDefault: ngSwitchDefaultDirective,
ngOptions: ngOptionsDirective,
ngView: ngViewDirective,
ngTransclude: ngTranscludeDirective,
ngModel: ngModelDirective,
ngList: ngListDirective,
ngChange: ngChangeDirective,
ngModelInstant: ngModelInstantDirective,
required: requiredDirective,
ngRequired: requiredDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
$cookies: $CookiesProvider,
$cookieStore: $CookieStoreProvider,
$defer: $DeferProvider,
$document: $DocumentProvider,
$exceptionHandler: $ExceptionHandlerProvider,
$filter: $FilterProvider,
$interpolate: $InterpolateProvider,
$http: $HttpProvider,
$httpBackend: $HttpBackendProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
$resource: $ResourceProvider,
$route: $RouteProvider,
$routeParams: $RouteParamsProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
$sanitize: $SanitizeProvider,
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
$window: $WindowProvider
});
}
]);
};
var array = [].constructor;
/**
* @ngdoc function
* @name angular.toJson
* @function
*
* @description
* Serializes input into a JSON-formatted string.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
* @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
* @returns {string} Jsonified string representing `obj`.
*/
function toJson(obj, pretty) {
var buf = [];
toJsonArray(buf, obj, pretty ? "\n " : null, []);
return buf.join('');
}
/**
* @ngdoc function
* @name angular.fromJson
* @function
*
* @description
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
* @param {boolean} [useNative=false] Use native JSON parser, if available.
* @returns {Object|Array|Date|string|number} Deserialized thingy.
*/
function fromJson(json, useNative) {
if (!isString(json)) return json;
var obj;
if (useNative && window.JSON && window.JSON.parse) {
obj = JSON.parse(json);
} else {
obj = parseJson(json, true)();
}
return transformDates(obj);
// TODO make forEach optionally recursive and remove this function
// TODO(misko): remove this once the $http service is checked in.
function transformDates(obj) {
if (isString(obj) && obj.length === DATE_ISOSTRING_LN) {
return jsonStringToDate(obj);
} else if (isArray(obj) || isObject(obj)) {
forEach(obj, function(val, name) {
obj[name] = transformDates(val);
});
}
return obj;
}
}
var R_ISO8061_STR = /^(\d{4})-(\d\d)-(\d\d)(?:T(\d\d)(?:\:(\d\d)(?:\:(\d\d)(?:\.(\d{3}))?)?)?Z)?$/;
function jsonStringToDate(string){
var match;
if (isString(string) && (match = string.match(R_ISO8061_STR))){
var date = new Date(0);
date.setUTCFullYear(match[1], match[2] - 1, match[3]);
date.setUTCHours(match[4]||0, match[5]||0, match[6]||0, match[7]||0);
return date;
}
return string;
}
function jsonDateToString(date){
if (!date) return date;
var isoString = date.toISOString ? date.toISOString() : '';
return (isoString.length==24)
? isoString
: padNumber(date.getUTCFullYear(), 4) + '-' +
padNumber(date.getUTCMonth() + 1, 2) + '-' +
padNumber(date.getUTCDate(), 2) + 'T' +
padNumber(date.getUTCHours(), 2) + ':' +
padNumber(date.getUTCMinutes(), 2) + ':' +
padNumber(date.getUTCSeconds(), 2) + '.' +
padNumber(date.getUTCMilliseconds(), 3) + 'Z';
}
function quoteUnicode(string) {
var chars = ['"'];
for ( var i = 0; i < string.length; i++) {
var code = string.charCodeAt(i);
var ch = string.charAt(i);
switch(ch) {
case '"': chars.push('\\"'); break;
case '\\': chars.push('\\\\'); break;
case '\n': chars.push('\\n'); break;
case '\f': chars.push('\\f'); break;
case '\r': chars.push(ch = '\\r'); break;
case '\t': chars.push(ch = '\\t'); break;
default:
if (32 <= code && code <= 126) {
chars.push(ch);
} else {
var encode = "000" + code.toString(16);
chars.push("\\u" + encode.substring(encode.length - 4));
}
}
}
chars.push('"');
return chars.join('');
}
function toJsonArray(buf, obj, pretty, stack) {
if (isObject(obj)) {
if (obj === window) {
buf.push('WINDOW');
return;
}
if (obj === document) {
buf.push('DOCUMENT');
return;
}
if (includes(stack, obj)) {
buf.push('RECURSION');
return;
}
stack.push(obj);
}
if (obj === null) {
buf.push('null');
} else if (obj instanceof RegExp) {
buf.push(quoteUnicode(obj.toString()));
} else if (isFunction(obj)) {
return;
} else if (isBoolean(obj)) {
buf.push('' + obj);
} else if (isNumber(obj)) {
if (isNaN(obj)) {
buf.push('null');
} else {
buf.push('' + obj);
}
} else if (isString(obj)) {
return buf.push(quoteUnicode(obj));
} else if (isObject(obj)) {
if (isArray(obj)) {
buf.push("[");
var len = obj.length;
var sep = false;
for(var i=0; i} modules A list of module functions or their aliases. See
* {@link angular.module}. The `ng` module must be explicitly added.
* @returns {function()} Injector function. See {@link angular.module.AUTO.$injector $injector}.
*
* @example
* Typical usage
*
* // create an injector
* var $injector = angular.injector(['ng']);
*
* // use the injector to kick of your application
* // use the type inference to auto inject arguments, or use implicit injection
* $injector.invoke(function($rootScope, $compile, $document){
* $compile($document)($rootScope);
* $rootScope.$digest();
* });
*
*/
/**
* @ngdoc overview
* @name angular.module.AUTO
* @description
*
* Implicit module which gets automatically added to each {@link angular.module.AUTO.$injector $injector}.
*/
var FN_ARGS = /^function\s*[^\(]*\(([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(.+?)\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function inferInjectionArgs(fn) {
assertArgFn(fn);
if (!fn.$inject) {
var args = fn.$inject = [];
var fnText = fn.toString().replace(STRIP_COMMENTS, '');
var argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, name){
args.push(name);
});
});
}
return fn.$inject;
}
///////////////////////////////////////
/**
* @ngdoc object
* @name angular.module.AUTO.$injector
* @function
*
* @description
*
* `$injector` is used to retrieve object instances as defined by
* {@link angular.module.AUTO.$provide provider}, instantiate types, invoke methods,
* and load modules.
*
* The following always holds true:
*
*
*
* # Injection Function Annotation
*
* JavaScript does not have annotations, and annotations are needed for dependency injection. The
* following ways are all valid way of annotating function with injection arguments and are equivalent.
*
*
* // inferred (only works if code not minified/obfuscated)
* $inject.invoke(function(serviceA){});
*
* // annotated
* function explicit(serviceA) {};
* explicit.$inject = ['serviceA'];
* $inject.invoke(explicit);
*
* // inline
* $inject.invoke(['serviceA', function(serviceA){}]);
*
*
* ## Inference
*
* In JavaScript calling `toString()` on a function returns the function definition. The definition can then be
* parsed and the function arguments can be extracted. *NOTE:* This does not work with minfication, and obfuscation
* tools since these tools change the argument names.
*
* ## `$inject` Annotation
* By adding a `$inject` property onto a function the injection parameters can be specified.
*
* ## Inline
* As an array of injection names, where the last item in the array is the function to call.
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$injector#get
* @methodOf angular.module.AUTO.$injector
*
* @description
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
* @return {*} The instance.
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$injector#invoke
* @methodOf angular.module.AUTO.$injector
*
* @description
* Invoke the method and supply the method arguments from the `$injector`.
*
* @param {!function} fn The function to invoke. The function arguments come form the function annotation.
* @param {Object=} self The `this` for the invoked method.
* @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
* the `$injector` is consulted.
* @return the value returned by the invoked `fn` function.
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$injector#instantiate
* @methodOf angular.module.AUTO.$injector
* @description
* Create a new instance of JS type. The method takes a constructor function invokes the new operator and supplies
* all of the arguments to the constructor function as specified by the constructor annotation.
*
* @param {function} Type Annotated constructor function.
* @param {Object=} locals Optional object. If preset then any argument names are read from this object first, before
* the `$injector` is consulted.
* @return new instance of `Type`.
*/
/**
* @ngdoc object
* @name angular.module.AUTO.$provide
*
* @description
*
* Use `$provide` to register new providers with the `$injector`. The providers are the factories for the instance.
* The providers share the same name as the instance they create with the `Provider` suffixed to them.
*
* A provider is an object with a `$get()` method. The injector calls the `$get` method to create a new instance of
* a service. The Provider can have additional methods which would allow for configuration of the provider.
*
*
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$provide#provider
* @methodOf angular.module.AUTO.$provide
* @description
*
* Register a provider for a service. The providers can be retrieved and can have additional configuration methods.
*
* @param {string} name The name of the instance. NOTE: the provider will be available under `name + 'Provider'` key.
* @param {(Object|function())} provider If the provider is:
*
* - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
* {@link angular.module.AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created.
* - `Constructor`: a new instance of the provider will be created using
* {@link angular.module.AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`.
*
* @returns {Object} registered provider instance
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$provide#factory
* @methodOf angular.module.AUTO.$provide
* @description
*
* A short hand for configuring services if only `$get` method is required.
*
* @param {string} name The name of the instance.
* @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand for
* `$provide.provider(name, {$get: $getFn})`.
* @returns {Object} registered provider instance
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$provide#service
* @methodOf angular.module.AUTO.$provide
* @description
*
* A short hand for registering service of given class.
*
* @param {string} name The name of the instance.
* @param {Function} constructor A class (constructor function) that will be instantiated.
* @returns {Object} registered provider instance
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$provide#value
* @methodOf angular.module.AUTO.$provide
* @description
*
* A short hand for configuring services if the `$get` method is a constant.
*
* @param {string} name The name of the instance.
* @param {*} value The value.
* @returns {Object} registered provider instance
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$provide#constant
* @methodOf angular.module.AUTO.$provide
* @description
*
* A constant value, but unlike {@link angular.module.AUTO.$provide#value value} it can be injected
* into configuration function (other modules) and it is not interceptable by
* {@link angular.module.AUTO.$provide#decorator decorator}.
*
* @param {string} name The name of the constant.
* @param {*} value The constant value.
* @returns {Object} registered instance
*/
/**
* @ngdoc method
* @name angular.module.AUTO.$provide#decorator
* @methodOf angular.module.AUTO.$provide
* @description
*
* Decoration of service, allows the decorator to intercept the service instance creation. The
* returned instance may be the original instance, or a new instance which delegates to the
* original instance.
*
* @param {string} name The name of the service to decorate.
* @param {function()} decorator This function will be invoked when the service needs to be
* instanciated. The function is called using the {@link angular.module.AUTO.$injector#invoke
* injector.invoke} method and is therefore fully injectable. Local injection arguments:
*
* * `$delegate` - The original service instance, which can be monkey patched, configured,
* decorated or delegated to.
*/
function createInjector(modulesToLoad) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap(),
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = createInternalInjector(providerCache, function() {
throw Error("Unknown provider: " + path.join(' <- '));
}),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider);
}));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
return instanceInjector;
////////////////////////////////////
// $provider
////////////////////////////////////
function supportObject(delegate) {
return function(key, value) {
if (isObject(key)) {
forEach(key, reverseParams(delegate));
} else {
return delegate(key, value);
}
}
}
function provider(name, provider_) {
if (isFunction(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
throw Error('Provider ' + name + ' must define $get factory method.');
}
return providerCache[name + providerSuffix] = provider_;
}
function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
function value(name, value) { return factory(name, valueFn(value)); }
function constant(name, value) {
providerCache[name] = value;
instanceCache[name] = value;
}
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
////////////////////////////////////
// Module Loading
////////////////////////////////////
function loadModules(modulesToLoad){
var runBlocks = [];
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);
if (isString(module)) {
var moduleFn = angularModule(module);
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
try {
for(var invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
var invokeArgs = invokeQueue[i],
provider = invokeArgs[0] == '$injector'
? providerInjector
: providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
} catch (e) {
if (e.message) e.message += ' from ' + module;
throw e;
}
} else if (isFunction(module)) {
try {
runBlocks.push(providerInjector.invoke(module));
} catch (e) {
if (e.message) e.message += ' from ' + module;
throw e;
}
} else if (isArray(module)) {
try {
runBlocks.push(providerInjector.invoke(module));
} catch (e) {
if (e.message) e.message += ' from ' + String(module[module.length - 1]);
throw e;
}
} else {
assertArgFn(module, 'module');
}
});
return runBlocks;
}
////////////////////////////////////
// internal Injector
////////////////////////////////////
function createInternalInjector(cache, factory) {
function getService(serviceName) {
if (typeof serviceName !== 'string') {
throw Error('Service name expected');
}
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw Error('Circular dependency: ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
return cache[serviceName] = factory(serviceName);
} finally {
path.shift();
}
}
}
function invoke(fn, self, locals){
var args = [],
$inject,
length,
key;
if (typeof fn == 'function') {
$inject = inferInjectionArgs(fn);
length = $inject.length;
} else {
if (isArray(fn)) {
$inject = fn;
length = $inject.length - 1;
fn = $inject[length];
}
assertArgFn(fn, 'fn');
}
for(var i = 0; i < length; i++) {
key = $inject[i];
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, path)
);
}
// Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
switch (self ? -1 : args.length) {
case 0: return fn();
case 1: return fn(args[0]);
case 2: return fn(args[0], args[1]);
case 3: return fn(args[0], args[1], args[2]);
case 4: return fn(args[0], args[1], args[2], args[3]);
case 5: return fn(args[0], args[1], args[2], args[3], args[4]);
case 6: return fn(args[0], args[1], args[2], args[3], args[4], args[5]);
case 7: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
case 8: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
case 9: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
case 10: return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]);
default: return fn.apply(self, args);
}
}
function instantiate(Type, locals) {
var Constructor = function() {},
instance, returnedValue;
Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
instance = new Constructor();
returnedValue = invoke(Type, instance, locals);
return isObject(returnedValue) ? returnedValue : instance;
}
return {
invoke: invoke,
instantiate: instantiate,
get: getService
};
}
}
function Route(template, defaults) {
this.template = template = template + '#';
this.defaults = defaults || {};
var urlParams = this.urlParams = {};
forEach(template.split(/\W/), function(param){
if (param && template.match(new RegExp(":" + param + "\\W"))) {
urlParams[param] = true;
}
});
}
Route.prototype = {
url: function(params) {
var self = this,
url = this.template,
encodedVal;
params = params || {};
forEach(this.urlParams, function(_, urlParam){
encodedVal = encodeUriSegment(params[urlParam] || self.defaults[urlParam] || "");
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), encodedVal + "$1");
});
url = url.replace(/\/?#$/, '');
var query = [];
forEachSorted(params, function(value, key){
if (!self.urlParams[key]) {
query.push(encodeUriQuery(key) + '=' + encodeUriQuery(value));
}
});
url = url.replace(/\/*$/, '');
return url + (query.length ? '?' + query.join('&') : '');
}
};
function ResourceFactory($http) {
this.$http = $http;
}
ResourceFactory.DEFAULT_ACTIONS = {
'get': {method:'GET'},
'save': {method:'POST'},
'query': {method:'GET', isArray:true},
'remove': {method:'DELETE'},
'delete': {method:'DELETE'}
};
ResourceFactory.prototype = {
route: function(url, paramDefaults, actions){
var self = this;
var route = new Route(url);
actions = extend({}, ResourceFactory.DEFAULT_ACTIONS, actions);
function extractParams(data){
var ids = {};
forEach(paramDefaults || {}, function(value, key){
ids[key] = value.charAt && value.charAt(0) == '@' ? getter(data, value.substr(1)) : value;
});
return ids;
}
function Resource(value){
copy(value || {}, this);
}
forEach(actions, function(action, name){
var isPostOrPut = action.method == 'POST' || action.method == 'PUT';
Resource[name] = function(a1, a2, a3, a4) {
var params = {};
var data;
var success = noop;
var error = null;
switch(arguments.length) {
case 4:
error = a4;
success = a3;
//fallthrough
case 3:
case 2:
if (isFunction(a2)) {
if (isFunction(a1)) {
success = a1;
error = a2;
break;
}
success = a2;
error = a3;
//fallthrough
} else {
params = a1;
data = a2;
success = a3;
break;
}
case 1:
if (isFunction(a1)) success = a1;
else if (isPostOrPut) data = a1;
else params = a1;
break;
case 0: break;
default:
throw "Expected between 0-4 arguments [params, data, success, error], got " +
arguments.length + " arguments.";
}
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
self.$http({
method: action.method,
url: route.url(extend({}, extractParams(data), action.params || {}, params)),
data: data
}).then(function(response) {
var data = response.data;
if (data) {
if (action.isArray) {
value.length = 0;
forEach(data, function(item) {
value.push(new Resource(item));
});
} else {
copy(data, value);
}
}
(success||noop)(value, response.headers);
}, error);
return value;
};
Resource.bind = function(additionalParamDefaults){
return self.route(url, extend({}, paramDefaults, additionalParamDefaults), actions);
};
Resource.prototype['$' + name] = function(a1, a2, a3) {
var params = extractParams(this),
success = noop,
error;
switch(arguments.length) {
case 3: params = a1; success = a2; error = a3; break;
case 2:
case 1:
if (isFunction(a1)) {
success = a1;
error = a2;
} else {
params = a1;
success = a2 || noop;
}
case 0: break;
default:
throw "Expected between 1-3 arguments [params, success, error], got " +
arguments.length + " arguments.";
}
var data = isPostOrPut ? this : undefined;
Resource[name].call(this, params, data, success, error);
};
});
return Resource;
}
};
//////////////////////////////////
//JQLite
//////////////////////////////////
/**
* @ngdoc function
* @name angular.element
* @function
*
* @description
* Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
* `angular.element` can be either an alias for [jQuery](http://api.jquery.com/jQuery/) function, if
* jQuery is available, or a function that wraps the element or string in Angular's jQuery lite
* implementation (commonly referred to as jqLite).
*
* Real jQuery always takes precedence over jqLite, provided it was loaded before `DOMContentLoaded`
* event fired.
*
* jqLite is a tiny, API-compatible subset of jQuery that allows
* Angular to manipulate the DOM. jqLite implements only the most commonly needed functionality
* within a very small footprint, so only a subset of the jQuery API - methods, arguments and
* invocation styles - are supported.
*
* Note: All element references in Angular are always wrapped with jQuery or jqLite; they are never
* raw DOM references.
*
* ## Angular's jQuery lite provides the following methods:
*
* - [addClass()](http://api.jquery.com/addClass/)
* - [after()](http://api.jquery.com/after/)
* - [append()](http://api.jquery.com/append/)
* - [attr()](http://api.jquery.com/attr/)
* - [bind()](http://api.jquery.com/bind/)
* - [children()](http://api.jquery.com/children/)
* - [clone()](http://api.jquery.com/clone/)
* - [contents()](http://api.jquery.com/contents/)
* - [css()](http://api.jquery.com/css/)
* - [data()](http://api.jquery.com/data/)
* - [eq()](http://api.jquery.com/eq/)
* - [find()](http://api.jquery.com/find/) - Limited to lookups by tag name.
* - [hasClass()](http://api.jquery.com/hasClass/)
* - [html()](http://api.jquery.com/html/)
* - [next()](http://api.jquery.com/next/)
* - [parent()](http://api.jquery.com/parent/)
* - [prepend()](http://api.jquery.com/prepend/)
* - [prop()](http://api.jquery.com/prop/)
* - [ready()](http://api.jquery.com/ready/)
* - [remove()](http://api.jquery.com/remove/)
* - [removeAttr()](http://api.jquery.com/removeAttr/)
* - [removeClass()](http://api.jquery.com/removeClass/)
* - [removeData()](http://api.jquery.com/removeData/)
* - [replaceWith()](http://api.jquery.com/replaceWith/)
* - [text()](http://api.jquery.com/text/)
* - [toggleClass()](http://api.jquery.com/toggleClass/)
* - [unbind()](http://api.jquery.com/unbind/)
* - [val()](http://api.jquery.com/val/)
* - [wrap()](http://api.jquery.com/wrap/)
*
* ## In addtion to the above, Angular privides an additional method to both jQuery and jQuery lite:
*
* - `scope()` - retrieves the current Angular scope of the element.
* - `injector()` - retrieves the Angular injector associated with application that the element is
* part of.
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
* @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
* @returns {Object} jQuery object.
*/
var jqCache = {},
jqName = 'ng-' + new Date().getTime(),
jqId = 1,
addEventListenerFn = (window.document.addEventListener
? function(element, type, fn) {element.addEventListener(type, fn, false);}
: function(element, type, fn) {element.attachEvent('on' + type, fn);}),
removeEventListenerFn = (window.document.removeEventListener
? function(element, type, fn) {element.removeEventListener(type, fn, false); }
: function(element, type, fn) {element.detachEvent('on' + type, fn); });
function jqNextId() { return (jqId++); }
function getStyle(element) {
var current = {}, style = element[0].style, value, name, i;
if (typeof style.length == 'number') {
for(i = 0; i < style.length; i++) {
name = style[i];
current[name] = style[name];
}
} else {
for (name in style) {
value = style[name];
if (1*name != name && name != 'cssText' && value && typeof value == 'string' && value !='false')
current[name] = value;
}
}
return current;
}
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
/**
* Converts snake_case to camelCase.
* Also there is special case for Moz prefix starting with upper case letter.
* @param name Name to normalize
*/
function camelCase(name) {
return name.
replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
return offset ? letter.toUpperCase() : letter;
}).
replace(MOZ_HACK_REGEXP, 'Moz$1');
}
/////////////////////////////////////////////
// jQuery mutation patch
//
// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a
// $destroy event on all DOM nodes being removed.
//
/////////////////////////////////////////////
function JQLitePatchJQueryRemove(name, dispatchThis) {
var originalJqFn = jQuery.fn[name];
originalJqFn = originalJqFn.$original || originalJqFn;
removePatch.$original = originalJqFn;
jQuery.fn[name] = removePatch;
function removePatch() {
var list = [this],
fireEvent = dispatchThis,
set, setIndex, setLength,
element, childIndex, childLength, children,
fns, data;
while(list.length) {
set = list.shift();
for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) {
element = jqLite(set[setIndex]);
if (fireEvent) {
data = element.data('events');
if ( (fns = data && data.$destroy) ) {
forEach(fns, function(fn){
fn.handler();
});
}
} else {
fireEvent = !fireEvent;
}
for(childIndex = 0, childLength = (children = element.children()).length;
childIndex < childLength;
childIndex++) {
list.push(jQuery(children[childIndex]));
}
}
}
return originalJqFn.apply(this, arguments);
}
}
/////////////////////////////////////////////
function jqLiteWrap(element) {
if (isString(element) && element.charAt(0) != '<') {
throw new Error('selectors not implemented');
}
return new JQLite(element);
}
function JQLite(element) {
if (element instanceof JQLite) {
return element;
} else if (isString(element)) {
var div = document.createElement('div');
// Read about the NoScope elements here:
// http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx
div.innerHTML = '
' + element; // IE insanity to make NoScope elements work!
div.removeChild(div.firstChild); // remove the superfluous div
JQLiteAddNodes(this, div.childNodes);
this.remove(); // detach the elements from the temporary DOM div.
} else {
JQLiteAddNodes(this, element);
}
}
function JQLiteClone(element) {
return element.cloneNode(true);
}
function JQLiteDealoc(element){
JQLiteRemoveData(element);
for ( var i = 0, children = element.childNodes || []; i < children.length; i++) {
JQLiteDealoc(children[i]);
}
}
function JQLiteRemoveData(element) {
var cacheId = element[jqName],
cache = jqCache[cacheId];
if (cache) {
if (cache.bind) {
forEach(cache.bind, function(fn, type){
if (type == '$destroy') {
fn({});
} else {
removeEventListenerFn(element, type, fn);
}
});
}
delete jqCache[cacheId];
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
}
}
function JQLiteData(element, key, value) {
var cacheId = element[jqName],
cache = jqCache[cacheId || -1];
if (isDefined(value)) {
if (!cache) {
element[jqName] = cacheId = jqNextId();
cache = jqCache[cacheId] = {};
}
cache[key] = value;
} else {
return cache ? cache[key] : null;
}
}
function JQLiteHasClass(element, selector) {
return ((" " + element.className + " ").replace(/[\n\t]/g, " ").
indexOf( " " + selector + " " ) > -1);
}
function JQLiteRemoveClass(element, selector) {
if (selector) {
forEach(selector.split(' '), function(cssClass) {
element.className = trim(
(" " + element.className + " ")
.replace(/[\n\t]/g, " ")
.replace(" " + trim(cssClass) + " ", " ")
);
});
}
}
function JQLiteAddClass(element, selector) {
if (selector) {
forEach(selector.split(' '), function(cssClass) {
if (!JQLiteHasClass(element, cssClass)) {
element.className = trim(element.className + ' ' + trim(cssClass));
}
});
}
}
function JQLiteAddNodes(root, elements) {
if (elements) {
elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements))
? elements
: [ elements ];
for(var i=0; i < elements.length; i++) {
root.push(elements[i]);
}
}
}
//////////////////////////////////////////
// Functions which are declared directly.
//////////////////////////////////////////
var JQLitePrototype = JQLite.prototype = {
ready: function(fn) {
var fired = false;
function trigger() {
if (fired) return;
fired = true;
fn();
}
this.bind('DOMContentLoaded', trigger); // works for modern browsers and IE9
// we can not use jqLite since we are not done loading and jQuery could be loaded later.
jqLiteWrap(window).bind('load', trigger); // fallback to window.onload for others
},
toString: function() {
var value = [];
forEach(this, function(e){ value.push('' + e);});
return '[' + value.join(', ') + ']';
},
eq: function(index) {
return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
},
length: 0,
push: push,
sort: [].sort,
splice: [].splice
};
//////////////////////////////////////////
// Functions iterating getter/setters.
// these functions return self on setter and
// value on get.
//////////////////////////////////////////
var BOOLEAN_ATTR = {};
forEach('multiple,selected,checked,disabled,readOnly,required'.split(','), function(value) {
BOOLEAN_ATTR[lowercase(value)] = value;
});
var BOOLEAN_ELEMENTS = {};
forEach('input,select,option,textarea,button'.split(','), function(value) {
BOOLEAN_ELEMENTS[uppercase(value)] = true;
});
function isBooleanAttr(element, name) {
return BOOLEAN_ELEMENTS[element.nodeName] && BOOLEAN_ATTR[name.toLowerCase()];
}
forEach({
data: JQLiteData,
inheritedData: function(element, name, value) {
element = jqLite(element);
while (element.length) {
if (value = element.data(name)) return value;
element = element.parent();
}
},
scope: function(element) {
return jqLite(element).inheritedData('$scope');
},
injector: function(element) {
return jqLite(element).inheritedData('$injector');
},
removeAttr: function(element,name) {
element.removeAttribute(name);
},
hasClass: JQLiteHasClass,
css: function(element, name, value) {
name = camelCase(name);
if (isDefined(value)) {
element.style[name] = value;
} else {
var val;
if (msie <= 8) {
// this is some IE specific weirdness that jQuery 1.6.4 does not sure why
val = element.currentStyle && element.currentStyle[name];
if (val === '') val = 'auto';
}
val = val || element.style[name];
if (msie <= 8) {
// jquery weirdness :-/
val = (val === '') ? undefined : val;
}
return val;
}
},
attr: function(element, name, value){
var lowercasedName = lowercase(name);
if (BOOLEAN_ATTR[lowercasedName]) {
if (isDefined(value)) {
if (!!value) {
element[name] = true;
element.setAttribute(name, lowercasedName);
} else {
element[name] = false;
element.removeAttribute(lowercasedName);
}
} else {
return (element[name] ||
element.getAttribute(name) !== null &&
(msie < 9 ? element.getAttribute(name) !== '' : true))
? lowercasedName
: undefined;
}
} else if (isDefined(value)) {
element.setAttribute(name, value);
} else if (element.getAttribute) {
// the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
// some elements (e.g. Document) don't have get attribute, so return undefined
var ret = element.getAttribute(name, 2);
// normalize non-existing attributes to undefined (as jQuery)
return ret === null ? undefined : ret;
}
},
prop: function(element, name, value) {
if (isDefined(value)) {
element[name] = value;
} else {
return element[name];
}
},
text: extend((msie < 9)
? function(element, value) {
if (element.nodeType == 1 /** Element */) {
if (isUndefined(value))
return element.innerText;
element.innerText = value;
} else {
if (isUndefined(value))
return element.nodeValue;
element.nodeValue = value;
}
}
: function(element, value) {
if (isUndefined(value)) {
return element.textContent;
}
element.textContent = value;
}, {$dv:''}),
val: function(element, value) {
if (isUndefined(value)) {
return element.value;
}
element.value = value;
},
html: function(element, value) {
if (isUndefined(value)) {
return element.innerHTML;
}
for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) {
JQLiteDealoc(childNodes[i]);
}
element.innerHTML = value;
}
}, function(fn, name){
/**
* Properties: writes return selection, reads return first value
*/
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
// JQLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
if (((fn.length == 2 && fn !== JQLiteHasClass) ? arg1 : arg2) === undefined) {
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
for(i=0; i < this.length; i++) {
for (key in arg1) {
fn(this[i], key, arg1[key]);
}
}
// return self for chaining
return this;
} else {
// we are a read, so read the first child.
if (this.length)
return fn(this[0], arg1, arg2);
}
} else {
// we are a write, so apply to all children
for(i=0; i < this.length; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
return this;
}
return fn.$dv;
};
});
function createEventHandler(element) {
var eventHandler = function (event) {
if (!event.preventDefault) {
event.preventDefault = function() {
event.returnValue = false; //ie
};
}
if (!event.stopPropagation) {
event.stopPropagation = function() {
event.cancelBubble = true; //ie
};
}
if (!event.target) {
event.target = event.srcElement || document;
}
if (isUndefined(event.defaultPrevented)) {
var prevent = event.preventDefault;
event.preventDefault = function() {
event.defaultPrevented = true;
prevent.call(event);
};
event.defaultPrevented = false;
}
event.isDefaultPrevented = function() {
return event.defaultPrevented;
};
forEach(eventHandler.fns, function(fn){
fn.call(element, event);
});
// Remove monkey-patched methods (IE),
// as they would cause memory leaks in IE8.
if (msie < 8) {
// IE7 does not allow to delete property on native object
event.preventDefault = null;
event.stopPropagation = null;
event.isDefaultPrevented = null;
} else {
// It shouldn't affect normal browsers (native methods are defined on prototype).
delete event.preventDefault;
delete event.stopPropagation;
delete event.isDefaultPrevented;
}
};
eventHandler.fns = [];
return eventHandler;
};
//////////////////////////////////////////
// Functions iterating traversal.
// These functions chain results into a single
// selector.
//////////////////////////////////////////
forEach({
removeData: JQLiteRemoveData,
dealoc: JQLiteDealoc,
bind: function bindFn(element, type, fn){
var bind = JQLiteData(element, 'bind');
if (!bind) JQLiteData(element, 'bind', bind = {});
forEach(type.split(' '), function(type){
var eventHandler = bind[type];
if (!eventHandler) {
if (type == 'mouseenter' || type == 'mouseleave') {
var mouseenter = bind.mouseenter = createEventHandler(element);
var mouseleave = bind.mouseleave = createEventHandler(element);
var counter = 0;
bindFn(element, 'mouseover', function(event) {
counter++;
if (counter == 1) {
event.type = 'mouseenter';
mouseenter(event);
}
});
bindFn(element, 'mouseout', function(event) {
counter --;
if (counter == 0) {
event.type = 'mouseleave';
mouseleave(event);
}
});
eventHandler = bind[type];
} else {
eventHandler = bind[type] = createEventHandler(element);
addEventListenerFn(element, type, eventHandler);
}
}
eventHandler.fns.push(fn);
});
},
unbind: function(element, type, fn) {
var bind = JQLiteData(element, 'bind');
if (!bind) return; //no listeners registered
if (isUndefined(type)) {
forEach(bind, function(eventHandler, type) {
removeEventListenerFn(element, type, eventHandler);
delete bind[type];
});
} else {
if (isUndefined(fn)) {
removeEventListenerFn(element, type, bind[type]);
delete bind[type];
} else {
arrayRemove(bind[type].fns, fn);
}
}
},
replaceWith: function(element, replaceNode) {
var index, parent = element.parentNode;
JQLiteDealoc(element);
forEach(new JQLite(replaceNode), function(node){
if (index) {
parent.insertBefore(node, index.nextSibling);
} else {
parent.replaceChild(node, element);
}
index = node;
});
},
children: function(element) {
var children = [];
forEach(element.childNodes, function(element){
if (element.nodeName != '#text')
children.push(element);
});
return children;
},
contents: function(element) {
return element.childNodes;
},
append: function(element, node) {
forEach(new JQLite(node), function(child){
if (element.nodeType === 1)
element.appendChild(child);
});
},
prepend: function(element, node) {
if (element.nodeType === 1) {
var index = element.firstChild;
forEach(new JQLite(node), function(child){
if (index) {
element.insertBefore(child, index);
} else {
element.appendChild(child);
index = child;
}
});
}
},
wrap: function(element, wrapNode) {
wrapNode = jqLite(wrapNode)[0];
var parent = element.parentNode;
if (parent) {
parent.replaceChild(wrapNode, element);
}
wrapNode.appendChild(element);
},
remove: function(element) {
JQLiteDealoc(element);
var parent = element.parentNode;
if (parent) parent.removeChild(element);
},
after: function(element, newElement) {
var index = element, parent = element.parentNode;
forEach(new JQLite(newElement), function(node){
parent.insertBefore(node, index.nextSibling);
index = node;
});
},
addClass: JQLiteAddClass,
removeClass: JQLiteRemoveClass,
toggleClass: function(element, selector, condition) {
if (isUndefined(condition)) {
condition = !JQLiteHasClass(element, selector);
}
(condition ? JQLiteAddClass : JQLiteRemoveClass)(element, selector);
},
parent: function(element) {
var parent = element.parentNode;
return parent && parent.nodeType !== 11 ? parent : null;
},
next: function(element) {
return element.nextSibling;
},
find: function(element, selector) {
return element.getElementsByTagName(selector);
},
clone: JQLiteClone
}, function(fn, name){
/**
* chaining functions
*/
JQLite.prototype[name] = function(arg1, arg2) {
var value;
for(var i=0; i < this.length; i++) {
if (value == undefined) {
value = fn(this[i], arg1, arg2);
if (value !== undefined) {
// any function which returns a value needs to be wrapped
value = jqLite(value);
}
} else {
JQLiteAddNodes(value, fn(this[i], arg1, arg2));
}
}
return value == undefined ? this : value;
};
});
/**
* Computes a hash of an 'obj'.
* Hash of a:
* string is string
* number is number as string
* object is either result of calling $$hashKey function on the object or uniquely generated id,
* that is also assigned to the $$hashKey property of the object.
*
* @param obj
* @returns {string} hash string such that the same input will have the same hash string.
* The resulting string key is in 'type:hashKey' format.
*/
function hashKey(obj) {
var objType = typeof obj,
key;
if (objType == 'object' && obj !== null) {
if (typeof (key = obj.$$hashKey) == 'function') {
// must invoke on object to keep the right this
key = obj.$$hashKey();
} else if (key === undefined) {
key = obj.$$hashKey = nextUid();
}
} else {
key = obj;
}
return objType + ':' + key;
}
/**
* HashMap which can use objects as keys
*/
function HashMap(array){
forEach(array, this.put, this);
}
HashMap.prototype = {
/**
* Store key value pair
* @param key key to store can be any type
* @param value value to store can be any type
*/
put: function(key, value) {
this[hashKey(key)] = value;
},
/**
* @param key
* @returns the value for the key
*/
get: function(key) {
return this[hashKey(key)];
},
/**
* Remove the key/value pair
* @param key
*/
remove: function(key) {
var value = this[key = hashKey(key)];
delete this[key];
return value;
}
};
/**
* A map where multiple values can be added to the same key such that they form a queue.
* @returns {HashQueueMap}
*/
function HashQueueMap() {}
HashQueueMap.prototype = {
/**
* Same as array push, but using an array as the value for the hash
*/
push: function(key, value) {
var array = this[key = hashKey(key)];
if (!array) {
this[key] = [value];
} else {
array.push(value);
}
},
/**
* Same as array shift, but using an array as the value for the hash
*/
shift: function(key) {
var array = this[key = hashKey(key)];
if (array) {
if (array.length == 1) {
delete this[key];
return array[0];
} else {
return array.shift();
}
}
}
};
/**
* @ngdoc function
* @name angular.module.ng.$anchorScroll
* @requires $window
* @requires $location
* @requires $rootScope
*
* @description
* When called, it checks current value of `$location.hash()` and scroll to related element,
* according to rules specified in
* {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}.
*
* It also watches the `$location.hash()` and scroll whenever it changes to match any anchor.
* This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
*/
function $AnchorScrollProvider() {
var autoScrollingEnabled = true;
this.disableAutoScrolling = function() {
autoScrollingEnabled = false;
};
this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
var document = $window.document;
// helper function to get first anchor from a NodeList
// can't use filter.filter, as it accepts only instances of Array
// and IE can't convert NodeList to an array using [].slice
// TODO(vojta): use filter if we change it to accept lists as well
function getFirstAnchor(list) {
var result = null;
forEach(list, function(element) {
if (!result && lowercase(element.nodeName) === 'a') result = element;
});
return result;
}
function scroll() {
var hash = $location.hash(), elm;
// empty hash, scroll to the top of the page
if (!hash) $window.scrollTo(0, 0);
// element with given id
else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
// first anchor with given name :-D
else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
// no element and hash == 'top', scroll to the top of the page
else if (hash === 'top') $window.scrollTo(0, 0);
}
// does not scroll when user clicks on anchor link that is currently on
// (no url change, no $locaiton.hash() change), browser native does scroll
if (autoScrollingEnabled) {
$rootScope.$watch(function() {return $location.hash();}, function() {
$rootScope.$evalAsync(scroll);
});
}
return scroll;
}];
}
/**
* @ngdoc object
* @name angular.module.ng.$browser
* @requires $log
* @description
* This object has two goals:
*
* - hide all the global state in the browser caused by the window object
* - abstract away all the browser specific features and inconsistencies
*
* For tests we provide {@link angular.module.ngMock.$browser mock implementation} of the `$browser`
* service, which can be used for convenient testing of the application without the interaction with
* the real browser apis.
*/
/**
* @param {object} window The global window object.
* @param {object} document jQuery wrapped document.
* @param {object} body jQuery wrapped document.body.
* @param {function()} XHR XMLHttpRequest constructor.
* @param {object} $log console.log or an object with the same interface.
* @param {object} $sniffer $sniffer service
*/
function Browser(window, document, body, $log, $sniffer) {
var self = this,
rawDocument = document[0],
location = window.location,
history = window.history,
setTimeout = window.setTimeout,
clearTimeout = window.clearTimeout,
pendingDeferIds = {};
self.isMock = false;
var outstandingRequestCount = 0;
var outstandingRequestCallbacks = [];
// TODO(vojta): remove this temporary api
self.$$completeOutstandingRequest = completeOutstandingRequest;
self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
/**
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
* counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
*/
function completeOutstandingRequest(fn) {
try {
fn.apply(null, sliceArgs(arguments, 1));
} finally {
outstandingRequestCount--;
if (outstandingRequestCount === 0) {
while(outstandingRequestCallbacks.length) {
try {
outstandingRequestCallbacks.pop()();
} catch (e) {
$log.error(e);
}
}
}
}
}
/**
* @private
* Note: this method is used only by scenario runner
* TODO(vojta): prefix this method with $$ ?
* @param {function()} callback Function that will be called when no outstanding request
*/
self.notifyWhenNoOutstandingRequests = function(callback) {
// force browser to execute all pollFns - this is needed so that cookies and other pollers fire
// at some deterministic time in respect to the test runner's actions. Leaving things up to the
// regular poller would result in flaky tests.
forEach(pollFns, function(pollFn){ pollFn(); });
if (outstandingRequestCount === 0) {
callback();
} else {
outstandingRequestCallbacks.push(callback);
}
};
//////////////////////////////////////////////////////////////
// Poll Watcher API
//////////////////////////////////////////////////////////////
var pollFns = [],
pollTimeout;
/**
* @ngdoc method
* @name angular.module.ng.$browser#addPollFn
* @methodOf angular.module.ng.$browser
*
* @param {function()} fn Poll function to add
*
* @description
* Adds a function to the list of functions that poller periodically executes,
* and starts polling if not started yet.
*
* @returns {function()} the added function
*/
self.addPollFn = function(fn) {
if (isUndefined(pollTimeout)) startPoller(100, setTimeout);
pollFns.push(fn);
return fn;
};
/**
* @param {number} interval How often should browser call poll functions (ms)
* @param {function()} setTimeout Reference to a real or fake `setTimeout` function.
*
* @description
* Configures the poller to run in the specified intervals, using the specified
* setTimeout fn and kicks it off.
*/
function startPoller(interval, setTimeout) {
(function check() {
forEach(pollFns, function(pollFn){ pollFn(); });
pollTimeout = setTimeout(check, interval);
})();
}
//////////////////////////////////////////////////////////////
// URL API
//////////////////////////////////////////////////////////////
var lastBrowserUrl = location.href;
/**
* @ngdoc method
* @name angular.module.ng.$browser#url
* @methodOf angular.module.ng.$browser
*
* @description
* GETTER:
* Without any argument, this method just returns current value of location.href.
*
* SETTER:
* With at least one argument, this method sets url to new value.
* If html5 history api supported, pushState/replaceState is used, otherwise
* location.href/location.replace is used.
* Returns its own instance to allow chaining
*
* NOTE: this api is intended for use only by the $location service. Please use the
* {@link angular.module.ng.$location $location service} to change url.
*
* @param {string} url New url (when used as setter)
* @param {boolean=} replace Should new url replace current history record ?
*/
self.url = function(url, replace) {
// setter
if (url) {
lastBrowserUrl = url;
if ($sniffer.history) {
if (replace) history.replaceState(null, '', url);
else history.pushState(null, '', url);
} else {
if (replace) location.replace(url);
else location.href = url;
}
return self;
// getter
} else {
return location.href;
}
};
var urlChangeListeners = [],
urlChangeInit = false;
function fireUrlChange() {
if (lastBrowserUrl == self.url()) return;
lastBrowserUrl = self.url();
forEach(urlChangeListeners, function(listener) {
listener(self.url());
});
}
/**
* @ngdoc method
* @name angular.module.ng.$browser#onUrlChange
* @methodOf angular.module.ng.$browser
* @TODO(vojta): refactor to use node's syntax for events
*
* @description
* Register callback function that will be called, when url changes.
*
* It's only called when the url is changed by outside of angular:
* - user types different url into address bar
* - user clicks on history (forward/back) button
* - user clicks on a link
*
* It's not called when url is changed by $browser.url() method
*
* The listener gets called with new url as parameter.
*
* NOTE: this api is intended for use only by the $location service. Please use the
* {@link angular.module.ng.$location $location service} to monitor url changes in angular apps.
*
* @param {function(string)} listener Listener function to be called when url changes.
* @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous.
*/
self.onUrlChange = function(callback) {
if (!urlChangeInit) {
// We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera)
// don't fire popstate when user change the address bar and don't fire hashchange when url
// changed by push/replaceState
// html5 history api - popstate event
if ($sniffer.history) jqLite(window).bind('popstate', fireUrlChange);
// hashchange event
if ($sniffer.hashchange) jqLite(window).bind('hashchange', fireUrlChange);
// polling
else self.addPollFn(fireUrlChange);
urlChangeInit = true;
}
urlChangeListeners.push(callback);
return callback;
};
//////////////////////////////////////////////////////////////
// Cookies API
//////////////////////////////////////////////////////////////
var lastCookies = {};
var lastCookieString = '';
/**
* @ngdoc method
* @name angular.module.ng.$browser#cookies
* @methodOf angular.module.ng.$browser
*
* @param {string=} name Cookie name
* @param {string=} value Cokkie value
*
* @description
* The cookies method provides a 'private' low level access to browser cookies.
* It is not meant to be used directly, use the $cookie service instead.
*
* The return values vary depending on the arguments that the method was called with as follows:
*
*
cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it
*
cookies(name, value) -> set name to value, if value is undefined delete the cookie
*
cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)
*
*
* @returns {Object} Hash of all cookies (if called without any parameter)
*/
self.cookies = function(name, value) {
var cookieLength, cookieArray, cookie, i, keyValue, index;
if (name) {
if (value === undefined) {
rawDocument.cookie = escape(name) + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
} else {
if (isString(value)) {
rawDocument.cookie = escape(name) + '=' + escape(value);
cookieLength = name.length + value.length + 1;
if (cookieLength > 4096) {
$log.warn("Cookie '"+ name +"' possibly not set or overflowed because it was too large ("+
cookieLength + " > 4096 bytes)!");
}
if (lastCookies.length > 20) {
$log.warn("Cookie '"+ name +"' possibly not set or overflowed because too many cookies " +
"were already set (" + lastCookies.length + " > 20 )");
}
}
}
} else {
if (rawDocument.cookie !== lastCookieString) {
lastCookieString = rawDocument.cookie;
cookieArray = lastCookieString.split("; ");
lastCookies = {};
for (i = 0; i < cookieArray.length; i++) {
cookie = cookieArray[i];
index = cookie.indexOf('=');
if (index > 0) { //ignore nameless cookies
lastCookies[unescape(cookie.substring(0, index))] = unescape(cookie.substring(index + 1));
}
}
}
return lastCookies;
}
};
/**
* @ngdoc method
* @name angular.module.ng.$browser#defer
* @methodOf angular.module.ng.$browser
* @param {function()} fn A function, who's execution should be defered.
* @param {number=} [delay=0] of milliseconds to defer the function execution.
* @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`.
*
* @description
* Executes a fn asynchroniously via `setTimeout(fn, delay)`.
*
* Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using
* `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed
* via `$browser.defer.flush()`.
*
*/
self.defer = function(fn, delay) {
var timeoutId;
outstandingRequestCount++;
timeoutId = setTimeout(function() {
delete pendingDeferIds[timeoutId];
completeOutstandingRequest(fn);
}, delay || 0);
pendingDeferIds[timeoutId] = true;
return timeoutId;
};
/**
* THIS DOC IS NOT VISIBLE because ngdocs can't process docs for foo#method.method
*
* @name angular.module.ng.$browser#defer.cancel
* @methodOf angular.module.ng.$browser.defer
*
* @description
* Cancels a defered task identified with `deferId`.
*
* @param {*} deferId Token returned by the `$browser.defer` function.
* @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
*/
self.defer.cancel = function(deferId) {
if (pendingDeferIds[deferId]) {
delete pendingDeferIds[deferId];
clearTimeout(deferId);
completeOutstandingRequest(noop);
return true;
}
return false;
};
//////////////////////////////////////////////////////////////
// Misc API
//////////////////////////////////////////////////////////////
/**
* @ngdoc method
* @name angular.module.ng.$browser#addCss
* @methodOf angular.module.ng.$browser
*
* @param {string} url Url to css file
* @description
* Adds a stylesheet tag to the head.
*/
self.addCss = function(url) {
var link = jqLite(rawDocument.createElement('link'));
link.attr('rel', 'stylesheet');
link.attr('type', 'text/css');
link.attr('href', url);
body.append(link);
};
/**
* @ngdoc method
* @name angular.module.ng.$browser#addJs
* @methodOf angular.module.ng.$browser
*
* @param {string} url Url to js file
*
* @description
* Adds a script tag to the head.
*/
self.addJs = function(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');
script.type = 'text/javascript';
script.src = url;
if (msie) {
script.onreadystatechange = function() {
/loaded|complete/.test(script.readyState) && done && done();
};
} else {
if (done) script.onload = script.onerror = done;
}
body[0].appendChild(script);
return script;
};
/**
* Returns current
* (always relative - without domain)
*
* @returns {string=}
*/
self.baseHref = function() {
var href = document.find('base').attr('href');
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
};
}
function $BrowserProvider(){
this.$get = ['$window', '$log', '$sniffer', '$document',
function( $window, $log, $sniffer, $document){
return new Browser($window, $document, $document.find('body'), $log, $sniffer);
}];
}
/**
* @ngdoc object
* @name angular.module.ng.$cacheFactory
*
* @description
* Factory that constructs cache objects.
*
*
* @param {string} cacheId Name or id of the newly created cache.
* @param {object=} options Options object that specifies the cache behavior. Properties:
*
* - `{number=}` `capacity` — turns the cache into LRU cache.
*
* @returns {object} Newly created cache object with the following set of methods:
*
* - `{object}` `info()` — Returns id, size, and options of cache.
* - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache.
* - `{{*}} `get({string} key) — Returns cached value for `key` or undefined for cache miss.
* - `{void}` `remove({string} key) — Removes a key-value pair from the cache.
* - `{void}` `removeAll() — Removes all cached values.
* - `{void}` `destroy() — Removes references to this cache from $cacheFactory.
*
*/
function $CacheFactoryProvider() {
this.$get = function() {
var caches = {};
function cacheFactory(cacheId, options) {
if (cacheId in caches) {
throw Error('cacheId ' + cacheId + ' taken');
}
var size = 0,
stats = extend({}, options, {id: cacheId}),
data = {},
capacity = (options && options.capacity) || Number.MAX_VALUE,
lruHash = {},
freshEnd = null,
staleEnd = null;
return caches[cacheId] = {
put: function(key, value) {
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
refresh(lruEntry);
if (isUndefined(value)) return;
if (!(key in data)) size++;
data[key] = value;
if (size > capacity) {
this.remove(staleEnd.key);
}
},
get: function(key) {
var lruEntry = lruHash[key];
if (!lruEntry) return;
refresh(lruEntry);
return data[key];
},
remove: function(key) {
var lruEntry = lruHash[key];
if (lruEntry == freshEnd) freshEnd = lruEntry.p;
if (lruEntry == staleEnd) staleEnd = lruEntry.n;
link(lruEntry.n,lruEntry.p);
delete lruHash[key];
delete data[key];
size--;
},
removeAll: function() {
data = {};
size = 0;
lruHash = {};
freshEnd = staleEnd = null;
},
destroy: function() {
data = null;
stats = null;
lruHash = null;
delete caches[cacheId];
},
info: function() {
return extend({}, stats, {size: size});
}
};
/**
* makes the `entry` the freshEnd of the LRU linked list
*/
function refresh(entry) {
if (entry != freshEnd) {
if (!staleEnd) {
staleEnd = entry;
} else if (staleEnd == entry) {
staleEnd = entry.n;
}
link(entry.n, entry.p);
link(entry, freshEnd);
freshEnd = entry;
freshEnd.n = null;
}
}
/**
* bidirectionally links two entries of the LRU linked list
*/
function link(nextEntry, prevEntry) {
if (nextEntry != prevEntry) {
if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
}
}
}
cacheFactory.info = function() {
var info = {};
forEach(caches, function(cache, cacheId) {
info[cacheId] = cache.info();
});
return info;
};
cacheFactory.get = function(cacheId) {
return caches[cacheId];
};
return cacheFactory;
};
}
function $TemplateCacheProvider() {
this.$get = ['$cacheFactory', function($cacheFactory) {
return $cacheFactory('templates');
}];
}
/**
* @ngdoc function
* @name angular.module.ng.$compile
* @function
*
* @description
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
* can then be used to link {@link angular.module.ng.$rootScope.Scope scope} and the template together.
*
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
* {@link angular.module.ng.$compileProvider.directive directives}. For each match it
* executes corresponding template function and collects the
* instance functions into a single template function which is then returned.
*
* The template function can then be used once to produce the view or as it is the case with
* {@link angular.module.ng.$compileProvider.directive.ng-repeat repeater} many-times, in which
* case each call results in a view that is a DOM clone of the original template.
*
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 angular.module.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 updateh 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/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide.
*/
$CompileProvider.$inject = ['$provide'];
function $CompileProvider($provide) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/,
CONTENT_REGEXP = /\<\\>/i,
HAS_ROOT_ELEMENT = /^\<[\s\S]*\>$/,
SIDE_EFFECT_ATTRS = {};
forEach('src,href,multiple,selected,checked,disabled,readonly,required'.split(','), function(name) {
SIDE_EFFECT_ATTRS[name] = name;
SIDE_EFFECT_ATTRS[directiveNormalize('ng_' + name)] = name;
});
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',
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
$controller) {
var LOCAL_MODE = {
attribute: function(localName, mode, parentScope, scope, attr) {
scope[localName] = attr[localName];
},
evaluate: function(localName, mode, parentScope, scope, attr) {
scope[localName] = parentScope.$eval(attr[localName]);
},
bind: function(localName, mode, parentScope, scope, attr) {
var getter = $interpolate(attr[localName]);
scope.$watch(
function() { return getter(parentScope); },
function(v) { scope[localName] = v; }
);
},
accessor: function(localName, mode, parentScope, scope, attr) {
var getter = noop,
setter = noop,
exp = attr[localName];
if (exp) {
getter = $parse(exp);
setter = getter.assign || function() {
throw Error("Expression '" + exp + "' not assignable.");
};
}
scope[localName] = function(value) {
return arguments.length ? setter(parentScope, value) : getter(parentScope);
};
},
expression: function(localName, mode, parentScope, scope, attr) {
scope[localName] = function(locals) {
$parse(attr[localName])(parentScope, locals);
};
}
};
return compile;
//================================
function compile(templateElement, transcludeFn, maxPriority) {
templateElement = jqLite(templateElement);
// 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(templateElement, function(node, index){
if (node.nodeType == 3 /* text node */) {
templateElement[index] = jqLite(node).wrap('').parent()[0];
}
});
var linkingFn = compileNodes(templateElement, transcludeFn, templateElement, maxPriority);
return function(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 element = cloneConnectFn
? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!!
: templateElement;
safeAddClass(element.data('$scope', scope), 'ng-scope');
if (cloneConnectFn) cloneConnectFn(element, scope);
if (linkingFn) linkingFn(scope, element, element);
return element;
};
}
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 linkingFns = [],
directiveLinkingFn, childLinkingFn, directives, attrs, linkingFnFound;
for(var i = 0, ii = nodeList.length; i < ii; i++) {
attrs = {
$attr: {},
$normalize: directiveNormalize,
$set: attrSetter,
$observe: interpolatedAttrObserve,
$observers: {}
};
// we must always refer to nodeList[i] since the nodes can be replaced underneath us.
directives = collectDirectives(nodeList[i], [], attrs, maxPriority);
directiveLinkingFn = (directives.length)
? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, rootElement)
: null;
childLinkingFn = (directiveLinkingFn && directiveLinkingFn.terminal)
? null
: compileNodes(nodeList[i].childNodes,
directiveLinkingFn ? directiveLinkingFn.transclude : transcludeFn);
linkingFns.push(directiveLinkingFn);
linkingFns.push(childLinkingFn);
linkingFnFound = (linkingFnFound || directiveLinkingFn || childLinkingFn);
}
// return a linking function if we have found anything, null otherwise
return linkingFnFound ? linkingFn : null;
/* nodesetLinkingFn */ function linkingFn(scope, nodeList, rootElement, boundTranscludeFn) {
if (linkingFns.length != nodeList.length * 2) {
throw Error('Template changed structure!');
}
var childLinkingFn, directiveLinkingFn, node, childScope, childTransclusionFn;
for(var i=0, n=0, ii=linkingFns.length; i
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];
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 (isBooleanAttr(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)) {
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 */
match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue);
if (match) {
nName = directiveNormalize(match[1]);
if (addDirective(directives, nName, 'M', maxPriority)) {
attrs[nName] = trim(match[2]);
}
}
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} templateNode 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 linkingFn
*/
function applyDirectivesToNode(directives, templateNode, templateAttrs, transcludeFn, rootElement) {
var terminalPriority = -Number.MAX_VALUE,
preLinkingFns = [],
postLinkingFns = [],
newScopeDirective = null,
newIsolatedScopeDirective = null,
templateDirective = null,
delayedLinkingFn = null,
element = templateAttrs.$element = jqLite(templateNode),
directive,
directiveName,
template,
transcludeDirective,
childTranscludeFn = transcludeFn,
controllerDirectives,
linkingFn,
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', newIsolatedScopeDirective, directive, element);
if (isObject(directiveValue)) {
safeAddClass(element, 'ng-isolate-scope');
newIsolatedScopeDirective = directive;
}
safeAddClass(element, 'ng-scope');
newScopeDirective = newScopeDirective || directive;
}
directiveName = directive.name;
if (directiveValue = directive.controller) {
controllerDirectives = controllerDirectives || {};
assertNoDuplicate("'" + directiveName + "' controller",
controllerDirectives[directiveName], directive, element);
controllerDirectives[directiveName] = directive;
}
if (directiveValue = directive.transclude) {
assertNoDuplicate('transclusion', transcludeDirective, directive, element);
transcludeDirective = directive;
terminalPriority = directive.priority;
if (directiveValue == 'element') {
template = jqLite(templateNode);
templateNode = (element = templateAttrs.$element = jqLite(
''))[0];
template.replaceWith(templateNode);
childTranscludeFn = compile(template, transcludeFn, terminalPriority);
} else {
template = jqLite(JQLiteClone(templateNode));
element.html(''); // clear contents
childTranscludeFn = compile(template.contents(), transcludeFn);
}
}
if (directiveValue = directive.template) {
assertNoDuplicate('template', templateDirective, directive, element);
templateDirective = directive;
// include the contents of the original element into the template and replace the element
var content = directiveValue.replace(CONTENT_REGEXP, element.html());
templateNode = jqLite(content)[0];
if (directive.replace) {
replaceWith(rootElement, element, templateNode);
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(
templateNode,
directives.splice(i + 1, directives.length - (i + 1)),
newTemplateAttrs
)
);
mergeTemplateAttributes(templateAttrs, newTemplateAttrs);
ii = directives.length;
} else {
element.html(content);
}
}
if (directive.templateUrl) {
assertNoDuplicate('template', templateDirective, directive, element);
templateDirective = directive;
delayedLinkingFn = compileTemplateUrl(directives.splice(i, directives.length - i),
/* directiveLinkingFn */ compositeLinkFn, element, templateAttrs, rootElement,
directive.replace, childTranscludeFn);
ii = directives.length;
} else if (directive.compile) {
try {
linkingFn = directive.compile(element, templateAttrs, childTranscludeFn);
if (isFunction(linkingFn)) {
addLinkingFns(null, linkingFn);
} else if (linkingFn) {
addLinkingFns(linkingFn.pre, linkingFn.post);
}
} catch (e) {
$exceptionHandler(e, startingTag(element));
}
}
if (directive.terminal) {
compositeLinkFn.terminal = true;
terminalPriority = Math.max(terminalPriority, directive.priority);
}
}
linkingFn = delayedLinkingFn || compositeLinkFn;
linkingFn.scope = newScopeDirective && newScopeDirective.scope;
linkingFn.transclude = transcludeDirective && childTranscludeFn;
// if we have templateUrl, then we have to delay linking
return linkingFn;
////////////////////
function addLinkingFns(pre, post) {
if (pre) {
pre.require = directive.require;
preLinkingFns.push(pre);
}
if (post) {
post.require = directive.require;
postLinkingFns.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;
}
/* directiveLinkingFn */
function compositeLinkFn(/* nodesetLinkingFn */ childLinkingFn,
scope, linkNode, rootElement, boundTranscludeFn) {
var attrs, element, i, ii, linkingFn, controller;
if (templateNode === linkNode) {
attrs = templateAttrs;
} else {
attrs = shallowCopy(templateAttrs);
attrs.$element = jqLite(linkNode);
}
element = attrs.$element;
if (newScopeDirective && isObject(newScopeDirective.scope)) {
forEach(newScopeDirective.scope, function(mode, name) {
(LOCAL_MODE[mode] || wrongMode)(name, mode,
scope.$parent || scope, scope, attrs);
});
}
if (controllerDirectives) {
forEach(controllerDirectives, function(directive) {
var locals = {
$scope: scope,
$element: element,
$attrs: attrs,
$transclude: boundTranscludeFn
};
forEach(directive.inject || {}, function(mode, name) {
(LOCAL_MODE[mode] || wrongMode)(name, mode,
newScopeDirective ? scope.$parent || scope : scope, locals, attrs);
});
controller = directive.controller;
if (controller == '@') {
controller = attrs[directive.name];
}
element.data(
'$' + directive.name + 'Controller',
$controller(controller, locals));
});
}
// PRELINKING
for(i = 0, ii = preLinkingFns.length; i < ii; i++) {
try {
linkingFn = preLinkingFns[i];
linkingFn(scope, element, attrs,
linkingFn.require && getControllers(linkingFn.require, element));
} catch (e) {
$exceptionHandler(e, startingTag(element));
}
}
// RECURSION
childLinkingFn && childLinkingFn(scope, linkNode.childNodes, undefined, boundTranscludeFn);
// POSTLINKING
for(i = 0, ii = postLinkingFns.length; i < ii; i++) {
try {
linkingFn = postLinkingFns[i];
linkingFn(scope, element, attrs,
linkingFn.require && getControllers(linkingFn.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) != '$') {
dst.$set(key, value, srcAttr[key]);
}
});
// copy the new attributes on the old attrs object
forEach(src, function(value, key) {
if (key == 'class') {
safeAddClass(element, 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, /* directiveLinkingFn */ beforeWidgetLinkFn,
tElement, tAttrs, rootElement, replace, transcludeFn) {
var linkQueue = [],
afterWidgetLinkFn,
afterWidgetChildrenLinkFn,
originalWidgetNode = tElement[0],
asyncWidgetDirective = directives.shift(),
// The fact that we have to copy and patch the directive seems wrong!
syncWidgetDirective = extend({}, asyncWidgetDirective, {templateUrl:null, transclude:null}),
html = tElement.html();
tElement.html('');
$http.get(asyncWidgetDirective.templateUrl, {cache: $templateCache}).
success(function(content) {
content = trim(content).replace(CONTENT_REGEXP, html);
if (replace && !content.match(HAS_ROOT_ELEMENT)) {
throw Error('Template must have exactly one root element: ' + content);
}
var templateNode, tempTemplateAttrs;
if (replace) {
tempTemplateAttrs = {$attr: {}};
templateNode = jqLite(content)[0];
replaceWith(rootElement, tElement, templateNode);
collectDirectives(tElement[0], directives, tempTemplateAttrs);
mergeTemplateAttributes(tAttrs, tempTemplateAttrs);
} else {
templateNode = tElement[0];
tElement.html(content);
}
directives.unshift(syncWidgetDirective);
afterWidgetLinkFn = /* directiveLinkingFn */ applyDirectivesToNode(directives, tElement, tAttrs, transcludeFn);
afterWidgetChildrenLinkFn = /* nodesetLinkingFn */ compileNodes(tElement.contents(), transcludeFn);
while(linkQueue.length) {
var controller = linkQueue.pop(),
linkRootElement = linkQueue.pop(),
cLinkNode = linkQueue.pop(),
scope = linkQueue.pop(),
node = templateNode;
if (cLinkNode !== originalWidgetNode) {
// it was cloned therefore we have to clone as well.
node = JQLiteClone(templateNode);
replaceWith(linkRootElement, jqLite(cLinkNode), node);
}
afterWidgetLinkFn(function() {
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, scope, node, rootElement, controller);
}, scope, node, rootElement, controller);
}
linkQueue = null;
}).
error(function(response, code, headers, config) {
throw Error('Failed to load template: ' + config.url);
});
return /* directiveLinkingFn */ function(ignoreChildLinkingFn, scope, node, rootElement,
controller) {
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
linkQueue.push(rootElement);
linkQueue.push(controller);
} else {
afterWidgetLinkFn(function() {
beforeWidgetLinkFn(afterWidgetChildrenLinkFn, 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(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
bindings.push(interpolateFn);
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
scope.$watch(interpolateFn, function(value) {
node[0].nodeValue = value;
});
})
});
}
}
function addAttrInterpolateDirective(node, directives, value, name) {
var interpolateFn = $interpolate(value, true);
if (SIDE_EFFECT_ATTRS[name]) {
name = SIDE_EFFECT_ATTRS[name];
if (isBooleanAttr(node, name)) {
value = true;
}
} else if (!interpolateFn) {
// we are not a side-effect attr, and we have no side-effects -> ignore
return;
}
directives.push({
priority: 100,
compile: function(element, attr) {
if (interpolateFn) {
return function(scope, element, attr) {
// we define observers array only for interpolated attrs
// and ignore observers for non interpolated attrs to save some memory
attr.$observers[name] = [];
attr[name] = undefined;
scope.$watch(interpolateFn, function(value) {
attr.$set(name, value);
});
};
} else {
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
* // 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 angular.module.ng.$filterProvider#register
* @methodOf angular.module.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 angular.module.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('linky', linkyFilter);
register('lowercase', lowercaseFilter);
register('number', numberFilter);
register('orderBy', orderByFilter);
register('uppercase', uppercaseFilter);
}
/**
* @ngdoc filter
* @name angular.module.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 angular.module.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 angular.module.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
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 angular.module.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 = [];
if (numStr.indexOf('e') !== -1) {
formatedText = numStr;
} else {
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 angular.module.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/dev_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 ISO 8601 extended datetime string (yyyy-MM-ddTHH:mm:ss.SSSZ).
* @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) {
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 = parseInt(date, 10);
} 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 angular.module.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.
*
* @css ng-monospace Always applied to the encapsulating element.
*
* @example:
{{ {'name':'value'} | json }}
it('should jsonify filtered objects', function() {
expect(binding("{'name':'value'}")).toBe('{\n "name":"value"}');
});
*
*/
function jsonFilter() {
return function(object) {
return toJson(object, true);
};
}
/**
* @ngdoc filter
* @name angular.module.ng.$filter.lowercase
* @function
* @description
* Converts string to lowercase.
* @see angular.lowercase
*/
var lowercaseFilter = valueFn(lowercase);
/**
* @ngdoc filter
* @name angular.module.ng.$filter.uppercase
* @function
* @description
* Converts string to uppercase.
* @see angular.uppercase
*/
var uppercaseFilter = valueFn(uppercase);
/**
* @ngdoc filter
* @name angular.module.ng.$filter.linky
* @function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plain email address links.
*
* @param {string} text Input text.
* @returns {string} Html-linkified text.
*
* @example
Snippet:
Filter
Source
Rendered
linky filter
<div ng-bind-html="snippet | linky"> </div>
no filter
<div ng-bind="snippet"> </div>
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.');
});
*/
function linkyFilter() {
var LINKY_URL_REGEXP = /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s\.\;\,\(\)\{\}\<\>]/,
MAILTO_REGEXP = /^mailto:/;
return function(text) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var writer = htmlSanitizeWriter(html);
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
writer.chars(raw.substr(0, i));
writer.start('a', {href:url});
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
writer.end('a');
raw = raw.substring(i + match[0].length);
}
writer.chars(raw);
return html.join('');
};
};
/**
* @ngdoc function
* @name angular.module.ng.$filter.limitTo
* @function
*
* @description
* Creates a new array containing only a specified number of elements in an array. The elements
* are taken from either the beginning or the end of the source array, 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 angular.module.ng.$filter} for more information about Angular arrays.
*
* @param {Array} array Source array to be limited.
* @param {string|Number} limit The length of the returned array. If the `limit` number is
* positive, `limit` number of items from the beginning of the source array are copied.
* If the number is negative, `limit` number of items from the end of the source array are
* copied. The `limit` will be trimmed if it exceeds `array.length`
* @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit`
* elements.
*
* @example
Limit {{numbers}} to:
Output: {{ numbers | limitTo:limit | json }}
it('should limit the numer array to first three items', function() {
expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3');
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3]');
});
it('should update the output when -3 is entered', function() {
input('limit').enter(-3);
expect(binding('numbers | limitTo:limit | json')).toEqual('[7,8,9]');
});
it('should not exceed the maximum size of input array', function() {
input('limit').enter(100);
expect(binding('numbers | limitTo:limit | json')).toEqual('[1,2,3,4,5,6,7,8,9]');
});
*/
function limitToFilter(){
return function(array, limit) {
if (!(array instanceof Array)) return array;
limit = parseInt(limit, 10);
var out = [],
i, n;
// check that array is iterable
if (!array || !(array instanceof Array))
return out;
// if abs(limit) exceeds maximum length, trim it
if (limit > array.length)
limit = array.length;
else if (limit < -array.length)
limit = -array.length;
if (limit > 0) {
i = 0;
n = limit;
} else {
i = array.length + limit;
n = array.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
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;
}
}
}
}
/**
* @ngdoc function
* @name angular.module.ng.$interpolateProvider
* @function
*
* @description
*
* Used for configuring the interpolation markup. Deafults to `{{` and `}}`.
*/
function $InterpolateProvider() {
var startSymbol = '{{';
var endSymbol = '}}';
/**
* @ngdoc method
* @name angular.module.ng.$interpolateProvider#startSymbol
* @methodOf angular.module.ng.$interpolateProvider
* @description
* Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
*
* @prop {string=} value new value to set the starting symbol to.
*/
this.startSymbol = function(value){
if (value) {
startSymbol = value;
return this;
} else {
return startSymbol;
}
};
/**
* @ngdoc method
* @name angular.module.ng.$interpolateProvider#endSymbol
* @methodOf angular.module.ng.$interpolateProvider
* @description
* Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
*
* @prop {string=} value new value to set the ending symbol to.
*/
this.endSymbol = function(value){
if (value) {
endSymbol = value;
return this;
} else {
return startSymbol;
}
};
this.$get = ['$parse', function($parse) {
var startSymbolLength = startSymbol.length,
endSymbolLength = endSymbol.length;
/**
* @ngdoc function
* @name angular.module.ng.$interpolate
* @function
*
* @requires $parse
*
* @description
*
* Compiles a string with markup into an interpolation function. This service is used by the
* HTML {@link angular.module.ng.$compile $compile} service for data binding. See
* {@link angular.module.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.
*
*/
return function(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) {
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 '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) {
pathPrefix = pathPrefix || '';
/**
* Parse given html5 (regular) url string into properties
* @param {string} url HTML5 url
* @private
*/
this.$$parse = function(url) {
var match = matchUrl(url, this);
if (match.path.indexOf(pathPrefix) !== 0) {
throw 'Invalid url "' + url + '", 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.$$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) {
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 '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.$$parse(url);
}
LocationUrl.prototype = {
/**
* Has any change been replacing ?
* @private
*/
$$replace: false,
/**
* @ngdoc method
* @name angular.module.ng.$location#absUrl
* @methodOf angular.module.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}
*/
absUrl: locationGetter('$$absUrl'),
/**
* @ngdoc method
* @name angular.module.ng.$location#url
* @methodOf angular.module.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: 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 angular.module.ng.$location#protocol
* @methodOf angular.module.ng.$location
*
* @description
* This method is getter only.
*
* Return protocol of current url.
*
* @return {string}
*/
protocol: locationGetter('$$protocol'),
/**
* @ngdoc method
* @name angular.module.ng.$location#host
* @methodOf angular.module.ng.$location
*
* @description
* This method is getter only.
*
* Return host of current url.
*
* @return {string}
*/
host: locationGetter('$$host'),
/**
* @ngdoc method
* @name angular.module.ng.$location#port
* @methodOf angular.module.ng.$location
*
* @description
* This method is getter only.
*
* Return port of current url.
*
* @return {Number}
*/
port: locationGetter('$$port'),
/**
* @ngdoc method
* @name angular.module.ng.$location#path
* @methodOf angular.module.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: locationGetterSetter('$$path', function(path) {
return path.charAt(0) == '/' ? path : '/' + path;
}),
/**
* @ngdoc method
* @name angular.module.ng.$location#search
* @methodOf angular.module.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: function(search, paramValue) {
if (isUndefined(search))
return this.$$search;
if (isDefined(paramValue)) {
if (paramValue === null) {
delete this.$$search[search];
} else {
this.$$search[search] = encodeUriQuery(paramValue);
}
} else {
this.$$search = isString(search) ? parseKeyValue(search) : search;
}
this.$$compose();
return this;
},
/**
* @ngdoc method
* @name angular.module.ng.$location#hash
* @methodOf angular.module.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: locationGetterSetter('$$hash', identity),
/**
* @ngdoc method
* @name angular.module.ng.$location#replace
* @methodOf angular.module.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 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 angular.module.ng.$location
*
* @requires $browser
* @requires $sniffer
* @requires $document
*
* @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 angular.module.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 angular.module.ng.$locationProvider#hashPrefix
* @methodOf angular.module.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 angular.module.ng.$locationProvider#html5Mode
* @methodOf angular.module.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', '$document',
function( $rootScope, $browser, $sniffer, $document) {
var currentUrl,
basePath = $browser.baseHref() || '/',
pathPrefix = pathPrefixFromBase(basePath),
initUrl = $browser.url();
if (html5Mode) {
if ($sniffer.history) {
currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix);
} else {
currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix),
hashPrefix);
}
// link rewriting
var u = currentUrl,
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
$document.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 (elm.length && lowercase(elm[0].nodeName) !== 'a') {
elm = elm.parent();
}
var href = elm.attr('href');
if (!href || isDefined(elm.attr('ng-ext-link')) || elm.attr('target')) return;
// remove same domain from full url links (IE7 always returns full hrefs)
href = href.replace(absUrlPrefix, '');
// link to different domain (or base path)
if (href.substr(0, 4) == 'http') return;
// remove pathPrefix from absolute links
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
currentUrl.url(href);
$rootScope.$apply();
event.preventDefault();
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window.angular['ff-684208-preventDefault'] = true;
});
} else {
currentUrl = new LocationHashbangUrl(initUrl, hashPrefix);
}
// rewrite hashbang url <> html5 url
if (currentUrl.absUrl() != initUrl) {
$browser.url(currentUrl.absUrl(), true);
}
// update $location when $browser url changes
$browser.onUrlChange(function(newUrl) {
if (currentUrl.absUrl() != newUrl) {
$rootScope.$evalAsync(function() {
currentUrl.$$parse(newUrl);
});
if (!$rootScope.$$phase) $rootScope.$digest();
}
});
// update browser
var changeCounter = 0;
$rootScope.$watch(function $locationWatch() {
if ($browser.url() != currentUrl.absUrl()) {
changeCounter++;
$rootScope.$evalAsync(function() {
$browser.url(currentUrl.absUrl(), currentUrl.$$replace);
currentUrl.$$replace = false;
});
}
return changeCounter;
});
return currentUrl;
}];
}
/**
* @ngdoc object
* @name angular.module.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
Reload this page with open console, enter text and hit the log button...
Message:
*/
function $LogProvider(){
this.$get = ['$window', function($window){
return {
/**
* @ngdoc method
* @name angular.module.ng.$log#log
* @methodOf angular.module.ng.$log
*
* @description
* Write a log message
*/
log: consoleLog('log'),
/**
* @ngdoc method
* @name angular.module.ng.$log#warn
* @methodOf angular.module.ng.$log
*
* @description
* Write a warning message
*/
warn: consoleLog('warn'),
/**
* @ngdoc method
* @name angular.module.ng.$log#info
* @methodOf angular.module.ng.$log
*
* @description
* Write an information message
*/
info: consoleLog('info'),
/**
* @ngdoc method
* @name angular.module.ng.$log#error
* @methodOf angular.module.ng.$log
*
* @description
* Write an error message
*/
error: consoleLog('error')
};
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 || {};
var 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);
};
} else {
// we are IE, in which case there is nothing we can do
return logFn;
}
}
}];
}
/**
* @ngdoc object
* @name angular.module.ng.$resource
* @requires $http
*
* @description
* A factory which creates a resource object that lets you interact with
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) 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 {@link angular.module.ng.$http $http} service.
*
* @param {string} url A parameterized URL template with parameters prefixed by `:` as in
* `/user/:username`.
*
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods.
*
* 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).
*
* @param {Object.