diff options
| -rw-r--r-- | example/widgets.html | 10 | ||||
| -rw-r--r-- | scenario/style.css | 7 | ||||
| -rw-r--r-- | scenario/widgets.html | 128 | ||||
| -rw-r--r-- | src/Angular.js | 234 | ||||
| -rw-r--r-- | src/Compiler.js | 11 | ||||
| -rw-r--r-- | src/Formatters.js | 11 | ||||
| -rw-r--r-- | src/JSON.js | 4 | ||||
| -rw-r--r-- | src/Scope.js | 278 | ||||
| -rw-r--r-- | src/UrlWatcher.js | 62 | ||||
| -rw-r--r-- | src/Widgets.js | 927 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 61 | ||||
| -rw-r--r-- | src/apis.js (renamed from src/API.js) | 35 | ||||
| -rw-r--r-- | src/delete/Binder.js (renamed from src/Binder.js) | 0 | ||||
| -rw-r--r-- | src/delete/Scope.js | 407 | ||||
| -rw-r--r-- | src/delete/Widgets.js | 806 | ||||
| -rw-r--r-- | src/directives.js | 15 | ||||
| -rw-r--r-- | src/jqLite.js | 16 | ||||
| -rw-r--r-- | src/markups.js (renamed from src/markup.js) | 2 | ||||
| -rw-r--r-- | src/moveToAngularCom/ControlBar.js (renamed from src/ControlBar.js) | 0 | ||||
| -rw-r--r-- | src/moveToAngularCom/DataStore.js (renamed from src/DataStore.js) | 0 | ||||
| -rw-r--r-- | src/moveToAngularCom/Model.js (renamed from src/Model.js) | 0 | ||||
| -rw-r--r-- | src/moveToAngularCom/Server.js (renamed from src/Server.js) | 0 | ||||
| -rw-r--r-- | src/moveToAngularCom/Users.js (renamed from src/Users.js) | 0 | ||||
| -rw-r--r-- | src/widgets2.js | 129 | ||||
| -rw-r--r-- | test/ScopeSpec.js | 16 | ||||
| -rw-r--r-- | test/delete/ScopeTest.js (renamed from test/ScopeTest.js) | 0 | ||||
| -rw-r--r-- | test/moveToAngularCom/Base64Test.js (renamed from test/Base64Test.js) | 0 | ||||
| -rw-r--r-- | test/moveToAngularCom/DataStoreTest.js (renamed from test/DataStoreTest.js) | 0 | ||||
| -rw-r--r-- | test/moveToAngularCom/EntityDeclarationTest.js (renamed from test/EntityDeclarationTest.js) | 0 | ||||
| -rw-r--r-- | test/moveToAngularCom/ModelTest.js (renamed from test/ModelTest.js) | 0 | ||||
| -rw-r--r-- | test/moveToAngularCom/ServerTest.js (renamed from test/ServerTest.js) | 0 | ||||
| -rw-r--r-- | test/moveToAngularCom/UsersTest.js (renamed from test/UsersTest.js) | 0 | 
32 files changed, 1637 insertions, 1522 deletions
| diff --git a/example/widgets.html b/example/widgets.html index 6a8214e7..525b35b9 100644 --- a/example/widgets.html +++ b/example/widgets.html @@ -1,13 +1,8 @@  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">    <head> -    <script type="text/javascript" src="../lib/underscore/underscore.js"></script> -    <script type="text/javascript" src="../lib/jquery/jquery-1.4.2.js"></script>      <script type="text/javascript" src="../src/angular-bootstrap.js"></script>      <script type="text/javascript"> -      $(document).ready(function(){ -        angular.compile(document).init(); -      });        function asyncValidate(value, callback){          var x = value.length % 2 ? null: "even";          //callback(x); @@ -16,7 +11,8 @@      </script>      <link rel="StyleSheet" type="text/css" href="../css/angular.css"/>    </head> -  <body> +  <body onload="angular.compile(document).$init()"> +      <input type="checkbox" name="form.checked" ng-format="boolean" value="true" checked="checked" />      <input ng-show="form.checked" name="form.required" ng-required/>      <hr/> @@ -26,8 +22,6 @@      <input type="checkbox" name="form.boolean" ng-format="boolean" value="true" checked="checked" />      <input type="checkbox" name="form.boolean" ng-format="boolean" value="true" />      <hr/> -    <input type="text" name="form.async" ng-validate="asynchronous:$window.asyncValidate" /> -    <hr/>      <select name="select">        <option>A</option>        <option selected>B</option> diff --git a/scenario/style.css b/scenario/style.css new file mode 100644 index 00000000..956bdc52 --- /dev/null +++ b/scenario/style.css @@ -0,0 +1,7 @@ +th { +  text-align: left; +} + +tr { +  border: 1px solid black; +} diff --git a/scenario/widgets.html b/scenario/widgets.html index cb28e78c..21060ebf 100644 --- a/scenario/widgets.html +++ b/scenario/widgets.html @@ -1,58 +1,84 @@  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">    <head> -    <script type="text/javascript" src="../lib/underscore/underscore.js"></script> -    <script type="text/javascript" src="../lib/jquery/jquery-1.3.2.js"></script> -    <script type="text/javascript" src="../src/angular-bootstrap.js"></script> -    <script type="text/javascript"> -      $(document).ready(function(){angular.compile(document).init();}); -    </script> +    <link rel="stylesheet" type="text/css" href="style.css"></link> +    <script type="text/javascript" src="../src/angular-bootstrap.js#autobind&rootScope=$view"></script>    </head>    <body> -    <p> -     name: <input type="text" name="name" /> name={{name}} <br/> -    </p> -    <p> -     <input type="radio" name="gender" value="female"/> Female -     <input type="radio" name="gender" value="male"/> Male -     gender={{gender}} -    </p> -    <p> -     <input type="checkbox" name="tea" checked value="on"/> tea={{tea}} <br/> -     <input type="checkbox" name="coffee" value="on"/> coffee={{coffee}} <br/> -    </p> -    <p ng-init="count = 0"> -      <form> -       <input type="button" value="button" ng-action="count = count + 1"/> -       <input type="submit" value="submit" ng-action="count = count + 1"/> -       <input type="image" src="" ng-action="count = count + 1"/> -       <a href="#ERROR" ng-action="count=count+1">action</a> -       count={{count}} -      </form> -    </p> -    <p> -      <select name="select"> -        <option>A</option> -        <option>B</option> -        <option>C</option> -      </select> -      select={{select}} -    </p> -    <p> -     <select name="multiple" multiple> -       <option>A</option> -       <option>B</option> -       <option>C</option> -     </select> -      multiple={{multiple}} -    </p> -    <p> -      <input type="hidden" name="hidden" value="hiddenValue" /> -      Hidden field = {{hidden}} -    </p> -    <p> -      <input type="password" name="password" value="passwordValue" /> -      Password field = {{password}} -    </p> +    <table> +      <tr> +        <th>Description</th> +        <th>Test</th> +        <th>Result</th> +      </tr> +      <tr><th colspan="3">Input text field</th></tr> +      <tr> +        <td>basic</td> +        <td><input type="text" name="text.basic" /></td> +        <td>text.basic={{text.basic}}</td> +      </tr> +      <tr> +        <td>password</td> +        <td><input type="password" name="text.password" /></td> +        <td>text.password={{text.password}}</td> +      </tr> +      <tr> +        <td>hidden</td> +        <td><input type="hidden" name="hidden" value="hiddenValue" /></td> +        <td>hidden={{hidden}}</td> +      </tr> +      <tr><th colspan="3">Input selection field</th></tr> +      <tr> +        <td>radio</td> +        <td> +         <input type="radio" name="gender" value="female"/> Female <br/> +         <input type="radio" name="gender" value="male"/> Male +        </td> +        <td>gender={{gender}}</td> +      </tr> +      <tr> +        <td>checkbox</td> +        <td> +         <input type="checkbox" name="checkbox.tea" checked value="on"/> Tea<br/> +         <input type="checkbox" name="checkbox.coffee" value="on"/> Coffe +        </td> +        <td>checkbox={{checkbox}}</td> +      </tr> +      <tr> +        <td>select</td> +        <td> +          <select name="select"> +            <option>A</option> +            <option>B</option> +            <option>C</option> +          </select> +        </td> +        <td>select={{select}}</td> +      </tr> +      <tr> +        <td>multiselect</td> +        <td> +          <select name="multiselect" multiple> +            <option>A</option> +            <option>B</option> +            <option>C</option> +          </select> +        </td> +        <td>multiselect={{multiselect}}</td> +      </tr> +      <tr><th colspan="3">Buttons</th></tr> +      <tr> +        <td>ng-action</td> +        <td> +          <form ng-init="button.count = 0"> +           <input type="button" value="button" ng-action="button.count = button.count + 1"/> <br/> +           <input type="submit" value="submit" ng-action="button.count = button.count + 1"/><br/> +           <input type="image" src="" ng-action="button.count = button.count + 1"/><br/> +           <a href="" ng-action="button.count = button.count + 1">action</a> +          </form> +        </td> +        <td>button={{button}}</td> +      </tr> +    </table>    </body>   </html> diff --git a/src/Angular.js b/src/Angular.js index 0cb89bbe..c3562e84 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1,22 +1,5 @@  if (typeof document.getAttribute == 'undefined')    document.getAttribute = function() {}; -if (typeof Node == 'undefined') { -  //TODO: can we get rid of this? -  Node = { -    ELEMENT_NODE : 1, -    ATTRIBUTE_NODE : 2, -    TEXT_NODE : 3, -    CDATA_SECTION_NODE : 4, -    ENTITY_REFERENCE_NODE : 5, -    ENTITY_NODE : 6, -    PROCESSING_INSTRUCTION_NODE : 7, -    COMMENT_NODE : 8, -    DOCUMENT_NODE : 9, -    DOCUMENT_TYPE_NODE : 10, -    DOCUMENT_FRAGMENT_NODE : 11, -    NOTATION_NODE : 12 -  }; -}  function noop() {}  function identity($) {return $;} @@ -32,9 +15,11 @@ function extensionMap(angular, name) {    });  } -var consoleNode, msie, +var consoleNode,      NOOP              = 'noop',      jQuery            = window['jQuery'] || window['$'], // weirdness to make IE happy +    _                 = window['_'], +    jqLite            = jQuery,      slice             = Array.prototype.slice,      angular           = window['angular']    || (window['angular']    = {}),      angularTextMarkup = extensionMap(angular, 'textMarkup'), @@ -77,6 +62,7 @@ function extend(dst, obj) {    return dst;  } +function isUndefined(value){ return typeof value == 'undefined'; }  function isDefined(value){ return typeof value != 'undefined'; }  function isObject(value){ return typeof value == 'object';}  function isString(value){ return typeof value == 'string';} @@ -85,6 +71,12 @@ function isFunction(value){ return typeof value == 'function';}  function lowercase(value){ return isString(value) ? value.toLowerCase() : value; }  function uppercase(value){ return isString(value) ? value.toUpperCase() : value; }  function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }; +function includes(array, obj) { +  for ( var i = 0; i < array.length; i++) { +    if (obj === array[i]) return true; +  } +  return false; +}  function log(a, b, c){    var console = window['console']; @@ -154,18 +146,18 @@ function copy(source, destination){    if (!destination) {      if (!source) {        return source; -    } else if (_.isArray(source)) { +    } else if (isArray(source)) {        return copy(source, []);      } else {        return copy(source, {});      }    } else { -    if (_.isArray(source)) { +    if (isArray(source)) {        while(destination.length) {          destination.pop();        }      } else { -      _(destination).each(function(value, key){ +      foreach(function(value, key){          delete destination[key];        });      } @@ -236,201 +228,19 @@ function merge(src, dst) {    }  } -// //////////////////////////// -// UrlWatcher -// //////////////////////////// - -function UrlWatcher(location) { -  this.location = location; -  this.delay = 25; -  this.setTimeout = function(fn, delay) { -    window.setTimeout(fn, delay); -  }; -  this.listener = function(url) { -    return url; -  }; -  this.expectedUrl = location.href; -} - -UrlWatcher.prototype = { -  listen: function(fn){ -    this.listener = fn; -  }, -  watch: function() { -    var self = this; -    var pull = function() { -      if (self.expectedUrl !== self.location.href) { -        var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/); -        if (notify) { -          if (!self.expectedUrl.match(/#/)) { -            self.expectedUrl += "#"; -          } -          self.location.href = self.expectedUrl; -          var id = '_iframe_notify_' + notify[1]; -          var notifyFn = angularCallbacks[id]; -          delete angularCallbacks[id]; -          try { -            (notifyFn||noop)(); -          } catch (e) { -            alert(e); -          } -        } else { -          self.listener(self.location.href); -          self.expectedUrl = self.location.href; -        } -      } -      self.setTimeout(pull, self.delay); -    }; -    pull(); -  }, - -  set: function(url) { -    var existingURL = this.location.href; -    if (!existingURL.match(/#/)) -      existingURL += '#'; -    if (existingURL != url) -      this.location.href = url; -    this.existingURL = url; -  }, - -  get: function() { -    return window.location.href; -  } -}; -  ///////////////////////////////////////////////// -function configureJQueryPlugins() { -  var fn = jQuery['fn']; -  fn['scope'] = function() { -    var element = this; -    while (element && element.get(0)) { -      var scope = element.data("scope"); -      if (scope) -        return scope; -      element = element.parent(); -    } -    return null; -  }; -  fn['controller'] = function() { -    return this.data('controller') || NullController.instance; -  }; -} - -function configureLogging(config) { -  if (config.debug == 'console' && !consoleNode) { -    consoleNode = document.createElement("div"); -    consoleNode.id = 'ng-console'; -    document.getElementsByTagName('body')[0].appendChild(consoleNode); -    log = function() { -      consoleLog('ng-console-info', arguments); -    }; -    console.error = function() { -      consoleLog('ng-console-error', arguments); -    }; -  } -} - -function exposeMethods(obj, methods){ -  var bound = {}; -  foreach(methods, function(fn, name){ -    bound[name] = _(fn).bind(obj); -  }); -  return bound; -} - -function wireAngular(element, config) { -  var widgetFactory = new WidgetFactory(config['server'], config['database']); -  var binder = new Binder(element[0], widgetFactory, datastore, config['location'], config); -  binder.updateListeners.push(config.onUpdateView); -  var controlBar = new ControlBar(element.find('body'), config['server'], config['database']); -  var onUpdate = function(){binder.updateView();}; -  var server = config['database'] =="$MEMORY" ? -      new FrameServer(window) : -      new Server(config['server'], jQuery['getScript']); -  server = new VisualServer(server, new NullStatus(element.find('body')), onUpdate); -  var users = new Users(server, controlBar); -  var databasePath = '/data/' + config['database']; -  var post = function(request, callback){ -    server.request("POST", databasePath, request, callback); -  }; -  var datastore = new DataStore(post, users, binder.anchor); -  binder.datastore = datastore; -  binder.updateListeners.push(function(){datastore.flush();}); -  var scope = new Scope({ -    '$anchor'    : binder.anchor, -    '$updateView': _(binder.updateView).bind(binder), -    '$config'    : config, -    '$invalidWidgets': [], -    '$console'   : window.console, -    '$datastore' : exposeMethods(datastore, { -      'load':                    datastore.load, -      'loadMany':                datastore.loadMany, -      'loadOrCreate':            datastore.loadOrCreate, -      'loadAll':                 datastore.loadAll, -      'save':                    datastore.save, -      'remove':                  datastore.remove, -      'flush':                   datastore.flush, -      'query':                   datastore.query, -      'entity':                  datastore.entity, -      'entities':                datastore.entities, -      'documentCountsByUser':    datastore.documentCountsByUser, -      'userDocumentIdsByEntity': datastore.userDocumentIdsByEntity, -      'join':                    datastore.join -    }), -    '$save' : function(callback) { -      datastore.saveScope(scope.state, callback, binder.anchor); -    }, -    '$window' : window, -    '$uid' : function() { -      return "" + new Date().getTime(); -    }, -    '$users' : users -  }, "ROOT"); - -  element.data('scope', scope); -  binder.entity(scope); -  binder.compile(); -  controlBar.bind(); - -  //TODO: remove this code -  new PopUp(element).bind(); - -  var self = _(exposeMethods(scope, { -    'set':        scope.set, -    'get':        scope.get, -    'eval':       scope.eval -  })).extend({ -    'init':function(){ -        config['location']['listen'](_(binder.onUrlChange).bind(binder)); -        binder.parseAnchor(); -        binder.executeInit(); -        binder.updateView(); -        return self; -      }, -    'element':element[0], -    'updateView': _(binder.updateView).bind(binder), -    'config':config -  }); -  return self; -} - -angular['startUrlWatcher'] = function(){ -  var watcher = new UrlWatcher(window['location']); -  watcher.watch(); -  return exposeMethods(watcher, {'listen':watcher.listen, 'set':watcher.set, 'get':watcher.get}); -};  angular['compile'] = function(element, config) { -  jQuery = window['jQuery']; -  msie   = jQuery['browser']['msie']; -  config = _({ +  config = extend({        'onUpdateView': noop,        'server': "",        'location': {'get':noop, 'set':noop, 'listen':noop} -    }).extend(config||{}); - -  configureLogging(config); -  configureJQueryPlugins(); - -  return wireAngular(jQuery(element), config); +    }, config||{}); + +  var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); +      $element = jqLite(element), +      rootScope = { +        '$window': window +      }; +  return rootScope['$root'] = compiler.compile($element)($element, rootScope);  }; diff --git a/src/Compiler.js b/src/Compiler.js index 3b492ebe..4f30521b 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -51,7 +51,7 @@ Template.prototype = {  //Compiler  //////////////////////////////////  function isTextNode(node) { -  return node.nodeType == Node.TEXT_NODE; +  return node.nodeName == '#text';  }  function eachTextNode(element, fn){ @@ -92,10 +92,13 @@ Compiler.prototype = {      rawElement = jqLite(rawElement);      var template = this.templatize(rawElement) || new Template();      return function(element, parentScope){ -      var model = scope(parentScope); -      return extend(model, { +      var scope = createScope(parentScope); +      return extend(scope, {          $element:element, -        $init: bind(template, template.init, element, model) +        $init: function() { +          template.init(element, scope); +          scope.$eval(); +        }        });      };    }, diff --git a/src/Formatters.js b/src/Formatters.js index 6aa832af..f2d5d33e 100644 --- a/src/Formatters.js +++ b/src/Formatters.js @@ -7,12 +7,17 @@ extend(angularFormatter, {    'list':formater(      function(obj) { return obj ? obj.join(", ") : obj; }, -    function(value) {  -      return value ? _(_(value.split(',')).map(jQuery.trim)).select(_.identity) : []; +    function(value) { +      var list = []; +      foreach(value.split(','), function(item){ +        item = trim(item); +        if (item) list.push(item); +      }); +      return list;      }    ),    'trim':formater(      function(obj) { return obj ? $.trim("" + obj) : ""; } -  )   +  )  }); diff --git a/src/JSON.js b/src/JSON.js index 98dfddd2..baf3a2fa 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -2,7 +2,7 @@ array = [].constructor;  function toJson(obj, pretty){    var buf = []; -  toJsonArray(buf, obj, pretty ? "\n  " : null, _([])); +  toJsonArray(buf, obj, pretty ? "\n  " : null, []);    return buf.join('');  }; @@ -27,7 +27,7 @@ angular['fromJson'] = fromJson;  function toJsonArray(buf, obj, pretty, stack){    if (typeof obj == "object") { -    if (stack.include(obj)) { +    if (includes(stack, obj)) {        buf.push("RECURSION");        return;      } diff --git a/src/Scope.js b/src/Scope.js index ccf55077..6ba6aa8e 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -1,253 +1,3 @@ -function Scope(initialState, name) { -  var self = this; -  self.widgets = []; -  self.evals = []; -  self.watchListeners = {}; -  self.name = name; -  initialState = initialState || {}; -  var State = function(){}; -  State.prototype = initialState; -  self.state = new State(); -  extend(self.state, { -    '$parent': initialState, -    '$watch': bind(self, self.addWatchListener), -    '$eval': bind(self, self.eval), -    '$bind': bind(self, bind, self), -    // change name to autoEval? -    '$addEval': bind(self, self.addEval), -    '$updateView': bind(self, self.updateView) -  }); -  if (name == "ROOT") { -    self.state['$root'] = self.state; -  } -}; - -Scope.expressionCache = {}; -Scope.getter = function(instance, path) { -  if (!path) return instance; -  var element = path.split('.'); -  var key; -  var lastInstance = instance; -  var len = element.length; -  for ( var i = 0; i < len; i++) { -    key = element[i]; -    if (!key.match(/^[\$\w][\$\w\d]*$/)) -        throw "Expression '" + path + "' is not a valid expression for accesing variables."; -    if (instance) { -      lastInstance = instance; -      instance = instance[key]; -    } -    if (_.isUndefined(instance)  && key.charAt(0) == '$') { -      var type = angular['Global']['typeOf'](lastInstance); -      type = angular[type.charAt(0).toUpperCase()+type.substring(1)]; -      var fn = type ? type[[key.substring(1)]] : undefined; -      if (fn) { -        instance = _.bind(fn, lastInstance, lastInstance); -        return instance; -      } -    } -  } -  if (typeof instance === 'function' && !instance['$$factory']) { -    return bind(lastInstance, instance); -  } -  return instance; -}; - -Scope.setter = function(instance, path, value){ -  var element = path.split('.'); -  for ( var i = 0; element.length > 1; i++) { -    var key = element.shift(); -    var newInstance = instance[key]; -    if (!newInstance) { -      newInstance = {}; -      instance[key] = newInstance; -    } -    instance = newInstance; -  } -  instance[element.shift()] = value; -  return value; -}; - -Scope.prototype = { -  // TODO: rename to update? or eval? -  updateView: function() { -    var self = this; -    this.fireWatchers(); -    foreach(this.widgets, function(widget){ -      self.evalWidget(widget, "", {}, function(){ -        this.updateView(self); -      }); -    }); -    foreach(this.evals, bind(this, this.apply)); -  }, - -  addWidget: function(controller) { -    if (controller) this.widgets.push(controller); -  }, - -  addEval: function(fn, listener) { -    // todo: this should take a function/string and a listener -    // todo: this is a hack, which will need to be cleaned up. -    var self = this, -        listenFn = listener || noop, -        expr = self.compile(fn); -    this.evals.push(function(){ -      self.apply(listenFn, expr()); -    }); -  }, - -  isProperty: function(exp) { -    for ( var i = 0; i < exp.length; i++) { -      var ch = exp.charAt(i); -      if (ch!='.'  && !Lexer.prototype.isIdent(ch)) { -        return false; -      } -    } -    return true; -  }, - -  get: function(path) { -//    log('SCOPE.get', path, Scope.getter(this.state, path)); -    return Scope.getter(this.state, path); -  }, - -  set: function(path, value) { -//    log('SCOPE.set', path, value); -    var instance = this.state; -    return Scope.setter(instance, path, value); -  }, - -  setEval: function(expressionText, value) { -    this.eval(expressionText + "=" + toJson(value)); -  }, - -  compile: function(exp) { -    if (isFunction(exp)) return bind(this.state, exp); -    var expFn = Scope.expressionCache[exp], self = this; -    if (!expFn) { -      var parser = new Parser(exp); -      expFn = parser.statements(); -      parser.assertAllConsumed(); -      Scope.expressionCache[exp] = expFn; -    } -    return function(context){ -      context = context || {}; -      context.self = self.state; -      context.scope = self; -      return expFn.call(self, context); -    }; -  }, - -  eval: function(exp, context) { -//    log('Scope.eval', expressionText); -    return this.compile(exp)(context); -  }, - -  //TODO: Refactor. This function needs to be an execution closure for widgets -  // move to widgets -  // remove expression, just have inner closure. -  evalWidget: function(widget, expression, context, onSuccess, onFailure) { -    try { -      var value = this.eval(expression, context); -      if (widget.hasError) { -        widget.hasError = false; -        jQuery(widget.view). -          removeClass('ng-exception'). -          removeAttr('ng-error'); -      } -      if (onSuccess) { -        value = onSuccess.apply(widget, [value]); -      } -      return true; -    } catch (e){ -      var jsonError = toJson(e, true); -      error('Eval Widget Error:', jsonError); -      widget.hasError = true; -      jQuery(widget.view). -        addClass('ng-exception'). -        attr('ng-error', jsonError); -      if (onFailure) { -        onFailure.apply(widget, [e, jsonError]); -      } -      return false; -    } -  }, - -  validate: function(expressionText, value, element) { -    var expression = Scope.expressionCache[expressionText]; -    if (!expression) { -      expression = new Parser(expressionText).validator(); -      Scope.expressionCache[expressionText] = expression; -    } -    var self = {scope:this, self:this.state, '$element':element}; -    return expression(self)(self, value); -  }, - -  entity: function(entityDeclaration, datastore) { -    var expression = new Parser(entityDeclaration).entityDeclaration(); -    return expression({scope:this, datastore:datastore}); -  }, - -  clearInvalid: function() { -    var invalid = this.state['$invalidWidgets']; -    while(invalid.length > 0) {invalid.pop();} -  }, - -  markInvalid: function(widget) { -    this.state['$invalidWidgets'].push(widget); -  }, - -  watch: function(declaration) { -    var self = this; -    new Parser(declaration).watch()({ -      scope:this, -      addListener:function(watch, exp){ -        self.addWatchListener(watch, function(n,o){ -          try { -            return exp({scope:self}, n, o); -          } catch(e) { -            alert(e); -          } -        }); -      } -    }); -  }, - -  addWatchListener: function(watchExpression, listener) { -    // TODO: clean me up! -    if (!isFunction(listener)) { -      listener = this.compile(listener); -    } -    var watcher = this.watchListeners[watchExpression]; -    if (!watcher) { -      watcher = {listeners:[], expression:watchExpression}; -      this.watchListeners[watchExpression] = watcher; -    } -    watcher.listeners.push(listener); -  }, - -  fireWatchers: function() { -    var self = this, fired = false; -    foreach(this.watchListeners, function(watcher) { -      var value = self.eval(watcher.expression); -      if (value !== watcher.lastValue) { -        foreach(watcher.listeners, function(listener){ -          listener(value, watcher.lastValue); -          fired = true; -        }); -        watcher.lastValue = value; -      } -    }); -    return fired; -  }, - -  apply: function(fn) { -    fn.apply(this.state, slice.call(arguments, 1, arguments.length)); -  } -}; - -////////////////////////////// -  function getter(instance, path) {    if (!path) return instance;    var element = path.split('.'); @@ -262,12 +12,12 @@ function getter(instance, path) {        lastInstance = instance;        instance = instance[key];      } -    if (_.isUndefined(instance)  && key.charAt(0) == '$') { +    if (isUndefined(instance)  && key.charAt(0) == '$') {        var type = angular['Global']['typeOf'](lastInstance);        type = angular[type.charAt(0).toUpperCase()+type.substring(1)];        var fn = type ? type[[key.substring(1)]] : undefined;        if (fn) { -        instance = _.bind(fn, lastInstance, lastInstance); +        instance = bind(fn, lastInstance, lastInstance);          return instance;        }      } @@ -303,24 +53,26 @@ function expressionCompile(exp){      parser.assertAllConsumed();      compileCache[exp] = expFn;    } -  // return expFn -  // TODO(remove this hack) +  return parserNewScopeAdapter(expFn); +}; + +// return expFn +// TODO(remove this hack) +function parserNewScopeAdapter(fn) {    return function(){ -    return expFn({ +    return fn({        scope: {          set: this.$set,          get: this.$get        }      });    }; -}; - -var NON_RENDERABLE_ELEMENTS = { -  '#text': 1, '#comment':1, 'TR':1, 'TH':1 -}; +} -function isRenderableElement(element){ -  return element && element[0] && !NON_RENDERABLE_ELEMENTS[element[0].nodeName]; +function isRenderableElement(element) { +  var name = element && element[0] && element[0].nodeName; +  return name && name.charAt(0) != '#' && +    !includes(['TR', 'COL', 'COLGROUP', 'TBODY', 'THEAD', 'TFOOT'], name);  }  function rethrow(e) { throw e; } @@ -334,7 +86,7 @@ function errorHandlerFor(element) {    };  } -function scope(parent, Class) { +function createScope(parent, Class) {    function Parent(){}    function API(){}    function Behavior(){} diff --git a/src/UrlWatcher.js b/src/UrlWatcher.js new file mode 100644 index 00000000..0892eb1a --- /dev/null +++ b/src/UrlWatcher.js @@ -0,0 +1,62 @@ + +// //////////////////////////// +// UrlWatcher +// //////////////////////////// + +function UrlWatcher(location) { +  this.location = location; +  this.delay = 25; +  this.setTimeout = function(fn, delay) { +    window.setTimeout(fn, delay); +  }; +  this.listener = function(url) { +    return url; +  }; +  this.expectedUrl = location.href; +} + +UrlWatcher.prototype = { +  listen: function(fn){ +    this.listener = fn; +  }, +  watch: function() { +    var self = this; +    var pull = function() { +      if (self.expectedUrl !== self.location.href) { +        var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/); +        if (notify) { +          if (!self.expectedUrl.match(/#/)) { +            self.expectedUrl += "#"; +          } +          self.location.href = self.expectedUrl; +          var id = '_iframe_notify_' + notify[1]; +          var notifyFn = angularCallbacks[id]; +          delete angularCallbacks[id]; +          try { +            (notifyFn||noop)(); +          } catch (e) { +            alert(e); +          } +        } else { +          self.listener(self.location.href); +          self.expectedUrl = self.location.href; +        } +      } +      self.setTimeout(pull, self.delay); +    }; +    pull(); +  }, + +  set: function(url) { +    var existingURL = this.location.href; +    if (!existingURL.match(/#/)) +      existingURL += '#'; +    if (existingURL != url) +      this.location.href = url; +    this.existingURL = url; +  }, + +  get: function() { +    return window.location.href; +  } +}; diff --git a/src/Widgets.js b/src/Widgets.js index 74f70f21..42b9e916 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -1,806 +1,137 @@ -function WidgetFactory(serverUrl, database) { -  this.nextUploadId = 0; -  this.serverUrl = serverUrl; -  this.database = database; -  if (window['swfobject']) { -    this.createSWF = window['swfobject']['createSWF']; -  } else { -    this.createSWF = function(){ -      alert("ERROR: swfobject not loaded!"); -    }; -  } -}; - -WidgetFactory.prototype = { -  createController: function(input, scope) { -    var controller; -    var type = input.attr('type').toLowerCase(); -    var exp = input.attr('name'); -    if (exp) exp = exp.split(':').pop(); -    var event = "change"; -    var bubbleEvent = true; -    var formatter = angularFormatter[input.attr('ng-format')] || angularFormatter['noop']; -    if (type == 'button' || type == 'submit' || type == 'reset' || type == 'image') { -      controller = new ButtonController(input[0], exp, formatter); -      event = "click"; -      bubbleEvent = false; -    } else if (type == 'text' || type == 'textarea' || type == 'hidden' || type == 'password') { -      controller = new TextController(input[0], exp, formatter); -      event = "keyup change"; -    } else if (type == 'checkbox') { -      controller = new CheckboxController(input[0], exp, formatter); -      event = "click"; -    } else if (type == 'radio') { -      controller = new RadioController(input[0], exp, formatter); -      event="click"; -    } else if (type == 'select-one') { -      controller = new SelectController(input[0], exp, formatter); -    } else if (type == 'select-multiple') { -      controller = new MultiSelectController(input[0], exp, formatter); -    } else if (type == 'file') { -      controller = this.createFileController(input, exp, formatter); -    } else { -      throw 'Unknown type: ' + type; -    } -    input.data('controller', controller); -    var updateView = scope.get('$updateView'); -    var action = function() { -      if (controller.updateModel(scope)) { -        var action = jQuery(controller.view).attr('ng-action') || ""; -        if (scope.evalWidget(controller, action)) { -          updateView(scope); -        } -      } -      return bubbleEvent; -    }; -    jQuery(controller.view, ":input"). -      bind(event, action); -    return controller; -  }, - -  createFileController: function(fileInput) { -    var uploadId = '__uploadWidget_' + (this.nextUploadId++); -    var view = FileController.template(uploadId); -    fileInput.after(view); -    var att = { -        'data':this.serverUrl + "/admin/ServerAPI.swf", -        'width':"95", 'height':"20", 'align':"top", -        'wmode':"transparent"}; -    var par = { -        'flashvars':"uploadWidgetId=" + uploadId, -        'allowScriptAccess':"always"}; -    var swfNode = this.createSWF(att, par, uploadId); -    fileInput.remove(); -    var cntl = new FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database); -    jQuery(swfNode).parent().data('controller', cntl); -    return cntl; -  } -}; -///////////////////// -// FileController -/////////////////////// - -function FileController(view, scopeName, uploader, databaseUrl) { -  this.view = view; -  this.uploader = uploader; -  this.scopeName = scopeName; -  this.attachmentsPath = databaseUrl + '/_attachments'; -  this.value = null; -  this.lastValue = undefined; -}; - -angularCallbacks['flashEvent'] = function(id, event, args) { -  var object = document.getElementById(id); -  var jobject = jQuery(object); -  var controller = jobject.parent().data("controller"); -  FileController.prototype[event].apply(controller, args); -  _.defer(jobject.scope().get('$updateView')); -}; - -FileController.template = function(id) { -  return jQuery('<span class="ng-upload-widget">' + -      '<input type="checkbox" ng-non-bindable="true"/>' + -      '<object id="' + id + '" />' + -      '<a></a>' + -      '<span/>' + -    '</span>'); -}; - -extend(FileController.prototype, { -  'cancel': noop, -  'complete': noop, -  'httpStatus': function(status) { -    alert("httpStatus:" + this.scopeName + " status:" + status); -  }, -  'ioError': function() { -    alert("ioError:" + this.scopeName); -  }, -  'open': function() { -    alert("open:" + this.scopeName); -  }, -  'progress':noop, -  'securityError':  function() { -    alert("securityError:" + this.scopeName); -  }, -  'uploadCompleteData': function(data) { -    var value = fromJson(data); -    value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; -    this.view.find("input").attr('checked', true); -    var scope = this.view.scope(); -    this.value = value; -    this.updateModel(scope); -    this.value = null; -  }, -  'select': function(name, size, type) { -    this.name = name; -    this.view.find("a").text(name).attr('href', name); -    this.view.find("span").text(angular['filter']['bytes'](size)); -    this.upload(); -  }, - -  updateModel: function(scope) { -    var isChecked = this.view.find("input").attr('checked'); -    var value = isChecked ? this.value : null; -    if (this.lastValue === value) { -      return false; -    } else { -      scope.set(this.scopeName, value); -      return true; -    } -  }, - -  updateView: function(scope) { -    var modelValue = scope.get(this.scopeName); -    if (modelValue && this.value !== modelValue) { -      this.value = modelValue; -      this.view.find("a"). -        attr("href", this.value.url). -        text(this.value.text); -      this.view.find("span").text(angular['filter']['bytes'](this.value.size)); -    } -    this.view.find("input").attr('checked', !!modelValue); -  }, - -  upload: function() { -    if (this.name) { -      this.uploader['uploadFile'](this.attachmentsPath); -    } -  } -}); - -/////////////////////// -// NullController -/////////////////////// -function NullController(view) {this.view = view;}; -NullController.prototype = { -  updateModel: function() { return true; }, -  updateView: noop -}; -NullController.instance = new NullController(); - - -/////////////////////// -// ButtonController -/////////////////////// -var ButtonController = NullController; - -/////////////////////// -// TextController -/////////////////////// -function TextController(view, exp, formatter) { -  this.view = view; -  this.formatter = formatter; -  this.exp = exp; -  this.validator = view.getAttribute('ng-validate'); -  this.required = typeof view.attributes['ng-required'] != "undefined"; -  this.lastErrorText = null; -  this.lastValue = undefined; -  this.initialValue = this.formatter['parse'](view.value); -  var widget = view.getAttribute('ng-widget'); -  if (widget === 'datepicker') { -    jQuery(view).datepicker(); -  } -}; - -TextController.prototype = { -  updateModel: function(scope) { -    var value = this.formatter['parse'](this.view.value); -    if (this.lastValue === value) { -      return false; -    } else { -      scope.setEval(this.exp, value); -      this.lastValue = value; -      return true; -    } -  }, - -  updateView: function(scope) { -    var view = this.view; -    var value = scope.get(this.exp); -    if (typeof value === "undefined") { -      value = this.initialValue; -      scope.setEval(this.exp, value); -    } -    value = value ? value : ''; -    if (!_(this.lastValue).isEqual(value)) { -      view.value = this.formatter['format'](value); -      this.lastValue = value; -    } - -    var isValidationError = false; -    view.removeAttribute('ng-error'); -    if (this.required) { -      isValidationError = !(value && $.trim("" + value).length > 0); -    } -    var errorText = isValidationError ? "Required Value" : null; -    if (!isValidationError && this.validator && value) { -      errorText = scope.validate(this.validator, value, view); -      isValidationError = !!errorText; -    } -    if (this.lastErrorText !== errorText) { -      this.lastErrorText = isValidationError; -      if (errorText && isVisible(view)) { -        view.setAttribute('ng-error', errorText); -        scope.markInvalid(this); -      } -      jQuery(view).toggleClass('ng-validation-error', isValidationError); -    } -  } -}; - -/////////////////////// -// CheckboxController -/////////////////////// -function CheckboxController(view, exp, formatter) { -  this.view = view; -  this.exp = exp; -  this.lastValue = undefined; -  this.formatter = formatter; -  this.initialValue = this.formatter['parse'](view.checked ? view.value : ""); -}; - -CheckboxController.prototype = { -  updateModel: function(scope) { -    var input = this.view; -    var value = input.checked ? input.value : ''; -    value = this.formatter['parse'](value); -    value = this.formatter['format'](value); -    if (this.lastValue === value) { -      return false; -    } else { -      scope.setEval(this.exp, this.formatter['parse'](value)); -      this.lastValue = value; -      return true; -    } -  }, - -  updateView: function(scope) { -    var input = this.view; -    var value = scope.eval(this.exp); -    if (typeof value === "undefined") { -      value = this.initialValue; -      scope.setEval(this.exp, value); -    } -    input.checked = this.formatter['parse'](input.value) == value; -  } -}; - -/////////////////////// -// SelectController -/////////////////////// -function SelectController(view, exp) { -  this.view = view; -  this.exp = exp; -  this.lastValue = undefined; -  this.initialValue = view.value; -}; - -SelectController.prototype = { -  updateModel: function(scope) { -    var input = this.view; -    if (input.selectedIndex < 0) { -      scope.setEval(this.exp, null); -    } else { -      var value = this.view.value; -      if (this.lastValue === value) { -        return false; +function modelAccessor(scope, element) { +  var expr = element.attr('name'), +      farmatterName = element.attr('ng-format') || NOOP, +      formatter = angularFormatter(farmatterName); +  if (!expr) throw "Required field 'name' not found."; +  if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; +  return { +    get: function() { +      return formatter['format'](scope.$eval(expr)); +    }, +    set: function(value) { +      scope.$eval(expr + '=' + toJson(formatter['parse'](value))); +    } +  }; +} + +function compileValidator(expr) { +  return new Parser(expr).validator()(); +} + +function valueAccessor(element) { +  var validatorName = element.attr('ng-validate') || NOOP, +      validator = compileValidator(validatorName), +      required = element.attr('ng-required'), +      lastError; +  required = required || required == ''; +  if (!validator) throw "Validator named '" + validatorName + "' not found."; +  function validate(value) { +    var error = required && !trim(value) ? "Required" : validator.call(this, value); +    if (error !== lastError) { +      if (error) { +        element.addClass(NG_VALIDATION_ERROR); +        element.attr(NG_ERROR, error);        } else { -        scope.setEval(this.exp, value); -        this.lastValue = value; -        return true; -      } -    } -  }, - -  updateView: function(scope) { -    var input = this.view; -    var value = scope.get(this.exp); -    if (typeof value === 'undefined') { -      value = this.initialValue; -      scope.setEval(this.exp, value); -    } -    if (value !== this.lastValue) { -      input.value = value ? value : ""; -      this.lastValue = value; -    } -  } -}; - -/////////////////////// -// MultiSelectController -/////////////////////// -function MultiSelectController(view, exp) { -  this.view = view; -  this.exp = exp; -  this.lastValue = undefined; -  this.initialValue = this.selected(); -}; - -MultiSelectController.prototype = { -  selected: function () { -    var value = []; -    var options = this.view.options; -    for ( var i = 0; i < options.length; i++) { -      var option = options[i]; -      if (option.selected) { -        value.push(option.value); +        element.removeClass(NG_VALIDATION_ERROR); +        element.removeAttr(NG_ERROR);        } +      lastError = error;      }      return value; -  }, - -  updateModel: function(scope) { -    var value = this.selected(); -    // TODO: This is wrong! no caching going on here as we are always comparing arrays -    if (this.lastValue === value) { -      return false; -    } else { -      scope.setEval(this.exp, value); -      this.lastValue = value; -      return true; -    } -  }, - -  updateView: function(scope) { -    var input = this.view; -    var selected = scope.get(this.exp); -    if (typeof selected === "undefined") { -      selected = this.initialValue; -      scope.setEval(this.exp, selected); -    } -    if (selected !== this.lastValue) { -      var options = input.options; -      for ( var i = 0; i < options.length; i++) { -        var option = options[i]; -        option.selected = _.include(selected, option.value); -      } -      this.lastValue = selected; -    } -  } -}; - -/////////////////////// -// RadioController -/////////////////////// -function RadioController(view, exp) { -  this.view = view; -  this.exp = exp; -  this.lastChecked = undefined; -  this.lastValue = undefined; -  this.inputValue = view.value; -  this.initialValue = view.checked ? view.value : null; -}; - -RadioController.prototype = { -  updateModel: function(scope) { -    var input = this.view; -    if (this.lastChecked) { -      return false; -    } else { -      input.checked = true; -      this.lastValue = scope.setEval(this.exp, this.inputValue); -      this.lastChecked = true; -      return true; -    } -  }, - -  updateView: function(scope) { -    var input = this.view; -    var value = scope.get(this.exp); -    if (this.initialValue && typeof value === "undefined") { -      value = this.initialValue; -      scope.setEval(this.exp, value); -    } -    if (this.lastValue != value) { -      this.lastChecked = input.checked = this.inputValue == (''+value); -      this.lastValue = value; -    } -  } -}; - -/////////////////////// -//ElementController -/////////////////////// -function BindUpdater(view, exp) { -  this.view = view; -  this.exp = Binder.parseBindings(exp); -  this.hasError = false; -}; - -BindUpdater.toText = function(obj) { -  var e = escapeHtml; -  switch(typeof obj) { -    case "string": -    case "boolean": -    case "number": -      return e(obj); -    case "function": -      return BindUpdater.toText(obj()); -    case "object": -      if (isNode(obj)) { -        return outerHTML(obj); -      } else if (obj instanceof angular.filter.Meta) { -        switch(typeof obj.html) { -          case "string": -          case "number": -            return obj.html; -          case "function": -            return obj.html(); -          case "object": -            if (isNode(obj.html)) -              return outerHTML(obj.html); -          default: -            break; -        } -        switch(typeof obj.text) { -          case "string": -          case "number": -            return e(obj.text); -          case "function": -            return e(obj.text()); -          default: -            break; -        } -      } -      if (obj === null) -        return ""; -      return e(toJson(obj, true)); -    default: -      return ""; -  } -}; - -BindUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    var html = []; -    var parts = this.exp; -    var length = parts.length; -    for(var i=0; i<length; i++) { -      var part = parts[i]; -      var binding = Binder.binding(part); -      if (binding) { -        scope.evalWidget(this, binding, {$element:this.view}, function(value){ -          html.push(BindUpdater.toText(value)); -        }, function(e, text){ -          setHtml(this.view, text); -        }); -        if (this.hasError) { -          return; -        } -      } else { -        html.push(escapeHtml(part)); -      } -    } -    setHtml(this.view, html.join('')); -  } -}; - -function BindAttrUpdater(view, attrs) { -  this.view = view; -  this.attrs = attrs; -}; - -BindAttrUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    var jNode = jQuery(this.view); -    var attributeTemplates = this.attrs; -    if (this.hasError) { -      this.hasError = false; -      jNode. -        removeClass('ng-exception'). -        removeAttr('ng-error'); -    } -    var isImage = jNode.is('img'); -    for (var attrName in attributeTemplates) { -      var attributeTemplate = Binder.parseBindings(attributeTemplates[attrName]); -      var attrValues = []; -      for ( var i = 0; i < attributeTemplate.length; i++) { -        var binding = Binder.binding(attributeTemplate[i]); -        if (binding) { -          try { -            var value = scope.eval(binding, {$element:jNode[0], attrName:attrName}); -            if (value && (value.constructor !== array || value.length !== 0)) -              attrValues.push(value); -          } catch (e) { -            this.hasError = true; -            error('BindAttrUpdater', e); -            var jsonError = toJson(e, true); -            attrValues.push('[' + jsonError + ']'); -            jNode. -              addClass('ng-exception'). -              attr('ng-error', jsonError); -          } -        } else { -          attrValues.push(attributeTemplate[i]); -        } -      } -      var attrValue = attrValues.length ? attrValues.join('') : null; -      if(isImage && attrName == 'src' && !attrValue) -        attrValue = scope.get('$config.blankImage'); -      jNode.attr(attrName, attrValue); -    } -  } -}; - -function EvalUpdater(view, exp) { -  this.view = view; -  this.exp = exp; -  this.hasError = false; -}; -EvalUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp); -  } -}; - -function HideUpdater(view, exp) { this.view = view; this.exp = exp; }; -HideUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp, {}, function(hideValue){ -      var view = jQuery(this.view); -      if (toBoolean(hideValue)) { -        view.hide(); -      } else { -        view.show(); -      } -    }); -  } -}; - -function ShowUpdater(view, exp) { this.view = view; this.exp = exp; }; -ShowUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp, {}, function(hideValue){ -      var view = jQuery(this.view); -      if (toBoolean(hideValue)) { -        view.show(); -      } else { -        view.hide(); -      } -    }); -  } -}; - -function ClassUpdater(view, exp) { this.view = view; this.exp = exp; }; -ClassUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp, {}, function(classValue){ -      if (classValue !== null && classValue !== undefined) { -        this.view.className = classValue; -      } -    }); -  } -}; - -function ClassEvenUpdater(view, exp) { this.view = view; this.exp = exp; }; -ClassEvenUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp, {}, function(classValue){ -      var index = scope.get('$index'); -      jQuery(this.view).toggleClass(classValue, index % 2 === 1); -    }); -  } -}; - -function ClassOddUpdater(view, exp) { this.view = view; this.exp = exp; }; -ClassOddUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp, {}, function(classValue){ -      var index = scope.get('$index'); -      jQuery(this.view).toggleClass(classValue, index % 2 === 0); -    });    } -}; - -function StyleUpdater(view, exp) { this.view = view; this.exp = exp; }; -StyleUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.exp, {}, function(styleValue){ -      jQuery(this.view).attr('style', "").css(styleValue); -    }); -  } -}; - -/////////////////////// -// RepeaterUpdater -/////////////////////// -function RepeaterUpdater(view, repeaterExpression, template, prefix) { -  this.view = view; -  this.template = template; -  this.prefix = prefix; -  this.children = []; -  var match = repeaterExpression.match(/^\s*(.+)\s+in\s+(.*)\s*$/); -  if (! match) { -    throw "Expected ng-repeat in form of 'item in collection' but got '" + -      repeaterExpression + "'."; -  } -  var keyValue = match[1]; -  this.iteratorExp = match[2]; -  match = keyValue.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); -  if (!match) { -    throw "'item' in 'item in collection' should be identifier or (key, value) but get '" + -      keyValue + "'."; -  } -  this.valueExp = match[3] || match[1]; -  this.keyExp = match[2]; -}; - -RepeaterUpdater.prototype = { -  updateModel: noop, -  updateView: function(scope) { -    scope.evalWidget(this, this.iteratorExp, {}, function(iterator){ -      var self = this; -      if (!iterator) { -        iterator = []; -        if (scope.isProperty(this.iteratorExp)) { -          scope.set(this.iteratorExp, iterator); -        } -      } -      var childrenLength = this.children.length; -      var cursor = this.view; -      var time = 0; -      var child = null; -      var keyExp = this.keyExp; -      var valueExp = this.valueExp; -      var iteratorCounter = 0; -      foreach(iterator, function(value, key){ -        if (iteratorCounter < childrenLength) { -          // reuse children -          child = self.children[iteratorCounter]; -          child.scope.set(valueExp, value); -        } else { -          // grow children -          var name = self.prefix + -            valueExp + " in " + self.iteratorExp + "[" + iteratorCounter + "]"; -          var childScope = new Scope(scope.state, name); -          childScope.set('$index', iteratorCounter); -          if (keyExp) -            childScope.set(keyExp, key); -          childScope.set(valueExp, value); -          child = { scope:childScope, element:self.template(childScope, self.prefix, iteratorCounter) }; -          cursor.after(child.element); -          self.children.push(child); -        } -        cursor = child.element; -        var s = new Date().getTime(); -        child.scope.updateView(); -        time += new Date().getTime() - s; -        iteratorCounter++; +  return { +    get: function(){ return validate(element.val()); }, +    set: function(value){ element.val(validate(value)); } +  }; +} + +function checkedAccessor(element) { +  var domElement = element[0]; +  return { +    get: function(){ return !!domElement.checked; }, +    set: function(value){ domElement.checked = !!value; } +  }; +} + +function radioAccessor(element) { +  var domElement = element[0]; +  return { +    get: function(){ return domElement.checked ? domElement.value : null; }, +    set: function(value){ domElement.checked = value == domElement.value; } +  }; +} + +function optionsAccessor(element) { +  var options = element[0].options; +  return { +    get: function(){ +      var values = []; +      foreach(options, function(option){ +        if (option.selected) values.push(option.value); +      }); +      return values; +    }, +    set: function(values){ +      var keys = {}; +      foreach(values, function(value){ keys[value] = true; }); +      foreach(options, function(option){ +        option.selected = keys[option.value];        }); -      // shrink children -      for ( var r = childrenLength; r > iteratorCounter; --r) { -        this.children.pop().element.remove(); -      } -      // Special case for option in select -      if (child && child.element[0].nodeName === "OPTION") { -        var select = jQuery(child.element[0].parentNode); -        var cntl = select.data('controller'); -        if (cntl) { -          cntl.lastValue = undefined; -          cntl.updateView(scope); -        } -      } -    }); -  } -}; - -////////////////////////////////// -// PopUp -////////////////////////////////// - -function PopUp(doc) { -  this.doc = doc; -}; - -PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup"; - -PopUp.onOver = function(e) { -  PopUp.onOut(); -  var jNode = jQuery(this); -  jNode.bind(PopUp.OUT_EVENT, PopUp.onOut); -  var position = jNode.position(); -  var de = document.documentElement; -  var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; -  var hasArea = w - position.left; -  var width = 300; -  var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."; -  var msg = jNode.attr("ng-error"); - -  var x; -  var arrowPos = hasArea>(width+75) ? "left" : "right"; -  var tip = jQuery( -    "<div id='ng-callout' style='width:"+width+"px'>" + -      "<div class='ng-arrow-"+arrowPos+"'/>" + -      "<div class='ng-title'>"+title+"</div>" + -      "<div class='ng-content'>"+msg+"</div>" + -    "</div>"); -  jQuery("body").append(tip); -  if(arrowPos === 'left'){ -    x = position.left + this.offsetWidth + 11; -  }else{ -    x = position.left - (width + 15); -    tip.find('.ng-arrow-right').css({left:width+1}); -  } - -  tip.css({left: x+"px", top: (position.top - 3)+"px"}); -  return true; -}; - -PopUp.onOut = function() { -  jQuery('#ng-callout'). -    unbind(PopUp.OUT_EVENT, PopUp.onOut). -    remove(); -  return true; -}; - -PopUp.prototype = { -  bind: function () { -    var self = this; -    this.doc.find('.ng-validation-error,.ng-exception'). -      live("mouseover", PopUp.onOver); -  } -}; - -////////////////////////////////// -// Status -////////////////////////////////// - -function NullStatus(body) { -}; - -NullStatus.prototype = { - beginRequest:function(){}, - endRequest:function(){} -}; - -function Status(body) { -  this.requestCount = 0; -  this.body = body; -}; - -Status.DOM ='<div id="ng-spacer"></div><div id="ng-loading">loading....</div>'; - -Status.prototype = { -  beginRequest: function () { -    if (this.requestCount === 0) { -      (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show();      } -    this.requestCount++; -  }, +  }; +} + +function noopAccessor() { return { get: noop, set: noop }; } + +var NG_ERROR = 'ng-error', +    NG_VALIDATION_ERROR = 'ng-validation-error', +    textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), +    buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), +    INPUT_TYPE = { +      'text':            textWidget, +      'textarea':        textWidget, +      'hidden':          textWidget, +      'password':        textWidget, +      'button':          buttonWidget, +      'submit':          buttonWidget, +      'reset':           buttonWidget, +      'image':           buttonWidget, +      'checkbox':        inputWidget('click', modelAccessor, checkedAccessor, false), +      'radio':           inputWidget('click', modelAccessor, radioAccessor, undefined), +      'select-one':      inputWidget('click', modelAccessor, valueAccessor, null), +      'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, []) +//      'file':            fileWidget??? +    }; -  endRequest: function () { -    this.requestCount--; -    if (this.requestCount === 0) { -      this.loader.hide("fold"); -    } -  } -}; +function inputWidget(events, modelAccessor, viewAccessor, initValue) { +  return function(element) { +    var scope = this, +        model = modelAccessor(scope, element), +        view = viewAccessor(element), +        action = element.attr('ng-action') || '', +        value = view.get() || copy(initValue); +    if (isDefined(value)) model.set(value); +    this.$eval(element.attr('ng-init')||''); +    element.bind(events, function(){ +      model.set(view.get()); +      scope.$tryEval(action, element); +      scope.$root.$eval(); +      // if we have no initValue than we are just a button, +      // therefore we want to prevent default action +      return isDefined(initValue); +    }); +    scope.$watch(model.get, view.set); +  }; +} + +function inputWidgetSelector(element){ +  return INPUT_TYPE[lowercase(element[0].type)] || noop; +} + +angularWidget('INPUT', inputWidgetSelector); +angularWidget('TEXTAREA', inputWidgetSelector); +angularWidget('BUTTON', inputWidgetSelector); +angularWidget('SELECT', function(element){ +  this.descend(true); +  return inputWidgetSelector.call(this, element); +}); diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index d2b2ff9c..7798afa5 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -1,18 +1,18 @@  /**   * The MIT License - *  + *   * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - *  + *   * Permission is hereby granted, free of charge, to any person obtaining a copy   * of this software and associated documentation files (the "Software"), to deal   * in the Software without restriction, including without limitation the rights   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell   * copies of the Software, and to permit persons to whom the Software is   * furnished to do so, subject to the following conditions: - *  + *   * The above copyright notice and this permission notice shall be included in   * all copies or substantial portions of the Software. - *  + *   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,35 +22,58 @@   * THE SOFTWARE.   */  (function(previousOnLoad){ -  var filename = /(.*)\/angular-(.*).js/; -  var scripts = document.getElementsByTagName("script"); +  var filename = /(.*)\/angular-(.*).js(#(.*))?/; +  var scripts = document.getElementsByTagName("SCRIPT");    var serverPath; +  var config = {};    for(var j = 0; j < scripts.length; j++) {      var match = (scripts[j].src || "").match(filename);      if (match) {        serverPath = match[1]; +      parseConfig(match[4]); +    } +  } + +  function parseConfig(args) { +    var keyValues = args.split('&'), keyValue, i = 0; +    for (; i < keyValues.length; i++) { +      keyValue = keyValues[i].split('='); +      config[keyValue[0]] = keyValue[1] || true;      }    }    function addScript(file){      document.write('<script type="text/javascript" src="' + serverPath + file +'"></script>'); -  }; +  }    addScript("/Angular.js"); -  addScript("/API.js"); -  addScript("/Binder.js"); -  addScript("/ControlBar.js"); -  addScript("/DataStore.js"); -  addScript("/Filters.js"); -  addScript("/Formatters.js");    addScript("/JSON.js"); -  addScript("/Model.js"); +  addScript("/Compiler.js"); +  addScript("/Scope.js"); +  addScript("/jqlite.js");    addScript("/Parser.js");    addScript("/Resource.js"); -  addScript("/Scope.js"); -  addScript("/Server.js"); -  addScript("/Users.js"); -  addScript("/Validators.js"); -  addScript("/Widgets.js"); +  addScript("/URLWatcher.js"); + +  // Extension points +  addScript("/apis.js"); +  addScript("/filters.js"); +  addScript("/formatters.js"); +  addScript("/validators.js"); +  addScript("/directives.js"); +  addScript("/markups.js"); +  addScript("/widgets.js"); + +  if (config.autobind) { +    window.onload = function(){ +      try { +        if (previousOnLoad) previousOnLoad(); +      } catch(e) {} +      var scope = angular.compile(window.document, config); +      if (config.rootScope) window[config.rootScope] = scope; +      scope.$init(); +    }; +  } +  })(window.onload); diff --git a/src/API.js b/src/apis.js index ce690ad1..e375e8fc 100644 --- a/src/API.js +++ b/src/apis.js @@ -48,7 +48,7 @@ var angularArray = {        if (fn($)){          defaultValue = $;          return true; -      }       +      }      });      return defaultValue;    }, @@ -146,7 +146,7 @@ var angularArray = {    },    'orderBy':function(array, expression, descend) {      function reverse(comp, descending) { -      return toBoolean(descending) ?  +      return toBoolean(descending) ?            function(a,b){return comp(b,a);} : comp;      }      function compare(v1, v2){ @@ -255,7 +255,7 @@ var angularString = {    },    'toDate':function(string){      var match; -    if (typeof string == 'string' &&  +    if (typeof string == 'string' &&          (match = string.match(/^(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$/))){        var date = new Date(0);        date.setUTCFullYear(match[1], match[2] - 1, match[3]); @@ -269,12 +269,13 @@ var angularString = {  var angularDate = {      'toString':function(date){        function pad(n) { return n < 10 ? "0" + n : n; } -      return  (date.getUTCFullYear()) + '-' + +      return  !date ? date : +        date.getUTCFullYear() + '-' +          pad(date.getUTCMonth() + 1) + '-' +          pad(date.getUTCDate()) + 'T' +          pad(date.getUTCHours()) + ':' +          pad(date.getUTCMinutes()) + ':' + -        pad(date.getUTCSeconds()) + 'Z'; +        pad(date.getUTCSeconds()) + 'Z' ;      }    }; @@ -295,25 +296,27 @@ var angularFunction = {  };  function defineApi(dst, chain, underscoreNames){ -  var lastChain = _.last(chain); -  foreach(underscoreNames, function(name){ -    lastChain[name] = _[name]; -  }); +  if (_) { +    var lastChain = _.last(chain); +    foreach(underscoreNames, function(name){ +      lastChain[name] = _[name]; +    }); +  }    angular[dst] = angular[dst] || {};    foreach(chain, function(parent){      extend(angular[dst], parent);    });  }  defineApi('Global', [angularGlobal], -    ['extend', 'clone','isEqual',  +    ['extend', 'clone','isEqual',       'isElement', 'isArray', 'isFunction', 'isUndefined']); -defineApi('Collection', [angularGlobal, angularCollection],  -    ['each', 'map', 'reduce', 'reduceRight', 'detect',  -     'select', 'reject', 'all', 'any', 'include',  -     'invoke', 'pluck', 'max', 'min', 'sortBy',  +defineApi('Collection', [angularGlobal, angularCollection], +    ['each', 'map', 'reduce', 'reduceRight', 'detect', +     'select', 'reject', 'all', 'any', 'include', +     'invoke', 'pluck', 'max', 'min', 'sortBy',       'sortedIndex', 'toArray', 'size']); -defineApi('Array', [angularGlobal, angularCollection, angularArray],  -    ['first', 'last', 'compact', 'flatten', 'without',  +defineApi('Array', [angularGlobal, angularCollection, angularArray], +    ['first', 'last', 'compact', 'flatten', 'without',       'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']);  defineApi('Object', [angularGlobal, angularCollection, angularObject],      ['keys', 'values']); diff --git a/src/Binder.js b/src/delete/Binder.js index 9fc32513..9fc32513 100644 --- a/src/Binder.js +++ b/src/delete/Binder.js diff --git a/src/delete/Scope.js b/src/delete/Scope.js new file mode 100644 index 00000000..ae3f9f11 --- /dev/null +++ b/src/delete/Scope.js @@ -0,0 +1,407 @@ +function Scope(initialState, name) { +  var self = this; +  self.widgets = []; +  self.evals = []; +  self.watchListeners = {}; +  self.name = name; +  initialState = initialState || {}; +  var State = function(){}; +  State.prototype = initialState; +  self.state = new State(); +  extend(self.state, { +    '$parent': initialState, +    '$watch': bind(self, self.addWatchListener), +    '$eval': bind(self, self.eval), +    '$bind': bind(self, bind, self), +    // change name to autoEval? +    '$addEval': bind(self, self.addEval), +    '$updateView': bind(self, self.updateView) +  }); +  if (name == "ROOT") { +    self.state['$root'] = self.state; +  } +}; + +Scope.expressionCache = {}; +Scope.getter = function(instance, path) { +  if (!path) return instance; +  var element = path.split('.'); +  var key; +  var lastInstance = instance; +  var len = element.length; +  for ( var i = 0; i < len; i++) { +    key = element[i]; +    if (!key.match(/^[\$\w][\$\w\d]*$/)) +        throw "Expression '" + path + "' is not a valid expression for accesing variables."; +    if (instance) { +      lastInstance = instance; +      instance = instance[key]; +    } +    if (_.isUndefined(instance)  && key.charAt(0) == '$') { +      var type = angular['Global']['typeOf'](lastInstance); +      type = angular[type.charAt(0).toUpperCase()+type.substring(1)]; +      var fn = type ? type[[key.substring(1)]] : undefined; +      if (fn) { +        instance = _.bind(fn, lastInstance, lastInstance); +        return instance; +      } +    } +  } +  if (typeof instance === 'function' && !instance['$$factory']) { +    return bind(lastInstance, instance); +  } +  return instance; +}; + +Scope.setter = function(instance, path, value){ +  var element = path.split('.'); +  for ( var i = 0; element.length > 1; i++) { +    var key = element.shift(); +    var newInstance = instance[key]; +    if (!newInstance) { +      newInstance = {}; +      instance[key] = newInstance; +    } +    instance = newInstance; +  } +  instance[element.shift()] = value; +  return value; +}; + +Scope.prototype = { +  // TODO: rename to update? or eval? +  updateView: function() { +    var self = this; +    this.fireWatchers(); +    foreach(this.widgets, function(widget){ +      self.evalWidget(widget, "", {}, function(){ +        this.updateView(self); +      }); +    }); +    foreach(this.evals, bind(this, this.apply)); +  }, + +  addWidget: function(controller) { +    if (controller) this.widgets.push(controller); +  }, + +  addEval: function(fn, listener) { +    // todo: this should take a function/string and a listener +    // todo: this is a hack, which will need to be cleaned up. +    var self = this, +        listenFn = listener || noop, +        expr = self.compile(fn); +    this.evals.push(function(){ +      self.apply(listenFn, expr()); +    }); +  }, + +  isProperty: function(exp) { +    for ( var i = 0; i < exp.length; i++) { +      var ch = exp.charAt(i); +      if (ch!='.'  && !Lexer.prototype.isIdent(ch)) { +        return false; +      } +    } +    return true; +  }, + +  get: function(path) { +//    log('SCOPE.get', path, Scope.getter(this.state, path)); +    return Scope.getter(this.state, path); +  }, + +  set: function(path, value) { +//    log('SCOPE.set', path, value); +    var instance = this.state; +    return Scope.setter(instance, path, value); +  }, + +  setEval: function(expressionText, value) { +    this.eval(expressionText + "=" + toJson(value)); +  }, + +  compile: function(exp) { +    if (isFunction(exp)) return bind(this.state, exp); +    var expFn = Scope.expressionCache[exp], self = this; +    if (!expFn) { +      var parser = new Parser(exp); +      expFn = parser.statements(); +      parser.assertAllConsumed(); +      Scope.expressionCache[exp] = expFn; +    } +    return function(context){ +      context = context || {}; +      context.self = self.state; +      context.scope = self; +      return expFn.call(self, context); +    }; +  }, + +  eval: function(exp, context) { +//    log('Scope.eval', expressionText); +    return this.compile(exp)(context); +  }, + +  //TODO: Refactor. This function needs to be an execution closure for widgets +  // move to widgets +  // remove expression, just have inner closure. +  evalWidget: function(widget, expression, context, onSuccess, onFailure) { +    try { +      var value = this.eval(expression, context); +      if (widget.hasError) { +        widget.hasError = false; +        jQuery(widget.view). +          removeClass('ng-exception'). +          removeAttr('ng-error'); +      } +      if (onSuccess) { +        value = onSuccess.apply(widget, [value]); +      } +      return true; +    } catch (e){ +      var jsonError = toJson(e, true); +      error('Eval Widget Error:', jsonError); +      widget.hasError = true; +      jQuery(widget.view). +        addClass('ng-exception'). +        attr('ng-error', jsonError); +      if (onFailure) { +        onFailure.apply(widget, [e, jsonError]); +      } +      return false; +    } +  }, + +  validate: function(expressionText, value, element) { +    var expression = Scope.expressionCache[expressionText]; +    if (!expression) { +      expression = new Parser(expressionText).validator(); +      Scope.expressionCache[expressionText] = expression; +    } +    var self = {scope:this, self:this.state, '$element':element}; +    return expression(self)(self, value); +  }, + +  entity: function(entityDeclaration, datastore) { +    var expression = new Parser(entityDeclaration).entityDeclaration(); +    return expression({scope:this, datastore:datastore}); +  }, + +  clearInvalid: function() { +    var invalid = this.state['$invalidWidgets']; +    while(invalid.length > 0) {invalid.pop();} +  }, + +  markInvalid: function(widget) { +    this.state['$invalidWidgets'].push(widget); +  }, + +  watch: function(declaration) { +    var self = this; +    new Parser(declaration).watch()({ +      scope:this, +      addListener:function(watch, exp){ +        self.addWatchListener(watch, function(n,o){ +          try { +            return exp({scope:self}, n, o); +          } catch(e) { +            alert(e); +          } +        }); +      } +    }); +  }, + +  addWatchListener: function(watchExpression, listener) { +    // TODO: clean me up! +    if (!isFunction(listener)) { +      listener = this.compile(listener); +    } +    var watcher = this.watchListeners[watchExpression]; +    if (!watcher) { +      watcher = {listeners:[], expression:watchExpression}; +      this.watchListeners[watchExpression] = watcher; +    } +    watcher.listeners.push(listener); +  }, + +  fireWatchers: function() { +    var self = this, fired = false; +    foreach(this.watchListeners, function(watcher) { +      var value = self.eval(watcher.expression); +      if (value !== watcher.lastValue) { +        foreach(watcher.listeners, function(listener){ +          listener(value, watcher.lastValue); +          fired = true; +        }); +        watcher.lastValue = value; +      } +    }); +    return fired; +  }, + +  apply: function(fn) { +    fn.apply(this.state, slice.call(arguments, 1, arguments.length)); +  } +}; + +////////////////////////////// + +function getter(instance, path) { +  if (!path) return instance; +  var element = path.split('.'); +  var key; +  var lastInstance = instance; +  var len = element.length; +  for ( var i = 0; i < len; i++) { +    key = element[i]; +    if (!key.match(/^[\$\w][\$\w\d]*$/)) +        throw "Expression '" + path + "' is not a valid expression for accesing variables."; +    if (instance) { +      lastInstance = instance; +      instance = instance[key]; +    } +    if (_.isUndefined(instance)  && key.charAt(0) == '$') { +      var type = angular['Global']['typeOf'](lastInstance); +      type = angular[type.charAt(0).toUpperCase()+type.substring(1)]; +      var fn = type ? type[[key.substring(1)]] : undefined; +      if (fn) { +        instance = _.bind(fn, lastInstance, lastInstance); +        return instance; +      } +    } +  } +  if (typeof instance === 'function' && !instance['$$factory']) { +    return bind(lastInstance, instance); +  } +  return instance; +}; + +function setter(instance, path, value){ +  var element = path.split('.'); +  for ( var i = 0; element.length > 1; i++) { +    var key = element.shift(); +    var newInstance = instance[key]; +    if (!newInstance) { +      newInstance = {}; +      instance[key] = newInstance; +    } +    instance = newInstance; +  } +  instance[element.shift()] = value; +  return value; +}; + +var compileCache = {}; +function expressionCompile(exp){ +  if (isFunction(exp)) return exp; +  var expFn = compileCache[exp]; +  if (!expFn) { +    var parser = new Parser(exp); +    expFn = parser.statements(); +    parser.assertAllConsumed(); +    compileCache[exp] = expFn; +  } +  // return expFn +  // TODO(remove this hack) +  return function(){ +    return expFn({ +      scope: { +        set: this.$set, +        get: this.$get +      } +    }); +  }; +}; + +var NON_RENDERABLE_ELEMENTS = { +  '#text': 1, '#comment':1, 'TR':1, 'TH':1 +}; + +function isRenderableElement(element){ +  return element && element[0] && !NON_RENDERABLE_ELEMENTS[element[0].nodeName]; +} + +function rethrow(e) { throw e; } +function errorHandlerFor(element) { +  while (!isRenderableElement(element)) { +    element = element.parent() || jqLite(document.body); +  } +  return function(error) { +    element.attr('ng-error', angular.toJson(error)); +    element.addClass('ng-exception'); +  }; +} + +function createScope(parent, Class) { +  function Parent(){} +  function API(){} +  function Behavior(){} + +  var instance, behavior, api, watchList = [], evalList = []; + +  Class = Class || noop; +  parent = Parent.prototype = parent || {}; +  api = API.prototype = new Parent(); +  behavior = Behavior.prototype = extend(new API(), Class.prototype); +  instance = new Behavior(); + +  extend(api, { +    $parent: parent, +    $bind: bind(instance, bind, instance), +    $get: bind(instance, getter, instance), +    $set: bind(instance, setter, instance), + +    $eval: function(exp) { +      if (isDefined(exp)) { +        return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length)); +      } else { +        foreach(evalList, function(eval) { +          instance.$tryEval(eval.fn, eval.handler); +        }); +        foreach(watchList, function(watch) { +          var value = instance.$tryEval(watch.watch, watch.handler); +          if (watch.last !== value) { +            instance.$tryEval(watch.listener, watch.handler, value, watch.last); +            watch.last = value; +          } +        }); +      } +    }, + +    $tryEval: function (expression, exceptionHandler) { +      try { +        return expressionCompile(expression).apply(instance, slice.call(arguments, 2, arguments.length)); +      } catch (e) { +        error(e); +        if (isFunction(exceptionHandler)) { +          exceptionHandler(e); +        } else if (exceptionHandler) { +          errorHandlerFor(exceptionHandler)(e); +        } +      } +    }, + +    $watch: function(watchExp, listener, exceptionHandler) { +      var watch = expressionCompile(watchExp); +      watchList.push({ +        watch: watch, +        last: watch.call(instance), +        handler: exceptionHandler, +        listener:expressionCompile(listener) +      }); +    }, + +    $onEval: function(expr, exceptionHandler){ +      evalList.push({ +        fn: expressionCompile(expr), +        handler: exceptionHandler +      }); +    } +  }); + +  Class.apply(instance, slice.call(arguments, 2, arguments.length)); + +  return instance; +} diff --git a/src/delete/Widgets.js b/src/delete/Widgets.js new file mode 100644 index 00000000..74f70f21 --- /dev/null +++ b/src/delete/Widgets.js @@ -0,0 +1,806 @@ +function WidgetFactory(serverUrl, database) { +  this.nextUploadId = 0; +  this.serverUrl = serverUrl; +  this.database = database; +  if (window['swfobject']) { +    this.createSWF = window['swfobject']['createSWF']; +  } else { +    this.createSWF = function(){ +      alert("ERROR: swfobject not loaded!"); +    }; +  } +}; + +WidgetFactory.prototype = { +  createController: function(input, scope) { +    var controller; +    var type = input.attr('type').toLowerCase(); +    var exp = input.attr('name'); +    if (exp) exp = exp.split(':').pop(); +    var event = "change"; +    var bubbleEvent = true; +    var formatter = angularFormatter[input.attr('ng-format')] || angularFormatter['noop']; +    if (type == 'button' || type == 'submit' || type == 'reset' || type == 'image') { +      controller = new ButtonController(input[0], exp, formatter); +      event = "click"; +      bubbleEvent = false; +    } else if (type == 'text' || type == 'textarea' || type == 'hidden' || type == 'password') { +      controller = new TextController(input[0], exp, formatter); +      event = "keyup change"; +    } else if (type == 'checkbox') { +      controller = new CheckboxController(input[0], exp, formatter); +      event = "click"; +    } else if (type == 'radio') { +      controller = new RadioController(input[0], exp, formatter); +      event="click"; +    } else if (type == 'select-one') { +      controller = new SelectController(input[0], exp, formatter); +    } else if (type == 'select-multiple') { +      controller = new MultiSelectController(input[0], exp, formatter); +    } else if (type == 'file') { +      controller = this.createFileController(input, exp, formatter); +    } else { +      throw 'Unknown type: ' + type; +    } +    input.data('controller', controller); +    var updateView = scope.get('$updateView'); +    var action = function() { +      if (controller.updateModel(scope)) { +        var action = jQuery(controller.view).attr('ng-action') || ""; +        if (scope.evalWidget(controller, action)) { +          updateView(scope); +        } +      } +      return bubbleEvent; +    }; +    jQuery(controller.view, ":input"). +      bind(event, action); +    return controller; +  }, + +  createFileController: function(fileInput) { +    var uploadId = '__uploadWidget_' + (this.nextUploadId++); +    var view = FileController.template(uploadId); +    fileInput.after(view); +    var att = { +        'data':this.serverUrl + "/admin/ServerAPI.swf", +        'width':"95", 'height':"20", 'align':"top", +        'wmode':"transparent"}; +    var par = { +        'flashvars':"uploadWidgetId=" + uploadId, +        'allowScriptAccess':"always"}; +    var swfNode = this.createSWF(att, par, uploadId); +    fileInput.remove(); +    var cntl = new FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database); +    jQuery(swfNode).parent().data('controller', cntl); +    return cntl; +  } +}; +///////////////////// +// FileController +/////////////////////// + +function FileController(view, scopeName, uploader, databaseUrl) { +  this.view = view; +  this.uploader = uploader; +  this.scopeName = scopeName; +  this.attachmentsPath = databaseUrl + '/_attachments'; +  this.value = null; +  this.lastValue = undefined; +}; + +angularCallbacks['flashEvent'] = function(id, event, args) { +  var object = document.getElementById(id); +  var jobject = jQuery(object); +  var controller = jobject.parent().data("controller"); +  FileController.prototype[event].apply(controller, args); +  _.defer(jobject.scope().get('$updateView')); +}; + +FileController.template = function(id) { +  return jQuery('<span class="ng-upload-widget">' + +      '<input type="checkbox" ng-non-bindable="true"/>' + +      '<object id="' + id + '" />' + +      '<a></a>' + +      '<span/>' + +    '</span>'); +}; + +extend(FileController.prototype, { +  'cancel': noop, +  'complete': noop, +  'httpStatus': function(status) { +    alert("httpStatus:" + this.scopeName + " status:" + status); +  }, +  'ioError': function() { +    alert("ioError:" + this.scopeName); +  }, +  'open': function() { +    alert("open:" + this.scopeName); +  }, +  'progress':noop, +  'securityError':  function() { +    alert("securityError:" + this.scopeName); +  }, +  'uploadCompleteData': function(data) { +    var value = fromJson(data); +    value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; +    this.view.find("input").attr('checked', true); +    var scope = this.view.scope(); +    this.value = value; +    this.updateModel(scope); +    this.value = null; +  }, +  'select': function(name, size, type) { +    this.name = name; +    this.view.find("a").text(name).attr('href', name); +    this.view.find("span").text(angular['filter']['bytes'](size)); +    this.upload(); +  }, + +  updateModel: function(scope) { +    var isChecked = this.view.find("input").attr('checked'); +    var value = isChecked ? this.value : null; +    if (this.lastValue === value) { +      return false; +    } else { +      scope.set(this.scopeName, value); +      return true; +    } +  }, + +  updateView: function(scope) { +    var modelValue = scope.get(this.scopeName); +    if (modelValue && this.value !== modelValue) { +      this.value = modelValue; +      this.view.find("a"). +        attr("href", this.value.url). +        text(this.value.text); +      this.view.find("span").text(angular['filter']['bytes'](this.value.size)); +    } +    this.view.find("input").attr('checked', !!modelValue); +  }, + +  upload: function() { +    if (this.name) { +      this.uploader['uploadFile'](this.attachmentsPath); +    } +  } +}); + +/////////////////////// +// NullController +/////////////////////// +function NullController(view) {this.view = view;}; +NullController.prototype = { +  updateModel: function() { return true; }, +  updateView: noop +}; +NullController.instance = new NullController(); + + +/////////////////////// +// ButtonController +/////////////////////// +var ButtonController = NullController; + +/////////////////////// +// TextController +/////////////////////// +function TextController(view, exp, formatter) { +  this.view = view; +  this.formatter = formatter; +  this.exp = exp; +  this.validator = view.getAttribute('ng-validate'); +  this.required = typeof view.attributes['ng-required'] != "undefined"; +  this.lastErrorText = null; +  this.lastValue = undefined; +  this.initialValue = this.formatter['parse'](view.value); +  var widget = view.getAttribute('ng-widget'); +  if (widget === 'datepicker') { +    jQuery(view).datepicker(); +  } +}; + +TextController.prototype = { +  updateModel: function(scope) { +    var value = this.formatter['parse'](this.view.value); +    if (this.lastValue === value) { +      return false; +    } else { +      scope.setEval(this.exp, value); +      this.lastValue = value; +      return true; +    } +  }, + +  updateView: function(scope) { +    var view = this.view; +    var value = scope.get(this.exp); +    if (typeof value === "undefined") { +      value = this.initialValue; +      scope.setEval(this.exp, value); +    } +    value = value ? value : ''; +    if (!_(this.lastValue).isEqual(value)) { +      view.value = this.formatter['format'](value); +      this.lastValue = value; +    } + +    var isValidationError = false; +    view.removeAttribute('ng-error'); +    if (this.required) { +      isValidationError = !(value && $.trim("" + value).length > 0); +    } +    var errorText = isValidationError ? "Required Value" : null; +    if (!isValidationError && this.validator && value) { +      errorText = scope.validate(this.validator, value, view); +      isValidationError = !!errorText; +    } +    if (this.lastErrorText !== errorText) { +      this.lastErrorText = isValidationError; +      if (errorText && isVisible(view)) { +        view.setAttribute('ng-error', errorText); +        scope.markInvalid(this); +      } +      jQuery(view).toggleClass('ng-validation-error', isValidationError); +    } +  } +}; + +/////////////////////// +// CheckboxController +/////////////////////// +function CheckboxController(view, exp, formatter) { +  this.view = view; +  this.exp = exp; +  this.lastValue = undefined; +  this.formatter = formatter; +  this.initialValue = this.formatter['parse'](view.checked ? view.value : ""); +}; + +CheckboxController.prototype = { +  updateModel: function(scope) { +    var input = this.view; +    var value = input.checked ? input.value : ''; +    value = this.formatter['parse'](value); +    value = this.formatter['format'](value); +    if (this.lastValue === value) { +      return false; +    } else { +      scope.setEval(this.exp, this.formatter['parse'](value)); +      this.lastValue = value; +      return true; +    } +  }, + +  updateView: function(scope) { +    var input = this.view; +    var value = scope.eval(this.exp); +    if (typeof value === "undefined") { +      value = this.initialValue; +      scope.setEval(this.exp, value); +    } +    input.checked = this.formatter['parse'](input.value) == value; +  } +}; + +/////////////////////// +// SelectController +/////////////////////// +function SelectController(view, exp) { +  this.view = view; +  this.exp = exp; +  this.lastValue = undefined; +  this.initialValue = view.value; +}; + +SelectController.prototype = { +  updateModel: function(scope) { +    var input = this.view; +    if (input.selectedIndex < 0) { +      scope.setEval(this.exp, null); +    } else { +      var value = this.view.value; +      if (this.lastValue === value) { +        return false; +      } else { +        scope.setEval(this.exp, value); +        this.lastValue = value; +        return true; +      } +    } +  }, + +  updateView: function(scope) { +    var input = this.view; +    var value = scope.get(this.exp); +    if (typeof value === 'undefined') { +      value = this.initialValue; +      scope.setEval(this.exp, value); +    } +    if (value !== this.lastValue) { +      input.value = value ? value : ""; +      this.lastValue = value; +    } +  } +}; + +/////////////////////// +// MultiSelectController +/////////////////////// +function MultiSelectController(view, exp) { +  this.view = view; +  this.exp = exp; +  this.lastValue = undefined; +  this.initialValue = this.selected(); +}; + +MultiSelectController.prototype = { +  selected: function () { +    var value = []; +    var options = this.view.options; +    for ( var i = 0; i < options.length; i++) { +      var option = options[i]; +      if (option.selected) { +        value.push(option.value); +      } +    } +    return value; +  }, + +  updateModel: function(scope) { +    var value = this.selected(); +    // TODO: This is wrong! no caching going on here as we are always comparing arrays +    if (this.lastValue === value) { +      return false; +    } else { +      scope.setEval(this.exp, value); +      this.lastValue = value; +      return true; +    } +  }, + +  updateView: function(scope) { +    var input = this.view; +    var selected = scope.get(this.exp); +    if (typeof selected === "undefined") { +      selected = this.initialValue; +      scope.setEval(this.exp, selected); +    } +    if (selected !== this.lastValue) { +      var options = input.options; +      for ( var i = 0; i < options.length; i++) { +        var option = options[i]; +        option.selected = _.include(selected, option.value); +      } +      this.lastValue = selected; +    } +  } +}; + +/////////////////////// +// RadioController +/////////////////////// +function RadioController(view, exp) { +  this.view = view; +  this.exp = exp; +  this.lastChecked = undefined; +  this.lastValue = undefined; +  this.inputValue = view.value; +  this.initialValue = view.checked ? view.value : null; +}; + +RadioController.prototype = { +  updateModel: function(scope) { +    var input = this.view; +    if (this.lastChecked) { +      return false; +    } else { +      input.checked = true; +      this.lastValue = scope.setEval(this.exp, this.inputValue); +      this.lastChecked = true; +      return true; +    } +  }, + +  updateView: function(scope) { +    var input = this.view; +    var value = scope.get(this.exp); +    if (this.initialValue && typeof value === "undefined") { +      value = this.initialValue; +      scope.setEval(this.exp, value); +    } +    if (this.lastValue != value) { +      this.lastChecked = input.checked = this.inputValue == (''+value); +      this.lastValue = value; +    } +  } +}; + +/////////////////////// +//ElementController +/////////////////////// +function BindUpdater(view, exp) { +  this.view = view; +  this.exp = Binder.parseBindings(exp); +  this.hasError = false; +}; + +BindUpdater.toText = function(obj) { +  var e = escapeHtml; +  switch(typeof obj) { +    case "string": +    case "boolean": +    case "number": +      return e(obj); +    case "function": +      return BindUpdater.toText(obj()); +    case "object": +      if (isNode(obj)) { +        return outerHTML(obj); +      } else if (obj instanceof angular.filter.Meta) { +        switch(typeof obj.html) { +          case "string": +          case "number": +            return obj.html; +          case "function": +            return obj.html(); +          case "object": +            if (isNode(obj.html)) +              return outerHTML(obj.html); +          default: +            break; +        } +        switch(typeof obj.text) { +          case "string": +          case "number": +            return e(obj.text); +          case "function": +            return e(obj.text()); +          default: +            break; +        } +      } +      if (obj === null) +        return ""; +      return e(toJson(obj, true)); +    default: +      return ""; +  } +}; + +BindUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    var html = []; +    var parts = this.exp; +    var length = parts.length; +    for(var i=0; i<length; i++) { +      var part = parts[i]; +      var binding = Binder.binding(part); +      if (binding) { +        scope.evalWidget(this, binding, {$element:this.view}, function(value){ +          html.push(BindUpdater.toText(value)); +        }, function(e, text){ +          setHtml(this.view, text); +        }); +        if (this.hasError) { +          return; +        } +      } else { +        html.push(escapeHtml(part)); +      } +    } +    setHtml(this.view, html.join('')); +  } +}; + +function BindAttrUpdater(view, attrs) { +  this.view = view; +  this.attrs = attrs; +}; + +BindAttrUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    var jNode = jQuery(this.view); +    var attributeTemplates = this.attrs; +    if (this.hasError) { +      this.hasError = false; +      jNode. +        removeClass('ng-exception'). +        removeAttr('ng-error'); +    } +    var isImage = jNode.is('img'); +    for (var attrName in attributeTemplates) { +      var attributeTemplate = Binder.parseBindings(attributeTemplates[attrName]); +      var attrValues = []; +      for ( var i = 0; i < attributeTemplate.length; i++) { +        var binding = Binder.binding(attributeTemplate[i]); +        if (binding) { +          try { +            var value = scope.eval(binding, {$element:jNode[0], attrName:attrName}); +            if (value && (value.constructor !== array || value.length !== 0)) +              attrValues.push(value); +          } catch (e) { +            this.hasError = true; +            error('BindAttrUpdater', e); +            var jsonError = toJson(e, true); +            attrValues.push('[' + jsonError + ']'); +            jNode. +              addClass('ng-exception'). +              attr('ng-error', jsonError); +          } +        } else { +          attrValues.push(attributeTemplate[i]); +        } +      } +      var attrValue = attrValues.length ? attrValues.join('') : null; +      if(isImage && attrName == 'src' && !attrValue) +        attrValue = scope.get('$config.blankImage'); +      jNode.attr(attrName, attrValue); +    } +  } +}; + +function EvalUpdater(view, exp) { +  this.view = view; +  this.exp = exp; +  this.hasError = false; +}; +EvalUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp); +  } +}; + +function HideUpdater(view, exp) { this.view = view; this.exp = exp; }; +HideUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp, {}, function(hideValue){ +      var view = jQuery(this.view); +      if (toBoolean(hideValue)) { +        view.hide(); +      } else { +        view.show(); +      } +    }); +  } +}; + +function ShowUpdater(view, exp) { this.view = view; this.exp = exp; }; +ShowUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp, {}, function(hideValue){ +      var view = jQuery(this.view); +      if (toBoolean(hideValue)) { +        view.show(); +      } else { +        view.hide(); +      } +    }); +  } +}; + +function ClassUpdater(view, exp) { this.view = view; this.exp = exp; }; +ClassUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp, {}, function(classValue){ +      if (classValue !== null && classValue !== undefined) { +        this.view.className = classValue; +      } +    }); +  } +}; + +function ClassEvenUpdater(view, exp) { this.view = view; this.exp = exp; }; +ClassEvenUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp, {}, function(classValue){ +      var index = scope.get('$index'); +      jQuery(this.view).toggleClass(classValue, index % 2 === 1); +    }); +  } +}; + +function ClassOddUpdater(view, exp) { this.view = view; this.exp = exp; }; +ClassOddUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp, {}, function(classValue){ +      var index = scope.get('$index'); +      jQuery(this.view).toggleClass(classValue, index % 2 === 0); +    }); +  } +}; + +function StyleUpdater(view, exp) { this.view = view; this.exp = exp; }; +StyleUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.exp, {}, function(styleValue){ +      jQuery(this.view).attr('style', "").css(styleValue); +    }); +  } +}; + +/////////////////////// +// RepeaterUpdater +/////////////////////// +function RepeaterUpdater(view, repeaterExpression, template, prefix) { +  this.view = view; +  this.template = template; +  this.prefix = prefix; +  this.children = []; +  var match = repeaterExpression.match(/^\s*(.+)\s+in\s+(.*)\s*$/); +  if (! match) { +    throw "Expected ng-repeat in form of 'item in collection' but got '" + +      repeaterExpression + "'."; +  } +  var keyValue = match[1]; +  this.iteratorExp = match[2]; +  match = keyValue.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); +  if (!match) { +    throw "'item' in 'item in collection' should be identifier or (key, value) but get '" + +      keyValue + "'."; +  } +  this.valueExp = match[3] || match[1]; +  this.keyExp = match[2]; +}; + +RepeaterUpdater.prototype = { +  updateModel: noop, +  updateView: function(scope) { +    scope.evalWidget(this, this.iteratorExp, {}, function(iterator){ +      var self = this; +      if (!iterator) { +        iterator = []; +        if (scope.isProperty(this.iteratorExp)) { +          scope.set(this.iteratorExp, iterator); +        } +      } +      var childrenLength = this.children.length; +      var cursor = this.view; +      var time = 0; +      var child = null; +      var keyExp = this.keyExp; +      var valueExp = this.valueExp; +      var iteratorCounter = 0; +      foreach(iterator, function(value, key){ +        if (iteratorCounter < childrenLength) { +          // reuse children +          child = self.children[iteratorCounter]; +          child.scope.set(valueExp, value); +        } else { +          // grow children +          var name = self.prefix + +            valueExp + " in " + self.iteratorExp + "[" + iteratorCounter + "]"; +          var childScope = new Scope(scope.state, name); +          childScope.set('$index', iteratorCounter); +          if (keyExp) +            childScope.set(keyExp, key); +          childScope.set(valueExp, value); +          child = { scope:childScope, element:self.template(childScope, self.prefix, iteratorCounter) }; +          cursor.after(child.element); +          self.children.push(child); +        } +        cursor = child.element; +        var s = new Date().getTime(); +        child.scope.updateView(); +        time += new Date().getTime() - s; +        iteratorCounter++; +      }); +      // shrink children +      for ( var r = childrenLength; r > iteratorCounter; --r) { +        this.children.pop().element.remove(); +      } +      // Special case for option in select +      if (child && child.element[0].nodeName === "OPTION") { +        var select = jQuery(child.element[0].parentNode); +        var cntl = select.data('controller'); +        if (cntl) { +          cntl.lastValue = undefined; +          cntl.updateView(scope); +        } +      } +    }); +  } +}; + +////////////////////////////////// +// PopUp +////////////////////////////////// + +function PopUp(doc) { +  this.doc = doc; +}; + +PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup"; + +PopUp.onOver = function(e) { +  PopUp.onOut(); +  var jNode = jQuery(this); +  jNode.bind(PopUp.OUT_EVENT, PopUp.onOut); +  var position = jNode.position(); +  var de = document.documentElement; +  var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; +  var hasArea = w - position.left; +  var width = 300; +  var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."; +  var msg = jNode.attr("ng-error"); + +  var x; +  var arrowPos = hasArea>(width+75) ? "left" : "right"; +  var tip = jQuery( +    "<div id='ng-callout' style='width:"+width+"px'>" + +      "<div class='ng-arrow-"+arrowPos+"'/>" + +      "<div class='ng-title'>"+title+"</div>" + +      "<div class='ng-content'>"+msg+"</div>" + +    "</div>"); +  jQuery("body").append(tip); +  if(arrowPos === 'left'){ +    x = position.left + this.offsetWidth + 11; +  }else{ +    x = position.left - (width + 15); +    tip.find('.ng-arrow-right').css({left:width+1}); +  } + +  tip.css({left: x+"px", top: (position.top - 3)+"px"}); +  return true; +}; + +PopUp.onOut = function() { +  jQuery('#ng-callout'). +    unbind(PopUp.OUT_EVENT, PopUp.onOut). +    remove(); +  return true; +}; + +PopUp.prototype = { +  bind: function () { +    var self = this; +    this.doc.find('.ng-validation-error,.ng-exception'). +      live("mouseover", PopUp.onOver); +  } +}; + +////////////////////////////////// +// Status +////////////////////////////////// + +function NullStatus(body) { +}; + +NullStatus.prototype = { + beginRequest:function(){}, + endRequest:function(){} +}; + +function Status(body) { +  this.requestCount = 0; +  this.body = body; +}; + +Status.DOM ='<div id="ng-spacer"></div><div id="ng-loading">loading....</div>'; + +Status.prototype = { +  beginRequest: function () { +    if (this.requestCount === 0) { +      (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show(); +    } +    this.requestCount++; +  }, + +  endRequest: function () { +    this.requestCount--; +    if (this.requestCount === 0) { +      this.loader.hide("fold"); +    } +  } +}; diff --git a/src/directives.js b/src/directives.js index 10476c77..c54c89e9 100644 --- a/src/directives.js +++ b/src/directives.js @@ -11,9 +11,15 @@ angularDirective("ng-eval", function(expression){  });  angularDirective("ng-bind", function(expression){ +  var templateFn = compileBindTemplate("{{" + expression + "}}");    return function(element) { -    this.$watch(expression, function(value){ -      element.text(value); +    var lastValue; +    this.$onEval(function() { +      var value = templateFn.call(this); +      if (value != lastValue) { +        element.text(value); +        lastValue = value; +      }      }, element);    };  }); @@ -34,7 +40,9 @@ function compileBindTemplate(template){      bindTemplateCache[template] = fn = function(){        var parts = [], self = this;        foreach(bindings, function(fn){ -        parts.push(fn.call(self)); +        var value = fn.call(self); +        if (isObject(value)) value = toJson(value, true); +        parts.push(value);        });        return parts.join('');      }; @@ -125,6 +133,7 @@ angularDirective("ng-action", function(expression, element){      var self = this;      element.click(function(){        self.$tryEval(expression, element); +      self.$eval();      });    };  }); diff --git a/src/jqLite.js b/src/jqLite.js index 7646bf98..a5014354 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -38,7 +38,8 @@ function JQLite(element) {    this[0] = element;  } -function jqLite(element) { + +function jqLiteWrap(element) {    if (typeof element == 'string') {      var div = document.createElement('div');      div.innerHTML = element; @@ -47,6 +48,8 @@ function jqLite(element) {    return element instanceof JQLite ? element : new JQLite(element);  } +jqLite = jqLite || jqLiteWrap; +  JQLite.prototype = {    data: function(key, value) {      var element = this[0], @@ -85,12 +88,15 @@ JQLite.prototype = {      foreach(type.split(' '), function(type){        eventHandler = bind[type];        if (!eventHandler) { -        bind[type] = eventHandler = function() { -          var value = false; +        bind[type] = eventHandler = function(event) { +          var bubbleEvent = false;            foreach(eventHandler.fns, function(fn){ -            value = value || fn.apply(self, arguments); +            bubbleEvent = bubbleEvent || fn.apply(self, arguments);            }); -          return value; +          if (!bubbleEvent) { +            event.preventDefault(); +            event.stopPropagation(); +          }          };          eventHandler.fns = [];          addEventListener(element, type, eventHandler); diff --git a/src/markup.js b/src/markups.js index 5fb10779..6bc27c85 100644 --- a/src/markup.js +++ b/src/markups.js @@ -24,7 +24,7 @@ function binding(string) {  };  function hasBindings(bindings) { -  return bindings.length > 1 || Binder.binding(bindings[0]) !== null; +  return bindings.length > 1 || binding(bindings[0]) !== null;  };  angularTextMarkup('{{}}', function(text, textNode, parentElement) { diff --git a/src/ControlBar.js b/src/moveToAngularCom/ControlBar.js index 685beeb2..685beeb2 100644 --- a/src/ControlBar.js +++ b/src/moveToAngularCom/ControlBar.js diff --git a/src/DataStore.js b/src/moveToAngularCom/DataStore.js index 70bcc623..70bcc623 100644 --- a/src/DataStore.js +++ b/src/moveToAngularCom/DataStore.js diff --git a/src/Model.js b/src/moveToAngularCom/Model.js index b09efd0e..b09efd0e 100644 --- a/src/Model.js +++ b/src/moveToAngularCom/Model.js diff --git a/src/Server.js b/src/moveToAngularCom/Server.js index 5c4ec3c6..5c4ec3c6 100644 --- a/src/Server.js +++ b/src/moveToAngularCom/Server.js diff --git a/src/Users.js b/src/moveToAngularCom/Users.js index fb5845d3..fb5845d3 100644 --- a/src/Users.js +++ b/src/moveToAngularCom/Users.js diff --git a/src/widgets2.js b/src/widgets2.js deleted file mode 100644 index 04045426..00000000 --- a/src/widgets2.js +++ /dev/null @@ -1,129 +0,0 @@ -function modelAccessor(scope, element) { -  var expr = element.attr('name'), -      farmatterName = element.attr('ng-format') || NOOP, -      formatter = angularFormatter(farmatterName); -  if (!expr) throw "Required field 'name' not found."; -  if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; -  return { -    get: function() { -      return formatter['format'](scope.$eval(expr)); -    }, -    set: function(value) { -      scope.$eval(expr + '=' + toJson(formatter['parse'](value))); -    } -  }; -} - -function valueAccessor(element) { -  var validatorName = element.attr('ng-validate') || NOOP, -      validator = angularValidator(validatorName), -      required = element.attr('ng-required'), -      lastError; -  required = required || required == ''; -  if (!validator) throw "Validator named '" + validatorName + "' not found."; -  function validate(value) { -    var error = required && !trim(value) ? "Required" : validator(value); -    if (error !== lastError) { -      if (error) { -        element.addClass(NG_VALIDATION_ERROR); -        element.attr(NG_ERROR, error); -      } else { -        element.removeClass(NG_VALIDATION_ERROR); -        element.removeAttr(NG_ERROR); -      } -      lastError = error; -    } -    return value; -  } -  return { -    get: function(){ return validate(element.val()); }, -    set: function(value){ element.val(validate(value)); } -  }; -} - -function checkedAccessor(element) { -  var domElement = element[0]; -  return { -    get: function(){ return !!domElement.checked; }, -    set: function(value){ domElement.checked = !!value; } -  }; -} - -function radioAccessor(element) { -  var domElement = element[0]; -  return { -    get: function(){ return domElement.checked ? domElement.value : null; }, -    set: function(value){ domElement.checked = value == domElement.value; } -  }; -} - -function optionsAccessor(element) { -  var options = element[0].options; -  return { -    get: function(){ -      var values = []; -      foreach(options, function(option){ -        if (option.selected) values.push(option.value); -      }); -      return values; -    }, -    set: function(values){ -      var keys = {}; -      foreach(values, function(value){ keys[value] = true; }); -      foreach(options, function(option){ -        option.selected = keys[option.value]; -      }); -    } -  }; -} - -function noopAccessor() { return { get: noop, set: noop }; } - -var NG_ERROR = 'ng-error', -    NG_VALIDATION_ERROR = 'ng-validation-error', -    textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), -    buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), -    INPUT_TYPE = { -      'text':            textWidget, -      'textarea':        textWidget, -      'hidden':          textWidget, -      'password':        textWidget, -      'button':          buttonWidget, -      'submit':          buttonWidget, -      'reset':           buttonWidget, -      'image':           buttonWidget, -      'checkbox':        inputWidget('click', modelAccessor, checkedAccessor, false), -      'radio':           inputWidget('click', modelAccessor, radioAccessor, undefined), -      'select-one':      inputWidget('click', modelAccessor, valueAccessor, null), -      'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, []) -//      'file':            fileWidget??? -    }; - -function inputWidget(events, modelAccessor, viewAccessor, initValue) { -  return function(element) { -    var scope = this, -        model = modelAccessor(scope, element), -        view = viewAccessor(element), -        action = element.attr('ng-action') || '', -        value = view.get() || copy(initValue); -    if (isDefined(value)) model.set(value); -    this.$eval(element.attr('ng-init')||''); -    element.bind(events, function(){ -      model.set(view.get()); -      scope.$tryEval(action, element); -    }); -    scope.$watch(model.get, view.set); -  }; -} - -function inputWidgetSelector(element){ -  return INPUT_TYPE[lowercase(element[0].type)] || noop; -} - -angularWidget('INPUT', inputWidgetSelector); -angularWidget('TEXTAREA', inputWidgetSelector); -angularWidget('BUTTON', inputWidgetSelector); -angularWidget('SELECT', function(element){ -  this.descend(true); -  return inputWidgetSelector.call(this, element); -}); diff --git a/test/ScopeSpec.js b/test/ScopeSpec.js index 0a0b4241..cfae42a8 100644 --- a/test/ScopeSpec.js +++ b/test/ScopeSpec.js @@ -1,13 +1,13 @@  describe('scope/model', function(){    it('should create a scope with parent', function(){ -    var model = scope({name:'Misko'}); +    var model = createScope({name:'Misko'});      expect(model.name).toEqual('Misko');    });    it('should have $get/set$/parent$', function(){      var parent = {}; -    var model = scope(parent); +    var model = createScope(parent);      model.$set('name', 'adam');      expect(model.name).toEqual('adam');      expect(model.$get('name')).toEqual('adam'); @@ -16,7 +16,7 @@ describe('scope/model', function(){    //$eval    it('should eval function with correct this and pass arguments', function(){ -    var model = scope(); +    var model = createScope();      model.$eval(function(name){        this.name = name;      }, 'works'); @@ -24,14 +24,14 @@ describe('scope/model', function(){    });    it('should eval expression with correct this', function(){ -    var model = scope(); +    var model = createScope();      model.$eval('name="works"');      expect(model.name).toEqual('works');    });    //$onEval    it('should watch an expression for change', function(){ -    var model = scope(); +    var model = createScope();      model.oldValue = "";      var count = 0;      model.name = 'adam'; @@ -48,7 +48,7 @@ describe('scope/model', function(){    });    it('should eval with no arguments', function(){ -    var model = scope(); +    var model = createScope();      var count = 0;      model.$onEval(function(){count++;});      model.$eval(); @@ -57,7 +57,7 @@ describe('scope/model', function(){    //$bind    it('should curry a function with respect to scope', function(){ -    var model = scope(); +    var model = createScope();      model.name = 'misko';      expect(model.$bind(function(){return this.name;})()).toEqual('misko');    }); @@ -70,7 +70,7 @@ describe('scope/model', function(){      Printer.prototype.print = function(){        this.printed = true;      }; -    var model = scope({ name: 'parent' }, Printer, 'hp'); +    var model = createScope({ name: 'parent' }, Printer, 'hp');      expect(model.brand).toEqual('hp');      model.print();      expect(model.printed).toEqual(true); diff --git a/test/ScopeTest.js b/test/delete/ScopeTest.js index 24febf19..24febf19 100644 --- a/test/ScopeTest.js +++ b/test/delete/ScopeTest.js diff --git a/test/Base64Test.js b/test/moveToAngularCom/Base64Test.js index a9353186..a9353186 100644 --- a/test/Base64Test.js +++ b/test/moveToAngularCom/Base64Test.js diff --git a/test/DataStoreTest.js b/test/moveToAngularCom/DataStoreTest.js index 87c5be2e..87c5be2e 100644 --- a/test/DataStoreTest.js +++ b/test/moveToAngularCom/DataStoreTest.js diff --git a/test/EntityDeclarationTest.js b/test/moveToAngularCom/EntityDeclarationTest.js index 28986ea8..28986ea8 100644 --- a/test/EntityDeclarationTest.js +++ b/test/moveToAngularCom/EntityDeclarationTest.js diff --git a/test/ModelTest.js b/test/moveToAngularCom/ModelTest.js index dbd97778..dbd97778 100644 --- a/test/ModelTest.js +++ b/test/moveToAngularCom/ModelTest.js diff --git a/test/ServerTest.js b/test/moveToAngularCom/ServerTest.js index 02fab84c..02fab84c 100644 --- a/test/ServerTest.js +++ b/test/moveToAngularCom/ServerTest.js diff --git a/test/UsersTest.js b/test/moveToAngularCom/UsersTest.js index f0ff545a..f0ff545a 100644 --- a/test/UsersTest.js +++ b/test/moveToAngularCom/UsersTest.js | 
