diff options
| author | Misko Hevery | 2010-11-12 15:16:33 -0800 |
|---|---|---|
| committer | Misko Hevery | 2010-11-15 10:04:17 -0800 |
| commit | 7e6f9992216157a10a64a86fe526f61f9f57e43f (patch) | |
| tree | e172da3909e44e8eb9a80eb86880fbb9cd814290 /src | |
| parent | 625cc7609cfd1a0a4fcecfd55875e548518a72a8 (diff) | |
| download | angular.js-7e6f9992216157a10a64a86fe526f61f9f57e43f.tar.bz2 | |
added remaining directives and search box.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Angular.js | 191 | ||||
| -rw-r--r-- | src/Compiler.js | 51 | ||||
| -rw-r--r-- | src/directives.js | 18 | ||||
| -rw-r--r-- | src/markups.js | 51 | ||||
| -rw-r--r-- | src/parser.js | 2 | ||||
| -rw-r--r-- | src/widgets.js | 109 |
6 files changed, 417 insertions, 5 deletions
diff --git a/src/Angular.js b/src/Angular.js index a1477e35..c40a7c67 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1,3 +1,12 @@ +/** + * @ngdoc overview + * @name angular + * @namespace Namespace for angular. + * @description + * Hello world! + * + * @example + */ //////////////////////////////////// if (typeof document.getAttribute == $undefined) @@ -91,6 +100,67 @@ var _undefined = undefined, angular = window[$angular] || (window[$angular] = {}), angularTextMarkup = extensionMap(angular, 'markup'), angularAttrMarkup = extensionMap(angular, 'attrMarkup'), + /** + * @ngdoc overview + * @name angular.directive + * @namespace Namespace for all directives. + * @description + * A directive is an XML attribute that you can use in an existing HTML + * element type or in a DOM element type that you create using + * `angular.widget`, to modify that element's properties. You can use + * any number of directives per element. + * + * For example, you can add the ng:bind directive as an attribute of an + * HTML span element, as in `<span ng:bind="1+2"></span>`. + * How does this work? The compiler passes the attribute value `1+2` + * to the ng:bind extension, which in turn tells the Scope to watch + * that expression and report changes. On any change it sets the span + * text to the expression value. + * + * Here's how to define ng:bind: + * <pre> + angular.directive('ng:bind', function(expression, compiledElement) { + var compiler = this; + return function(linkElement) { + var currentScope = this; + currentScope.$watch(expression, function(value) { + linkElement.text(value); + }); + }; + }); + * </pre> + * + * ## Directive vs. Attribute Widget + * Both attribute widgets and directives can compile a DOM element + * attribute. So why have two different ways to do the same thing? + * The answer is that order matters, but you have no control over + * the order in which attributes are read. To solve this we + * apply attribute widget before the directive. + * + * For example, consider this piece of HTML, which uses the + * directives `ng:repeat`, `ng:init`, and `ng:bind`: + * <pre> + <ul ng:init="people=['mike', 'mary']"> + <li ng:repeat="person in people" ng:init="a=a+1" ng:bind="person"></li> + </ul> + * </pre> + * + * Notice that the order of execution matters here. We need to + * execute ng:repeat before we run the `ng:init` and `ng:bind` + * on the `<li/>;`. This is because we want to run the + * `ng:init="a=a+1` and `ng:bind="person"` once for each + * person in people. We could not have used directive to + * create this template because attributes are read in an + * unspecified order and there is no way of guaranteeing + * that the repeater attribute would execute first. Using + * the `ng:repeat` attribute directive ensures that we can + * transform the DOM element into a template. + * + * Widgets run before directives. Widgets are expected to + * manipulate the DOM whereas directives are not expected + * to manipulate the DOM, and they run last. + * + */ angularDirective = extensionMap(angular, 'directive'), /** @@ -332,7 +402,7 @@ var _undefined = undefined, * The formatters are responsible for translating user readable text in an input widget to a * data model stored in an application. * - * # Writting your own Fromatter + * # Writting your own Formatter * Writing your own formatter is easy. Just register a pair of JavaScript functions with * `angular.formatter`. One function for parsing user input text to the stored form, * and one for formatting the stored data to user-visible text. @@ -391,7 +461,7 @@ var _undefined = undefined, * * * @scenario - * it('should store reverse', function(){ + * iit('should store reverse', function(){ * expect(element('.doc-example input:first').val()).toEqual('angular'); * expect(element('.doc-example input:last').val()).toEqual('RALUGNA'); * @@ -399,7 +469,7 @@ var _undefined = undefined, * $document.elements('.doc-example input:last').val('XYZ').trigger('change'); * done(); * }); - * expect(element('input:first').val()).toEqual('zyx'); + * expect(element('.doc-example input:first').val()).toEqual('zyx'); * }); */ angularFormatter = extensionMap(angular, 'formatter'), @@ -767,6 +837,121 @@ function toKeyValue(obj) { return parts.length ? parts.join('&') : ''; } +/** + * @ngdoc directive + * @name angular.directive.ng:autobind + * @element script + * + * @description + * This section explains how to bootstrap your application to + * the <angular/> environment using either the + * `angular-x.x.x.js` or `angular-x.x.x.min.js` script. + * + * ## The bootstrap code + * Note that there are two versions of the bootstrap code that you can use: + * + * * `angular-x.x.x.js` - this file is unobfuscated, uncompressed, and thus + * human-readable. Note that despite the name of the file, there is + * no additional functionality built in to help you debug your + * application; it has the prefix debug because you can read + * the source code. + * * `angular-x.x.x.min.js` - this is a compressed and obfuscated version + * of `angular-x.x.x.js`. You might want to use this version if you + * want to load a smaller but functionally equivalent version of the + * code in your application. Note: this minified version was created + * using the Closure Compiler. + * + * + * ## Auto bind using: <tt>ng:autobind</tt> + * The simplest way to get an <angular/> application up and running is by + * inserting a script tag in your HTML file that bootstraps the + * `http://code.angularjs.org/angular-x.x.x.min.js` code and uses the + * special `ng:autobind` attribute, like in this snippet of HTML: + * + * <pre> + <!doctype html> + <html xmlns:ng="http://angularjs.org"> + <head> + <script type="text/javascript" ng:autobind + src="http://code.angularjs.org/angular-0.9.3.min.js"></script> + </head> + <body> + Hello {{'world'}}! + </body> + </html> + * </pre> + * + * The `ng:autobind` attribute tells <angular/> to compile and manage + * the whole HTML document. The compilation occurs in the page's + * `onLoad` handler. Note that you don't need to explicitly add an + * `onLoad` event; auto bind mode takes care of all the magic for you. + * + * # Manual Bind + * Using autobind mode is a handy way to start using <angular/>, but + * advanced users who want more control over the initialization process + * might prefer to use manual bind mode instead. + * + * The best way to get started with manual bind mode is to look at the + * magic behind `ng:autobind` by writing out each step of the autobind + * process explicitly. Note that the following code is equivalent to + * the code in the previous section. + * + * <pre> + <!doctype html> + <html xmlns:ng="http://angularjs.org"> + <head> + <script type="text/javascript" ng:autobind + src="http://code.angularjs.org/angular-0.9.3.min.js"></script> + <script type="text/javascript"> + (function(window, previousOnLoad){ + window.onload = function(){ + try { (previousOnLoad||angular.noop)(); } catch(e) {} + angular.compile(window.document).$init(); + }; + })(window, window.onload); + </script> + </head> + <body> + Hello {{'World'}}! + </body> + </html> + * </pre> + * + * This is the sequence that your code should follow if you're writing + * your own manual binding code: + * + * * After the page is loaded, find the root of the HTML template, + * which is typically the root of the document. + * * Run the HTML compiler, which converts the templates into an + * executable, bi-directionally bound application. + * + * #XML Namespace + * *IMPORTANT:* When using <angular/> you must declare the ng namespace + * using the xmlsn tag. If you don't declare the namespace, + * Internet Explorer does not render widgets properly. + * + * <pre> + * <html xmlns:ng="http://angularjs.org"> + * </pre> + * + * # Create your own namespace + * If you want to define your own widgets, you must create your own + * namespace and use that namespace to form the fully qualified + * widget name. For example, you could map the alias `my` to your + * domain and create a widget called my:widget. To create your own + * namespace, simply add another xmlsn tag to your page, create an + * alias, and set it to your unique domain: + * + * <pre> + * <html xmlns:ng="http://angularjs.org" xmlns:my="http://mydomain.com"> + * </pre> + * + * # Global Object + * The <angular/> script creates a single global variable `angular` + * in the global namespace. All APIs are bound to fields of this + * global object. + * + */ function angularInit(config){ if (config.autobind) { // TODO default to the source of angular.js diff --git a/src/Compiler.js b/src/Compiler.js index b0210247..680ead11 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -109,6 +109,57 @@ Compiler.prototype = { }; }, + + /** + * @ngdoc directive + * @name angular.directive.ng:eval-order + * + * @description + * Normally the view is updated from top to bottom. This usually is + * not a problem, but under some circumstances the values for data + * is not available until after the full view is computed. If such + * values are needed before they are computed the order of + * evaluation can be change using ng:eval-order + * + * @element ANY + * @param {integer|string=} [priority=0] priority integer, or FIRST, LAST constant + * + * @exampleDescription + * try changing the invoice and see that the Total will lag in evaluation + * @example + <div>TOTAL: without ng:eval-order {{ items.$sum('total') | currency }}</div> + <div ng:eval-order='LAST'>TOTAL: with ng:eval-order {{ items.$sum('total') | currency }}</div> + <table ng:init="items=[{qty:1, cost:9.99, desc:'gadget'}]"> + <tr> + <td>QTY</td> + <td>Description</td> + <td>Cost</td> + <td>Total</td> + <td></td> + </tr> + <tr ng:repeat="item in items"> + <td><input name="item.qty"/></td> + <td><input name="item.desc"/></td> + <td><input name="item.cost"/></td> + <td>{{item.total = item.qty * item.cost | currency}}</td> + <td><a href="" ng:click="items.$remove(item)">X</a></td> + </tr> + <tr> + <td colspan="3"><a href="" ng:click="items.$add()">add</a></td> + <td>{{ items.$sum('total') | currency }}</td> + </tr> + </table> + * + * @scenario + it('should check ng:format', function(){ + expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99'); + expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$9.99'); + input('item.qty').enter('2'); + expect(using('.doc-example-live div:first').binding("items.$sum('total')")).toBe('$9.99'); + expect(using('.doc-example-live div:last').binding("items.$sum('total')")).toBe('$19.98'); + }); + */ + templatize: function(element, elementIndex, priority){ var self = this, widget, diff --git a/src/directives.js b/src/directives.js index 50901cbe..e359d6cc 100644 --- a/src/directives.js +++ b/src/directives.js @@ -155,7 +155,7 @@ angularDirective("ng:eval", function(expression){ * HTML element with the value of the given expression and kept it up to * date when the expression's value changes. Usually you just write * {{expression}} and let <angular/> compile it into - * <span ng:bind="expression"></span> at bootstrap time. + * `<span ng:bind="expression"></span>` at bootstrap time. * * @element ANY * @param {expression} expression to eval. @@ -649,6 +649,7 @@ angularDirective("ng:submit", function(expression, element) { expect(using('.doc-example-live').binding('counter')).toBe('3'); }); */ +//TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) angularDirective("ng:watch", function(expression, element){ return function(element){ var self = this; @@ -862,15 +863,28 @@ angularDirective("ng:hide", function(expression, element){ * @name angular.directive.ng:style * * @description + * The ng:style allows you to set CSS style on an HTML element conditionally. * * @element ANY - * @param {expression} expression to eval. + * @param {expression} expression which evals to an object whes key's are + * CSS style names and values are coresponding values for those + * CSS keys. * * @exampleDescription * @example + <input type="button" value="set" ng:click="myStyle={color:'red'}"> + <input type="button" value="clear" ng:click="myStyle={}"> + <br/> + <span ng:style="myStyle">Sample Text</span> + <pre>myStyle={{myStyle}}</pre> * * @scenario it('should check ng:style', function(){ + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); + element('.doc-example-live :button[value=set]').click(); + expect(element('.doc-example-live span').css('color')).toBe('red'); + element('.doc-example-live :button[value=clear]').click(); + expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); }); */ angularDirective("ng:style", function(expression, element){ diff --git a/src/markups.js b/src/markups.js index 159d7b12..57a209b7 100644 --- a/src/markups.js +++ b/src/markups.js @@ -68,6 +68,57 @@ angularTextMarkup('OPTION', function(text, textNode, parentElement){ } }); +/** + * @ngdoc directive + * @name angular.directive.ng:href + * + * @description + * Using <angular/> markup like {{hash}} in an href attribute makes + * the page open to a wrong URL, ff the user clicks that link before + * angular has a chance to replace the {{hash}} with actual URL, the + * link will be broken and will most likely return a 404 error. + * The `ng:href` solves this problem by placing the `href` in the + * `ng:` namespace. + * + * The buggy way to write it: + * <pre> + * <a href="http://www.gravatar.com/avatar/{{hash}}"/> + * </pre> + * + * The correct way to write it: + * <pre> + * <a ng:href="http://www.gravatar.com/avatar/{{hash}}"/> + * </pre> + * + * @element ANY + * @param {template} template any string which can contain `{{}}` markup. + */ + +/** + * @ngdoc directive + * @name angular.directive.ng:src + * + * @description + * Using <angular/> markup like `{{hash}}` in a `src` attribute doesn't + * work right: The browser will fetch from the URL with the literal + * text `{{hash}}` until <angular/> replaces the expression inside + * `{{hash}}`. The `ng:src` attribute solves this problem by placing + * the `src` attribute in the `ng:` namespace. + * + * The buggy way to write it: + * <pre> + * <img src="http://www.gravatar.com/avatar/{{hash}}"/> + * </pre> + * + * The correct way to write it: + * <pre> + * <img ng:src="http://www.gravatar.com/avatar/{{hash}}"/> + * </pre> + * + * @element ANY + * @param {template} template any string which can contain `{{}}` markup. + */ + var NG_BIND_ATTR = 'ng:bind-attr'; var SPECIAL_ATTRS = {'ng:src': 'src', 'ng:href': 'href'}; angularAttrMarkup('{{}}', function(value, name, element){ diff --git a/src/parser.js b/src/parser.js index 97d5740d..85b9c651 100644 --- a/src/parser.js +++ b/src/parser.js @@ -244,6 +244,7 @@ function parser(text, json){ statements: statements, validator: validator, filter: filter, + //TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) watch: watch }; @@ -624,6 +625,7 @@ function parser(text, json){ }; } + //TODO: delete me, since having watch in UI is logic in UI. (leftover form getangular) function watch () { var decl = []; while(hasTokens()) { diff --git a/src/widgets.js b/src/widgets.js index e639e32b..0ebdd1d5 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -165,6 +165,82 @@ function compileValidator(expr) { return parser(expr).validator()(); } +/** + * @ngdoc directive + * @name angular.directive.ng:validate + * + * @description + * This directive validates the user input. If the input does not + * pass validation, this sets an `ng-validation-error` CSS class and + * an `ng:error` attribute on the input element. Visit validators to + * find out more. + * + * @element INPUT + * @css ng-validation-error + * @param {function} validation call this function to validate input + * falsy return means validation passed, To return error, simply + * return the error string. + * + * @exampleDescription + * @example + I don't validate: <input type="text" name="value"><br/> + I cannot be blank: <input type="text" name="value" ng:required><br/> + I need an integer or nothing: <input type="text" name="value" ng:validate="integer"><br/> + I must have an integer: <input type="text" name="value" ng:required ng:validate="integer"><br/> + * + * @scenario + it('should check ng:validate', function(){ + expect(element('.doc-example-live :input:last').attr('className')).toMatch(/ng-validation-error/); + input('value').enter('123'); + expect(element('.doc-example-live :input:last').attr('className')).not().toMatch(/ng-validation-error/); + }); + */ +/** + * @ngdoc directive + * @name angular.directive.ng:required + * + * @description + * This directive requires the user input to be present. + * + * @element INPUT + * @css ng-validation-error + * + * @exampleDescription + * @example + I cannot be blank: <input type="text" name="value" ng:required><br/> + * + * @scenario + it('should check ng:required', function(){ + expect(element('.doc-example-live :input').attr('className')).toMatch(/ng-validation-error/); + input('value').enter('123'); + expect(element('.doc-example-live :input').attr('className')).not().toMatch(/ng-validation-error/); + }); + */ +/** + * @ngdoc directive + * @name angular.directive.ng:format + * + * @description + * The `ng:format` directive formats stored data to user-readable + * text and parses the text back to the stored form. You might + * find this useful for example if you collect user input in a + * text field but need to store the data in the model as a list. + * + * @element INPUT + * + * @exampleDescription + * @example + Enter a comma separated list of items: + <input type="text" name="list" ng:format="list" value="table, chairs, plate"> + <pre>list={{list}}</pre> + * + * @scenario + it('should check ng:format', function(){ + expect(binding('list')).toBe('list=["table","chairs","plate"]'); + input('list').enter(',,, a ,,,'); + expect(binding('list')).toBe('list=["a"]'); + }); + */ function valueAccessor(scope, element) { var validatorName = element.attr('ng:validate') || NOOP, validator = compileValidator(validatorName), @@ -320,6 +396,39 @@ function radioInit(model, view, element) { view.set(modelValue); } +/** + * @ngdoc directive + * @name angular.directive.ng:change + * + * @description + * The directive executes an expression whenever the input widget changes. + * + * @element INPUT + * @param {expression} expression to execute. + * + * @exampleDescription + * @example + <div ng:init="checkboxCount=0; textCount=0"></div> + <input type="text" name="text" ng:change="textCount = 1 + textCount"> + changeCount {{textCount}}<br/> + <input type="checkbox" name="checkbox" ng:change="checkboxCount = 1 + checkboxCount"> + changeCount {{checkboxCount}}<br/> + * + * @scenario + it('should check ng:change', function(){ + expect(binding('textCount')).toBe('0'); + expect(binding('checkboxCount')).toBe('0'); + + using('.doc-example-live').input('text').enter('abc'); + expect(binding('textCount')).toBe('1'); + expect(binding('checkboxCount')).toBe('0'); + + + using('.doc-example-live').input('checkbox').check(); + expect(binding('textCount')).toBe('1'); + expect(binding('checkboxCount')).toBe('1'); + }); + */ function inputWidget(events, modelAccessor, viewAccessor, initFn) { return function(element) { var scope = this, |
