diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Angular.js | 29 | ||||
| -rw-r--r-- | src/directives.js | 560 | ||||
| -rw-r--r-- | src/filters.js | 4 |
3 files changed, 586 insertions, 7 deletions
diff --git a/src/Angular.js b/src/Angular.js index e6e7be8d..a1477e35 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -17,7 +17,7 @@ var lowercase = function (value){ return isString(value) ? value.toLowerCase() : /** * @ngdoc - * @name angular#uppercase + * @name angular.uppercase * @function * * @description Converts string to uppercase. @@ -150,6 +150,21 @@ var _undefined = undefined, * }; * }); * </pre> + * + * @example + * <script> + * angular.widget('my:time', function(compileElement){ + * compileElement.css('display', 'block'); + * return function(linkElement){ + * function update(){ + * linkElement.text('Current time is: ' + new Date()); + * setTimeout(update, 1000); + * } + * update(); + * }; + * }); + * </script> + * <my:time></my:time> */ angularWidget = extensionMap(angular, 'widget', lowercase), @@ -366,15 +381,19 @@ var _undefined = undefined, * }); * </script> * - * Formatted: <input type="text" name="data" value="<angular/>" ng:format="reverse"/><br/> - * Stored: <input type="text" name="data"/><br/> + * Formatted: + * <input type="text" name="data" value="angular" ng:format="reverse"/> + * <br/> + * + * Stored: + * <input type="text" name="data"/><br/> * <pre>{{data}}</pre> * * * @scenario * it('should store reverse', function(){ - * expect(element('.doc-example input:first').val()).toEqual('<angular/>'); - * expect(element('.doc-example input:last').val()).toEqual('>/RALUGNA<'); + * expect(element('.doc-example input:first').val()).toEqual('angular'); + * expect(element('.doc-example input:last').val()).toEqual('RALUGNA'); * * this.addFutureAction('change to XYZ', function($window, $document, done){ * $document.elements('.doc-example input:last').val('XYZ').trigger('change'); diff --git a/src/directives.js b/src/directives.js index e21dca89..7dcc7f39 100644 --- a/src/directives.js +++ b/src/directives.js @@ -1,9 +1,97 @@ +/** + * @ngdoc directive + * @name angular.directive.ng:init + * + * @description + * `ng:init` attribute allows the for initialization tasks to be executed + * before the template enters execution mode during bootstrap. + * + * @element ANY + * @param {expression} expression to eval. + * + * @example + <div ng:init="greeting='Hello'; person='World'"> + {{greeting}} {{person}}! + </div> + * + * @scenario + it('should check greeting', function(){ + expect(binding('greeting')).toBe('Hello'); + expect(binding('person')).toBe('World'); + }); + */ angularDirective("ng:init", function(expression){ return function(element){ this.$tryEval(expression, element); }; }); +/** + * @ngdoc directive + * @name angular.directive.ng:controller + * + * @description + * To support the Model-View-Controller design pattern, it is possible + * to assign behavior to a scope through `ng:controller`. The scope is + * the MVC model. The HTML (with data bindings) is the MVC view. + * The `ng:controller` directive specifies the MVC controller class + * + * @element ANY + * @param {expression} expression to eval. + * + * @example + <script type="text/javascript"> + function SettingsController() { + this.name = "John Smith"; + this.contacts = [ + {type:'phone', value:'408 555 1212'}, + {type:'email', value:'john.smith@example.org'} ]; + } + SettingsController.prototype = { + greet: function(){ + alert(this.name); + }, + addContact: function(){ + this.contacts.push({type:'email', value:'yourname@example.org'}); + }, + removeContact: function(contactToRemove) { + angular.Array.remove(this.contacts, contactToRemove); + }, + clearContact: function(contact) { + contact.type = 'phone'; + contact.value = ''; + } + }; + </script> + <div ng:controller="SettingsController"> + Name: <input type="text" name="name"/> + [ <a href="" ng:click="greet()">greet</a> ]<br/> + Contact: + <ul> + <li ng:repeat="contact in contacts"> + <select name="contact.type"> + <option>phone</option> + <option>email</option> + </select> + <input type="text" name="contact.value"/> + [ <a href="" ng:click="clearContact(contact)">clear</a> + | <a href="" ng:click="removeContact(contact)">X</a> ] + </li> + <li>[ <a href="" ng:click="addContact()">add</a> ]</li> + </ul> + </div> + * + * @scenario + it('should check controller', function(){ + expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); + expect(element('.doc-example-live li[ng\\:repeat-index="0"] input').val()).toBe('408 555 1212'); + expect(element('.doc-example-live li[ng\\:repeat-index="1"] input').val()).toBe('john.smith@example.org'); + element('.doc-example-live li:first a:contains("clear")').click(); + expect(element('.doc-example-live li:first input').val()).toBe(''); + element('.doc-example-live li:last a:contains("add")').click(); + expect(element('.doc-example-live li[ng\\:repeat-index="2"] input').val()).toBe('yourname@example.org'); + }); + */ angularDirective("ng:controller", function(expression){ this.scope(true); return function(element){ @@ -16,12 +104,75 @@ angularDirective("ng:controller", function(expression){ }; }); +/** + * @ngdoc directive + * @name angular.directive.ng:eval + * + * @description + * The `ng:eval` allows you to execute a binding which has side effects + * without displaying the result to the user. + * + * @element ANY + * @param {expression} expression to eval. + * + * @exampleDescription + * Notice that `{{` `obj.multiplied = obj.a * obj.b` `}}` has a side effect of assigning + * a value to `obj.multiplied` and displaying the result to the user. Sometimes, + * however, it is desirable to execute a side effect without showing the value to + * the user. In such a case `ng:eval` allows you to execute code without updating + * the display. + * + * @example + * <input name="obj.a" value="6" > + * * <input name="obj.b" value="2"> + * = {{obj.multiplied = obj.a * obj.b}} <br> + * <span ng:eval="obj.divide = obj.a / obj.b"></span> + * <span ng:eval="obj.updateCount = 1 + (obj.updateCount||0)"></span> + * <tt>obj.divide = {{obj.divide}}</tt><br/> + * <tt>obj.updateCount = {{obj.updateCount}}</tt> + * + * @scenario + it('should check eval', function(){ + expect(binding('obj.divide')).toBe('3'); + expect(binding('obj.updateCount')).toBe('2'); + input('obj.a').enter('12'); + expect(binding('obj.divide')).toBe('6'); + expect(binding('obj.updateCount')).toBe('3'); + }); + */ angularDirective("ng:eval", function(expression){ return function(element){ this.$onEval(expression, element); }; }); +/** + * @ngdoc directive + * @name angular.directive.ng:bind + * + * @description + * The `ng:bind` attribute asks <angular/> to replace the text content of this + * 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. + * + * @element ANY + * @param {expression} expression to eval. + * + * @exampleDescription + * Try it here: enter text in text box and watch the greeting change. + * @example + * Enter name: <input type="text" name="name" value="Whirled">. <br> + * Hello <span ng:bind="name" />! + * + * @scenario + it('should check ng:bind', function(){ + expect(using('.doc-example-live').binding('name')).toBe('Whirled'); + using('.doc-example-live').input('name').enter('world'); + expect(using('.doc-example-live').binding('name')).toBe('world'); + }); + */ angularDirective("ng:bind", function(expression, element){ element.addClass('ng-binding'); return function(element) { @@ -98,6 +249,38 @@ function compileBindTemplate(template){ return fn; } +/** + * @ngdoc directive + * @name angular.directive.ng:bind-template + * + * @description + * The `ng:bind-template` attribute specifies that the element + * text should be replaced with the template in ng:bind-template. + * Unlike ng:bind the ng:bind-template can contain multiple `{{` `}}` + * expressions. (This is required since some HTML elements + * can not have SPAN elements such as TITLE, or OPTION to name a few. + * + * @element ANY + * @param {string} template of form + * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. + * + * @exampleDescription + * Try it here: enter text in text box and watch the greeting change. + * @example + Salutation: <input type="text" name="salutation" value="Hello"><br/> + Name: <input type="text" name="name" value="World"><br/> + <pre ng:bind-template="{{salutation}} {{name}}!"></pre> + * + * @scenario + it('should check ng:bind', function(){ + expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). + toBe('Hello World!'); + using('.doc-example-live').input('salutation').enter('Greetings'); + using('.doc-example-live').input('name').enter('user'); + expect(using('.doc-example-live').binding('{{salutation}} {{name}}')). + toBe('Greetings user!'); + }); + */ angularDirective("ng:bind-template", function(expression, element){ element.addClass('ng-binding'); var templateFn = compileBindTemplate(expression); @@ -118,6 +301,55 @@ var REMOVE_ATTRIBUTES = { 'readonly':'readOnly', 'checked':'checked' }; +/** + * @ngdoc directive + * @name angular.directive.ng:bind-attr + * + * @description + * The `ng:bind-attr` attribute specifies that the element attributes + * which should be replaced by the expression in it. Unlike `ng:bind` + * the `ng:bind-attr` contains a JSON key value pairs representing + * which attributes need to be changed. You don’t usually write the + * `ng:bind-attr` in the HTML since embedding + * <tt ng:non-bindable>{{expression}}</tt> into the + * attribute directly is the preferred way. The attributes get + * translated into <span ng:bind-attr="{attr:expression}"/> at + * bootstrap time. + * + * This HTML snippet is preferred way of working with `ng:bind-attr` + * <pre> + * <a href="http://www.google.com/search?q={{query}}">Google</a> + * </pre> + * + * The above gets translated to bellow during bootstrap time. + * <pre> + * <a ng:bind-attr='{"href":"http://www.google.com/search?q={{query}}"}'>Google</a> + * </pre> + * + * @element ANY + * @param {string} attribute_json a JSON key-value pairs representing + * the attributes to replace. Each key matches the attribute + * which needs to be replaced. Each value is a text template of + * the attribute with embedded + * <tt ng:non-bindable>{{expression}}</tt>s. Any number of + * key-value pairs can be specified. + * + * @exampleDescription + * Try it here: enter text in text box and click Google. + * @example + Google for: + <input type="text" name="query" value="AngularJS"/> + <a href="http://www.google.com/search?q={{query}}">Google</a> + * + * @scenario + it('should check ng:bind-attr', function(){ + expect(using('.doc-example-live').element('a').attr('href')). + toBe('http://www.google.com/search?q=AngularJS'); + using('.doc-example-live').input('query').enter('google'); + expect(using('.doc-example-live').element('a').attr('href')). + toBe('http://www.google.com/search?q=google'); + }); + */ angularDirective("ng:bind-attr", function(expression){ return function(element){ var lastValue = {}; @@ -146,8 +378,82 @@ angularDirective("ng:bind-attr", function(expression){ }; }); +/** + * @ngdoc directive + * @name angular.directive.ng:non-bindable + * + * @description + * Sometimes it is necessary to write code which looks like + * bindings but which should be left alone by <angular/>. + * Use `ng:non-bindable` to ignore a chunk of HTML. + * + * @element ANY + * @param {string} ignore + * + * @exampleDescription + * In this example there are two location where + * <tt ng:non-bindable>{{1 + 2}}</tt> is present, but the one + * wrapped in `ng:non-bindable` is left alone + * @example + <div>Normal: {{1 + 2}}</div> + <div ng:non-bindable>Ignored: {{1 + 2}}</div> + * + * @scenario + it('should check ng:non-bindable', function(){ + expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); + expect(using('.doc-example-live').element('div:last').text()). + toMatch(/1 \+ 2/); + }); + */ angularWidget("@ng:non-bindable", noop); +/** + * @ngdoc directive + * @name angular.directive.ng:repeat + * + * @description + * `ng:repeat` instantiates a template once per item from a + * collection. The collection is enumerated with + * `ng:repeat-index` attribute starting from 0. Each template + * instance gets its own scope where the given loop variable + * is set to the current collection item and `$index` is set + * to the item index or key. + * + * NOTE: `ng:repeat` looks like a directive, but is actually a + * attribute widget. + * + * @element ANY + * @param {repeat} repeat_expression to itterate over. + * + * * `variable in expression`, where variable is the user + * defined loop variable and expression is a scope expression + * giving the collection to enumerate. For example: + * `track in cd.tracks`. + * * `(key, value) in expression`, where key and value can + * be any user defined identifiers, and expression is the + * scope expression giving the collection to enumerate. + * For example: `(name, age) in {'adam':10, 'amalie':12}`. + * + * @exampleDescription + * This example initializes the scope to a list of names and + * than uses `ng:repeat` to display every person. + * @example + <div ng:init="friends = [{name:'John', age:25}, {name:'Mary', age:28}]"> + I have {{friends.length}} friends. They are: + <ul> + <li ng:repeat="friend in friends"> + [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. + </li> + </ul> + </div> + * @scenario + it('should check ng:repeat', function(){ + var r = using('.doc-example-live').repeater('ul li'); + expect(r.count()).toBe(2); + expect(r.row(0)).toEqual(["1","John","25"]); + expect(r.row(1)).toEqual(["2","Mary","28"]); + }); + */ angularWidget("@ng:repeat", function(expression, element){ element.removeAttr('ng:repeat'); element.replaceWith(this.comment("ng:repeat: " + expression)); @@ -205,6 +511,29 @@ angularWidget("@ng:repeat", function(expression, element){ }); +/** + * @ngdoc directive + * @name angular.directive.ng:click + * + * @description + * The ng:click allows you to specify custom behavior when + * element is clicked. + * + * @element ANY + * @param {expression} expression to eval upon click. + * + * @example + <button ng:click="count = count + 1" ng:init="count=0"> + Increment + </button> + count: {{count}} + * @scenario + it('should check ng:click', function(){ + expect(binding('count')).toBe('0'); + element('.doc-example-live :button').click(); + expect(binding('count')).toBe('1'); + }); + */ /* * A directive that allows creation of custom onclick handlers that are defined as angular * expressions and are compiled and executed within the current scope. @@ -226,6 +555,35 @@ angularDirective("ng:click", function(expression, element){ /** + * @ngdoc directive + * @name angular.directive.ng:submit + * + * @description + * + * @element form + * @param {expression} expression to eval. + * + * @exampleDescription + * @example + * <form ng:submit="list.push(text);text='';" ng:init="list=[]"> + * Enter text and hit enter: + * <input type="text" name="text" value="hello"/> + * </form> + * <pre>list={{list}}</pre> + * @scenario + it('should check ng:submit', function(){ + expect(binding('list')).toBe('list=[]'); + element('.doc-example-live form input').click(); + this.addFutureAction('submit from', function($window, $document, done) { + $window.angular.element( + $document.elements('.doc-example-live form')). + trigger('submit'); + done(); + }); + expect(binding('list')).toBe('list=["hello"]'); + }); + */ +/** * Enables binding angular expressions to onsubmit events. * * Additionally it prevents the default action (which for form means sending the request to the @@ -243,6 +601,32 @@ angularDirective("ng:submit", function(expression, element) { }); +/** + * @ngdoc directive + * @name angular.directive.ng:watch + * + * @description + * The `ng:watch` allows you watch a variable and then execute + * an evaluation on variable change. + * + * @element ANY + * @param {expression} expression to eval. + * + * @exampleDescription + * Notice that the counter is incremented + * every time you change the text. + * @example + <div ng:init="counter=0" ng:watch="name: counter = counter+1"> + <input type="text" name="name" value="hello"><br/> + Change counter: {{counter}} Name: {{name}} + </div> + * @scenario + it('should check ng:watch', function(){ + expect(using('.doc-example-live').binding('counter')).toBe('1'); + using('.doc-example-live').input('name').enter('abc'); + expect(using('.doc-example-live').binding('counter')).toBe('2'); + }); + */ angularDirective("ng:watch", function(expression, element){ return function(element){ var self = this; @@ -271,10 +655,141 @@ function ngClass(selector) { }; } +/** + * @ngdoc directive + * @name angular.directive.ng:class + * + * @description + * The `ng:class` allows you to set CSS class on HTML element + * conditionally. + * + * @element ANY + * @param {expression} expression to eval. + * + * @exampleDescription + * @example + <input type="button" value="set" ng:click="myVar='ng-input-indicator-wait'"> + <input type="button" value="clear" ng:click="myVar=''"> + <br> + <span ng:class="myVar">Sample Text </span> + * + * @scenario + it('should check ng:class', function(){ + expect(element('.doc-example-live span').attr('className')).not(). + toMatch(/ng-input-indicator-wait/); + + using('.doc-example-live').element(':button:first').click(); + + expect(element('.doc-example-live span').attr('className')). + toMatch(/ng-input-indicator-wait/); + + using('.doc-example-live').element(':button:last').click(); + + expect(element('.doc-example-live span').attr('className')).not(). + toMatch(/ng-input-indicator-wait/); + }); + */ angularDirective("ng:class", ngClass(function(){return true;})); + +/** + * @ngdoc directive + * @name angular.directive.ng:class-odd + * + * @description + * The `ng:class-odd` and `ng:class-even` works exactly as + * `ng:class`, except it works in conjunction with `ng:repeat` + * and takes affect only on odd (even) rows. + * + * @element ANY + * @param {expression} expression to eval. Must be inside + * `ng:repeat`. + + * + * @exampleDescription + * @example + <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> + <li ng:repeat="name in names"> + <span ng:class-odd="'ng-format-negative'" + ng:class-even="'ng-input-indicator-wait'"> + {{name}} + </span> + </li> + </ol> + * + * @scenario + it('should check ng:class-odd and ng:class-even', function(){ + expect(element('.doc-example-live li:first span').attr('className')). + toMatch(/ng-format-negative/); + expect(element('.doc-example-live li:last span').attr('className')). + toMatch(/ng-input-indicator-wait/); + }); + */ angularDirective("ng:class-odd", ngClass(function(i){return i % 2 === 0;})); + +/** + * @ngdoc directive + * @name angular.directive.ng:class-even + * + * @description + * The `ng:class-odd` and `ng:class-even` works exactly as + * `ng:class`, except it works in conjunction with `ng:repeat` + * and takes affect only on odd (even) rows. + * + * @element ANY + * @param {expression} expression to eval. Must be inside + * `ng:repeat`. + + * + * @exampleDescription + * @example + <ol ng:init="names=['John', 'Mary', 'Cate', 'Suz']"> + <li ng:repeat="name in names"> + <span ng:class-odd="'ng-format-negative'" + ng:class-even="'ng-input-indicator-wait'"> + {{name}} + </span> + </li> + </ol> + * + * @scenario + it('should check ng:class-odd and ng:class-even', function(){ + expect(element('.doc-example-live li:first span').attr('className')). + toMatch(/ng-format-negative/); + expect(element('.doc-example-live li:last span').attr('className')). + toMatch(/ng-input-indicator-wait/); + }); + */ angularDirective("ng:class-even", ngClass(function(i){return i % 2 === 1;})); +/** + * @ngdoc directive + * @name angular.directive.ng:show + * + * @description + * The `ng:show` and `ng:hide` allows you to show or hide a portion + * of the HTML conditionally. + * + * @element ANY + * @param {expression} expression if truthy then the element is + * shown or hidden respectively. + * + * @exampleDescription + * @example + Click me: <input type="checkbox" name="checked"><br/> + Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/> + Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span> + * + * @scenario + it('should check ng:show / ng:hide', function(){ + expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live span:first:visible').count()).toEqual(1); + expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); + }); + */ angularDirective("ng:show", function(expression, element){ return function(element){ this.$onEval(function(){ @@ -283,6 +798,35 @@ angularDirective("ng:show", function(expression, element){ }; }); +/** + * @ngdoc directive + * @name angular.directive.ng:hide + * + * @description + * The `ng:show` and `ng:hide` allows you to show or hide a portion + * of the HTML conditionally. + * + * @element ANY + * @param {expression} expression if truthy then the element is + * shown or hidden respectively. + * + * @exampleDescription + * @example + Click me: <input type="checkbox" name="checked"><br/> + Show: <span ng:show="checked">I show up when you checkbox is checked?</span> <br/> + Hide: <span ng:hide="checked">I hide when you checkbox is checked?</span> + * + * @scenario + it('should check ng:show / ng:hide', function(){ + expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); + expect(element('.doc-example-live span:last:visible').count()).toEqual(1); + + input('checked').check(); + + expect(element('.doc-example-live span:first:visible').count()).toEqual(1); + expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); + }); + */ angularDirective("ng:hide", function(expression, element){ return function(element){ this.$onEval(function(){ @@ -291,6 +835,22 @@ angularDirective("ng:hide", function(expression, element){ }; }); +/** + * @ngdoc directive + * @name angular.directive.ng:style + * + * @description + * + * @element ANY + * @param {expression} expression to eval. + * + * @exampleDescription + * @example + * + * @scenario + it('should check ng:style', function(){ + }); + */ angularDirective("ng:style", function(expression, element){ return function(element){ var resetStyle = getStyle(element); diff --git a/src/filters.js b/src/filters.js index 2fec3cd2..2f41d12c 100644 --- a/src/filters.js +++ b/src/filters.js @@ -23,8 +23,8 @@ it('should update', function(){ input('amount').enter('-1234'); expect(binding('amount | currency')).toBe('$-1,234.00'); - expect(element('span[ng\\:bind="amount | currency"]').attr('class')). - toMatch(/ng-format-negative/); + expect(element('.doc-example-live .ng-binding').attr('className')). + toMatch(/ng-format-negative/); }); */ angularFilter.currency = function(amount){ |
