From 6b8ed42670039f53d2b20dc1079f742840f62ae9 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 10 Nov 2010 12:02:49 -0800 Subject: Added Directives --- src/Angular.js | 29 ++- src/directives.js | 560 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/filters.js | 4 +- 3 files changed, 586 insertions(+), 7 deletions(-) (limited to 'src') 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, * }; * }); * + * + * @example + * + * */ angularWidget = extensionMap(angular, 'widget', lowercase), @@ -366,15 +381,19 @@ var _undefined = undefined, * }); * * - * Formatted:
- * Stored:
+ * Formatted: + * + *
+ * + * Stored: + *
*
{{data}}
* * * @scenario * it('should store reverse', function(){ - * expect(element('.doc-example input:first').val()).toEqual(''); - * 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 +
+ {{greeting}} {{person}}! +
+ * + * @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 + +
+ Name: + [ greet ]
+ Contact: + +
+ * + * @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 + * + * * + * = {{obj.multiplied = obj.a * obj.b}}
+ * + * + * obj.divide = {{obj.divide}}
+ * obj.updateCount = {{obj.updateCount}} + * + * @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 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 compile it into + * 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: .
+ * Hello ! + * + * @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 + * {{ expression }} to eval. + * + * @exampleDescription + * Try it here: enter text in text box and watch the greeting change. + * @example + Salutation:
+ Name:
+

+ * 
+ * @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 
+ * {{expression}} into the 
+ * attribute directly is the preferred way. The attributes get
+ * translated into  at
+ * bootstrap time.
+ * 
+ * This HTML snippet is preferred way of working with `ng:bind-attr`
+ * 
+ *   Google
+ * 
+ * + * The above gets translated to bellow during bootstrap time. + *
+ *   Google
+ * 
+ * + * @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 + * {{expression}}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: + + Google + * + * @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 . + * Use `ng:non-bindable` to ignore a chunk of HTML. + * + * @element ANY + * @param {string} ignore + * + * @exampleDescription + * In this example there are two location where + * {{1 + 2}} is present, but the one + * wrapped in `ng:non-bindable` is left alone + * @example +
Normal: {{1 + 2}}
+
Ignored: {{1 + 2}}
+ * + * @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 +
+ I have {{friends.length}} friends. They are: +
    +
  • + [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. +
  • +
+
+ * @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 + + 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. @@ -225,6 +554,35 @@ angularDirective("ng:click", function(expression, element){ }); +/** + * @ngdoc directive + * @name angular.directive.ng:submit + * + * @description + * + * @element form + * @param {expression} expression to eval. + * + * @exampleDescription + * @example + *
+ * Enter text and hit enter: + * + *
+ *
list={{list}}
+ * @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. * @@ -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 +
+
+ Change counter: {{counter}} Name: {{name}} +
+ * @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 + + +
+ Sample Text      + * + * @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 +
    +
  1. + + {{name}}       + +
  2. +
+ * + * @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 +
    +
  1. + + {{name}}       + +
  2. +
+ * + * @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:
+ Show: I show up when you checkbox is checked?
+ Hide: I hide when you checkbox is checked? + * + * @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:
+ Show: I show up when you checkbox is checked?
+ Hide: I hide when you checkbox is checked? + * + * @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){ -- cgit v1.2.3