aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/API.js318
-rw-r--r--src/Binder.js341
-rw-r--r--src/ControlBar.js71
-rw-r--r--src/DataStore.js332
-rw-r--r--src/Filters.js290
-rw-r--r--src/JSON.js92
-rw-r--r--src/Loader.js389
-rw-r--r--src/Model.js65
-rw-r--r--src/Parser.js741
-rw-r--r--src/Scope.js198
-rw-r--r--src/Server.js69
-rw-r--r--src/Users.js36
-rw-r--r--src/Validators.js80
-rw-r--r--src/Widgets.js774
-rw-r--r--src/Widgets.js.orig764
-rw-r--r--src/XSitePost.js100
-rw-r--r--src/angular-bootstrap.js100
-rw-r--r--src/test/Runner.js160
-rw-r--r--src/test/Steps.js57
-rw-r--r--src/test/_namespace.js5
20 files changed, 4982 insertions, 0 deletions
diff --git a/src/API.js b/src/API.js
new file mode 100644
index 00000000..c51fe01d
--- /dev/null
+++ b/src/API.js
@@ -0,0 +1,318 @@
+angular.Global = {
+ typeOf:function(obj){
+ var type = typeof obj;
+ switch(type) {
+ case "object":
+ if (obj === null) return "null";
+ if (obj instanceof Array) return "array";
+ if (obj instanceof Date) return "date";
+ if (obj.nodeType == 1) return "element";
+ }
+ return type;
+ }
+};
+
+angular.Collection = {};
+angular.Object = {};
+angular.Array = {
+ includeIf:function(array, value, condition) {
+ var index = _.indexOf(array, value);
+ if (condition) {
+ if (index == -1)
+ array.push(value);
+ } else {
+ array.splice(index, 1);
+ }
+ return array;
+ },
+ sum:function(array, expression) {
+ var fn = angular.Function.compile(expression);
+ var sum = 0;
+ for (var i = 0; i < array.length; i++) {
+ var value = 1 * fn(array[i]);
+ if (!isNaN(value)){
+ sum += value;
+ }
+ }
+ return sum;
+ },
+ remove:function(array, value) {
+ var index = _.indexOf(array, value);
+ if (index >=0)
+ array.splice(index, 1);
+ return value;
+ },
+ find:function(array, condition, defaultValue) {
+ if (!condition) return undefined;
+ var fn = angular.Function.compile(condition);
+ _.detect(array, function($){
+ if (fn($)){
+ defaultValue = $;
+ return true;
+ }
+ });
+ return defaultValue;
+ },
+ findById:function(array, id) {
+ return angular.Array.find(array, function($){return $.$id == id;}, null);
+ },
+ filter:function(array, expression) {
+ var predicates = [];
+ predicates.check = function(value) {
+ for (var j = 0; j < predicates.length; j++) {
+ if(!predicates[j](value)) {
+ return false;
+ }
+ }
+ return true;
+ };
+ var getter = nglr.Scope.getter;
+ var search = function(obj, text){
+ if (text.charAt(0) === '!') {
+ return !search(obj, text.substr(1));
+ }
+ switch (typeof obj) {
+ case "boolean":
+ case "number":
+ case "string":
+ return ('' + obj).toLowerCase().indexOf(text) > -1;
+ case "object":
+ for ( var objKey in obj) {
+ if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
+ return true;
+ }
+ }
+ return false;
+ case "array":
+ for ( var i = 0; i < obj.length; i++) {
+ if (search(obj[i], text)) {
+ return true;
+ }
+ }
+ return false;
+ default:
+ return false;
+ }
+ };
+ switch (typeof expression) {
+ case "boolean":
+ case "number":
+ case "string":
+ expression = {$:expression};
+ case "object":
+ for (var key in expression) {
+ if (key == '$') {
+ (function(){
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(value, text);
+ });
+ })();
+ } else {
+ (function(){
+ var path = key;
+ var text = (''+expression[key]).toLowerCase();
+ if (!text) return;
+ predicates.push(function(value) {
+ return search(getter(value, path), text);
+ });
+ })();
+ }
+ }
+ break;
+ case "function":
+ predicates.push(expression);
+ break;
+ default:
+ return array;
+ }
+ var filtered = [];
+ for ( var j = 0; j < array.length; j++) {
+ var value = array[j];
+ if (predicates.check(value)) {
+ filtered.push(value);
+ }
+ }
+ return filtered;
+ },
+ add:function(array, value) {
+ array.push(_.isUndefined(value)? {} : value);
+ return array;
+ },
+ count:function(array, condition) {
+ if (!condition) return array.length;
+ var fn = angular.Function.compile(condition);
+ return _.reduce(array, 0, function(count, $){return count + (fn($)?1:0);});
+ },
+ orderBy:function(array, expression, descend) {
+ function reverse(comp, descending) {
+ return nglr.toBoolean(descending) ?
+ function(a,b){return comp(b,a);} : comp;
+ }
+ function compare(v1, v2){
+ var t1 = typeof v1;
+ var t2 = typeof v2;
+ if (t1 == t2) {
+ if (t1 == "string") v1 = v1.toLowerCase();
+ if (t1 == "string") v2 = v2.toLowerCase();
+ if (v1 === v2) return 0;
+ return v1 < v2 ? -1 : 1;
+ } else {
+ return t1 < t2 ? -1 : 1;
+ }
+ }
+ expression = _.isArray(expression) ? expression: [expression];
+ expression = _.map(expression, function($){
+ var descending = false;
+ if (typeof $ == "string" && ($.charAt(0) == '+' || $.charAt(0) == '-')) {
+ descending = $.charAt(0) == '-';
+ $ = $.substring(1);
+ }
+ var get = $ ? angular.Function.compile($) : _.identity;
+ return reverse(function(a,b){
+ return compare(get(a),get(b));
+ }, descending);
+ });
+ var comparator = function(o1, o2){
+ for ( var i = 0; i < expression.length; i++) {
+ var comp = expression[i](o1, o2);
+ if (comp != 0) return comp;
+ }
+ return 0;
+ };
+ return _.clone(array).sort(reverse(comparator, descend));
+ },
+ orderByToggle:function(predicate, attribute) {
+ var STRIP = /^([+|-])?(.*)/;
+ var ascending = false;
+ var index = -1;
+ _.detect(predicate, function($, i){
+ if ($ == attribute) {
+ ascending = true;
+ index = i;
+ return true;
+ }
+ if (($.charAt(0)=='+'||$.charAt(0)=='-') && $.substring(1) == attribute) {
+ ascending = $.charAt(0) == '+';
+ index = i;
+ return true;
+ };
+ });
+ if (index >= 0) {
+ predicate.splice(index, 1);
+ }
+ predicate.unshift((ascending ? "-" : "+") + attribute);
+ return predicate;
+ },
+ orderByDirection:function(predicate, attribute, ascend, descend) {
+ ascend = ascend || 'ng-ascend';
+ descend = descend || 'ng-descend';
+ var att = predicate[0] || '';
+ var direction = true;
+ if (att.charAt(0) == '-') {
+ att = att.substring(1);
+ direction = false;
+ } else if(att.charAt(0) == '+') {
+ att = att.substring(1);
+ }
+ return att == attribute ? (direction ? ascend : descend) : "";
+ },
+ merge:function(array, index, mergeValue) {
+ var value = array[index];
+ if (!value) {
+ value = {};
+ array[index] = value;
+ }
+ nglr.merge(mergeValue, value);
+ return array;
+ }
+};
+angular.String = {
+ quote:function(string) {
+ return '"' + string.replace(/\\/g, '\\\\').
+ replace(/"/g, '\\"').
+ replace(/\n/g, '\\n').
+ replace(/\f/g, '\\f').
+ replace(/\r/g, '\\r').
+ replace(/\t/g, '\\t').
+ replace(/\v/g, '\\v') +
+ '"';
+ },
+ quoteUnicode:function(string) {
+ var str = angular.String.quote(string);
+ var chars = [];
+ for ( var i = 0; i < str.length; i++) {
+ var ch = str.charCodeAt(i);
+ if (ch < 128) {
+ chars.push(str.charAt(i));
+ } else {
+ var encode = "000" + ch.toString(16);
+ chars.push("\\u" + encode.substring(encode.length - 4));
+ }
+ }
+ return chars.join('');
+ },
+ toDate:function(string){
+ var match;
+ 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]);
+ date.setUTCHours(match[4], match[5], match[6], 0);
+ return date;
+ }
+ return string;
+ }
+};
+angular.Date = {
+ toString:function(date){
+ function pad(n) { return n < 10 ? "0" + n : n; }
+ return (date.getUTCFullYear()) + '-' +
+ pad(date.getUTCMonth() + 1) + '-' +
+ pad(date.getUTCDate()) + 'T' +
+ pad(date.getUTCHours()) + ':' +
+ pad(date.getUTCMinutes()) + ':' +
+ pad(date.getUTCSeconds()) + 'Z';
+ }
+ };
+angular.Function = {
+ compile:function(expression) {
+ if (_.isFunction(expression)){
+ return expression;
+ } else if (expression){
+ var scope = new nglr.Scope();
+ return function($) {
+ scope.state = $;
+ return scope.eval(expression);
+ };
+ } else {
+ return function($){return $;};
+ }
+ }
+};
+
+(function(){
+ function extend(dst, src, names){
+ _.extend(dst, src);
+ _.each((names||[]), function(name){
+ dst[name] = _[name];
+ });
+ };
+ extend(angular.Global, {},
+ ['extend', 'clone','isEqual',
+ 'isElement', 'isArray', 'isFunction', 'isUndefined']);
+ extend(angular.Collection, angular.Global,
+ ['each', 'map', 'reduce', 'reduceRight', 'detect',
+ 'select', 'reject', 'all', 'any', 'include',
+ 'invoke', 'pluck', 'max', 'min', 'sortBy',
+ 'sortedIndex', 'toArray', 'size']);
+ extend(angular.Array, angular.Collection,
+ ['first', 'last', 'compact', 'flatten', 'without',
+ 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']);
+ extend(angular.Object, angular.Collection,
+ ['keys', 'values']);
+ extend(angular.String, angular.Global);
+ extend(angular.Function, angular.Global,
+ ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']);
+})(); \ No newline at end of file
diff --git a/src/Binder.js b/src/Binder.js
new file mode 100644
index 00000000..86e99fb8
--- /dev/null
+++ b/src/Binder.js
@@ -0,0 +1,341 @@
+// Copyright (C) 2009 BRAT Tech LLC
+nglr.Binder = function(doc, widgetFactory, urlWatcher, config) {
+ this.doc = doc;
+ this.urlWatcher = urlWatcher;
+ this.anchor = {};
+ this.widgetFactory = widgetFactory;
+ this.config = config || {};
+ this.updateListeners = [];
+};
+
+nglr.Binder.parseBindings = function(string) {
+ var results = [];
+ var lastIndex = 0;
+ var index;
+ while((index = string.indexOf('{{', lastIndex)) > -1) {
+ if (lastIndex < index)
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+
+ index = string.indexOf('}}', index);
+ index = index < 0 ? string.length : index + 2;
+
+ results.push(string.substr(lastIndex, index - lastIndex));
+ lastIndex = index;
+ }
+ if (lastIndex != string.length)
+ results.push(string.substr(lastIndex, string.length - lastIndex));
+ return results.length === 0 ? [ string ] : results;
+};
+
+nglr.Binder.hasBinding = function(string) {
+ var bindings = nglr.Binder.parseBindings(string);
+ return bindings.length > 1 || nglr.Binder.binding(bindings[0]) !== null;
+};
+
+nglr.Binder.binding = function(string) {
+ var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/);
+ return binding ? binding[1] : null;
+};
+
+
+nglr.Binder.prototype.parseQueryString = function(query) {
+ var params = {};
+ query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g,
+ function (match, left, right) {
+ if (left) params[decodeURIComponent(left)] = decodeURIComponent(right);
+ });
+ return params;
+};
+
+nglr.Binder.prototype.parseAnchor = function(url) {
+ var self = this;
+ url = url || this.urlWatcher.getUrl();
+
+ var anchorIndex = url.indexOf('#');
+ if (anchorIndex < 0) return;
+ var anchor = url.substring(anchorIndex + 1);
+
+ var anchorQuery = this.parseQueryString(anchor);
+ jQuery.each(self.anchor, function(key, newValue) {
+ delete self.anchor[key];
+ });
+ jQuery.each(anchorQuery, function(key, newValue) {
+ self.anchor[key] = newValue;
+ });
+};
+
+nglr.Binder.prototype.onUrlChange = function (url) {
+ console.log("URL change detected", url);
+ this.parseAnchor(url);
+ this.updateView();
+};
+
+nglr.Binder.prototype.updateAnchor = function() {
+ var url = this.urlWatcher.getUrl();
+ var anchorIndex = url.indexOf('#');
+ if (anchorIndex > -1)
+ url = url.substring(0, anchorIndex);
+ url += "#";
+ var sep = '';
+ for (var key in this.anchor) {
+ var value = this.anchor[key];
+ if (typeof value === 'undefined' || value === null) {
+ delete this.anchor[key];
+ } else {
+ url += sep + encodeURIComponent(key);
+ if (value !== true)
+ url += "=" + encodeURIComponent(value);
+ sep = '&';
+ }
+ }
+ this.urlWatcher.setUrl(url);
+ return url;
+};
+
+nglr.Binder.prototype.updateView = function() {
+ var start = new Date().getTime();
+ var scope = jQuery(this.doc).scope();
+ scope.set("$invalidWidgets", []);
+ scope.updateView();
+ var end = new Date().getTime();
+ this.updateAnchor();
+ _.each(this.updateListeners, function(fn) {fn();});
+};
+
+nglr.Binder.prototype.executeInit = function() {
+ jQuery("[ng-init]", this.doc).each(function() {
+ var jThis = jQuery(this);
+ var scope = jThis.scope();
+ try {
+ scope.eval(jThis.attr('ng-init'));
+ } catch (e) {
+ nglr.alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + nglr.toJson(e, true));
+ }
+ });
+};
+
+nglr.Binder.prototype.entity = function (scope) {
+ jQuery("[ng-entity]", this.doc).attr("ng-watch", function() {
+ try {
+ var jNode = jQuery(this);
+ var decl = scope.entity(jNode.attr("ng-entity"));
+ return decl + (jNode.attr('ng-watch') || "");
+ } catch (e) {
+ nglr.alert(e);
+ }
+ });
+};
+
+nglr.Binder.prototype.compile = function() {
+ var jNode = jQuery(this.doc);
+ var self = this;
+ if (this.config.autoSubmit) {
+ var submits = jQuery(":submit", this.doc).not("[ng-action]");
+ submits.attr("ng-action", "$save()");
+ submits.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr", '{disabled:"{{$invalidWidgets}}"}');
+ }
+ this.precompile(this.doc)(this.doc, jNode.scope(), "");
+ jQuery("a[ng-action]", this.doc).live('click', function (event) {
+ var jNode = jQuery(this);
+ try {
+ jNode.scope().eval(jNode.attr('ng-action'));
+ jNode.removeAttr('ng-error');
+ jNode.removeClass("ng-exception");
+ } catch (e) {
+ jNode.addClass("ng-exception");
+ jNode.attr('ng-error', nglr.toJson(e, true));
+ }
+ self.updateView();
+ return false;
+ });
+};
+
+nglr.Binder.prototype.translateBinding = function(node, parentPath, factories) {
+ var path = parentPath.concat();
+ var offset = path.pop();
+ var parts = nglr.Binder.parseBindings(node.nodeValue);
+ if (parts.length > 1 || nglr.Binder.binding(parts[0])) {
+ var parent = node.parentNode;
+ if (nglr.isLeafNode(parent)) {
+ parent.setAttribute('ng-bind-template', node.nodeValue);
+ factories.push({path:path, fn:function(node, scope, prefix) {
+ return new nglr.BindUpdater(node, node.getAttribute('ng-bind-template'));
+ }});
+ } else {
+ for (var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ var binding = nglr.Binder.binding(part);
+ var newNode;
+ if (binding) {
+ newNode = document.createElement("span");
+ var jNewNode = jQuery(newNode);
+ jNewNode.attr("ng-bind", binding);
+ if (i === 0) {
+ factories.push({path:path.concat(offset + i), fn:nglr.Binder.prototype.ng_bind});
+ }
+ } else if (nglr.msie && part.charAt(0) == ' ') {
+ newNode = document.createElement("span");
+ newNode.innerHTML = '&nbsp;' + part.substring(1);
+ } else {
+ newNode = document.createTextNode(part);
+ }
+ parent.insertBefore(newNode, node);
+ }
+ }
+ parent.removeChild(node);
+ }
+};
+
+nglr.Binder.prototype.precompile = function(root) {
+ var factories = [];
+ this.precompileNode(root, [], factories);
+ return function (template, scope, prefix) {
+ var len = factories.length;
+ for (var i = 0; i < len; i++) {
+ var factory = factories[i];
+ var node = template;
+ var path = factory.path;
+ for (var j = 0; j < path.length; j++) {
+ node = node.childNodes[path[j]];
+ }
+ try {
+ scope.addWidget(factory.fn(node, scope, prefix));
+ } catch (e) {
+ nglr.alert(e);
+ }
+ }
+ };
+};
+
+nglr.Binder.prototype.precompileNode = function(node, path, factories) {
+ var nodeType = node.nodeType;
+ if (nodeType == Node.TEXT_NODE) {
+ this.translateBinding(node, path, factories);
+ return;
+ } else if (nodeType != Node.ELEMENT_NODE && nodeType != Node.DOCUMENT_NODE) {
+ return;
+ }
+
+ if (!node.getAttribute) return;
+ var nonBindable = node.getAttribute('ng-non-bindable');
+ if (nonBindable || nonBindable === "") return;
+
+ var attributes = node.attributes;
+ if (attributes) {
+ var bindings = node.getAttribute('ng-bind-attr');
+ node.removeAttribute('ng-bind-attr');
+ bindings = bindings ? nglr.fromJson(bindings) : {};
+ var attrLen = attributes.length;
+ for (var i = 0; i < attrLen; i++) {
+ var attr = attributes[i];
+ var attrName = attr.name;
+ // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
+ var attrValue = nglr.msie && attrName == 'href' ?
+ decodeURI(node.getAttribute(attrName, 2)) : attr.value;
+ if (nglr.Binder.hasBinding(attrValue)) {
+ bindings[attrName] = attrValue;
+ }
+ }
+ var json = nglr.toJson(bindings);
+ if (json.length > 2) {
+ node.setAttribute("ng-bind-attr", json);
+ }
+ }
+
+ if (!node.getAttribute) console.log(node);
+ var repeaterExpression = node.getAttribute('ng-repeat');
+ if (repeaterExpression) {
+ node.removeAttribute('ng-repeat');
+ var precompiled = this.precompile(node);
+ var view = document.createComment("ng-repeat: " + repeaterExpression);
+ var parentNode = node.parentNode;
+ parentNode.insertBefore(view, node);
+ parentNode.removeChild(node);
+ var template = function(childScope, prefix, i) {
+ var clone = jQuery(node).clone();
+ clone.css('display', '');
+ clone.attr('ng-repeat-index', "" + i);
+ clone.data('scope', childScope);
+ precompiled(clone[0], childScope, prefix + i + ":");
+ return clone;
+ };
+ factories.push({path:path, fn:function(node, scope, prefix) {
+ return new nglr.RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix);
+ }});
+ return;
+ }
+
+ if (node.getAttribute('ng-eval')) factories.push({path:path, fn:this.ng_eval});
+ if (node.getAttribute('ng-bind')) factories.push({path:path, fn:this.ng_bind});
+ if (node.getAttribute('ng-bind-attr')) factories.push({path:path, fn:this.ng_bind_attr});
+ if (node.getAttribute('ng-hide')) factories.push({path:path, fn:this.ng_hide});
+ if (node.getAttribute('ng-show')) factories.push({path:path, fn:this.ng_show});
+ if (node.getAttribute('ng-class')) factories.push({path:path, fn:this.ng_class});
+ if (node.getAttribute('ng-class-odd')) factories.push({path:path, fn:this.ng_class_odd});
+ if (node.getAttribute('ng-class-even')) factories.push({path:path, fn:this.ng_class_even});
+ if (node.getAttribute('ng-style')) factories.push({path:path, fn:this.ng_style});
+ if (node.getAttribute('ng-watch')) factories.push({path:path, fn:this.ng_watch});
+ var nodeName = node.nodeName;
+ if ((nodeName == 'INPUT' ) ||
+ nodeName == 'TEXTAREA' ||
+ nodeName == 'SELECT' ||
+ nodeName == 'BUTTON') {
+ var self = this;
+ factories.push({path:path, fn:function(node, scope, prefix) {
+ node.name = prefix + node.name.split(":").pop();
+ return self.widgetFactory.createController(jQuery(node), scope);
+ }});
+ }
+ if (nodeName == 'OPTION') {
+ var html = jQuery('<select/>').append(jQuery(node).clone()).html();
+ if (!html.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
+ node.value = node.text;
+ }
+ }
+
+ var children = node.childNodes;
+ for (var k = 0; k < children.length; k++) {
+ this.precompileNode(children[k], path.concat(k), factories);
+ }
+};
+
+nglr.Binder.prototype.ng_eval = function(node) {
+ return new nglr.EvalUpdater(node, node.getAttribute('ng-eval'));
+};
+
+nglr.Binder.prototype.ng_bind = function(node) {
+ return new nglr.BindUpdater(node, "{{" + node.getAttribute('ng-bind') + "}}");
+};
+
+nglr.Binder.prototype.ng_bind_attr = function(node) {
+ return new nglr.BindAttrUpdater(node, nglr.fromJson(node.getAttribute('ng-bind-attr')));
+};
+
+nglr.Binder.prototype.ng_hide = function(node) {
+ return new nglr.HideUpdater(node, node.getAttribute('ng-hide'));
+};
+
+nglr.Binder.prototype.ng_show = function(node) {
+ return new nglr.ShowUpdater(node, node.getAttribute('ng-show'));
+};
+
+nglr.Binder.prototype.ng_class = function(node) {
+ return new nglr.ClassUpdater(node, node.getAttribute('ng-class'));
+};
+
+nglr.Binder.prototype.ng_class_even = function(node) {
+ return new nglr.ClassEvenUpdater(node, node.getAttribute('ng-class-even'));
+};
+
+nglr.Binder.prototype.ng_class_odd = function(node) {
+ return new nglr.ClassOddUpdater(node, node.getAttribute('ng-class-odd'));
+};
+
+nglr.Binder.prototype.ng_style = function(node) {
+ return new nglr.StyleUpdater(node, node.getAttribute('ng-style'));
+};
+
+nglr.Binder.prototype.ng_watch = function(node, scope) {
+ scope.watch(node.getAttribute('ng-watch'));
+};
diff --git a/src/ControlBar.js b/src/ControlBar.js
new file mode 100644
index 00000000..3e1f0b57
--- /dev/null
+++ b/src/ControlBar.js
@@ -0,0 +1,71 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+nglr.ControlBar = function (document, serverUrl) {
+ this.document = document;
+ this.serverUrl = serverUrl;
+ this.window = window;
+ this.callbacks = [];
+};
+
+nglr.ControlBar.prototype.bind = function () {
+};
+
+nglr.ControlBar.HTML =
+ '<div>' +
+ '<div class="ui-widget-overlay"></div>' +
+ '<div id="ng-login" ng-non-bindable="true">' +
+ '<div class="ng-login-container"></div>' +
+ '</div>' +
+ '</div>';
+
+nglr.ControlBar.prototype.login = function (loginSubmitFn) {
+ this.callbacks.push(loginSubmitFn);
+ if (this.callbacks.length == 1) {
+ this.doTemplate("/user_session/new.mini?return_url=" + encodeURIComponent(this.urlWithoutAnchor()));
+ }
+};
+
+nglr.ControlBar.prototype.logout = function (loginSubmitFn) {
+ this.callbacks.push(loginSubmitFn);
+ if (this.callbacks.length == 1) {
+ this.doTemplate("/user_session/do_destroy.mini");
+ }
+};
+
+nglr.ControlBar.prototype.urlWithoutAnchor = function (path) {
+ return this.window.location.href.split("#")[0];
+};
+
+nglr.ControlBar.prototype.doTemplate = function (path) {
+ var self = this;
+ var id = new Date().getTime();
+ var url = this.urlWithoutAnchor();
+ url += "#$iframe_notify=" + id;
+ var iframeHeight = 330;
+ var loginView = jQuery('<div style="overflow:hidden; padding:2px 0 0 0;"><iframe name="'+ url +'" src="'+this.serverUrl + path + '" width="500" height="'+ iframeHeight +'"/></div>');
+ this.document.append(loginView);
+ loginView.dialog({
+ height:iframeHeight + 33, width:500,
+ resizable: false, modal:true,
+ title: 'Authentication: <a href="http://www.getangular.com"><tt>&lt;angular/&gt;</tt></a>'
+ });
+ nglr["_iframe_notify_" + id] = function() {
+ loginView.dialog("destroy");
+ loginView.remove();
+ jQuery.each(self.callbacks, function(i, callback){
+ callback();
+ });
+ self.callbacks = [];
+ };
+};
+
+nglr.ControlBar.FORBIDEN =
+ '<div ng-non-bindable="true" title="Permission Error:">' +
+ 'Sorry, you do not have permission for this!'+
+ '</div>';
+
+nglr.ControlBar.prototype.notAuthorized = function () {
+ if (this.forbidenView) return;
+ this.forbidenView = jQuery(nglr.ControlBar.FORBIDEN);
+ this.forbidenView.dialog({bgiframe:true, height:70, modal:true});
+};
diff --git a/src/DataStore.js b/src/DataStore.js
new file mode 100644
index 00000000..97ab92ff
--- /dev/null
+++ b/src/DataStore.js
@@ -0,0 +1,332 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+nglr.DataStore = function(post, users, anchor) {
+ this.post = post;
+ this.users = users;
+ this._cache = {$collections:[]};
+ this.anchor = anchor;
+ this.bulkRequest = [];
+};
+
+nglr.DataStore.prototype.cache = function(document) {
+ if (document.constructor != nglr.Model) {
+ throw "Parameter must be an instance of Entity! " + nglr.toJson(document);
+ }
+ var key = document.$entity + '/' + document.$id;
+ var cachedDocument = this._cache[key];
+ if (cachedDocument) {
+ nglr.Model.copyDirectFields(document, cachedDocument);
+ } else {
+ this._cache[key] = document;
+ cachedDocument = document;
+ }
+ return cachedDocument;
+};
+
+nglr.DataStore.prototype.load = function(instance, id, callback, failure) {
+ if (id && id !== '*') {
+ var self = this;
+ this._jsonRequest(["GET", instance.$entity + "/" + id], function(response) {
+ instance.$loadFrom(response);
+ instance.$migrate();
+ var clone = instance.$$entity(instance);
+ self.cache(clone);
+ (callback||nglr.noop)(instance);
+ }, failure);
+ }
+ return instance;
+};
+
+nglr.DataStore.prototype.loadMany = function(entity, ids, callback) {
+ var self=this;
+ var list = [];
+ var callbackCount = 0;
+ jQuery.each(ids, function(i, id){
+ list.push(self.load(entity(), id, function(){
+ callbackCount++;
+ if (callbackCount == ids.length) {
+ (callback||nglr.noop)(list);
+ }
+ }));
+ });
+ return list;
+}
+
+nglr.DataStore.prototype.loadOrCreate = function(instance, id, callback) {
+ var self=this;
+ return this.load(instance, id, callback, function(response){
+ if (response.$status_code == 404) {
+ instance.$id = id;
+ (callback||nglr.noop)(instance);
+ } else {
+ throw response;
+ }
+ });
+};
+
+nglr.DataStore.prototype.loadAll = function(entity, callback) {
+ var self = this;
+ var list = [];
+ list.$$accept = function(doc){
+ return doc.$entity == entity.title;
+ };
+ this._cache.$collections.push(list);
+ this._jsonRequest(["GET", entity.title], function(response) {
+ var rows = response;
+ for ( var i = 0; i < rows.length; i++) {
+ var document = entity();
+ document.$loadFrom(rows[i]);
+ list.push(self.cache(document));
+ }
+ (callback||nglr.noop)(list);
+ });
+ return list;
+};
+
+nglr.DataStore.prototype.save = function(document, callback) {
+ var self = this;
+ var data = {};
+ document.$saveTo(data);
+ this._jsonRequest(["POST", "", data], function(response) {
+ document.$loadFrom(response);
+ var cachedDoc = self.cache(document);
+ _.each(self._cache.$collections, function(collection){
+ if (collection.$$accept(document)) {
+ angular.Array.includeIf(collection, cachedDoc, true);
+ }
+ });
+ if (document.$$anchor) {
+ self.anchor[document.$$anchor] = document.$id;
+ }
+ if (callback)
+ callback(document);
+ });
+};
+
+nglr.DataStore.prototype.remove = function(document, callback) {
+ var self = this;
+ var data = {};
+ document.$saveTo(data);
+ this._jsonRequest(["DELETE", "", data], function(response) {
+ delete self._cache[document.$entity + '/' + document.$id];
+ _.each(self._cache.$collections, function(collection){
+ for ( var i = 0; i < collection.length; i++) {
+ var item = collection[i];
+ if (item.$id == document.$id) {
+ collection.splice(i, 1);
+ }
+ }
+ });
+ (callback||nglr.noop)(response);
+ });
+};
+
+nglr.DataStore.prototype._jsonRequest = function(request, callback, failure) {
+ request.$$callback = callback;
+ request.$$failure = failure||function(response){
+ throw response;
+ };
+ this.bulkRequest.push(request);
+};
+
+nglr.DataStore.prototype.flush = function() {
+ if (this.bulkRequest.length === 0) return;
+ var self = this;
+ var bulkRequest = this.bulkRequest;
+ this.bulkRequest = [];
+ console.log('REQUEST:', bulkRequest);
+ function callback(code, bulkResponse){
+ console.log('RESPONSE[' + code + ']: ', bulkResponse);
+ if(bulkResponse.$status_code == 401) {
+ self.users.login(function(){
+ self.post(bulkRequest, callback);
+ });
+ } else if(bulkResponse.$status_code) {
+ nglr.alert(nglr.toJson(bulkResponse));
+ } else {
+ for ( var i = 0; i < bulkResponse.length; i++) {
+ var response = bulkResponse[i];
+ var request = bulkRequest[i];
+ var code = response.$status_code;
+ if(code) {
+ if(code == 403) {
+ self.users.notAuthorized();
+ } else {
+ request.$$failure(response);
+ }
+ } else {
+ request.$$callback(response);
+ }
+ }
+ }
+ }
+ this.post(bulkRequest, callback);
+};
+
+nglr.DataStore.prototype.saveScope = function(scope, callback) {
+ var saveCounter = 1;
+ function onSaveDone() {
+ saveCounter--;
+ if (saveCounter === 0 && callback)
+ callback();
+ }
+ for(var key in scope) {
+ var item = scope[key];
+ if (item && item.$save == nglr.Model.prototype.$save) {
+ saveCounter++;
+ item.$save(onSaveDone);
+ }
+ }
+ onSaveDone();
+};
+
+nglr.DataStore.prototype.query = function(type, query, arg, callback){
+ var self = this;
+ var queryList = [];
+ queryList.$$accept = function(doc){
+ return false;
+ };
+ this._cache.$collections.push(queryList);
+ var request = type.title + '/' + query + '=' + arg;
+ this._jsonRequest(["GET", request], function(response){
+ var list = response;
+ for(var i = 0; i < list.length; i++) {
+ var document = new type().$loadFrom(list[i]);
+ queryList.push(self.cache(document));
+ }
+ if (callback)
+ callback(queryList);
+ });
+ return queryList;
+};
+
+nglr.DataStore.prototype.entities = function(callback) {
+ var entities = [];
+ var self = this;
+ this._jsonRequest(["GET", "$entities"], function(response) {
+ for (var entityName in response) {
+ entities.push(self.entity(entityName));
+ }
+ entities.sort(function(a,b){return a.title > b.title ? 1 : -1;});
+ if (callback) callback(entities);
+ });
+ return entities;
+};
+
+nglr.DataStore.prototype.documentCountsByUser = function(){
+ var counts = {};
+ var self = this;
+ self.post([["GET", "$users"]], function(code, response){
+ jQuery.each(response[0], function(key, value){
+ counts[key] = value;
+ });
+ });
+ return counts;
+};
+
+nglr.DataStore.prototype.userDocumentIdsByEntity = function(user){
+ var ids = {};
+ var self = this;
+ self.post([["GET", "$users/" + user]], function(code, response){
+ jQuery.each(response[0], function(key, value){
+ ids[key] = value;
+ });
+ });
+ return ids;
+};
+
+nglr.DataStore.NullEntity = function(){};
+nglr.DataStore.NullEntity.all = function(){return [];};
+nglr.DataStore.NullEntity.query = function(){return [];};
+nglr.DataStore.NullEntity.load = function(){return {};};
+nglr.DataStore.NullEntity.title = undefined;
+
+nglr.DataStore.prototype.entity = function(name, defaults){
+ if (!name) {
+ return nglr.DataStore.NullEntity;
+ }
+ var self = this;
+ var entity = function(initialState){
+ return new nglr.Model(entity, initialState);
+ };
+ // entity.name does not work as name seems to be reserved for functions
+ entity.title = name;
+ entity.$$factory = true;
+ entity.datastore = this;
+ entity.defaults = defaults || {};
+ entity.load = function(id, callback){
+ return self.load(entity(), id, callback);
+ };
+ entity.loadMany = function(ids, callback){
+ return self.loadMany(entity, ids, callback);
+ };
+ entity.loadOrCreate = function(id, callback){
+ return self.loadOrCreate(entity(), id, callback);
+ };
+ entity.all = function(callback){
+ return self.loadAll(entity, callback);
+ };
+ entity.query = function(query, queryArgs, callback){
+ return self.query(entity, query, queryArgs, callback);
+ };
+ entity.properties = function(callback) {
+ self._jsonRequest(["GET", name + "/$properties"], callback);
+ };
+ return entity;
+};
+
+nglr.DataStore.prototype.join = function(join){
+ var fn = function(){
+ throw "Joined entities can not be instantiated into a document.";
+ };
+ function base(name){return name ? name.substring(0, name.indexOf('.')) : undefined;}
+ function next(name){return name.substring(name.indexOf('.') + 1);}
+ var joinOrder = _(join).chain().
+ map(function($, name){
+ return name;}).
+ sortBy(function(name){
+ var path = [];
+ do {
+ if (_(path).include(name)) throw "Infinite loop in join: " + path.join(" -> ");
+ path.push(name);
+ if (!join[name]) throw _("Named entity '<%=name%>' is undefined.").template({name:name});
+ name = base(join[name].on);
+ } while(name);
+ return path.length;
+ }).
+ value();
+ if (_(joinOrder).select(function($){return join[$].on;}).length != joinOrder.length - 1)
+ throw "Exactly one entity needs to be primary.";
+ fn.query = function(exp, value) {
+ var joinedResult = [];
+ var baseName = base(exp);
+ if (baseName != joinOrder[0]) throw _("Named entity '<%=name%>' is not a primary entity.").template({name:baseName});
+ var Entity = join[baseName].join;
+ var joinIndex = 1;
+ Entity.query(next(exp), value, function(result){
+ var nextJoinName = joinOrder[joinIndex++];
+ var nextJoin = join[nextJoinName];
+ var nextJoinOn = nextJoin.on;
+ var joinIds = {};
+ _(result).each(function(doc){
+ var row = {};
+ joinedResult.push(row);
+ row[baseName] = doc;
+ var id = nglr.Scope.getter(row, nextJoinOn);
+ joinIds[id] = id;
+ });
+ nextJoin.join.loadMany(_.toArray(joinIds), function(result){
+ var byId = {};
+ _(result).each(function(doc){
+ byId[doc.$id] = doc;
+ });
+ _(joinedResult).each(function(row){
+ var id = nglr.Scope.getter(row, nextJoinOn);
+ row[nextJoinName] = byId[id];
+ });
+ });
+ });
+ return joinedResult;
+ };
+ return fn;
+};
diff --git a/src/Filters.js b/src/Filters.js
new file mode 100644
index 00000000..f75f3603
--- /dev/null
+++ b/src/Filters.js
@@ -0,0 +1,290 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+angular.filter.Meta = function(obj){
+ if (obj) {
+ for ( var key in obj) {
+ this[key] = obj[key];
+ }
+ }
+};
+angular.filter.Meta.get = function(obj, attr){
+ attr = attr || 'text';
+ switch(typeof obj) {
+ case "string":
+ return attr == "text" ? obj : undefined;
+ case "object":
+ if (obj && typeof obj[attr] !== "undefined") {
+ return obj[attr];
+ }
+ return undefined;
+ default:
+ return obj;
+ }
+};
+
+angular.filter.currency = function(amount){
+ jQuery(this.element).toggleClass('ng-format-negative', amount < 0);
+ return '$' + angular.filter.number.apply(this, [amount, 2]);
+};
+
+angular.filter.number = function(amount, fractionSize){
+ if (isNaN(amount) || !isFinite(amount)) {
+ return '';
+ }
+ fractionSize = typeof fractionSize == 'undefined' ? 2 : fractionSize;
+ var isNegative = amount < 0;
+ amount = Math.abs(amount);
+ var pow = Math.pow(10, fractionSize);
+ var text = "" + Math.round(amount * pow);
+ var whole = text.substring(0, text.length - fractionSize);
+ whole = whole || '0';
+ var frc = text.substring(text.length - fractionSize);
+ text = isNegative ? '-' : '';
+ for (var i = 0; i < whole.length; i++) {
+ if ((whole.length - i)%3 === 0 && i !== 0) {
+ text += ',';
+ }
+ text += whole.charAt(i);
+ }
+ if (fractionSize > 0) {
+ for (var j = frc.length; j < fractionSize; j++) {
+ frc += '0';
+ }
+ text += '.' + frc.substring(0, fractionSize);
+ }
+ return text;
+};
+
+angular.filter.date = function(amount) {
+};
+
+angular.filter.json = function(object) {
+ jQuery(this.element).addClass("ng-monospace");
+ return nglr.toJson(object, true);
+};
+
+angular.filter.trackPackage = function(trackingNo, noMatch) {
+ trackingNo = nglr.trim(trackingNo);
+ var tNo = trackingNo.replace(/ /g, '');
+ var MATCHERS = angular.filter.trackPackage.MATCHERS;
+ for ( var i = 0; i < MATCHERS.length; i++) {
+ var carrier = MATCHERS[i];
+ for ( var j = 0; j < carrier.regexp.length; j++) {
+ var regexp = carrier.regexp[j];
+ if (regexp.test(tNo)) {
+ var text = carrier.name + ": " + trackingNo;
+ var url = carrier.url + trackingNo;
+ return new angular.filter.Meta({
+ text:text,
+ url:url,
+ html: '<a href="' + nglr.escapeAttr(url) + '">' + text + '</a>',
+ trackingNo:trackingNo});
+ }
+ }
+ }
+ if (trackingNo)
+ return noMatch ||
+ new angular.filter.Meta({text:trackingNo + " is not recognized"});
+ else
+ return null;
+};
+
+angular.filter.trackPackage.MATCHERS = [
+ { name: "UPS",
+ url: "http://wwwapps.ups.com/WebTracking/processInputRequest?sort_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=en_US&track.x=0&track.y=0&InquiryNumber1=",
+ regexp: [
+ /^1Z[0-9A-Z]{16}$/i]},
+ { name: "FedEx",
+ url: "http://www.fedex.com/Tracking?tracknumbers=",
+ regexp: [
+ /^96\d{10}?$/i,
+ /^96\d{17}?$/i,
+ /^96\d{20}?$/i,
+ /^\d{15}$/i,
+ /^\d{12}$/i]},
+ { name: "USPS",
+ url: "http://trkcnfrm1.smi.usps.com/PTSInternetWeb/InterLabelInquiry.do?origTrackNum=",
+ regexp: [
+ /^(91\d{20})$/i,
+ /^(91\d{18})$/i]}];
+
+angular.filter.link = function(obj, title) {
+ var text = title || angular.filter.Meta.get(obj);
+ var url = angular.filter.Meta.get(obj, "url") || angular.filter.Meta.get(obj);
+ if (url) {
+ if (angular.validator.email(url) === null) {
+ url = "mailto:" + url;
+ }
+ var html = '<a href="' + nglr.escapeHtml(url) + '">' + text + '</a>';
+ return new angular.filter.Meta({text:text, url:url, html:html});
+ }
+ return obj;
+};
+
+
+angular.filter.bytes = function(size) {
+ if(size === null) return "";
+
+ var suffix = 0;
+ while (size > 1000) {
+ size = size / 1024;
+ suffix++;
+ }
+ var txt = "" + size;
+ var dot = txt.indexOf('.');
+ if (dot > -1 && dot + 2 < txt.length) {
+ txt = txt.substring(0, dot + 2);
+ }
+ return txt + " " + angular.filter.bytes.SUFFIX[suffix];
+};
+angular.filter.bytes.SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+
+angular.filter.image = function(obj, width, height) {
+ if (obj && obj.url) {
+ var style = "";
+ if (width) {
+ style = ' style="max-width: ' + width +
+ 'px; max-height: ' + (height || width) + 'px;"';
+ }
+ return new angular.filter.Meta({url:obj.url, text:obj.url,
+ html:'<img src="'+obj.url+'"' + style + '/>'});
+ }
+ return null;
+};
+
+angular.filter.lowercase = function (obj) {
+ var text = angular.filter.Meta.get(obj);
+ return text ? ("" + text).toLowerCase() : text;
+};
+
+angular.filter.uppercase = function (obj) {
+ var text = angular.filter.Meta.get(obj);
+ return text ? ("" + text).toUpperCase() : text;
+};
+
+angular.filter.linecount = function (obj) {
+ var text = angular.filter.Meta.get(obj);
+ if (text==='' || !text) return 1;
+ return text.split(/\n|\f/).length;
+};
+
+angular.filter['if'] = function (result, expression) {
+ return expression ? result : undefined;
+};
+
+angular.filter.unless = function (result, expression) {
+ return expression ? undefined : result;
+};
+
+angular.filter.googleChartApi = function(type, data, width, height) {
+ data = data || {};
+ var api = angular.filter.googleChartApi;
+ var chart = {
+ cht:type,
+ chco:api.collect(data, 'color'),
+ chtt:api.title(data),
+ chdl:api.collect(data, 'label'),
+ chd:api.values(data),
+ chf:'bg,s,FFFFFF00'
+ };
+ if (_.isArray(data.xLabels)) {
+ chart.chxt='x';
+ chart.chxl='0:|' + data.xLabels.join('|');
+ }
+ return angular.filter.googleChartApi.encode(chart, width, height);
+};
+
+angular.filter.googleChartApi.values = function(data){
+ var seriesValues = [];
+ _.each(data.series||[], function(serie){
+ var values = [];
+ _.each(serie.values||[], function(value){
+ values.push(value);
+ });
+ seriesValues.push(values.join(','));
+ });
+ var values = seriesValues.join('|');
+ return values === "" ? null : "t:" + values;
+};
+
+angular.filter.googleChartApi.title = function(data){
+ var titles = [];
+ var title = data.title || [];
+ _.each(_.isArray(title)?title:[title], function(text){
+ titles.push(encodeURIComponent(text));
+ });
+ return titles.join('|');
+};
+
+angular.filter.googleChartApi.collect = function(data, key){
+ var outterValues = [];
+ var count = 0;
+ _.each(data.series||[], function(serie){
+ var innerValues = [];
+ var value = serie[key] || [];
+ _.each(_.isArray(value)?value:[value], function(color){
+ innerValues.push(encodeURIComponent(color));
+ count++;
+ });
+ outterValues.push(innerValues.join('|'));
+ });
+ return count?outterValues.join(','):null;
+};
+
+angular.filter.googleChartApi.encode= function(params, width, height) {
+ width = width || 200;
+ height = height || width;
+ var url = "http://chart.apis.google.com/chart?";
+ var urlParam = [];
+ params.chs = width + "x" + height;
+ for ( var key in params) {
+ var value = params[key];
+ if (value) {
+ urlParam.push(key + "=" + value);
+ }
+ }
+ urlParam.sort();
+ url += urlParam.join("&");
+ return new angular.filter.Meta({url:url, text:value,
+ html:'<img width="' + width + '" height="' + height + '" src="'+url+'"/>'});
+};
+
+angular.filter.qrcode = function(value, width, height) {
+ return angular.filter.googleChartApi.encode({cht:'qr', chl:encodeURIComponent(value)}, width, height);
+};
+angular.filter.chart = {
+ pie:function(data, width, height) {
+ return angular.filter.googleChartApi('p', data, width, height);
+ },
+ pie3d:function(data, width, height) {
+ return angular.filter.googleChartApi('p3', data, width, height);
+ },
+ pieConcentric:function(data, width, height) {
+ return angular.filter.googleChartApi('pc', data, width, height);
+ },
+ barHorizontalStacked:function(data, width, height) {
+ return angular.filter.googleChartApi('bhs', data, width, height);
+ },
+ barHorizontalGrouped:function(data, width, height) {
+ return angular.filter.googleChartApi('bhg', data, width, height);
+ },
+ barVerticalStacked:function(data, width, height) {
+ return angular.filter.googleChartApi('bvs', data, width, height);
+ },
+ barVerticalGrouped:function(data, width, height) {
+ return angular.filter.googleChartApi('bvg', data, width, height);
+ },
+ line:function(data, width, height) {
+ return angular.filter.googleChartApi('lc', data, width, height);
+ },
+ sparkline:function(data, width, height) {
+ return angular.filter.googleChartApi('ls', data, width, height);
+ },
+ scatter:function(data, width, height) {
+ return angular.filter.googleChartApi('s', data, width, height);
+ }
+};
+
+angular.filter.html = function(html){
+ return new angular.filter.Meta({html:html});
+};
diff --git a/src/JSON.js b/src/JSON.js
new file mode 100644
index 00000000..2b6393bf
--- /dev/null
+++ b/src/JSON.js
@@ -0,0 +1,92 @@
+nglr.array = [].constructor;
+
+nglr.toJson = function(obj, pretty){
+ var buf = [];
+ nglr.toJsonArray(buf, obj, pretty ? "\n " : null);
+ return buf.join('');
+};
+
+nglr.toPrettyJson = function(obj) {
+ return nglr.toJson(obj, true);
+};
+
+nglr.fromJson = function(json) {
+ try {
+ var parser = new nglr.Parser(json, true);
+ var expression = parser.primary();
+ parser.assertAllConsumed();
+ return expression();
+ } catch (e) {
+ console.error("fromJson error: ", json, e);
+ throw e;
+ }
+};
+
+
+nglr.toJsonArray = function(buf, obj, pretty){
+ var type = typeof obj;
+ if (obj === null) {
+ buf.push("null");
+ } else if (type === 'function') {
+ return;
+ } else if (type === 'boolean') {
+ buf.push('' + obj);
+ } else if (type === 'number') {
+ if (isNaN(obj)) {
+ buf.push('null');
+ } else {
+ buf.push('' + obj);
+ }
+ } else if (type === 'string') {
+ return buf.push(angular.String.quoteUnicode(obj));
+ } else if (type === 'object') {
+ if (obj instanceof Array) {
+ buf.push("[");
+ var len = obj.length;
+ var sep = false;
+ for(var i=0; i<len; i++) {
+ var item = obj[i];
+ if (sep) buf.push(",");
+ if (typeof item == 'function' || typeof item == 'undefined') {
+ buf.push("null");
+ } else {
+ nglr.toJsonArray(buf, item, pretty);
+ }
+ sep = true;
+ }
+ buf.push("]");
+ } else if (obj instanceof Date) {
+ buf.push(angular.String.quoteUnicode(angular.Date.toString(obj)));
+ } else {
+ buf.push("{");
+ if (pretty) buf.push(pretty);
+ var comma = false;
+ var childPretty = pretty ? pretty + " " : false;
+ var keys = [];
+ for(var k in obj) {
+ if (k.indexOf('$$') === 0)
+ continue;
+ keys.push(k);
+ }
+ keys.sort();
+ for ( var keyIndex = 0; keyIndex < keys.length; keyIndex++) {
+ var key = keys[keyIndex];
+ try {
+ var value = obj[key];
+ if (typeof value != 'function') {
+ if (comma) {
+ buf.push(",");
+ if (pretty) buf.push(pretty);
+ }
+ buf.push(angular.String.quote(key));
+ buf.push(":");
+ nglr.toJsonArray(buf, value, childPretty);
+ comma = true;
+ }
+ } catch (e) {
+ }
+ }
+ buf.push("}");
+ }
+ }
+};
diff --git a/src/Loader.js b/src/Loader.js
new file mode 100644
index 00000000..fdcfa3cc
--- /dev/null
+++ b/src/Loader.js
@@ -0,0 +1,389 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+// IE compatibility
+
+if (typeof document.getAttribute == 'undefined')
+ document.getAttribute = function() {
+ };
+if (typeof Node == 'undefined') {
+ 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
+ };
+}
+
+if (_.isUndefined(window.nglr)) nglr = {};
+if (_.isUndefined(window.angular)) angular = {};
+if (_.isUndefined(angular.validator)) angular.validator = {};
+if (_.isUndefined(angular.filter)) angular.filter = {};
+if (_.isUndefined(window.console))
+ window.console = {
+ log:function() {},
+ error:function() {}
+ };
+if (_.isUndefined(nglr.alert)) {
+ nglr.alert = function(){console.log(arguments); window.alert.apply(window, arguments); };
+}
+
+nglr.consoleLog = function(level, objs) {
+ var log = document.createElement("div");
+ log.className = level;
+ var msg = "";
+ var sep = "";
+ for ( var i = 0; i < objs.length; i++) {
+ var obj = objs[i];
+ msg += sep + (typeof obj == 'string' ? obj : nglr.toJson(obj));
+ sep = " ";
+ }
+ log.appendChild(document.createTextNode(msg));
+ nglr.consoleNode.appendChild(log);
+};
+
+nglr.isNode = function(inp) {
+ return inp &&
+ inp.tagName &&
+ inp.nodeName &&
+ inp.ownerDocument &&
+ inp.removeAttribute;
+};
+
+nglr.isLeafNode = function(node) {
+ switch (node.nodeName) {
+ case "OPTION":
+ case "PRE":
+ case "TITLE":
+ return true;
+ default:
+ return false;
+ }
+};
+
+nglr.noop = function() {
+};
+nglr.setHtml = function(node, html) {
+ if (nglr.isLeafNode(node)) {
+ if (nglr.msie) {
+ node.innerText = html;
+ } else {
+ node.textContent = html;
+ }
+ } else {
+ node.innerHTML = html;
+ }
+};
+
+nglr.escapeHtml = function(html) {
+ if (!html || !html.replace)
+ return html;
+ return html.
+ replace(/&/g, '&amp;').
+ replace(/</g, '&lt;').
+ replace(/>/g, '&gt;');
+};
+
+nglr.escapeAttr = function(html) {
+ if (!html || !html.replace)
+ return html;
+ return html.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g,
+ '&quot;');
+};
+
+nglr.bind = function(_this, _function) {
+ if (!_this)
+ throw "Missing this";
+ if (!_.isFunction(_function))
+ throw "Missing function";
+ return function() {
+ return _function.apply(_this, arguments);
+ };
+};
+
+nglr.shiftBind = function(_this, _function) {
+ return function() {
+ var args = [ this ];
+ for ( var i = 0; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ return _function.apply(_this, args);
+ };
+};
+
+nglr.outerHTML = function(node) {
+ var temp = document.createElement('div');
+ temp.appendChild(node);
+ var outerHTML = temp.innerHTML;
+ temp.removeChild(node);
+ return outerHTML;
+};
+
+nglr.trim = function(str) {
+ return str.replace(/^ */, '').replace(/ *$/, '');
+};
+
+nglr.toBoolean = function(value) {
+ var v = ("" + value).toLowerCase();
+ if (v == 'f' || v == '0' || v == 'false' || v == 'no')
+ value = false;
+ return !!value;
+};
+
+nglr.merge = function(src, dst) {
+ for ( var key in src) {
+ var value = dst[key];
+ var type = typeof value;
+ if (type == 'undefined') {
+ dst[key] = nglr.fromJson(nglr.toJson(src[key]));
+ } else if (type == 'object' && value.constructor != nglr.array &&
+ key.substring(0, 1) != "$") {
+ nglr.merge(src[key], value);
+ }
+ }
+};
+
+// ////////////////////////////
+// Loader
+// ////////////////////////////
+
+nglr.Loader = function(document, head, config) {
+ this.document = jQuery(document);
+ this.head = jQuery(head);
+ this.config = config;
+ this.location = window.location;
+};
+
+nglr.Loader.prototype.load = function() {
+ this.configureLogging();
+ this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css');
+ this.loadCss('/stylesheets/nglr.css');
+ console.log("Server: " + this.config.server);
+ jQuery.noConflict();
+ nglr.msie = jQuery.browser.msie;
+ this.configureJQueryPlugins();
+ this.computeConfiguration();
+ this.bindHtml();
+};
+
+nglr.Loader.prototype.configureJQueryPlugins = function() {
+ console.log('Loader.configureJQueryPlugins()');
+ jQuery.fn.removeNode = function() {
+ var node = this.get(0);
+ node.parentNode.removeChild(node);
+ };
+ jQuery.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;
+ };
+ jQuery.fn.controller = function() {
+ return this.data('controller') || nglr.NullController.instance;
+ };
+};
+
+nglr.Loader.prototype.uid = function() {
+ return "" + new Date().getTime();
+};
+
+nglr.Loader.prototype.computeConfiguration = function() {
+ var config = this.config;
+ if (!config.database) {
+ var match = config.server.match(/https?:\/\/([\w]*)/)
+ config.database = match ? match[1] : "$MEMORY";
+ }
+};
+
+nglr.Loader.prototype.bindHtml = function() {
+ console.log('Loader.bindHtml()');
+ var watcher = new nglr.UrlWatcher(this.location);
+ var document = this.document;
+ var widgetFactory = new nglr.WidgetFactory(this.config.server, this.config.database);
+ var binder = new nglr.Binder(document[0], widgetFactory, watcher, this.config);
+ widgetFactory.onChangeListener = nglr.shiftBind(binder, binder.updateModel);
+ var controlBar = new nglr.ControlBar(document.find('body'), this.config.server);
+ var onUpdate = function(){binder.updateView();};
+ var server = this.config.database=="$MEMORY" ?
+ new nglr.FrameServer(this.window) :
+ new nglr.Server(this.config.server, jQuery.getScript);
+ server = new nglr.VisualServer(server, new nglr.Status(jQuery(document.body)), onUpdate);
+ var users = new nglr.Users(server, controlBar);
+ var databasePath = '/data/' + this.config.database;
+ var post = function(request, callback){
+ server.request("POST", databasePath, request, callback);
+ };
+ var datastore = new nglr.DataStore(post, users, binder.anchor);
+ binder.updateListeners.push(function(){datastore.flush();});
+ var scope = new nglr.Scope( {
+ $anchor : binder.anchor,
+ $binder : binder,
+ $config : this.config,
+ $console : window.console,
+ $datastore : datastore,
+ $save : function(callback) {
+ datastore.saveScope(scope.state, callback, binder.anchor);
+ },
+ $window : window,
+ $uid : this.uid,
+ $users : users
+ }, "ROOT");
+
+ jQuery.each(["get", "set", "eval", "addWatchListener", "updateView"],
+ function(i, method){
+ angular[method] = nglr.bind(scope, scope[method]);
+ });
+
+ document.data('scope', scope);
+ console.log('$binder.entity()');
+ binder.entity(scope);
+
+ console.log('$binder.compile()');
+ binder.compile();
+
+ console.log('ControlBar.bind()');
+ controlBar.bind();
+
+ console.log('$users.fetchCurrentUser()');
+ function fetchCurrentUser() {
+ users.fetchCurrentUser(function(u) {
+ if (!u && document.find("[ng-auth=eager]").length) {
+ users.login();
+ }
+ });
+ }
+ fetchCurrentUser();
+
+ console.log('PopUp.bind()');
+ new nglr.PopUp(document).bind();
+
+ console.log('$binder.parseAnchor()');
+ binder.parseAnchor();
+
+ console.log('$binder.executeInit()');
+ binder.executeInit();
+
+ console.log('$binder.updateView()');
+ binder.updateView();
+
+ watcher.listener = nglr.bind(binder, binder.onUrlChange, watcher);
+ watcher.onUpdate = function(){nglr.alert("update");};
+ watcher.watch();
+ document.find("body").show();
+ console.log('ready()');
+
+};
+
+nglr.Loader.prototype.visualPost = function(delegate) {
+ var status = new nglr.Status(jQuery(document.body));
+ return function(request, delegateCallback) {
+ status.beginRequest(request);
+ var callback = function() {
+ status.endRequest();
+ try {
+ delegateCallback.apply(this, arguments);
+ } catch (e) {
+ nglr.alert(nglr.toJson(e));
+ }
+ };
+ delegate(request, callback);
+ };
+};
+
+nglr.Loader.prototype.configureLogging = function() {
+ var url = window.location.href + '#';
+ url = url.split('#')[1];
+ var config = {
+ debug : null
+ };
+ var configs = url.split('&');
+ for ( var i = 0; i < configs.length; i++) {
+ var part = (configs[i] + '=').split('=');
+ config[part[0]] = part[1];
+ }
+ if (config.debug == 'console') {
+ nglr.consoleNode = document.createElement("div");
+ nglr.consoleNode.id = 'ng-console';
+ document.getElementsByTagName('body')[0].appendChild(nglr.consoleNode);
+ console.log = function() {
+ nglr.consoleLog('ng-console-info', arguments);
+ };
+ console.error = function() {
+ nglr.consoleLog('ng-console-error', arguments);
+ };
+ }
+};
+
+nglr.Loader.prototype.loadCss = function(css) {
+ var cssTag = document.createElement('link');
+ cssTag.rel = "stylesheet";
+ cssTag.type = "text/css";
+ if (!css.match(/^http:/))
+ css = this.config.server + css;
+ cssTag.href = css;
+ this.head[0].appendChild(cssTag);
+};
+
+nglr.UrlWatcher = function(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;
+};
+
+nglr.UrlWatcher.prototype.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 = nglr[id];
+ delete nglr[id];
+ try {
+ (notifyFn||nglr.noop)();
+ } catch (e) {
+ nglr.alert(e);
+ }
+ } else {
+ self.listener(self.location.href);
+ self.expectedUrl = self.location.href;
+ }
+ }
+ self.setTimeout(pull, self.delay);
+ };
+ pull();
+};
+
+nglr.UrlWatcher.prototype.setUrl = function(url) {
+ var existingURL = window.location.href;
+ if (!existingURL.match(/#/))
+ existingURL += '#';
+ if (existingURL != url)
+ window.location.href = url;
+ self.existingURL = url;
+};
+
+nglr.UrlWatcher.prototype.getUrl = function() {
+ return window.location.href;
+};
diff --git a/src/Model.js b/src/Model.js
new file mode 100644
index 00000000..5e48251f
--- /dev/null
+++ b/src/Model.js
@@ -0,0 +1,65 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+// Single $ is special and does not get searched
+// Double $$ is special an is client only (does not get sent to server)
+
+nglr.Model = function(entity, initial) {
+ this.$$entity = entity;
+ this.$loadFrom(initial||{});
+ this.$entity = entity.title;
+ this.$migrate();
+};
+
+nglr.Model.copyDirectFields = function(src, dst) {
+ if (src === dst || !src || !dst) return;
+ var isDataField = function(src, dst, field) {
+ return (field.substring(0,2) !== '$$') &&
+ (typeof src[field] !== 'function') &&
+ (typeof dst[field] !== 'function');
+ };
+ for (var field in dst) {
+ if (isDataField(src, dst, field))
+ delete dst[field];
+ }
+ for (field in src) {
+ if (isDataField(src, dst, field))
+ dst[field] = src[field];
+ }
+};
+
+nglr.Model.prototype.$migrate = function() {
+ nglr.merge(this.$$entity.defaults, this);
+ return this;
+};
+
+nglr.Model.prototype.$merge = function(other) {
+ nglr.merge(other, this);
+ return this;
+};
+
+nglr.Model.prototype.$save = function(callback) {
+ this.$$entity.datastore.save(this, callback === true ? undefined : callback);
+ if (callback === true) this.$$entity.datastore.flush();
+ return this;
+};
+
+nglr.Model.prototype.$delete = function(callback) {
+ this.$$entity.datastore.remove(this, callback === true ? undefined : callback);
+ if (callback === true) this.$$entity.datastore.flush();
+ return this;
+};
+
+nglr.Model.prototype.$loadById = function(id, callback) {
+ this.$$entity.datastore.load(this, id, callback);
+ return this;
+};
+
+nglr.Model.prototype.$loadFrom = function(other) {
+ nglr.Model.copyDirectFields(other, this);
+ return this;
+};
+
+nglr.Model.prototype.$saveTo = function(other) {
+ nglr.Model.copyDirectFields(this, other);
+ return this;
+};
diff --git a/src/Parser.js b/src/Parser.js
new file mode 100644
index 00000000..3d72bebf
--- /dev/null
+++ b/src/Parser.js
@@ -0,0 +1,741 @@
+nglr.Lexer = function(text, parsStrings){
+ this.text = text;
+ // UTC dates have 20 characters, we send them through parser
+ this.dateParseLength = parsStrings ? 20 : -1;
+ this.tokens = [];
+ this.index = 0;
+};
+
+nglr.Lexer.OPERATORS = {
+ 'null':function(self){return null;},
+ 'true':function(self){return true;},
+ 'false':function(self){return false;},
+ '+':function(self, a,b){return (a||0)+(b||0);},
+ '-':function(self, a,b){return (a||0)-(b||0);},
+ '*':function(self, a,b){return a*b;},
+ '/':function(self, a,b){return a/b;},
+ '%':function(self, a,b){return a%b;},
+ '^':function(self, a,b){return a^b;},
+ '=':function(self, a,b){return self.scope.set(a, b);},
+ '==':function(self, a,b){return a==b;},
+ '!=':function(self, a,b){return a!=b;},
+ '<':function(self, a,b){return a<b;},
+ '>':function(self, a,b){return a>b;},
+ '<=':function(self, a,b){return a<=b;},
+ '>=':function(self, a,b){return a>=b;},
+ '&&':function(self, a,b){return a&&b;},
+ '||':function(self, a,b){return a||b;},
+ '&':function(self, a,b){return a&b;},
+// '|':function(self, a,b){return a|b;},
+ '|':function(self, a,b){return b(self, a);},
+ '!':function(self, a){return !a;}
+};
+
+nglr.Lexer.prototype.peek = function() {
+ if (this.index + 1 < this.text.length) {
+ return this.text.charAt(this.index + 1);
+ } else {
+ return false;
+ }
+};
+
+nglr.Lexer.prototype.parse = function() {
+ var tokens = this.tokens;
+ var OPERATORS = nglr.Lexer.OPERATORS;
+ var canStartRegExp = true;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (ch == '"' || ch == "'") {
+ this.readString(ch);
+ canStartRegExp = true;
+ } else if (ch == '(' || ch == '[') {
+ tokens.push({index:this.index, text:ch});
+ this.index++;
+ } else if (ch == '{' ) {
+ var peekCh = this.peek();
+ if (peekCh == ':' || peekCh == '(') {
+ tokens.push({index:this.index, text:ch + peekCh});
+ this.index++;
+ } else {
+ tokens.push({index:this.index, text:ch});
+ }
+ this.index++;
+ canStartRegExp = true;
+ } else if (ch == ')' || ch == ']' || ch == '}' ) {
+ tokens.push({index:this.index, text:ch});
+ this.index++;
+ canStartRegExp = false;
+ } else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') {
+ tokens.push({index:this.index, text:ch});
+ this.index++;
+ canStartRegExp = true;
+ } else if ( canStartRegExp && ch == '/' ) {
+ this.readRegexp();
+ canStartRegExp = false;
+ } else if ( this.isNumber(ch) ) {
+ this.readNumber();
+ canStartRegExp = false;
+ } else if (this.isIdent(ch)) {
+ this.readIdent();
+ canStartRegExp = false;
+ } else if (this.isWhitespace(ch)) {
+ this.index++;
+ } else {
+ var ch2 = ch + this.peek();
+ var fn = OPERATORS[ch];
+ var fn2 = OPERATORS[ch2];
+ if (fn2) {
+ tokens.push({index:this.index, text:ch2, fn:fn2});
+ this.index += 2;
+ } else if (fn) {
+ tokens.push({index:this.index, text:ch, fn:fn});
+ this.index += 1;
+ } else {
+ throw "Lexer Error: Unexpected next character [" +
+ this.text.substring(this.index) +
+ "] in expression '" + this.text +
+ "' at column '" + (this.index+1) + "'.";
+ }
+ canStartRegExp = true;
+ }
+ }
+ return tokens;
+};
+
+nglr.Lexer.prototype.isNumber = function(ch) {
+ return '0' <= ch && ch <= '9';
+};
+
+nglr.Lexer.prototype.isWhitespace = function(ch) {
+ return ch == ' ' || ch == '\r' || ch == '\t' ||
+ ch == '\n' || ch == '\v';
+};
+
+nglr.Lexer.prototype.isIdent = function(ch) {
+ return 'a' <= ch && ch <= 'z' ||
+ 'A' <= ch && ch <= 'Z' ||
+ '_' == ch || ch == '$';
+};
+
+nglr.Lexer.prototype.readNumber = function() {
+ var number = "";
+ var start = this.index;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (ch == '.' || this.isNumber(ch)) {
+ number += ch;
+ } else {
+ break;
+ }
+ this.index++;
+ }
+ number = 1 * number;
+ this.tokens.push({index:start, text:number,
+ fn:function(){return number;}});
+};
+
+nglr.Lexer.prototype.readIdent = function() {
+ var ident = "";
+ var start = this.index;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (ch == '.' || this.isIdent(ch) || this.isNumber(ch)) {
+ ident += ch;
+ } else {
+ break;
+ }
+ this.index++;
+ }
+ var fn = nglr.Lexer.OPERATORS[ident];
+ if (!fn) {
+ fn = function(self){
+ return self.scope.get(ident);
+ };
+ fn.isAssignable = ident;
+ }
+ this.tokens.push({index:start, text:ident, fn:fn});
+};
+nglr.Lexer.ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
+nglr.Lexer.prototype.readString = function(quote) {
+ var start = this.index;
+ var dateParseLength = this.dateParseLength;
+ this.index++;
+ var string = "";
+ var escape = false;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (escape) {
+ if (ch == 'u') {
+ var hex = this.text.substring(this.index + 1, this.index + 5);
+ this.index += 4;
+ string += String.fromCharCode(parseInt(hex, 16));
+ } else {
+ var rep = nglr.Lexer.ESCAPE[ch];
+ if (rep) {
+ string += rep;
+ } else {
+ string += ch;
+ }
+ }
+ escape = false;
+ } else if (ch == '\\') {
+ escape = true;
+ } else if (ch == quote) {
+ this.index++;
+ this.tokens.push({index:start, text:string,
+ fn:function(){
+ return (string.length == dateParseLength) ?
+ angular.String.toDate(string) : string;
+ }});
+ return;
+ } else {
+ string += ch;
+ }
+ this.index++;
+ }
+ throw "Lexer Error: Unterminated quote [" +
+ this.text.substring(start) + "] starting at column '" +
+ (start+1) + "' in expression '" + this.text + "'.";
+};
+
+nglr.Lexer.prototype.readRegexp = function(quote) {
+ var start = this.index;
+ this.index++;
+ var regexp = "";
+ var escape = false;
+ while (this.index < this.text.length) {
+ var ch = this.text.charAt(this.index);
+ if (escape) {
+ regexp += ch;
+ escape = false;
+ } else if (ch === '\\') {
+ regexp += ch;
+ escape = true;
+ } else if (ch === '/') {
+ this.index++;
+ var flags = "";
+ if (this.isIdent(this.text.charAt(this.index))) {
+ this.readIdent();
+ flags = this.tokens.pop().text;
+ }
+ var compiledRegexp = new RegExp(regexp, flags);
+ this.tokens.push({index:start, text:regexp, flags:flags,
+ fn:function(){return compiledRegexp;}});
+ return;
+ } else {
+ regexp += ch;
+ }
+ this.index++;
+ }
+ throw "Lexer Error: Unterminated RegExp [" +
+ this.text.substring(start) + "] starting at column '" +
+ (start+1) + "' in expression '" + this.text + "'.";
+};
+
+
+nglr.Parser = function(text, parseStrings){
+ this.text = text;
+ this.tokens = new nglr.Lexer(text, parseStrings).parse();
+ this.index = 0;
+};
+
+nglr.Parser.ZERO = function(){
+ return 0;
+};
+
+nglr.Parser.prototype.error = function(msg, token) {
+ throw "Token '" + token.text +
+ "' is " + msg + " at column='" +
+ (token.index + 1) + "' of expression '" +
+ this.text + "' starting at '" + this.text.substring(token.index) + "'.";
+};
+
+nglr.Parser.prototype.peekToken = function() {
+ if (this.tokens.length === 0)
+ throw "Unexpected end of expression: " + this.text;
+ return this.tokens[0];
+};
+
+nglr.Parser.prototype.peek = function(e1, e2, e3, e4) {
+ var tokens = this.tokens;
+ if (tokens.length > 0) {
+ var token = tokens[0];
+ var t = token.text;
+ if (t==e1 || t==e2 || t==e3 || t==e4 ||
+ (!e1 && !e2 && !e3 && !e4)) {
+ return token;
+ }
+ }
+ return false;
+};
+
+nglr.Parser.prototype.expect = function(e1, e2, e3, e4){
+ var token = this.peek(e1, e2, e3, e4);
+ if (token) {
+ this.tokens.shift();
+ this.currentToken = token;
+ return token;
+ }
+ return false;
+};
+
+nglr.Parser.prototype.consume = function(e1){
+ if (!this.expect(e1)) {
+ var token = this.peek();
+ throw "Expecting '" + e1 + "' at column '" +
+ (token.index+1) + "' in '" +
+ this.text + "' got '" +
+ this.text.substring(token.index) + "'.";
+ }
+};
+
+nglr.Parser.prototype._unary = function(fn, parse) {
+ var right = parse.apply(this);
+ return function(self) {
+ return fn(self, right(self));
+ };
+};
+
+nglr.Parser.prototype._binary = function(left, fn, parse) {
+ var right = parse.apply(this);
+ return function(self) {
+ return fn(self, left(self), right(self));
+ };
+};
+
+nglr.Parser.prototype.hasTokens = function () {
+ return this.tokens.length > 0;
+};
+
+nglr.Parser.prototype.assertAllConsumed = function(){
+ if (this.tokens.length !== 0) {
+ throw "Did not understand '" + this.text.substring(this.tokens[0].index) +
+ "' while evaluating '" + this.text + "'.";
+ }
+};
+
+nglr.Parser.prototype.statements = function(){
+ var statements = [];
+ while(true) {
+ if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
+ statements.push(this.filterChain());
+ if (!this.expect(';')) {
+ return function (self){
+ var value;
+ for ( var i = 0; i < statements.length; i++) {
+ var statement = statements[i];
+ if (statement)
+ value = statement(self);
+ }
+ return value;
+ };
+ }
+ }
+};
+
+nglr.Parser.prototype.filterChain = function(){
+ var left = this.expression();
+ var token;
+ while(true) {
+ if ((token = this.expect('|'))) {
+ left = this._binary(left, token.fn, this.filter);
+ } else {
+ return left;
+ }
+ }
+};
+
+nglr.Parser.prototype.filter = function(){
+ return this._pipeFunction(angular.filter);
+};
+
+nglr.Parser.prototype.validator = function(){
+ return this._pipeFunction(angular.validator);
+};
+
+nglr.Parser.prototype._pipeFunction = function(fnScope){
+ var fn = this.functionIdent(fnScope);
+ var argsFn = [];
+ var token;
+ while(true) {
+ if ((token = this.expect(':'))) {
+ argsFn.push(this.expression());
+ } else {
+ var fnInvoke = function(self, input){
+ var args = [input];
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self));
+ }
+ return fn.apply(self, args);
+ };
+ return function(){
+ return fnInvoke;
+ };
+ }
+ }
+};
+
+nglr.Parser.prototype.expression = function(){
+ return this.throwStmt();
+};
+
+nglr.Parser.prototype.throwStmt = function(){
+ if (this.expect('throw')) {
+ var throwExp = this.assignment();
+ return function (self) {
+ throw throwExp(self);
+ };
+ } else {
+ return this.assignment();
+ }
+};
+
+nglr.Parser.prototype.assignment = function(){
+ var left = this.logicalOR();
+ var token;
+ if (token = this.expect('=')) {
+ if (!left.isAssignable) {
+ throw "Left hand side '" +
+ this.text.substring(0, token.index) + "' of assignment '" +
+ this.text.substring(token.index) + "' is not assignable.";
+ }
+ var ident = function(){return left.isAssignable;};
+ return this._binary(ident, token.fn, this.logicalOR);
+ } else {
+ return left;
+ }
+};
+
+nglr.Parser.prototype.logicalOR = function(){
+ var left = this.logicalAND();
+ var token;
+ while(true) {
+ if ((token = this.expect('||'))) {
+ left = this._binary(left, token.fn, this.logicalAND);
+ } else {
+ return left;
+ }
+ }
+};
+
+nglr.Parser.prototype.logicalAND = function(){
+ var left = this.negated();
+ var token;
+ while(true) {
+ if ((token = this.expect('&&'))) {
+ left = this._binary(left, token.fn, this.negated);
+ } else {
+ return left;
+ }
+ }
+};
+
+nglr.Parser.prototype.negated = function(){
+ var token;
+ if (token = this.expect('!')) {
+ return this._unary(token.fn, this.equality);
+ } else {
+ return this.equality();
+ }
+};
+
+nglr.Parser.prototype.equality = function(){
+ var left = this.relational();
+ var token;
+ while(true) {
+ if ((token = this.expect('==','!='))) {
+ left = this._binary(left, token.fn, this.relational);
+ } else {
+ return left;
+ }
+ }
+};
+
+nglr.Parser.prototype.relational = function(){
+ var left = this.additive();
+ var token;
+ while(true) {
+ if ((token = this.expect('<', '>', '<=', '>='))) {
+ left = this._binary(left, token.fn, this.additive);
+ } else {
+ return left;
+ }
+ }
+};
+
+nglr.Parser.prototype.additive = function(){
+ var left = this.multiplicative();
+ var token;
+ while(token = this.expect('+','-')) {
+ left = this._binary(left, token.fn, this.multiplicative);
+ }
+ return left;
+};
+
+nglr.Parser.prototype.multiplicative = function(){
+ var left = this.unary();
+ var token;
+ while(token = this.expect('*','/','%')) {
+ left = this._binary(left, token.fn, this.unary);
+ }
+ return left;
+};
+
+nglr.Parser.prototype.unary = function(){
+ var token;
+ if (this.expect('+')) {
+ return this.primary();
+ } else if (token = this.expect('-')) {
+ return this._binary(nglr.Parser.ZERO, token.fn, this.multiplicative);
+ } else {
+ return this.primary();
+ }
+};
+
+nglr.Parser.prototype.functionIdent = function(fnScope) {
+ var token = this.expect();
+ var element = token.text.split('.');
+ var instance = fnScope;
+ var key;
+ for ( var i = 0; i < element.length; i++) {
+ key = element[i];
+ if (instance)
+ instance = instance[key];
+ }
+ if (typeof instance != 'function') {
+ throw "Function '" + token.text + "' at column '" +
+ (token.index+1) + "' in '" + this.text + "' is not defined.";
+ }
+ return instance;
+};
+
+nglr.Parser.prototype.primary = function() {
+ var primary;
+ if (this.expect('(')) {
+ var expression = this.filterChain();
+ this.consume(')');
+ primary = expression;
+ } else if (this.expect('[')) {
+ primary = this.arrayDeclaration();
+ } else if (this.expect('{')) {
+ primary = this.object();
+ } else if (this.expect('{:')) {
+ primary = this.closure(false);
+ } else if (this.expect('{(')) {
+ primary = this.closure(true);
+ } else {
+ var token = this.expect();
+ primary = token.fn;
+ if (!primary) {
+ this.error("not a primary expression", token);
+ }
+ }
+ var next;
+ while (next = this.expect('(', '[', '.')) {
+ if (next.text === '(') {
+ primary = this.functionCall(primary);
+ } else if (next.text === '[') {
+ primary = this.objectIndex(primary);
+ } else if (next.text === '.') {
+ primary = this.fieldAccess(primary);
+ } else {
+ throw "IMPOSSIBLE";
+ }
+ }
+ return primary;
+};
+
+nglr.Parser.prototype.closure = function(hasArgs) {
+ var args = [];
+ if (hasArgs) {
+ if (!this.expect(')')) {
+ args.push(this.expect().text);
+ while(this.expect(',')) {
+ args.push(this.expect().text);
+ }
+ this.consume(')');
+ }
+ this.consume(":");
+ }
+ var statements = this.statements();
+ this.consume("}");
+ return function(self){
+ return function($){
+ var scope = new nglr.Scope(self.scope.state);
+ scope.set('$', $);
+ for ( var i = 0; i < args.length; i++) {
+ scope.set(args[i], arguments[i]);
+ }
+ return statements({scope:scope});
+ };
+ };
+};
+
+nglr.Parser.prototype.fieldAccess = function(object) {
+ var field = this.expect().text;
+ var fn = function (self){
+ return nglr.Scope.getter(object(self), field);
+ };
+ fn.isAssignable = field;
+ return fn;
+};
+
+nglr.Parser.prototype.objectIndex = function(obj) {
+ var indexFn = this.expression();
+ this.consume(']');
+ if (this.expect('=')) {
+ var rhs = this.expression();
+ return function (self){
+ return obj(self)[indexFn(self)] = rhs(self);
+ };
+ } else {
+ return function (self){
+ var o = obj(self);
+ var i = indexFn(self);
+ return (o) ? o[i] : undefined;
+ };
+ }
+};
+
+nglr.Parser.prototype.functionCall = function(fn) {
+ var argsFn = [];
+ if (this.peekToken().text != ')') {
+ do {
+ argsFn.push(this.expression());
+ } while (this.expect(','));
+ }
+ this.consume(')');
+ return function (self){
+ var args = [];
+ for ( var i = 0; i < argsFn.length; i++) {
+ args.push(argsFn[i](self));
+ }
+ var fnPtr = fn(self);
+ if (typeof fnPtr === 'function') {
+ return fnPtr.apply(self, args);
+ } else {
+ throw "Expression '" + fn.isAssignable + "' is not a function.";
+ }
+ };
+};
+
+// This is used with json array declaration
+nglr.Parser.prototype.arrayDeclaration = function () {
+ var elementFns = [];
+ if (this.peekToken().text != ']') {
+ do {
+ elementFns.push(this.expression());
+ } while (this.expect(','));
+ }
+ this.consume(']');
+ return function (self){
+ var array = [];
+ for ( var i = 0; i < elementFns.length; i++) {
+ array.push(elementFns[i](self));
+ }
+ return array;
+ };
+};
+
+nglr.Parser.prototype.object = function () {
+ var keyValues = [];
+ if (this.peekToken().text != '}') {
+ do {
+ var key = this.expect().text;
+ this.consume(":");
+ var value = this.expression();
+ keyValues.push({key:key, value:value});
+ } while (this.expect(','));
+ }
+ this.consume('}');
+ return function (self){
+ var object = {};
+ for ( var i = 0; i < keyValues.length; i++) {
+ var keyValue = keyValues[i];
+ var value = keyValue.value(self);
+ object[keyValue.key] = value;
+ }
+ return object;
+ };
+};
+
+nglr.Parser.prototype.entityDeclaration = function () {
+ var decl = [];
+ while(this.hasTokens()) {
+ decl.push(this.entityDecl());
+ if (!this.expect(';')) {
+ this.assertAllConsumed();
+ }
+ }
+ return function (self){
+ var code = "";
+ for ( var i = 0; i < decl.length; i++) {
+ code += decl[i](self);
+ }
+ return code;
+ };
+};
+
+nglr.Parser.prototype.entityDecl = function () {
+ var entity = this.expect().text;
+ var instance;
+ var defaults;
+ if (this.expect('=')) {
+ instance = entity;
+ entity = this.expect().text;
+ }
+ if (this.expect(':')) {
+ defaults = this.primary()(null);
+ }
+ return function(self) {
+ var datastore = self.scope.get('$datastore');
+ var Entity = datastore.entity(entity, defaults);
+ self.scope.set(entity, Entity);
+ if (instance) {
+ var document = Entity();
+ document.$$anchor = instance;
+ self.scope.set(instance, document);
+ return "$anchor." + instance + ":{" +
+ instance + "=" + entity + ".load($anchor." + instance + ");" +
+ instance + ".$$anchor=" + angular.String.quote(instance) + ";" +
+ "};";
+ } else {
+ return "";
+ }
+ };
+};
+
+nglr.Parser.prototype.watch = function () {
+ var decl = [];
+ while(this.hasTokens()) {
+ decl.push(this.watchDecl());
+ if (!this.expect(';')) {
+ this.assertAllConsumed();
+ }
+ }
+ this.assertAllConsumed();
+ return function (self){
+ for ( var i = 0; i < decl.length; i++) {
+ var d = decl[i](self);
+ self.addListener(d.name, d.fn);
+ }
+ };
+};
+
+nglr.Parser.prototype.watchDecl = function () {
+ var anchorName = this.expect().text;
+ this.consume(":");
+ var expression;
+ if (this.peekToken().text == '{') {
+ this.consume("{");
+ expression = this.statements();
+ this.consume("}");
+ } else {
+ expression = this.expression();
+ }
+ return function(self) {
+ return {name:anchorName, fn:expression};
+ };
+};
+
+
diff --git a/src/Scope.js b/src/Scope.js
new file mode 100644
index 00000000..45dd15a4
--- /dev/null
+++ b/src/Scope.js
@@ -0,0 +1,198 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+nglr.Scope = function(initialState, name) {
+ this.widgets = [];
+ this.watchListeners = {};
+ this.name = name;
+ initialState = initialState || {};
+ var State = function(){};
+ State.prototype = initialState;
+ this.state = new State();
+ this.state.$parent = initialState;
+ if (name == "ROOT") {
+ this.state.$root = this.state;
+ }
+};
+
+nglr.Scope.expressionCache = {};
+
+nglr.Scope.prototype.updateView = function() {
+ var self = this;
+ this.fireWatchers();
+ _.each(this.widgets, function(widget){
+ self.evalWidget(widget, "", {}, function(){
+ this.updateView(self);
+ });
+ });
+};
+
+nglr.Scope.prototype.addWidget = function(controller) {
+ if (controller) this.widgets.push(controller);
+};
+
+nglr.Scope.prototype.isProperty = function(exp) {
+ for ( var i = 0; i < exp.length; i++) {
+ var ch = exp.charAt(i);
+ if (ch!='.' && !nglr.Lexer.prototype.isIdent(ch)) {
+ return false;
+ }
+ }
+ return true;
+};
+
+nglr.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 nglr.bind(lastInstance, instance);
+ }
+ return instance;
+};
+
+nglr.Scope.prototype.get = function(path) {
+ return nglr.Scope.getter(this.state, path);
+};
+
+nglr.Scope.prototype.set = function(path, value) {
+ var element = path.split('.');
+ var instance = this.state;
+ 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;
+};
+
+nglr.Scope.prototype.setEval = function(expressionText, value) {
+ this.eval(expressionText + "=" + nglr.toJson(value));
+};
+
+nglr.Scope.prototype.eval = function(expressionText, context) {
+ var expression = nglr.Scope.expressionCache[expressionText];
+ if (!expression) {
+ var parser = new nglr.Parser(expressionText);
+ expression = parser.statements();
+ parser.assertAllConsumed();
+ nglr.Scope.expressionCache[expressionText] = expression;
+ }
+ context = context || {};
+ context.scope = this;
+ return expression(context);
+};
+
+//TODO: Refactor. This function needs to be an execution closure for widgets
+// move to widgets
+// remove expression, just have inner closure.
+nglr.Scope.prototype.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){
+ console.error('Eval Widget Error:', e);
+ var jsonError = nglr.toJson(e, true);
+ widget.hasError = true;
+ jQuery(widget.view).
+ addClass('ng-exception').
+ attr('ng-error', jsonError);
+ if (onFailure) {
+ onFailure.apply(widget, [e, jsonError]);
+ }
+ return false;
+ }
+};
+
+nglr.Scope.prototype.validate = function(expressionText, value) {
+ var expression = nglr.Scope.expressionCache[expressionText];
+ if (!expression) {
+ expression = new nglr.Parser(expressionText).validator();
+ nglr.Scope.expressionCache[expressionText] = expression;
+ }
+ var self = {scope:this};
+ return expression(self)(self, value);
+};
+
+nglr.Scope.prototype.entity = function(entityDeclaration) {
+ var expression = new nglr.Parser(entityDeclaration).entityDeclaration();
+ return expression({scope:this});
+};
+
+nglr.Scope.prototype.markInvalid = function(widget) {
+ this.state.$invalidWidgets.push(widget);
+};
+
+nglr.Scope.prototype.watch = function(declaration) {
+ var self = this;
+ new nglr.Parser(declaration).watch()({
+ scope:this,
+ addListener:function(watch, exp){
+ self.addWatchListener(watch, function(n,o){
+ try {
+ return exp({scope:self}, n, o);
+ } catch(e) {
+ nglr.alert(e);
+ }
+ });
+ }
+ });
+};
+
+nglr.Scope.prototype.addWatchListener = function(watchExpression, listener) {
+ var watcher = this.watchListeners[watchExpression];
+ if (!watcher) {
+ watcher = {listeners:[], expression:watchExpression};
+ this.watchListeners[watchExpression] = watcher;
+ }
+ watcher.listeners.push(listener);
+};
+
+nglr.Scope.prototype.fireWatchers = function() {
+ var self = this;
+ var fired = false;
+ jQuery.each(this.watchListeners, function(name, watcher) {
+ var value = self.eval(watcher.expression);
+ if (value !== watcher.lastValue) {
+ jQuery.each(watcher.listeners, function(i, listener){
+ listener(value, watcher.lastValue);
+ fired = true;
+ });
+ watcher.lastValue = value;
+ }
+ });
+ return fired;
+};
diff --git a/src/Server.js b/src/Server.js
new file mode 100644
index 00000000..94b0cc10
--- /dev/null
+++ b/src/Server.js
@@ -0,0 +1,69 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+nglr.Server = function(url, getScript) {
+ this.url = url;
+ this.nextId = 0;
+ this.getScript = getScript;
+ this.uuid = "_" + ("" + Math.random()).substr(2) + "_";
+ this.maxSize = 1800;
+};
+
+nglr.Server.prototype.base64url = function(txt) {
+ return Base64.encode(txt);
+};
+
+nglr.Server.prototype.request = function(method, url, request, callback) {
+ var requestId = this.uuid + (this.nextId++);
+ nglr[requestId] = function(response) {
+ delete nglr[requestId];
+ callback(200, response);
+ };
+ var payload = {u:url, m:method, p:request};
+ payload = this.base64url(nglr.toJson(payload));
+ var totalPockets = Math.ceil(payload.length / this.maxSize);
+ var baseUrl = this.url + "/$/" + requestId + "/" + totalPockets + "/";
+ for ( var pocketNo = 0; pocketNo < totalPockets; pocketNo++) {
+ var pocket = payload.substr(pocketNo * this.maxSize, this.maxSize);
+ this.getScript(baseUrl + (pocketNo+1) + "?h=" + pocket, nglr.noop);
+ }
+};
+
+nglr.FrameServer = function(frame) {
+ this.frame = frame;
+};
+nglr.FrameServer.PREFIX = "$DATASET:";
+
+nglr.FrameServer.prototype = {
+ read:function(){
+ this.data = nglr.fromJson(this.frame.name.substr(nglr.FrameServer.PREFIX.length));
+ },
+ write:function(){
+ this.frame.name = nglr.FrameServer.PREFIX + nglr.toJson(this.data);
+ },
+ request: function(method, url, request, callback) {
+ //alert(method + " " + url + " " + nglr.toJson(request) + " " + nglr.toJson(callback));
+ }
+};
+
+
+nglr.VisualServer = function(delegate, status, update) {
+ this.delegate = delegate;
+ this.update = update;
+ this.status = status;
+};
+
+nglr.VisualServer.prototype = {
+ request:function(method, url, request, callback) {
+ var self = this;
+ this.status.beginRequest(request);
+ this.delegate.request(method, url, request, function() {
+ self.status.endRequest();
+ try {
+ callback.apply(this, arguments);
+ } catch (e) {
+ nglr.alert(nglr.toJson(e));
+ }
+ self.update();
+ });
+ }
+};
diff --git a/src/Users.js b/src/Users.js
new file mode 100644
index 00000000..c0c15848
--- /dev/null
+++ b/src/Users.js
@@ -0,0 +1,36 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+nglr.Users = function(server, controlBar) {
+ this.server = server;
+ this.controlBar = controlBar;
+};
+
+nglr.Users.prototype = {
+ fetchCurrentUser:function(callback) {
+ var self = this;
+ this.server.request("GET", "/account.json", {}, function(code, response){
+ self.current = response.user;
+ callback(response.user);
+ });
+ },
+
+ logout: function(callback) {
+ var self = this;
+ this.controlBar.logout(function(){
+ delete self.current;
+ (callback||nglr.noop)();
+ });
+ },
+
+ login: function(callback) {
+ var self = this;
+ this.controlBar.login(function(){
+ self.fetchCurrentUser(function(){
+ (callback||nglr.noop)();
+ });
+ });
+ },
+
+ notAuthorized: function(){
+ this.controlBar.notAuthorized();
+ }
+};
diff --git a/src/Validators.js b/src/Validators.js
new file mode 100644
index 00000000..94cb1d52
--- /dev/null
+++ b/src/Validators.js
@@ -0,0 +1,80 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+angular.validator.regexp = function(value, regexp, msg) {
+ if (!value.match(regexp)) {
+ return msg ||
+ "Value does not match expected format " + regexp + ".";
+ } else {
+ return null;
+ }
+};
+
+angular.validator.number = function(value, min, max) {
+ var num = 1 * value;
+ if (num == value) {
+ if (typeof min != 'undefined' && num < min) {
+ return "Value can not be less than " + min + ".";
+ }
+ if (typeof min != 'undefined' && num > max) {
+ return "Value can not be greater than " + max + ".";
+ }
+ return null;
+ } else {
+ return "Value is not a number.";
+ }
+};
+
+angular.validator.integer = function(value, min, max) {
+ var number = angular.validator.number(value, min, max);
+ if (number === null && value != Math.round(value)) {
+ return "Value is not a whole number.";
+ }
+ return number;
+};
+
+angular.validator.date = function(value, min, max) {
+ if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) {
+ return null;
+ }
+ return "Value is not a date. (Expecting format: 12/31/2009).";
+};
+
+angular.validator.ssn = function(value) {
+ if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {
+ return null;
+ }
+ return "SSN needs to be in 999-99-9999 format.";
+};
+
+angular.validator.email = function(value) {
+ if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {
+ return null;
+ }
+ return "Email needs to be in username@host.com format.";
+};
+
+angular.validator.phone = function(value) {
+ if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {
+ return null;
+ }
+ if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) {
+ return null;
+ }
+ return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
+};
+
+angular.validator.url = function(value) {
+ if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {
+ return null;
+ }
+ return "URL needs to be in http://server[:port]/path format.";
+};
+
+angular.validator.json = function(value) {
+ try {
+ nglr.fromJson(value);
+ return null;
+ } catch (e) {
+ return e.toString();
+ }
+};
diff --git a/src/Widgets.js b/src/Widgets.js
new file mode 100644
index 00000000..de74533a
--- /dev/null
+++ b/src/Widgets.js
@@ -0,0 +1,774 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+
+nglr.WidgetFactory = function(serverUrl, database) {
+ this.nextUploadId = 0;
+ this.serverUrl = serverUrl;
+ this.database = database;
+ this.createSWF = swfobject.createSWF;
+ this.onChangeListener = function(){};
+};
+
+nglr.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;
+ if (type == 'button' || type == 'submit' || type == 'reset' || type == 'image') {
+ controller = new nglr.ButtonController(input[0], exp);
+ event = "click";
+ bubbleEvent = false;
+ } else if (type == 'text' || type == 'textarea' || type == 'hidden' || type == 'password') {
+ controller = new nglr.TextController(input[0], exp);
+ event = "keyup change";
+ } else if (type == 'checkbox') {
+ controller = new nglr.CheckboxController(input[0], exp);
+ event = "click";
+ } else if (type == 'radio') {
+ controller = new nglr.RadioController(input[0], exp);
+ event="click";
+ } else if (type == 'select-one') {
+ controller = new nglr.SelectController(input[0], exp);
+ } else if (type == 'select-multiple') {
+ controller = new nglr.MultiSelectController(input[0], exp);
+ } else if (type == 'file') {
+ controller = this.createFileController(input, exp);
+ } else {
+ throw 'Unknown type: ' + type;
+ }
+ input.data('controller', controller);
+ var binder = scope.get('$binder');
+ var action = function() {
+ if (controller.updateModel(scope)) {
+ var action = jQuery(controller.view).attr('ng-action') || "";
+ if (scope.evalWidget(controller, action)) {
+ binder.updateView(scope);
+ }
+ }
+ return bubbleEvent;
+ };
+ jQuery(controller.view, ":input").
+ bind(event, action);
+ return controller;
+};
+
+nglr.WidgetFactory.prototype.createFileController = function(fileInput) {
+ var uploadId = '__uploadWidget_' + (this.nextUploadId++);
+ var view = nglr.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 nglr.FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database);
+ jQuery(swfNode).data('controller', cntl);
+ return cntl;
+};
+
+nglr.WidgetFactory.prototype.createTextWidget = function(textInput) {
+ var controller = new nglr.TextController(textInput);
+ controller.onChange(this.onChangeListener);
+ return controller;
+};
+
+/////////////////////
+// FileController
+///////////////////////
+
+nglr.FileController = function(view, scopeName, uploader, databaseUrl) {
+ this.view = view;
+ this.uploader = uploader;
+ this.scopeName = scopeName;
+ this.attachmentsPath = databaseUrl + '/_attachments';
+ this.value = null;
+ this.lastValue = undefined;
+};
+
+nglr.FileController.dispatchEvent = function(id, event, args) {
+ var object = document.getElementById(id);
+ var controller = jQuery(object).data("controller");
+ nglr.FileController.prototype['_on_' + event].apply(controller, args);
+};
+
+nglr.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>');
+};
+
+nglr.FileController.prototype._on_cancel = function() {
+};
+
+nglr.FileController.prototype._on_complete = function() {
+};
+
+nglr.FileController.prototype._on_httpStatus = function(status) {
+ nglr.alert("httpStatus:" + this.scopeName + " status:" + status);
+};
+
+nglr.FileController.prototype._on_ioError = function() {
+ nglr.alert("ioError:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_open = function() {
+ nglr.alert("open:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_progress = function(bytesLoaded, bytesTotal) {
+};
+
+nglr.FileController.prototype._on_securityError = function() {
+ nglr.alert("securityError:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_uploadCompleteData = function(data) {
+ var value = nglr.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;
+ scope.get('$binder').updateView();
+};
+
+nglr.FileController.prototype._on_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();
+};
+
+nglr.FileController.prototype.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;
+ }
+};
+
+nglr.FileController.prototype.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);
+};
+
+nglr.FileController.prototype.upload = function() {
+ if (this.name) {
+ this.uploader.uploadFile(this.attachmentsPath);
+ }
+};
+
+
+///////////////////////
+// NullController
+///////////////////////
+nglr.NullController = function(view) {this.view = view;};
+nglr.NullController.prototype.updateModel = function() { return true; };
+nglr.NullController.prototype.updateView = function() { };
+nglr.NullController.instance = new nglr.NullController();
+
+
+///////////////////////
+// ButtonController
+///////////////////////
+nglr.ButtonController = function(view) {this.view = view;};
+nglr.ButtonController.prototype.updateModel = function(scope) { return true; };
+nglr.ButtonController.prototype.updateView = function(scope) {};
+
+///////////////////////
+// TextController
+///////////////////////
+nglr.TextController = function(view, exp) {
+ this.view = view;
+ 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 = view.value;
+ var widget = view.getAttribute('ng-widget');
+ if (widget === 'datepicker') {
+ jQuery(view).datepicker();
+ }
+};
+
+nglr.TextController.prototype.updateModel = function(scope) {
+ var value = this.view.value;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.TextController.prototype.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 != value) {
+ view.value = value;
+ this.lastValue = value;
+ }
+ var isValidationError = false;
+ view.removeAttribute('ng-error');
+ if (this.required) {
+ isValidationError = !(value && value.length > 0);
+ }
+ var errorText = isValidationError ? "Required Value" : null;
+ if (!isValidationError && this.validator && value) {
+ errorText = scope.validate(this.validator, value);
+ isValidationError = !!errorText;
+ }
+ if (this.lastErrorText !== errorText) {
+ this.lastErrorText = isValidationError;
+ if (errorText !== null) {
+ view.setAttribute('ng-error', errorText);
+ scope.markInvalid(this);
+ }
+ jQuery(view).toggleClass('ng-validation-error', isValidationError);
+ }
+};
+
+///////////////////////
+// CheckboxController
+///////////////////////
+nglr.CheckboxController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.checked ? view.value : "";
+};
+
+nglr.CheckboxController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ var value = input.checked ? input.value : '';
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.CheckboxController.prototype.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 = input.value == (''+value);
+};
+
+///////////////////////
+// SelectController
+///////////////////////
+nglr.SelectController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.value;
+};
+
+nglr.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;
+ }
+ }
+};
+
+nglr.SelectController.prototype.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
+///////////////////////
+nglr.MultiSelectController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = this.selected();
+};
+
+nglr.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;
+};
+
+nglr.MultiSelectController.prototype.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;
+ }
+};
+
+nglr.MultiSelectController.prototype.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
+///////////////////////
+nglr.RadioController = function(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;
+};
+
+nglr.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;
+ }
+};
+
+nglr.RadioController.prototype.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
+///////////////////////
+nglr.BindUpdater = function(view, exp) {
+ this.view = view;
+ this.exp = nglr.Binder.parseBindings(exp);
+ this.hasError = false;
+ this.scopeSelf = {element:view};
+};
+
+nglr.BindUpdater.toText = function(obj) {
+ var e = nglr.escapeHtml;
+ switch(typeof obj) {
+ case "string":
+ case "boolean":
+ case "number":
+ return e(obj);
+ case "function":
+ return nglr.BindUpdater.toText(obj());
+ case "object":
+ if (nglr.isNode(obj)) {
+ return nglr.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 (nglr.isNode(obj.html))
+ return nglr.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(nglr.toJson(obj, true));
+ default:
+ return "";
+ }
+};
+
+nglr.BindUpdater.prototype.updateModel = function(scope) {};
+nglr.BindUpdater.prototype.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 = nglr.Binder.binding(part);
+ if (binding) {
+ scope.evalWidget(this, binding, this.scopeSelf, function(value){
+ html.push(nglr.BindUpdater.toText(value));
+ }, function(e, text){
+ nglr.setHtml(this.view, text);
+ });
+ if (this.hasError) {
+ return;
+ }
+ } else {
+ html.push(nglr.escapeHtml(part));
+ }
+ }
+ nglr.setHtml(this.view, html.join(''));
+};
+
+nglr.BindAttrUpdater = function(view, attrs) {
+ this.view = view;
+ this.attrs = attrs;
+};
+
+nglr.BindAttrUpdater.prototype.updateModel = function(scope) {};
+nglr.BindAttrUpdater.prototype.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 = nglr.Binder.parseBindings(attributeTemplates[attrName]);
+ var attrValues = [];
+ for ( var i = 0; i < attributeTemplate.length; i++) {
+ var binding = nglr.Binder.binding(attributeTemplate[i]);
+ if (binding) {
+ try {
+ var value = scope.eval(binding, {element:jNode[0], attrName:attrName});
+ if (value && (value.constructor !== nglr.array || value.length !== 0))
+ attrValues.push(value);
+ } catch (e) {
+ this.hasError = true;
+ console.error('BindAttrUpdater', e);
+ var jsonError = nglr.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.server') + '/images/blank.gif';
+ jNode.attr(attrName, attrValue);
+ }
+};
+
+nglr.EvalUpdater = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.hasError = false;
+};
+nglr.EvalUpdater.prototype.updateModel = function(scope) {};
+nglr.EvalUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp);
+};
+
+nglr.HideUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.HideUpdater.prototype.updateModel = function(scope) {};
+nglr.HideUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (nglr.toBoolean(hideValue)) {
+ view.hide();
+ } else {
+ view.show();
+ }
+ });
+};
+
+nglr.ShowUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ShowUpdater.prototype.updateModel = function(scope) {};
+nglr.ShowUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (nglr.toBoolean(hideValue)) {
+ view.show();
+ } else {
+ view.hide();
+ }
+ });
+};
+
+nglr.ClassUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ if (classValue !== null && classValue !== undefined) {
+ this.view.className = classValue;
+ }
+ });
+};
+
+nglr.ClassEvenUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassEvenUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassEvenUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 1);
+ });
+};
+
+nglr.ClassOddUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassOddUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassOddUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 0);
+ });
+};
+
+nglr.StyleUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.StyleUpdater.prototype.updateModel = function(scope) {};
+nglr.StyleUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(styleValue){
+ jQuery(this.view).attr('style', "").css(styleValue);
+ });
+};
+
+///////////////////////
+// RepeaterUpdater
+///////////////////////
+nglr.RepeaterUpdater = function(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];
+};
+
+nglr.RepeaterUpdater.prototype.updateModel = function(scope) {};
+nglr.RepeaterUpdater.prototype.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 iteratorLength = iterator.length;
+ var childrenLength = this.children.length;
+ var cursor = this.view;
+ var time = 0;
+ var child = null;
+ var keyExp = this.keyExp;
+ var valueExp = this.valueExp;
+ var i = 0;
+ jQuery.each(iterator, function(key, value){
+ if (i < childrenLength) {
+ // reuse children
+ child = self.children[i];
+ child.scope.set(valueExp, value);
+ } else {
+ // grow children
+ var name = self.prefix +
+ valueExp + " in " + self.iteratorExp + "[" + i + "]";
+ var childScope = new nglr.Scope(scope.state, name);
+ childScope.set('$index', i);
+ if (keyExp)
+ childScope.set(keyExp, key);
+ childScope.set(valueExp, value);
+ child = { scope:childScope, element:self.template(childScope, self.prefix, i) };
+ cursor.after(child.element);
+ self.children.push(child);
+ }
+ cursor = child.element;
+ var s = new Date().getTime();
+ child.scope.updateView();
+ time += new Date().getTime() - s;
+ i++;
+ });
+ // shrink children
+ for ( var r = childrenLength; r > iteratorLength; --r) {
+ var unneeded = this.children.pop();
+ unneeded.element.removeNode();
+ }
+ // 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
+//////////////////////////////////
+
+nglr.PopUp = function(doc) {
+ this.doc = doc;
+};
+
+nglr.PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup";
+
+nglr.PopUp.prototype.bind = function () {
+ var self = this;
+ this.doc.find('.ng-validation-error,.ng-exception').
+ live("mouseover", nglr.PopUp.onOver);
+};
+
+nglr.PopUp.onOver = function(e) {
+ nglr.PopUp.onOut();
+ var jNode = jQuery(this);
+ jNode.bind(nglr.PopUp.OUT_EVENT, nglr.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;
+};
+
+nglr.PopUp.onOut = function() {
+ jQuery('#ng-callout').
+ unbind(nglr.PopUp.OUT_EVENT, nglr.PopUp.onOut).
+ remove();
+ return true;
+};
+
+//////////////////////////////////
+// Status
+//////////////////////////////////
+
+
+nglr.Status = function(body) {
+ this.loader = body.append(nglr.Status.DOM).find("#ng-loading");
+ this.requestCount = 0;
+};
+
+nglr.Status.DOM ='<div id="ng-spacer"></div><div id="ng-loading">loading....</div>';
+
+nglr.Status.prototype.beginRequest = function () {
+ if (this.requestCount === 0) {
+ this.loader.show();
+ }
+ this.requestCount++;
+};
+
+nglr.Status.prototype.endRequest = function () {
+ this.requestCount--;
+ if (this.requestCount === 0) {
+ this.loader.hide("fold");
+ }
+};
diff --git a/src/Widgets.js.orig b/src/Widgets.js.orig
new file mode 100644
index 00000000..df1d3e40
--- /dev/null
+++ b/src/Widgets.js.orig
@@ -0,0 +1,764 @@
+ // Copyright (C) 2009 BRAT Tech LLC
+
+
+nglr.WidgetFactory = function(serverUrl) {
+ this.nextUploadId = 0;
+ this.serverUrl = serverUrl;
+ this.createSWF = swfobject.createSWF;
+ this.onChangeListener = function(){};
+};
+
+nglr.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;
+ if (type == 'button' || type == 'submit' || type == 'reset') {
+ controller = new nglr.ButtonController(input[0], exp);
+ event = "click";
+ bubbleEvent = false;
+ } else if (type == 'text' || type == 'textarea') {
+ controller = new nglr.TextController(input[0], exp);
+ event = "keyup change";
+ } else if (type == 'checkbox') {
+ controller = new nglr.CheckboxController(input[0], exp);
+ event = "click";
+ } else if (type == 'radio') {
+ controller = new nglr.RadioController(input[0], exp);
+ event="click";
+ } else if (type == 'select-one') {
+ controller = new nglr.SelectController(input[0], exp);
+ } else if (type == 'select-multiple') {
+ controller = new nglr.MultiSelectController(input[0], exp);
+ } else if (type == 'file') {
+ controller = this.createFileController(input, exp);
+ } else {
+ throw 'Unknown type: ' + type;
+ }
+ input.data('controller', controller);
+ var binder = scope.get('$binder');
+ var action = function() {
+ if (controller.updateModel(scope)) {
+ var action = jQuery(controller.view).attr('ng-action') || "";
+ if (scope.evalWidget(controller, action)) {
+ binder.updateView(scope);
+ }
+ }
+ return bubbleEvent;
+ };
+ jQuery(controller.view, ":input").
+ bind(event, action);
+ return controller;
+};
+
+nglr.WidgetFactory.prototype.createFileController = function(fileInput) {
+ var uploadId = '__uploadWidget_' + (this.nextUploadId++);
+ var view = nglr.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 nglr.FileController(view, fileInput[0].name, swfNode, this.serverUrl);
+ jQuery(swfNode).data('controller', cntl);
+ return cntl;
+};
+
+nglr.WidgetFactory.prototype.createTextWidget = function(textInput) {
+ var controller = new nglr.TextController(textInput);
+ controller.onChange(this.onChangeListener);
+ return controller;
+};
+
+/////////////////////
+// FileController
+///////////////////////
+
+nglr.FileController = function(view, scopeName, uploader, serverUrl) {
+ this.view = view;
+ this.uploader = uploader;
+ this.scopeName = scopeName;
+ this.uploadUrl = serverUrl + '/upload';
+ this.attachmentBase = serverUrl + '/attachments';
+ this.value = null;
+ this.lastValue = undefined;
+};
+
+nglr.FileController.dispatchEvent = function(id, event, args) {
+ var object = document.getElementById(id);
+ var controller = jQuery(object).data("controller");
+ nglr.FileController.prototype['_on_' + event].apply(controller, args);
+};
+
+nglr.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>');
+};
+
+nglr.FileController.prototype._on_cancel = function() {
+};
+
+nglr.FileController.prototype._on_complete = function() {
+};
+
+nglr.FileController.prototype._on_httpStatus = function(status) {
+ nglr.alert("httpStatus:" + this.scopeName + " status:" + status);
+};
+
+nglr.FileController.prototype._on_ioError = function() {
+ nglr.alert("ioError:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_open = function() {
+ nglr.alert("open:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_progress = function(bytesLoaded, bytesTotal) {
+};
+
+nglr.FileController.prototype._on_securityError = function() {
+ nglr.alert("securityError:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_uploadCompleteData = function(data) {
+ this.value = nglr.fromJson(data);
+ this.value.url = this.attachmentBase + '/' + this.value.id + '/' + this.value.text;
+ this.view.find("input").attr('checked', true);
+ var scope = this.view.scope();
+ this.updateModel(scope);
+ scope.get('$binder').updateView();
+};
+
+nglr.FileController.prototype._on_select = function(name, size, type) {
+ this.name = name;
+ this.view.find("a").text(name).attr('href', name);
+ this.view.find("span").text(filters.bytes(size));
+ this.upload();
+};
+
+nglr.FileController.prototype.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;
+ }
+};
+
+nglr.FileController.prototype.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.name);
+ this.view.find("span").text(filters.bytes(this.value.size));
+ }
+ this.view.find("input").attr('checked', !!modelValue);
+};
+
+nglr.FileController.prototype.upload = function() {
+ if (this.name) {
+ this.uploader.uploadFile(this.uploadUrl);
+ }
+};
+
+
+///////////////////////
+// NullController
+///////////////////////
+nglr.NullController = function(view) {this.view = view;};
+nglr.NullController.prototype.updateModel = function() { return true; };
+nglr.NullController.prototype.updateView = function() { };
+nglr.NullController.instance = new nglr.NullController();
+
+
+///////////////////////
+// ButtonController
+///////////////////////
+nglr.ButtonController = function(view) {this.view = view;};
+nglr.ButtonController.prototype.updateModel = function(scope) { return true; };
+nglr.ButtonController.prototype.updateView = function(scope) {};
+
+///////////////////////
+// TextController
+///////////////////////
+nglr.TextController = function(view, exp) {
+ this.view = view;
+ 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 = view.value;
+ var widget = view.getAttribute('ng-widget');
+ if (widget === 'datepicker') {
+ jQuery(view).datepicker();
+ }
+};
+
+nglr.TextController.prototype.updateModel = function(scope) {
+ var value = this.view.value;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.set(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.TextController.prototype.updateView = function(scope) {
+ var view = this.view;
+ var value = scope.get(this.exp);
+ if (typeof value === "undefined") {
+ value = this.initialValue;
+ scope.set(this.exp, value);
+ }
+ value = value ? value : '';
+ if (this.lastValue != value) {
+ view.value = value;
+ this.lastValue = value;
+ }
+ var isValidationError = false;
+ view.removeAttribute('ng-error');
+ if (this.required) {
+ isValidationError = !(value && value.length > 0);
+ }
+ var errorText = isValidationError ? "Required Value" : null;
+ if (!isValidationError && this.validator && value) {
+ errorText = scope.validate(this.validator, value);
+ isValidationError = !!errorText;
+ }
+ if (this.lastErrorText !== errorText) {
+ this.lastErrorText = isValidationError;
+ if (errorText !== null) {
+ view.setAttribute('ng-error', errorText);
+ scope.markInvalid(this);
+ }
+ jQuery(view).toggleClass('ng-validation-error', isValidationError);
+ }
+};
+
+///////////////////////
+// CheckboxController
+///////////////////////
+nglr.CheckboxController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.checked ? view.value : "";
+};
+
+nglr.CheckboxController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ var value = input.checked ? input.value : '';
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.CheckboxController.prototype.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 = input.value == (''+value);
+};
+
+///////////////////////
+// SelectController
+///////////////////////
+nglr.SelectController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.value;
+};
+
+nglr.SelectController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ if (input.selectedIndex < 0) {
+ scope.set(this.exp, null);
+ } else {
+ var value = this.view.value;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.set(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+ }
+};
+
+nglr.SelectController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var value = scope.get(this.exp);
+ if (typeof value === 'undefined') {
+ value = this.initialValue;
+ scope.set(this.exp, value);
+ }
+ if (value !== this.lastValue) {
+ input.value = value ? value : "";
+ this.lastValue = value;
+ }
+};
+
+///////////////////////
+// MultiSelectController
+///////////////////////
+nglr.MultiSelectController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = this.selected();
+};
+
+nglr.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;
+};
+
+nglr.MultiSelectController.prototype.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.set(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.MultiSelectController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var selected = scope.get(this.exp);
+ if (typeof selected === "undefined") {
+ selected = this.initialValue;
+ scope.set(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 = selected.contains(option.value);
+ }
+ this.lastValue = selected;
+ }
+};
+
+///////////////////////
+// RadioController
+///////////////////////
+nglr.RadioController = function(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;
+};
+
+nglr.RadioController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ if (this.lastChecked) {
+ return false;
+ } else {
+ input.checked = true;
+ this.lastValue = scope.set(this.exp, this.inputValue);
+ this.lastChecked = true;
+ return true;
+ }
+};
+
+nglr.RadioController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var value = scope.get(this.exp);
+ if (this.initialValue && typeof value === "undefined") {
+ value = this.initialValue;
+ scope.set(this.exp, value);
+ }
+ if (this.lastValue != value) {
+ this.lastChecked = input.checked = this.inputValue == (''+value);
+ this.lastValue = value;
+ }
+};
+
+///////////////////////
+//ElementController
+///////////////////////
+nglr.BindUpdater = function(view, exp) {
+ this.view = view;
+ this.exp = exp.parseBindings();
+ this.hasError = false;
+ this.scopeSelf = {element:view};
+};
+
+nglr.BindUpdater.toText = function(obj) {
+ var e = nglr.escapeHtml;
+ switch(typeof obj) {
+ case "string":
+ case "boolean":
+ case "number":
+ return e(obj);
+ case "function":
+ return nglr.BindUpdater.toText(obj());
+ case "object":
+ if (nglr.isNode(obj)) {
+ return nglr.outerHTML(obj);
+ } else if (obj && obj.TAG === filters.Meta.TAG) {
+ switch(typeof obj.html) {
+ case "string":
+ case "number":
+ return obj.html;
+ case "function":
+ return 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(nglr.toJson(obj, true));
+ default:
+ return "";
+ }
+};
+
+nglr.BindUpdater.prototype.updateModel = function(scope) {};
+nglr.BindUpdater.prototype.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 = part.binding();
+ if (binding) {
+ scope.evalWidget(this, binding, this.scopeSelf, function(value){
+ html.push(nglr.BindUpdater.toText(value));
+ }, function(e, text){
+ nglr.setHtml(this.view, text);
+ });
+ if (this.hasError) {
+ return;
+ }
+ } else {
+ html.push(nglr.escapeHtml(part));
+ }
+ }
+ nglr.setHtml(this.view, html.join(''));
+};
+
+nglr.BindAttrUpdater = function(view, attrs) {
+ this.view = view;
+ this.attrs = attrs;
+};
+
+nglr.BindAttrUpdater.prototype.updateModel = function(scope) {};
+nglr.BindAttrUpdater.prototype.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 = attributeTemplates[attrName].parseBindings();
+ var attrValues = [];
+ for ( var i = 0; i < attributeTemplate.length; i++) {
+ var binding = attributeTemplate[i].binding();
+ if (binding) {
+ try {
+ var value = scope.eval(binding, {element:jNode[0], attrName:attrName});
+ if (value && (value.constructor !== nglr.array || value.length !== 0))
+ attrValues.push(value);
+ } catch (e) {
+ this.hasError = true;
+ console.error('BindAttrUpdater', e);
+ var jsonError = nglr.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.server') + '/images/blank.gif';
+ jNode.attr(attrName, attrValue);
+ }
+};
+
+nglr.EvalUpdater = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.hasError = false;
+};
+nglr.EvalUpdater.prototype.updateModel = function(scope) {};
+nglr.EvalUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp);
+};
+
+nglr.HideUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.HideUpdater.prototype.updateModel = function(scope) {};
+nglr.HideUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (nglr.toBoolean(hideValue)) {
+ view.hide();
+ } else {
+ view.show();
+ }
+ });
+};
+
+nglr.ShowUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ShowUpdater.prototype.updateModel = function(scope) {};
+nglr.ShowUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (nglr.toBoolean(hideValue)) {
+ view.show();
+ } else {
+ view.hide();
+ }
+ });
+};
+
+nglr.ClassUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ if (classValue !== null && classValue !== undefined) {
+ this.view.className = classValue;
+ }
+ });
+};
+
+nglr.ClassEvenUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassEvenUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassEvenUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 1);
+ });
+};
+
+nglr.ClassOddUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassOddUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassOddUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 0);
+ });
+};
+
+nglr.StyleUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.StyleUpdater.prototype.updateModel = function(scope) {};
+nglr.StyleUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(styleValue){
+ jQuery(this.view).attr('style', "").css(styleValue);
+ });
+};
+
+///////////////////////
+// RepeaterUpdater
+///////////////////////
+nglr.RepeaterUpdater = function(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 + "'.";
+ }
+ this.itemExp = match[1];
+ this.iteratorExp = match[2];
+};
+
+nglr.RepeaterUpdater.prototype.updateModel = function(scope) {};
+nglr.RepeaterUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.iteratorExp, {}, function(iterator){
+ if (!iterator) {
+ iterator = [];
+ if (scope.isProperty(this.iteratorExp)) {
+ scope.set(this.iteratorExp, iterator);
+ }
+ }
+ var iteratorLength = iterator.length;
+ var childrenLength = this.children.length;
+ var cursor = this.view;
+ var time = 0;
+ var child = null;
+ var itemExp = this.itemExp;
+ for ( var i = 0; i < iteratorLength; i++) {
+ if (i < iteratorLength) {
+ if (i < childrenLength) { // reuse children
+ child = this.children[i];
+ child.scope.set(itemExp, iterator[i]);
+ } else { // grow children
+ var name = this.prefix +
+ itemExp + " in " + this.iteratorExp + "[" + i + "]";
+ var childScope = new nglr.Scope(scope.state, name);
+ childScope.set('$index', i);
+ childScope.set(itemExp, iterator[i]);
+ child = { scope:childScope, element:this.template(childScope, this.prefix, i) };
+ cursor.after(child.element);
+ this.children.push(child);
+ }
+ cursor = child.element;
+ var s = new Date().getTime();
+ child.scope.updateView();
+ time += new Date().getTime() - s;
+ }
+ }
+ // shrink children
+ for ( var r = childrenLength; r > iteratorLength; --r) {
+ var unneeded = this.children.pop();
+ unneeded.element.removeNode();
+ }
+ // 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
+//////////////////////////////////
+
+nglr.PopUp = function(doc) {
+ this.doc = doc;
+};
+
+nglr.PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup";
+
+nglr.PopUp.prototype.bind = function () {
+ var self = this;
+ this.doc.find('.ng-validation-error,.ng-exception').
+ live("mouseover", nglr.PopUp.onOver);
+};
+
+nglr.PopUp.onOver = function(e) {
+ nglr.PopUp.onOut();
+ var jNode = jQuery(this);
+ jNode.bind(nglr.PopUp.OUT_EVENT, nglr.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;
+};
+
+nglr.PopUp.onOut = function() {
+ jQuery('#ng-callout').
+ unbind(nglr.PopUp.OUT_EVENT, nglr.PopUp.onOut).
+ remove();
+ return true;
+};
+
+//////////////////////////////////
+// Status
+//////////////////////////////////
+
+nglr.Status = function (body) {
+ this.body = body;
+ this.requestCount = 0;
+};
+nglr.Status.ANGULAR = "&lt;a class='ng-angular-logo' href='http://www.getangular.com'&gt;&amp;lt;angular/&amp;gt;&lt;/a&gt;&trade;";
+
+nglr.Status.prototype.beginRequest = function () {
+ if (this.requestCount === 0) {
+<<<<<<< HEAD:public/javascripts/nglr/Widgets.js
+ this.dialogView = jQuery('<div class="ng-dialog" title="'+nglr.ControlBar.ANGULAR+' Server Communication:">Please Wait...<div/><div class="loader"></div></div>');
+=======
+ this.dialogView = jQuery('<div title="'+nglr.Status.ANGULAR+' Server Communication:">Please Wait...<div/></div>');
+ this.progressWidget = this.dialogView.find("div");
+ this.progressWidget.progressbar({value:0});
+>>>>>>> master:public/javascripts/nglr/Widgets.js
+ this.dialogView.dialog({bgiframe:true, minHeight:50, modal:true});
+ this.maxRequestCount = 0;
+ }
+ this.requestCount++;
+ this.maxRequestCount++;
+};
+
+nglr.Status.prototype.endRequest = function () {
+ this.requestCount--;
+ if (this.requestCount === 0) {
+ this.dialogView.dialog("destroy");
+ this.dialogView.remove();
+ this.dialogView = null;
+ }
+};
diff --git a/src/XSitePost.js b/src/XSitePost.js
new file mode 100644
index 00000000..7d81e207
--- /dev/null
+++ b/src/XSitePost.js
@@ -0,0 +1,100 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+if (typeof nglr == 'undefined') nglr = {};
+
+if (typeof console == 'undefined') console = {};
+if (typeof console.log == 'undefined')
+ console.log = function() {};
+if (typeof console.error == 'undefined')
+ console.error = function() {};
+
+nglr.XSitePost = function(baseUrl, window, prefix) {
+ this.baseUrl = baseUrl;
+ this.post = jQuery.post;
+ this.window = window;
+ this.inQueue = {};
+ this.outQueue = [];
+ this.maxMsgSize = 100000;
+ this.delay = 20;
+ this.prefix = prefix;
+ this.setTimeout=function(fn, delay){window.setTimeout(fn, delay);};
+};
+
+nglr.XSitePost.prototype.init = function() {
+ this.window.name = '';
+ this.response('ready', 'null');
+};
+
+nglr.XSitePost.prototype.incomingFragment = function(fragment) {
+ var parts = fragment.split(";");
+ this.incomingMsg(parts.shift(), 1*parts.shift(), 1*parts.shift(), parts.shift());
+};
+
+nglr.XSitePost.prototype.incomingMsg = function(id, partNo, totalParts, msgPart) {
+ var msg = this.inQueue[id];
+ if (!msg) {
+ msg = {id:id, parts:[], count:0};
+ this.inQueue[id] = msg;
+ }
+ msg.parts[partNo] = msgPart;
+ msg.count++;
+ if (totalParts === msg.count) {
+ delete this.inQueue[id];
+ var request = this.decodePost(msg.parts.join(''));
+ var self = this;
+ this.post(this.baseUrl + request.url, request.params, function(response, status){
+ self.response(id, response, status);
+ });
+ }
+};
+
+nglr.XSitePost.prototype.response = function(id, response, status) {
+ var start = 0;
+ var end;
+ var msg = Base64.encode(response);
+ var msgLen = msg.length;
+ var total = Math.ceil(msgLen / this.maxMsgSize);
+ var part = 0;
+ while (start < msgLen) {
+ end = Math.min(msgLen, start + this.maxMsgSize);
+ this.outQueue.push(id + ':'+part+':'+total+':' + msg.substring(start, end));
+ start = end;
+ part++;
+ }
+};
+
+nglr.XSitePost.prototype.decodePost = function(post) {
+ var parts = post.split(':');
+ var url = Base64.decode(parts.shift());
+ var params = {};
+ while(parts.length !== 0) {
+ var key = parts.shift();
+ var value = Base64.decode(parts.shift());
+ params[key] = value;
+ }
+ return {url:url, params:params};
+};
+
+nglr.XSitePost.prototype.listen = function() {
+ console.log("listen()");
+ var self = this;
+ var window = this.window;
+ var outQueue = this.outQueue;
+ var setTimeout = this.setTimeout;
+ var prefix = this.prefix;
+ var prefixLen = prefix.length;
+ var prefixRec = prefix + '>';
+ var prefixRecLen = prefixRec.length;
+ window.name = prefix;
+ var pull = function(){
+ var value = window.name;
+ if (value == prefix && outQueue.length > 0) {
+ window.name = prefix + '<' + outQueue.shift();
+ } else if (value.substr(0, prefixRecLen) == prefixRec) {
+ self.incomingFragment(value.substr(prefixRecLen));
+ window.name = prefix;
+ }
+ setTimeout(pull, self.delay);
+ };
+ pull();
+};
diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js
new file mode 100644
index 00000000..b7ae6a38
--- /dev/null
+++ b/src/angular-bootstrap.js
@@ -0,0 +1,100 @@
+// Copyright (C) 2008,2009 BRAT Tech LLC
+
+(function(previousOnLoad){
+ var filename = /(.*)\/angular-(.*).js(#(.*))?/;
+ var scripts = document.getElementsByTagName("script");
+ var scriptConfig = {
+ autoSubmit:true,
+ autoBind:true,
+ autoLoadDependencies:false
+ };
+ for(var j = 0; j < scripts.length; j++) {
+ var src = scripts[j].src;
+ if (src && src.match(filename)) {
+ var parts = src.match(filename);
+ if (parts[2] == 'bootstrap') {
+ scriptConfig.autoLoadDependencies = true;
+ }
+ scriptConfig.server = parts[1] || '';
+ if (!scriptConfig.server) {
+ scriptConfig.server = window.location.toString().split(window.location.pathname)[0];
+ }
+ if (parts[4]) {
+ var directive = parts[4].split('&');
+ for ( var i = 0; i < directive.length; i++) {
+ var keyValue = directive[i].split('=');
+ var key = keyValue[0];
+ var value = keyValue.length == 1 ? true : keyValue[1];
+ if (value == 'false') value = false;
+ if (value == 'true') value = true;
+ scriptConfig[key] = value;
+ }
+ }
+ }
+ }
+
+ var addScript = function(path, server){
+ server = server || scriptConfig.server;
+ document.write('<script type="text/javascript" src="' + server + path +'"></script>');
+ };
+
+ if (scriptConfig.autoLoadDependencies) {
+ addScript("/javascripts/webtoolkit.base64.js");
+ addScript("/javascripts/swfobject.js");
+ addScript("/javascripts/jQuery/jquery-1.3.2.js");
+ addScript("/javascripts/jQuery/jquery-ui-1.7.1.custom.min.js");
+ addScript("/javascripts/underscore/underscore.js");
+ addScript("/javascripts/nglr/Loader.js");
+ addScript("/javascripts/nglr/API.js");
+ addScript("/javascripts/nglr/Binder.js");
+ addScript("/javascripts/nglr/ControlBar.js");
+ addScript("/javascripts/nglr/DataStore.js");
+ addScript("/javascripts/nglr/Filters.js");
+ addScript("/javascripts/nglr/JSON.js");
+ addScript("/javascripts/nglr/Model.js");
+ addScript("/javascripts/nglr/Parser.js");
+ addScript("/javascripts/nglr/Scope.js");
+ addScript("/javascripts/nglr/Server.js");
+ addScript("/javascripts/nglr/Users.js");
+ addScript("/javascripts/nglr/Validators.js");
+ addScript("/javascripts/nglr/Widgets.js");
+ } else {
+ addScript("/ajax/libs/swfobject/2.2/swfobject.js", "http://ajax.googleapis.com");
+ addScript("/ajax/libs/jquery/1.3.2/jquery.min.js", "http://ajax.googleapis.com");
+ addScript("/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js", "http://ajax.googleapis.com");
+ }
+
+ window.onload = function() {
+ window.angular.init = function(root, config){
+ var cnfgMerged = _.clone(scriptConfig||{});
+ _.extend(cnfgMerged, config);
+ new nglr.Loader(root, jQuery("head"), cnfgMerged).load();
+ };
+
+ var doc = window.document;
+ if (scriptConfig.bindRootId) {
+ doc = null;
+ var ids = scriptConfig.bindRootId.split('|');
+ for ( var i = 0; i < ids.length && !doc; i++) {
+ var idCond = ids[i].split('?');
+ var id = idCond[0];
+ if (idCond.length > 1) {
+ if (!window.document.getElementById(idCond[1])) {
+ continue;
+ }
+ }
+ doc = window.document.getElementById(id);
+ }
+ }
+ if (scriptConfig.autoBind && doc) {
+ window.angular.init(doc);
+ }
+ if (typeof previousOnLoad === 'function') {
+ try {
+ previousOnLoad.apply(this, arguments);
+ } catch (e) {}
+ }
+ };
+})(window.onload);
+
+
diff --git a/src/test/Runner.js b/src/test/Runner.js
new file mode 100644
index 00000000..478ef73e
--- /dev/null
+++ b/src/test/Runner.js
@@ -0,0 +1,160 @@
+nglr.test.ScenarioRunner = function(scenarios, body) {
+ this.scenarios = scenarios;
+ this.body = body;
+};
+
+nglr.test.ScenarioRunner.prototype = {
+ run:function(){
+ this.setUpUI();
+ this.runScenarios();
+ },
+ setUpUI:function(){
+ this.body.html(
+ '<div id="runner">' +
+ '<div class="console"></div>' +
+ '</div>' +
+ '<div id="testView">' +
+ '<iframe></iframe>' +
+ '</div>');
+ this.console = this.body.find(".console");
+ this.testFrame = this.body.find("iframe");
+ this.console.find(".run").live("click", function(){
+ jQuery(this).parent().find('.log').toggle();
+ });
+ },
+ runScenarios:function(){
+ var runner = new nglr.test.Runner(this.console, this.testFrame);
+ _.stepper(this.scenarios, function(next, scenario, name){
+ new nglr.test.Scenario(name, scenario).run(runner, next);
+ }, function(){
+ }
+ );
+ }
+};
+
+nglr.test.Runner = function(console, frame){
+ this.console = console;
+ this.current = null;
+ this.tests = [];
+ this.frame = frame;
+};
+nglr.test.Runner.prototype = {
+ start:function(name){
+ var current = this.current = {
+ name:name,
+ start:new Date().getTime(),
+ scenario:jQuery('<div class="scenario"></div>')
+ };
+ current.run = current.scenario.append(
+ '<div class="run">' +
+ '<span class="name">.</span>' +
+ '<span class="time">.</span>' +
+ '<span class="state">.</span>' +
+ '</run>').find(".run");
+ current.log = current.scenario.append('<div class="log"></div>').find(".log");
+ current.run.find(".name").text(name);
+ this.tests.push(current);
+ this.console.append(current.scenario);
+ },
+ end:function(name){
+ var current = this.current;
+ var run = current.run;
+ this.current = null;
+ current.end = new Date().getTime();
+ current.time = current.end - current.start;
+ run.find(".time").text(current.time);
+ run.find(".state").text(current.error ? "FAIL" : "PASS");
+ run.addClass(current.error ? "fail" : "pass");
+ if (current.error)
+ run.find(".run").append('<span div="error"></span>').text(current.error);
+ current.scenario.find(".log").hide();
+ },
+ log:function(level) {
+ var buf = [];
+ for ( var i = 1; i < arguments.length; i++) {
+ var arg = arguments[i];
+ buf.push(typeof arg == "string" ?arg:nglr.toJson(arg));
+ }
+ var log = jQuery('<div class="' + level + '"></div>');
+ log.text(buf.join(" "));
+ this.current.log.append(log);
+ this.console.scrollTop(this.console[0].scrollHeight);
+ if (level == "error")
+ this.current.error = buf.join(" ");
+ }
+};
+
+nglr.test.Scenario = function(name, scenario){
+ this.name = name;
+ this.scenario = scenario;
+};
+nglr.test.Scenario.prototype = {
+ run:function(runner, callback) {
+ var self = this;
+ _.stepper(this.scenario, function(next, steps, name){
+ if (name.charAt(0) == '$') {
+ next();
+ } else {
+ runner.start(self.name + "::" + name);
+ var allSteps = (self.scenario.$before||[]).concat(steps);
+ _.stepper(allSteps, function(next, step){
+ self.executeStep(runner, step, next);
+ }, function(){
+ runner.end();
+ next();
+ });
+ }
+ }, callback);
+ },
+ verb:function(step){
+ var fn = null;
+ if (!step) fn = function (){ throw "Step is null!"; }
+ else if (step.Given) fn = angular.test.GIVEN[step.Given];
+ else if (step.When) fn = angular.test.WHEN[step.When];
+ else if (step.Then) fn = angular.test.THEN[step.Then];
+ return fn || function (){
+ throw "ERROR: Need Given/When/Then got: " + nglr.toJson(step);
+ };
+ },
+ context: function(runner) {
+ var frame = runner.frame;
+ var window = frame[0].contentWindow;
+ var document;
+ if (window.jQuery)
+ document = window.jQuery(window.document);
+ var context = {
+ frame:frame,
+ window:window,
+ log:_.bind(runner.log, runner, "info"),
+ document:document,
+ assert:function(element, path){
+ if (element.size() != 1) {
+ throw "Expected to find '1' found '"+
+ element.size()+"' for '"+path+"'.";
+ }
+ return element;
+ },
+ element:function(path){
+ var exp = path.replace("{{","[ng-bind=").replace("}}", "]");
+ var element = document.find(exp);
+ return context.assert(element, path);
+ }
+ };
+ return context;
+ },
+ executeStep:function(runner, step, callback) {
+ if (!step) {
+ callback();
+ return;
+ }
+ runner.log("info", nglr.toJson(step));
+ var fn = this.verb(step);
+ var context = this.context(runner);
+ _.extend(context, step);
+ try {
+ (fn.call(context)||function(c){c();})(callback);
+ } catch (e) {
+ runner.log("error", "ERROR: " + nglr.toJson(e));
+ }
+ }
+};
diff --git a/src/test/Steps.js b/src/test/Steps.js
new file mode 100644
index 00000000..af4b84d6
--- /dev/null
+++ b/src/test/Steps.js
@@ -0,0 +1,57 @@
+angular.test.GIVEN = {
+ browser:function(){
+ var self = this;
+ if (jQuery.browser.safari && this.frame.attr('src') == this.at) {
+ this.window.location.reload();
+ } else {
+ this.frame.attr('src', this.at);
+ }
+ return function(done){
+ self.frame.load(function(){
+ self.frame.unbind();
+ done();
+ });
+ };
+ },
+ dataset:function(){
+ this.frame.name="$DATASET:" + nglr.toJson({dataset:this.dataset});
+ }
+};
+angular.test.WHEN = {
+ enter:function(){
+ var element = this.element(this.at);
+ element.attr('value', this.text);
+ element.change();
+ },
+ click:function(){
+ var element = this.element(this.at);
+ var input = element[0];
+ // emulate the browser behavior which causes it
+ // to be overridden at the end.
+ var checked = input.checked = !input.checked;
+ element.click();
+ input.checked = checked;
+ },
+ select:function(){
+ var element = this.element(this.at);
+ var path = "option[value=" + this.option + "]";
+ var option = this.assert(element.find(path));
+ option[0].selected = !option[0].selected;
+ element.change();
+ }
+};
+angular.test.THEN = {
+ text:function(){
+ var element = this.element(this.at);
+ if (typeof this.should_be != undefined ) {
+ var should_be = this.should_be;
+ if (_.isArray(this.should_be))
+ should_be = JSON.stringify(should_be);
+ if (element.text() != should_be)
+ throw "Expected " + should_be +
+ " but was " + element.text() + ".";
+ }
+ },
+ drainRequestQueue:function(){
+ }
+};
diff --git a/src/test/_namespace.js b/src/test/_namespace.js
new file mode 100644
index 00000000..78f430f1
--- /dev/null
+++ b/src/test/_namespace.js
@@ -0,0 +1,5 @@
+if (!angular) angular = {};
+if (!angular.test) angular.test = {};
+if (!angular.test.GIVEN) angular.test.GIVEN = {};
+if (!angular.test.WHEN) angular.test.WHEN = {};
+if (!angular.test.THEN) angular.test.THEN = {};