var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.-]*)(:([0-9]+))?(\/[^\?#]*)?(\?([^#]*))?(#(.*))?$/, HASH_MATCH = /^([^\?]*)?(\?([^\?]*))?$/, DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}, EAGER = true; function angularServiceInject(name, fn, inject, eager) { angularService(name, fn, {$inject:inject, $eager:eager}); } /** * @workInProgress * @ngdoc service * @name angular.service.$window * * @description * Is reference to the browser's `window` object. While `window` * is globally available in JavaScript, it causes testability problems, because * it is a global variable. In angular we always refer to it through the * `$window` service, so it may be overriden, removed or mocked for testing. * * All expressions are evaluated with respect to current scope so they don't * suffer from window globality. * * @example */ angularServiceInject("$window", bind(window, identity, window), [], EAGER); /** * @workInProgress * @ngdoc service * @name angular.service.$document * @requires $window * * @description * Reference to the browser window.document, but wrapped into angular.element(). */ angularServiceInject("$document", function(window){ return jqLite(window.document); }, ['$window'], EAGER); /** * @workInProgress * @ngdoc service * @name angular.service.$location * @requires $browser * * @property {string} href * @property {string} protocol * @property {string} host * @property {number} port * @property {string} path * @property {Object.} search * @property {string} hash * @property {string} hashPath * @property {Object.} hashSearch * * @description * Parses the browser location url and makes it available to your application. * Any changes to the url are reflected into $location service and changes to * $location are reflected to url. * Notice that using browser's forward/back buttons changes the $location. * * @example clear hash | test hash
$location = {{$location}}
*/ angularServiceInject("$location", function($browser) { var scope = this, location = {update:update, updateHash: updateHash}, lastLocation = {}; $browser.onHashChange(function() { //register update($browser.getUrl()); copy(location, lastLocation); scope.$eval(); })(); //initialize this.$onEval(PRIORITY_FIRST, sync); this.$onEval(PRIORITY_LAST, updateBrowser); return location; // PUBLIC METHODS /** * @workInProgress * @ngdoc method * @name angular.service.$location#update * @methodOf angular.service.$location * * @description * Update location object * Does not immediately update the browser * Browser is updated at the end of $eval() * * @example scope.$location.update('http://www.angularjs.org/path#hash?search=x'); scope.$location.update({host: 'www.google.com', protocol: 'https'}); scope.$location.update({hashPath: '/path', hashSearch: {a: 'b', x: true}}); * * @param {(string|Object)} href Full href as a string or object with properties */ function update(href) { if (isString(href)) { extend(location, parseHref(href)); } else { if (isDefined(href.hash)) { extend(href, isString(href.hash) ? parseHash(href.hash) : href.hash); } extend(location, href); if (isDefined(href.hashPath || href.hashSearch)) { location.hash = composeHash(location); } location.href = composeHref(location); } } /** * @workInProgress * @ngdoc method * @name angular.service.$location#updateHash * @methodOf angular.service.$location * * @description * Update location hash part * @see update() * * @example scope.$location.updateHash('/hp') ==> update({hashPath: '/hp'}) scope.$location.updateHash({a: true, b: 'val'}) ==> update({hashSearch: {a: true, b: 'val'}}) scope.$location.updateHash('/hp', {a: true}) ==> update({hashPath: '/hp', hashSearch: {a: true}}) * * @param {(string|Object)} path A hashPath or hashSearch object * @param {Object=} search A hashSearch object */ function updateHash(path, search) { var hash = {}; if (isString(path)) { hash.hashPath = path; hash.hashSearch = search || {}; } else hash.hashSearch = path; hash.hash = composeHash(hash); update({hash: hash}); } // INNER METHODS /** * Synchronizes all location object properties. * * User is allowed to change properties, so after property change, * location object is not in consistent state. * * Properties are synced with the following precedence order: * * - `$location.href` * - `$location.hash` * - everything else * * @example *
   *   scope.$location.href = 'http://www.angularjs.org/path#a/b'
   * 
* immediately after this call, other properties are still the old ones... * * This method checks the changes and update location to the consistent state */ function sync() { if (!equals(location, lastLocation)) { if (location.href != lastLocation.href) { update(location.href); return; } if (location.hash != lastLocation.hash) { var hash = parseHash(location.hash); updateHash(hash.hashPath, hash.hashSearch); } else { location.hash = composeHash(location); location.href = composeHref(location); } update(location.href); } } /** * If location has changed, update the browser * This method is called at the end of $eval() phase */ function updateBrowser() { sync(); if ($browser.getUrl() != location.href) { $browser.setUrl(location.href); copy(location, lastLocation); } } /** * Compose href string from a location object * * @param {Object} loc The location object with all properties * @return {string} Composed href */ function composeHref(loc) { var url = toKeyValue(loc.search); var port = (loc.port == DEFAULT_PORTS[loc.protocol] ? _null : loc.port); return loc.protocol + '://' + loc.host + (port ? ':' + port : '') + loc.path + (url ? '?' + url : '') + (loc.hash ? '#' + loc.hash : ''); } /** * Compose hash string from location object * * @param {Object} loc Object with hashPath and hashSearch properties * @return {string} Hash string */ function composeHash(loc) { var hashSearch = toKeyValue(loc.hashSearch); //TODO: temporary fix for issue #158 return escape(loc.hashPath).replace(/%21/gi, '!').replace(/%3A/gi, ':').replace(/%24/gi, '$') + (hashSearch ? '?' + hashSearch : ''); } /** * Parse href string into location object * * @param {string} href * @return {Object} The location object */ function parseHref(href) { var loc = {}; var match = URL_MATCH.exec(href); if (match) { loc.href = href.replace(/#$/, ''); loc.protocol = match[1]; loc.host = match[3] || ''; loc.port = match[5] || DEFAULT_PORTS[loc.protocol] || _null; loc.path = match[6] || ''; loc.search = parseKeyValue(match[8]); loc.hash = match[10] || ''; extend(loc, parseHash(loc.hash)); } return loc; } /** * Parse hash string into object * * @param {string} hash */ function parseHash(hash) { var h = {}; var match = HASH_MATCH.exec(hash); if (match) { h.hash = hash; h.hashPath = unescape(match[1] || ''); h.hashSearch = parseKeyValue(match[3]); } return h; } }, ['$browser']); /** * @workInProgress * @ngdoc service * @name angular.service.$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:
*/ var $logFactory; //reference to be used only in tests angularServiceInject("$log", $logFactory = function($window){ return { /** * @workInProgress * @ngdoc method * @name angular.service.$log#log * @methodOf angular.service.$log * * @description * Write a log message */ log: consoleLog('log'), /** * @workInProgress * @ngdoc method * @name angular.service.$log#warn * @methodOf angular.service.$log * * @description * Write a warning message */ warn: consoleLog('warn'), /** * @workInProgress * @ngdoc method * @name angular.service.$log#info * @methodOf angular.service.$log * * @description * Write an information message */ info: consoleLog('info'), /** * @workInProgress * @ngdoc method * @name angular.service.$log#error * @methodOf angular.service.$log * * @description * Write an error message */ error: consoleLog('error') }; 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; } } }, ['$window'], EAGER); /** * @workInProgress * @ngdoc service * @name angular.service.$exceptionHandler * @requires $log * * @description * Any uncaught exception in angular expressions is delegated to this service. * The default implementation simply delegates to `$log.error` which logs it into * the browser console. * * In unit tests, if `angular-mocks.js` is loaded, this service is overriden by * {@link angular.mock.service.$exceptionHandler mock $exceptionHandler} * * @example */ var $exceptionHandlerFactory; //reference to be used only in tests angularServiceInject('$exceptionHandler', $exceptionHandlerFactory = function($log){ return function(e) { $log.error(e); }; }, ['$log'], EAGER); /** * @workInProgress * @ngdoc service * @name angular.service.$updateView * @requires $browser * * @description * Calling `$updateView` enqueues the eventual update of the view. (Update the DOM to reflect the * model). The update is eventual, since there are often multiple updates to the model which may * be deferred. The default update delayed is 25 ms. This means that the view lags the model by * that time. (25ms is small enough that it is perceived as instantaneous by the user). The delay * can be adjusted by setting the delay property of the service. * *
angular.service('$updateView').delay = 10
* * The delay is there so that multiple updates to the model which occur sufficiently close * together can be merged into a single update. * * You don't usually call '$updateView' directly since angular does it for you in most cases, * but there are some cases when you need to call it. * * - `$updateView()` called automatically by angular: * - Your Application Controllers: Your controller code is called by angular and hence * angular is aware that you may have changed the model. * - Your Services: Your service is usually called by your controller code, hence same rules * apply. * - May need to call `$updateView()` manually: * - Widgets / Directives: If you listen to any DOM events or events on any third party * libraries, then angular is not aware that you may have changed state state of the * model, and hence you need to call '$updateView()' manually. * - 'setTimeout'/'XHR': If you call 'setTimeout' (instead of {@link angular.service.$defer}) * or 'XHR' (instead of {@link angular.service.$xhr}) then you may be changing the model * without angular knowledge and you may need to call '$updateView()' directly. * * NOTE: if you wish to update the view immediately (without delay), you can do so by calling * {@link scope.$eval} at any time from your code: *
scope.$root.$eval()
* * In unit-test mode the update is instantaneous and synchronous to simplify writing tests. * */ function serviceUpdateViewFactory($browser){ var rootScope = this; var scheduled; function update(){ scheduled = false; rootScope.$eval(); } return $browser.isMock ? update : function(){ if (!scheduled) { scheduled = true; $browser.defer(update, serviceUpdateViewFactory.delay); } }; } serviceUpdateViewFactory.delay = 25; angularServiceInject('$updateView', serviceUpdateViewFactory, ['$browser']); /** * @workInProgress * @ngdoc service * @name angular.service.$hover * @requires $browser * @requires $document * * @description * * @example */ angularServiceInject("$hover", function(browser, document) { var tooltip, self = this, error, width = 300, arrowWidth = 10, body = jqLite(document[0].body); browser.hover(function(element, show){ if (show && (error = element.attr(NG_EXCEPTION) || element.attr(NG_VALIDATION_ERROR))) { if (!tooltip) { tooltip = { callout: jqLite('
'), arrow: jqLite('
'), title: jqLite('
'), content: jqLite('
') }; tooltip.callout.append(tooltip.arrow); tooltip.callout.append(tooltip.title); tooltip.callout.append(tooltip.content); body.append(tooltip.callout); } var docRect = body[0].getBoundingClientRect(), elementRect = element[0].getBoundingClientRect(), leftSpace = docRect.right - elementRect.right - arrowWidth; tooltip.title.text(element.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."); tooltip.content.text(error); if (leftSpace < width) { tooltip.arrow.addClass('ng-arrow-right'); tooltip.arrow.css({left: (width + 1)+'px'}); tooltip.callout.css({ position: 'fixed', left: (elementRect.left - arrowWidth - width - 4) + "px", top: (elementRect.top - 3) + "px", width: width + "px" }); } else { tooltip.arrow.addClass('ng-arrow-left'); tooltip.callout.css({ position: 'fixed', left: (elementRect.right + arrowWidth) + "px", top: (elementRect.top - 3) + "px", width: width + "px" }); } } else if (tooltip) { tooltip.callout.remove(); tooltip = _null; } }); }, ['$browser', '$document'], EAGER); /** * @workInProgress * @ngdoc service * @name angular.service.$invalidWidgets * * @description * Keeps references to all invalid widgets found during validation. * Can be queried to find whether there are any invalid widgets currently displayed. * * @example */ angularServiceInject("$invalidWidgets", function(){ var invalidWidgets = []; /** Remove an element from the array of invalid widgets */ invalidWidgets.markValid = function(element){ var index = indexOf(invalidWidgets, element); if (index != -1) invalidWidgets.splice(index, 1); }; /** Add an element to the array of invalid widgets */ invalidWidgets.markInvalid = function(element){ var index = indexOf(invalidWidgets, element); if (index === -1) invalidWidgets.push(element); }; /** Return count of all invalid widgets that are currently visible */ invalidWidgets.visible = function() { var count = 0; forEach(invalidWidgets, function(widget){ count = count + (isVisible(widget) ? 1 : 0); }); return count; }; /* At the end of each eval removes all invalid widgets that are not part of the current DOM. */ this.$onEval(PRIORITY_LAST, function() { for(var i = 0; i < invalidWidgets.length;) { var widget = invalidWidgets[i]; if (isOrphan(widget[0])) { invalidWidgets.splice(i, 1); if (widget.dealoc) widget.dealoc(); } else { i++; } } }); /** * Traverses DOM element's (widget's) parents and considers the element to be an orphant if one of * it's parents isn't the current window.document. */ function isOrphan(widget) { if (widget == window.document) return false; var parent = widget.parentNode; return !parent || isOrphan(parent); } return invalidWidgets; }, [], EAGER); function switchRouteMatcher(on, when, dstName) { var regex = '^' + when.replace(/[\.\\\(\)\^\$]/g, "\$1") + '$', params = [], dst = {}; forEach(when.split(/\W/), function(param){ if (param) { var paramRegExp = new RegExp(":" + param + "([\\W])"); if (regex.match(paramRegExp)) { regex = regex.replace(paramRegExp, "([^\/]*)$1"); params.push(param); } } }); var match = on.match(new RegExp(regex)); if (match) { forEach(params, function(name, index){ dst[name] = match[index + 1]; }); if (dstName) this.$set(dstName, dst); } return match ? dst : _null; } /** * @workInProgress * @ngdoc service * @name angular.service.$route * @requires $location * * @property {Object} current Reference to the current route definition. * @property {Array.} routes Array of all configured routes. * * @description * Watches `$location.hashPath` and tries to map the hash to an existing route * definition. It is used for deep-linking URLs to controllers and views (HTML partials). * * The `$route` service is typically used in conjunction with {@link angular.widget.ng:view ng:view} * widget. * * @example This example shows how changing the URL hash causes the $route to match a route against the URL, and the [[ng:include]] pulls in the partial. Try changing the URL in the input box to see changes. Chose: Moby | Moby: Ch1 | Gatsby | Gatsby: Ch4
$location={{$location}}
$route.current.template={{$route.current.template}}
$route.current.params={{$route.current.params}}
$route.current.scope.name={{$route.current.scope.name}}

*/ angularServiceInject('$route', function(location, $updateView) { var routes = {}, onChange = [], matcher = switchRouteMatcher, parentScope = this, dirty = 0, $route = { routes: routes, /** * @workInProgress * @ngdoc method * @name angular.service.$route#onChange * @methodOf angular.service.$route * * @param {function()} fn Function that will be called when `$route.current` changes. * @returns {function()} The registered function. * * @description * Register a handler function that will be called when route changes */ onChange: function(fn) { onChange.push(fn); return fn; }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#parent * @methodOf angular.service.$route * * @param {Scope} [scope=rootScope] Scope to be used as parent for newly created * `$route.current.scope` scopes. * * @description * Sets a scope to be used as the parent scope for scopes created on route change. If not * set, defaults to the root scope. */ parent: function(scope) { if (scope) parentScope = scope; }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#when * @methodOf angular.service.$route * * @param {string} path Route path (matched against `$location.hash`) * @param {Object} params Mapping information to be assigned to `$route.current` on route * match. * * Object properties: * * - `controller` – `{function()=}` – Controller fn that should be associated with newly * created scope. * - `template` – `{string=}` – path to an html template that should be used by * {@link angular.widget.ng:view ng:view} or * {@link angular.widget.ng:include ng:include} widgets. * - `redirectTo` – {string=} – value to update * {@link angular.service.$location $location} hash with and trigger route * redirection. * * @returns {Object} route object * * @description * Adds a new route definition to the `$route` service. */ when:function (path, params) { if (isUndefined(path)) return routes; //TODO(im): remove - not needed! var route = routes[path]; if (!route) route = routes[path] = {}; if (params) extend(route, params); dirty++; return route; }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#otherwise * @methodOf angular.service.$route * * @description * Sets route definition that will be used on route change when no other route definition * is matched. * * @param {Object} params Mapping information to be assigned to `$route.current`. */ otherwise: function(params) { $route.when(null, params); }, /** * @workInProgress * @ngdoc method * @name angular.service.$route#reload * @methodOf angular.service.$route * * @description * Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next * eval even if {@link angular.service.$location $location} hasn't changed. */ reload: function() { dirty++; } }; function updateRoute(){ var childScope, routeParams, pathParams; $route.current = _null; forEach(routes, function(rParams, rPath) { if (!pathParams) { if (pathParams = matcher(location.hashPath, rPath)) { routeParams = rParams; } } }); // "otherwise" fallback routeParams = routeParams || routes[_null]; if(routeParams) { if (routeParams.redirectTo) { location.updateHash(routeParams.redirectTo); $updateView(); //TODO this is to work around the $location<=>$browser issues return; } childScope = createScope(parentScope); $route.current = extend({}, routeParams, { scope: childScope, params: extend({}, location.hashSearch, pathParams) }); } //fire onChange callbacks forEach(onChange, parentScope.$tryEval); if (childScope) { childScope.$become($route.current.controller); } } this.$watch(function(){return dirty + location.hash;}, updateRoute); return $route; }, ['$location', '$updateView']); /** * @workInProgress * @ngdoc service * @name angular.service.$xhr * @function * @requires $browser * @requires $xhr.error * @requires $log * * @description * Generates an XHR request. The $xhr service adds error handling then delegates all requests to * {@link angular.service.$browser $browser.xhr()}. * * @param {string} method HTTP method to use. Valid values are: `GET`, `POST`, `PUT`, `DELETE`, and * `JSON`. `JSON` is a special case which causes a * [JSONP](http://en.wikipedia.org/wiki/JSON#JSONP) cross domain request using script tag * insertion. * @param {string} url Relative or absolute URL specifying the destination of the request. For * `JSON` requests, `url` should include `JSON_CALLBACK` string to be replaced with a name of an * angular generated callback function. * @param {(string|Object)=} post Request content as either a string or an object to be stringified * as JSON before sent to the server. * @param {function(number, (string|Object))} callback A function to be called when the response is * received. The callback will be called with: * * - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of * the response. This will currently always be 200, since all non-200 responses are routed to * {@link angular.service.$xhr.error} service. * - {string|Object} response Response object as string or an Object if the response was in JSON * format. * * @example

sample buzz
code={{code}}
response={{response}}
*/ angularServiceInject('$xhr', function($browser, $error, $log){ var self = this; return function(method, url, post, callback){ if (isFunction(post)) { callback = post; post = _null; } if (post && isObject(post)) { post = toJson(post); } $browser.xhr(method, url, post, function(code, response){ try { if (isString(response) && /^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) { response = fromJson(response, true); } if (code == 200) { callback(code, response); } else { $error( {method: method, url:url, data:post, callback:callback}, {status: code, body:response}); } } catch (e) { $log.error(e); } finally { self.$eval(); } }); }; }, ['$browser', '$xhr.error', '$log']); /** * @workInProgress * @ngdoc service * @name angular.service.$xhr.error * @function * @requires $log * * @description * Error handler for {@link angular.service.$xhr $xhr service}. An application can replaces this * service with one specific for the application. The default implementation logs the error to * {@link angular.service.$log $log.error}. * * @param {Object} request Request object. * * The object has the following properties * * - `method` – `{string}` – The http request method. * - `url` – `{string}` – The request destination. * - `data` – `{(string|Object)=} – An optional request body. * - `callback` – `{function()}` – The callback function * * @param {Object} response Response object. * * The response object has the following properties: * * - status – {number} – Http status code. * - body – {string|Object} – Body of the response. * * @example fetch a non-existent file and log an error in the console: */ angularServiceInject('$xhr.error', function($log){ return function(request, response){ $log.error('ERROR: XHR: ' + request.url, request, response); }; }, ['$log']); /** * @workInProgress * @ngdoc service * @name angular.service.$xhr.bulk * @requires $xhr * @requires $xhr.error * @requires $log * * @description * * @example */ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){ var requests = [], scope = this; function bulkXHR(method, url, post, callback) { if (isFunction(post)) { callback = post; post = _null; } var currentQueue; forEach(bulkXHR.urls, function(queue){ if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) { currentQueue = queue; } }); if (currentQueue) { if (!currentQueue.requests) currentQueue.requests = []; currentQueue.requests.push({method: method, url: url, data:post, callback:callback}); } else { $xhr(method, url, post, callback); } } bulkXHR.urls = {}; bulkXHR.flush = function(callback){ forEach(bulkXHR.urls, function(queue, url){ var currentRequests = queue.requests; if (currentRequests && currentRequests.length) { queue.requests = []; queue.callbacks = []; $xhr('POST', url, {requests:currentRequests}, function(code, response){ forEach(response, function(response, i){ try { if (response.status == 200) { (currentRequests[i].callback || noop)(response.status, response.response); } else { $error(currentRequests[i], response); } } catch(e) { $log.error(e); } }); (callback || noop)(); }); scope.$eval(); } }); }; this.$onEval(PRIORITY_LAST, bulkXHR.flush); return bulkXHR; }, ['$xhr', '$xhr.error', '$log']); /** * @workInProgress * @ngdoc service * @name angular.service.$defer * @requires $browser * @requires $log * * @description * Delegates to {@link angular.service.$browser.defer $browser.defer}, but wraps the `fn` function * into a try/catch block and delegates any exceptions to * {@link angular.services.$exceptionHandler $exceptionHandler} service. * * In tests you can use `$browser.defer.flush()` to flush the queue of deferred functions. * * @param {function()} fn A function, who's execution should be deferred. */ angularServiceInject('$defer', function($browser, $exceptionHandler, $updateView) { var scope = this; return function(fn) { $browser.defer(function() { try { fn(); } catch(e) { $exceptionHandler(e); } finally { $updateView(); } }); }; }, ['$browser', '$exceptionHandler', '$updateView']); /** * @workInProgress * @ngdoc service * @name angular.service.$xhr.cache * @function * @requires $xhr * * @description * Acts just like the {@link angular.service.$xhr $xhr} service but caches responses for `GET` * requests. All cache misses are delegated to the $xhr service. * * @property {function()} delegate Function to delegate all the cache misses to. Defaults to * the {@link angular.service.$xhr $xhr} service. * @property {object} data The hashmap where all cached entries are stored. * * @param {string} method HTTP method. * @param {string} url Destination URL. * @param {(string|Object)=} post Request body. * @param {function(number, (string|Object))} callback Response callback. * @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache * (if present) while a request is sent to the server for a fresh response that will update the * cached entry. The `callback` function will be called when the response is received. */ angularServiceInject('$xhr.cache', function($xhr, $defer, $log){ var inflight = {}, self = this; function cache(method, url, post, callback, verifyCache){ if (isFunction(post)) { callback = post; post = _null; } if (method == 'GET') { var data, dataCached; if (dataCached = cache.data[url]) { $defer(function() { callback(200, copy(dataCached.value)); }); if (!verifyCache) return; } if (data = inflight[url]) { data.callbacks.push(callback); } else { inflight[url] = {callbacks: [callback]}; cache.delegate(method, url, post, function(status, response){ if (status == 200) cache.data[url] = { value: response }; var callbacks = inflight[url].callbacks; delete inflight[url]; forEach(callbacks, function(callback){ try { (callback||noop)(status, copy(response)); } catch(e) { $log.error(e); } }); }); } } else { cache.data = {}; cache.delegate(method, url, post, callback); } } cache.data = {}; cache.delegate = $xhr; return cache; }, ['$xhr.bulk', '$defer', '$log']); /** * @workInProgress * @ngdoc service * @name angular.service.$resource * @requires $xhr.cache * * @description * Is 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.service.$xhr $xhr} service or * raw XMLHttpRequest. * * @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.=} actions Hash with declaration of custom action that should extend the * default set of resource actions. The declaration should be created in the following format: * * {action1: {method:?, params:?, isArray:?, verifyCache:?}, * action2: {method:?, params:?, isArray:?, verifyCache:?}, * ...} * * Where: * * - `action` – {string} – The name of action. This name becomes the name of the method on your * resource object. * - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`, * and `JSON` (also known as JSONP). * - `params` – {object=} – Optional set of pre-bound parameters for this action. * - isArray – {boolean=} – If true then the returned object for this action is an array, see * `returns` section. * - verifyCache – {boolean=} – If true then whenever cache hit occurs, the object is returned and * an async request will be made to the server and the resources as well as the cache will be * updated when the response is received. * * @returns {Object} A resource "class" object with methods for the default set of resource actions * optionally extended with custom `actions`. The default set contains these actions: * * { 'get': {method:'GET'}, * 'save': {method:'POST'}, * 'query': {method:'GET', isArray:true}, * 'remove': {method:'DELETE'}, * 'delete': {method:'DELETE'} }; * * Calling these methods invoke an {@link angular.service.$xhr} with the specified http method, * destination and parameters. When the data is returned from the server then the object is an * instance of the resource class `save`, `remove` and `delete` actions are available on it as * methods with the `$` prefix. This allows you to easily perform CRUD operations (create, read, * update, delete) on server-side data like this: *
        var User = $resource('/user/:userId', {userId:'@id'});
        var user = User.get({userId:123}, function(){
          user.abc = true;
          user.$save();
        });
     
* * It is important to realize that invoking a $resource object method immediately returns an * empty reference (object or array depending on `isArray`). Once the data is returned from the * server the existing reference is populated with the actual data. This is a useful trick since * usually the resource is assigned to a model which is then rendered by the view. Having an empty * object results in no rendering, once the data arrives from the server then the object is * populated with the data and the view automatically re-renders itself showing the new data. This * means that in most case one never has to write a callback function for the action methods. * * The action methods on the class object or instance object can be invoked with the following * parameters: * * - HTTP GET "class" actions: `Resource.action([parameters], [callback])` * - non-GET "class" actions: `Resource.action(postData, [parameters], [callback])` * - non-GET instance actions: `instance.$action([parameters], [callback])` * * * @example * * # Credit card resource * *
     // Define CreditCard class
     var CreditCard = $resource('/user/:userId/card/:cardId',
      {userId:123, cardId:'@id'}, {
       charge: {method:'POST', params:{charge:true}}
      });

     // We can retrieve a collection from the server
     var cards = CreditCard.query();
     // GET: /user/123/card
     // server returns: [ {id:456, number:'1234', name:'Smith'} ];

     var card = cards[0];
     // each item is an instance of CreditCard
     expect(card instanceof CreditCard).toEqual(true);
     card.name = "J. Smith";
     // non GET methods are mapped onto the instances
     card.$save();
     // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
     // server returns: {id:456, number:'1234', name: 'J. Smith'};

     // our custom method is mapped as well.
     card.$charge({amount:9.99});
     // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
     // server returns: {id:456, number:'1234', name: 'J. Smith'};

     // we can create an instance as well
     var newCard = new CreditCard({number:'0123'});
     newCard.name = "Mike Smith";
     newCard.$save();
     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
     // server returns: {id:789, number:'01234', name: 'Mike Smith'};
     expect(newCard.id).toEqual(789);
 * 
* * The object returned from this function execution is a resource "class" which has "static" method * for each action in the definition. * * Calling these methods invoke `$xhr` on the `url` template with the given `method` and `params`. * When the data is returned from the server then the object is an instance of the resource type and * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD * operations (create, read, update, delete) on server-side data.
     var User = $resource('/user/:userId', {userId:'@id'});
     var user = User.get({userId:123}, function(){
       user.abc = true;
       user.$save();
     });
   
* * It's worth noting that the callback for `get`, `query` and other method gets passed in the * response that came from the server, so one could rewrite the above example as: *
     var User = $resource('/user/:userId', {userId:'@id'});
     User.get({userId:123}, function(u){
       u.abc = true;
       u.$save();
     });
   
* # Buzz client Let's look at what a buzz client created with the `$resource` service looks like:

{{item.actor.name}} Expand replies: {{item.links.replies[0].count}}

{{item.object.content | html}}
{{reply.actor.name}}: {{reply.content | html}}
*/ angularServiceInject('$resource', function($xhr){ var resource = new ResourceFactory($xhr); return bind(resource, resource.route); }, ['$xhr.cache']); /** * @workInProgress * @ngdoc service * @name angular.service.$cookies * @requires $browser * * @description * Provides read/write access to browser's cookies. * * Only a simple Object is exposed and by adding or removing properties to/from * this object, new cookies are created/deleted at the end of current $eval. * * @example */ angularServiceInject('$cookies', function($browser) { var rootScope = this, cookies = {}, lastCookies = {}, lastBrowserCookies; //creates a poller fn that copies all cookies from the $browser to service & inits the service $browser.addPollFn(function() { var currentCookies = $browser.cookies(); if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl lastBrowserCookies = currentCookies; copy(currentCookies, lastCookies); copy(currentCookies, cookies); rootScope.$eval(); } })(); //at the end of each eval, push cookies //TODO: this should happen before the "delayed" watches fire, because if some cookies are not // strings or browser refuses to store some cookies, we update the model in the push fn. this.$onEval(PRIORITY_LAST, push); return cookies; /** * Pushes all the cookies from the service to the browser and verifies if all cookies were stored. */ function push(){ var name, value, browserCookies, updated; //delete any cookies deleted in $cookies for (name in lastCookies) { if (isUndefined(cookies[name])) { $browser.cookies(name, _undefined); } } //update all cookies updated in $cookies for(name in cookies) { value = cookies[name]; if (!isString(value)) { if (isDefined(lastCookies[name])) { cookies[name] = lastCookies[name]; } else { delete cookies[name]; } } else if (value !== lastCookies[name]) { $browser.cookies(name, value); updated = true; } } //verify what was actually stored if (updated){ updated = false; browserCookies = $browser.cookies(); for (name in cookies) { if (cookies[name] !== browserCookies[name]) { //delete or reset all cookies that the browser dropped from $cookies if (isUndefined(browserCookies[name])) { delete cookies[name]; } else { cookies[name] = browserCookies[name]; } updated = true; } } } } }, ['$browser']); /** * @workInProgress * @ngdoc service * @name angular.service.$cookieStore * @requires $cookies * * @description * Provides a key-value (string-object) storage, that is backed by session cookies. * Objects put or retrieved from this storage are automatically serialized or * deserialized by angular's toJson/fromJson. * @example */ angularServiceInject('$cookieStore', function($store) { return { /** * @workInProgress * @ngdoc method * @name angular.service.$cookieStore#get * @methodOf angular.service.$cookieStore * * @description * Returns the value of given cookie key * * @param {string} key Id to use for lookup. * @returns {Object} Deserialized cookie value. */ get: function(key) { return fromJson($store[key]); }, /** * @workInProgress * @ngdoc method * @name angular.service.$cookieStore#put * @methodOf angular.service.$cookieStore * * @description * Sets a value for given cookie key * * @param {string} key Id for the `value`. * @param {Object} value Value to be stored. */ put: function(key, value) { $store[key] = toJson(value); }, /** * @workInProgress * @ngdoc method * @name angular.service.$cookieStore#remove * @methodOf angular.service.$cookieStore * * @description * Remove given cookie * * @param {string} key Id of the key-value pair to delete. */ remove: function(key) { delete $store[key]; } }; }, ['$cookies']);