aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--example/widgets.html10
-rw-r--r--scenario/style.css7
-rw-r--r--scenario/widgets.html128
-rw-r--r--src/Angular.js234
-rw-r--r--src/Compiler.js11
-rw-r--r--src/Formatters.js11
-rw-r--r--src/JSON.js4
-rw-r--r--src/Scope.js278
-rw-r--r--src/UrlWatcher.js62
-rw-r--r--src/Widgets.js927
-rw-r--r--src/angular-bootstrap.js61
-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.js407
-rw-r--r--src/delete/Widgets.js806
-rw-r--r--src/directives.js15
-rw-r--r--src/jqLite.js16
-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.js129
-rw-r--r--test/ScopeSpec.js16
-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