diff options
| author | Vojta Jina | 2010-11-15 01:10:16 +0000 |
|---|---|---|
| committer | Misko Hevery | 2010-11-15 21:55:37 -0800 |
| commit | cc749760fd59418433de5a055d1ca401f7500444 (patch) | |
| tree | a6bc273b68b10515b9e00bc0b166c46d88748033 | |
| parent | b467a50bc75b7f4c0d9bcee521387eda460337bf (diff) | |
| download | angular.js-cc749760fd59418433de5a055d1ca401f7500444.tar.bz2 | |
Added basic Services, which support @memberOf and @methodOf
| -rw-r--r-- | docs/collect.js | 105 | ||||
| -rw-r--r-- | docs/docs.css | 5 | ||||
| -rw-r--r-- | docs/service.template | 37 | ||||
| -rw-r--r-- | docs/spec/collectSpec.js | 141 | ||||
| -rw-r--r-- | docs/specs.js | 2 | ||||
| -rw-r--r-- | src/Angular.js | 48 | ||||
| -rw-r--r-- | src/AngularPublic.js | 8 | ||||
| -rw-r--r-- | src/Browser.js | 155 | ||||
| -rw-r--r-- | src/services.js | 402 |
9 files changed, 838 insertions, 65 deletions
diff --git a/docs/collect.js b/docs/collect.js index 4e845f5b..7ad1630b 100644 --- a/docs/collect.js +++ b/docs/collect.js @@ -7,7 +7,8 @@ var fs = require('fs'), Showdown = require('showdown').Showdown; var documentation = { - pages:[] + pages:[], + byName: {} }; var keywordPages = []; @@ -23,18 +24,7 @@ var work = callback.chain(function () { //console.log('reading', file, '...'); findNgDoc(file, work.waitMany(function(doc) { parseNgDoc(doc); - if (doc.ngdoc) { - keywordPages.push({ - name:doc.name, - keywords:keywords(doc.raw.text) - } - ); - documentation.pages.push(doc); - console.log('Found:', doc.ngdoc + ':' + doc.shortName); - mergeTemplate( - doc.ngdoc + '.template', - doc.name + '.html', doc, work.waitFor()); - } + processNgDoc(documentation, doc); })); })); })); @@ -42,6 +32,7 @@ var work = callback.chain(function () { console.log('ERROR:', err.stack || err); }).onDone(function(){ keywordPages.sort(function(a,b){ return a.name == b.name ? 0:(a.name < b.name ? -1 : 1);}); + writeDoc(documentation.pages); mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(keywordPages)}, callback.chain()); mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain()); copy('docs-scenario.html', callback.chain()); @@ -156,11 +147,17 @@ function markdownTag(doc, name, value) { } function markdown(text) { - text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>'); - text = text.replace(/(angular\.[\w\._\-:]+)/gm, '<a href="#$1">$1</a>'); - text = text.replace(/(`(ng:[\w\._\-]+)`)/gm, '<a href="#angular.directive.$2">$1</a>'); - text = new Showdown.converter().makeHtml(text); - return text; + var parts = text.split(/(<pre>[\s\S]*<\/pre>)/); + parts.forEach(function(text, i){ + if (!text.match(/^<pre>/)) { + text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>'); + text = new Showdown.converter().makeHtml(text); + text = text.replace(/(angular\.[\$\w\._\-:]+)/gm, '<a href="#!$1">$1</a>'); + text = text.replace(/(`(ng:[\w\._\-]+)`)/gm, '<a href="#!angular.directive.$2">$1</a>'); + parts[i] = text; + } + }); + return parts.join(''); } function markdownNoP(text) { @@ -171,6 +168,43 @@ function markdownNoP(text) { return lines.join('\n'); } +function requiresTag(doc, name, value) { + doc.requires = doc.requires || []; + doc.requires.push({name: value}); +} + +function propertyTag(doc, name, value) { + doc[name] = doc[name] || []; + var match = value.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/); + + if (match) { + var tag = { + type: match[2], + name: match[3], + description: match[5] || false + }; + } else { + throw "[" + doc.raw.file + ":" + doc.raw.line + + "]: @" + name + " must be in format '{type} name description' got: " + value; + } + return doc[name].push(tag); +} + +function returnsTag(doc, name, value) { + var match = value.match(/^({(\S+)}\s*)?(.*)?/); + + if (match) { + var tag = { + type: match[2], + description: match[3] || false + }; + } else { + throw "[" + doc.raw.file + ":" + doc.raw.line + + "]: @" + name + " must be in format '{type} description' got: " + value; + } + return doc[name] = tag; +} + var TAG = { ngdoc: valueTag, example: escapedHtmlTag, @@ -186,6 +220,7 @@ var TAG = { paramDescription: markdownTag, exampleDescription: markdownTag, element: valueTag, + methodOf: valueTag, name: function(doc, name, value) { doc.name = value; doc.shortName = value.split(/\./).pop(); @@ -212,7 +247,10 @@ var TAG = { throw "[" + doc.raw.file + ":" + doc.raw.line + "]: @param must be in format '{type} name=value description' got: " + value; } - } + }, + property: propertyTag, + requires: requiresTag, + returns: returnsTag }; function parseNgDoc(doc){ @@ -291,3 +329,32 @@ function findJsFiles(dir, callback){ callback.done(); })); } + +function processNgDoc(documentation, doc) { + if (!doc.ngdoc) return; + console.log('Found:', doc.ngdoc + ':' + doc.name); + + documentation.byName[doc.name] = doc; + + if (doc.methodOf) { + if (parent = documentation.byName[doc.methodOf]) { + (parent.method = parent.method || []).push(doc); + } else { + throw 'Owner "' + doc.methodOf + '" is not defined.'; + } + } else { + documentation.pages.push(doc); + keywordPages.push({ + name:doc.name, + keywords:keywords(doc.raw.text) + }); + } +} + +function writeDoc(pages) { + pages.forEach(function(doc) { + mergeTemplate( + doc.ngdoc + '.template', + doc.name + '.html', doc, callback.chain()); + }); +} diff --git a/docs/docs.css b/docs/docs.css index 25a8ffea..664328d0 100644 --- a/docs/docs.css +++ b/docs/docs.css @@ -125,3 +125,8 @@ a { margin-left: 2em; font-family: monospace; } + +#sidebar ul li.level-3 { + margin-left: 3em; + font-family: monospace; +} diff --git a/docs/service.template b/docs/service.template new file mode 100644 index 00000000..2774cad2 --- /dev/null +++ b/docs/service.template @@ -0,0 +1,37 @@ +<h1><tt>{{name}}</tt></h1> +<h2>Description</h2> +{{{description}}} + +<h2>Dependencies</h2> +<ul> + {{#requires}} + <li><tt>{{name}}</tt></li> + {{/requires}} +</ul> + +<h2>Methods</h2> +<ul> + {{#method}} + <li><tt>{{shortName}}</tt>: {{{description}}}</li> + {{/method}} +</ul> + +<h2>Properties</h2> +<ul> + {{#property}} + <li><tt>{{name}}:{{#type}}{{type}}{{/type}}</tt>{{#description}}: {{{description}}}{{/description}}</li> + {{/property}} +</ul> + +{{#example}} +<h2>Example</h2> +{{{exampleDescription}}} +<doc:example> + <doc:source> +{{/example}} + {{{example}}} +{{#example}} + </doc:source> + {{#scenario}}<doc:scenario>{{{scenario}}}</doc:scenario>{{/scenario}} +</doc:example> +{{/example}}
\ No newline at end of file diff --git a/docs/spec/collectSpec.js b/docs/spec/collectSpec.js index f20342b1..9de08dc8 100644 --- a/docs/spec/collectSpec.js +++ b/docs/spec/collectSpec.js @@ -11,6 +11,63 @@ describe('collect', function(){ expect(collect.markdown('<angular/>')). toEqual('<p><tt><angular/></tt></p>'); }); + + it('should not replace anything in <pre>', function(){ + expect(collect.markdown('angular.x\n<pre>\nangular.k\n</pre>\nangular.x')). + toEqual( + '<p><a href="#!angular.x">angular.x</a></p>' + + '<pre>\nangular.k\n</pre>' + + '<p><a href="#!angular.x">angular.x</a></p>'); + }); + }); + + describe('processNgDoc', function() { + var processNgDoc = collect.processNgDoc, + documentation; + + beforeEach(function() { + documentation = { + pages: [], + byName: {} + }; + }); + + it('should store references to docs by name', function() { + var doc = {ngdoc: 'section', name: 'fake', raw: {text:''}}; + processNgDoc(documentation, doc); + expect(documentation.byName.fake).toBe(doc); + }); + + it('should connect doc to owner (specified by @methodOf)', function() { + var parentDoc = {ngdoc: 'section', name: 'parent', raw: {text:''}}; + var doc = {ngdoc: 'section', name: 'child', methodOf: 'parent', raw: {text:''}}; + processNgDoc(documentation, parentDoc); + processNgDoc(documentation, doc); + expect(documentation.byName.parent.method).toBeDefined(); + expect(documentation.byName.parent.method[0]).toBe(doc); + }); + + it('should not add doc to sections if @memberOf specified', function() { + var parentDoc = {ngdoc: 'parent', name: 'parent', raw: {text:''}}; + var doc = {ngdoc: 'child', name: 'child', methodOf: 'parent', raw: {text:''}}; + processNgDoc(documentation, parentDoc); + processNgDoc(documentation, doc); + expect(documentation.pages.child).not.toBeDefined(); + }); + + it('should throw exception if owner does not exist', function() { + expect(function() { + processNgDoc(documentation, {ngdoc: 'section', methodOf: 'not.exist', raw: {text:''}}); + }).toThrow('Owner "not.exist" is not defined.'); + }); + + it('should ignore non-ng docs', function() { + var doc = {name: 'anything'}; + expect(function() { + processNgDoc(documentation, doc); + }).not.toThrow(); + expect(documentation.pages).not.toContain(doc); + }); }); describe('TAG', function(){ @@ -41,6 +98,90 @@ describe('collect', function(){ }); }); + describe('@requires', function() { + it('should parse more @requires tag into array', function() { + TAG.requires(doc, 'requires', '$service'); + TAG.requires(doc, 'requires', '$another'); + + expect(doc.requires).toEqual([ + {name: '$service'}, + {name: '$another'} + ]); + }); + }); + + describe('@property', function() { + it('should parse @property tags into array', function() { + TAG.property(doc, 'property', '{type} name1 desc'); + TAG.property(doc, 'property', '{type} name2 desc'); + expect(doc.property.length).toEqual(2); + }); + + it('should parse @property with only name', function() { + TAG.property(doc, 'property', 'fake'); + expect(doc.property[0].name).toEqual('fake'); + }); + + it('should parse @property with optional type', function() { + TAG.property(doc, 'property', '{string} name'); + expect(doc.property[0].name).toEqual('name'); + expect(doc.property[0].type).toEqual('string'); + }); + + it('should parse @property with optional description', function() { + TAG.property(doc, 'property', 'name desc rip tion'); + expect(doc.property[0].name).toEqual('name'); + expect(doc.property[0].description).toEqual('desc rip tion'); + }); + + it('should parse @property with type and description both', function() { + TAG.property(doc, 'property', '{bool} name desc rip tion'); + expect(doc.property[0].name).toEqual('name'); + expect(doc.property[0].type).toEqual('bool'); + expect(doc.property[0].description).toEqual('desc rip tion'); + }); + + /** + * If property description is undefined, this variable is not set in the template, + * so the whole @description tag is used instead + */ + it('should set undefined description to "false"', function() { + TAG.property(doc, 'property', 'name'); + expect(doc.property[0].description).toBe(false); + }); + }); + + describe('@methodOf', function() { + it('should parse @methodOf tag', function() { + expect(function() { + TAG.methodOf(doc, 'methodOf', 'parentName'); + }).not.toThrow(); + expect(doc.methodOf).toEqual('parentName'); + }); + }); + + describe('@returns', function() { + it('should parse @returns', function() { + expect(function() {TAG.returns(doc, 'returns', '');}) + .not.toThrow(); + }); + + it('should parse @returns with type', function() { + TAG.returns(doc, 'returns', '{string}'); + expect(doc.returns.type).toEqual('string'); + }); + + it('should parse @returns with description', function() { + TAG.returns(doc, 'returns', 'descrip tion'); + expect(doc.returns.description).toEqual('descrip tion'); + }); + + it('should parse @returns with type and description', function() { + TAG.returns(doc, 'returns', '{string} description'); + expect(doc.returns).toEqual({type: 'string', description: 'description'}); + }); + }); + describe('@describe', function(){ it('should support pre blocks', function(){ TAG.description(doc, 'description', '<pre>abc</pre>'); diff --git a/docs/specs.js b/docs/specs.js index 5f1cd153..d564b045 100644 --- a/docs/specs.js +++ b/docs/specs.js @@ -1,6 +1,6 @@ require.paths.push("./lib"); var jasmine = require('jasmine-1.0.1'); -var sys = require('sys'); +var sys = require('util'); for(var key in jasmine) { global[key] = jasmine[key]; diff --git a/src/Angular.js b/src/Angular.js index e917b3ee..5a5a4ab3 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -315,7 +315,6 @@ var _undefined = undefined, */ angularValidator = extensionMap(angular, 'validator'), - /** * @ngdoc overview * @name angular.filter @@ -473,6 +472,53 @@ var _undefined = undefined, * }); */ angularFormatter = extensionMap(angular, 'formatter'), + + /** + * @ngdoc overview + * @name angular.service + * + * @description + * # Overview + * Services are substituable objects, which are wired together using dependency injection. + * Each service could have dependencies (other services), which are passed in constructor. + * Because JS is dynamicaly typed language, dependency injection can not use static types + * to satisfy these dependencies, so each service must explicitely define its dependencies. + * This is done by $inject property. + * + * For now, life time of all services is the same as the life time of page. + * + * + * # Standard services + * The Angular framework provides a standard set of services for common operations. + * You can write your own services and rewrite these standard services as well. + * Like other core angular variables, standard services always start with $. + * + * * `angular.service.$window` + * * `angular.service.$document` + * * `angular.service.$location` + * * `angular.service.$log` + * * `angular.service.$exceptionHandler` + * * `angular.service.$hover` + * * `angular.service.$invalidWidgets` + * * `angular.service.$route` + * * `angular.service.$xhr` + * * `angular.service.$xhr.error` + * * `angular.service.$xhr.bulk` + * * `angular.service.$xhr.cache` + * * `angular.service.$resource` + * * `angular.service.$cookies` + * * `angular.service.$cookieStore` + * + * # Writing your own services + * <pre> + * angular.service('notify', function(location) { + * this.one = function() { + * } + * }, {$inject: ['$location']}); + * </pre> + * + * # Using services in controller + */ angularService = extensionMap(angular, 'service'), angularCallbacks = extensionMap(angular, 'callbacks'), nodeName, diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 2b5d4fbc..c241238c 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -1,4 +1,12 @@ var browserSingleton; +/** + * @ngdoc service + * @name angular.service.$browser + * @requires $log + * + * @description + * Represents the browser. + */ angularService('$browser', function($log){ if (!browserSingleton) { browserSingleton = new Browser( diff --git a/src/Browser.js b/src/Browser.js index bf9b5483..2608f160 100644 --- a/src/Browser.js +++ b/src/Browser.js @@ -19,7 +19,20 @@ function Browser(location, document, head, XHR, $log) { var outstandingRequestCount = 0; var outstandingRequestCallbacks = []; - self.xhr = function(method, url, post, callback){ + /** + * @ngdoc method + * @name angular.service.$browser#xhr + * @methodOf angular.service.$browser + * + * @param {string} method Requested method (get|post|put|delete|head|json) + * @param {string} url Requested url + * @param {string=} post Post data to send + * @param {function(number, string)} callback Function that will be called on response + * + * @description + * Send ajax request + */ + self.xhr = function(method, url, post, callback) { if (isFunction(post)) { callback = post; post = _null; @@ -63,7 +76,14 @@ function Browser(location, document, head, XHR, $log) { } }; - self.notifyWhenNoOutstandingRequests = function(callback){ + /** + * @ngdoc method + * @name angular.service.$browser#notifyWhenNoOutstandingRequests + * @methodOf angular.service.$browser + * + * @param {function} callback Function that will be called when no outstanding request + */ + self.notifyWhenNoOutstandingRequests = function(callback) { if (outstandingRequestCount === 0) { callback(); } else { @@ -75,27 +95,48 @@ function Browser(location, document, head, XHR, $log) { // Poll Watcher API ////////////////////////////////////////////////////////////// var pollFns = []; - function poll(){ + + /** + * @ngdoc method + * @name angular.service.$browser#poll + * @methodOf angular.service.$browser + */ + self.poll = function() { foreach(pollFns, function(pollFn){ pollFn(); }); - } - self.poll = poll; + }; /** + * @ngdoc method + * @name angular.service.$browser#addPollFn + * @methodOf angular.service.$browser + * + * @param {function} fn Poll function to add + * + * @description * Adds a function to the list of functions that poller periodically executes - * @return {Function} the added function + * + * @returns {function} the added function */ - self.addPollFn = function(/**Function*/fn){ + self.addPollFn = function(fn) { pollFns.push(fn); return fn; }; /** - * Configures the poller to run in the specified intervals, using the specified setTimeout fn and - * kicks it off. + * @ngdoc method + * @name angular.service.$browser#startPoller + * @methodOf angular.service.$browser + * + * @param {number} interval How often should browser call poll functions (ms) + * @param {function} setTimeout + * + * @description + * Configures the poller to run in the specified intervals, using the specified + * setTimeout fn and kicks it off. */ - self.startPoller = function(/**number*/interval, /**Function*/setTimeout){ + self.startPoller = function(interval, setTimeout) { (function check(){ - poll(); + self.poll(); setTimeout(check, interval); })(); }; @@ -103,15 +144,37 @@ function Browser(location, document, head, XHR, $log) { ////////////////////////////////////////////////////////////// // URL API ////////////////////////////////////////////////////////////// + + /** + * @ngdoc method + * @name angular.service.$browser#setUrl + * @methodOf angular.service.$browser + * + * @param {string} url New url + * + * @description + * Sets browser's url + */ self.setUrl = function(url) { var existingURL = location.href; if (!existingURL.match(/#/)) existingURL += '#'; if (!url.match(/#/)) url += '#'; location.href = url; }; - self.getUrl = function() { + + /** + * @ngdoc method + * @name angular.service.$browser#getUrl + * @methodOf angular.service.$browser + * + * @description + * Get current browser's url + * + * @returns {string} Browser's url + */ + self.getUrl = function() { return location.href; - }; + }; ////////////////////////////////////////////////////////////// // Cookies API @@ -121,19 +184,27 @@ function Browser(location, document, head, XHR, $log) { var lastCookieString = ''; /** - * 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. + * @ngdoc method + * @name angular.service.$browser#cookies + * @methodOf angular.service.$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: - * <ul><li> - * cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it - * </li><li> - * cookies(name, value) -> set name to value, if value is undefined delete the cookie - * </li><li> - * cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way) - * </li></ul> + * <ul> + * <li>cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify it</li> + * <li>cookies(name, value) -> set name to value, if value is undefined delete the cookie</li> + * <li>cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that way)</li> + * </ul> + * + * @returns {Object} Hash of all cookies (if called without any parameter) */ - self.cookies = function (/**string*/name, /**string*/value){ + self.cookies = function (name, value) { var cookieLength, cookieArray, i, keyValue; if (name) { @@ -175,7 +246,27 @@ function Browser(location, document, head, XHR, $log) { // Misc API ////////////////////////////////////////////////////////////// var hoverListener = noop; + + /** + * @ngdoc method + * @name angular.service.$browser#hover + * @methodOf angular.service.$browser + * + * @param {function(Object, boolean)} listener + * + * @description + * Set hover listener - function that will be called when hover event occurs. + */ self.hover = function(listener) { hoverListener = listener; }; + + /** + * @ngdoc method + * @name angular.service.$browser#bind + * @methodOf angular.service.$browser + * + * @description + * Register hover function to real browser + */ self.bind = function() { document.bind("mouseover", function(event){ hoverListener(jqLite(msie ? event.srcElement : event.target), true); @@ -189,9 +280,15 @@ function Browser(location, document, head, XHR, $log) { /** + * @ngdoc method + * @name angular.service.$browser#addCss + * @methodOf angular.service.$browser + * + * @param {string} url Url to css file + * @description * Adds a stylesheet tag to the head. */ - self.addCss = function(/**string*/url) { + self.addCss = function(url) { var link = jqLite(rawDocument.createElement('link')); link.attr('rel', 'stylesheet'); link.attr('type', 'text/css'); @@ -201,9 +298,17 @@ function Browser(location, document, head, XHR, $log) { /** + * @ngdoc method + * @name angular.service.$browser#addJs + * @methodOf angular.service.$browser + * + * @param {string} url Url to js file + * @param {string=} dom_id Optional id for the script tag + * + * @description * Adds a script tag to the head. */ - self.addJs = function(/**string*/url, /**string*/dom_id) { + self.addJs = function(url, dom_id) { var script = jqLite(rawDocument.createElement('script')); script.attr('type', 'text/javascript'); script.attr('src', url); diff --git a/src/services.js b/src/services.js index 9af69403..be442c20 100644 --- a/src/services.js +++ b/src/services.js @@ -8,11 +8,64 @@ function angularServiceInject(name, fn, inject, eager) { angularService(name, fn, {$inject:inject, $creation:eager}); } +/** + * @ngdoc service + * @name angular.service.$window + * + * @description + * Is reference to the browser's <b>window</b> object. While <b>window</b> + * is globally available in JavaScript, it causes testability problems, because + * it is a global variable. In <b><angular/></b> 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 + <input ng:init="greeting='Hello World!'" type="text" name="greeting" /> + <button ng:click="$window.alert(greeting)">ALERT</button> + */ angularServiceInject("$window", bind(window, identity, window), [], EAGER_PUBLISHED); + +/** + * @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_PUBLISHED); +/** + * @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.<string|boolean>} search + * @property {string} hash + * @property {string} hashPath + * @property {Object.<string|boolean>} 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 + <a href="#">clear hash</a> | + <a href="#myPath?name=misko">test hash</a><br/> + <input type='text' name="$location.hash"/> + <pre>$location = {{$location}}</pre> + */ angularServiceInject("$location", function(browser) { var scope = this, location = {toString:toString, update:update, updateHash: updateHash}, @@ -39,6 +92,11 @@ angularServiceInject("$location", function(browser) { // PUBLIC METHODS /** + * @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() @@ -69,7 +127,12 @@ angularServiceInject("$location", function(browser) { } /** - * Update location hash + * @ngdoc method + * @name angular.service.$location#updateHash + * @methodOf angular.service.$location + * + * @description + * Update location hash part * @see update() * * @example @@ -99,9 +162,12 @@ angularServiceInject("$location", function(browser) { } /** + * @ngdoc method + * @name angular.service.$location#toString + * @methodOf angular.service.$location + * + * @description * Returns string representation - href - * - * @return {string} Location's href property */ function toString() { updateLocation(); @@ -224,23 +290,105 @@ angularServiceInject("$location", function(browser) { }, ['$browser'], EAGER_PUBLISHED); +/** + * @ngdoc service + * @name angular.service.$log + * @requires $window + * + * @description + * Is simple service for logging. Default implementation writes the message + * into the browser's console (if present). + * + * This is useful for debugging. + * + * @example + <p>Reload this page with open console, enter text and hit the log button...</p> + Message: + <input type="text" name="message" value="Hello World!"/> + <button ng:click="$log.log(message)">log</button> + <button ng:click="$log.warn(message)">warn</button> + <button ng:click="$log.info(message)">info</button> + <button ng:click="$log.error(message)">error</button> + */ angularServiceInject("$log", function($window){ var console = $window.console || {log: noop, warn: noop, info: noop, error: noop}, log = console.log || noop; return { + /** + * @ngdoc method + * @name angular.service.$log#log + * @methodOf angular.service.$log + * + * @description + * Write a log message + */ log: bind(console, log), + + /** + * @ngdoc method + * @name angular.service.$log#warn + * @methodOf angular.service.$log + * + * @description + * Write a warning message + */ warn: bind(console, console.warn || log), + + /** + * @ngdoc method + * @name angular.service.$log#info + * @methodOf angular.service.$log + * + * @description + * Write an information message + */ info: bind(console, console.info || log), + + /** + * @ngdoc method + * @name angular.service.$log#error + * @methodOf angular.service.$log + * + * @description + * Write an error message + */ error: bind(console, console.error || log) }; }, ['$window'], EAGER_PUBLISHED); +/** + * @ngdoc service + * @name angular.service.$exceptionHandler + * @requires $log + * + * @description + * Any uncaught exception in <angular/> is delegated to this service. + * The default implementation simply delegates to $log.error which logs it into + * the browser console. + * + * When unit testing it is useful to have uncaught exceptions propagate + * to the test so the test will fail rather than silently log the exception + * to the browser console. For this purpose you can override this service with + * a simple rethrow. + * + * @example + */ angularServiceInject('$exceptionHandler', function($log){ return function(e) { $log.error(e); }; }, ['$log'], EAGER_PUBLISHED); +/** + * @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){ @@ -287,9 +435,15 @@ angularServiceInject("$hover", function(browser, document) { }); }, ['$browser', '$document'], EAGER); - -/* Keeps references to all invalid widgets found during validation. Can be queried to find if there - * are invalid widgets currently displayed +/** + * @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 = []; @@ -373,7 +527,59 @@ function switchRouteMatcher(on, when, dstName) { return match ? dst : _null; } -angularServiceInject('$route', function(location){ +/** + * @ngdoc service + * @name angular.service.$route + * @requires $location + * + * @property {Object} current Name of the current route + * @property {Array.<Object>} routes List of 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). + * + * $route is typically used in conjunction with ng:include widget. + * + * @example +<p> + This example shows how changing the URL hash causes the <tt>$route</tt> + to match a route against the URL, and the <tt>[[ng:include]]</tt> pulls in the partial. + Try changing the URL in the input box to see changes. +</p> + +<script> + angular.service('myApp', function($route) { + $route.when('/Book/:bookId', {template:'rsrc/book.html', controller:BookCntl}); + $route.when('/Book/:bookId/ch/:chapterId', {template:'rsrc/chapter.html', controller:ChapterCntl}); + $route.onChange(function() { + $route.current.scope.params = $route.current.params; + }); + }, {$inject: ['$route']}); + + function BookCntl() { + this.name = "BookCntl"; + } + + function ChapterCntl() { + this.name = "ChapterCntl"; + } +</script> + +Chose: +<a href="#/Book/Moby">Moby</a> | +<a href="#/Book/Moby/ch/1">Moby: Ch1</a> | +<a href="#/Book/Gatsby">Gatsby</a> | +<a href="#/Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a><br/> +<input type="text" name="$location.hashPath" size="80" /> +<pre>$location={{$location}}</pre> +<pre>$route.current.template={{$route.current.template}}</pre> +<pre>$route.current.params={{$route.current.params}}</pre> +<pre>$route.current.scope.name={{$route.current.scope.name}}</pre> +<hr/> +<ng:include src="$route.current.template" scope="$route.current.scope"/> + */ +angularServiceInject('$route', function(location) { var routes = {}, onChange = [], matcher = switchRouteMatcher, @@ -381,8 +587,32 @@ angularServiceInject('$route', function(location){ dirty = 0, $route = { routes: routes, + + /** + * @ngdoc method + * @name angular.service.$route#onChange + * @methodOf angular.service.$route + * + * @param {function} fn Function that will be called on route change + * + * @description + * Register a handler function that will be called when route changes + */ onChange: bind(onChange, onChange.push), - when:function (path, params){ + + /** + * @ngdoc method + * @name angular.service.$route#when + * @methodOf angular.service.$route + * + * @param {string} path Route path (matched against $location.hash) + * @param {Object} params + * @returns {Object} route object + * + * @description + * Add new route + */ + when:function (path, params) { if (angular.isUndefined(path)) return routes; var route = routes[path]; if (!route) route = routes[path] = {}; @@ -415,6 +645,17 @@ angularServiceInject('$route', function(location){ return $route; }, ['$location'], EAGER_PUBLISHED); +/** + * @ngdoc service + * @name angular.service.$xhr + * @requires $browser + * @requires $error + * @requires $log + * + * @description + * + * @example + */ angularServiceInject('$xhr', function($browser, $error, $log){ var self = this; return function(method, url, post, callback){ @@ -446,12 +687,32 @@ angularServiceInject('$xhr', function($browser, $error, $log){ }; }, ['$browser', '$xhr.error', '$log']); +/** + * @ngdoc service + * @name angular.service.$xhr.error + * @requires $log + * + * @description + * + * @example + */ angularServiceInject('$xhr.error', function($log){ return function(request, response){ $log.error('ERROR: XHR: ' + request.url, request, response); }; }, ['$log']); +/** + * @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; @@ -502,6 +763,15 @@ angularServiceInject('$xhr.bulk', function($xhr, $error, $log){ return bulkXHR; }, ['$xhr', '$xhr.error', '$log']); +/** + * @ngdoc service + * @name angular.service.$xhr.cache + * @requires $xhr + * + * @description + * + * @example + */ angularServiceInject('$xhr.cache', function($xhr){ var inflight = {}, self = this; function cache(method, url, post, callback, verifyCache){ @@ -546,18 +816,74 @@ angularServiceInject('$xhr.cache', function($xhr){ return cache; }, ['$xhr.bulk']); +/** + * @ngdoc service + * @name angular.service.$resource + * @requires $xhr + * + * @description + * Is a factory which creates a resource object which lets you interact with + * <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer" target="_blank">RESTful</a> + * server-side data sources. + * Resource object has action methods which provide high-level behaviors without + * the need to interact with the low level $xhr or XMLHttpRequest(). + * + * @example + <script> + function BuzzController($resource) { + this.Activity = $resource( + 'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments', + {alt:'json', callback:'JSON_CALLBACK'}, + {get:{method:'JSON', params:{visibility:'@self'}}, replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}} + ); + } + + BuzzController.prototype = { + fetch: function() { + this.activities = this.Activity.get({userId:this.userId}); + }, + expandReplies: function(activity) { + activity.replies = this.Activity.replies({userId:this.userId, activityId:activity.id}); + } + }; + BuzzController.$inject = ['$resource']; + </script> + + <div ng:controller="BuzzController"> + <input name="userId" value="googlebuzz"/> + <button ng:click="fetch()">fetch</button> + <hr/> + <div ng:repeat="item in activities.data.items"> + <h1 style="font-size: 15px;"> + <img src="{{item.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/> + <a href="{{item.actor.profileUrl}}">{{item.actor.name}}</a> + <a href="#" ng:click="expandReplies(item)" style="float: right;">Expand replies: {{item.links.replies[0].count}}</a> + </h1> + {{item.object.content | html}} + <div ng:repeat="reply in item.replies.data.items" style="margin-left: 20px;"> + <img src="{{reply.actor.thumbnailUrl}}" style="max-height:30px;max-width:30px;"/> + <a href="{{reply.actor.profileUrl}}">{{reply.actor.name}}</a>: {{reply.content | html}} + </div> + </div> + </div> + */ angularServiceInject('$resource', function($xhr){ var resource = new ResourceFactory($xhr); return bind(resource, resource.route); }, ['$xhr.cache']); - /** - * $cookies service provides read/write access to the browser cookies. Currently only session - * cookies are supported. - * - * Only a simple Object is exposed and by adding or removing properties to/from this object, new - * cookies are created or deleted from the browser at the end of the current eval. + * @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, @@ -630,23 +956,61 @@ angularServiceInject('$cookies', function($browser) { } }, ['$browser'], EAGER_PUBLISHED); - /** - * $cookieStore 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. + * @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 { + /** + * @ngdoc method + * @name angular.service.$cookieStore#get + * @methodOf angular.service.$cookieStore + * + * @description + * Returns the value of given cookie key + * + * @param {string} key + * @returns Cookie value + */ get: function(/**string*/key) { return fromJson($store[key]); }, - put: function(/**string*/key, /**Object*/value) { + /** + * @ngdoc method + * @name angular.service.$cookieStore#put + * @methodOf angular.service.$cookieStore + * + * @description + * Sets a value for given cookie key + * + * @param {string} key + * @param {Object} value + */ + put: function(key, value) { $store[key] = toJson(value); }, - remove: function(/**string*/key) { + /** + * @ngdoc method + * @name angular.service.$cookieStore#remove + * @methodOf angular.service.$cookieStore + * + * @description + * Remove given cookie + * + * @param {string} key + */ + remove: function(key) { delete $store[key]; } }; |
