aboutsummaryrefslogtreecommitdiffstats
path: root/src/directives.js
blob: adcfa5084fa17c9aa07b7d751f14f51d160f7d85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
angularDirective("ng-init", function(expression){
  return function(){
    this.$eval(expression);
  };
});

angularDirective("ng-eval", function(expression){
  return function(){
    this.$addEval(expression);
  };
});

angularDirective("ng-bind", function(expression){
  return function(element) {
    this.$watch(expression, function(value){
      element.text(value);
    });
  };
});

angularDirective("ng-bind-attr", function(expression){
  return function(element){
    this.$watch(expression, bind(element, element.attr));
  };
});

angularDirective("ng-non-bindable", function(){
  this.descend(false);
});

angularDirective("ng-repeat", function(expression, element){
  var reference = this.reference("ng-repeat: " + expression),
      r = element.removeAttr('ng-repeat'),
      template = this.compile(element),
      match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/),
      lhs, rhs, valueIdent, keyIdent;
  if (! match) {
    throw "Expected ng-repeat in form of 'item in collection' but got '" +
      expression + "'.";
  }
  lhs = match[1];
  rhs = match[2];
  match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
  if (!match) {
    throw "'item' in 'item in collection' should be identifier or (key, value) but got '" +
      keyValue + "'.";
  }
  valueIdent = match[3] || match[1];
  keyIdent = match[2];

  var parent = element.parent();
  element.replaceWith(reference);
  return function(){
    var children = [],
        currentScope = this;
    this.$addEval(rhs, function(items){
      var index = 0, childCount = children.length, childScope, lastElement = reference;
      foreach(items || [], function(value, key){
        if (index < childCount) {
          // reuse existing child
          childScope = children[index];
        } else {
          // grow children
          childScope = template(element.clone(), currentScope);
          childScope.init();
          childScope.scope.set('$index', index);
          childScope.element.attr('ng-index', index);
          lastElement.after(childScope.element);
          children.push(childScope);
        }
        childScope.scope.set(valueIdent, value);
        if (keyIdent) childScope.scope.set(keyIdent, key);
        childScope.scope.updateView();
        lastElement = childScope.element;
        index ++;
      });
      // shrink children
      while(children.length > index) {
        children.pop().element.remove();
      }
    });
  };
}, {exclusive: true});

angularDirective("ng-action", function(expression, element){
  return function(){
    var self = this;
    element.click(function(){
      self.$eval(expression);
    });
  };
});

angularDirective("ng-watch", function(expression, element){
  var match = expression.match(/^([^.]*):(.*)$/);
  if (!match) {
    throw "Expecting watch expression 'ident_to_watch: watch_statement' got '"
        + expression + "'";
  }
  return function(){
    this.$watch(match[1], match[2]);
  };
});

//Styling
//
//ng-class
//ng-class-odd, ng-class-even
//ng-style
//ng-show, ng-hide

/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////
/////////////////////////////////////////





//widget related
//ng-validate, ng-required, ng-formatter
//ng-error

