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 | |
| parent | 625cc7609cfd1a0a4fcecfd55875e548518a72a8 (diff) | |
| download | angular.js-7e6f9992216157a10a64a86fe526f61f9f57e43f.tar.bz2 | |
added remaining directives and search box.
| -rw-r--r-- | docs/collect.js | 20 | ||||
| -rw-r--r-- | docs/directive.template | 2 | ||||
| -rw-r--r-- | docs/docs-scenario.js | 4 | ||||
| -rw-r--r-- | docs/formatter.template | 2 | ||||
| -rw-r--r-- | docs/index.html | 105 | ||||
| -rw-r--r-- | docs/overview.template | 2 | ||||
| -rw-r--r-- | docs/spec/collectSpec.js | 4 | ||||
| -rw-r--r-- | docs/validator.template | 2 | ||||
| -rw-r--r-- | docs/widget.template | 2 | ||||
| -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 | 
15 files changed, 519 insertions, 46 deletions
| diff --git a/docs/collect.js b/docs/collect.js index 08bb6be3..29d2a33f 100644 --- a/docs/collect.js +++ b/docs/collect.js @@ -7,8 +7,7 @@ var fs       = require('fs'),      Showdown = require('showdown').Showdown;  var documentation = { -    section:{}, -    all:[] +    pages:[]  };  var SRC_DIR = "docs/"; @@ -24,9 +23,7 @@ var work = callback.chain(function () {          parseNgDoc(doc);          if (doc.ngdoc) {            delete doc.raw.text; -          var section = documentation.section; -          (section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc); -          documentation.all.push(doc); +          documentation.pages.push(doc);            console.log('Found:', doc.ngdoc + ':' + doc.shortName);            mergeTemplate(                      doc.ngdoc + '.template', @@ -38,6 +35,7 @@ var work = callback.chain(function () {  }).onError(function(err){    console.log('ERROR:', err.stack || err);  }).onDone(function(){ +  documentation.pages.sort(function(a,b){ return a.name == b.name ? 0:(a.name < b.name ? -1 : 1);});    mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain());    mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain());    copy('docs-scenario.html', callback.chain()); @@ -128,12 +126,16 @@ function escapedHtmlTag(doc, name, value) {  function markdownTag(doc, name, value) {    doc[name] = markdown(value.replace(/^#/gm, '##')). -    replace(/\<pre\>/gmi, '<pre class="brush: xml; brush: js;" ng:non-bindable>'); +    replace(/\<pre\>/gmi, '<div ng:non-bindable><pre class="brush: js; html-script: true; toolbar: false;">'). +    replace(/\<\/pre\>/gmi, '</pre></div>');  }  function markdown(text) {    text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>'); -  return 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>'); +  text = new Showdown.converter().makeHtml(text); +  return text;  }  function markdownNoP(text) { @@ -161,8 +163,8 @@ var TAG = {    element: valueTag,    name: function(doc, name, value) {      doc.name = value; -    var match = value.match(/^angular[\.\#](([^\.]+)\.(.*)|(.*))/); -    doc.shortName  = match[3] || match[4]; +    doc.shortName  = value.split(/\./).pop(); +    doc.depth = value.split(/\./).length - 1;    },    param: function(doc, name, value){      doc.param = doc.param || []; diff --git a/docs/directive.template b/docs/directive.template index 84b87ca7..a3a11056 100644 --- a/docs/directive.template +++ b/docs/directive.template @@ -35,3 +35,5 @@    <doc:scenario>{{{scenario}}}</doc:scenario>  </doc:example>  {{/example}} + +<script>SyntaxHighlighter.highlight();</script> diff --git a/docs/docs-scenario.js b/docs/docs-scenario.js index 08d0e91b..7383bd7f 100644 --- a/docs/docs-scenario.js +++ b/docs/docs-scenario.js @@ -1,4 +1,4 @@ -{{#all}} +{{#pages}}  describe('{{name}}', function(){    beforeEach(function(){      browser().navigateTo('index.html#{{name}}'); @@ -6,4 +6,4 @@ describe('{{name}}', function(){    // {{raw.file}}:{{raw.line}}  {{{scenario}}}  }); -{{/all}} +{{/pages}} diff --git a/docs/formatter.template b/docs/formatter.template index af95f729..b0871a2d 100644 --- a/docs/formatter.template +++ b/docs/formatter.template @@ -31,3 +31,5 @@ var modelValue = angular.formatter.{{shortName}}.parse(userInputString);    <doc:scenario>{{{scenario}}}</doc:scenario>  </doc:example>  {{/example}} + +<script>SyntaxHighlighter.highlight();</script> diff --git a/docs/index.html b/docs/index.html index b9dfebc0..2533caf3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -26,18 +26,22 @@    	  };    	  this.getCurrentPartial = function(){ -  	    if ($location.hashPath.match(/^angular\./)) { -  	      this.partialUrl = './' + $location.hashPath + '.html'; -  	    } -  	    return this.partialUrl; +        return './' + this.getTitle() + '.html';    	  }    	  this.getTitle = function(){ -  	    if ($location.hashPath.match(/^angular\./)) { -  	      this.partialTitle = $location.hashPath; +        var hashPath = $location.hashPath || 'angular'; +        if (hashPath.match(/^angular/)) { +  	      this.partialTitle = hashPath;    	    }    	    return this.partialTitle;    	  } +  	   +  	  this.getClass = function(page) { +  	    return 'level-' + page.depth +  +  	     (page.name == this.getTitle() ? ' selected' : ''); +  	  }; +  	      	}    </script>    <style type="text/css" media="screen"> @@ -47,12 +51,20 @@        margin: 0;        padding: 0;      } - -    #sidebar { -      width: 15em; -      float: left; +     +    a { +      color: blue;      } - +     +    .nav-section { +      margin-left: 1em; +      margin-top: 0.5em; +    } +     +    .section-title { +      float: right; +    } +          #header {        background-color: #F2C200;        margin-bottom: 1em; @@ -95,11 +107,27 @@      #section h1 {        font-family: monospace;        margin-top: 0; +      padding-bottom: 5px; +      border-bottom: 1px solid #CCC;      } -    #sidebar h2 { -      font-size: 1.2em; -      margin: 5px 5px 5px 0.8em; +    #sidebar { +      width: 180px; +      float: left; +      padding-left: 1em; +    } +     +    #sidebar a { +      text-decoration: none; +    } +     +    #sidebar a:hover { +      text-decoration: underline; +    } +     +    #sidebar input { +      width: 175px; +      margin-bottom: 1em;      }      #sidebar ul { @@ -110,18 +138,39 @@      }      #sidebar ul li { -      margin: 0; -      padding: 1px 1px 1px 1.5em;      } +     +    #sidebar ul li a { +      display: block; +      padding: 2px 2px 2px 4px; +    }  -    .nav-section { +    #sidebar ul li.selected a { +      background-color: #DDD; +      border-radius: 5px; +      -moz-border-radius: 5px; +      border: 1px solid #CCC; +      padding: 1px 1px 1px 3px; +    } +     +    #sidebar ul li.level-0 { +      margin-left: 0em; +      font-weight: bold; +      font-size: 1.2em; +    } +     +    #sidebar ul li.level-1 {        margin-left: 1em; -      margin-top: 0.5em; +      margin-top: 5px; +      font-size: 1.1em; +      font-weight: bold;      } -    .section-title { -      float: right; +    #sidebar ul li.level-2 { +      margin-left: 2em; +      font-family: monospace;      } +        </style>    <title><angular/>: {{getTitle()}}</title>  </head> @@ -133,14 +182,14 @@      </h1>    </div>    <div id="sidebar" class="nav"> -    <div ng:repeat="(name, type) in docs.section" class="nav-section"> -      <h2>{{name}}</h2> -      <ul> -        <li ng:repeat="page in type.$orderBy('shortName')"> -          <a href="{{getUrl(page)}}" ng:click="">{{page.shortName}}</a> -        </li> -      </ul> -    </div> +   <div> +     <input type="text" name="filterText" placeholder="search documentaiton"/> +	   <ul> +	     <li ng:repeat="page in docs.pages.$filter(filterText)" ng:class="getClass(page)"> +	       <a href="{{getUrl(page)}}" ng:click="">{{page.shortName}}</a> +	     </li> +	   </ul> +   </div>    </div>    <div id="section">      <ng:include src="getCurrentPartial()"></ng:include> diff --git a/docs/overview.template b/docs/overview.template index 7af05ff4..b05a9b1a 100644 --- a/docs/overview.template +++ b/docs/overview.template @@ -13,3 +13,5 @@    <doc:scenario>{{{scenario}}}</doc:scenario>  </doc:example>  {{/example}} + +<script>SyntaxHighlighter.highlight();</script> diff --git a/docs/spec/collectSpec.js b/docs/spec/collectSpec.js index 4df04b9f..b810f823 100644 --- a/docs/spec/collectSpec.js +++ b/docs/spec/collectSpec.js @@ -43,8 +43,8 @@ describe('collect', function(){      describe('@describe', function(){        it('should support pre blocks', function(){ -        TAG.description(doc, 'description', '<pre class="brush: xml;" ng:non-bindable>abc</pre>'); -        expect(doc.description).toEqual('<pre class="brush: xml;" ng:non-bindable>abc</pre>'); +        TAG.description(doc, 'description', '<pre>abc</pre>'); +        expect(doc.description).toEqual('<div ng:non-bindable><pre class="brush: js; html-script: true; toolbar: false;">abc</pre></div>');        });        describe('@example', function(){ diff --git a/docs/validator.template b/docs/validator.template index 63d17072..f7b4e888 100644 --- a/docs/validator.template +++ b/docs/validator.template @@ -37,3 +37,5 @@ angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {    <doc:scenario>{{{scenario}}}</doc:scenario>  </doc:example>  {{/example}} + +<script>SyntaxHighlighter.highlight();</script> diff --git a/docs/widget.template b/docs/widget.template index 4a20629e..7ca3fcf4 100644 --- a/docs/widget.template +++ b/docs/widget.template @@ -37,3 +37,5 @@    <doc:scenario>{{{scenario}}}</doc:scenario>  </doc:example>  {{/example}} + +<script>SyntaxHighlighter.highlight();</script> 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, | 