//ng-scope ng-controller????
)).not().toMatch(/ng-validation-error/); input('n1').enter('1.x'); expect(n1.attr('className')).toMatch(/ng-validation-error/); var n2 = element('.doc-example :input[name=n2]'); expect(n2.attr('className')).not().toMatch(/ng-validation-error/); input('n2').enter('9'); expect(n2.attr('className')).toMatch(/ng-validation-error/); var n3 = element('.doc-example :input[name=n3]'); expect(n3.attr('className')).not().toMatch(/ng-validation-error/); input('n3').enter('201'); expect(n3.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ 'number': function(value, min, max) { var num = 1 * value; if (num == value) { if (typeof min != $undefined && num < min) { return "Value can not be less than " + min + "."; } if (typeof min != $undefined && num > max) { return "Value can not be greater than " + max + "."; } return _null; } else { return "Not a number"; } }, /** * @workInProgress * @ngdoc validator * @name angular.validator.integer * @description * Use number validator to restrict the input to integers with an * optional range. (See integer for whole numbers validator). * * @param {string} value value to validate * @param {int=} [min=MIN_INT] minimum value. * @param {int=} [max=MAX_INT] maximum value. * @css ng-validation-error * * @example <doc:example> <doc:source> Enter integer: <input name="n1" ng:validate="integer" > <br> Enter integer equal or greater than 10: <input name="n2" ng:validate="integer:10" > <br> Enter integer between 100 and 200 (inclusive): <input name="n3" ng:validate="integer:100:200" > <br> </doc:source> <doc:scenario> it('should invalidate integer', function(){ var n1 = element('.doc-example :input[name=n1]'); expect(n1.attr('className')).not().toMatch(/ng-validation-error/); input('n1').enter('1.1'); expect(n1.attr('className')).toMatch(/ng-validation-error/); var n2 = element('.doc-example :input[name=n2]'); expect(n2.attr('className')).not().toMatch(/ng-validation-error/); input('n2').enter('10.1'); expect(n2.attr('className')).toMatch(/ng-validation-error/); var n3 = element('.doc-example :input[name=n3]'); expect(n3.attr('className')).not().toMatch(/ng-validation-error/); input('n3').enter('100.1'); expect(n3.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> */ 'integer': function(value, min, max) { var numberError = angularValidator['number'](value, min, max); if (numberError) return numberError; if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) { return "Not a whole number"; } return _null; }, /** * @workInProgress * @ngdoc validator * @name angular.validator.date * @description * Use date validator to restrict the user input to a valid date * in format in format MM/DD/YYYY. * * @param {string} value value to validate * @css ng-validation-error * * @example <doc:example> <doc:source> Enter valid date: <input name="text" value="1/1/2009" ng:validate="date" > </doc:source> <doc:scenario> it('should invalidate date', function(){ var n1 = element('.doc-example :input'); expect(n1.attr('className')).not().toMatch(/ng-validation-error/); input('text').enter('123/123/123'); expect(n1.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ 'date': function(value) { var fields = /^(\d\d?)\/(\d\d?)\/(\d\d\d\d)$/.exec(value); var date = fields ? new Date(fields[3], fields[1]-1, fields[2]) : 0; return (date && date.getFullYear() == fields[3] && date.getMonth() == fields[1]-1 && date.getDate() == fields[2]) ? _null : "Value is not a date. (Expecting format: 12/31/2009)."; }, /** * @workInProgress * @ngdoc validator * @name angular.validator.email * @description * Use email validator if you wist to restrict the user input to a valid email. * * @param {string} value value to validate * @css ng-validation-error * * @example <doc:example> <doc:source> Enter valid email: <input name="text" ng:validate="email" value="me@example.com"> </doc:source> <doc:scenario> it('should invalidate email', function(){ var n1 = element('.doc-example :input'); expect(n1.attr('className')).not().toMatch(/ng-validation-error/); input('text').enter('a@b.c'); expect(n1.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ 'email': function(value) { if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) { return _null; } return "Email needs to be in username@host.com format."; }, /** * @workInProgress * @ngdoc validator * @name angular.validator.phone * @description * Use phone validator to restrict the input phone numbers. * * @param {string} value value to validate * @css ng-validation-error * * @example <doc:example> <doc:source> Enter valid phone number: <input name="text" value="1(234)567-8901" ng:validate="phone" > </doc:source> <doc:scenario> it('should invalidate phone', function(){ var n1 = element('.doc-example :input'); expect(n1.attr('className')).not().toMatch(/ng-validation-error/); input('text').enter('+12345678'); expect(n1.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ 'phone': function(value) { if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) { return _null; } if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) { return _null; } return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."; }, /** * @workInProgress * @ngdoc validator * @name angular.validator.url * @description * Use phone validator to restrict the input URLs. * * @param {string} value value to validate * @css ng-validation-error * * @example <doc:example> <doc:source> Enter valid phone number: <input name="text" value="http://example.com/abc.html" size="40" ng:validate="url" > </doc:source> <doc:scenario> it('should invalidate url', function(){ var n1 = element('.doc-example :input'); expect(n1.attr('className')).not().toMatch(/ng-validation-error/); input('text').enter('abc://server/path'); expect(n1.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ 'url': function(value) { if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) { return _null; } return "URL needs to be in http://server[:port]/path format."; }, /** * @workInProgress * @ngdoc validator * @name angular.validator.json * @description * Use json validator if you wish to restrict the user input to a valid JSON. * * @param {string} value value to validate * @css ng-validation-error * * @example <doc:example> <doc:source> <textarea name="json" cols="60" rows="5" ng:validate="json"> {name:'abc'} </textarea> </doc:source> <doc:scenario> it('should invalidate json', function(){ var n1 = element('.doc-example :input'); expect(n1.attr('className')).not().toMatch(/ng-validation-error/); input('json').enter('{name}'); expect(n1.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ 'json': function(value) { try { fromJson(value); return _null; } catch (e) { return e.toString(); } }, /** * @workInProgress * @ngdoc validator * @name angular.validator.asynchronous * @description * Use asynchronous validator if the validation can not be computed * immediately, but is provided through a callback. The widget * automatically shows a spinning indicator while the validity of * the widget is computed. This validator caches the result. * * @param {string} value value to validate * @param {function(inputToValidate,validationDone)} validate function to call to validate the state * of the input. * @param {function(data)=} [update=noop] function to call when state of the * validator changes * * @paramDescription * The `validate` function (specified by you) is called as * `validate(inputToValidate, validationDone)`: * * * `inputToValidate`: value of the input box. * * `validationDone`: `function(error, data){...}` * * `error`: error text to display if validation fails * * `data`: data object to pass to update function * * The `update` function is optionally specified by you and is * called by <angular/> on input change. Since the * asynchronous validator caches the results, the update * function can be called without a call to `validate` * function. The function is called as `update(data)`: * * * `data`: data object as passed from validate function * * @css ng-input-indicator-wait, ng-validation-error * * @example <doc:example> <doc:source> <script> function MyCntl(){ this.myValidator = function (inputToValidate, validationDone) { setTimeout(function(){ validationDone(inputToValidate.length % 2); }, 500); } } </script> This input is validated asynchronously: <div ng:controller="MyCntl"> <input name="text" ng:validate="asynchronous:myValidator"> </div> </doc:source> <doc:scenario> it('should change color in delayed way', function(){ var textBox = element('.doc-example :input'); expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/); expect(textBox.attr('className')).not().toMatch(/ng-validation-error/); input('text').enter('X'); expect(textBox.attr('className')).toMatch(/ng-input-indicator-wait/); pause(.6); expect(textBox.attr('className')).not().toMatch(/ng-input-indicator-wait/); expect(textBox.attr('className')).toMatch(/ng-validation-error/); }); </doc:scenario> </doc:example> * */ /* * cache is attached to the element * cache: { * inputs : { * 'user input': { * response: server response, * error: validation error * }, * current: 'current input' * } * */ 'asynchronous': function(input, asynchronousFn, updateFn) { if (!input) return; var scope = this; var element = scope.$element; var cache = element.data('$asyncValidator'); if (!cache) { element.data('$asyncValidator', cache = {inputs:{}}); } cache.current = input; var inputState = cache.inputs[input], $invalidWidgets = scope.$service('$invalidWidgets'); if (!inputState) { cache.inputs[input] = inputState = { inFlight: true }; $invalidWidgets.markInvalid(scope.$element); element.addClass('ng-input-indicator-wait'); asynchronousFn(input, function(error, data) { inputState.response = data; inputState.error = error; inputState.inFlight = false; if (cache.current == input) { element.removeClass('ng-input-indicator-wait'); $invalidWidgets.markValid(element); } element.data($$validate)(); scope.$service('$updateView')(); }); } else if (inputState.inFlight) { // request in flight, mark widget invalid, but don't show it to user $invalidWidgets.markInvalid(scope.$element); } else { (updateFn||noop)(inputState.response); } return inputState.error; } });