From c9c176a53b1632ca2b1c6ed27382ab72ac21d45d Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 5 Jan 2010 16:36:58 -0800 Subject: angular.js --- src/API.js | 318 +++++++++++++++++++ src/Binder.js | 341 +++++++++++++++++++++ src/ControlBar.js | 71 +++++ src/DataStore.js | 332 ++++++++++++++++++++ src/Filters.js | 290 ++++++++++++++++++ src/JSON.js | 92 ++++++ src/Loader.js | 389 ++++++++++++++++++++++++ src/Model.js | 65 ++++ src/Parser.js | 741 +++++++++++++++++++++++++++++++++++++++++++++ src/Scope.js | 198 ++++++++++++ src/Server.js | 69 +++++ src/Users.js | 36 +++ src/Validators.js | 80 +++++ src/Widgets.js | 774 +++++++++++++++++++++++++++++++++++++++++++++++ src/Widgets.js.orig | 764 ++++++++++++++++++++++++++++++++++++++++++++++ src/XSitePost.js | 100 ++++++ src/angular-bootstrap.js | 100 ++++++ src/test/Runner.js | 160 ++++++++++ src/test/Steps.js | 57 ++++ src/test/_namespace.js | 5 + 20 files changed, 4982 insertions(+) create mode 100644 src/API.js create mode 100644 src/Binder.js create mode 100644 src/ControlBar.js create mode 100644 src/DataStore.js create mode 100644 src/Filters.js create mode 100644 src/JSON.js create mode 100644 src/Loader.js create mode 100644 src/Model.js create mode 100644 src/Parser.js create mode 100644 src/Scope.js create mode 100644 src/Server.js create mode 100644 src/Users.js create mode 100644 src/Validators.js create mode 100644 src/Widgets.js create mode 100644 src/Widgets.js.orig create mode 100644 src/XSitePost.js create mode 100644 src/angular-bootstrap.js create mode 100644 src/test/Runner.js create mode 100644 src/test/Steps.js create mode 100644 src/test/_namespace.js (limited to 'src') 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 = ' ' + 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('' + + '' + + '' + + '' + + ''); +}; + +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 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( + "
" + + "
" + + "
"+title+"
" + + "
"+msg+"
" + + "
"); + 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 ='
loading....
'; + +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('' + + '' + + '' + + '' + + '' + + ''); +}; + +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 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( + "
" + + "
" + + "
"+title+"
" + + "
"+msg+"
" + + "
"); + 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 = "<a class='ng-angular-logo' href='http://www.getangular.com'>&lt;angular/&gt;</a>™"; + +nglr.Status.prototype.beginRequest = function () { + if (this.requestCount === 0) { +<<<<<<< HEAD:public/javascripts/nglr/Widgets.js + this.dialogView = jQuery('
Please Wait...
'); +======= + this.dialogView = jQuery('
Please Wait...
'); + 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(''); + }; + + 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( + '
' + + '
' + + '
' + + '
' + + '' + + '
'); + 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('
') + }; + current.run = current.scenario.append( + '
' + + '.' + + '.' + + '.' + + '').find(".run"); + current.log = current.scenario.append('
').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('').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('
'); + 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 = {}; -- cgit v1.2.3 From 214c142d9de60a7f53d8c7ada2812ffff4837e0f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 8 Jan 2010 16:04:35 -0800 Subject: created a way to init the code without autobootstrap --- src/Binder.js | 17 +++++++++++++---- src/Loader.js | 19 +++++++++++++++++-- src/angular-bootstrap.js | 49 ++++++++++++++++++++++-------------------------- src/test/Runner.js | 18 ++++++++++-------- 4 files changed, 62 insertions(+), 41 deletions(-) (limited to 'src') diff --git a/src/Binder.js b/src/Binder.js index 86e99fb8..8b4d27fb 100644 --- a/src/Binder.js +++ b/src/Binder.js @@ -103,8 +103,17 @@ nglr.Binder.prototype.updateView = function() { _.each(this.updateListeners, function(fn) {fn();}); }; +nglr.Binder.prototype.docFindWithSelf = function(exp){ + var doc = jQuery(this.doc); + var selection = doc.find(exp); + if (doc.is(exp)){ + selection = selection.andSelf(); + } + return selection; +}; + nglr.Binder.prototype.executeInit = function() { - jQuery("[ng-init]", this.doc).each(function() { + this.docFindWithSelf("[ng-init]").each(function() { var jThis = jQuery(this); var scope = jThis.scope(); try { @@ -116,7 +125,7 @@ nglr.Binder.prototype.executeInit = function() { }; nglr.Binder.prototype.entity = function (scope) { - jQuery("[ng-entity]", this.doc).attr("ng-watch", function() { + this.docFindWithSelf("[ng-entity]").attr("ng-watch", function() { try { var jNode = jQuery(this); var decl = scope.entity(jNode.attr("ng-entity")); @@ -131,12 +140,12 @@ 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]"); + var submits = this.docFindWithSelf(":submit").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) { + this.docFindWithSelf("a[ng-action]").live('click', function (event) { var jNode = jQuery(this); try { jNode.scope().eval(jNode.attr('ng-action')); diff --git a/src/Loader.js b/src/Loader.js index fdcfa3cc..f7482d24 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -166,7 +166,6 @@ nglr.Loader.prototype.load = function() { 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(); @@ -201,7 +200,7 @@ nglr.Loader.prototype.uid = function() { nglr.Loader.prototype.computeConfiguration = function() { var config = this.config; if (!config.database) { - var match = config.server.match(/https?:\/\/([\w]*)/) + var match = config.server.match(/https?:\/\/([\w]*)/); config.database = match ? match[1] : "$MEMORY"; } }; @@ -387,3 +386,19 @@ nglr.UrlWatcher.prototype.setUrl = function(url) { nglr.UrlWatcher.prototype.getUrl = function() { return window.location.href; }; + +window['angularFactory'] = function(config) { + var defaults = { + server: "" + }; + //todo: don't load stylesheet by default + //todo: don't start watcher + function compile(root){ + var loader = new nglr.Loader(root, jQuery("head"), _(defaults).extend(config)); + loader.load(); + return jQuery(root).scope(); + }; + return { + compile:compile + }; +}; diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index b7ae6a38..8ac4f9f3 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -39,25 +39,25 @@ }; 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"); + addScript("/../lib/webtoolkit/webtoolkit.base64.js"); + addScript("/../lib/swfobject/swfobject.js"); + addScript("/../lib/jquery/jquery-1.3.2.js"); + addScript("/../lib/jquery/jquery-ui-1.7.1.custom.min.js"); + addScript("/../lib/underscore/underscore.js"); + addScript("/Loader.js"); + addScript("/API.js"); + addScript("/Binder.js"); + addScript("/ControlBar.js"); + addScript("/DataStore.js"); + addScript("/Filters.js"); + addScript("/JSON.js"); + addScript("/Model.js"); + addScript("/Parser.js"); + addScript("/Scope.js"); + addScript("/Server.js"); + addScript("/Users.js"); + addScript("/Validators.js"); + addScript("/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"); @@ -65,12 +65,6 @@ } 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; @@ -86,12 +80,13 @@ doc = window.document.getElementById(id); } } + var angular = window.angularFactory(scriptConfig); if (scriptConfig.autoBind && doc) { - window.angular.init(doc); + window.angularScope = angular.compile(doc); } if (typeof previousOnLoad === 'function') { try { - previousOnLoad.apply(this, arguments); + previousOnLoad.apply(this, arguments); } catch (e) {} } }; diff --git a/src/test/Runner.js b/src/test/Runner.js index 478ef73e..c7dd431a 100644 --- a/src/test/Runner.js +++ b/src/test/Runner.js @@ -1,3 +1,5 @@ +if (!nglr.test) nglr.test = {}; + nglr.test.ScenarioRunner = function(scenarios, body) { this.scenarios = scenarios; this.body = body; @@ -46,10 +48,10 @@ nglr.test.Runner.prototype = { scenario:jQuery('
') }; current.run = current.scenario.append( - '
' + - '.' + - '.' + - '.' + + '
' + + '.' + + '.' + + '.' + '').find(".run"); current.log = current.scenario.append('
').find(".log"); current.run.find(".name").text(name); @@ -79,7 +81,7 @@ nglr.test.Runner.prototype = { log.text(buf.join(" ")); this.current.log.append(log); this.console.scrollTop(this.console[0].scrollHeight); - if (level == "error") + if (level == "error") this.current.error = buf.join(" "); } }; @@ -114,16 +116,16 @@ nglr.test.Scenario.prototype = { 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) + if (window.jQuery) document = window.jQuery(window.document); var context = { - frame:frame, + frame:frame, window:window, log:_.bind(runner.log, runner, "info"), document:document, -- cgit v1.2.3 From eb9e66f4804cf417ce142e5515b039db73d31144 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 9 Jan 2010 13:21:24 -0800 Subject: cleanup --- src/Loader.js | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/Loader.js b/src/Loader.js index f7482d24..5240944c 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -402,3 +402,4 @@ window['angularFactory'] = function(config) { compile:compile }; }; + -- cgit v1.2.3 From 88eca572fdc7f68a7f384b612052c49de00df433 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 9 Jan 2010 13:43:16 -0800 Subject: change bootstrap to angular.compile --- src/Loader.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/Loader.js b/src/Loader.js index 5240944c..eae6e4f5 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -387,19 +387,15 @@ nglr.UrlWatcher.prototype.getUrl = function() { return window.location.href; }; -window['angularFactory'] = function(config) { +angular['compile'] = function(root, config) { + config = config || {}; var defaults = { server: "" }; //todo: don't load stylesheet by default //todo: don't start watcher - function compile(root){ - var loader = new nglr.Loader(root, jQuery("head"), _(defaults).extend(config)); - loader.load(); - return jQuery(root).scope(); - }; - return { - compile:compile - }; + var loader = new nglr.Loader(root, jQuery("head"), _(defaults).extend(config)); + loader.load(); + return jQuery(root).scope(); }; -- cgit v1.2.3 From 9b9a0dadcce82ae42ac09ad396d647739af20a06 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sat, 9 Jan 2010 15:02:43 -0800 Subject: removed nglr namespace --- src/API.js | 8 +- src/Binder.js | 106 +++---- src/ControlBar.js | 22 +- src/DataStore.js | 72 ++--- src/Filters.js | 8 +- src/JSON.js | 20 +- src/Loader.js | 145 ++++----- src/Model.js | 26 +- src/Parser.js | 116 +++---- src/Scope.js | 62 ++-- src/Server.js | 32 +- src/Users.js | 8 +- src/Validators.js | 2 +- src/Widgets.js | 244 +++++++-------- src/Widgets.js.orig | 764 ----------------------------------------------- src/XSitePost.js | 100 ------- src/angular-bootstrap.js | 3 +- src/angular.prefix | 2 + src/angular.suffix | 1 + src/test/Runner.js | 26 +- src/test/Steps.js | 2 +- 21 files changed, 455 insertions(+), 1314 deletions(-) delete mode 100644 src/Widgets.js.orig delete mode 100644 src/XSitePost.js create mode 100644 src/angular.prefix create mode 100644 src/angular.suffix (limited to 'src') diff --git a/src/API.js b/src/API.js index c51fe01d..6fb6e8fc 100644 --- a/src/API.js +++ b/src/API.js @@ -66,7 +66,7 @@ angular.Array = { } return true; }; - var getter = nglr.Scope.getter; + var getter = Scope.getter; var search = function(obj, text){ if (text.charAt(0) === '!') { return !search(obj, text.substr(1)); @@ -147,7 +147,7 @@ angular.Array = { }, orderBy:function(array, expression, descend) { function reverse(comp, descending) { - return nglr.toBoolean(descending) ? + return toBoolean(descending) ? function(a,b){return comp(b,a);} : comp; } function compare(v1, v2){ @@ -224,7 +224,7 @@ angular.Array = { value = {}; array[index] = value; } - nglr.merge(mergeValue, value); + merge(mergeValue, value); return array; } }; @@ -281,7 +281,7 @@ angular.Function = { if (_.isFunction(expression)){ return expression; } else if (expression){ - var scope = new nglr.Scope(); + var scope = new Scope(); return function($) { scope.state = $; return scope.eval(expression); diff --git a/src/Binder.js b/src/Binder.js index 8b4d27fb..3589cb88 100644 --- a/src/Binder.js +++ b/src/Binder.js @@ -1,5 +1,5 @@ // Copyright (C) 2009 BRAT Tech LLC -nglr.Binder = function(doc, widgetFactory, urlWatcher, config) { +Binder = function(doc, widgetFactory, urlWatcher, config) { this.doc = doc; this.urlWatcher = urlWatcher; this.anchor = {}; @@ -8,7 +8,7 @@ nglr.Binder = function(doc, widgetFactory, urlWatcher, config) { this.updateListeners = []; }; -nglr.Binder.parseBindings = function(string) { +Binder.parseBindings = function(string) { var results = []; var lastIndex = 0; var index; @@ -28,18 +28,18 @@ nglr.Binder.parseBindings = function(string) { 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; +Binder.hasBinding = function(string) { + var bindings = Binder.parseBindings(string); + return bindings.length > 1 || Binder.binding(bindings[0]) !== null; }; -nglr.Binder.binding = function(string) { +Binder.binding = function(string) { var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); return binding ? binding[1] : null; }; -nglr.Binder.prototype.parseQueryString = function(query) { +Binder.prototype.parseQueryString = function(query) { var params = {}; query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function (match, left, right) { @@ -48,7 +48,7 @@ nglr.Binder.prototype.parseQueryString = function(query) { return params; }; -nglr.Binder.prototype.parseAnchor = function(url) { +Binder.prototype.parseAnchor = function(url) { var self = this; url = url || this.urlWatcher.getUrl(); @@ -65,13 +65,13 @@ nglr.Binder.prototype.parseAnchor = function(url) { }); }; -nglr.Binder.prototype.onUrlChange = function (url) { +Binder.prototype.onUrlChange = function (url) { console.log("URL change detected", url); this.parseAnchor(url); this.updateView(); }; -nglr.Binder.prototype.updateAnchor = function() { +Binder.prototype.updateAnchor = function() { var url = this.urlWatcher.getUrl(); var anchorIndex = url.indexOf('#'); if (anchorIndex > -1) @@ -93,7 +93,7 @@ nglr.Binder.prototype.updateAnchor = function() { return url; }; -nglr.Binder.prototype.updateView = function() { +Binder.prototype.updateView = function() { var start = new Date().getTime(); var scope = jQuery(this.doc).scope(); scope.set("$invalidWidgets", []); @@ -103,7 +103,7 @@ nglr.Binder.prototype.updateView = function() { _.each(this.updateListeners, function(fn) {fn();}); }; -nglr.Binder.prototype.docFindWithSelf = function(exp){ +Binder.prototype.docFindWithSelf = function(exp){ var doc = jQuery(this.doc); var selection = doc.find(exp); if (doc.is(exp)){ @@ -112,31 +112,31 @@ nglr.Binder.prototype.docFindWithSelf = function(exp){ return selection; }; -nglr.Binder.prototype.executeInit = function() { +Binder.prototype.executeInit = function() { this.docFindWithSelf("[ng-init]").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)); + alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); } }); }; -nglr.Binder.prototype.entity = function (scope) { +Binder.prototype.entity = function (scope) { this.docFindWithSelf("[ng-entity]").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); + alert(e); } }); }; -nglr.Binder.prototype.compile = function() { +Binder.prototype.compile = function() { var jNode = jQuery(this.doc); var self = this; if (this.config.autoSubmit) { @@ -153,37 +153,37 @@ nglr.Binder.prototype.compile = function() { jNode.removeClass("ng-exception"); } catch (e) { jNode.addClass("ng-exception"); - jNode.attr('ng-error', nglr.toJson(e, true)); + jNode.attr('ng-error', toJson(e, true)); } self.updateView(); return false; }); }; -nglr.Binder.prototype.translateBinding = function(node, parentPath, factories) { +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 parts = Binder.parseBindings(node.nodeValue); + if (parts.length > 1 || Binder.binding(parts[0])) { var parent = node.parentNode; - if (nglr.isLeafNode(parent)) { + if (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')); + return new 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 binding = 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}); + factories.push({path:path.concat(offset + i), fn:Binder.prototype.ng_bind}); } - } else if (nglr.msie && part.charAt(0) == ' ') { + } else if (msie && part.charAt(0) == ' ') { newNode = document.createElement("span"); newNode.innerHTML = ' ' + part.substring(1); } else { @@ -196,7 +196,7 @@ nglr.Binder.prototype.translateBinding = function(node, parentPath, factories) { } }; -nglr.Binder.prototype.precompile = function(root) { +Binder.prototype.precompile = function(root) { var factories = []; this.precompileNode(root, [], factories); return function (template, scope, prefix) { @@ -211,13 +211,13 @@ nglr.Binder.prototype.precompile = function(root) { try { scope.addWidget(factory.fn(node, scope, prefix)); } catch (e) { - nglr.alert(e); + alert(e); } } }; }; -nglr.Binder.prototype.precompileNode = function(node, path, factories) { +Binder.prototype.precompileNode = function(node, path, factories) { var nodeType = node.nodeType; if (nodeType == Node.TEXT_NODE) { this.translateBinding(node, path, factories); @@ -234,19 +234,19 @@ nglr.Binder.prototype.precompileNode = function(node, path, factories) { if (attributes) { var bindings = node.getAttribute('ng-bind-attr'); node.removeAttribute('ng-bind-attr'); - bindings = bindings ? nglr.fromJson(bindings) : {}; + bindings = bindings ? 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' ? + var attrValue = msie && attrName == 'href' ? decodeURI(node.getAttribute(attrName, 2)) : attr.value; - if (nglr.Binder.hasBinding(attrValue)) { + if (Binder.hasBinding(attrValue)) { bindings[attrName] = attrValue; } } - var json = nglr.toJson(bindings); + var json = toJson(bindings); if (json.length > 2) { node.setAttribute("ng-bind-attr", json); } @@ -270,7 +270,7 @@ nglr.Binder.prototype.precompileNode = function(node, path, factories) { return clone; }; factories.push({path:path, fn:function(node, scope, prefix) { - return new nglr.RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix); + return new RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix); }}); return; } @@ -309,42 +309,42 @@ nglr.Binder.prototype.precompileNode = function(node, path, factories) { } }; -nglr.Binder.prototype.ng_eval = function(node) { - return new nglr.EvalUpdater(node, node.getAttribute('ng-eval')); +Binder.prototype.ng_eval = function(node) { + return new EvalUpdater(node, node.getAttribute('ng-eval')); }; -nglr.Binder.prototype.ng_bind = function(node) { - return new nglr.BindUpdater(node, "{{" + node.getAttribute('ng-bind') + "}}"); +Binder.prototype.ng_bind = function(node) { + return new 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'))); +Binder.prototype.ng_bind_attr = function(node) { + return new BindAttrUpdater(node, fromJson(node.getAttribute('ng-bind-attr'))); }; -nglr.Binder.prototype.ng_hide = function(node) { - return new nglr.HideUpdater(node, node.getAttribute('ng-hide')); +Binder.prototype.ng_hide = function(node) { + return new HideUpdater(node, node.getAttribute('ng-hide')); }; -nglr.Binder.prototype.ng_show = function(node) { - return new nglr.ShowUpdater(node, node.getAttribute('ng-show')); +Binder.prototype.ng_show = function(node) { + return new ShowUpdater(node, node.getAttribute('ng-show')); }; -nglr.Binder.prototype.ng_class = function(node) { - return new nglr.ClassUpdater(node, node.getAttribute('ng-class')); +Binder.prototype.ng_class = function(node) { + return new ClassUpdater(node, node.getAttribute('ng-class')); }; -nglr.Binder.prototype.ng_class_even = function(node) { - return new nglr.ClassEvenUpdater(node, node.getAttribute('ng-class-even')); +Binder.prototype.ng_class_even = function(node) { + return new 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')); +Binder.prototype.ng_class_odd = function(node) { + return new ClassOddUpdater(node, node.getAttribute('ng-class-odd')); }; -nglr.Binder.prototype.ng_style = function(node) { - return new nglr.StyleUpdater(node, node.getAttribute('ng-style')); +Binder.prototype.ng_style = function(node) { + return new StyleUpdater(node, node.getAttribute('ng-style')); }; -nglr.Binder.prototype.ng_watch = function(node, scope) { +Binder.prototype.ng_watch = function(node, scope) { scope.watch(node.getAttribute('ng-watch')); }; diff --git a/src/ControlBar.js b/src/ControlBar.js index 3e1f0b57..b66a1464 100644 --- a/src/ControlBar.js +++ b/src/ControlBar.js @@ -1,16 +1,16 @@ // Copyright (C) 2008,2009 BRAT Tech LLC -nglr.ControlBar = function (document, serverUrl) { +ControlBar = function (document, serverUrl) { this.document = document; this.serverUrl = serverUrl; this.window = window; this.callbacks = []; }; -nglr.ControlBar.prototype.bind = function () { +ControlBar.prototype.bind = function () { }; -nglr.ControlBar.HTML = +ControlBar.HTML = '
' + '
' + '
' + @@ -18,25 +18,25 @@ nglr.ControlBar.HTML = '
' + '
'; -nglr.ControlBar.prototype.login = function (loginSubmitFn) { +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) { +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) { +ControlBar.prototype.urlWithoutAnchor = function (path) { return this.window.location.href.split("#")[0]; }; -nglr.ControlBar.prototype.doTemplate = function (path) { +ControlBar.prototype.doTemplate = function (path) { var self = this; var id = new Date().getTime(); var url = this.urlWithoutAnchor(); @@ -49,7 +49,7 @@ nglr.ControlBar.prototype.doTemplate = function (path) { resizable: false, modal:true, title: 'Authentication: <angular/>' }); - nglr["_iframe_notify_" + id] = function() { + callbacks["_iframe_notify_" + id] = function() { loginView.dialog("destroy"); loginView.remove(); jQuery.each(self.callbacks, function(i, callback){ @@ -59,13 +59,13 @@ nglr.ControlBar.prototype.doTemplate = function (path) { }; }; -nglr.ControlBar.FORBIDEN = +ControlBar.FORBIDEN = '
' + 'Sorry, you do not have permission for this!'+ '
'; -nglr.ControlBar.prototype.notAuthorized = function () { +ControlBar.prototype.notAuthorized = function () { if (this.forbidenView) return; - this.forbidenView = jQuery(nglr.ControlBar.FORBIDEN); + this.forbidenView = jQuery(ControlBar.FORBIDEN); this.forbidenView.dialog({bgiframe:true, height:70, modal:true}); }; diff --git a/src/DataStore.js b/src/DataStore.js index 97ab92ff..bdf882a0 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -1,6 +1,6 @@ // Copyright (C) 2009 BRAT Tech LLC -nglr.DataStore = function(post, users, anchor) { +DataStore = function(post, users, anchor) { this.post = post; this.users = users; this._cache = {$collections:[]}; @@ -8,14 +8,14 @@ nglr.DataStore = function(post, users, 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); +DataStore.prototype.cache = function(document) { + if (document.constructor != Model) { + throw "Parameter must be an instance of Entity! " + toJson(document); } var key = document.$entity + '/' + document.$id; var cachedDocument = this._cache[key]; if (cachedDocument) { - nglr.Model.copyDirectFields(document, cachedDocument); + Model.copyDirectFields(document, cachedDocument); } else { this._cache[key] = document; cachedDocument = document; @@ -23,7 +23,7 @@ nglr.DataStore.prototype.cache = function(document) { return cachedDocument; }; -nglr.DataStore.prototype.load = function(instance, id, callback, failure) { +DataStore.prototype.load = function(instance, id, callback, failure) { if (id && id !== '*') { var self = this; this._jsonRequest(["GET", instance.$entity + "/" + id], function(response) { @@ -31,13 +31,13 @@ nglr.DataStore.prototype.load = function(instance, id, callback, failure) { instance.$migrate(); var clone = instance.$$entity(instance); self.cache(clone); - (callback||nglr.noop)(instance); + (callback||noop)(instance); }, failure); } return instance; }; -nglr.DataStore.prototype.loadMany = function(entity, ids, callback) { +DataStore.prototype.loadMany = function(entity, ids, callback) { var self=this; var list = []; var callbackCount = 0; @@ -45,26 +45,26 @@ nglr.DataStore.prototype.loadMany = function(entity, ids, callback) { list.push(self.load(entity(), id, function(){ callbackCount++; if (callbackCount == ids.length) { - (callback||nglr.noop)(list); + (callback||noop)(list); } })); }); return list; } -nglr.DataStore.prototype.loadOrCreate = function(instance, id, callback) { +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); + (callback||noop)(instance); } else { throw response; } }); }; -nglr.DataStore.prototype.loadAll = function(entity, callback) { +DataStore.prototype.loadAll = function(entity, callback) { var self = this; var list = []; list.$$accept = function(doc){ @@ -78,12 +78,12 @@ nglr.DataStore.prototype.loadAll = function(entity, callback) { document.$loadFrom(rows[i]); list.push(self.cache(document)); } - (callback||nglr.noop)(list); + (callback||noop)(list); }); return list; }; -nglr.DataStore.prototype.save = function(document, callback) { +DataStore.prototype.save = function(document, callback) { var self = this; var data = {}; document.$saveTo(data); @@ -103,7 +103,7 @@ nglr.DataStore.prototype.save = function(document, callback) { }); }; -nglr.DataStore.prototype.remove = function(document, callback) { +DataStore.prototype.remove = function(document, callback) { var self = this; var data = {}; document.$saveTo(data); @@ -117,11 +117,11 @@ nglr.DataStore.prototype.remove = function(document, callback) { } } }); - (callback||nglr.noop)(response); + (callback||noop)(response); }); }; -nglr.DataStore.prototype._jsonRequest = function(request, callback, failure) { +DataStore.prototype._jsonRequest = function(request, callback, failure) { request.$$callback = callback; request.$$failure = failure||function(response){ throw response; @@ -129,7 +129,7 @@ nglr.DataStore.prototype._jsonRequest = function(request, callback, failure) { this.bulkRequest.push(request); }; -nglr.DataStore.prototype.flush = function() { +DataStore.prototype.flush = function() { if (this.bulkRequest.length === 0) return; var self = this; var bulkRequest = this.bulkRequest; @@ -142,7 +142,7 @@ nglr.DataStore.prototype.flush = function() { self.post(bulkRequest, callback); }); } else if(bulkResponse.$status_code) { - nglr.alert(nglr.toJson(bulkResponse)); + alert(toJson(bulkResponse)); } else { for ( var i = 0; i < bulkResponse.length; i++) { var response = bulkResponse[i]; @@ -163,7 +163,7 @@ nglr.DataStore.prototype.flush = function() { this.post(bulkRequest, callback); }; -nglr.DataStore.prototype.saveScope = function(scope, callback) { +DataStore.prototype.saveScope = function(scope, callback) { var saveCounter = 1; function onSaveDone() { saveCounter--; @@ -172,7 +172,7 @@ nglr.DataStore.prototype.saveScope = function(scope, callback) { } for(var key in scope) { var item = scope[key]; - if (item && item.$save == nglr.Model.prototype.$save) { + if (item && item.$save == Model.prototype.$save) { saveCounter++; item.$save(onSaveDone); } @@ -180,7 +180,7 @@ nglr.DataStore.prototype.saveScope = function(scope, callback) { onSaveDone(); }; -nglr.DataStore.prototype.query = function(type, query, arg, callback){ +DataStore.prototype.query = function(type, query, arg, callback){ var self = this; var queryList = []; queryList.$$accept = function(doc){ @@ -200,7 +200,7 @@ nglr.DataStore.prototype.query = function(type, query, arg, callback){ return queryList; }; -nglr.DataStore.prototype.entities = function(callback) { +DataStore.prototype.entities = function(callback) { var entities = []; var self = this; this._jsonRequest(["GET", "$entities"], function(response) { @@ -213,7 +213,7 @@ nglr.DataStore.prototype.entities = function(callback) { return entities; }; -nglr.DataStore.prototype.documentCountsByUser = function(){ +DataStore.prototype.documentCountsByUser = function(){ var counts = {}; var self = this; self.post([["GET", "$users"]], function(code, response){ @@ -224,7 +224,7 @@ nglr.DataStore.prototype.documentCountsByUser = function(){ return counts; }; -nglr.DataStore.prototype.userDocumentIdsByEntity = function(user){ +DataStore.prototype.userDocumentIdsByEntity = function(user){ var ids = {}; var self = this; self.post([["GET", "$users/" + user]], function(code, response){ @@ -235,19 +235,19 @@ nglr.DataStore.prototype.userDocumentIdsByEntity = function(user){ 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; +DataStore.NullEntity = function(){}; +DataStore.NullEntity.all = function(){return [];}; +DataStore.NullEntity.query = function(){return [];}; +DataStore.NullEntity.load = function(){return {};}; +DataStore.NullEntity.title = undefined; -nglr.DataStore.prototype.entity = function(name, defaults){ +DataStore.prototype.entity = function(name, defaults){ if (!name) { - return nglr.DataStore.NullEntity; + return DataStore.NullEntity; } var self = this; var entity = function(initialState){ - return new nglr.Model(entity, initialState); + return new Model(entity, initialState); }; // entity.name does not work as name seems to be reserved for functions entity.title = name; @@ -275,7 +275,7 @@ nglr.DataStore.prototype.entity = function(name, defaults){ return entity; }; -nglr.DataStore.prototype.join = function(join){ +DataStore.prototype.join = function(join){ var fn = function(){ throw "Joined entities can not be instantiated into a document."; }; @@ -312,7 +312,7 @@ nglr.DataStore.prototype.join = function(join){ var row = {}; joinedResult.push(row); row[baseName] = doc; - var id = nglr.Scope.getter(row, nextJoinOn); + var id = Scope.getter(row, nextJoinOn); joinIds[id] = id; }); nextJoin.join.loadMany(_.toArray(joinIds), function(result){ @@ -321,7 +321,7 @@ nglr.DataStore.prototype.join = function(join){ byId[doc.$id] = doc; }); _(joinedResult).each(function(row){ - var id = nglr.Scope.getter(row, nextJoinOn); + var id = Scope.getter(row, nextJoinOn); row[nextJoinName] = byId[id]; }); }); diff --git a/src/Filters.js b/src/Filters.js index f75f3603..dd4217be 100644 --- a/src/Filters.js +++ b/src/Filters.js @@ -60,11 +60,11 @@ angular.filter.date = function(amount) { angular.filter.json = function(object) { jQuery(this.element).addClass("ng-monospace"); - return nglr.toJson(object, true); + return toJson(object, true); }; angular.filter.trackPackage = function(trackingNo, noMatch) { - trackingNo = nglr.trim(trackingNo); + trackingNo = trim(trackingNo); var tNo = trackingNo.replace(/ /g, ''); var MATCHERS = angular.filter.trackPackage.MATCHERS; for ( var i = 0; i < MATCHERS.length; i++) { @@ -77,7 +77,7 @@ angular.filter.trackPackage = function(trackingNo, noMatch) { return new angular.filter.Meta({ text:text, url:url, - html: '' + text + '', + html: '' + text + '', trackingNo:trackingNo}); } } @@ -115,7 +115,7 @@ angular.filter.link = function(obj, title) { if (angular.validator.email(url) === null) { url = "mailto:" + url; } - var html = '' + text + ''; + var html = '' + text + ''; return new angular.filter.Meta({text:text, url:url, html:html}); } return obj; diff --git a/src/JSON.js b/src/JSON.js index 2b6393bf..84c9a857 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -1,18 +1,18 @@ -nglr.array = [].constructor; +array = [].constructor; -nglr.toJson = function(obj, pretty){ +toJson = function(obj, pretty){ var buf = []; - nglr.toJsonArray(buf, obj, pretty ? "\n " : null); + toJsonArray(buf, obj, pretty ? "\n " : null); return buf.join(''); }; -nglr.toPrettyJson = function(obj) { - return nglr.toJson(obj, true); +toPrettyJson = function(obj) { + return toJson(obj, true); }; -nglr.fromJson = function(json) { +fromJson = function(json) { try { - var parser = new nglr.Parser(json, true); + var parser = new Parser(json, true); var expression = parser.primary(); parser.assertAllConsumed(); return expression(); @@ -23,7 +23,7 @@ nglr.fromJson = function(json) { }; -nglr.toJsonArray = function(buf, obj, pretty){ +toJsonArray = function(buf, obj, pretty){ var type = typeof obj; if (obj === null) { buf.push("null"); @@ -50,7 +50,7 @@ nglr.toJsonArray = function(buf, obj, pretty){ if (typeof item == 'function' || typeof item == 'undefined') { buf.push("null"); } else { - nglr.toJsonArray(buf, item, pretty); + toJsonArray(buf, item, pretty); } sep = true; } @@ -80,7 +80,7 @@ nglr.toJsonArray = function(buf, obj, pretty){ } buf.push(angular.String.quote(key)); buf.push(":"); - nglr.toJsonArray(buf, value, childPretty); + toJsonArray(buf, value, childPretty); comma = true; } } catch (e) { diff --git a/src/Loader.js b/src/Loader.js index eae6e4f5..dfaa355a 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -22,34 +22,37 @@ if (typeof Node == 'undefined') { }; } -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)) +var callbacks = {}; + +if (!window.angular){ angular = {}; window['angular'] = angular; } +if (!angular.validator) angular.validator = {}; +if (!angular.filter) angular.filter = {}; +if (!window.console) window.console = { log:function() {}, error:function() {} }; -if (_.isUndefined(nglr.alert)) { - nglr.alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; +if (_.isUndefined(alert)) { + alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; } -nglr.consoleLog = function(level, objs) { +var consoleNode; + +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)); + msg += sep + (typeof obj == 'string' ? obj : toJson(obj)); sep = " "; } log.appendChild(document.createTextNode(msg)); - nglr.consoleNode.appendChild(log); + consoleNode.appendChild(log); }; -nglr.isNode = function(inp) { +isNode = function(inp) { return inp && inp.tagName && inp.nodeName && @@ -57,7 +60,7 @@ nglr.isNode = function(inp) { inp.removeAttribute; }; -nglr.isLeafNode = function(node) { +isLeafNode = function(node) { switch (node.nodeName) { case "OPTION": case "PRE": @@ -68,11 +71,11 @@ nglr.isLeafNode = function(node) { } }; -nglr.noop = function() { +noop = function() { }; -nglr.setHtml = function(node, html) { - if (nglr.isLeafNode(node)) { - if (nglr.msie) { +setHtml = function(node, html) { + if (isLeafNode(node)) { + if (msie) { node.innerText = html; } else { node.textContent = html; @@ -82,7 +85,7 @@ nglr.setHtml = function(node, html) { } }; -nglr.escapeHtml = function(html) { +escapeHtml = function(html) { if (!html || !html.replace) return html; return html. @@ -91,14 +94,14 @@ nglr.escapeHtml = function(html) { replace(/>/g, '>'); }; -nglr.escapeAttr = function(html) { +escapeAttr = function(html) { if (!html || !html.replace) return html; return html.replace(//g, '>').replace(/\"/g, '"'); }; -nglr.bind = function(_this, _function) { +bind = function(_this, _function) { if (!_this) throw "Missing this"; if (!_.isFunction(_function)) @@ -108,7 +111,7 @@ nglr.bind = function(_this, _function) { }; }; -nglr.shiftBind = function(_this, _function) { +shiftBind = function(_this, _function) { return function() { var args = [ this ]; for ( var i = 0; i < arguments.length; i++) { @@ -118,7 +121,7 @@ nglr.shiftBind = function(_this, _function) { }; }; -nglr.outerHTML = function(node) { +outerHTML = function(node) { var temp = document.createElement('div'); temp.appendChild(node); var outerHTML = temp.innerHTML; @@ -126,26 +129,26 @@ nglr.outerHTML = function(node) { return outerHTML; }; -nglr.trim = function(str) { +trim = function(str) { return str.replace(/^ */, '').replace(/ *$/, ''); }; -nglr.toBoolean = function(value) { +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) { +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 && + dst[key] = fromJson(toJson(src[key])); + } else if (type == 'object' && value.constructor != array && key.substring(0, 1) != "$") { - nglr.merge(src[key], value); + merge(src[key], value); } } }; @@ -154,25 +157,25 @@ nglr.merge = function(src, dst) { // Loader // //////////////////////////// -nglr.Loader = function(document, head, config) { +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() { +Loader.prototype.load = function() { this.configureLogging(); this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); - this.loadCss('/stylesheets/nglr.css'); + this.loadCss('/stylesheets/css'); console.log("Server: " + this.config.server); - nglr.msie = jQuery.browser.msie; + msie = jQuery.browser.msie; this.configureJQueryPlugins(); this.computeConfiguration(); this.bindHtml(); }; -nglr.Loader.prototype.configureJQueryPlugins = function() { +Loader.prototype.configureJQueryPlugins = function() { console.log('Loader.configureJQueryPlugins()'); jQuery.fn.removeNode = function() { var node = this.get(0); @@ -189,15 +192,15 @@ nglr.Loader.prototype.configureJQueryPlugins = function() { return null; }; jQuery.fn.controller = function() { - return this.data('controller') || nglr.NullController.instance; + return this.data('controller') || NullController.instance; }; }; -nglr.Loader.prototype.uid = function() { +Loader.prototype.uid = function() { return "" + new Date().getTime(); }; -nglr.Loader.prototype.computeConfiguration = function() { +Loader.prototype.computeConfiguration = function() { var config = this.config; if (!config.database) { var match = config.server.match(/https?:\/\/([\w]*)/); @@ -205,27 +208,27 @@ nglr.Loader.prototype.computeConfiguration = function() { } }; -nglr.Loader.prototype.bindHtml = function() { +Loader.prototype.bindHtml = function() { console.log('Loader.bindHtml()'); - var watcher = new nglr.UrlWatcher(this.location); + var watcher = new 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 widgetFactory = new WidgetFactory(this.config.server, this.config.database); + var binder = new Binder(document[0], widgetFactory, watcher, this.config); + widgetFactory.onChangeListener = shiftBind(binder, binder.updateModel); + var controlBar = new 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); + new FrameServer(this.window) : + new Server(this.config.server, jQuery.getScript); + server = new VisualServer(server, new Status(jQuery(document.body)), onUpdate); + var users = new 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); + var datastore = new DataStore(post, users, binder.anchor); binder.updateListeners.push(function(){datastore.flush();}); - var scope = new nglr.Scope( { + var scope = new Scope( { $anchor : binder.anchor, $binder : binder, $config : this.config, @@ -241,7 +244,7 @@ nglr.Loader.prototype.bindHtml = function() { jQuery.each(["get", "set", "eval", "addWatchListener", "updateView"], function(i, method){ - angular[method] = nglr.bind(scope, scope[method]); + angular[method] = bind(scope, scope[method]); }); document.data('scope', scope); @@ -265,7 +268,7 @@ nglr.Loader.prototype.bindHtml = function() { fetchCurrentUser(); console.log('PopUp.bind()'); - new nglr.PopUp(document).bind(); + new PopUp(document).bind(); console.log('$binder.parseAnchor()'); binder.parseAnchor(); @@ -276,16 +279,16 @@ nglr.Loader.prototype.bindHtml = function() { console.log('$binder.updateView()'); binder.updateView(); - watcher.listener = nglr.bind(binder, binder.onUrlChange, watcher); - watcher.onUpdate = function(){nglr.alert("update");}; + watcher.listener = bind(binder, binder.onUrlChange, watcher); + watcher.onUpdate = function(){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)); +Loader.prototype.visualPost = function(delegate) { + var status = new Status(jQuery(document.body)); return function(request, delegateCallback) { status.beginRequest(request); var callback = function() { @@ -293,14 +296,14 @@ nglr.Loader.prototype.visualPost = function(delegate) { try { delegateCallback.apply(this, arguments); } catch (e) { - nglr.alert(nglr.toJson(e)); + alert(toJson(e)); } }; delegate(request, callback); }; }; -nglr.Loader.prototype.configureLogging = function() { +Loader.prototype.configureLogging = function() { var url = window.location.href + '#'; url = url.split('#')[1]; var config = { @@ -312,19 +315,19 @@ nglr.Loader.prototype.configureLogging = function() { 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); + consoleNode = document.createElement("div"); + consoleNode.id = 'ng-console'; + document.getElementsByTagName('body')[0].appendChild(consoleNode); console.log = function() { - nglr.consoleLog('ng-console-info', arguments); + consoleLog('ng-console-info', arguments); }; console.error = function() { - nglr.consoleLog('ng-console-error', arguments); + consoleLog('ng-console-error', arguments); }; } }; -nglr.Loader.prototype.loadCss = function(css) { +Loader.prototype.loadCss = function(css) { var cssTag = document.createElement('link'); cssTag.rel = "stylesheet"; cssTag.type = "text/css"; @@ -334,7 +337,7 @@ nglr.Loader.prototype.loadCss = function(css) { this.head[0].appendChild(cssTag); }; -nglr.UrlWatcher = function(location) { +UrlWatcher = function(location) { this.location = location; this.delay = 25; this.setTimeout = function(fn, delay) { @@ -346,7 +349,7 @@ nglr.UrlWatcher = function(location) { this.expectedUrl = location.href; }; -nglr.UrlWatcher.prototype.watch = function() { +UrlWatcher.prototype.watch = function() { var self = this; var pull = function() { if (self.expectedUrl !== self.location.href) { @@ -357,12 +360,12 @@ nglr.UrlWatcher.prototype.watch = function() { } self.location.href = self.expectedUrl; var id = '_iframe_notify_' + notify[1]; - var notifyFn = nglr[id]; - delete nglr[id]; + var notifyFn = callbacks[id]; + delete callbacks[id]; try { - (notifyFn||nglr.noop)(); + (notifyFn||noop)(); } catch (e) { - nglr.alert(e); + alert(e); } } else { self.listener(self.location.href); @@ -374,16 +377,16 @@ nglr.UrlWatcher.prototype.watch = function() { pull(); }; -nglr.UrlWatcher.prototype.setUrl = function(url) { +UrlWatcher.prototype.setUrl = function(url) { var existingURL = window.location.href; if (!existingURL.match(/#/)) existingURL += '#'; if (existingURL != url) window.location.href = url; - self.existingURL = url; + this.existingURL = url; }; -nglr.UrlWatcher.prototype.getUrl = function() { +UrlWatcher.prototype.getUrl = function() { return window.location.href; }; @@ -394,7 +397,7 @@ angular['compile'] = function(root, config) { }; //todo: don't load stylesheet by default //todo: don't start watcher - var loader = new nglr.Loader(root, jQuery("head"), _(defaults).extend(config)); + var loader = new Loader(root, jQuery("head"), _(defaults).extend(config)); loader.load(); return jQuery(root).scope(); }; diff --git a/src/Model.js b/src/Model.js index 5e48251f..35f6a1c1 100644 --- a/src/Model.js +++ b/src/Model.js @@ -3,14 +3,14 @@ // 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) { +Model = function(entity, initial) { this.$$entity = entity; this.$loadFrom(initial||{}); this.$entity = entity.title; this.$migrate(); }; -nglr.Model.copyDirectFields = function(src, dst) { +Model.copyDirectFields = function(src, dst) { if (src === dst || !src || !dst) return; var isDataField = function(src, dst, field) { return (field.substring(0,2) !== '$$') && @@ -27,39 +27,39 @@ nglr.Model.copyDirectFields = function(src, dst) { } }; -nglr.Model.prototype.$migrate = function() { - nglr.merge(this.$$entity.defaults, this); +Model.prototype.$migrate = function() { + merge(this.$$entity.defaults, this); return this; }; -nglr.Model.prototype.$merge = function(other) { - nglr.merge(other, this); +Model.prototype.$merge = function(other) { + merge(other, this); return this; }; -nglr.Model.prototype.$save = function(callback) { +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) { +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) { +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); +Model.prototype.$loadFrom = function(other) { + Model.copyDirectFields(other, this); return this; }; -nglr.Model.prototype.$saveTo = function(other) { - nglr.Model.copyDirectFields(this, other); +Model.prototype.$saveTo = function(other) { + Model.copyDirectFields(this, other); return this; }; diff --git a/src/Parser.js b/src/Parser.js index 3d72bebf..b23215be 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -1,4 +1,4 @@ -nglr.Lexer = function(text, parsStrings){ +Lexer = function(text, parsStrings){ this.text = text; // UTC dates have 20 characters, we send them through parser this.dateParseLength = parsStrings ? 20 : -1; @@ -6,7 +6,7 @@ nglr.Lexer = function(text, parsStrings){ this.index = 0; }; -nglr.Lexer.OPERATORS = { +Lexer.OPERATORS = { 'null':function(self){return null;}, 'true':function(self){return true;}, 'false':function(self){return false;}, @@ -31,7 +31,7 @@ nglr.Lexer.OPERATORS = { '!':function(self, a){return !a;} }; -nglr.Lexer.prototype.peek = function() { +Lexer.prototype.peek = function() { if (this.index + 1 < this.text.length) { return this.text.charAt(this.index + 1); } else { @@ -39,9 +39,9 @@ nglr.Lexer.prototype.peek = function() { } }; -nglr.Lexer.prototype.parse = function() { +Lexer.prototype.parse = function() { var tokens = this.tokens; - var OPERATORS = nglr.Lexer.OPERATORS; + var OPERATORS = Lexer.OPERATORS; var canStartRegExp = true; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); @@ -102,22 +102,22 @@ nglr.Lexer.prototype.parse = function() { return tokens; }; -nglr.Lexer.prototype.isNumber = function(ch) { +Lexer.prototype.isNumber = function(ch) { return '0' <= ch && ch <= '9'; }; -nglr.Lexer.prototype.isWhitespace = function(ch) { +Lexer.prototype.isWhitespace = function(ch) { return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v'; }; -nglr.Lexer.prototype.isIdent = function(ch) { +Lexer.prototype.isIdent = function(ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' == ch || ch == '$'; }; -nglr.Lexer.prototype.readNumber = function() { +Lexer.prototype.readNumber = function() { var number = ""; var start = this.index; while (this.index < this.text.length) { @@ -134,7 +134,7 @@ nglr.Lexer.prototype.readNumber = function() { fn:function(){return number;}}); }; -nglr.Lexer.prototype.readIdent = function() { +Lexer.prototype.readIdent = function() { var ident = ""; var start = this.index; while (this.index < this.text.length) { @@ -146,7 +146,7 @@ nglr.Lexer.prototype.readIdent = function() { } this.index++; } - var fn = nglr.Lexer.OPERATORS[ident]; + var fn = Lexer.OPERATORS[ident]; if (!fn) { fn = function(self){ return self.scope.get(ident); @@ -155,8 +155,8 @@ nglr.Lexer.prototype.readIdent = function() { } 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) { +Lexer.ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; +Lexer.prototype.readString = function(quote) { var start = this.index; var dateParseLength = this.dateParseLength; this.index++; @@ -170,7 +170,7 @@ nglr.Lexer.prototype.readString = function(quote) { this.index += 4; string += String.fromCharCode(parseInt(hex, 16)); } else { - var rep = nglr.Lexer.ESCAPE[ch]; + var rep = Lexer.ESCAPE[ch]; if (rep) { string += rep; } else { @@ -198,7 +198,7 @@ nglr.Lexer.prototype.readString = function(quote) { (start+1) + "' in expression '" + this.text + "'."; }; -nglr.Lexer.prototype.readRegexp = function(quote) { +Lexer.prototype.readRegexp = function(quote) { var start = this.index; this.index++; var regexp = ""; @@ -233,30 +233,30 @@ nglr.Lexer.prototype.readRegexp = function(quote) { }; -nglr.Parser = function(text, parseStrings){ +Parser = function(text, parseStrings){ this.text = text; - this.tokens = new nglr.Lexer(text, parseStrings).parse(); + this.tokens = new Lexer(text, parseStrings).parse(); this.index = 0; }; -nglr.Parser.ZERO = function(){ +Parser.ZERO = function(){ return 0; }; -nglr.Parser.prototype.error = function(msg, token) { +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() { +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) { +Parser.prototype.peek = function(e1, e2, e3, e4) { var tokens = this.tokens; if (tokens.length > 0) { var token = tokens[0]; @@ -269,7 +269,7 @@ nglr.Parser.prototype.peek = function(e1, e2, e3, e4) { return false; }; -nglr.Parser.prototype.expect = function(e1, e2, e3, e4){ +Parser.prototype.expect = function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { this.tokens.shift(); @@ -279,7 +279,7 @@ nglr.Parser.prototype.expect = function(e1, e2, e3, e4){ return false; }; -nglr.Parser.prototype.consume = function(e1){ +Parser.prototype.consume = function(e1){ if (!this.expect(e1)) { var token = this.peek(); throw "Expecting '" + e1 + "' at column '" + @@ -289,32 +289,32 @@ nglr.Parser.prototype.consume = function(e1){ } }; -nglr.Parser.prototype._unary = function(fn, parse) { +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) { +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 () { +Parser.prototype.hasTokens = function () { return this.tokens.length > 0; }; -nglr.Parser.prototype.assertAllConsumed = function(){ +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(){ +Parser.prototype.statements = function(){ var statements = []; while(true) { if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) @@ -333,7 +333,7 @@ nglr.Parser.prototype.statements = function(){ } }; -nglr.Parser.prototype.filterChain = function(){ +Parser.prototype.filterChain = function(){ var left = this.expression(); var token; while(true) { @@ -345,15 +345,15 @@ nglr.Parser.prototype.filterChain = function(){ } }; -nglr.Parser.prototype.filter = function(){ +Parser.prototype.filter = function(){ return this._pipeFunction(angular.filter); }; -nglr.Parser.prototype.validator = function(){ +Parser.prototype.validator = function(){ return this._pipeFunction(angular.validator); }; -nglr.Parser.prototype._pipeFunction = function(fnScope){ +Parser.prototype._pipeFunction = function(fnScope){ var fn = this.functionIdent(fnScope); var argsFn = []; var token; @@ -375,11 +375,11 @@ nglr.Parser.prototype._pipeFunction = function(fnScope){ } }; -nglr.Parser.prototype.expression = function(){ +Parser.prototype.expression = function(){ return this.throwStmt(); }; -nglr.Parser.prototype.throwStmt = function(){ +Parser.prototype.throwStmt = function(){ if (this.expect('throw')) { var throwExp = this.assignment(); return function (self) { @@ -390,7 +390,7 @@ nglr.Parser.prototype.throwStmt = function(){ } }; -nglr.Parser.prototype.assignment = function(){ +Parser.prototype.assignment = function(){ var left = this.logicalOR(); var token; if (token = this.expect('=')) { @@ -406,7 +406,7 @@ nglr.Parser.prototype.assignment = function(){ } }; -nglr.Parser.prototype.logicalOR = function(){ +Parser.prototype.logicalOR = function(){ var left = this.logicalAND(); var token; while(true) { @@ -418,7 +418,7 @@ nglr.Parser.prototype.logicalOR = function(){ } }; -nglr.Parser.prototype.logicalAND = function(){ +Parser.prototype.logicalAND = function(){ var left = this.negated(); var token; while(true) { @@ -430,7 +430,7 @@ nglr.Parser.prototype.logicalAND = function(){ } }; -nglr.Parser.prototype.negated = function(){ +Parser.prototype.negated = function(){ var token; if (token = this.expect('!')) { return this._unary(token.fn, this.equality); @@ -439,7 +439,7 @@ nglr.Parser.prototype.negated = function(){ } }; -nglr.Parser.prototype.equality = function(){ +Parser.prototype.equality = function(){ var left = this.relational(); var token; while(true) { @@ -451,7 +451,7 @@ nglr.Parser.prototype.equality = function(){ } }; -nglr.Parser.prototype.relational = function(){ +Parser.prototype.relational = function(){ var left = this.additive(); var token; while(true) { @@ -463,7 +463,7 @@ nglr.Parser.prototype.relational = function(){ } }; -nglr.Parser.prototype.additive = function(){ +Parser.prototype.additive = function(){ var left = this.multiplicative(); var token; while(token = this.expect('+','-')) { @@ -472,7 +472,7 @@ nglr.Parser.prototype.additive = function(){ return left; }; -nglr.Parser.prototype.multiplicative = function(){ +Parser.prototype.multiplicative = function(){ var left = this.unary(); var token; while(token = this.expect('*','/','%')) { @@ -481,18 +481,18 @@ nglr.Parser.prototype.multiplicative = function(){ return left; }; -nglr.Parser.prototype.unary = function(){ +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); + return this._binary(Parser.ZERO, token.fn, this.multiplicative); } else { return this.primary(); } }; -nglr.Parser.prototype.functionIdent = function(fnScope) { +Parser.prototype.functionIdent = function(fnScope) { var token = this.expect(); var element = token.text.split('.'); var instance = fnScope; @@ -509,7 +509,7 @@ nglr.Parser.prototype.functionIdent = function(fnScope) { return instance; }; -nglr.Parser.prototype.primary = function() { +Parser.prototype.primary = function() { var primary; if (this.expect('(')) { var expression = this.filterChain(); @@ -545,7 +545,7 @@ nglr.Parser.prototype.primary = function() { return primary; }; -nglr.Parser.prototype.closure = function(hasArgs) { +Parser.prototype.closure = function(hasArgs) { var args = []; if (hasArgs) { if (!this.expect(')')) { @@ -561,7 +561,7 @@ nglr.Parser.prototype.closure = function(hasArgs) { this.consume("}"); return function(self){ return function($){ - var scope = new nglr.Scope(self.scope.state); + var scope = new Scope(self.scope.state); scope.set('$', $); for ( var i = 0; i < args.length; i++) { scope.set(args[i], arguments[i]); @@ -571,16 +571,16 @@ nglr.Parser.prototype.closure = function(hasArgs) { }; }; -nglr.Parser.prototype.fieldAccess = function(object) { +Parser.prototype.fieldAccess = function(object) { var field = this.expect().text; var fn = function (self){ - return nglr.Scope.getter(object(self), field); + return Scope.getter(object(self), field); }; fn.isAssignable = field; return fn; }; -nglr.Parser.prototype.objectIndex = function(obj) { +Parser.prototype.objectIndex = function(obj) { var indexFn = this.expression(); this.consume(']'); if (this.expect('=')) { @@ -597,7 +597,7 @@ nglr.Parser.prototype.objectIndex = function(obj) { } }; -nglr.Parser.prototype.functionCall = function(fn) { +Parser.prototype.functionCall = function(fn) { var argsFn = []; if (this.peekToken().text != ')') { do { @@ -620,7 +620,7 @@ nglr.Parser.prototype.functionCall = function(fn) { }; // This is used with json array declaration -nglr.Parser.prototype.arrayDeclaration = function () { +Parser.prototype.arrayDeclaration = function () { var elementFns = []; if (this.peekToken().text != ']') { do { @@ -637,7 +637,7 @@ nglr.Parser.prototype.arrayDeclaration = function () { }; }; -nglr.Parser.prototype.object = function () { +Parser.prototype.object = function () { var keyValues = []; if (this.peekToken().text != '}') { do { @@ -659,7 +659,7 @@ nglr.Parser.prototype.object = function () { }; }; -nglr.Parser.prototype.entityDeclaration = function () { +Parser.prototype.entityDeclaration = function () { var decl = []; while(this.hasTokens()) { decl.push(this.entityDecl()); @@ -676,7 +676,7 @@ nglr.Parser.prototype.entityDeclaration = function () { }; }; -nglr.Parser.prototype.entityDecl = function () { +Parser.prototype.entityDecl = function () { var entity = this.expect().text; var instance; var defaults; @@ -705,7 +705,7 @@ nglr.Parser.prototype.entityDecl = function () { }; }; -nglr.Parser.prototype.watch = function () { +Parser.prototype.watch = function () { var decl = []; while(this.hasTokens()) { decl.push(this.watchDecl()); @@ -722,7 +722,7 @@ nglr.Parser.prototype.watch = function () { }; }; -nglr.Parser.prototype.watchDecl = function () { +Parser.prototype.watchDecl = function () { var anchorName = this.expect().text; this.consume(":"); var expression; diff --git a/src/Scope.js b/src/Scope.js index 45dd15a4..e3634cee 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -1,6 +1,6 @@ // Copyright (C) 2009 BRAT Tech LLC -nglr.Scope = function(initialState, name) { +Scope = function(initialState, name) { this.widgets = []; this.watchListeners = {}; this.name = name; @@ -14,9 +14,9 @@ nglr.Scope = function(initialState, name) { } }; -nglr.Scope.expressionCache = {}; +Scope.expressionCache = {}; -nglr.Scope.prototype.updateView = function() { +Scope.prototype.updateView = function() { var self = this; this.fireWatchers(); _.each(this.widgets, function(widget){ @@ -26,21 +26,21 @@ nglr.Scope.prototype.updateView = function() { }); }; -nglr.Scope.prototype.addWidget = function(controller) { +Scope.prototype.addWidget = function(controller) { if (controller) this.widgets.push(controller); }; -nglr.Scope.prototype.isProperty = function(exp) { +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)) { + if (ch!='.' && !Lexer.prototype.isIdent(ch)) { return false; } } return true; }; -nglr.Scope.getter = function(instance, path) { +Scope.getter = function(instance, path) { if (!path) return instance; var element = path.split('.'); var key; @@ -65,16 +65,16 @@ nglr.Scope.getter = function(instance, path) { } } if (typeof instance === 'function' && !instance.$$factory) { - return nglr.bind(lastInstance, instance); + return bind(lastInstance, instance); } return instance; }; -nglr.Scope.prototype.get = function(path) { - return nglr.Scope.getter(this.state, path); +Scope.prototype.get = function(path) { + return Scope.getter(this.state, path); }; -nglr.Scope.prototype.set = function(path, value) { +Scope.prototype.set = function(path, value) { var element = path.split('.'); var instance = this.state; for ( var i = 0; element.length > 1; i++) { @@ -90,17 +90,17 @@ nglr.Scope.prototype.set = function(path, value) { return value; }; -nglr.Scope.prototype.setEval = function(expressionText, value) { - this.eval(expressionText + "=" + nglr.toJson(value)); +Scope.prototype.setEval = function(expressionText, value) { + this.eval(expressionText + "=" + toJson(value)); }; -nglr.Scope.prototype.eval = function(expressionText, context) { - var expression = nglr.Scope.expressionCache[expressionText]; +Scope.prototype.eval = function(expressionText, context) { + var expression = Scope.expressionCache[expressionText]; if (!expression) { - var parser = new nglr.Parser(expressionText); + var parser = new Parser(expressionText); expression = parser.statements(); parser.assertAllConsumed(); - nglr.Scope.expressionCache[expressionText] = expression; + Scope.expressionCache[expressionText] = expression; } context = context || {}; context.scope = this; @@ -110,7 +110,7 @@ nglr.Scope.prototype.eval = function(expressionText, 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) { +Scope.prototype.evalWidget = function(widget, expression, context, onSuccess, onFailure) { try { var value = this.eval(expression, context); if (widget.hasError) { @@ -125,7 +125,7 @@ nglr.Scope.prototype.evalWidget = function(widget, expression, context, onSucces return true; } catch (e){ console.error('Eval Widget Error:', e); - var jsonError = nglr.toJson(e, true); + var jsonError = toJson(e, true); widget.hasError = true; jQuery(widget.view). addClass('ng-exception'). @@ -137,42 +137,42 @@ nglr.Scope.prototype.evalWidget = function(widget, expression, context, onSucces } }; -nglr.Scope.prototype.validate = function(expressionText, value) { - var expression = nglr.Scope.expressionCache[expressionText]; +Scope.prototype.validate = function(expressionText, value) { + var expression = Scope.expressionCache[expressionText]; if (!expression) { - expression = new nglr.Parser(expressionText).validator(); - nglr.Scope.expressionCache[expressionText] = expression; + expression = new Parser(expressionText).validator(); + 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(); +Scope.prototype.entity = function(entityDeclaration) { + var expression = new Parser(entityDeclaration).entityDeclaration(); return expression({scope:this}); }; -nglr.Scope.prototype.markInvalid = function(widget) { +Scope.prototype.markInvalid = function(widget) { this.state.$invalidWidgets.push(widget); }; -nglr.Scope.prototype.watch = function(declaration) { +Scope.prototype.watch = function(declaration) { var self = this; - new nglr.Parser(declaration).watch()({ + new Parser(declaration).watch()({ scope:this, addListener:function(watch, exp){ self.addWatchListener(watch, function(n,o){ try { return exp({scope:self}, n, o); } catch(e) { - nglr.alert(e); + alert(e); } }); } }); }; -nglr.Scope.prototype.addWatchListener = function(watchExpression, listener) { +Scope.prototype.addWatchListener = function(watchExpression, listener) { var watcher = this.watchListeners[watchExpression]; if (!watcher) { watcher = {listeners:[], expression:watchExpression}; @@ -181,7 +181,7 @@ nglr.Scope.prototype.addWatchListener = function(watchExpression, listener) { watcher.listeners.push(listener); }; -nglr.Scope.prototype.fireWatchers = function() { +Scope.prototype.fireWatchers = function() { var self = this; var fired = false; jQuery.each(this.watchListeners, function(name, watcher) { diff --git a/src/Server.js b/src/Server.js index 94b0cc10..d00f893b 100644 --- a/src/Server.js +++ b/src/Server.js @@ -1,6 +1,6 @@ // Copyright (C) 2008,2009 BRAT Tech LLC -nglr.Server = function(url, getScript) { +Server = function(url, getScript) { this.url = url; this.nextId = 0; this.getScript = getScript; @@ -8,51 +8,51 @@ nglr.Server = function(url, getScript) { this.maxSize = 1800; }; -nglr.Server.prototype.base64url = function(txt) { +Server.prototype.base64url = function(txt) { return Base64.encode(txt); }; -nglr.Server.prototype.request = function(method, url, request, callback) { +Server.prototype.request = function(method, url, request, callback) { var requestId = this.uuid + (this.nextId++); - nglr[requestId] = function(response) { - delete nglr[requestId]; + callbacks[requestId] = function(response) { + delete angular[requestId]; callback(200, response); }; var payload = {u:url, m:method, p:request}; - payload = this.base64url(nglr.toJson(payload)); + payload = this.base64url(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); + this.getScript(baseUrl + (pocketNo+1) + "?h=" + pocket, noop); } }; -nglr.FrameServer = function(frame) { +FrameServer = function(frame) { this.frame = frame; }; -nglr.FrameServer.PREFIX = "$DATASET:"; +FrameServer.PREFIX = "$DATASET:"; -nglr.FrameServer.prototype = { +FrameServer.prototype = { read:function(){ - this.data = nglr.fromJson(this.frame.name.substr(nglr.FrameServer.PREFIX.length)); + this.data = fromJson(this.frame.name.substr(FrameServer.PREFIX.length)); }, write:function(){ - this.frame.name = nglr.FrameServer.PREFIX + nglr.toJson(this.data); + this.frame.name = FrameServer.PREFIX + toJson(this.data); }, request: function(method, url, request, callback) { - //alert(method + " " + url + " " + nglr.toJson(request) + " " + nglr.toJson(callback)); + //alert(method + " " + url + " " + toJson(request) + " " + toJson(callback)); } }; -nglr.VisualServer = function(delegate, status, update) { +VisualServer = function(delegate, status, update) { this.delegate = delegate; this.update = update; this.status = status; }; -nglr.VisualServer.prototype = { +VisualServer.prototype = { request:function(method, url, request, callback) { var self = this; this.status.beginRequest(request); @@ -61,7 +61,7 @@ nglr.VisualServer.prototype = { try { callback.apply(this, arguments); } catch (e) { - nglr.alert(nglr.toJson(e)); + alert(toJson(e)); } self.update(); }); diff --git a/src/Users.js b/src/Users.js index c0c15848..d10b96df 100644 --- a/src/Users.js +++ b/src/Users.js @@ -1,10 +1,10 @@ // Copyright (C) 2008,2009 BRAT Tech LLC -nglr.Users = function(server, controlBar) { +Users = function(server, controlBar) { this.server = server; this.controlBar = controlBar; }; -nglr.Users.prototype = { +Users.prototype = { fetchCurrentUser:function(callback) { var self = this; this.server.request("GET", "/account.json", {}, function(code, response){ @@ -17,7 +17,7 @@ nglr.Users.prototype = { var self = this; this.controlBar.logout(function(){ delete self.current; - (callback||nglr.noop)(); + (callback||noop)(); }); }, @@ -25,7 +25,7 @@ nglr.Users.prototype = { var self = this; this.controlBar.login(function(){ self.fetchCurrentUser(function(){ - (callback||nglr.noop)(); + (callback||noop)(); }); }); }, diff --git a/src/Validators.js b/src/Validators.js index 94cb1d52..7cfaa2b4 100644 --- a/src/Validators.js +++ b/src/Validators.js @@ -72,7 +72,7 @@ angular.validator.url = function(value) { angular.validator.json = function(value) { try { - nglr.fromJson(value); + fromJson(value); return null; } catch (e) { return e.toString(); diff --git a/src/Widgets.js b/src/Widgets.js index de74533a..3a0f2845 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -1,7 +1,7 @@ // Copyright (C) 2009 BRAT Tech LLC -nglr.WidgetFactory = function(serverUrl, database) { +WidgetFactory = function(serverUrl, database) { this.nextUploadId = 0; this.serverUrl = serverUrl; this.database = database; @@ -9,7 +9,7 @@ nglr.WidgetFactory = function(serverUrl, database) { this.onChangeListener = function(){}; }; -nglr.WidgetFactory.prototype.createController = function(input, scope) { +WidgetFactory.prototype.createController = function(input, scope) { var controller; var type = input.attr('type').toLowerCase(); var exp = input.attr('name'); @@ -17,22 +17,22 @@ nglr.WidgetFactory.prototype.createController = function(input, scope) { var event = "change"; var bubbleEvent = true; if (type == 'button' || type == 'submit' || type == 'reset' || type == 'image') { - controller = new nglr.ButtonController(input[0], exp); + controller = new 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); + controller = new TextController(input[0], exp); event = "keyup change"; } else if (type == 'checkbox') { - controller = new nglr.CheckboxController(input[0], exp); + controller = new CheckboxController(input[0], exp); event = "click"; } else if (type == 'radio') { - controller = new nglr.RadioController(input[0], exp); + controller = new RadioController(input[0], exp); event="click"; } else if (type == 'select-one') { - controller = new nglr.SelectController(input[0], exp); + controller = new SelectController(input[0], exp); } else if (type == 'select-multiple') { - controller = new nglr.MultiSelectController(input[0], exp); + controller = new MultiSelectController(input[0], exp); } else if (type == 'file') { controller = this.createFileController(input, exp); } else { @@ -54,9 +54,9 @@ nglr.WidgetFactory.prototype.createController = function(input, scope) { return controller; }; -nglr.WidgetFactory.prototype.createFileController = function(fileInput) { +WidgetFactory.prototype.createFileController = function(fileInput) { var uploadId = '__uploadWidget_' + (this.nextUploadId++); - var view = nglr.FileController.template(uploadId); + var view = FileController.template(uploadId); fileInput.after(view); var att = { data:this.serverUrl + "/admin/ServerAPI.swf", @@ -67,13 +67,13 @@ nglr.WidgetFactory.prototype.createFileController = function(fileInput) { 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); + var cntl = new 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); +WidgetFactory.prototype.createTextWidget = function(textInput) { + var controller = new TextController(textInput); controller.onChange(this.onChangeListener); return controller; }; @@ -82,7 +82,7 @@ nglr.WidgetFactory.prototype.createTextWidget = function(textInput) { // FileController /////////////////////// -nglr.FileController = function(view, scopeName, uploader, databaseUrl) { +FileController = function(view, scopeName, uploader, databaseUrl) { this.view = view; this.uploader = uploader; this.scopeName = scopeName; @@ -91,13 +91,13 @@ nglr.FileController = function(view, scopeName, uploader, databaseUrl) { this.lastValue = undefined; }; -nglr.FileController.dispatchEvent = function(id, event, args) { +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); + FileController.prototype['_on_' + event].apply(controller, args); }; -nglr.FileController.template = function(id) { +FileController.template = function(id) { return jQuery('' + '' + '' + @@ -106,33 +106,33 @@ nglr.FileController.template = function(id) { ''); }; -nglr.FileController.prototype._on_cancel = function() { +FileController.prototype._on_cancel = function() { }; -nglr.FileController.prototype._on_complete = function() { +FileController.prototype._on_complete = function() { }; -nglr.FileController.prototype._on_httpStatus = function(status) { - nglr.alert("httpStatus:" + this.scopeName + " status:" + status); +FileController.prototype._on_httpStatus = function(status) { + alert("httpStatus:" + this.scopeName + " status:" + status); }; -nglr.FileController.prototype._on_ioError = function() { - nglr.alert("ioError:" + this.scopeName); +FileController.prototype._on_ioError = function() { + alert("ioError:" + this.scopeName); }; -nglr.FileController.prototype._on_open = function() { - nglr.alert("open:" + this.scopeName); +FileController.prototype._on_open = function() { + alert("open:" + this.scopeName); }; -nglr.FileController.prototype._on_progress = function(bytesLoaded, bytesTotal) { +FileController.prototype._on_progress = function(bytesLoaded, bytesTotal) { }; -nglr.FileController.prototype._on_securityError = function() { - nglr.alert("securityError:" + this.scopeName); +FileController.prototype._on_securityError = function() { + alert("securityError:" + this.scopeName); }; -nglr.FileController.prototype._on_uploadCompleteData = function(data) { - var value = nglr.fromJson(data); +FileController.prototype._on_uploadCompleteData = function(data) { + var value = fromJson(data); value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; this.view.find("input").attr('checked', true); var scope = this.view.scope(); @@ -142,14 +142,14 @@ nglr.FileController.prototype._on_uploadCompleteData = function(data) { scope.get('$binder').updateView(); }; -nglr.FileController.prototype._on_select = function(name, size, type) { +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) { +FileController.prototype.updateModel = function(scope) { var isChecked = this.view.find("input").attr('checked'); var value = isChecked ? this.value : null; if (this.lastValue === value) { @@ -160,7 +160,7 @@ nglr.FileController.prototype.updateModel = function(scope) { } }; -nglr.FileController.prototype.updateView = function(scope) { +FileController.prototype.updateView = function(scope) { var modelValue = scope.get(this.scopeName); if (modelValue && this.value !== modelValue) { this.value = modelValue; @@ -172,7 +172,7 @@ nglr.FileController.prototype.updateView = function(scope) { this.view.find("input").attr('checked', !!modelValue); }; -nglr.FileController.prototype.upload = function() { +FileController.prototype.upload = function() { if (this.name) { this.uploader.uploadFile(this.attachmentsPath); } @@ -182,23 +182,23 @@ nglr.FileController.prototype.upload = function() { /////////////////////// // 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(); +NullController = function(view) {this.view = view;}; +NullController.prototype.updateModel = function() { return true; }; +NullController.prototype.updateView = function() { }; +NullController.instance = new NullController(); /////////////////////// // ButtonController /////////////////////// -nglr.ButtonController = function(view) {this.view = view;}; -nglr.ButtonController.prototype.updateModel = function(scope) { return true; }; -nglr.ButtonController.prototype.updateView = function(scope) {}; +ButtonController = function(view) {this.view = view;}; +ButtonController.prototype.updateModel = function(scope) { return true; }; +ButtonController.prototype.updateView = function(scope) {}; /////////////////////// // TextController /////////////////////// -nglr.TextController = function(view, exp) { +TextController = function(view, exp) { this.view = view; this.exp = exp; this.validator = view.getAttribute('ng-validate'); @@ -212,7 +212,7 @@ nglr.TextController = function(view, exp) { } }; -nglr.TextController.prototype.updateModel = function(scope) { +TextController.prototype.updateModel = function(scope) { var value = this.view.value; if (this.lastValue === value) { return false; @@ -223,7 +223,7 @@ nglr.TextController.prototype.updateModel = function(scope) { } }; -nglr.TextController.prototype.updateView = function(scope) { +TextController.prototype.updateView = function(scope) { var view = this.view; var value = scope.get(this.exp); if (typeof value === "undefined") { @@ -258,14 +258,14 @@ nglr.TextController.prototype.updateView = function(scope) { /////////////////////// // CheckboxController /////////////////////// -nglr.CheckboxController = function(view, exp) { +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) { +CheckboxController.prototype.updateModel = function(scope) { var input = this.view; var value = input.checked ? input.value : ''; if (this.lastValue === value) { @@ -277,7 +277,7 @@ nglr.CheckboxController.prototype.updateModel = function(scope) { } }; -nglr.CheckboxController.prototype.updateView = function(scope) { +CheckboxController.prototype.updateView = function(scope) { var input = this.view; var value = scope.eval(this.exp); if (typeof value === "undefined") { @@ -290,14 +290,14 @@ nglr.CheckboxController.prototype.updateView = function(scope) { /////////////////////// // SelectController /////////////////////// -nglr.SelectController = function(view, exp) { +SelectController = function(view, exp) { this.view = view; this.exp = exp; this.lastValue = undefined; this.initialValue = view.value; }; -nglr.SelectController.prototype.updateModel = function(scope) { +SelectController.prototype.updateModel = function(scope) { var input = this.view; if (input.selectedIndex < 0) { scope.setEval(this.exp, null); @@ -313,7 +313,7 @@ nglr.SelectController.prototype.updateModel = function(scope) { } }; -nglr.SelectController.prototype.updateView = function(scope) { +SelectController.prototype.updateView = function(scope) { var input = this.view; var value = scope.get(this.exp); if (typeof value === 'undefined') { @@ -329,14 +329,14 @@ nglr.SelectController.prototype.updateView = function(scope) { /////////////////////// // MultiSelectController /////////////////////// -nglr.MultiSelectController = function(view, exp) { +MultiSelectController = function(view, exp) { this.view = view; this.exp = exp; this.lastValue = undefined; this.initialValue = this.selected(); }; -nglr.MultiSelectController.prototype.selected = function () { +MultiSelectController.prototype.selected = function () { var value = []; var options = this.view.options; for ( var i = 0; i < options.length; i++) { @@ -348,7 +348,7 @@ nglr.MultiSelectController.prototype.selected = function () { return value; }; -nglr.MultiSelectController.prototype.updateModel = function(scope) { +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) { @@ -360,7 +360,7 @@ nglr.MultiSelectController.prototype.updateModel = function(scope) { } }; -nglr.MultiSelectController.prototype.updateView = function(scope) { +MultiSelectController.prototype.updateView = function(scope) { var input = this.view; var selected = scope.get(this.exp); if (typeof selected === "undefined") { @@ -380,7 +380,7 @@ nglr.MultiSelectController.prototype.updateView = function(scope) { /////////////////////// // RadioController /////////////////////// -nglr.RadioController = function(view, exp) { +RadioController = function(view, exp) { this.view = view; this.exp = exp; this.lastChecked = undefined; @@ -389,7 +389,7 @@ nglr.RadioController = function(view, exp) { this.initialValue = view.checked ? view.value : null; }; -nglr.RadioController.prototype.updateModel = function(scope) { +RadioController.prototype.updateModel = function(scope) { var input = this.view; if (this.lastChecked) { return false; @@ -401,7 +401,7 @@ nglr.RadioController.prototype.updateModel = function(scope) { } }; -nglr.RadioController.prototype.updateView = function(scope) { +RadioController.prototype.updateView = function(scope) { var input = this.view; var value = scope.get(this.exp); if (this.initialValue && typeof value === "undefined") { @@ -417,25 +417,25 @@ nglr.RadioController.prototype.updateView = function(scope) { /////////////////////// //ElementController /////////////////////// -nglr.BindUpdater = function(view, exp) { +BindUpdater = function(view, exp) { this.view = view; - this.exp = nglr.Binder.parseBindings(exp); + this.exp = Binder.parseBindings(exp); this.hasError = false; this.scopeSelf = {element:view}; }; -nglr.BindUpdater.toText = function(obj) { - var e = nglr.escapeHtml; +BindUpdater.toText = function(obj) { + var e = escapeHtml; switch(typeof obj) { case "string": case "boolean": case "number": return e(obj); case "function": - return nglr.BindUpdater.toText(obj()); + return BindUpdater.toText(obj()); case "object": - if (nglr.isNode(obj)) { - return nglr.outerHTML(obj); + if (isNode(obj)) { + return outerHTML(obj); } else if (obj instanceof angular.filter.Meta) { switch(typeof obj.html) { case "string": @@ -444,8 +444,8 @@ nglr.BindUpdater.toText = function(obj) { case "function": return obj.html(); case "object": - if (nglr.isNode(obj.html)) - return nglr.outerHTML(obj.html); + if (isNode(obj.html)) + return outerHTML(obj.html); default: break; } @@ -461,43 +461,43 @@ nglr.BindUpdater.toText = function(obj) { } if (obj === null) return ""; - return e(nglr.toJson(obj, true)); + return e(toJson(obj, true)); default: return ""; } }; -nglr.BindUpdater.prototype.updateModel = function(scope) {}; -nglr.BindUpdater.prototype.updateView = function(scope) { +BindUpdater.prototype.updateModel = function(scope) {}; +BindUpdater.prototype.updateView = function(scope) { var html = []; var parts = this.exp; var length = parts.length; for(var i=0; i' + - '' + - '' + - '' + - '' + - ''); -}; - -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 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( - "
" + - "
" + - "
"+title+"
" + - "
"+msg+"
" + - "
"); - 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 = "<a class='ng-angular-logo' href='http://www.getangular.com'>&lt;angular/&gt;</a>™"; - -nglr.Status.prototype.beginRequest = function () { - if (this.requestCount === 0) { -<<<<<<< HEAD:public/javascripts/nglr/Widgets.js - this.dialogView = jQuery('
Please Wait...
'); -======= - this.dialogView = jQuery('
Please Wait...
'); - 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 deleted file mode 100644 index 7d81e207..00000000 --- a/src/XSitePost.js +++ /dev/null @@ -1,100 +0,0 @@ -// 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 index 8ac4f9f3..b13bbf34 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -80,9 +80,8 @@ doc = window.document.getElementById(id); } } - var angular = window.angularFactory(scriptConfig); if (scriptConfig.autoBind && doc) { - window.angularScope = angular.compile(doc); + window.angularScope = angular.compile(doc, scriptConfig); } if (typeof previousOnLoad === 'function') { try { diff --git a/src/angular.prefix b/src/angular.prefix new file mode 100644 index 00000000..522c17bf --- /dev/null +++ b/src/angular.prefix @@ -0,0 +1,2 @@ + +(function(window, document){ \ No newline at end of file diff --git a/src/angular.suffix b/src/angular.suffix new file mode 100644 index 00000000..4b3cc37b --- /dev/null +++ b/src/angular.suffix @@ -0,0 +1 @@ +})(window, document); \ No newline at end of file diff --git a/src/test/Runner.js b/src/test/Runner.js index c7dd431a..5840282e 100644 --- a/src/test/Runner.js +++ b/src/test/Runner.js @@ -1,11 +1,11 @@ -if (!nglr.test) nglr.test = {}; +if (typeof test == 'undefined') test = {}; -nglr.test.ScenarioRunner = function(scenarios, body) { +test.ScenarioRunner = function(scenarios, body) { this.scenarios = scenarios; this.body = body; }; -nglr.test.ScenarioRunner.prototype = { +test.ScenarioRunner.prototype = { run:function(){ this.setUpUI(); this.runScenarios(); @@ -25,22 +25,22 @@ nglr.test.ScenarioRunner.prototype = { }); }, runScenarios:function(){ - var runner = new nglr.test.Runner(this.console, this.testFrame); + var runner = new test.Runner(this.console, this.testFrame); _.stepper(this.scenarios, function(next, scenario, name){ - new nglr.test.Scenario(name, scenario).run(runner, next); + new test.Scenario(name, scenario).run(runner, next); }, function(){ } ); } }; -nglr.test.Runner = function(console, frame){ +test.Runner = function(console, frame){ this.console = console; this.current = null; this.tests = []; this.frame = frame; }; -nglr.test.Runner.prototype = { +test.Runner.prototype = { start:function(name){ var current = this.current = { name:name, @@ -75,7 +75,7 @@ nglr.test.Runner.prototype = { var buf = []; for ( var i = 1; i < arguments.length; i++) { var arg = arguments[i]; - buf.push(typeof arg == "string" ?arg:nglr.toJson(arg)); + buf.push(typeof arg == "string" ?arg:toJson(arg)); } var log = jQuery('
'); log.text(buf.join(" ")); @@ -86,11 +86,11 @@ nglr.test.Runner.prototype = { } }; -nglr.test.Scenario = function(name, scenario){ +test.Scenario = function(name, scenario){ this.name = name; this.scenario = scenario; }; -nglr.test.Scenario.prototype = { +test.Scenario.prototype = { run:function(runner, callback) { var self = this; _.stepper(this.scenario, function(next, steps, name){ @@ -115,7 +115,7 @@ nglr.test.Scenario.prototype = { 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); + throw "ERROR: Need Given/When/Then got: " + toJson(step); }; }, context: function(runner) { @@ -149,14 +149,14 @@ nglr.test.Scenario.prototype = { callback(); return; } - runner.log("info", nglr.toJson(step)); + runner.log("info", 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)); + runner.log("error", "ERROR: " + toJson(e)); } } }; diff --git a/src/test/Steps.js b/src/test/Steps.js index af4b84d6..cc9ff549 100644 --- a/src/test/Steps.js +++ b/src/test/Steps.js @@ -14,7 +14,7 @@ angular.test.GIVEN = { }; }, dataset:function(){ - this.frame.name="$DATASET:" + nglr.toJson({dataset:this.dataset}); + this.frame.name="$DATASET:" + toJson({dataset:this.dataset}); } }; angular.test.WHEN = { -- cgit v1.2.3 From 1aba6b53b88c70b61a0cc991b1371739305d117b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Sun, 10 Jan 2010 08:58:57 -0800 Subject: basic calculator works with minified.js, lots of references still broken --- src/API.js | 72 +++++++++++++++++++++++++------------------------- src/DataStore.js | 2 +- src/Filters.js | 2 +- src/JSON.js | 6 ++--- src/Loader.js | 47 +++++++++++++++----------------- src/Parser.js | 8 +++--- src/Scope.js | 2 +- src/Widgets.js | 16 +++++++---- src/test/Runner.js | 2 +- src/test/_namespace.js | 10 +++---- 10 files changed, 85 insertions(+), 82 deletions(-) (limited to 'src') diff --git a/src/API.js b/src/API.js index 6fb6e8fc..49089da0 100644 --- a/src/API.js +++ b/src/API.js @@ -1,5 +1,5 @@ -angular.Global = { - typeOf:function(obj){ +angular['Global'] = { + 'typeOf':function(obj){ var type = typeof obj; switch(type) { case "object": @@ -12,10 +12,10 @@ angular.Global = { } }; -angular.Collection = {}; -angular.Object = {}; -angular.Array = { - includeIf:function(array, value, condition) { +angular['Collection'] = {}; +angular['Object'] = {}; +angular['Array'] = { + 'includeIf':function(array, value, condition) { var index = _.indexOf(array, value); if (condition) { if (index == -1) @@ -25,8 +25,8 @@ angular.Array = { } return array; }, - sum:function(array, expression) { - var fn = angular.Function.compile(expression); + '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]); @@ -36,15 +36,15 @@ angular.Array = { } return sum; }, - remove:function(array, value) { + 'remove':function(array, value) { var index = _.indexOf(array, value); if (index >=0) array.splice(index, 1); return value; }, - find:function(array, condition, defaultValue) { + 'find':function(array, condition, defaultValue) { if (!condition) return undefined; - var fn = angular.Function.compile(condition); + var fn = angular['Function']['compile'](condition); _.detect(array, function($){ if (fn($)){ defaultValue = $; @@ -53,10 +53,10 @@ angular.Array = { }); return defaultValue; }, - findById:function(array, id) { + 'findById':function(array, id) { return angular.Array.find(array, function($){return $.$id == id;}, null); }, - filter:function(array, expression) { + 'filter':function(array, expression) { var predicates = []; predicates.check = function(value) { for (var j = 0; j < predicates.length; j++) { @@ -136,16 +136,16 @@ angular.Array = { } return filtered; }, - add:function(array, value) { + 'add':function(array, value) { array.push(_.isUndefined(value)? {} : value); return array; }, - count:function(array, condition) { + 'count':function(array, condition) { if (!condition) return array.length; - var fn = angular.Function.compile(condition); + var fn = angular['Function']['compile'](condition); return _.reduce(array, 0, function(count, $){return count + (fn($)?1:0);}); }, - orderBy:function(array, expression, descend) { + 'orderBy':function(array, expression, descend) { function reverse(comp, descending) { return toBoolean(descending) ? function(a,b){return comp(b,a);} : comp; @@ -169,7 +169,7 @@ angular.Array = { descending = $.charAt(0) == '-'; $ = $.substring(1); } - var get = $ ? angular.Function.compile($) : _.identity; + var get = $ ? angular['Function']['compile']($) : _.identity; return reverse(function(a,b){ return compare(get(a),get(b)); }, descending); @@ -183,7 +183,7 @@ angular.Array = { }; return _.clone(array).sort(reverse(comparator, descend)); }, - orderByToggle:function(predicate, attribute) { + 'orderByToggle':function(predicate, attribute) { var STRIP = /^([+|-])?(.*)/; var ascending = false; var index = -1; @@ -205,7 +205,7 @@ angular.Array = { predicate.unshift((ascending ? "-" : "+") + attribute); return predicate; }, - orderByDirection:function(predicate, attribute, ascend, descend) { + 'orderByDirection':function(predicate, attribute, ascend, descend) { ascend = ascend || 'ng-ascend'; descend = descend || 'ng-descend'; var att = predicate[0] || ''; @@ -218,7 +218,7 @@ angular.Array = { } return att == attribute ? (direction ? ascend : descend) : ""; }, - merge:function(array, index, mergeValue) { + 'merge':function(array, index, mergeValue) { var value = array[index]; if (!value) { value = {}; @@ -228,8 +228,8 @@ angular.Array = { return array; } }; -angular.String = { - quote:function(string) { +angular['String'] = { + 'quote':function(string) { return '"' + string.replace(/\\/g, '\\\\'). replace(/"/g, '\\"'). replace(/\n/g, '\\n'). @@ -239,8 +239,8 @@ angular.String = { replace(/\v/g, '\\v') + '"'; }, - quoteUnicode:function(string) { - var str = angular.String.quote(string); + 'quoteUnicode':function(string) { + var str = angular['String']['quote'](string); var chars = []; for ( var i = 0; i < str.length; i++) { var ch = str.charCodeAt(i); @@ -253,7 +253,7 @@ angular.String = { } return chars.join(''); }, - toDate:function(string){ + '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$/))){ @@ -265,8 +265,8 @@ angular.String = { return string; } }; -angular.Date = { - toString:function(date){ +angular['Date'] = { + 'toString':function(date){ function pad(n) { return n < 10 ? "0" + n : n; } return (date.getUTCFullYear()) + '-' + pad(date.getUTCMonth() + 1) + '-' + @@ -276,8 +276,8 @@ angular.Date = { pad(date.getUTCSeconds()) + 'Z'; } }; -angular.Function = { - compile:function(expression) { +angular['Function'] = { + 'compile':function(expression) { if (_.isFunction(expression)){ return expression; } else if (expression){ @@ -299,20 +299,20 @@ angular.Function = { dst[name] = _[name]; }); }; - extend(angular.Global, {}, + extend(angular['Global'], {}, ['extend', 'clone','isEqual', 'isElement', 'isArray', 'isFunction', 'isUndefined']); - extend(angular.Collection, angular.Global, + 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, + extend(angular['Array'], angular['Collection'], ['first', 'last', 'compact', 'flatten', 'without', 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); - extend(angular.Object, angular.Collection, + extend(angular['Object'], angular['Collection'], ['keys', 'values']); - extend(angular.String, angular.Global); - extend(angular.Function, angular.Global, + 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/DataStore.js b/src/DataStore.js index bdf882a0..6eeabb21 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -92,7 +92,7 @@ DataStore.prototype.save = function(document, callback) { var cachedDoc = self.cache(document); _.each(self._cache.$collections, function(collection){ if (collection.$$accept(document)) { - angular.Array.includeIf(collection, cachedDoc, true); + angular['Array']['includeIf'](collection, cachedDoc, true); } }); if (document.$$anchor) { diff --git a/src/Filters.js b/src/Filters.js index dd4217be..b3f56e75 100644 --- a/src/Filters.js +++ b/src/Filters.js @@ -22,7 +22,7 @@ angular.filter.Meta.get = function(obj, attr){ } }; -angular.filter.currency = function(amount){ +angular.filter['currency'] = function(amount){ jQuery(this.element).toggleClass('ng-format-negative', amount < 0); return '$' + angular.filter.number.apply(this, [amount, 2]); }; diff --git a/src/JSON.js b/src/JSON.js index 84c9a857..238ed489 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -38,7 +38,7 @@ toJsonArray = function(buf, obj, pretty){ buf.push('' + obj); } } else if (type === 'string') { - return buf.push(angular.String.quoteUnicode(obj)); + return buf.push(angular['String']['quoteUnicode'](obj)); } else if (type === 'object') { if (obj instanceof Array) { buf.push("["); @@ -56,7 +56,7 @@ toJsonArray = function(buf, obj, pretty){ } buf.push("]"); } else if (obj instanceof Date) { - buf.push(angular.String.quoteUnicode(angular.Date.toString(obj))); + buf.push(angular['String']['quoteUnicode'](angular['Date']['toString'](obj))); } else { buf.push("{"); if (pretty) buf.push(pretty); @@ -78,7 +78,7 @@ toJsonArray = function(buf, obj, pretty){ buf.push(","); if (pretty) buf.push(pretty); } - buf.push(angular.String.quote(key)); + buf.push(angular['String']['quote'](key)); buf.push(":"); toJsonArray(buf, value, childPretty); comma = true; diff --git a/src/Loader.js b/src/Loader.js index dfaa355a..19840567 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -23,6 +23,8 @@ if (typeof Node == 'undefined') { } var callbacks = {}; +var jQuery = window['jQuery']; +var msie = jQuery['browser']['msie']; if (!window.angular){ angular = {}; window['angular'] = angular; } if (!angular.validator) angular.validator = {}; @@ -32,8 +34,8 @@ if (!window.console) log:function() {}, error:function() {} }; -if (_.isUndefined(alert)) { - alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; +if (!angular.alert) { + angular.alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; } var consoleNode; @@ -169,7 +171,6 @@ Loader.prototype.load = function() { this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); this.loadCss('/stylesheets/css'); console.log("Server: " + this.config.server); - msie = jQuery.browser.msie; this.configureJQueryPlugins(); this.computeConfiguration(); this.bindHtml(); @@ -177,11 +178,7 @@ Loader.prototype.load = function() { 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() { + jQuery['fn']['scope'] = function() { var element = this; while (element && element.get(0)) { var scope = element.data("scope"); @@ -191,7 +188,7 @@ Loader.prototype.configureJQueryPlugins = function() { } return null; }; - jQuery.fn.controller = function() { + jQuery['fn']['controller'] = function() { return this.data('controller') || NullController.instance; }; }; @@ -229,24 +226,19 @@ Loader.prototype.bindHtml = function() { var datastore = new DataStore(post, users, binder.anchor); binder.updateListeners.push(function(){datastore.flush();}); var scope = new Scope( { - $anchor : binder.anchor, - $binder : binder, - $config : this.config, - $console : window.console, - $datastore : datastore, - $save : function(callback) { + '$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 + '$window' : window, + '$uid' : this.uid, + '$users' : users }, "ROOT"); - jQuery.each(["get", "set", "eval", "addWatchListener", "updateView"], - function(i, method){ - angular[method] = bind(scope, scope[method]); - }); - document.data('scope', scope); console.log('$binder.entity()'); binder.entity(scope); @@ -284,7 +276,6 @@ Loader.prototype.bindHtml = function() { watcher.watch(); document.find("body").show(); console.log('ready()'); - }; Loader.prototype.visualPost = function(delegate) { @@ -399,6 +390,12 @@ angular['compile'] = function(root, config) { //todo: don't start watcher var loader = new Loader(root, jQuery("head"), _(defaults).extend(config)); loader.load(); - return jQuery(root).scope(); + var scope = jQuery(root).scope(); + //TODO: cleanup + return { + 'updateView':function(){return scope.updateView.apply(scope, arguments);}, + 'set':function(){return scope.set.apply(scope, arguments);}, + 'get':function(){return scope.get.apply(scope, arguments);} + }; }; diff --git a/src/Parser.js b/src/Parser.js index b23215be..cdece11e 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -185,7 +185,7 @@ Lexer.prototype.readString = function(quote) { this.tokens.push({index:start, text:string, fn:function(){ return (string.length == dateParseLength) ? - angular.String.toDate(string) : string; + angular['String']['toDate'](string) : string; }}); return; } else { @@ -346,11 +346,11 @@ Parser.prototype.filterChain = function(){ }; Parser.prototype.filter = function(){ - return this._pipeFunction(angular.filter); + return this._pipeFunction(angular['filter']); }; Parser.prototype.validator = function(){ - return this._pipeFunction(angular.validator); + return this._pipeFunction(angular['validator']); }; Parser.prototype._pipeFunction = function(fnScope){ @@ -697,7 +697,7 @@ Parser.prototype.entityDecl = function () { self.scope.set(instance, document); return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular.String.quote(instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; diff --git a/src/Scope.js b/src/Scope.js index e3634cee..dff3bfbd 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -55,7 +55,7 @@ Scope.getter = function(instance, path) { instance = instance[key]; } if (_.isUndefined(instance) && key.charAt(0) == '$') { - var type = angular.Global.typeOf(lastInstance); + 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) { diff --git a/src/Widgets.js b/src/Widgets.js index 3a0f2845..4e4facf8 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -5,7 +5,13 @@ WidgetFactory = function(serverUrl, database) { this.nextUploadId = 0; this.serverUrl = serverUrl; this.database = database; - this.createSWF = swfobject.createSWF; + if (window.swfobject) { + this.createSWF = swfobject.createSWF; + } else { + this.createSWF = function(){ + alert("ERROR: swfobject not loaded!"); + }; + } this.onChangeListener = function(){}; }; @@ -145,7 +151,7 @@ FileController.prototype._on_uploadCompleteData = function(data) { 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.view.find("span").text(angular['filter']['bytes'](size)); this.upload(); }; @@ -167,7 +173,7 @@ FileController.prototype.updateView = function(scope) { 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("span").text(angular['filter']['bytes'](this.value.size)); } this.view.find("input").attr('checked', !!modelValue); }; @@ -677,8 +683,8 @@ RepeaterUpdater.prototype.updateView = function(scope) { }); // shrink children for ( var r = childrenLength; r > iteratorLength; --r) { - var unneeded = this.children.pop(); - unneeded.element.removeNode(); + var unneeded = this.children.pop().element[0]; + unneeded.parentNode.removeChild(unneeded); } // Special case for option in select if (child && child.element[0].nodeName === "OPTION") { diff --git a/src/test/Runner.js b/src/test/Runner.js index 5840282e..c6684951 100644 --- a/src/test/Runner.js +++ b/src/test/Runner.js @@ -110,7 +110,7 @@ test.Scenario.prototype = { }, verb:function(step){ var fn = null; - if (!step) fn = function (){ throw "Step is 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]; diff --git a/src/test/_namespace.js b/src/test/_namespace.js index 78f430f1..e29ae72a 100644 --- a/src/test/_namespace.js +++ b/src/test/_namespace.js @@ -1,5 +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 = {}; +if (!angular) var angular = window['angular'] = {}; +if (!angular['test']) var angularTest = angular['test'] = {}; +if (!angular['test']['GIVEN']) angularTest['GIVEN'] = {}; +if (!angular['test']['WHEN']) angularTest['WHEN'] = {}; +if (!angular['test']['THEN']) angularTest['THEN'] = {}; -- cgit v1.2.3 From 1a42a3fab99ca02af0476f5a87175c53104aa2e3 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 11 Jan 2010 16:15:12 -0800 Subject: green --- src/API.js | 77 ++++---- src/Binder.js | 8 +- src/ControlBar.js | 2 +- src/DataStore.js | 18 +- src/Filters.js | 511 +++++++++++++++++++++++++++-------------------------- src/JSON.js | 2 +- src/Loader.js | 491 +++++++++++++++++++++++++------------------------- src/Scope.js | 6 +- src/Server.js | 2 +- src/Validators.js | 152 ++++++++-------- src/Widgets.js | 4 +- src/angular.prefix | 3 +- 12 files changed, 647 insertions(+), 629 deletions(-) (limited to 'src') diff --git a/src/API.js b/src/API.js index 49089da0..d795f4c3 100644 --- a/src/API.js +++ b/src/API.js @@ -1,9 +1,8 @@ -angular['Global'] = { +var angularGlobal = { 'typeOf':function(obj){ + if (obj === null) return "null"; var type = typeof obj; - switch(type) { - case "object": - if (obj === null) return "null"; + if (type == "object") { if (obj instanceof Array) return "array"; if (obj instanceof Date) return "date"; if (obj.nodeType == 1) return "element"; @@ -12,9 +11,9 @@ angular['Global'] = { } }; -angular['Collection'] = {}; -angular['Object'] = {}; -angular['Array'] = { +var angularCollection = {}; +var angularObject = {}; +var angularArray = { 'includeIf':function(array, value, condition) { var index = _.indexOf(array, value); if (condition) { @@ -177,7 +176,7 @@ angular['Array'] = { var comparator = function(o1, o2){ for ( var i = 0; i < expression.length; i++) { var comp = expression[i](o1, o2); - if (comp != 0) return comp; + if (comp !== 0) return comp; } return 0; }; @@ -197,7 +196,7 @@ angular['Array'] = { ascending = $.charAt(0) == '+'; index = i; return true; - }; + } }); if (index >= 0) { predicate.splice(index, 1); @@ -228,7 +227,8 @@ angular['Array'] = { return array; } }; -angular['String'] = { + +var angularString = { 'quote':function(string) { return '"' + string.replace(/\\/g, '\\\\'). replace(/"/g, '\\"'). @@ -265,7 +265,8 @@ angular['String'] = { return string; } }; -angular['Date'] = { + +var angularDate = { 'toString':function(date){ function pad(n) { return n < 10 ? "0" + n : n; } return (date.getUTCFullYear()) + '-' + @@ -276,7 +277,8 @@ angular['Date'] = { pad(date.getUTCSeconds()) + 'Z'; } }; -angular['Function'] = { + +var angularFunction = { 'compile':function(expression) { if (_.isFunction(expression)){ return expression; @@ -292,27 +294,30 @@ angular['Function'] = { } }; -(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 +function defineApi(dst, chain, underscoreNames){ + var lastChain = _.last(chain); + foreach(underscoreNames, function(name){ + lastChain[name] = _[name]; + }); + angular[dst] = angular[dst] || {}; + foreach(chain, function(parent){ + extend(angular[dst], parent); + }); +} +defineApi('Global', [angularGlobal], + ['extend', 'clone','isEqual', + 'isElement', 'isArray', 'isFunction', 'isUndefined']); +defineApi('Collection', [angularGlobal, angularCollection], + ['each', 'map', 'reduce', 'reduceRight', 'detect', + 'select', 'reject', 'all', 'any', 'include', + 'invoke', 'pluck', 'max', 'min', 'sortBy', + 'sortedIndex', 'toArray', 'size']); +defineApi('Array', [angularGlobal, angularCollection, angularArray], + ['first', 'last', 'compact', 'flatten', 'without', + 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); +defineApi('Object', [angularGlobal, angularCollection, angularObject], + ['keys', 'values']); +defineApi('String', [angularGlobal, angularString], []); +defineApi('Date', [angularGlobal, angularDate], []); +defineApi('Function', [angularGlobal, angularCollection, angularFunction], + ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); diff --git a/src/Binder.js b/src/Binder.js index 3589cb88..4c5299ed 100644 --- a/src/Binder.js +++ b/src/Binder.js @@ -57,16 +57,16 @@ Binder.prototype.parseAnchor = function(url) { var anchor = url.substring(anchorIndex + 1); var anchorQuery = this.parseQueryString(anchor); - jQuery.each(self.anchor, function(key, newValue) { + foreach(self.anchor, function(newValue, key) { delete self.anchor[key]; }); - jQuery.each(anchorQuery, function(key, newValue) { + foreach(anchorQuery, function(newValue, key) { self.anchor[key] = newValue; }); }; Binder.prototype.onUrlChange = function (url) { - console.log("URL change detected", url); + log("URL change detected", url); this.parseAnchor(url); this.updateView(); }; @@ -252,7 +252,7 @@ Binder.prototype.precompileNode = function(node, path, factories) { } } - if (!node.getAttribute) console.log(node); + if (!node.getAttribute) log(node); var repeaterExpression = node.getAttribute('ng-repeat'); if (repeaterExpression) { node.removeAttribute('ng-repeat'); diff --git a/src/ControlBar.js b/src/ControlBar.js index b66a1464..fb8147d5 100644 --- a/src/ControlBar.js +++ b/src/ControlBar.js @@ -52,7 +52,7 @@ ControlBar.prototype.doTemplate = function (path) { callbacks["_iframe_notify_" + id] = function() { loginView.dialog("destroy"); loginView.remove(); - jQuery.each(self.callbacks, function(i, callback){ + foreach(self.callbacks, function(callback){ callback(); }); self.callbacks = []; diff --git a/src/DataStore.js b/src/DataStore.js index 6eeabb21..f99e5824 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -41,7 +41,7 @@ DataStore.prototype.loadMany = function(entity, ids, callback) { var self=this; var list = []; var callbackCount = 0; - jQuery.each(ids, function(i, id){ + foreach(ids, function(id){ list.push(self.load(entity(), id, function(){ callbackCount++; if (callbackCount == ids.length) { @@ -50,7 +50,7 @@ DataStore.prototype.loadMany = function(entity, ids, callback) { })); }); return list; -} +}; DataStore.prototype.loadOrCreate = function(instance, id, callback) { var self=this; @@ -134,9 +134,9 @@ DataStore.prototype.flush = function() { var self = this; var bulkRequest = this.bulkRequest; this.bulkRequest = []; - console.log('REQUEST:', bulkRequest); + log('REQUEST:', bulkRequest); function callback(code, bulkResponse){ - console.log('RESPONSE[' + code + ']: ', bulkResponse); + log('RESPONSE[' + code + ']: ', bulkResponse); if(bulkResponse.$status_code == 401) { self.users.login(function(){ self.post(bulkRequest, callback); @@ -147,9 +147,9 @@ DataStore.prototype.flush = function() { 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) { + var responseCode = response.$status_code; + if(responseCode) { + if(responseCode == 403) { self.users.notAuthorized(); } else { request.$$failure(response); @@ -217,7 +217,7 @@ DataStore.prototype.documentCountsByUser = function(){ var counts = {}; var self = this; self.post([["GET", "$users"]], function(code, response){ - jQuery.each(response[0], function(key, value){ + foreach(response[0], function(value, key){ counts[key] = value; }); }); @@ -228,7 +228,7 @@ 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){ + foreach(response[0], function(value, key){ ids[key] = value; }); }); diff --git a/src/Filters.js b/src/Filters.js index b3f56e75..67fcffa1 100644 --- a/src/Filters.js +++ b/src/Filters.js @@ -1,13 +1,13 @@ // Copyright (C) 2009 BRAT Tech LLC -angular.filter.Meta = function(obj){ +angularFilter.Meta = function(obj){ if (obj) { for ( var key in obj) { this[key] = obj[key]; } } }; -angular.filter.Meta.get = function(obj, attr){ +angularFilter.Meta.get = function(obj, attr){ attr = attr || 'text'; switch(typeof obj) { case "string": @@ -22,269 +22,280 @@ angular.filter.Meta.get = function(obj, attr){ } }; -angular.filter['currency'] = function(amount){ - jQuery(this.element).toggleClass('ng-format-negative', amount < 0); - return '$' + angular.filter.number.apply(this, [amount, 2]); -}; +var angularFilterGoogleChartApi; -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'; +foreach({ + 'currency': function(amount){ + jQuery(this.element).toggleClass('ng-format-negative', amount < 0); + return '$' + angularFilter['number'].apply(this, [amount, 2]); + }, + + 'number': function(amount, fractionSize){ + if (isNaN(amount) || !isFinite(amount)) { + return ''; } - text += '.' + frc.substring(0, fractionSize); - } - return text; -}; - -angular.filter.date = function(amount) { -}; - -angular.filter.json = function(object) { - jQuery(this.element).addClass("ng-monospace"); - return toJson(object, true); -}; - -angular.filter.trackPackage = function(trackingNo, noMatch) { - trackingNo = 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: '' + text + '', - trackingNo:trackingNo}); + 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 (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 = '' + text + ''; - 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;"'; + if (fractionSize > 0) { + for (var j = frc.length; j < fractionSize; j++) { + frc += '0'; + } + text += '.' + frc.substring(0, fractionSize); } - return new angular.filter.Meta({url:obj.url, text:obj.url, - html:''}); - } - 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++; + return text; + }, + + 'date': function(amount) { + }, + + 'json': function(object) { + jQuery(this.element).addClass("ng-monospace"); + return toJson(object, true); + }, + + 'trackPackage': (function(){ + var 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]}]; + return function(trackingNo, noMatch) { + trackingNo = trim(trackingNo); + var tNo = trackingNo.replace(/ /g, ''); + var returnValue; + foreach(MATCHERS, function(carrier){ + foreach(carrier.regexp, function(regexp){ + if (regexp.test(tNo)) { + var text = carrier.name + ": " + trackingNo; + var url = carrier.url + trackingNo; + returnValue = new angularFilter.Meta({ + text:text, + url:url, + html: '' + text + '', + trackingNo:trackingNo}); + _.breakLoop(); + } + }); + if (returnValue) _.breakLoop(); }); - 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); + if (returnValue) + return returnValue; + else if (trackingNo) + return noMatch || new angularFilter.Meta({text:trackingNo + " is not recognized"}); + else + return null; + };})(), + + 'link': function(obj, title) { + var text = title || angularFilter.Meta.get(obj); + var url = angularFilter.Meta.get(obj, "url") || angularFilter.Meta.get(obj); + if (url) { + if (angular.validator.email(url) === null) { + url = "mailto:" + url; + } + var html = '' + text + ''; + return new angularFilter.Meta({text:text, url:url, html:html}); } - } - urlParam.sort(); - url += urlParam.join("&"); - return new angular.filter.Meta({url:url, text:value, - html:''}); -}; - -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); + return obj; }, - pie3d:function(data, width, height) { - return angular.filter.googleChartApi('p3', data, width, height); + + + 'bytes': (function(){ + var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; + return 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 + " " + SUFFIX[suffix]; + }; + })(), + + '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 angularFilter.Meta({url:obj.url, text:obj.url, + html:''}); + } + return null; }, - pieConcentric:function(data, width, height) { - return angular.filter.googleChartApi('pc', data, width, height); + + 'lowercase': function (obj) { + var text = angularFilter.Meta.get(obj); + return text ? ("" + text).toLowerCase() : text; }, - barHorizontalStacked:function(data, width, height) { - return angular.filter.googleChartApi('bhs', data, width, height); + + 'uppercase': function (obj) { + var text = angularFilter.Meta.get(obj); + return text ? ("" + text).toUpperCase() : text; }, - barHorizontalGrouped:function(data, width, height) { - return angular.filter.googleChartApi('bhg', data, width, height); + + 'linecount': function (obj) { + var text = angularFilter.Meta.get(obj); + if (text==='' || !text) return 1; + return text.split(/\n|\f/).length; }, - barVerticalStacked:function(data, width, height) { - return angular.filter.googleChartApi('bvs', data, width, height); + + 'if': function (result, expression) { + return expression ? result : undefined; }, - barVerticalGrouped:function(data, width, height) { - return angular.filter.googleChartApi('bvg', data, width, height); + + 'unless': function (result, expression) { + return expression ? undefined : result; }, - line:function(data, width, height) { - return angular.filter.googleChartApi('lc', data, width, height); + + 'googleChartApi': extend( + function(type, data, width, height) { + data = data || {}; + var chart = { + cht:type, + chco:angularFilterGoogleChartApi.collect(data, 'color'), + chtt:angularFilterGoogleChartApi.title(data), + chdl:angularFilterGoogleChartApi.collect(data, 'label'), + chd:angularFilterGoogleChartApi.values(data), + chf:'bg,s,FFFFFF00' + }; + if (_.isArray(data.xLabels)) { + chart.chxt='x'; + chart.chxl='0:|' + data.xLabels.join('|'); + } + return angularFilterGoogleChartApi['encode'](chart, width, height); + }, + { + 'values': function(data){ + var seriesValues = []; + foreach(data.series||[], function(serie){ + var values = []; + foreach(serie.values||[], function(value){ + values.push(value); + }); + seriesValues.push(values.join(',')); + }); + var values = seriesValues.join('|'); + return values === "" ? null : "t:" + values; + }, + + 'title': function(data){ + var titles = []; + var title = data.title || []; + foreach(_.isArray(title)?title:[title], function(text){ + titles.push(encodeURIComponent(text)); + }); + return titles.join('|'); + }, + + 'collect': function(data, key){ + var outterValues = []; + var count = 0; + foreach(data.series||[], function(serie){ + var innerValues = []; + var value = serie[key] || []; + foreach(_.isArray(value)?value:[value], function(color){ + innerValues.push(encodeURIComponent(color)); + count++; + }); + outterValues.push(innerValues.join('|')); + }); + return count?outterValues.join(','):null; + }, + + '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; + foreach(params, function(value, key){ + if (value) { + urlParam.push(key + "=" + value); + } + }); + urlParam.sort(); + url += urlParam.join("&"); + return new angularFilter.Meta({url:url, + html:''}); + } + } + ), + + + 'qrcode': function(value, width, height) { + return angularFilterGoogleChartApi['encode']({cht:'qr', chl:encodeURIComponent(value)}, width, height); }, - sparkline:function(data, width, height) { - return angular.filter.googleChartApi('ls', data, width, height); + 'chart': { + pie:function(data, width, height) { + return angularFilterGoogleChartApi('p', data, width, height); + }, + pie3d:function(data, width, height) { + return angularFilterGoogleChartApi('p3', data, width, height); + }, + pieConcentric:function(data, width, height) { + return angularFilterGoogleChartApi('pc', data, width, height); + }, + barHorizontalStacked:function(data, width, height) { + return angularFilterGoogleChartApi('bhs', data, width, height); + }, + barHorizontalGrouped:function(data, width, height) { + return angularFilterGoogleChartApi('bhg', data, width, height); + }, + barVerticalStacked:function(data, width, height) { + return angularFilterGoogleChartApi('bvs', data, width, height); + }, + barVerticalGrouped:function(data, width, height) { + return angularFilterGoogleChartApi('bvg', data, width, height); + }, + line:function(data, width, height) { + return angularFilterGoogleChartApi('lc', data, width, height); + }, + sparkline:function(data, width, height) { + return angularFilterGoogleChartApi('ls', data, width, height); + }, + scatter:function(data, width, height) { + return angularFilterGoogleChartApi('s', data, width, height); + } }, - scatter:function(data, width, height) { - return angular.filter.googleChartApi('s', data, width, height); + + 'html': function(html){ + return new angularFilter.Meta({html:html}); } -}; +}, function(v,k){angularFilter[k] = v;}); -angular.filter.html = function(html){ - return new angular.filter.Meta({html:html}); -}; +angularFilterGoogleChartApi = angularFilter['googleChartApi']; diff --git a/src/JSON.js b/src/JSON.js index 238ed489..14fce1fb 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -17,7 +17,7 @@ fromJson = function(json) { parser.assertAllConsumed(); return expression(); } catch (e) { - console.error("fromJson error: ", json, e); + error("fromJson error: ", json, e); throw e; } }; diff --git a/src/Loader.js b/src/Loader.js index 19840567..104dfec8 100644 --- a/src/Loader.js +++ b/src/Loader.js @@ -3,8 +3,7 @@ // IE compatibility if (typeof document.getAttribute == 'undefined') - document.getAttribute = function() { - }; + document.getAttribute = function() {}; if (typeof Node == 'undefined') { Node = { ELEMENT_NODE : 1, @@ -22,25 +21,26 @@ if (typeof Node == 'undefined') { }; } -var callbacks = {}; -var jQuery = window['jQuery']; -var msie = jQuery['browser']['msie']; - -if (!window.angular){ angular = {}; window['angular'] = angular; } -if (!angular.validator) angular.validator = {}; -if (!angular.filter) angular.filter = {}; -if (!window.console) - window.console = { - log:function() {}, - error:function() {} - }; -if (!angular.alert) { - angular.alert = function(){console.log(arguments); window.alert.apply(window, arguments); }; -} - -var consoleNode; - -consoleLog = function(level, objs) { +function noop() {} +if (!window['console']) window['console']={'log':noop, 'error':noop}; + +var consoleNode, + foreach = _.each, + extend = _.extend, + jQuery = window['jQuery'], + msie = jQuery['browser']['msie'], + log = function(){window['console']['log'].apply(this, arguments);}, + error = function(){window['console']['error'].apply(this, arguments);}, + angular = window['angular'] || (window['angular'] = {}), + angularValidator = angular['validator'] || (angular['validator'] = {}), + angularFilter = angular['filter'] || (angular['filter'] = {}), + angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), + angularAlert = angular['alert'] || (angular['alert'] = function(){ + log(arguments); window.alert.apply(window, arguments); + }); + + +function consoleLog(level, objs) { var log = document.createElement("div"); log.className = level; var msg = ""; @@ -52,17 +52,17 @@ consoleLog = function(level, objs) { } log.appendChild(document.createTextNode(msg)); consoleNode.appendChild(log); -}; +} -isNode = function(inp) { +function isNode(inp) { return inp && inp.tagName && inp.nodeName && inp.ownerDocument && inp.removeAttribute; -}; +} -isLeafNode = function(node) { +function isLeafNode (node) { switch (node.nodeName) { case "OPTION": case "PRE": @@ -71,11 +71,9 @@ isLeafNode = function(node) { default: return false; } -}; +} -noop = function() { -}; -setHtml = function(node, html) { +function setHtml(node, html) { if (isLeafNode(node)) { if (msie) { node.innerText = html; @@ -85,25 +83,25 @@ setHtml = function(node, html) { } else { node.innerHTML = html; } -}; +} -escapeHtml = function(html) { +function escapeHtml(html) { if (!html || !html.replace) return html; return html. replace(/&/g, '&'). replace(//g, '>'); -}; +} -escapeAttr = function(html) { +function escapeAttr(html) { if (!html || !html.replace) return html; return html.replace(//g, '>').replace(/\"/g, '"'); -}; +} -bind = function(_this, _function) { +function bind(_this, _function) { if (!_this) throw "Missing this"; if (!_.isFunction(_function)) @@ -111,9 +109,9 @@ bind = function(_this, _function) { return function() { return _function.apply(_this, arguments); }; -}; +} -shiftBind = function(_this, _function) { +function shiftBind(_this, _function) { return function() { var args = [ this ]; for ( var i = 0; i < arguments.length; i++) { @@ -121,28 +119,28 @@ shiftBind = function(_this, _function) { } return _function.apply(_this, args); }; -}; +} -outerHTML = function(node) { +function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); var outerHTML = temp.innerHTML; temp.removeChild(node); return outerHTML; -}; +} -trim = function(str) { +function trim(str) { return str.replace(/^ */, '').replace(/ *$/, ''); -}; +} -toBoolean = function(value) { +function toBoolean(value) { var v = ("" + value).toLowerCase(); if (v == 'f' || v == '0' || v == 'false' || v == 'no') value = false; return !!value; -}; +} -merge = function(src, dst) { +function merge(src, dst) { for ( var key in src) { var value = dst[key]; var type = typeof value; @@ -153,182 +151,184 @@ merge = function(src, dst) { merge(src[key], value); } } -}; +} // //////////////////////////// // Loader // //////////////////////////// -Loader = function(document, head, config) { +function Loader(document, head, config) { this.document = jQuery(document); this.head = jQuery(head); this.config = config; this.location = window.location; -}; - -Loader.prototype.load = function() { - this.configureLogging(); - this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); - this.loadCss('/stylesheets/css'); - console.log("Server: " + this.config.server); - this.configureJQueryPlugins(); - this.computeConfiguration(); - this.bindHtml(); -}; - -Loader.prototype.configureJQueryPlugins = function() { - console.log('Loader.configureJQueryPlugins()'); - 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') || NullController.instance; - }; -}; - -Loader.prototype.uid = function() { - return "" + new Date().getTime(); -}; - -Loader.prototype.computeConfiguration = function() { - var config = this.config; - if (!config.database) { - var match = config.server.match(/https?:\/\/([\w]*)/); - config.database = match ? match[1] : "$MEMORY"; - } -}; - -Loader.prototype.bindHtml = function() { - console.log('Loader.bindHtml()'); - var watcher = new UrlWatcher(this.location); - var document = this.document; - var widgetFactory = new WidgetFactory(this.config.server, this.config.database); - var binder = new Binder(document[0], widgetFactory, watcher, this.config); - widgetFactory.onChangeListener = shiftBind(binder, binder.updateModel); - var controlBar = new ControlBar(document.find('body'), this.config.server); - var onUpdate = function(){binder.updateView();}; - var server = this.config.database=="$MEMORY" ? - new FrameServer(this.window) : - new Server(this.config.server, jQuery.getScript); - server = new VisualServer(server, new Status(jQuery(document.body)), onUpdate); - var users = new Users(server, controlBar); - var databasePath = '/data/' + this.config.database; - var post = function(request, callback){ - server.request("POST", databasePath, request, callback); - }; - var datastore = new DataStore(post, users, binder.anchor); - binder.updateListeners.push(function(){datastore.flush();}); - var scope = new 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"); - - 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 PopUp(document).bind(); - - console.log('$binder.parseAnchor()'); - binder.parseAnchor(); - - console.log('$binder.executeInit()'); - binder.executeInit(); - - console.log('$binder.updateView()'); - binder.updateView(); - - watcher.listener = bind(binder, binder.onUrlChange, watcher); - watcher.onUpdate = function(){alert("update");}; - watcher.watch(); - document.find("body").show(); - console.log('ready()'); -}; +} -Loader.prototype.visualPost = function(delegate) { - var status = new Status(jQuery(document.body)); - return function(request, delegateCallback) { - status.beginRequest(request); - var callback = function() { - status.endRequest(); - try { - delegateCallback.apply(this, arguments); - } catch (e) { - alert(toJson(e)); +Loader.prototype = { + load: function() { + this.configureLogging(); + this.loadCss('/stylesheets/jquery-ui/smoothness/jquery-ui-1.7.1.css'); + this.loadCss('/stylesheets/css'); + log("Server: " + this.config.server); + this.configureJQueryPlugins(); + this.computeConfiguration(); + this.bindHtml(); + }, + + configureJQueryPlugins: function() { + log('Loader.configureJQueryPlugins()'); + 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; }; - delegate(request, callback); - }; -}; - -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') { - consoleNode = document.createElement("div"); - consoleNode.id = 'ng-console'; - document.getElementsByTagName('body')[0].appendChild(consoleNode); - console.log = function() { - consoleLog('ng-console-info', arguments); + jQuery['fn']['controller'] = function() { + return this.data('controller') || NullController.instance; + }; + }, + + uid: function() { + return "" + new Date().getTime(); + }, + + computeConfiguration: function() { + var config = this.config; + if (!config.database) { + var match = config.server.match(/https?:\/\/([\w]*)/); + config.database = match ? match[1] : "$MEMORY"; + } + }, + + bindHtml: function() { + log('Loader.bindHtml()'); + var watcher = new UrlWatcher(this.location); + var document = this.document; + var widgetFactory = new WidgetFactory(this.config.server, this.config.database); + var binder = new Binder(document[0], widgetFactory, watcher, this.config); + widgetFactory.onChangeListener = shiftBind(binder, binder.updateModel); + var controlBar = new ControlBar(document.find('body'), this.config.server); + var onUpdate = function(){binder.updateView();}; + var server = this.config.database=="$MEMORY" ? + new FrameServer(this.window) : + new Server(this.config.server, jQuery.getScript); + server = new VisualServer(server, new Status(jQuery(document.body)), onUpdate); + var users = new Users(server, controlBar); + var databasePath = '/data/' + this.config.database; + var post = function(request, callback){ + server.request("POST", databasePath, request, callback); + }; + var datastore = new DataStore(post, users, binder.anchor); + binder.updateListeners.push(function(){datastore.flush();}); + var scope = new 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"); + + document.data('scope', scope); + log('$binder.entity()'); + binder.entity(scope); + + log('$binder.compile()'); + binder.compile(); + + log('ControlBar.bind()'); + controlBar.bind(); + + log('$users.fetchCurrentUser()'); + function fetchCurrentUser() { + users.fetchCurrentUser(function(u) { + if (!u && document.find("[ng-auth=eager]").length) { + users.login(); + } + }); + } + fetchCurrentUser(); + + log('PopUp.bind()'); + new PopUp(document).bind(); + + log('$binder.parseAnchor()'); + binder.parseAnchor(); + + log('$binder.executeInit()'); + binder.executeInit(); + + log('$binder.updateView()'); + binder.updateView(); + + watcher.listener = bind(binder, binder.onUrlChange, watcher); + watcher.onUpdate = function(){alert("update");}; + watcher.watch(); + document.find("body").show(); + log('ready()'); + }, + + visualPost: function(delegate) { + var status = new Status(jQuery(document.body)); + return function(request, delegateCallback) { + status.beginRequest(request); + var callback = function() { + status.endRequest(); + try { + delegateCallback.apply(this, arguments); + } catch (e) { + alert(toJson(e)); + } + }; + delegate(request, callback); }; - console.error = function() { - consoleLog('ng-console-error', arguments); + }, + + 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') { + consoleNode = document.createElement("div"); + consoleNode.id = 'ng-console'; + document.getElementsByTagName('body')[0].appendChild(consoleNode); + log = function() { + consoleLog('ng-console-info', arguments); + }; + console.error = function() { + consoleLog('ng-console-error', arguments); + }; + } + }, + + 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); } }; -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); -}; - -UrlWatcher = function(location) { +function UrlWatcher(location) { this.location = location; this.delay = 25; this.setTimeout = function(fn, delay) { @@ -338,49 +338,51 @@ UrlWatcher = function(location) { return url; }; this.expectedUrl = location.href; -}; +} -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 = callbacks[id]; - delete callbacks[id]; - try { - (notifyFn||noop)(); - } catch (e) { - alert(e); +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 = angularCallbacks[id]; + delete angularCallbacks[id]; + try { + (notifyFn||noop)(); + } catch (e) { + alert(e); + } + } else { + self.listener(self.location.href); + self.expectedUrl = self.location.href; } - } else { - self.listener(self.location.href); - self.expectedUrl = self.location.href; } - } - self.setTimeout(pull, self.delay); - }; - pull(); -}; - -UrlWatcher.prototype.setUrl = function(url) { - var existingURL = window.location.href; - if (!existingURL.match(/#/)) - existingURL += '#'; - if (existingURL != url) - window.location.href = url; - this.existingURL = url; -}; - -UrlWatcher.prototype.getUrl = function() { - return window.location.href; + self.setTimeout(pull, self.delay); + }; + pull(); + }, + + setUrl: function(url) { + var existingURL = window.location.href; + if (!existingURL.match(/#/)) + existingURL += '#'; + if (existingURL != url) + window.location.href = url; + this.existingURL = url; + }, + + getUrl: function() { + return window.location.href; + } }; - + angular['compile'] = function(root, config) { config = config || {}; var defaults = { @@ -397,5 +399,4 @@ angular['compile'] = function(root, config) { 'set':function(){return scope.set.apply(scope, arguments);}, 'get':function(){return scope.get.apply(scope, arguments);} }; -}; - +}; \ No newline at end of file diff --git a/src/Scope.js b/src/Scope.js index dff3bfbd..f4b34c3c 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -124,7 +124,7 @@ Scope.prototype.evalWidget = function(widget, expression, context, onSuccess, on } return true; } catch (e){ - console.error('Eval Widget Error:', e); + error('Eval Widget Error:', e); var jsonError = toJson(e, true); widget.hasError = true; jQuery(widget.view). @@ -184,10 +184,10 @@ Scope.prototype.addWatchListener = function(watchExpression, listener) { Scope.prototype.fireWatchers = function() { var self = this; var fired = false; - jQuery.each(this.watchListeners, function(name, watcher) { + foreach(this.watchListeners, function(watcher) { var value = self.eval(watcher.expression); if (value !== watcher.lastValue) { - jQuery.each(watcher.listeners, function(i, listener){ + foreach(watcher.listeners, function(listener){ listener(value, watcher.lastValue); fired = true; }); diff --git a/src/Server.js b/src/Server.js index d00f893b..8f682038 100644 --- a/src/Server.js +++ b/src/Server.js @@ -14,7 +14,7 @@ Server.prototype.base64url = function(txt) { Server.prototype.request = function(method, url, request, callback) { var requestId = this.uuid + (this.nextId++); - callbacks[requestId] = function(response) { + angularCallbacks[requestId] = function(response) { delete angular[requestId]; callback(200, response); }; diff --git a/src/Validators.js b/src/Validators.js index 7cfaa2b4..eb8cff59 100644 --- a/src/Validators.js +++ b/src/Validators.js @@ -1,80 +1,82 @@ // 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 + "."; +foreach({ + 'regexp': function(value, regexp, msg) { + if (!value.match(regexp)) { + return msg || + "Value does not match expected format " + regexp + "."; + } else { + return null; } - if (typeof min != 'undefined' && num > max) { - return "Value can not be greater than " + max + "."; + }, + + '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."; + } + }, + + 'integer': function(value, min, max) { + var number = angularValidator['number'](value, min, max); + if (number === null && value != Math.round(value)) { + return "Value is not a whole number."; + } + return number; + }, + + '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)."; + }, + + '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."; + }, + + '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."; + }, + + '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."; + }, + + '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."; + }, + + 'json': function(value) { + try { + fromJson(value); + return null; + } catch (e) { + return e.toString(); } - 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 { - fromJson(value); - return null; - } catch (e) { - return e.toString(); } -}; +}, function(v,k) {angularValidator[k] = v;}); diff --git a/src/Widgets.js b/src/Widgets.js index 4e4facf8..5dcb84c4 100644 --- a/src/Widgets.js +++ b/src/Widgets.js @@ -525,7 +525,7 @@ BindAttrUpdater.prototype.updateView = function(scope) { attrValues.push(value); } catch (e) { this.hasError = true; - console.error('BindAttrUpdater', e); + error('BindAttrUpdater', e); var jsonError = toJson(e, true); attrValues.push('[' + jsonError + ']'); jNode. @@ -657,7 +657,7 @@ RepeaterUpdater.prototype.updateView = function(scope) { var keyExp = this.keyExp; var valueExp = this.valueExp; var i = 0; - jQuery.each(iterator, function(key, value){ + foreach(iterator, function(value, key){ if (i < childrenLength) { // reuse children child = self.children[i]; diff --git a/src/angular.prefix b/src/angular.prefix index 522c17bf..dbd4959a 100644 --- a/src/angular.prefix +++ b/src/angular.prefix @@ -1,2 +1 @@ - -(function(window, document){ \ No newline at end of file +(function(window, document){ -- cgit v1.2.3 From 6d5471c9bea9671dec7900a7bc548aea8ddd5a3e Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 11 Jan 2010 17:32:33 -0800 Subject: all files converted to prototype= {} --- src/Binder.js | 595 ++++++++++----------- src/ControlBar.js | 106 ++-- src/DataStore.js | 604 +++++++++++----------- src/Filters.js | 2 - src/JSON.js | 10 +- src/Loader.js | 4 - src/Model.js | 82 +-- src/Parser.js | 1292 +++++++++++++++++++++++----------------------- src/Scope.js | 295 ++++++----- src/Server.js | 44 +- src/Users.js | 11 +- src/Validators.js | 2 - src/Widgets.js | 972 +++++++++++++++++----------------- src/angular-bootstrap.js | 25 +- src/angular.prefix | 23 + 15 files changed, 2068 insertions(+), 1999 deletions(-) (limited to 'src') diff --git a/src/Binder.js b/src/Binder.js index 4c5299ed..36cb6ec3 100644 --- a/src/Binder.js +++ b/src/Binder.js @@ -1,12 +1,11 @@ -// Copyright (C) 2009 BRAT Tech LLC -Binder = function(doc, widgetFactory, urlWatcher, config) { +function Binder(doc, widgetFactory, urlWatcher, config) { this.doc = doc; this.urlWatcher = urlWatcher; this.anchor = {}; this.widgetFactory = widgetFactory; this.config = config || {}; this.updateListeners = []; -}; +} Binder.parseBindings = function(string) { var results = []; @@ -39,312 +38,314 @@ Binder.binding = function(string) { }; -Binder.prototype.parseQueryString = function(query) { - var params = {}; - query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, - function (match, left, right) { - if (left) params[decodeURIComponent(left)] = decodeURIComponent(right); - }); - return params; -}; - -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); - foreach(self.anchor, function(newValue, key) { - delete self.anchor[key]; - }); - foreach(anchorQuery, function(newValue, key) { - self.anchor[key] = newValue; - }); -}; - -Binder.prototype.onUrlChange = function (url) { - log("URL change detected", url); - this.parseAnchor(url); - this.updateView(); -}; - -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; -}; - -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();}); -}; - -Binder.prototype.docFindWithSelf = function(exp){ - var doc = jQuery(this.doc); - var selection = doc.find(exp); - if (doc.is(exp)){ - selection = selection.andSelf(); - } - return selection; -}; - -Binder.prototype.executeInit = function() { - this.docFindWithSelf("[ng-init]").each(function() { - var jThis = jQuery(this); - var scope = jThis.scope(); - try { - scope.eval(jThis.attr('ng-init')); - } catch (e) { - alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); +Binder.prototype = { + parseQueryString: function(query) { + var params = {}; + query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, + function (match, left, right) { + if (left) params[decodeURIComponent(left)] = decodeURIComponent(right); + }); + return params; + }, + + 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); + foreach(self.anchor, function(newValue, key) { + delete self.anchor[key]; + }); + foreach(anchorQuery, function(newValue, key) { + self.anchor[key] = newValue; + }); + }, + + onUrlChange: function (url) { + log("URL change detected", url); + this.parseAnchor(url); + this.updateView(); + }, + + 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 = '&'; + } } - }); -}; - -Binder.prototype.entity = function (scope) { - this.docFindWithSelf("[ng-entity]").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) { - alert(e); + this.urlWatcher.setUrl(url); + return url; + }, + + 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();}); + }, + + docFindWithSelf: function(exp){ + var doc = jQuery(this.doc); + var selection = doc.find(exp); + if (doc.is(exp)){ + selection = selection.andSelf(); } - }); -}; - -Binder.prototype.compile = function() { - var jNode = jQuery(this.doc); - var self = this; - if (this.config.autoSubmit) { - var submits = this.docFindWithSelf(":submit").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(), ""); - this.docFindWithSelf("a[ng-action]").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', toJson(e, true)); + return selection; + }, + + executeInit: function() { + this.docFindWithSelf("[ng-init]").each(function() { + var jThis = jQuery(this); + var scope = jThis.scope(); + try { + scope.eval(jThis.attr('ng-init')); + } catch (e) { + alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); + } + }); + }, + + entity: function (scope) { + this.docFindWithSelf("[ng-entity]").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) { + alert(e); + } + }); + }, + + compile: function() { + var jNode = jQuery(this.doc); + var self = this; + if (this.config.autoSubmit) { + var submits = this.docFindWithSelf(":submit").not("[ng-action]"); + submits.attr("ng-action", "$save()"); + submits.not(":disabled").not("ng-bind-attr").attr("ng-bind-attr", '{disabled:"{{$invalidWidgets}}"}'); } - self.updateView(); - return false; - }); -}; - -Binder.prototype.translateBinding = function(node, parentPath, factories) { - var path = parentPath.concat(); - var offset = path.pop(); - var parts = Binder.parseBindings(node.nodeValue); - if (parts.length > 1 || Binder.binding(parts[0])) { - var parent = node.parentNode; - if (isLeafNode(parent)) { - parent.setAttribute('ng-bind-template', node.nodeValue); - factories.push({path:path, fn:function(node, scope, prefix) { - return new BindUpdater(node, node.getAttribute('ng-bind-template')); - }}); - } else { - for (var i = 0; i < parts.length; i++) { - var part = parts[i]; - var binding = 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:Binder.prototype.ng_bind}); + this.precompile(this.doc)(this.doc, jNode.scope(), ""); + this.docFindWithSelf("a[ng-action]").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', toJson(e, true)); + } + self.updateView(); + return false; + }); + }, + + translateBinding: function(node, parentPath, factories) { + var path = parentPath.concat(); + var offset = path.pop(); + var parts = Binder.parseBindings(node.nodeValue); + if (parts.length > 1 || Binder.binding(parts[0])) { + var parent = node.parentNode; + if (isLeafNode(parent)) { + parent.setAttribute('ng-bind-template', node.nodeValue); + factories.push({path:path, fn:function(node, scope, prefix) { + return new BindUpdater(node, node.getAttribute('ng-bind-template')); + }}); + } else { + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + var binding = 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:this.ng_bind}); + } + } else if (msie && part.charAt(0) == ' ') { + newNode = document.createElement("span"); + newNode.innerHTML = ' ' + part.substring(1); + } else { + newNode = document.createTextNode(part); } - } else if (msie && part.charAt(0) == ' ') { - newNode = document.createElement("span"); - newNode.innerHTML = ' ' + part.substring(1); - } else { - newNode = document.createTextNode(part); + parent.insertBefore(newNode, node); } - parent.insertBefore(newNode, node); } + parent.removeChild(node); } - parent.removeChild(node); - } -}; - -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]]; + }, + + 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) { + alert(e); + } } - try { - scope.addWidget(factory.fn(node, scope, prefix)); - } catch (e) { - alert(e); + }; + }, + + 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 ? 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 = msie && attrName == 'href' ? + decodeURI(node.getAttribute(attrName, 2)) : attr.value; + if (Binder.hasBinding(attrValue)) { + bindings[attrName] = attrValue; + } + } + var json = toJson(bindings); + if (json.length > 2) { + node.setAttribute("ng-bind-attr", json); } } - }; -}; - -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 ? 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 = msie && attrName == 'href' ? - decodeURI(node.getAttribute(attrName, 2)) : attr.value; - if (Binder.hasBinding(attrValue)) { - bindings[attrName] = attrValue; + + if (!node.getAttribute) 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); + function template(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 RepeaterUpdater(jQuery(node), repeaterExpression, template, prefix); + }}); + return; } - var json = toJson(bindings); - if (json.length > 2) { - node.setAttribute("ng-bind-attr", json); + + 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 (!node.getAttribute) 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 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('').append(jQuery(node).clone()).html(); + if (!html.match(/.*<\/\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); + } + }, + + ng_eval: function(node) { + return new EvalUpdater(node, node.getAttribute('ng-eval')); + }, + + ng_bind: function(node) { + return new BindUpdater(node, "{{" + node.getAttribute('ng-bind') + "}}"); + }, + + ng_bind_attr: function(node) { + return new BindAttrUpdater(node, fromJson(node.getAttribute('ng-bind-attr'))); + }, + + ng_hide: function(node) { + return new HideUpdater(node, node.getAttribute('ng-hide')); + }, + + ng_show: function(node) { + return new ShowUpdater(node, node.getAttribute('ng-show')); + }, + + ng_class: function(node) { + return new ClassUpdater(node, node.getAttribute('ng-class')); + }, + + ng_class_even: function(node) { + return new ClassEvenUpdater(node, node.getAttribute('ng-class-even')); + }, + + ng_class_odd: function(node) { + return new ClassOddUpdater(node, node.getAttribute('ng-class-odd')); + }, + + ng_style: function(node) { + return new StyleUpdater(node, node.getAttribute('ng-style')); + }, + + ng_watch: function(node, scope) { + scope.watch(node.getAttribute('ng-watch')); } - - var children = node.childNodes; - for (var k = 0; k < children.length; k++) { - this.precompileNode(children[k], path.concat(k), factories); - } -}; - -Binder.prototype.ng_eval = function(node) { - return new EvalUpdater(node, node.getAttribute('ng-eval')); -}; - -Binder.prototype.ng_bind = function(node) { - return new BindUpdater(node, "{{" + node.getAttribute('ng-bind') + "}}"); -}; - -Binder.prototype.ng_bind_attr = function(node) { - return new BindAttrUpdater(node, fromJson(node.getAttribute('ng-bind-attr'))); -}; - -Binder.prototype.ng_hide = function(node) { - return new HideUpdater(node, node.getAttribute('ng-hide')); -}; - -Binder.prototype.ng_show = function(node) { - return new ShowUpdater(node, node.getAttribute('ng-show')); -}; - -Binder.prototype.ng_class = function(node) { - return new ClassUpdater(node, node.getAttribute('ng-class')); -}; - -Binder.prototype.ng_class_even = function(node) { - return new ClassEvenUpdater(node, node.getAttribute('ng-class-even')); -}; - -Binder.prototype.ng_class_odd = function(node) { - return new ClassOddUpdater(node, node.getAttribute('ng-class-odd')); -}; - -Binder.prototype.ng_style = function(node) { - return new StyleUpdater(node, node.getAttribute('ng-style')); -}; - -Binder.prototype.ng_watch = function(node, scope) { - scope.watch(node.getAttribute('ng-watch')); -}; +}; \ No newline at end of file diff --git a/src/ControlBar.js b/src/ControlBar.js index fb8147d5..53c87199 100644 --- a/src/ControlBar.js +++ b/src/ControlBar.js @@ -1,15 +1,10 @@ -// Copyright (C) 2008,2009 BRAT Tech LLC - -ControlBar = function (document, serverUrl) { +function ControlBar(document, serverUrl) { this.document = document; this.serverUrl = serverUrl; this.window = window; this.callbacks = []; }; -ControlBar.prototype.bind = function () { -}; - ControlBar.HTML = '
' + '
' + @@ -18,54 +13,61 @@ ControlBar.HTML = '
' + '
'; -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())); - } -}; - -ControlBar.prototype.logout = function (loginSubmitFn) { - this.callbacks.push(loginSubmitFn); - if (this.callbacks.length == 1) { - this.doTemplate("/user_session/do_destroy.mini"); - } -}; - -ControlBar.prototype.urlWithoutAnchor = function (path) { - return this.window.location.href.split("#")[0]; -}; - -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('
' + + '
'); + 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 scenario.Runner(this.console, this.testFrame); + _.stepper(this.scenarios, function(next, scenarioObj, name){ + new scenario.Scenario(name, scenarioObj).run(runner, next); + }, function(){ + } + ); + } +}; + +scenario.Runner = function(console, frame){ + this.console = console; + this.current = null; + this.tests = []; + this.frame = frame; +}; +scenario.Runner.prototype = { + start:function(name){ + var current = this.current = { + name:name, + start:new Date().getTime(), + scenario:jQuery('
') + }; + current.run = current.scenario.append( + '
' + + '.' + + '.' + + '.' + + '').find(".run"); + current.log = current.scenario.append('
').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('').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:toJson(arg)); + } + var log = jQuery('
'); + log.text(buf.join(" ")); + this.current.log.append(log); + this.console.scrollTop(this.console[0].scrollHeight); + if (level == "error") + this.current.error = buf.join(" "); + } +}; + +scenario.Scenario = function(name, scenario){ + this.name = name; + this.scenario = scenario; +}; +scenario.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 = scenario.GIVEN[step.Given]; + else if (step.When) fn = scenario.WHEN[step.When]; + else if (step.Then) fn = scenario.THEN[step.Then]; + return fn || function (){ + throw "ERROR: Need Given/When/Then got: " + 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", 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: " + toJson(e)); + } + } +}; diff --git a/src/scenario/Steps.js b/src/scenario/Steps.js new file mode 100644 index 00000000..f8ac173f --- /dev/null +++ b/src/scenario/Steps.js @@ -0,0 +1,57 @@ +angular.scenario.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:" + toJson({dataset:this.dataset}); + } +}; +angular.scenario.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.scenario.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/scenario/_namespace.js b/src/scenario/_namespace.js new file mode 100644 index 00000000..7da3a5d8 --- /dev/null +++ b/src/scenario/_namespace.js @@ -0,0 +1,6 @@ +if (!angular) var angular = window['angular'] = {}; +if (!angular['scenario']) var angularScenario = angular['scenario'] = {}; +if (!angular['scenarioDef']) var scenarioDef = angular['scenarioDef'] = {}; +if (!angular['scenario']['GIVEN']) angularScenario['GIVEN'] = {}; +if (!angular['scenario']['WHEN']) angularScenario['WHEN'] = {}; +if (!angular['scenario']['THEN']) angularScenario['THEN'] = {}; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js new file mode 100644 index 00000000..1d40b9d0 --- /dev/null +++ b/src/scenario/bootstrap.js @@ -0,0 +1,47 @@ +(function(onLoadDelegate){ + var prefix = (function(){ + var filename = /(.*\/)bootstrap.js(#(.*))?/; + var scripts = document.getElementsByTagName("script"); + for(var j = 0; j < scripts.length; j++) { + var src = scripts[j].src; + if (src && src.match(filename)) { + var parts = src.match(filename); + return parts[1]; + } + } + })(); + function addScript(path) { + document.write(''); + }; + function addCSS(path) { + document.write(''); + }; + window.onload = function(){ + if (!_.stepper) { + _.stepper = function(collection, iterator, done){ + var keys = _.keys(collection); + function next() { + if (keys.length) { + var key = keys.shift(); + iterator(next, collection[key], key); + } else { + (done||_.identity)(); + } + } + next(); + }; + } + _.defer(function(){ + new angular.scenario.SuiteRunner(angular.scenarioDef, jQuery(document.body)).run(); + }); + (onLoadDelegate||function(){})(); + }; + addCSS("../../css/angular-scenario.css"); + addScript("../../lib/underscore/underscore.js"); + addScript("../../lib/jquery/jquery-1.3.2.js"); + addScript("../angular-bootstrap.js"); + addScript("_namespace.js"); + addScript("Steps.js"); + addScript("Runner.js"); +})(window.onload); + diff --git a/src/test/Runner.js b/src/test/Runner.js deleted file mode 100644 index c6684951..00000000 --- a/src/test/Runner.js +++ /dev/null @@ -1,162 +0,0 @@ -if (typeof test == 'undefined') test = {}; - -test.ScenarioRunner = function(scenarios, body) { - this.scenarios = scenarios; - this.body = body; -}; - -test.ScenarioRunner.prototype = { - run:function(){ - this.setUpUI(); - this.runScenarios(); - }, - setUpUI:function(){ - this.body.html( - '
' + - '
' + - '
' + - '
' + - '' + - '
'); - 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 test.Runner(this.console, this.testFrame); - _.stepper(this.scenarios, function(next, scenario, name){ - new test.Scenario(name, scenario).run(runner, next); - }, function(){ - } - ); - } -}; - -test.Runner = function(console, frame){ - this.console = console; - this.current = null; - this.tests = []; - this.frame = frame; -}; -test.Runner.prototype = { - start:function(name){ - var current = this.current = { - name:name, - start:new Date().getTime(), - scenario:jQuery('
') - }; - current.run = current.scenario.append( - '
' + - '.' + - '.' + - '.' + - '').find(".run"); - current.log = current.scenario.append('
').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('').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:toJson(arg)); - } - var log = jQuery('
'); - log.text(buf.join(" ")); - this.current.log.append(log); - this.console.scrollTop(this.console[0].scrollHeight); - if (level == "error") - this.current.error = buf.join(" "); - } -}; - -test.Scenario = function(name, scenario){ - this.name = name; - this.scenario = scenario; -}; -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: " + 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", 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: " + toJson(e)); - } - } -}; diff --git a/src/test/Steps.js b/src/test/Steps.js deleted file mode 100644 index cc9ff549..00000000 --- a/src/test/Steps.js +++ /dev/null @@ -1,57 +0,0 @@ -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:" + 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 deleted file mode 100644 index e29ae72a..00000000 --- a/src/test/_namespace.js +++ /dev/null @@ -1,5 +0,0 @@ -if (!angular) var angular = window['angular'] = {}; -if (!angular['test']) var angularTest = angular['test'] = {}; -if (!angular['test']['GIVEN']) angularTest['GIVEN'] = {}; -if (!angular['test']['WHEN']) angularTest['WHEN'] = {}; -if (!angular['test']['THEN']) angularTest['THEN'] = {}; -- cgit v1.2.3 From 923289112e34c2d426f6b2e687e93601894fb088 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 10:30:26 -0700 Subject: spike on directives --- src/directives.js | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 src/directives.js (limited to 'src') diff --git a/src/directives.js b/src/directives.js new file mode 100644 index 00000000..0e99d633 --- /dev/null +++ b/src/directives.js @@ -0,0 +1,121 @@ + +angular.directive("auth", function(expression, element){ + return function(){ + if(expression == "eager") { + this.$users.fetchCurrent(); + } + } +}); + + +//expression = "book=Book:{year=2000}" +angular.directive("entity", function(expression, element){ + //parse expression, ignore element + var entityName; // "Book"; + var instanceName; // "book"; + var defaults; // {year: 2000}; + + parse(expression); + + return function(){ + this[entityName] = this.$datastore.entity(entityName, defaults); + this[instanceName] = this[entityName](); + this.$watch("$anchor."+instanceName, function(newAnchor){ + this[instanceName] = this[entityName].get(this.$anchor[instanceName]); + }); + }; +}); + + +angular.directive("init", function(expression, element){ + return function(){ + this.$eval(expresssion); + } +}); + + +//translation of {{ }} to ng-bind is external to this +angular.directive("bind", function(expression, element){ + return function() { + this.$watch(expression, function(value){ + element.innerText = value; + }); + }; +}); + + +// translation of {{ }} to ng-bind-attr is external to this +// link +// becomes +// link +angular.directive("bind-attr", function(expression, element){ + var jElement = jQuery(element); + return function(){ + this.$watch(expression, _(jElement.attr).bind(jElement)); + }; +}); + +angular.directive("repeat", function(expression, element){ + var anchor = document.createComment(expression); + jQuery(element).replace(anchor); + var template = this.compile(element); + var lhs = "item"; + var rhs = "items"; + var children = []; + return function(){ + this.$watch(rhs, function(items){ + foreach(children, function(child){ + child.element.remove(); + }); + foreach(items, function(item){ + var child = template(item); // create scope + element.addChild(child.element, anchor); + children.push(child); + }); + }); + }; +}); + + +//ng-non-bindable +angular.directive("non-bindable", function(expression, element){ + return false; +}); + +//Styling +// +//ng-class +//ng-class-odd, ng-class-even +//ng-style +//ng-show, ng-hide + + +angular.directive("action", function(expression, element){ + return function(){ + var self = this; + jQuery(element).click(function(){ + self.$eval(expression); + }); + }; +}); + +//ng-eval +angular.directive("eval", function(expression, element){ + return function(){ + this.$onUpdate( expression); + } +}); +//ng-watch +//
+angular.directive("watch", function(expression, element){ + var watches = { + 'lhs':'rhs' + }; // parse + return function(){ + this.$watch(watches); + } +}); + +//widget related +//ng-validate, ng-required, ng-formatter +//ng-error -- cgit v1.2.3 From 5119c8a86fa2b8ce9d0f0c343b57dd96aa88ce8c Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 13:50:47 -0700 Subject: spike widgets in new style --- src/directives.js | 2 ++ src/widgets2.js | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/widgets2.js (limited to 'src') diff --git a/src/directives.js b/src/directives.js index 0e99d633..13cd4b59 100644 --- a/src/directives.js +++ b/src/directives.js @@ -119,3 +119,5 @@ angular.directive("watch", function(expression, element){ //widget related //ng-validate, ng-required, ng-formatter //ng-error + +//ng-scope ng-controller???? diff --git a/src/widgets2.js b/src/widgets2.js new file mode 100644 index 00000000..0d7bbd49 --- /dev/null +++ b/src/widgets2.js @@ -0,0 +1,85 @@ +// -> +angular.widget("inputtext", function(element) { + var expression = element.attr('name'); + var formatter = this.formatter(element.attr('formatter')); + var validator = this.validator(element.attr('validator')); + + function validate(value) { + var error = validator(element); + if (error) { + element.addClass("ng-error"); + scope.markInvalid(this); //move out of scope + } else { + scope.clearInvalid(this); + } + } + + + element.keyup(this.withScope(function(){ + this.$evalSet(expression, formatter.parse(element.val())); + validate(element.val()); + })); + + return {watch: expression, apply: function(newValue){ + element.val(formatter.format(newValue)); + validate(element.val()); + }}; + +}); + +angular.widget("inputfile", function(element) { + +}); + +angular.widget("inputradio", function(element) { + +}); + + +// +angular.widget("colorpicker", function(element) { + var name = element.attr('datasource'); + var formatter = this.formatter(element.attr('ng-formatter')); + + element.colorPicker(this.withScope(function(selectedColor){ + this.$evalSet(name, formatter.parse(selectedColor)); + })); + + return function(){ + this.$watch(expression, function(cmyk){ + element.setColor(formatter.format(cmyk)); + }); + } +}); + +angular.widget("template", function(element) { + var srcExpression = element.attr('src'); + var self = this; + return {watch:srcExpression, apply:function(src){ + $.load(src, function(html){ + self.destroy(element); + element.html(html); + self.compile(element); + }); + }}; +}); + + +/** + * + * { + * withScope: //safely executes, with a try/catch. applies scope + * compile: + * widget: + * directive: + * validator: + * formatter: + * + * + * config: + * loadCSS: + * loadScript: + * loadTemplate: + * } + * + **/ -- cgit v1.2.3 From 2df072e3f89e8998b06b5a9e5ffb10fa32155136 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 14:38:56 -0700 Subject: twitter using resources --- src/Resource.js | 2 +- src/angular-bootstrap.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/Resource.js b/src/Resource.js index c0c7934c..0ff46726 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -49,7 +49,7 @@ ResourceFactory.prototype = { actions = $.extend({}, ResourceFactory.DEFAULT_ACTIONS, actions); function extractParams(data){ var ids = {}; - foreach(paramDefaults, function(value, key){ + foreach(paramDefaults || {}, function(value, key){ ids[key] = value.charAt && value.charAt(0) == '@' ? Scope.getter(data, value.substr(1)) : value; }); return ids; diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index 0f7cd2ea..d2b2ff9c 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -46,6 +46,7 @@ addScript("/JSON.js"); addScript("/Model.js"); addScript("/Parser.js"); + addScript("/Resource.js"); addScript("/Scope.js"); addScript("/Server.js"); addScript("/Users.js"); -- cgit v1.2.3 From c9aba8b442adce496f0600c88764f7ffcc166879 Mon Sep 17 00:00:00 2001 From: Adam Abrons Date: Tue, 16 Mar 2010 14:48:11 -0700 Subject: make xhr just a method --- src/Resource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/Resource.js b/src/Resource.js index 0ff46726..587c331e 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -83,7 +83,7 @@ ResourceFactory.prototype = { } var value = action.isArray ? [] : new Resource(data); - self.xhr.method(action.method, route.url($.extend({}, action.params || {}, extractParams(data), params)), data, function(response) { + self.xhr(action.method, route.url($.extend({}, action.params || {}, extractParams(data), params)), data, function(response) { if (action.isArray) { foreach(response, function(item){ value.push(new Resource(item)); -- cgit v1.2.3 From 7634a3ed5227f8bc2a2ba83752d0e2c78adb6051 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 12:20:06 -0700 Subject: initial revision of new plugable compiler --- src/Angular.js | 10 ++++++++-- src/DataStore.js | 32 ++++++++++++++++---------------- src/Resource.js | 4 ++-- src/Scope.js | 5 +++++ src/directives.js | 14 +++++++------- 5 files changed, 38 insertions(+), 27 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index 9b3634be..39a6e91d 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -24,9 +24,14 @@ var consoleNode, msie, jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy foreach = _.each, extend = _.extend, + slice = Array.prototype.slice, identity = _.identity, angular = window['angular'] || (window['angular'] = {}), angularValidator = angular['validator'] || (angular['validator'] = {}), + angularDirective = angular['directive'] || (angular['directive'] = function(name, fn){ + if (fn) {angularDirective[name] = fn;}; + return angularDirective[name]; + }), angularFilter = angular['filter'] || (angular['filter'] = {}), angularFormatter = angular['formatter'] || (angular['formatter'] = {}), angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), @@ -37,7 +42,7 @@ angular['copy'] = copy; var isVisible = isVisible || function (element) { return jQuery(element).is(":visible"); -} +}; function log(a, b, c){ var console = window['console']; @@ -154,12 +159,13 @@ function escapeAttr(html) { } function bind(_this, _function) { + var curryArgs = slice.call(arguments, 2, arguments.length); if (!_this) throw "Missing this"; if (!_.isFunction(_function)) throw "Missing function"; return function() { - return _function.apply(_this, arguments); + return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); }; } diff --git a/src/DataStore.js b/src/DataStore.js index 789b8f71..70bcc623 100644 --- a/src/DataStore.js +++ b/src/DataStore.js @@ -29,7 +29,7 @@ DataStore.prototype = { } return cachedDocument; }, - + load: function(instance, id, callback, failure) { if (id && id !== '*') { var self = this; @@ -43,7 +43,7 @@ DataStore.prototype = { } return instance; }, - + loadMany: function(entity, ids, callback) { var self=this; var list = []; @@ -58,7 +58,7 @@ DataStore.prototype = { }); return list; }, - + loadOrCreate: function(instance, id, callback) { var self=this; return this.load(instance, id, callback, function(response){ @@ -70,7 +70,7 @@ DataStore.prototype = { } }); }, - + loadAll: function(entity, callback) { var self = this; var list = []; @@ -89,7 +89,7 @@ DataStore.prototype = { }); return list; }, - + save: function(document, callback) { var self = this; var data = {}; @@ -109,7 +109,7 @@ DataStore.prototype = { callback(document); }); }, - + remove: function(document, callback) { var self = this; var data = {}; @@ -127,7 +127,7 @@ DataStore.prototype = { (callback||noop)(response); }); }, - + _jsonRequest: function(request, callback, failure) { request['$$callback'] = callback; request['$$failure'] = failure||function(response){ @@ -135,7 +135,7 @@ DataStore.prototype = { }; this.bulkRequest.push(request); }, - + flush: function() { if (this.bulkRequest.length === 0) return; var self = this; @@ -169,7 +169,7 @@ DataStore.prototype = { } this.post(bulkRequest, callback); }, - + saveScope: function(scope, callback) { var saveCounter = 1; function onSaveDone() { @@ -186,7 +186,7 @@ DataStore.prototype = { } onSaveDone(); }, - + query: function(type, query, arg, callback){ var self = this; var queryList = []; @@ -205,7 +205,7 @@ DataStore.prototype = { }); return queryList; }, - + entities: function(callback) { var entities = []; var self = this; @@ -218,7 +218,7 @@ DataStore.prototype = { }); return entities; }, - + documentCountsByUser: function(){ var counts = {}; var self = this; @@ -227,7 +227,7 @@ DataStore.prototype = { }); return counts; }, - + userDocumentIdsByEntity: function(user){ var ids = {}; var self = this; @@ -236,7 +236,7 @@ DataStore.prototype = { }); return ids; }, - + entity: function(name, defaults){ if (!name) { return DataStore.NullEntity; @@ -271,7 +271,7 @@ DataStore.prototype = { }); return entity; }, - + join: function(join){ function fn(){ throw "Joined entities can not be instantiated into a document."; @@ -327,4 +327,4 @@ DataStore.prototype = { }; return fn; } -}; \ No newline at end of file +}; diff --git a/src/Resource.js b/src/Resource.js index 587c331e..971ad6e5 100644 --- a/src/Resource.js +++ b/src/Resource.js @@ -79,7 +79,7 @@ ResourceFactory.prototype = { case 1: if (isPost) data = a1; else params = a1; break; case 0: break; default: - throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments." + throw "Expected between 0-3 arguments [params, data, callback], got " + arguments.length + " arguments."; } var value = action.isArray ? [] : new Resource(data); @@ -109,7 +109,7 @@ ResourceFactory.prototype = { case 1: if (typeof a1 == 'function') callback = a1; else params = a1; case 0: break; default: - throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments." + throw "Expected between 1-3 arguments [params, data, callback], got " + arguments.length + " arguments."; } var self = this; Resource[name](params, this, function(response){ diff --git a/src/Scope.js b/src/Scope.js index daf4b36c..442477b4 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -10,6 +10,7 @@ function Scope(initialState, name) { if (name == "ROOT") { this.state['$root'] = this.state; } + this.set('$watch', bind(this, this.addWatchListener)); }; Scope.expressionCache = {}; @@ -202,5 +203,9 @@ Scope.prototype = { } }); return fired; + }, + + apply: function(fn) { + fn.apply(this.state, slice(arguments, 0, arguments.length)); } }; diff --git a/src/directives.js b/src/directives.js index 0e99d633..7c5cc257 100644 --- a/src/directives.js +++ b/src/directives.js @@ -4,7 +4,7 @@ angular.directive("auth", function(expression, element){ if(expression == "eager") { this.$users.fetchCurrent(); } - } + }; }); @@ -30,7 +30,7 @@ angular.directive("entity", function(expression, element){ angular.directive("init", function(expression, element){ return function(){ this.$eval(expresssion); - } + }; }); @@ -49,8 +49,8 @@ angular.directive("bind", function(expression, element){ // becomes // link angular.directive("bind-attr", function(expression, element){ - var jElement = jQuery(element); - return function(){ + return function(expression, element){ + var jElement = jQuery(element); this.$watch(expression, _(jElement.attr).bind(jElement)); }; }); @@ -58,7 +58,7 @@ angular.directive("bind-attr", function(expression, element){ angular.directive("repeat", function(expression, element){ var anchor = document.createComment(expression); jQuery(element).replace(anchor); - var template = this.compile(element); + var template = this.templetize(element); var lhs = "item"; var rhs = "items"; var children = []; @@ -103,7 +103,7 @@ angular.directive("action", function(expression, element){ angular.directive("eval", function(expression, element){ return function(){ this.$onUpdate( expression); - } + }; }); //ng-watch //
@@ -113,7 +113,7 @@ angular.directive("watch", function(expression, element){ }; // parse return function(){ this.$watch(watches); - } + }; }); //widget related -- cgit v1.2.3 From df607da0d1b9726bce6584238fe3ad7e9b65a966 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 18 Mar 2010 14:43:49 -0700 Subject: support for templates --- src/Scope.js | 15 +++++++++++---- src/directives.js | 8 ++++---- 2 files changed, 15 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/Scope.js b/src/Scope.js index 442477b4..3633f960 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -1,5 +1,6 @@ function Scope(initialState, name) { this.widgets = []; + this.evals = []; this.watchListeners = {}; this.name = name; initialState = initialState || {}; @@ -11,6 +12,7 @@ function Scope(initialState, name) { this.state['$root'] = this.state; } this.set('$watch', bind(this, this.addWatchListener)); + this.set('$eval', bind(this, this.addEval)); }; Scope.expressionCache = {}; @@ -48,17 +50,23 @@ Scope.prototype = { updateView: function() { var self = this; this.fireWatchers(); - _.each(this.widgets, function(widget){ + foreach(this.widgets, function(widget){ self.evalWidget(widget, "", {}, function(){ this.updateView(self); }); }); + foreach(this.evals, bind(this, this.apply)); }, addWidget: function(controller) { if (controller) this.widgets.push(controller); }, + addEval: function(fn, listener) { + // todo: this should take a function/string and a listener + this.evals.push(fn); + }, + isProperty: function(exp) { for ( var i = 0; i < exp.length; i++) { var ch = exp.charAt(i); @@ -190,8 +198,7 @@ Scope.prototype = { }, fireWatchers: function() { - var self = this; - var fired = false; + var self = this, fired = false; foreach(this.watchListeners, function(watcher) { var value = self.eval(watcher.expression); if (value !== watcher.lastValue) { @@ -206,6 +213,6 @@ Scope.prototype = { }, apply: function(fn) { - fn.apply(this.state, slice(arguments, 0, arguments.length)); + fn.apply(this.state, slice.call(arguments, 1, arguments.length)); } }; diff --git a/src/directives.js b/src/directives.js index 7c5cc257..26cbfe2c 100644 --- a/src/directives.js +++ b/src/directives.js @@ -58,12 +58,12 @@ angular.directive("bind-attr", function(expression, element){ angular.directive("repeat", function(expression, element){ var anchor = document.createComment(expression); jQuery(element).replace(anchor); - var template = this.templetize(element); + var template = this.compile(element); var lhs = "item"; var rhs = "items"; - var children = []; return function(){ - this.$watch(rhs, function(items){ + var children = []; + this.$eval(rhs, function(items){ foreach(children, function(child){ child.element.remove(); }); @@ -102,7 +102,7 @@ angular.directive("action", function(expression, element){ //ng-eval angular.directive("eval", function(expression, element){ return function(){ - this.$onUpdate( expression); + this.$eval(expression); }; }); //ng-watch -- cgit v1.2.3 From c3eac13aa7106d099e8f09c39518051ccf939060 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 19 Mar 2010 16:41:02 -0700 Subject: showing off problem to corry --- src/Compiler.js | 188 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/Compiler.js (limited to 'src') diff --git a/src/Compiler.js b/src/Compiler.js new file mode 100644 index 00000000..f4d901fb --- /dev/null +++ b/src/Compiler.js @@ -0,0 +1,188 @@ +/** + * Template provides directions an how to bind to a given element. + * It contains a list of init functions which need to be called to + * bind to a new instance of elements. It also provides a list + * of child paths which contain child templates + */ +function Template() { + this.paths = []; + this.children = []; + this.inits = []; +} + +Template.prototype = { + init: function(element, scope) { + foreach(this.inits, function(fn) { + scope.apply(fn, nodeLite(element)); + }); + + var i, + childNodes = element.childNodes, + children = this.children, + paths = this.paths, + length = paths.length; + for (i = 0; i < length; i++) { + children[i].init(childNodes[paths[i]], scope); + } + }, + + addInit:function(init) { + if (init) { + this.inits.push(init); + } + }, + + setExclusiveInit: function(init) { + this.inits = [init]; + this.addInit = noop; + }, + + + addChild: function(index, template) { + this.paths.push(index); + this.children.push(template); + } +}; + +/////////////////////////////////// +//NodeLite +////////////////////////////////// + +function NodeLite(element) { + this.element = element; +} + +function nodeLite(element) { + return element instanceof NodeLite ? element : new NodeLite(element); +} + +NodeLite.prototype = { + eachTextNode: function(fn){ + var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; + for (i = 0; i < size; i++) { + if((chld = new NodeLite(chldNodes[i])).isText()) { + fn(chld, i); + } + } + }, + + eachNode: function(fn){ + var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; + for (i = 0; i < size; i++) { + if(!(chld = new NodeLite(chldNodes[i])).isText()) { + fn(chld, i); + } + } + }, + + eachAttribute: function(fn){ + var i, attrs = this.element.attributes || [], size = attrs.length, chld, attr; + for (i = 0; i < size; i++) { + var attr = attrs[i]; + fn(attr.name, attr.value); + } + }, + + replaceWith: function(replaceNode) { + this.element.parentNode.replaceChild(nodeLite(replaceNode).element, this.element); + }, + + removeAttribute: function(name) { + this.element.removeAttribute(name); + }, + + after: function(element) { + this.element.parentNode.insertBefore(element, this.element.nextSibling); + }, + + attr: function(name, value){ + if (typeof value == 'undefined') { + return this.element.getAttribute(name); + } else { + this.element.setAttribute(name); + } + }, + + isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, + text: function() { return this.element.nodeValue; }, + clone: function() { return nodeLite(this.element.cloneNode(true)); } +}; + +/////////////////////////////////// +//Compiler +////////////////////////////////// + +function Compiler(markup, directives, widgets){ + this.markup = markup; + this.directives = directives; + this.widgets = widgets; +} + +DIRECTIVE = /^ng-(.*)$/; + +Compiler.prototype = { + compile: function(element) { + var template = this.templetize(nodeLite(element)) || new Template(); + return function(element){ + var scope = new Scope(); + scope.element = element; + return { + scope: scope, + element:element, + init: bind(template, template.init, element, scope) + }; + }; + }, + + templetize: function(element){ + var self = this, + markup = self.markup, + markupSize = markup.length, + directives = self.directives, + widgets = self.widgets, + recurse = true, + exclusive = false, + template; + + // process markup for text nodes only + element.eachTextNode(function(textNode){ + for (var i = 0, text = textNode.text(); i < markupSize; i++) { + markup[i].call(self, text, textNode, element); + } + }); + + // Process attributes/directives + element.eachAttribute(function(name, value){ + var match = name.match(DIRECTIVE), + directive, init; + if (!exclusive && match) { + directive = directives[match[1]]; + if (directive) { + init = directive.call(self, value, element); + template = template || new Template(); + if (directive.exclusive) { + template.setExclusiveInit(init); + exclusive = true; + } else { + template.addInit(init); + } + recurse = recurse && init; + } else { + error("Directive '" + match[0] + "' is not recognized."); + } + } + }); + + // Process non text child nodes + if (recurse) { + element.eachNode(function(child, i){ + var childTemplate = self.templetize(child); + if(childTemplate) { + template = template || new Template(); + template.addChild(i, childTemplate); + } + }); + } + return template; + } +}; -- cgit v1.2.3 From f6664ed7f6f6dd1f4f9756f57611a316089149cb Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 19 Mar 2010 22:18:39 -0700 Subject: tests fixed, still missing widgets --- src/Angular.js | 4 ++++ src/Compiler.js | 49 +++++++++++++++++++++++++++++-------------------- 2 files changed, 33 insertions(+), 20 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index 39a6e91d..8793274c 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,6 +44,10 @@ var isVisible = isVisible || function (element) { return jQuery(element).is(":visible"); }; +function isDefined(value){ + return typeof value !== 'undefined'; +} + function log(a, b, c){ var console = window['console']; switch(arguments.length) { diff --git a/src/Compiler.js b/src/Compiler.js index f4d901fb..3c757dd0 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -26,21 +26,21 @@ Template.prototype = { } }, + addInit:function(init) { if (init) { this.inits.push(init); } }, - setExclusiveInit: function(init) { - this.inits = [init]; - this.addInit = noop; - }, - addChild: function(index, template) { this.paths.push(index); this.children.push(template); + }, + + empty: function() { + return this.inits.length == 0 && this.paths.length == 0; } }; @@ -92,19 +92,25 @@ NodeLite.prototype = { }, after: function(element) { - this.element.parentNode.insertBefore(element, this.element.nextSibling); + this.element.parentNode.insertBefore(nodeLite(element).element, this.element.nextSibling); }, attr: function(name, value){ - if (typeof value == 'undefined') { - return this.element.getAttribute(name); + if (isDefined(value)) { + this.element.setAttribute(name, value); } else { - this.element.setAttribute(name); + return this.element.getAttribute(name); + } + }, + + text: function(value) { + if (isDefined(value)) { + this.element.nodeValue = value; } + return this.element.nodeValue; }, isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, - text: function() { return this.element.nodeValue; }, clone: function() { return nodeLite(this.element.cloneNode(true)); } }; @@ -142,7 +148,8 @@ Compiler.prototype = { widgets = self.widgets, recurse = true, exclusive = false, - template; + directiveQueue = [], + template = new Template(); // process markup for text nodes only element.eachTextNode(function(textNode){ @@ -154,35 +161,37 @@ Compiler.prototype = { // Process attributes/directives element.eachAttribute(function(name, value){ var match = name.match(DIRECTIVE), - directive, init; + directive; if (!exclusive && match) { directive = directives[match[1]]; if (directive) { - init = directive.call(self, value, element); - template = template || new Template(); if (directive.exclusive) { - template.setExclusiveInit(init); exclusive = true; - } else { - template.addInit(init); + directiveQueue = []; } - recurse = recurse && init; + directiveQueue.push(bind(self, directive, value, element)); } else { error("Directive '" + match[0] + "' is not recognized."); } } }); + // Execute directives + foreach(directiveQueue, function(directive){ + var init = directive(); + template.addInit(init); + recurse = recurse && init; + }); + // Process non text child nodes if (recurse) { element.eachNode(function(child, i){ var childTemplate = self.templetize(child); if(childTemplate) { - template = template || new Template(); template.addChild(i, childTemplate); } }); } - return template; + return template.empty() ? null : template; } }; -- cgit v1.2.3 From 84552f7f8ac3f39c4dbd7d946ae2938d63302840 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 13:58:04 -0700 Subject: got few directives working --- src/Angular.js | 37 ++++++++--- src/Compiler.js | 149 ++++++++++++++++++++++++++------------------ src/Parser.js | 107 +++++++++++++++---------------- src/Scope.js | 52 +++++++++++----- src/directives.js | 124 +++++++++++++++++------------------- src/directivesAngularCom.js | 29 +++++++++ 6 files changed, 290 insertions(+), 208 deletions(-) create mode 100644 src/directivesAngularCom.js (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index 8793274c..cfffab04 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -18,23 +18,32 @@ if (typeof Node == 'undefined') { } function noop() {} +function identity($) {return $;} if (!window['console']) window['console']={'log':noop, 'error':noop}; +function extension(angular, name) { + var extPoint; + return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ + if (isDefined(fn)) { + extPoint[name] = extend(fn, prop || {}); + } + return extPoint[name]; + }); +} + var consoleNode, msie, jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy foreach = _.each, extend = _.extend, slice = Array.prototype.slice, - identity = _.identity, angular = window['angular'] || (window['angular'] = {}), - angularValidator = angular['validator'] || (angular['validator'] = {}), - angularDirective = angular['directive'] || (angular['directive'] = function(name, fn){ - if (fn) {angularDirective[name] = fn;}; - return angularDirective[name]; - }), - angularFilter = angular['filter'] || (angular['filter'] = {}), - angularFormatter = angular['formatter'] || (angular['formatter'] = {}), - angularCallbacks = angular['callbacks'] || (angular['callbacks'] = {}), + angularDirective = extension(angular, 'directive'), + angularMarkup = extension(angular, 'markup'), + angularWidget = extension(angular, 'widget'), + angularValidator = extension(angular, 'validator'), + angularFilter = extension(angular, 'filter'), + angularFormatter = extension(angular, 'formatter'), + angularCallbacks = extension(angular, 'callbacks'), angularAlert = angular['alert'] || (angular['alert'] = function(){ log(arguments); window.alert.apply(window, arguments); }); @@ -45,7 +54,15 @@ var isVisible = isVisible || function (element) { }; function isDefined(value){ - return typeof value !== 'undefined'; + return typeof value != 'undefined'; +} + +function isObject(value){ + return typeof value == 'object'; +} + +function isFunction(value){ + return typeof value == 'function'; } function log(a, b, c){ diff --git a/src/Compiler.js b/src/Compiler.js index 3c757dd0..5c650204 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -13,7 +13,7 @@ function Template() { Template.prototype = { init: function(element, scope) { foreach(this.inits, function(fn) { - scope.apply(fn, nodeLite(element)); + scope.apply(fn, jqLite(element)); }); var i, @@ -35,8 +35,10 @@ Template.prototype = { addChild: function(index, template) { - this.paths.push(index); - this.children.push(template); + if (template) { + this.paths.push(index); + this.children.push(template); + } }, empty: function() { @@ -45,31 +47,37 @@ Template.prototype = { }; /////////////////////////////////// -//NodeLite +//JQLite ////////////////////////////////// -function NodeLite(element) { +function JQLite(element) { this.element = element; } -function nodeLite(element) { - return element instanceof NodeLite ? element : new NodeLite(element); +function jqLite(element) { + if (typeof element == 'string') { + var div = document.createElement('div'); + div.innerHTML = element; + element = div.childNodes[0]; + } + return element instanceof JQLite ? element : new JQLite(element); } -NodeLite.prototype = { +JQLite.prototype = { eachTextNode: function(fn){ var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { - if((chld = new NodeLite(chldNodes[i])).isText()) { + if((chld = new JQLite(chldNodes[i])).isText()) { fn(chld, i); } } }, + eachNode: function(fn){ var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { - if(!(chld = new NodeLite(chldNodes[i])).isText()) { + if(!(chld = new JQLite(chldNodes[i])).isText()) { fn(chld, i); } } @@ -84,34 +92,51 @@ NodeLite.prototype = { }, replaceWith: function(replaceNode) { - this.element.parentNode.replaceChild(nodeLite(replaceNode).element, this.element); + this.element.parentNode.replaceChild(jqLite(replaceNode).element, this.element); }, - removeAttribute: function(name) { + remove: function() { + this.element.parentNode.removeChild(this.element); + }, + + removeAttr: function(name) { this.element.removeAttribute(name); }, after: function(element) { - this.element.parentNode.insertBefore(nodeLite(element).element, this.element.nextSibling); + this.element.parentNode.insertBefore(jqLite(element).element, this.element.nextSibling); }, attr: function(name, value){ - if (isDefined(value)) { - this.element.setAttribute(name, value); + var e = this.element; + if (isObject(name)) { + foreach(name, function(value, name){ + e.setAttribute(name, value); + }); + } else if (isDefined(value)) { + e.setAttribute(name, value); } else { - return this.element.getAttribute(name); + return e.getAttribute(name); } }, text: function(value) { if (isDefined(value)) { - this.element.nodeValue = value; + this.element.textContent = value; } - return this.element.nodeValue; + return this.element.textContent; }, + html: function(value) { + if (isDefined(value)) { + this.element.innerHTML = value; + } + return this.element.innerHTML; + }, + + parent: function() { return jqLite(this.element.parentNode);}, isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, - clone: function() { return nodeLite(this.element.cloneNode(true)); } + clone: function() { return jqLite(this.element.cloneNode(true)); } }; /////////////////////////////////// @@ -124,14 +149,14 @@ function Compiler(markup, directives, widgets){ this.widgets = widgets; } -DIRECTIVE = /^ng-(.*)$/; - Compiler.prototype = { - compile: function(element) { - var template = this.templetize(nodeLite(element)) || new Template(); - return function(element){ - var scope = new Scope(); + compile: function(rawElement) { + rawElement = jqLite(rawElement); + var template = this.templatize(rawElement) || new Template(); + return function(element, parentScope){ + var scope = new Scope(parentScope); scope.element = element; + // todo return should be a scope with everything already set on it as element return { scope: scope, element:element, @@ -140,57 +165,57 @@ Compiler.prototype = { }; }, - templetize: function(element){ + templatize: function(element){ var self = this, + elementName = element.element.nodeName, + widgets = self.widgets, + widget = widgets[elementName], markup = self.markup, markupSize = markup.length, directives = self.directives, - widgets = self.widgets, - recurse = true, + descend = true, exclusive = false, directiveQueue = [], - template = new Template(); - - // process markup for text nodes only - element.eachTextNode(function(textNode){ - for (var i = 0, text = textNode.text(); i < markupSize; i++) { - markup[i].call(self, text, textNode, element); - } - }); + template = new Template(), + selfApi = { + compile: bind(self, self.compile), + reference:function(name) {return jqLite(document.createComment(name));}, + descend: function(value){ if(isDefined(value)) descend = value; return descend;} + }; + + if (widget) { + template.addInit(widget.call(selfApi, element)); + } else { + // process markup for text nodes only + element.eachTextNode(function(textNode){ + for (var i = 0, text = textNode.text(); i < markupSize; i++) { + markup[i].call(selfApi, text, textNode, element); + } + }); - // Process attributes/directives - element.eachAttribute(function(name, value){ - var match = name.match(DIRECTIVE), - directive; - if (!exclusive && match) { - directive = directives[match[1]]; - if (directive) { + // Process attributes/directives + element.eachAttribute(function(name, value){ + var directive = directives[name]; + if (!exclusive && directive) { if (directive.exclusive) { exclusive = true; directiveQueue = []; } - directiveQueue.push(bind(self, directive, value, element)); - } else { - error("Directive '" + match[0] + "' is not recognized."); + directiveQueue.push(bind(selfApi, directive, value, element)); } - } - }); - - // Execute directives - foreach(directiveQueue, function(directive){ - var init = directive(); - template.addInit(init); - recurse = recurse && init; - }); + }); - // Process non text child nodes - if (recurse) { - element.eachNode(function(child, i){ - var childTemplate = self.templetize(child); - if(childTemplate) { - template.addChild(i, childTemplate); - } + // Execute directives + foreach(directiveQueue, function(directive){ + template.addInit(directive()); }); + + // Process non text child nodes + if (descend) { + element.eachNode(function(child, i){ + template.addChild(i, self.templatize(child)); + }); + } } return template.empty() ? null : template; } diff --git a/src/Parser.js b/src/Parser.js index b59b21a7..c18a6250 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -40,7 +40,7 @@ Lexer.prototype = { return false; } }, - + parse: function() { var tokens = this.tokens; var OPERATORS = Lexer.OPERATORS; @@ -103,22 +103,22 @@ Lexer.prototype = { } return tokens; }, - + isNumber: function(ch) { return '0' <= ch && ch <= '9'; }, - + isWhitespace: function(ch) { return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v'; }, - + isIdent: function(ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' == ch || ch == '$'; }, - + readNumber: function() { var number = ""; var start = this.index; @@ -135,7 +135,7 @@ Lexer.prototype = { this.tokens.push({index:start, text:number, fn:function(){return number;}}); }, - + readIdent: function() { var ident = ""; var start = this.index; @@ -157,15 +157,17 @@ Lexer.prototype = { } this.tokens.push({index:start, text:ident, fn:fn}); }, - + readString: function(quote) { var start = this.index; var dateParseLength = this.dateParseLength; this.index++; var string = ""; + var rawString = quote; var escape = false; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); + rawString += ch; if (escape) { if (ch == 'u') { var hex = this.text.substring(this.index + 1, this.index + 5); @@ -184,7 +186,7 @@ Lexer.prototype = { escape = true; } else if (ch == quote) { this.index++; - this.tokens.push({index:start, text:string, + this.tokens.push({index:start, text:rawString, string:string, fn:function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; @@ -199,7 +201,7 @@ Lexer.prototype = { this.text.substring(start) + "] starting at column '" + (start+1) + "' in expression '" + this.text + "'."; }, - + readRegexp: function(quote) { var start = this.index; this.index++; @@ -249,18 +251,18 @@ Parser.ZERO = function(){ Parser.prototype = { error: function(msg, token) { - throw "Token '" + token.text + - "' is " + msg + " at column='" + - (token.index + 1) + "' of expression '" + + throw "Token '" + token.text + + "' is " + msg + " at column='" + + (token.index + 1) + "' of expression '" + this.text + "' starting at '" + this.text.substring(token.index) + "'."; }, - + peekToken: function() { - if (this.tokens.length === 0) + if (this.tokens.length === 0) throw "Unexpected end of expression: " + this.text; return this.tokens[0]; }, - + peek: function(e1, e2, e3, e4) { var tokens = this.tokens; if (tokens.length > 0) { @@ -273,7 +275,7 @@ Parser.prototype = { } return false; }, - + expect: function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { @@ -283,7 +285,7 @@ Parser.prototype = { } return false; }, - + consume: function(e1){ if (!this.expect(e1)) { var token = this.peek(); @@ -293,30 +295,30 @@ Parser.prototype = { this.text.substring(token.index) + "'."; } }, - + _unary: function(fn, right) { return function(self) { return fn(self, right(self)); }; }, - + _binary: function(left, fn, right) { return function(self) { return fn(self, left(self), right(self)); }; }, - + hasTokens: function () { return this.tokens.length > 0; }, - + assertAllConsumed: function(){ if (this.tokens.length !== 0) { throw "Did not understand '" + this.text.substring(this.tokens[0].index) + "' while evaluating '" + this.text + "'."; } }, - + statements: function(){ var statements = []; while(true) { @@ -335,7 +337,7 @@ Parser.prototype = { } } }, - + filterChain: function(){ var left = this.expression(); var token; @@ -347,15 +349,15 @@ Parser.prototype = { } } }, - + filter: function(){ return this._pipeFunction(angularFilter); }, - + validator: function(){ return this._pipeFunction(angularValidator); }, - + _pipeFunction: function(fnScope){ var fn = this.functionIdent(fnScope); var argsFn = []; @@ -373,7 +375,7 @@ Parser.prototype = { var _this = this; foreach(self, function(v, k) { if (k.charAt(0) == '$') { - _this[k] = v; + _this[k] = v; } }); }; @@ -386,11 +388,11 @@ Parser.prototype = { } } }, - + expression: function(){ return this.throwStmt(); }, - + throwStmt: function(){ if (this.expect('throw')) { var throwExp = this.assignment(); @@ -401,7 +403,7 @@ Parser.prototype = { return this.assignment(); } }, - + assignment: function(){ var left = this.logicalOR(); var token; @@ -417,7 +419,7 @@ Parser.prototype = { return left; } }, - + logicalOR: function(){ var left = this.logicalAND(); var token; @@ -429,7 +431,7 @@ Parser.prototype = { } } }, - + logicalAND: function(){ var left = this.equality(); var token; @@ -438,7 +440,7 @@ Parser.prototype = { } return left; }, - + equality: function(){ var left = this.relational(); var token; @@ -447,7 +449,7 @@ Parser.prototype = { } return left; }, - + relational: function(){ var left = this.additive(); var token; @@ -456,7 +458,7 @@ Parser.prototype = { } return left; }, - + additive: function(){ var left = this.multiplicative(); var token; @@ -465,7 +467,7 @@ Parser.prototype = { } return left; }, - + multiplicative: function(){ var left = this.unary(); var token; @@ -474,7 +476,7 @@ Parser.prototype = { } return left; }, - + unary: function(){ var token; if (this.expect('+')) { @@ -487,7 +489,7 @@ Parser.prototype = { return this.primary(); } }, - + functionIdent: function(fnScope) { var token = this.expect(); var element = token.text.split('.'); @@ -504,7 +506,7 @@ Parser.prototype = { } return instance; }, - + primary: function() { var primary; if (this.expect('(')) { @@ -540,7 +542,7 @@ Parser.prototype = { } return primary; }, - + closure: function(hasArgs) { var args = []; if (hasArgs) { @@ -566,7 +568,7 @@ Parser.prototype = { }; }; }, - + fieldAccess: function(object) { var field = this.expect().text; var fn = function (self){ @@ -575,7 +577,7 @@ Parser.prototype = { fn.isAssignable = field; return fn; }, - + objectIndex: function(obj) { var indexFn = this.expression(); this.consume(']'); @@ -592,7 +594,7 @@ Parser.prototype = { }; } }, - + functionCall: function(fn) { var argsFn = []; if (this.peekToken().text != ')') { @@ -614,7 +616,7 @@ Parser.prototype = { } }; }, - + // This is used with json array declaration arrayDeclaration: function () { var elementFns = []; @@ -632,12 +634,13 @@ Parser.prototype = { return array; }; }, - + object: function () { var keyValues = []; if (this.peekToken().text != '}') { do { - var key = this.expect().text; + var token = this.expect(), + key = token.string || token.text; this.consume(":"); var value = this.expression(); keyValues.push({key:key, value:value}); @@ -654,7 +657,7 @@ Parser.prototype = { return object; }; }, - + entityDeclaration: function () { var decl = []; while(this.hasTokens()) { @@ -671,7 +674,7 @@ Parser.prototype = { return code; }; }, - + entityDecl: function () { var entity = this.expect().text; var instance; @@ -690,16 +693,16 @@ Parser.prototype = { var document = Entity(); document['$$anchor'] = instance; self.scope.set(instance, document); - return "$anchor." + instance + ":{" + + return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; } }; }, - + watch: function () { var decl = []; while(this.hasTokens()) { @@ -716,7 +719,7 @@ Parser.prototype = { } }; }, - + watchDecl: function () { var anchorName = this.expect().text; this.consume(":"); diff --git a/src/Scope.js b/src/Scope.js index 3633f960..d22604fd 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -1,18 +1,23 @@ function Scope(initialState, name) { - this.widgets = []; - this.evals = []; - this.watchListeners = {}; - this.name = name; + var self = this; + self.widgets = []; + self.evals = []; + self.watchListeners = {}; + self.name = name; initialState = initialState || {}; var State = function(){}; State.prototype = initialState; - this.state = new State(); - this.state['$parent'] = initialState; + self.state = new State(); + extend(self.state, { + '$parent': initialState, + '$watch': bind(self, self.addWatchListener), + '$eval': bind(self, self.eval), + // change name to onEval? + '$addEval': bind(self, self.addEval) + }); if (name == "ROOT") { - this.state['$root'] = this.state; + self.state['$root'] = self.state; } - this.set('$watch', bind(this, this.addWatchListener)); - this.set('$eval', bind(this, this.addEval)); }; Scope.expressionCache = {}; @@ -47,6 +52,7 @@ Scope.getter = function(instance, path) { }; Scope.prototype = { + // TODO: rename to update? or eval? updateView: function() { var self = this; this.fireWatchers(); @@ -64,7 +70,13 @@ Scope.prototype = { addEval: function(fn, listener) { // todo: this should take a function/string and a listener - this.evals.push(fn); + // todo: this is a hack, which will need to be cleaned up. + var self = this, + listenFn = listener || noop, + expr = bind(self, self.compile(fn), {scope: self, self: self.state}); + this.evals.push(function(){ + self.apply(listenFn, expr()); + }); }, isProperty: function(exp) { @@ -103,15 +115,21 @@ Scope.prototype = { this.eval(expressionText + "=" + toJson(value)); }, - eval: function(expressionText, context) { -// log('Scope.eval', expressionText); - var expression = Scope.expressionCache[expressionText]; - if (!expression) { - var parser = new Parser(expressionText); - expression = parser.statements(); + compile: function(exp) { + if (isFunction(exp)) return exp; + var expFn = Scope.expressionCache[exp]; + if (!expFn) { + var parser = new Parser(exp); + expFn = parser.statements(); parser.assertAllConsumed(); - Scope.expressionCache[expressionText] = expression; + Scope.expressionCache[exp] = expFn; } + return expFn; + }, + + eval: function(expressionText, context) { +// log('Scope.eval', expressionText); + var expression = this.compile(expressionText); context = context || {}; context.scope = this; context.self = this.state; diff --git a/src/directives.js b/src/directives.js index 26cbfe2c..66a5e864 100644 --- a/src/directives.js +++ b/src/directives.js @@ -1,86 +1,82 @@ - -angular.directive("auth", function(expression, element){ +angularDirective("ng-init", function(expression){ return function(){ - if(expression == "eager") { - this.$users.fetchCurrent(); - } - }; -}); - - -//expression = "book=Book:{year=2000}" -angular.directive("entity", function(expression, element){ - //parse expression, ignore element - var entityName; // "Book"; - var instanceName; // "book"; - var defaults; // {year: 2000}; - - parse(expression); - - return function(){ - this[entityName] = this.$datastore.entity(entityName, defaults); - this[instanceName] = this[entityName](); - this.$watch("$anchor."+instanceName, function(newAnchor){ - this[instanceName] = this[entityName].get(this.$anchor[instanceName]); - }); + this.$eval(expression); }; }); - -angular.directive("init", function(expression, element){ +angularDirective("ng-eval", function(expression){ return function(){ - this.$eval(expresssion); + this.$addEval(expression); }; }); - -//translation of {{ }} to ng-bind is external to this -angular.directive("bind", function(expression, element){ - return function() { +angular.directive("ng-bind", function(expression){ + return function(element) { this.$watch(expression, function(value){ - element.innerText = value; + element.text(value); }); }; }); - -// translation of {{ }} to ng-bind-attr is external to this -// link -// becomes -// link -angular.directive("bind-attr", function(expression, element){ - return function(expression, element){ - var jElement = jQuery(element); - this.$watch(expression, _(jElement.attr).bind(jElement)); +angular.directive("ng-bind-attr", function(expression){ + return function(element){ + this.$watch(expression, bind(element, element.attr)); }; }); -angular.directive("repeat", function(expression, element){ - var anchor = document.createComment(expression); - jQuery(element).replace(anchor); - var template = this.compile(element); - var lhs = "item"; - var rhs = "items"; +angular.directive("ng-non-bindable", function(){ + this.descend(false); +}); + +angular.directive("ng-repeat", function(expression, element){ + var reference = this.reference("ng-repeat: " + expression), + r = element.removeAttr('ng-repeat'), + template = this.compile(element), + path = expression.split(' in '), + lhs = path[0], + rhs = path[1]; + var parent = element.parent(); + element.replaceWith(reference); return function(){ - var children = []; - this.$eval(rhs, function(items){ - foreach(children, function(child){ - child.element.remove(); - }); - foreach(items, function(item){ - var child = template(item); // create scope - element.addChild(child.element, anchor); - children.push(child); + var children = [], + currentScope = this; + this.$addEval(rhs, function(items){ + var index = 0, childCount = children.length, childScope, lastElement = reference; + foreach(items, function(value, key){ + if (index < childCount) { + // reuse existing child + childScope = children[index]; + } else { + // grow children + childScope = template(element.clone(), currentScope); + childScope.init(); + childScope.scope.set('$index', index); + childScope.element.attr('ng-index', index); + lastElement.after(childScope.element); + children.push(childScope); + } + childScope.scope.set(lhs, value); + childScope.scope.updateView(); + lastElement = childScope.element; + index ++; }); + // shrink children + while(children.length > index) { + children.pop().element.remove(); + } }); }; -}); +}, {exclusive: true}); + + +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// + -//ng-non-bindable -angular.directive("non-bindable", function(expression, element){ - return false; -}); //Styling // @@ -99,12 +95,6 @@ angular.directive("action", function(expression, element){ }; }); -//ng-eval -angular.directive("eval", function(expression, element){ - return function(){ - this.$eval(expression); - }; -}); //ng-watch //
angular.directive("watch", function(expression, element){ diff --git a/src/directivesAngularCom.js b/src/directivesAngularCom.js new file mode 100644 index 00000000..84032bdd --- /dev/null +++ b/src/directivesAngularCom.js @@ -0,0 +1,29 @@ + +angular.directive("auth", function(expression, element){ + return function(){ + if(expression == "eager") { + this.$users.fetchCurrent(); + } + }; +}); + + +//expression = "book=Book:{year=2000}" +angular.directive("entity", function(expression, element){ + //parse expression, ignore element + var entityName; // "Book"; + var instanceName; // "book"; + var defaults; // {year: 2000}; + + parse(expression); + + return function(){ + this[entityName] = this.$datastore.entity(entityName, defaults); + this[instanceName] = this[entityName](); + this.$watch("$anchor."+instanceName, function(newAnchor){ + this[instanceName] = this[entityName].get(this.$anchor[instanceName]); + }); + }; +}); + + -- cgit v1.2.3 From b4561ff951ff452e55e820f6f8344dc2668cfd90 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 15:46:34 -0700 Subject: ng-repeat works --- src/Angular.js | 25 +++++++++++++++++++++---- src/Compiler.js | 16 +++++++++++++++- src/directives.js | 36 +++++++++++++++++++++++++----------- 3 files changed, 61 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index cfffab04..1549e7a7 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -181,15 +181,32 @@ function escapeAttr(html) { function bind(_this, _function) { var curryArgs = slice.call(arguments, 2, arguments.length); - if (!_this) - throw "Missing this"; - if (!_.isFunction(_function)) - throw "Missing function"; return function() { return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); }; } +function bindTry(_this, _function) { + var args = arguments, + last = args.length - 1, + curryArgs = slice.call(args, 2, last), + exceptionHandler = args[last]; + return function() { + try { + return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); + } catch (e) { + if (e = exceptionHandler(e)) throw e; + } + }; +} + +function errorHandlerFor(element) { + return function(error){ + element.attr('ng-error', angular.toJson(error)); + element.addClass('ng-exception'); + }; +} + function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); diff --git a/src/Compiler.js b/src/Compiler.js index 5c650204..ece44805 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -107,6 +107,20 @@ JQLite.prototype = { this.element.parentNode.insertBefore(jqLite(element).element, this.element.nextSibling); }, + hasClass: function(selector) { + var className = " " + selector + " "; + if ( (" " + this.element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { + return true; + } + return false; + }, + + addClass: function( selector ) { + if (!this.hasClass(selector)) { + this.element.className += ' ' + selector; + } + }, + attr: function(name, value){ var e = this.element; if (isObject(name)) { @@ -201,7 +215,7 @@ Compiler.prototype = { exclusive = true; directiveQueue = []; } - directiveQueue.push(bind(selfApi, directive, value, element)); + directiveQueue.push(bindTry(selfApi, directive, value, element, errorHandlerFor(element))); } }); diff --git a/src/directives.js b/src/directives.js index 66a5e864..8047cdbd 100644 --- a/src/directives.js +++ b/src/directives.js @@ -10,7 +10,7 @@ angularDirective("ng-eval", function(expression){ }; }); -angular.directive("ng-bind", function(expression){ +angularDirective("ng-bind", function(expression){ return function(element) { this.$watch(expression, function(value){ element.text(value); @@ -18,23 +18,36 @@ angular.directive("ng-bind", function(expression){ }; }); -angular.directive("ng-bind-attr", function(expression){ +angularDirective("ng-bind-attr", function(expression){ return function(element){ this.$watch(expression, bind(element, element.attr)); }; }); -angular.directive("ng-non-bindable", function(){ +angularDirective("ng-non-bindable", function(){ this.descend(false); }); -angular.directive("ng-repeat", function(expression, element){ +angularDirective("ng-repeat", function(expression, element){ var reference = this.reference("ng-repeat: " + expression), r = element.removeAttr('ng-repeat'), template = this.compile(element), - path = expression.split(' in '), - lhs = path[0], - rhs = path[1]; + match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), + lhs, rhs, valueIdent, keyIdent; + if (! match) { + throw "Expected ng-repeat in form of 'item in collection' but got '" + + expression + "'."; + } + lhs = match[1]; + rhs = match[2]; + match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); + if (!match) { + throw "'item' in 'item in collection' should be identifier or (key, value) but got '" + + keyValue + "'."; + } + valueIdent = match[3] || match[1]; + keyIdent = match[2]; + var parent = element.parent(); element.replaceWith(reference); return function(){ @@ -42,7 +55,7 @@ angular.directive("ng-repeat", function(expression, element){ currentScope = this; this.$addEval(rhs, function(items){ var index = 0, childCount = children.length, childScope, lastElement = reference; - foreach(items, function(value, key){ + foreach(items || [], function(value, key){ if (index < childCount) { // reuse existing child childScope = children[index]; @@ -55,7 +68,8 @@ angular.directive("ng-repeat", function(expression, element){ lastElement.after(childScope.element); children.push(childScope); } - childScope.scope.set(lhs, value); + childScope.scope.set(valueIdent, value); + if (keyIdent) childScope.scope.set(keyIdent, key); childScope.scope.updateView(); lastElement = childScope.element; index ++; @@ -86,7 +100,7 @@ angular.directive("ng-repeat", function(expression, element){ //ng-show, ng-hide -angular.directive("action", function(expression, element){ +angularDirective("action", function(expression, element){ return function(){ var self = this; jQuery(element).click(function(){ @@ -97,7 +111,7 @@ angular.directive("action", function(expression, element){ //ng-watch //
-angular.directive("watch", function(expression, element){ +angularDirective("watch", function(expression, element){ var watches = { 'lhs':'rhs' }; // parse -- cgit v1.2.3 From 6f8276a8e3735396999bd158005ca86bb1bb0978 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 16:07:42 -0700 Subject: ng-watch directive --- src/Scope.js | 4 ++++ src/directives.js | 22 +++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/Scope.js b/src/Scope.js index d22604fd..7b1d2673 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -207,6 +207,10 @@ Scope.prototype = { }, addWatchListener: function(watchExpression, listener) { + // TODO: clean me up! + if (!isFunction(listener)) { + listener = bind(this, this.compile(listener), {scope: this, self: this.state}); + } var watcher = this.watchListeners[watchExpression]; if (!watcher) { watcher = {listeners:[], expression:watchExpression}; diff --git a/src/directives.js b/src/directives.js index 861805fe..e081d179 100644 --- a/src/directives.js +++ b/src/directives.js @@ -82,6 +82,17 @@ angularDirective("ng-repeat", function(expression, element){ }; }, {exclusive: true}); +angularDirective("ng-watch", function(expression, element){ + var match = expression.match(/^([^.]*):(.*)$/); + if (!match) { + throw "Expecting watch expression 'ident_to_watch: watch_statement' got '" + + expression + "'"; + } + return function(){ + this.$watch(match[1], match[2]); + }; +}); + ///////////////////////////////////////// ///////////////////////////////////////// @@ -109,17 +120,6 @@ angularDirective("action", function(expression, element){ }; }); -//ng-watch -//
-angularDirective("watch", function(expression, element){ - var watches = { - 'lhs':'rhs' - }; // parse - return function(){ - this.$watch(watches); - }; -}); - //widget related //ng-validate, ng-required, ng-formatter //ng-error -- cgit v1.2.3 From 7c87c17d08dbba318af1a149c0bbedb696b03458 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 18:20:49 -0700 Subject: upgraded jquery to 1.4.2 and made ng-action work with jquery --- src/directives.js | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/directives.js b/src/directives.js index e081d179..9f98badb 100644 --- a/src/directives.js +++ b/src/directives.js @@ -82,6 +82,15 @@ angularDirective("ng-repeat", function(expression, element){ }; }, {exclusive: true}); +angularDirective("ng-action", function(expression, element){ + return function(){ + var self = this; + jQuery(element.element).click(function(){ + self.$eval(expression); + }); + }; +}); + angularDirective("ng-watch", function(expression, element){ var match = expression.match(/^([^.]*):(.*)$/); if (!match) { @@ -93,6 +102,12 @@ angularDirective("ng-watch", function(expression, element){ }; }); +//Styling +// +//ng-class +//ng-class-odd, ng-class-even +//ng-style +//ng-show, ng-hide ///////////////////////////////////////// ///////////////////////////////////////// @@ -103,22 +118,6 @@ angularDirective("ng-watch", function(expression, element){ -//Styling -// -//ng-class -//ng-class-odd, ng-class-even -//ng-style -//ng-show, ng-hide - - -angularDirective("action", function(expression, element){ - return function(){ - var self = this; - jQuery(element).click(function(){ - self.$eval(expression); - }); - }; -}); //widget related //ng-validate, ng-required, ng-formatter -- cgit v1.2.3 From a8227086748e37c31c1bb71dec50c96d63c45eef Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 20:20:05 -0700 Subject: rudementary event bind and trigger for jqlite --- src/Compiler.js | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/Scope.js | 19 +++++------ src/directives.js | 2 +- 3 files changed, 105 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/Compiler.js b/src/Compiler.js index ece44805..115ed094 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -50,7 +50,39 @@ Template.prototype = { //JQLite ////////////////////////////////// +var jqCache = {}; +var jqName = 'ng-' + new Date().getTime(); +var jqId = 1; +function jqNextId() { return jqId++; } + +var addEventListener = window.document.attachEvent ? + function(element, type, fn) { + element.attachEvent('on' + type, fn); + } : function(element, type, fn) { + element.addEventListener(type, fn, false); + }; + +var removeEventListener = window.document.detachEvent ? + function(element, type, fn) { + element.detachEvent('on' + type, fn); + } : function(element, type, fn) { + element.removeEventListener(type, fn, false); + }; + +function jqClearData(element) { + var cacheId = element[jqName], + cache = jqCache[cacheId]; + if (cache) { + foreach(cache.bind || {}, function(fn, type){ + removeEventListener(element, type, fn); + }); + delete jqCache[cacheId]; + delete element[jqName]; + } +}; + function JQLite(element) { + //todo: change to this[0]; this.element = element; } @@ -64,6 +96,67 @@ function jqLite(element) { } JQLite.prototype = { + data: function(key, value) { + var element = this.element, + cacheId = element[jqName], + cache = jqCache[cacheId || -1]; + if (isDefined(value)) { + if (!cache) { + element[jqName] = cacheId = jqNextId(); + cache = jqCache[cacheId] = {}; + } + cache[key] = value; + } else { + return cache ? cache[key] : null; + } + }, + + removeData: function(){ + jqClearData(this.element); + }, + + dealoc: function(){ + (function dealoc(element){ + jqClearData(element); + for ( var i = 0, children = element.childNodes; i < children.length; i++) { + dealoc(children[0]); + } + })(this.element); + }, + + bind: function(type, fn){ + var element = this.element, + bind = this.data('bind'), + eventHandler; + if (!bind) this.data('bind', bind = {}); + eventHandler = bind[type]; + if (!eventHandler) { + bind[type] = eventHandler = function() { + var self = this; + foreach(eventHandler.fns, function(fn){ + fn.apply(self, arguments); + }); + }; + eventHandler.fns = []; + addEventListener(element, type, eventHandler); + } + eventHandler.fns.push(fn); + }, + + trigger: function(type) { + var cache = this.data('bind'); + if (cache) { + (cache[type] || noop)(); + } + }, + + click: function(fn) { + if (fn) + this.bind('click', fn); + else + this.trigger('click'); + }, + eachTextNode: function(fn){ var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { @@ -96,6 +189,7 @@ JQLite.prototype = { }, remove: function() { + this.dealoc(); this.element.parentNode.removeChild(this.element); }, diff --git a/src/Scope.js b/src/Scope.js index 7b1d2673..a3e128b6 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -73,7 +73,7 @@ Scope.prototype = { // todo: this is a hack, which will need to be cleaned up. var self = this, listenFn = listener || noop, - expr = bind(self, self.compile(fn), {scope: self, self: self.state}); + expr = self.compile(fn); this.evals.push(function(){ self.apply(listenFn, expr()); }); @@ -117,23 +117,24 @@ Scope.prototype = { compile: function(exp) { if (isFunction(exp)) return exp; - var expFn = Scope.expressionCache[exp]; + var expFn = Scope.expressionCache[exp], self = this; if (!expFn) { var parser = new Parser(exp); expFn = parser.statements(); parser.assertAllConsumed(); Scope.expressionCache[exp] = expFn; } - return expFn; + return function(context){ + context = context || {}; + context.self = self.state; + context.scope = self; + return expFn.call(self, context); + }; }, eval: function(expressionText, context) { // log('Scope.eval', expressionText); - var expression = this.compile(expressionText); - context = context || {}; - context.scope = this; - context.self = this.state; - return expression(context); + return this.compile(expressionText)(context); }, //TODO: Refactor. This function needs to be an execution closure for widgets @@ -209,7 +210,7 @@ Scope.prototype = { addWatchListener: function(watchExpression, listener) { // TODO: clean me up! if (!isFunction(listener)) { - listener = bind(this, this.compile(listener), {scope: this, self: this.state}); + listener = this.compile(listener); } var watcher = this.watchListeners[watchExpression]; if (!watcher) { diff --git a/src/directives.js b/src/directives.js index 9f98badb..adcfa508 100644 --- a/src/directives.js +++ b/src/directives.js @@ -85,7 +85,7 @@ angularDirective("ng-repeat", function(expression, element){ angularDirective("ng-action", function(expression, element){ return function(){ var self = this; - jQuery(element.element).click(function(){ + element.click(function(){ self.$eval(expression); }); }; -- cgit v1.2.3 From 6ff550cfa9524bbb124d10caf1fc13c911ab3b4b Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Mon, 22 Mar 2010 21:29:57 -0700 Subject: all angular.js directives now work --- src/Angular.js | 16 +++++--------- src/Compiler.js | 65 +++++++++++++++++++++++++++++++++---------------------- src/directives.js | 38 +++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index 1549e7a7..ce1038cc 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -53,17 +53,11 @@ var isVisible = isVisible || function (element) { return jQuery(element).is(":visible"); }; -function isDefined(value){ - return typeof value != 'undefined'; -} - -function isObject(value){ - return typeof value == 'object'; -} - -function isFunction(value){ - return typeof value == 'function'; -} +function isDefined(value){ return typeof value != 'undefined'; } +function isObject(value){ return typeof value == 'object';} +function isString(value){ return typeof value == 'string';} +function isArray(value) { return value instanceof Array; } +function isFunction(value){ return typeof value == 'function';} function log(a, b, c){ var console = window['console']; diff --git a/src/Compiler.js b/src/Compiler.js index 115ed094..ca94c893 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -12,12 +12,13 @@ function Template() { Template.prototype = { init: function(element, scope) { + element = jqLite(element); foreach(this.inits, function(fn) { - scope.apply(fn, jqLite(element)); + scope.apply(fn, element); }); var i, - childNodes = element.childNodes, + childNodes = element[0].childNodes, children = this.children, paths = this.paths, length = paths.length; @@ -82,8 +83,7 @@ function jqClearData(element) { }; function JQLite(element) { - //todo: change to this[0]; - this.element = element; + this[0] = element; } function jqLite(element) { @@ -97,7 +97,7 @@ function jqLite(element) { JQLite.prototype = { data: function(key, value) { - var element = this.element, + var element = this[0], cacheId = element[jqName], cache = jqCache[cacheId || -1]; if (isDefined(value)) { @@ -112,7 +112,7 @@ JQLite.prototype = { }, removeData: function(){ - jqClearData(this.element); + jqClearData(this[0]); }, dealoc: function(){ @@ -121,11 +121,11 @@ JQLite.prototype = { for ( var i = 0, children = element.childNodes; i < children.length; i++) { dealoc(children[0]); } - })(this.element); + })(this[0]); }, bind: function(type, fn){ - var element = this.element, + var element = this[0], bind = this.data('bind'), eventHandler; if (!bind) this.data('bind', bind = {}); @@ -158,7 +158,7 @@ JQLite.prototype = { }, eachTextNode: function(fn){ - var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; + var i, chldNodes = this[0].childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { if((chld = new JQLite(chldNodes[i])).isText()) { fn(chld, i); @@ -168,7 +168,7 @@ JQLite.prototype = { eachNode: function(fn){ - var i, chldNodes = this.element.childNodes || [], size = chldNodes.length, chld; + var i, chldNodes = this[0].childNodes || [], size = chldNodes.length, chld; for (i = 0; i < size; i++) { if(!(chld = new JQLite(chldNodes[i])).isText()) { fn(chld, i); @@ -177,7 +177,7 @@ JQLite.prototype = { }, eachAttribute: function(fn){ - var i, attrs = this.element.attributes || [], size = attrs.length, chld, attr; + var i, attrs = this[0].attributes || [], size = attrs.length, chld, attr; for (i = 0; i < size; i++) { var attr = attrs[i]; fn(attr.name, attr.value); @@ -185,25 +185,25 @@ JQLite.prototype = { }, replaceWith: function(replaceNode) { - this.element.parentNode.replaceChild(jqLite(replaceNode).element, this.element); + this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]); }, remove: function() { this.dealoc(); - this.element.parentNode.removeChild(this.element); + this[0].parentNode.removeChild(this[0]); }, removeAttr: function(name) { - this.element.removeAttribute(name); + this[0].removeAttribute(name); }, after: function(element) { - this.element.parentNode.insertBefore(jqLite(element).element, this.element.nextSibling); + this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling); }, hasClass: function(selector) { var className = " " + selector + " "; - if ( (" " + this.element.className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { + if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { return true; } return false; @@ -211,12 +211,25 @@ JQLite.prototype = { addClass: function( selector ) { if (!this.hasClass(selector)) { - this.element.className += ' ' + selector; + this[0].className += ' ' + selector; + } + }, + + css: function(name, value) { + var style = this[0].style; + if (isString(name)) { + if (isDefined(value)) { + style[name] = value; + } else { + return style[name]; + } + } else { + extend(style, name); } }, attr: function(name, value){ - var e = this.element; + var e = this[0]; if (isObject(name)) { foreach(name, function(value, name){ e.setAttribute(name, value); @@ -230,21 +243,21 @@ JQLite.prototype = { text: function(value) { if (isDefined(value)) { - this.element.textContent = value; + this[0].textContent = value; } - return this.element.textContent; + return this[0].textContent; }, html: function(value) { if (isDefined(value)) { - this.element.innerHTML = value; + this[0].innerHTML = value; } - return this.element.innerHTML; + return this[0].innerHTML; }, - parent: function() { return jqLite(this.element.parentNode);}, - isText: function() { return this.element.nodeType == Node.TEXT_NODE; }, - clone: function() { return jqLite(this.element.cloneNode(true)); } + parent: function() { return jqLite(this[0].parentNode);}, + isText: function() { return this[0].nodeType == Node.TEXT_NODE; }, + clone: function() { return jqLite(this[0].cloneNode(true)); } }; /////////////////////////////////// @@ -275,7 +288,7 @@ Compiler.prototype = { templatize: function(element){ var self = this, - elementName = element.element.nodeName, + elementName = element[0].nodeName, widgets = self.widgets, widget = widgets[elementName], markup = self.markup, diff --git a/src/directives.js b/src/directives.js index adcfa508..bbf68669 100644 --- a/src/directives.js +++ b/src/directives.js @@ -102,13 +102,39 @@ angularDirective("ng-watch", function(expression, element){ }; }); -//Styling -// -//ng-class -//ng-class-odd, ng-class-even -//ng-style -//ng-show, ng-hide +function ngClass(selector) { + return function(expression, element){ + var existing = element[0].className + ' '; + return function(element){ + this.$addEval(expression, function(value){ + if (selector(this.$index)) { + if (isArray(value)) value = value.join(' '); + element[0].className = (existing + value).replace(/\s\s+/g, ' '); + } + }); + }; + }; +} + +angularDirective("ng-class", ngClass(function(){return true;})); +angularDirective("ng-class-odd", ngClass(function(i){return i % 2 == 1;})); +angularDirective("ng-class-even", ngClass(function(i){return i % 2 == 0;})); +angularDirective("ng-show", function(expression, element){ + return function(element){ + this.$addEval(expression, function(value){ + element.css('display', toBoolean(value) ? '' : 'none'); + }); + }; +}); + +angularDirective("ng-hide", function(expression, element){ + return function(element){ + this.$addEval(expression, function(value){ + element.css('display', toBoolean(value) ? 'none' : ''); + }); + }; +}); ///////////////////////////////////////// ///////////////////////////////////////// ///////////////////////////////////////// -- cgit v1.2.3 From bb98ae14f2aef74efbd8345e93f62ac67f460f7f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 23 Mar 2010 14:57:11 -0700 Subject: markup now wroks, some refactorings --- src/Angular.js | 77 +++++++++++----- src/Compiler.js | 259 +++++++++--------------------------------------------- src/Filters.js | 52 ++++++----- src/Scope.js | 2 +- src/directives.js | 65 ++++++++++---- src/jqLite.js | 185 ++++++++++++++++++++++++++++++++++++++ src/markup.js | 64 ++++++++++++++ src/widgets2.js | 18 +++- 8 files changed, 439 insertions(+), 283 deletions(-) create mode 100644 src/jqLite.js create mode 100644 src/markup.js (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index ce1038cc..95f7325a 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1,6 +1,7 @@ if (typeof document.getAttribute == 'undefined') document.getAttribute = function() {}; if (typeof Node == 'undefined') { + //TODO: can we get rid of this? Node = { ELEMENT_NODE : 1, ATTRIBUTE_NODE : 2, @@ -21,7 +22,7 @@ function noop() {} function identity($) {return $;} if (!window['console']) window['console']={'log':noop, 'error':noop}; -function extension(angular, name) { +function extensionMap(angular, name) { var extPoint; return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ if (isDefined(fn)) { @@ -31,20 +32,30 @@ function extension(angular, name) { }); } +function extensionList(angular, name) { + var extPoint, length = 0; + return angular[name] || (extPoint = angular[name] = function (fn, prop){ + if (isDefined(fn)) { + extPoint[length] = extend(fn, prop || {}); + length++; + } + return extPoint; + }); +} + var consoleNode, msie, - jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy - foreach = _.each, - extend = _.extend, - slice = Array.prototype.slice, - angular = window['angular'] || (window['angular'] = {}), - angularDirective = extension(angular, 'directive'), - angularMarkup = extension(angular, 'markup'), - angularWidget = extension(angular, 'widget'), - angularValidator = extension(angular, 'validator'), - angularFilter = extension(angular, 'filter'), - angularFormatter = extension(angular, 'formatter'), - angularCallbacks = extension(angular, 'callbacks'), - angularAlert = angular['alert'] || (angular['alert'] = function(){ + jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy + slice = Array.prototype.slice, + angular = window['angular'] || (window['angular'] = {}), + angularTextMarkup = extensionList(angular, 'textMarkup'), + angularAttrMarkup = extensionList(angular, 'attrMarkup'), + angularDirective = extensionMap(angular, 'directive'), + angularWidget = extensionMap(angular, 'widget'), + angularValidator = extensionMap(angular, 'validator'), + angularFilter = extensionMap(angular, 'filter'), + angularFormatter = extensionMap(angular, 'formatter'), + angularCallbacks = extensionMap(angular, 'callbacks'), + angularAlert = angular['alert'] || (angular['alert'] = function(){ log(arguments); window.alert.apply(window, arguments); }); angular['copy'] = copy; @@ -53,6 +64,29 @@ var isVisible = isVisible || function (element) { return jQuery(element).is(":visible"); }; +function foreach(obj, iterator, context) { + var key; + if (obj) { + if (obj.forEach) { + obj.forEach(iterator, context); + } else if (obj instanceof Array) { + for (key = 0; key < obj.length; key++) + iterator.call(context, obj[key], key); + } else { + for (key in obj) + iterator.call(context, obj[key], key); + } + } + return obj; +} + +function extend(dst, obj) { + foreach(obj, function(value, key){ + dst[key] = value; + }); + return dst; +} + function isDefined(value){ return typeof value != 'undefined'; } function isObject(value){ return typeof value == 'object';} function isString(value){ return typeof value == 'string';} @@ -112,14 +146,15 @@ function isNode(inp) { } function isLeafNode (node) { - switch (node.nodeName) { - case "OPTION": - case "PRE": - case "TITLE": - return true; - default: - return false; + if (node) { + switch (node.nodeName) { + case "OPTION": + case "PRE": + case "TITLE": + return true; + } } + return false; } function copy(source, destination){ diff --git a/src/Compiler.js b/src/Compiler.js index ca94c893..ba598a43 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -48,224 +48,41 @@ Template.prototype = { }; /////////////////////////////////// -//JQLite +//Compiler ////////////////////////////////// - -var jqCache = {}; -var jqName = 'ng-' + new Date().getTime(); -var jqId = 1; -function jqNextId() { return jqId++; } - -var addEventListener = window.document.attachEvent ? - function(element, type, fn) { - element.attachEvent('on' + type, fn); - } : function(element, type, fn) { - element.addEventListener(type, fn, false); - }; - -var removeEventListener = window.document.detachEvent ? - function(element, type, fn) { - element.detachEvent('on' + type, fn); - } : function(element, type, fn) { - element.removeEventListener(type, fn, false); - }; - -function jqClearData(element) { - var cacheId = element[jqName], - cache = jqCache[cacheId]; - if (cache) { - foreach(cache.bind || {}, function(fn, type){ - removeEventListener(element, type, fn); - }); - delete jqCache[cacheId]; - delete element[jqName]; - } -}; - -function JQLite(element) { - this[0] = element; +function isTextNode(node) { + return node.nodeType == Node.TEXT_NODE; } -function jqLite(element) { - if (typeof element == 'string') { - var div = document.createElement('div'); - div.innerHTML = element; - element = div.childNodes[0]; +function eachTextNode(element, fn){ + var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld; + for (i = 0; i < size; i++) { + if(isTextNode(chld = chldNodes[i])) { + fn(jqLite(chld), i); + } } - return element instanceof JQLite ? element : new JQLite(element); } -JQLite.prototype = { - data: function(key, value) { - var element = this[0], - cacheId = element[jqName], - cache = jqCache[cacheId || -1]; - if (isDefined(value)) { - if (!cache) { - element[jqName] = cacheId = jqNextId(); - cache = jqCache[cacheId] = {}; - } - cache[key] = value; - } else { - return cache ? cache[key] : null; +function eachNode(element, fn){ + var i, chldNodes = element[0].childNodes || [], size = chldNodes.length, chld; + for (i = 0; i < size; i++) { + if(!isTextNode(chld = chldNodes[i])) { + fn(jqLite(chld), i); } - }, - - removeData: function(){ - jqClearData(this[0]); - }, - - dealoc: function(){ - (function dealoc(element){ - jqClearData(element); - for ( var i = 0, children = element.childNodes; i < children.length; i++) { - dealoc(children[0]); - } - })(this[0]); - }, - - bind: function(type, fn){ - var element = this[0], - bind = this.data('bind'), - eventHandler; - if (!bind) this.data('bind', bind = {}); - eventHandler = bind[type]; - if (!eventHandler) { - bind[type] = eventHandler = function() { - var self = this; - foreach(eventHandler.fns, function(fn){ - fn.apply(self, arguments); - }); - }; - eventHandler.fns = []; - addEventListener(element, type, eventHandler); - } - eventHandler.fns.push(fn); - }, - - trigger: function(type) { - var cache = this.data('bind'); - if (cache) { - (cache[type] || noop)(); - } - }, - - click: function(fn) { - if (fn) - this.bind('click', fn); - else - this.trigger('click'); - }, - - eachTextNode: function(fn){ - var i, chldNodes = this[0].childNodes || [], size = chldNodes.length, chld; - for (i = 0; i < size; i++) { - if((chld = new JQLite(chldNodes[i])).isText()) { - fn(chld, i); - } - } - }, - - - eachNode: function(fn){ - var i, chldNodes = this[0].childNodes || [], size = chldNodes.length, chld; - for (i = 0; i < size; i++) { - if(!(chld = new JQLite(chldNodes[i])).isText()) { - fn(chld, i); - } - } - }, - - eachAttribute: function(fn){ - var i, attrs = this[0].attributes || [], size = attrs.length, chld, attr; - for (i = 0; i < size; i++) { - var attr = attrs[i]; - fn(attr.name, attr.value); - } - }, - - replaceWith: function(replaceNode) { - this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]); - }, - - remove: function() { - this.dealoc(); - this[0].parentNode.removeChild(this[0]); - }, - - removeAttr: function(name) { - this[0].removeAttribute(name); - }, - - after: function(element) { - this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling); - }, - - hasClass: function(selector) { - var className = " " + selector + " "; - if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { - return true; - } - return false; - }, - - addClass: function( selector ) { - if (!this.hasClass(selector)) { - this[0].className += ' ' + selector; - } - }, - - css: function(name, value) { - var style = this[0].style; - if (isString(name)) { - if (isDefined(value)) { - style[name] = value; - } else { - return style[name]; - } - } else { - extend(style, name); - } - }, - - attr: function(name, value){ - var e = this[0]; - if (isObject(name)) { - foreach(name, function(value, name){ - e.setAttribute(name, value); - }); - } else if (isDefined(value)) { - e.setAttribute(name, value); - } else { - return e.getAttribute(name); - } - }, - - text: function(value) { - if (isDefined(value)) { - this[0].textContent = value; - } - return this[0].textContent; - }, - - html: function(value) { - if (isDefined(value)) { - this[0].innerHTML = value; - } - return this[0].innerHTML; - }, - - parent: function() { return jqLite(this[0].parentNode);}, - isText: function() { return this[0].nodeType == Node.TEXT_NODE; }, - clone: function() { return jqLite(this[0].cloneNode(true)); } -}; + } +} -/////////////////////////////////// -//Compiler -////////////////////////////////// +function eachAttribute(element, fn){ + var i, attrs = element[0].attributes || [], size = attrs.length, chld, attr; + for (i = 0; i < size; i++) { + var attr = attrs[i]; + fn(attr.name, attr.value); + } +} -function Compiler(markup, directives, widgets){ - this.markup = markup; +function Compiler(textMarkup, attrMarkup, directives, widgets){ + this.textMarkup = textMarkup; + this.attrMarkup = attrMarkup; this.directives = directives; this.widgets = widgets; } @@ -291,8 +108,6 @@ Compiler.prototype = { elementName = element[0].nodeName, widgets = self.widgets, widget = widgets[elementName], - markup = self.markup, - markupSize = markup.length, directives = self.directives, descend = true, exclusive = false, @@ -300,7 +115,9 @@ Compiler.prototype = { template = new Template(), selfApi = { compile: bind(self, self.compile), - reference:function(name) {return jqLite(document.createComment(name));}, + comment:function(text) {return jqLite(document.createComment(text));}, + element:function(type) {return jqLite(document.createElement(type));}, + text:function(text) {return jqLite(document.createTextNode(text));}, descend: function(value){ if(isDefined(value)) descend = value; return descend;} }; @@ -308,14 +125,20 @@ Compiler.prototype = { template.addInit(widget.call(selfApi, element)); } else { // process markup for text nodes only - element.eachTextNode(function(textNode){ - for (var i = 0, text = textNode.text(); i < markupSize; i++) { - markup[i].call(selfApi, text, textNode, element); - } + eachTextNode(element, function(textNode){ + var text = textNode.text(); + foreach(self.textMarkup, function(markup){ + markup.call(selfApi, text, textNode, element); + }); }); // Process attributes/directives - element.eachAttribute(function(name, value){ + eachAttribute(element, function(name, value){ + foreach(self.attrMarkup, function(markup){ + markup.call(selfApi, value, name, element); + }); + }); + eachAttribute(element, function(name, value){ var directive = directives[name]; if (!exclusive && directive) { if (directive.exclusive) { @@ -333,7 +156,7 @@ Compiler.prototype = { // Process non text child nodes if (descend) { - element.eachNode(function(child, i){ + eachNode(element, function(child, i){ template.addChild(i, self.templatize(child)); }); } diff --git a/src/Filters.js b/src/Filters.js index 60d53fb9..dac8d31d 100644 --- a/src/Filters.js +++ b/src/Filters.js @@ -27,7 +27,7 @@ foreach({ jQuery(this.$element).toggleClass('ng-format-negative', amount < 0); return '$' + angularFilter['number'].apply(this, [amount, 2]); }, - + 'number': function(amount, fractionSize){ if (isNaN(amount) || !isFinite(amount)) { return ''; @@ -55,15 +55,15 @@ foreach({ } return text; }, - + 'date': function(amount) { }, - + 'json': function(object) { jQuery(this.$element).addClass("ng-monospace"); return toJson(object, true); }, - + 'trackPackage': (function(){ var MATCHERS = [ { name: "UPS", @@ -89,7 +89,7 @@ foreach({ var returnValue; foreach(MATCHERS, function(carrier){ foreach(carrier.regexp, function(regexp){ - if (regexp.test(tNo)) { + if (!returnValue && regexp.test(tNo)) { var text = carrier.name + ": " + trackingNo; var url = carrier.url + trackingNo; returnValue = new angularFilter.Meta({ @@ -97,19 +97,17 @@ foreach({ url:url, html: '' + text + '', trackingNo:trackingNo}); - _.breakLoop(); } }); - if (returnValue) _.breakLoop(); }); - if (returnValue) + if (returnValue) return returnValue; else if (trackingNo) return noMatch || new angularFilter.Meta({text:trackingNo + " is not recognized"}); else return null; };})(), - + 'link': function(obj, title) { var text = title || angularFilter.Meta.get(obj); var url = angularFilter.Meta.get(obj, "url") || angularFilter.Meta.get(obj); @@ -122,13 +120,13 @@ foreach({ } return obj; }, - - + + 'bytes': (function(){ var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB']; return function(size) { if(size === null) return ""; - + var suffix = 0; while (size > 1000) { size = size / 1024; @@ -142,7 +140,7 @@ foreach({ return txt + " " + SUFFIX[suffix]; }; })(), - + 'image': function(obj, width, height) { if (obj && obj.url) { var style = ""; @@ -155,36 +153,36 @@ foreach({ } return null; }, - + 'lowercase': function (obj) { var text = angularFilter.Meta.get(obj); return text ? ("" + text).toLowerCase() : text; }, - + 'uppercase': function (obj) { var text = angularFilter.Meta.get(obj); return text ? ("" + text).toUpperCase() : text; }, - + 'linecount': function (obj) { var text = angularFilter.Meta.get(obj); if (text==='' || !text) return 1; return text.split(/\n|\f/).length; }, - + 'if': function (result, expression) { return expression ? result : undefined; }, - + 'unless': function (result, expression) { return expression ? undefined : result; }, - + 'googleChartApi': extend( function(type, data, width, height) { data = data || {}; var chart = { - 'cht':type, + 'cht':type, 'chco':angularFilterGoogleChartApi['collect'](data, 'color'), 'chtt':angularFilterGoogleChartApi['title'](data), 'chdl':angularFilterGoogleChartApi['collect'](data, 'label'), @@ -210,7 +208,7 @@ foreach({ var values = seriesValues.join('|'); return values === "" ? null : "t:" + values; }, - + 'title': function(data){ var titles = []; var title = data['title'] || []; @@ -219,7 +217,7 @@ foreach({ }); return titles.join('|'); }, - + 'collect': function(data, key){ var outterValues = []; var count = 0; @@ -234,7 +232,7 @@ foreach({ }); return count?outterValues.join(','):null; }, - + 'encode': function(params, width, height) { width = width || 200; height = height || width; @@ -253,8 +251,8 @@ foreach({ } } ), - - + + 'qrcode': function(value, width, height) { return angularFilterGoogleChartApi['encode']({ 'cht':'qr', 'chl':encodeURIComponent(value)}, width, height); @@ -291,11 +289,11 @@ foreach({ return angularFilterGoogleChartApi('s', data, width, height); } }, - + 'html': function(html){ return new angularFilter.Meta({html:html}); }, - + 'linky': function(text){ if (!text) return text; function regExpEscape(text) { diff --git a/src/Scope.js b/src/Scope.js index a3e128b6..daafabb0 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -116,7 +116,7 @@ Scope.prototype = { }, compile: function(exp) { - if (isFunction(exp)) return exp; + if (isFunction(exp)) return bind(this.state, exp); var expFn = Scope.expressionCache[exp], self = this; if (!expFn) { var parser = new Parser(exp); diff --git a/src/directives.js b/src/directives.js index bbf68669..747da3f5 100644 --- a/src/directives.js +++ b/src/directives.js @@ -18,9 +18,50 @@ angularDirective("ng-bind", function(expression){ }; }); +var bindTemplateCache = {}; +function compileBindTemplate(template){ + var fn = bindTemplateCache[template]; + if (!fn) { + var bindings = []; + foreach(parseBindings(template), function(text){ + var exp = binding(text); + bindings.push(exp ? function(){ + return this.$eval(exp); + } : function(){ + return text; + }); + }); + bindTemplateCache[template] = fn = function(){ + var parts = [], self = this; + foreach(bindings, function(fn){ + parts.push(fn.call(self)); + }); + return parts.join(''); + }; + } + return fn; +}; +angularDirective("ng-bind-template", function(expression){ + var templateFn = compileBindTemplate(expression); + return function(element) { + var lastValue; + this.$addEval(function() { + var value = templateFn.call(this); + if (value != lastValue) { + element.text(value); + lastValue = value; + } + }); + }; +}); + angularDirective("ng-bind-attr", function(expression){ return function(element){ - this.$watch(expression, bind(element, element.attr)); + this.$addEval(function(){ + foreach(this.$eval(expression), function(value, key){ + element.attr(key, compileBindTemplate(value).call(this)); + }, this); + }); }; }); @@ -29,7 +70,7 @@ angularDirective("ng-non-bindable", function(){ }); angularDirective("ng-repeat", function(expression, element){ - var reference = this.reference("ng-repeat: " + expression), + var reference = this.comment("ng-repeat: " + expression), r = element.removeAttr('ng-repeat'), template = this.compile(element), match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), @@ -135,18 +176,12 @@ angularDirective("ng-hide", function(expression, element){ }); }; }); -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// - - - - -//widget related -//ng-validate, ng-required, ng-formatter -//ng-error +angularDirective("ng-style", function(expression, element){ + return function(element){ + this.$addEval(expression, function(value){ + element.css(value); + }); + }; +}); -//ng-scope ng-controller???? diff --git a/src/jqLite.js b/src/jqLite.js new file mode 100644 index 00000000..035a7a1b --- /dev/null +++ b/src/jqLite.js @@ -0,0 +1,185 @@ + +/////////////////////////////////// +//JQLite +////////////////////////////////// + +var jqCache = {}; +var jqName = 'ng-' + new Date().getTime(); +var jqId = 1; +function jqNextId() { return jqId++; } + +var addEventListener = window.document.attachEvent ? + function(element, type, fn) { + element.attachEvent('on' + type, fn); + } : function(element, type, fn) { + element.addEventListener(type, fn, false); + }; + +var removeEventListener = window.document.detachEvent ? + function(element, type, fn) { + element.detachEvent('on' + type, fn); + } : function(element, type, fn) { + element.removeEventListener(type, fn, false); + }; + +function jqClearData(element) { + var cacheId = element[jqName], + cache = jqCache[cacheId]; + if (cache) { + foreach(cache.bind || {}, function(fn, type){ + removeEventListener(element, type, fn); + }); + delete jqCache[cacheId]; + delete element[jqName]; + } +}; + +function JQLite(element) { + this[0] = element; +} + +function jqLite(element) { + if (typeof element == 'string') { + var div = document.createElement('div'); + div.innerHTML = element; + element = div.childNodes[0]; + } + return element instanceof JQLite ? element : new JQLite(element); +} + +JQLite.prototype = { + data: function(key, value) { + var element = this[0], + cacheId = element[jqName], + cache = jqCache[cacheId || -1]; + if (isDefined(value)) { + if (!cache) { + element[jqName] = cacheId = jqNextId(); + cache = jqCache[cacheId] = {}; + } + cache[key] = value; + } else { + return cache ? cache[key] : null; + } + }, + + removeData: function(){ + jqClearData(this[0]); + }, + + dealoc: function(){ + (function dealoc(element){ + jqClearData(element); + for ( var i = 0, children = element.childNodes; i < children.length; i++) { + dealoc(children[0]); + } + })(this[0]); + }, + + bind: function(type, fn){ + var element = this[0], + bind = this.data('bind'), + eventHandler; + if (!bind) this.data('bind', bind = {}); + eventHandler = bind[type]; + if (!eventHandler) { + bind[type] = eventHandler = function() { + var self = this; + foreach(eventHandler.fns, function(fn){ + fn.apply(self, arguments); + }); + }; + eventHandler.fns = []; + addEventListener(element, type, eventHandler); + } + eventHandler.fns.push(fn); + }, + + trigger: function(type) { + var cache = this.data('bind'); + if (cache) { + (cache[type] || noop)(); + } + }, + + click: function(fn) { + if (fn) + this.bind('click', fn); + else + this.trigger('click'); + }, + + replaceWith: function(replaceNode) { + this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]); + }, + + remove: function() { + this.dealoc(); + this[0].parentNode.removeChild(this[0]); + }, + + removeAttr: function(name) { + this[0].removeAttribute(name); + }, + + after: function(element) { + this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling); + }, + + hasClass: function(selector) { + var className = " " + selector + " "; + if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) { + return true; + } + return false; + }, + + addClass: function( selector ) { + if (!this.hasClass(selector)) { + this[0].className += ' ' + selector; + } + }, + + css: function(name, value) { + var style = this[0].style; + if (isString(name)) { + if (isDefined(value)) { + style[name] = value; + } else { + return style[name]; + } + } else { + extend(style, name); + } + }, + + attr: function(name, value){ + var e = this[0]; + if (isObject(name)) { + foreach(name, function(value, name){ + e.setAttribute(name, value); + }); + } else if (isDefined(value)) { + e.setAttribute(name, value); + } else { + return e.getAttribute(name); + } + }, + + text: function(value) { + if (isDefined(value)) { + this[0].textContent = value; + } + return this[0].textContent; + }, + + html: function(value) { + if (isDefined(value)) { + this[0].innerHTML = value; + } + return this[0].innerHTML; + }, + + parent: function() { return jqLite(this[0].parentNode);}, + clone: function() { return jqLite(this[0].cloneNode(true)); } +}; diff --git a/src/markup.js b/src/markup.js new file mode 100644 index 00000000..add7ce03 --- /dev/null +++ b/src/markup.js @@ -0,0 +1,64 @@ +function parseBindings(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; +}; + +function binding(string) { + var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); + return binding ? binding[1] : null; +}; + +function hasBindings(bindings) { + return bindings.length > 1 || Binder.binding(bindings[0]) !== null; +}; + +angularTextMarkup(function(text, textNode, parentElement) { + var bindings = parseBindings(text), + self = this; + if (isLeafNode(parentElement[0])) { + parentElement.attr('ng-bind-template', text); + } else { + var cursor = textNode, newElement; + foreach(parseBindings(text), function(text){ + var exp = binding(text); + if (exp) { + newElement = self.element('span'); + newElement.attr('ng-bind', exp); + } else { + newElement = self.text(text); + } + cursor.after(newElement); + cursor = newElement; + }); + } + textNode.remove(); +}); + +var NG_BIND_ATTR = 'ng-bind-attr'; +angularAttrMarkup(function(value, name, element){ + if (name.substr(0, 3) != 'ng-') { + var bindings = parseBindings(value), + bindAttr; + if (hasBindings(bindings)) { + element.removeAttr(name); + bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); + bindAttr[name] = value; + element.attr(NG_BIND_ATTR, toJson(bindAttr)); + } + } +}); diff --git a/src/widgets2.js b/src/widgets2.js index 0d7bbd49..b0f467d4 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,3 +1,19 @@ +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// +///////////////////////////////////////// + + + + + +//widget related +//ng-validate, ng-required, ng-formatter +//ng-error + +//ng-scope ng-controller???? + // -> angular.widget("inputtext", function(element) { var expression = element.attr('name'); @@ -49,7 +65,7 @@ angular.widget("colorpicker", function(element) { this.$watch(expression, function(cmyk){ element.setColor(formatter.format(cmyk)); }); - } + }; }); angular.widget("template", function(element) { -- cgit v1.2.3 From 1b976dc27d022c681d764d51a70a1af6a7e35dd6 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 23 Mar 2010 15:16:44 -0700 Subject: tweeter demo script --- src/scenario/bootstrap.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 1d40b9d0..b49530df 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -8,7 +8,7 @@ var parts = src.match(filename); return parts[1]; } - } + } })(); function addScript(path) { document.write(''); @@ -17,7 +17,7 @@ document.write(''); }; window.onload = function(){ - if (!_.stepper) { + if (!_.stepper) { _.stepper = function(collection, iterator, done){ var keys = _.keys(collection); function next() { @@ -38,7 +38,7 @@ }; addCSS("../../css/angular-scenario.css"); addScript("../../lib/underscore/underscore.js"); - addScript("../../lib/jquery/jquery-1.3.2.js"); + addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); addScript("_namespace.js"); addScript("Steps.js"); -- cgit v1.2.3 From 03ddc4570b786a7b945e1b40a16f29d2349c68b8 Mon Sep 17 00:00:00 2001 From: Shyam Seshadri Date: Wed, 24 Mar 2010 10:35:01 -0700 Subject: Fix parsing bug with strings for - --- src/Parser.js | 108 ++++++++++++++++++++++++++++++---------------------------- 1 file changed, 55 insertions(+), 53 deletions(-) (limited to 'src') diff --git a/src/Parser.js b/src/Parser.js index b59b21a7..941d37f7 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -40,7 +40,7 @@ Lexer.prototype = { return false; } }, - + parse: function() { var tokens = this.tokens; var OPERATORS = Lexer.OPERATORS; @@ -103,22 +103,22 @@ Lexer.prototype = { } return tokens; }, - + isNumber: function(ch) { return '0' <= ch && ch <= '9'; }, - + isWhitespace: function(ch) { return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v'; }, - + isIdent: function(ch) { return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || '_' == ch || ch == '$'; }, - + readNumber: function() { var number = ""; var start = this.index; @@ -135,7 +135,7 @@ Lexer.prototype = { this.tokens.push({index:start, text:number, fn:function(){return number;}}); }, - + readIdent: function() { var ident = ""; var start = this.index; @@ -157,15 +157,17 @@ Lexer.prototype = { } this.tokens.push({index:start, text:ident, fn:fn}); }, - + readString: function(quote) { var start = this.index; var dateParseLength = this.dateParseLength; this.index++; var string = ""; + var rawString = quote; var escape = false; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); + rawString += ch; if (escape) { if (ch == 'u') { var hex = this.text.substring(this.index + 1, this.index + 5); @@ -184,7 +186,7 @@ Lexer.prototype = { escape = true; } else if (ch == quote) { this.index++; - this.tokens.push({index:start, text:string, + this.tokens.push({index:start, text:rawString, string:string, fn:function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; @@ -199,7 +201,7 @@ Lexer.prototype = { this.text.substring(start) + "] starting at column '" + (start+1) + "' in expression '" + this.text + "'."; }, - + readRegexp: function(quote) { var start = this.index; this.index++; @@ -249,18 +251,18 @@ Parser.ZERO = function(){ Parser.prototype = { error: function(msg, token) { - throw "Token '" + token.text + - "' is " + msg + " at column='" + - (token.index + 1) + "' of expression '" + + throw "Token '" + token.text + + "' is " + msg + " at column='" + + (token.index + 1) + "' of expression '" + this.text + "' starting at '" + this.text.substring(token.index) + "'."; }, - + peekToken: function() { - if (this.tokens.length === 0) + if (this.tokens.length === 0) throw "Unexpected end of expression: " + this.text; return this.tokens[0]; }, - + peek: function(e1, e2, e3, e4) { var tokens = this.tokens; if (tokens.length > 0) { @@ -273,7 +275,7 @@ Parser.prototype = { } return false; }, - + expect: function(e1, e2, e3, e4){ var token = this.peek(e1, e2, e3, e4); if (token) { @@ -283,7 +285,7 @@ Parser.prototype = { } return false; }, - + consume: function(e1){ if (!this.expect(e1)) { var token = this.peek(); @@ -293,30 +295,30 @@ Parser.prototype = { this.text.substring(token.index) + "'."; } }, - + _unary: function(fn, right) { return function(self) { return fn(self, right(self)); }; }, - + _binary: function(left, fn, right) { return function(self) { return fn(self, left(self), right(self)); }; }, - + hasTokens: function () { return this.tokens.length > 0; }, - + assertAllConsumed: function(){ if (this.tokens.length !== 0) { throw "Did not understand '" + this.text.substring(this.tokens[0].index) + "' while evaluating '" + this.text + "'."; } }, - + statements: function(){ var statements = []; while(true) { @@ -335,7 +337,7 @@ Parser.prototype = { } } }, - + filterChain: function(){ var left = this.expression(); var token; @@ -347,15 +349,15 @@ Parser.prototype = { } } }, - + filter: function(){ return this._pipeFunction(angularFilter); }, - + validator: function(){ return this._pipeFunction(angularValidator); }, - + _pipeFunction: function(fnScope){ var fn = this.functionIdent(fnScope); var argsFn = []; @@ -373,7 +375,7 @@ Parser.prototype = { var _this = this; foreach(self, function(v, k) { if (k.charAt(0) == '$') { - _this[k] = v; + _this[k] = v; } }); }; @@ -386,11 +388,11 @@ Parser.prototype = { } } }, - + expression: function(){ return this.throwStmt(); }, - + throwStmt: function(){ if (this.expect('throw')) { var throwExp = this.assignment(); @@ -401,7 +403,7 @@ Parser.prototype = { return this.assignment(); } }, - + assignment: function(){ var left = this.logicalOR(); var token; @@ -417,7 +419,7 @@ Parser.prototype = { return left; } }, - + logicalOR: function(){ var left = this.logicalAND(); var token; @@ -429,7 +431,7 @@ Parser.prototype = { } } }, - + logicalAND: function(){ var left = this.equality(); var token; @@ -438,7 +440,7 @@ Parser.prototype = { } return left; }, - + equality: function(){ var left = this.relational(); var token; @@ -447,7 +449,7 @@ Parser.prototype = { } return left; }, - + relational: function(){ var left = this.additive(); var token; @@ -456,7 +458,7 @@ Parser.prototype = { } return left; }, - + additive: function(){ var left = this.multiplicative(); var token; @@ -465,7 +467,7 @@ Parser.prototype = { } return left; }, - + multiplicative: function(){ var left = this.unary(); var token; @@ -474,7 +476,7 @@ Parser.prototype = { } return left; }, - + unary: function(){ var token; if (this.expect('+')) { @@ -487,7 +489,7 @@ Parser.prototype = { return this.primary(); } }, - + functionIdent: function(fnScope) { var token = this.expect(); var element = token.text.split('.'); @@ -504,7 +506,7 @@ Parser.prototype = { } return instance; }, - + primary: function() { var primary; if (this.expect('(')) { @@ -540,7 +542,7 @@ Parser.prototype = { } return primary; }, - + closure: function(hasArgs) { var args = []; if (hasArgs) { @@ -566,7 +568,7 @@ Parser.prototype = { }; }; }, - + fieldAccess: function(object) { var field = this.expect().text; var fn = function (self){ @@ -575,7 +577,7 @@ Parser.prototype = { fn.isAssignable = field; return fn; }, - + objectIndex: function(obj) { var indexFn = this.expression(); this.consume(']'); @@ -592,7 +594,7 @@ Parser.prototype = { }; } }, - + functionCall: function(fn) { var argsFn = []; if (this.peekToken().text != ')') { @@ -614,7 +616,7 @@ Parser.prototype = { } }; }, - + // This is used with json array declaration arrayDeclaration: function () { var elementFns = []; @@ -632,12 +634,13 @@ Parser.prototype = { return array; }; }, - + object: function () { var keyValues = []; if (this.peekToken().text != '}') { do { - var key = this.expect().text; + var token = this.expect(), + key = token.string || token.text; this.consume(":"); var value = this.expression(); keyValues.push({key:key, value:value}); @@ -654,7 +657,7 @@ Parser.prototype = { return object; }; }, - + entityDeclaration: function () { var decl = []; while(this.hasTokens()) { @@ -671,7 +674,7 @@ Parser.prototype = { return code; }; }, - + entityDecl: function () { var entity = this.expect().text; var instance; @@ -690,16 +693,16 @@ Parser.prototype = { var document = Entity(); document['$$anchor'] = instance; self.scope.set(instance, document); - return "$anchor." + instance + ":{" + + return "$anchor." + instance + ":{" + instance + "=" + entity + ".load($anchor." + instance + ");" + - instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + + instance + ".$$anchor=" + angular['String']['quote'](instance) + ";" + "};"; } else { return ""; } }; }, - + watch: function () { var decl = []; while(this.hasTokens()) { @@ -716,7 +719,7 @@ Parser.prototype = { } }; }, - + watchDecl: function () { var anchorName = this.expect().text; this.consume(":"); @@ -734,4 +737,3 @@ Parser.prototype = { } }; - -- cgit v1.2.3 From 0c42eb9909d554807549cd3394e0ea0c715cc2d1 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 24 Mar 2010 16:13:42 -0700 Subject: input[type=text] now works with binding, validation, formatter, required --- src/Angular.js | 9 +++---- src/Compiler.js | 4 +--- src/Validators.js | 24 ++++++++++--------- src/jqLite.js | 34 +++++++++++++++----------- src/widgets2.js | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 108 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index 95f7325a..b76926b9 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,6 +44,8 @@ function extensionList(angular, name) { } var consoleNode, msie, + VALUE = 'value', + NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy slice = Array.prototype.slice, angular = window['angular'] || (window['angular'] = {}), @@ -92,6 +94,9 @@ function isObject(value){ return typeof value == 'object';} function isString(value){ return typeof value == 'string';} function isArray(value) { return value instanceof Array; } function isFunction(value){ return typeof value == 'function';} +function lowercase(value){ return isString(value) ? value.toLowerCase() : value; } +function uppercase(value){ return isString(value) ? value.toUpperCase() : value; } +function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }; function log(a, b, c){ var console = window['console']; @@ -244,10 +249,6 @@ function outerHTML(node) { return outerHTML; } -function trim(str) { - return str.replace(/^ */, '').replace(/ *$/, ''); -} - function toBoolean(value) { var v = ("" + value).toLowerCase(); if (v == 'f' || v == '0' || v == 'false' || v == 'no') diff --git a/src/Compiler.js b/src/Compiler.js index ba598a43..4423fcef 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -105,9 +105,7 @@ Compiler.prototype = { templatize: function(element){ var self = this, - elementName = element[0].nodeName, - widgets = self.widgets, - widget = widgets[elementName], + widget = self.widgets[element[0].nodeName], directives = self.directives, descend = true, exclusive = false, diff --git a/src/Validators.js b/src/Validators.js index b7efcb4a..cdff5e1a 100644 --- a/src/Validators.js +++ b/src/Validators.js @@ -1,4 +1,6 @@ foreach({ + 'noop': noop, + 'regexp': function(value, regexp, msg) { if (!value.match(regexp)) { return msg || @@ -7,7 +9,7 @@ foreach({ return null; } }, - + 'number': function(value, min, max) { var num = 1 * value; if (num == value) { @@ -19,40 +21,40 @@ foreach({ } return null; } else { - return "Value is not a number."; + return "Not a number"; } }, - + 'integer': function(value, min, max) { var numberError = angularValidator['number'](value, min, max); if (numberError) return numberError; if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) { - return "Value is not a whole number."; + return "Not a whole number"; } return null; }, - + '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)."; }, - + '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."; }, - + '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."; }, - + 'phone': function(value) { if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) { return null; @@ -62,14 +64,14 @@ foreach({ } return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly."; }, - + '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."; }, - + 'json': function(value) { try { fromJson(value); @@ -78,7 +80,7 @@ foreach({ return e.toString(); } }, - + 'asynchronous': function(text, asynchronousFn) { var stateKey = '$validateState'; var lastKey = '$lastKey'; diff --git a/src/jqLite.js b/src/jqLite.js index 035a7a1b..2ac3f6c9 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -77,22 +77,24 @@ JQLite.prototype = { }, bind: function(type, fn){ - var element = this[0], - bind = this.data('bind'), + var self = this, + element = self[0], + bind = self.data('bind'), eventHandler; if (!bind) this.data('bind', bind = {}); - eventHandler = bind[type]; - if (!eventHandler) { - bind[type] = eventHandler = function() { - var self = this; - foreach(eventHandler.fns, function(fn){ - fn.apply(self, arguments); - }); - }; - eventHandler.fns = []; - addEventListener(element, type, eventHandler); - } - eventHandler.fns.push(fn); + foreach(type.split(' '), function(type){ + eventHandler = bind[type]; + if (!eventHandler) { + bind[type] = eventHandler = function() { + foreach(eventHandler.fns, function(fn){ + fn.apply(self, arguments); + }); + }; + eventHandler.fns = []; + addEventListener(element, type, eventHandler); + } + eventHandler.fns.push(fn); + }); }, trigger: function(type) { @@ -134,6 +136,10 @@ JQLite.prototype = { return false; }, + removeClass: function(selector) { + this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", "")); + }, + addClass: function( selector ) { if (!this.hasClass(selector)) { this[0].className += ' ' + selector; diff --git a/src/widgets2.js b/src/widgets2.js index b0f467d4..cee0e49e 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,3 +1,72 @@ +function scopeAccessor(scope, element) { + var expr = element.attr('name'), + farmatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(farmatterName); + if (!expr) throw "Required field 'name' not found."; + if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + return { + get: function() { + return formatter['format'](scope.$eval(expr)); + }, + set: function(value) { + scope.$eval(expr + '=' + toJson(formatter['parse'](value))); + } + }; +} + +function domAccessor(element) { + var validatorName = element.attr('ng-validate') || NOOP, + validator = angularValidator(validatorName), + required = element.attr('ng-required'), + lastError; + required = required || required == ''; + if (!validator) throw "Validator named '" + validatorName + "' not found."; + function validate(value) { + var error = required && !trim(value) ? "Required" : validator(value); + if (error !== lastError) { + if (error) { + element.addClass(NG_VALIDATION_ERROR); + element.attr(NG_ERROR, error); + } else { + element.removeClass(NG_VALIDATION_ERROR); + element.removeAttr(NG_ERROR); + } + lastError = error; + } + return value; + } + return { + get: function(){ + return validate(element.attr(VALUE)); + }, + set: function(value){ + element.attr(VALUE, validate(value)); + } + }; +} + +var NG_ERROR = 'ng-error', + NG_VALIDATION_ERROR = 'ng-validation-error', + INPUT_META = { + 'text': ["", 'keyup change'] +}; + +angularWidget('INPUT', function input(element){ + var meta = INPUT_META[lowercase(element.attr('type'))]; + return meta ? function(element) { + var scope = scopeAccessor(this, element), + dom = domAccessor(element); + scope.set(dom.get() || meta[0]); + element.bind(meta[1], function(){ + scope.set(dom.get()); + }); + this.$watch(scope.get, dom.set); + } : 0; +}); + + + + ///////////////////////////////////////// ///////////////////////////////////////// ///////////////////////////////////////// @@ -6,8 +75,6 @@ - - //widget related //ng-validate, ng-required, ng-formatter //ng-error -- cgit v1.2.3 From f29f6a47c4d81c5b8e365a3dae307159f1b12968 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 24 Mar 2010 16:47:11 -0700 Subject: fixed .value vs attr(value) access --- src/Angular.js | 1 - src/jqLite.js | 7 +++++++ src/widgets2.js | 27 +++++++++++++++++---------- 3 files changed, 24 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index b76926b9..dc530921 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -44,7 +44,6 @@ function extensionList(angular, name) { } var consoleNode, msie, - VALUE = 'value', NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy slice = Array.prototype.slice, diff --git a/src/jqLite.js b/src/jqLite.js index 2ac3f6c9..2132bfc1 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -179,6 +179,13 @@ JQLite.prototype = { return this[0].textContent; }, + val: function(value) { + if (isDefined(value)) { + this[0].value = value; + } + return this[0].value; + }, + html: function(value) { if (isDefined(value)) { this[0].innerHTML = value; diff --git a/src/widgets2.js b/src/widgets2.js index cee0e49e..5425b5a4 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -36,23 +36,22 @@ function domAccessor(element) { return value; } return { - get: function(){ - return validate(element.attr(VALUE)); - }, - set: function(value){ - element.attr(VALUE, validate(value)); - } + get: function(){ return validate(element.val()); }, + set: function(value){ element.val(validate(value)); } }; } var NG_ERROR = 'ng-error', NG_VALIDATION_ERROR = 'ng-validation-error', + TEXT_META = ['', 'keyup change'], INPUT_META = { - 'text': ["", 'keyup change'] -}; + 'text': TEXT_META, + 'textarea': TEXT_META, + 'hidden': TEXT_META, + 'password': TEXT_META + }; -angularWidget('INPUT', function input(element){ - var meta = INPUT_META[lowercase(element.attr('type'))]; +function inputWidget(meta) { return meta ? function(element) { var scope = scopeAccessor(this, element), dom = domAccessor(element); @@ -62,6 +61,14 @@ angularWidget('INPUT', function input(element){ }); this.$watch(scope.get, dom.set); } : 0; +} + +angularWidget('INPUT', function input(element){ + return inputWidget(INPUT_META[lowercase(element[0].type)]); +}); + +angularWidget('TEXTAREA', function(){ + return inputWidget(INPUT_META['text']); }); -- cgit v1.2.3 From b814c79b58deeeeaa12b03261399ef80c0d6cc9f Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 13:01:08 -0700 Subject: checkbox and radio now working --- src/Parser.js | 1 + src/Scope.js | 6 +- src/jqLite.js | 13 +++-- src/widgets2.js | 176 +++++++++++++++++--------------------------------------- 4 files changed, 66 insertions(+), 130 deletions(-) (limited to 'src') diff --git a/src/Parser.js b/src/Parser.js index 941d37f7..81a2afdc 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -10,6 +10,7 @@ Lexer.OPERATORS = { 'null':function(self){return null;}, 'true':function(self){return true;}, 'false':function(self){return false;}, + 'undefined':noop, '+':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;}, diff --git a/src/Scope.js b/src/Scope.js index daafabb0..5f1cfdda 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -12,8 +12,10 @@ function Scope(initialState, name) { '$parent': initialState, '$watch': bind(self, self.addWatchListener), '$eval': bind(self, self.eval), - // change name to onEval? - '$addEval': bind(self, self.addEval) + '$bind': bind(self, bind, self), + // change name to autoEval? + '$addEval': bind(self, self.addEval), + '$updateView': bind(self, self.updateView) }); if (name == "ROOT") { self.state['$root'] = self.state; diff --git a/src/jqLite.js b/src/jqLite.js index 2132bfc1..7646bf98 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -71,7 +71,7 @@ JQLite.prototype = { (function dealoc(element){ jqClearData(element); for ( var i = 0, children = element.childNodes; i < children.length; i++) { - dealoc(children[0]); + dealoc(children[i]); } })(this[0]); }, @@ -86,9 +86,11 @@ JQLite.prototype = { eventHandler = bind[type]; if (!eventHandler) { bind[type] = eventHandler = function() { + var value = false; foreach(eventHandler.fns, function(fn){ - fn.apply(self, arguments); + value = value || fn.apply(self, arguments); }); + return value; }; eventHandler.fns = []; addEventListener(element, type, eventHandler); @@ -98,10 +100,9 @@ JQLite.prototype = { }, trigger: function(type) { - var cache = this.data('bind'); - if (cache) { - (cache[type] || noop)(); - } + var evnt = document.createEvent('MouseEvent'); + evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + this[0].dispatchEvent(evnt); }, click: function(fn) { diff --git a/src/widgets2.js b/src/widgets2.js index 5425b5a4..b67694b1 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -1,4 +1,4 @@ -function scopeAccessor(scope, element) { +function modelAccessor(scope, element) { var expr = element.attr('name'), farmatterName = element.attr('ng-format') || NOOP, formatter = angularFormatter(farmatterName); @@ -14,7 +14,7 @@ function scopeAccessor(scope, element) { }; } -function domAccessor(element) { +function valueAccessor(element) { var validatorName = element.attr('ng-validate') || NOOP, validator = angularValidator(validatorName), required = element.attr('ng-required'), @@ -41,135 +41,67 @@ function domAccessor(element) { }; } +function checkedAccessor(element) { + var domElement = element[0]; + return { + get: function(){ return !!domElement.checked; }, + set: function(value){ domElement.checked = !!value; } + }; +} + +function radioAccessor(element) { + var domElement = element[0]; + return { + get: function(){ return domElement.checked ? domElement.value : null; }, + set: function(value){ domElement.checked = value == domElement.value; } + }; +} + +function noopAccessor() { return { get: noop, set: noop }; } + var NG_ERROR = 'ng-error', NG_VALIDATION_ERROR = 'ng-validation-error', - TEXT_META = ['', 'keyup change'], - INPUT_META = { - 'text': TEXT_META, - 'textarea': TEXT_META, - 'hidden': TEXT_META, - 'password': TEXT_META + textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), + buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), + INPUT_TYPE = { + 'text': textWidget, + 'textarea': textWidget, + 'hidden': textWidget, + 'password': textWidget, + 'button': buttonWidget, + 'submit': buttonWidget, + 'reset': buttonWidget, + 'image': buttonWidget, + 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), + 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined) +// 'select-one': [null, 'change'], +// 'select-multiple': [[], 'change'], +// 'file': [{}, 'click'] }; -function inputWidget(meta) { - return meta ? function(element) { - var scope = scopeAccessor(this, element), - dom = domAccessor(element); - scope.set(dom.get() || meta[0]); - element.bind(meta[1], function(){ - scope.set(dom.get()); +function inputWidget(events, modelAccessor, viewAccessor, initValue) { + return function(element) { + var scope = this, + model = modelAccessor(scope, element), + view = viewAccessor(element), + action = element.attr('ng-action') || ''; + var value = view.get() || initValue; + if (isDefined(value)) model.set(value); + element.bind(events, function(){ + model.set(view.get()); + scope.$eval(action); }); - this.$watch(scope.get, dom.set); - } : 0; + scope.$watch(model.get, view.set); + }; } angularWidget('INPUT', function input(element){ - return inputWidget(INPUT_META[lowercase(element[0].type)]); -}); - -angularWidget('TEXTAREA', function(){ - return inputWidget(INPUT_META['text']); -}); - - - - -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// -///////////////////////////////////////// - - - -//widget related -//ng-validate, ng-required, ng-formatter -//ng-error - -//ng-scope ng-controller???? - -// -> -angular.widget("inputtext", function(element) { - var expression = element.attr('name'); - var formatter = this.formatter(element.attr('formatter')); - var validator = this.validator(element.attr('validator')); - - function validate(value) { - var error = validator(element); - if (error) { - element.addClass("ng-error"); - scope.markInvalid(this); //move out of scope - } else { - scope.clearInvalid(this); - } - } - - - element.keyup(this.withScope(function(){ - this.$evalSet(expression, formatter.parse(element.val())); - validate(element.val()); - })); - - return {watch: expression, apply: function(newValue){ - element.val(formatter.format(newValue)); - validate(element.val()); - }}; - -}); - -angular.widget("inputfile", function(element) { - -}); - -angular.widget("inputradio", function(element) { - -}); - - -// -angular.widget("colorpicker", function(element) { - var name = element.attr('datasource'); - var formatter = this.formatter(element.attr('ng-formatter')); - - element.colorPicker(this.withScope(function(selectedColor){ - this.$evalSet(name, formatter.parse(selectedColor)); - })); - - return function(){ - this.$watch(expression, function(cmyk){ - element.setColor(formatter.format(cmyk)); - }); + return function(element) { + this.$eval(element.attr('ng-init')||''); + (INPUT_TYPE[lowercase(element[0].type)] || noop).call(this, element); }; }); -angular.widget("template", function(element) { - var srcExpression = element.attr('src'); - var self = this; - return {watch:srcExpression, apply:function(src){ - $.load(src, function(html){ - self.destroy(element); - element.html(html); - self.compile(element); - }); - }}; +angularWidget('TEXTAREA', function(){ + return textWidget; }); - - -/** - * - * { - * withScope: //safely executes, with a try/catch. applies scope - * compile: - * widget: - * directive: - * validator: - * formatter: - * - * - * config: - * loadCSS: - * loadScript: - * loadTemplate: - * } - * - **/ -- cgit v1.2.3 From 4fa166866b97d4f4dbd21514dbd674347da0a109 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 14:43:05 -0700 Subject: input select-one now works --- src/Angular.js | 15 ++------------- src/Compiler.js | 13 +++++++------ src/markup.js | 48 ++++++++++++++++++++++++++++++------------------ src/widgets2.js | 26 ++++++++++++++------------ 4 files changed, 53 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index dc530921..0c6d081e 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -32,24 +32,13 @@ function extensionMap(angular, name) { }); } -function extensionList(angular, name) { - var extPoint, length = 0; - return angular[name] || (extPoint = angular[name] = function (fn, prop){ - if (isDefined(fn)) { - extPoint[length] = extend(fn, prop || {}); - length++; - } - return extPoint; - }); -} - var consoleNode, msie, NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy slice = Array.prototype.slice, angular = window['angular'] || (window['angular'] = {}), - angularTextMarkup = extensionList(angular, 'textMarkup'), - angularAttrMarkup = extensionList(angular, 'attrMarkup'), + angularTextMarkup = extensionMap(angular, 'textMarkup'), + angularAttrMarkup = extensionMap(angular, 'attrMarkup'), angularDirective = extensionMap(angular, 'directive'), angularWidget = extensionMap(angular, 'widget'), angularValidator = extensionMap(angular, 'validator'), diff --git a/src/Compiler.js b/src/Compiler.js index 4423fcef..47ab0c14 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -120,6 +120,7 @@ Compiler.prototype = { }; if (widget) { + descend = false; template.addInit(widget.call(selfApi, element)); } else { // process markup for text nodes only @@ -152,12 +153,12 @@ Compiler.prototype = { template.addInit(directive()); }); - // Process non text child nodes - if (descend) { - eachNode(element, function(child, i){ - template.addChild(i, self.templatize(child)); - }); - } + } + // Process non text child nodes + if (descend) { + eachNode(element, function(child, i){ + template.addChild(i, self.templatize(child)); + }); } return template.empty() ? null : template; } diff --git a/src/markup.js b/src/markup.js index add7ce03..5fb10779 100644 --- a/src/markup.js +++ b/src/markup.js @@ -27,30 +27,42 @@ function hasBindings(bindings) { return bindings.length > 1 || Binder.binding(bindings[0]) !== null; }; -angularTextMarkup(function(text, textNode, parentElement) { +angularTextMarkup('{{}}', function(text, textNode, parentElement) { var bindings = parseBindings(text), self = this; - if (isLeafNode(parentElement[0])) { - parentElement.attr('ng-bind-template', text); - } else { - var cursor = textNode, newElement; - foreach(parseBindings(text), function(text){ - var exp = binding(text); - if (exp) { - newElement = self.element('span'); - newElement.attr('ng-bind', exp); - } else { - newElement = self.text(text); - } - cursor.after(newElement); - cursor = newElement; - }); + if (hasBindings(bindings)) { + if (isLeafNode(parentElement[0])) { + parentElement.attr('ng-bind-template', text); + } else { + var cursor = textNode, newElement; + foreach(parseBindings(text), function(text){ + var exp = binding(text); + if (exp) { + newElement = self.element('span'); + newElement.attr('ng-bind', exp); + } else { + newElement = self.text(text); + } + cursor.after(newElement); + cursor = newElement; + }); + } + textNode.remove(); + } +}); + +angularTextMarkup('OPTION', function(text, textNode, parentElement){ + if (parentElement[0].nodeName == "OPTION") { + var select = document.createElement('select'); + select.insertBefore(parentElement[0].cloneNode(true), null); + if (!select.innerHTML.match(/.*<\/\s*option\s*>/gi)) { + parentElement.attr('value', text); + } } - textNode.remove(); }); var NG_BIND_ATTR = 'ng-bind-attr'; -angularAttrMarkup(function(value, name, element){ +angularAttrMarkup('{{}}', function(value, name, element){ if (name.substr(0, 3) != 'ng-') { var bindings = parseBindings(value), bindAttr; diff --git a/src/widgets2.js b/src/widgets2.js index b67694b1..a8d17105 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -73,8 +73,8 @@ var NG_ERROR = 'ng-error', 'reset': buttonWidget, 'image': buttonWidget, 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), - 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined) -// 'select-one': [null, 'change'], + 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined), + 'select-one': inputWidget('click', modelAccessor, valueAccessor, null) // 'select-multiple': [[], 'change'], // 'file': [{}, 'click'] }; @@ -84,9 +84,10 @@ function inputWidget(events, modelAccessor, viewAccessor, initValue) { var scope = this, model = modelAccessor(scope, element), view = viewAccessor(element), - action = element.attr('ng-action') || ''; - var value = view.get() || initValue; + action = element.attr('ng-action') || '', + value = view.get() || initValue; if (isDefined(value)) model.set(value); + this.$eval(element.attr('ng-init')||''); element.bind(events, function(){ model.set(view.get()); scope.$eval(action); @@ -95,13 +96,14 @@ function inputWidget(events, modelAccessor, viewAccessor, initValue) { }; } -angularWidget('INPUT', function input(element){ - return function(element) { - this.$eval(element.attr('ng-init')||''); - (INPUT_TYPE[lowercase(element[0].type)] || noop).call(this, element); - }; -}); +function inputWidgetSelector(element){ + return INPUT_TYPE[lowercase(element[0].type)] || noop; +} -angularWidget('TEXTAREA', function(){ - return textWidget; +angularWidget('INPUT', inputWidgetSelector); +angularWidget('TEXTAREA', inputWidgetSelector); +angularWidget('BUTTON', inputWidgetSelector); +angularWidget('SELECT', function(element){ + this.descend(true); + return inputWidgetSelector.call(this, element); }); -- cgit v1.2.3 From 0cc9b0732003451537a5bfc444fb6590f4ed103a Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 14:51:42 -0700 Subject: input select-multiple now works --- src/widgets2.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/widgets2.js b/src/widgets2.js index a8d17105..c4b39bc1 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -57,6 +57,26 @@ function radioAccessor(element) { }; } +function optionsAccessor(element) { + var options = element[0].options; + return { + get: function(){ + var values = []; + foreach(options, function(option){ + if (option.selected) values.push(option.value); + }); + return values; + }, + set: function(values){ + var keys = {}; + foreach(values, function(value){ keys[value] = true; }); + foreach(options, function(option){ + option.selected = keys[option.value]; + }); + } + }; +} + function noopAccessor() { return { get: noop, set: noop }; } var NG_ERROR = 'ng-error', @@ -74,8 +94,8 @@ var NG_ERROR = 'ng-error', 'image': buttonWidget, 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined), - 'select-one': inputWidget('click', modelAccessor, valueAccessor, null) -// 'select-multiple': [[], 'change'], + 'select-one': inputWidget('click', modelAccessor, valueAccessor, null), + 'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, []) // 'file': [{}, 'click'] }; @@ -85,7 +105,7 @@ function inputWidget(events, modelAccessor, viewAccessor, initValue) { model = modelAccessor(scope, element), view = viewAccessor(element), action = element.attr('ng-action') || '', - value = view.get() || initValue; + value = view.get() || copy(initValue); if (isDefined(value)) model.set(value); this.$eval(element.attr('ng-init')||''); element.bind(events, function(){ -- cgit v1.2.3 From d934054cfc22325d817eb0643dc061f9d212804d Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 22:03:11 -0700 Subject: major refactoring of scope --- src/Angular.js | 21 ------ src/Compiler.js | 17 ++--- src/Scope.js | 192 ++++++++++++++++++++++++++++++++++++++++++++++++++---- src/directives.js | 120 +++++++++++++++++----------------- src/widgets2.js | 2 +- 5 files changed, 245 insertions(+), 107 deletions(-) (limited to 'src') diff --git a/src/Angular.js b/src/Angular.js index 0c6d081e..0cb89bbe 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -208,27 +208,6 @@ function bind(_this, _function) { }; } -function bindTry(_this, _function) { - var args = arguments, - last = args.length - 1, - curryArgs = slice.call(args, 2, last), - exceptionHandler = args[last]; - return function() { - try { - return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length))); - } catch (e) { - if (e = exceptionHandler(e)) throw e; - } - }; -} - -function errorHandlerFor(element) { - return function(error){ - element.attr('ng-error', angular.toJson(error)); - element.addClass('ng-exception'); - }; -} - function outerHTML(node) { var temp = document.createElement('div'); temp.appendChild(node); diff --git a/src/Compiler.js b/src/Compiler.js index 47ab0c14..3b492ebe 100644 --- a/src/Compiler.js +++ b/src/Compiler.js @@ -14,7 +14,7 @@ Template.prototype = { init: function(element, scope) { element = jqLite(element); foreach(this.inits, function(fn) { - scope.apply(fn, element); + scope.$tryEval(fn, element, element); }); var i, @@ -92,14 +92,11 @@ Compiler.prototype = { rawElement = jqLite(rawElement); var template = this.templatize(rawElement) || new Template(); return function(element, parentScope){ - var scope = new Scope(parentScope); - scope.element = element; - // todo return should be a scope with everything already set on it as element - return { - scope: scope, - element:element, - init: bind(template, template.init, element, scope) - }; + var model = scope(parentScope); + return extend(model, { + $element:element, + $init: bind(template, template.init, element, model) + }); }; }, @@ -144,7 +141,7 @@ Compiler.prototype = { exclusive = true; directiveQueue = []; } - directiveQueue.push(bindTry(selfApi, directive, value, element, errorHandlerFor(element))); + directiveQueue.push(bind(selfApi, directive, value, element)); } }); diff --git a/src/Scope.js b/src/Scope.js index 5f1cfdda..ccf55077 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -53,6 +53,21 @@ Scope.getter = function(instance, path) { return instance; }; +Scope.setter = function(instance, path, value){ + var element = path.split('.'); + for ( var i = 0; element.length > 1; i++) { + var key = element.shift(); + var newInstance = instance[key]; + if (!newInstance) { + newInstance = {}; + instance[key] = newInstance; + } + instance = newInstance; + } + instance[element.shift()] = value; + return value; +}; + Scope.prototype = { // TODO: rename to update? or eval? updateView: function() { @@ -98,19 +113,8 @@ Scope.prototype = { set: function(path, value) { // log('SCOPE.set', 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; + return Scope.setter(instance, path, value); }, setEval: function(expressionText, value) { @@ -134,9 +138,9 @@ Scope.prototype = { }; }, - eval: function(expressionText, context) { + eval: function(exp, context) { // log('Scope.eval', expressionText); - return this.compile(expressionText)(context); + return this.compile(exp)(context); }, //TODO: Refactor. This function needs to be an execution closure for widgets @@ -241,3 +245,163 @@ Scope.prototype = { fn.apply(this.state, slice.call(arguments, 1, arguments.length)); } }; + +////////////////////////////// + +function getter(instance, path) { + if (!path) return instance; + var element = path.split('.'); + var key; + var lastInstance = instance; + var len = element.length; + for ( var i = 0; i < len; i++) { + key = element[i]; + if (!key.match(/^[\$\w][\$\w\d]*$/)) + throw "Expression '" + path + "' is not a valid expression for accesing variables."; + if (instance) { + lastInstance = instance; + instance = instance[key]; + } + if (_.isUndefined(instance) && key.charAt(0) == '$') { + var type = angular['Global']['typeOf'](lastInstance); + type = angular[type.charAt(0).toUpperCase()+type.substring(1)]; + var fn = type ? type[[key.substring(1)]] : undefined; + if (fn) { + instance = _.bind(fn, lastInstance, lastInstance); + return instance; + } + } + } + if (typeof instance === 'function' && !instance['$$factory']) { + return bind(lastInstance, instance); + } + return instance; +}; + +function setter(instance, path, value){ + var element = path.split('.'); + for ( var i = 0; element.length > 1; i++) { + var key = element.shift(); + var newInstance = instance[key]; + if (!newInstance) { + newInstance = {}; + instance[key] = newInstance; + } + instance = newInstance; + } + instance[element.shift()] = value; + return value; +}; + +var compileCache = {}; +function expressionCompile(exp){ + if (isFunction(exp)) return exp; + var expFn = compileCache[exp]; + if (!expFn) { + var parser = new Parser(exp); + expFn = parser.statements(); + parser.assertAllConsumed(); + compileCache[exp] = expFn; + } + // return expFn + // TODO(remove this hack) + return function(){ + return expFn({ + scope: { + set: this.$set, + get: this.$get + } + }); + }; +}; + +var NON_RENDERABLE_ELEMENTS = { + '#text': 1, '#comment':1, 'TR':1, 'TH':1 +}; + +function isRenderableElement(element){ + return element && element[0] && !NON_RENDERABLE_ELEMENTS[element[0].nodeName]; +} + +function rethrow(e) { throw e; } +function errorHandlerFor(element) { + while (!isRenderableElement(element)) { + element = element.parent() || jqLite(document.body); + } + return function(error) { + element.attr('ng-error', angular.toJson(error)); + element.addClass('ng-exception'); + }; +} + +function scope(parent, Class) { + function Parent(){} + function API(){} + function Behavior(){} + + var instance, behavior, api, watchList = [], evalList = []; + + Class = Class || noop; + parent = Parent.prototype = parent || {}; + api = API.prototype = new Parent(); + behavior = Behavior.prototype = extend(new API(), Class.prototype); + instance = new Behavior(); + + extend(api, { + $parent: parent, + $bind: bind(instance, bind, instance), + $get: bind(instance, getter, instance), + $set: bind(instance, setter, instance), + + $eval: function(exp) { + if (isDefined(exp)) { + return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length)); + } else { + foreach(evalList, function(eval) { + instance.$tryEval(eval.fn, eval.handler); + }); + foreach(watchList, function(watch) { + var value = instance.$tryEval(watch.watch, watch.handler); + if (watch.last !== value) { + instance.$tryEval(watch.listener, watch.handler, value, watch.last); + watch.last = value; + } + }); + } + }, + + $tryEval: function (expression, exceptionHandler) { + try { + return expressionCompile(expression).apply(instance, slice.call(arguments, 2, arguments.length)); + } catch (e) { + error(e); + if (isFunction(exceptionHandler)) { + exceptionHandler(e); + } else if (exceptionHandler) { + errorHandlerFor(exceptionHandler)(e); + } + } + }, + + $watch: function(watchExp, listener, exceptionHandler) { + var watch = expressionCompile(watchExp); + watchList.push({ + watch: watch, + last: watch.call(instance), + handler: exceptionHandler, + listener:expressionCompile(listener) + }); + }, + + $onEval: function(expr, exceptionHandler){ + evalList.push({ + fn: expressionCompile(expr), + handler: exceptionHandler + }); + } + }); + + Class.apply(instance, slice.call(arguments, 2, arguments.length)); + + return instance; +} diff --git a/src/directives.js b/src/directives.js index 747da3f5..10476c77 100644 --- a/src/directives.js +++ b/src/directives.js @@ -1,12 +1,12 @@ angularDirective("ng-init", function(expression){ - return function(){ - this.$eval(expression); + return function(element){ + this.$tryEval(expression, element); }; }); angularDirective("ng-eval", function(expression){ - return function(){ - this.$addEval(expression); + return function(element){ + this.$onEval(expression, element); }; }); @@ -14,7 +14,7 @@ angularDirective("ng-bind", function(expression){ return function(element) { this.$watch(expression, function(value){ element.text(value); - }); + }, element); }; }); @@ -45,23 +45,23 @@ angularDirective("ng-bind-template", function(expression){ var templateFn = compileBindTemplate(expression); return function(element) { var lastValue; - this.$addEval(function() { + this.$onEval(function() { var value = templateFn.call(this); if (value != lastValue) { element.text(value); lastValue = value; } - }); + }, element); }; }); angularDirective("ng-bind-attr", function(expression){ return function(element){ - this.$addEval(function(){ + this.$onEval(function(){ foreach(this.$eval(expression), function(value, key){ element.attr(key, compileBindTemplate(value).call(this)); }, this); - }); + }, element); }; }); @@ -70,76 +70,73 @@ angularDirective("ng-non-bindable", function(){ }); angularDirective("ng-repeat", function(expression, element){ - var reference = this.comment("ng-repeat: " + expression), - r = element.removeAttr('ng-repeat'), - template = this.compile(element), - match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), - lhs, rhs, valueIdent, keyIdent; - if (! match) { - throw "Expected ng-repeat in form of 'item in collection' but got '" + + element.removeAttr('ng-repeat'); + element.replaceWith(this.comment("ng-repeat: " + expression)); + var template = this.compile(element); + return function(reference){ + var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), + lhs, rhs, valueIdent, keyIdent; + if (! match) { + throw "Expected ng-repeat in form of 'item in collection' but got '" + expression + "'."; - } - lhs = match[1]; - rhs = match[2]; - match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); - if (!match) { - throw "'item' in 'item in collection' should be identifier or (key, value) but got '" + + } + lhs = match[1]; + rhs = match[2]; + match = lhs.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/); + if (!match) { + throw "'item' in 'item in collection' should be identifier or (key, value) but got '" + keyValue + "'."; - } - valueIdent = match[3] || match[1]; - keyIdent = match[2]; + } + valueIdent = match[3] || match[1]; + keyIdent = match[2]; - var parent = element.parent(); - element.replaceWith(reference); - return function(){ - var children = [], - currentScope = this; - this.$addEval(rhs, function(items){ + var children = [], currentScope = this; + this.$onEval(function(){ var index = 0, childCount = children.length, childScope, lastElement = reference; - foreach(items || [], function(value, key){ + foreach(this.$tryEval(rhs, reference), function(value, key){ if (index < childCount) { // reuse existing child childScope = children[index]; } else { // grow children childScope = template(element.clone(), currentScope); - childScope.init(); - childScope.scope.set('$index', index); - childScope.element.attr('ng-index', index); - lastElement.after(childScope.element); + lastElement.after(childScope.$element); + childScope.$index = index; + childScope.$element.attr('ng-index', index); + childScope.$init(); children.push(childScope); } - childScope.scope.set(valueIdent, value); - if (keyIdent) childScope.scope.set(keyIdent, key); - childScope.scope.updateView(); - lastElement = childScope.element; + childScope[valueIdent] = value; + if (keyIdent) childScope[keyIdent] = key; + childScope.$eval(); + lastElement = childScope.$element; index ++; }); // shrink children while(children.length > index) { - children.pop().element.remove(); + children.pop().$element.remove(); } - }); + }, reference); }; }, {exclusive: true}); angularDirective("ng-action", function(expression, element){ - return function(){ + return function(element){ var self = this; element.click(function(){ - self.$eval(expression); + self.$tryEval(expression, element); }); }; }); angularDirective("ng-watch", function(expression, element){ var match = expression.match(/^([^.]*):(.*)$/); - if (!match) { - throw "Expecting watch expression 'ident_to_watch: watch_statement' got '" - + expression + "'"; - } - return function(){ - this.$watch(match[1], match[2]); + return function(element){ + if (!match) { + throw "Expecting watch expression 'ident_to_watch: watch_statement' got '" + + expression + "'"; + } + this.$watch(match[1], match[2], element); }; }); @@ -147,12 +144,13 @@ function ngClass(selector) { return function(expression, element){ var existing = element[0].className + ' '; return function(element){ - this.$addEval(expression, function(value){ + this.$onEval(function(){ + var value = this.$eval(expression); if (selector(this.$index)) { if (isArray(value)) value = value.join(' '); element[0].className = (existing + value).replace(/\s\s+/g, ' '); } - }); + }, element); }; }; } @@ -163,25 +161,25 @@ angularDirective("ng-class-even", ngClass(function(i){return i % 2 == 0;})); angularDirective("ng-show", function(expression, element){ return function(element){ - this.$addEval(expression, function(value){ - element.css('display', toBoolean(value) ? '' : 'none'); - }); + this.$onEval(function(){ + element.css('display', toBoolean(this.$eval(expression)) ? '' : 'none'); + }, element); }; }); angularDirective("ng-hide", function(expression, element){ return function(element){ - this.$addEval(expression, function(value){ - element.css('display', toBoolean(value) ? 'none' : ''); - }); + this.$onEval(function(){ + element.css('display', toBoolean(this.$eval(expression)) ? 'none' : ''); + }, element); }; }); angularDirective("ng-style", function(expression, element){ return function(element){ - this.$addEval(expression, function(value){ - element.css(value); - }); + this.$onEval(function(){ + element.css(this.$eval(expression)); + }, element); }; }); diff --git a/src/widgets2.js b/src/widgets2.js index c4b39bc1..21da3986 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -96,7 +96,7 @@ var NG_ERROR = 'ng-error', 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined), 'select-one': inputWidget('click', modelAccessor, valueAccessor, null), 'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, []) -// 'file': [{}, 'click'] +// 'file': fileWidget??? }; function inputWidget(events, modelAccessor, viewAccessor, initValue) { -- cgit v1.2.3 From 1990cbbf2817e04657ccd616da1d9d6b78cc2949 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 25 Mar 2010 22:07:36 -0700 Subject: added few extra tests --- src/widgets2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/widgets2.js b/src/widgets2.js index 21da3986..04045426 100644 --- a/src/widgets2.js +++ b/src/widgets2.js @@ -110,7 +110,7 @@ function inputWidget(events, modelAccessor, viewAccessor, initValue) { this.$eval(element.attr('ng-init')||''); element.bind(events, function(){ model.set(view.get()); - scope.$eval(action); + scope.$tryEval(action, element); }); scope.$watch(model.get, view.set); }; -- cgit v1.2.3 From 258ca5f16581f0e8befa493644225a02ae2fc002 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Fri, 26 Mar 2010 16:27:18 -0700 Subject: moved all uneeded files out, widgets.html works, tests horribly broken --- src/API.js | 325 ------------- src/Angular.js | 234 +--------- src/Binder.js | 356 -------------- src/Compiler.js | 11 +- src/ControlBar.js | 72 --- src/DataStore.js | 330 ------------- src/Formatters.js | 11 +- src/JSON.js | 4 +- src/Model.js | 65 --- src/Scope.js | 278 +---------- src/Server.js | 68 --- src/UrlWatcher.js | 62 +++ src/Users.js | 35 -- src/Widgets.js | 927 ++++++------------------------------- src/angular-bootstrap.js | 61 ++- src/apis.js | 328 +++++++++++++ src/delete/Binder.js | 356 ++++++++++++++ src/delete/Scope.js | 407 ++++++++++++++++ src/delete/Widgets.js | 806 ++++++++++++++++++++++++++++++++ src/directives.js | 15 +- src/jqLite.js | 16 +- src/markup.js | 76 --- src/markups.js | 76 +++ src/moveToAngularCom/ControlBar.js | 72 +++ src/moveToAngularCom/DataStore.js | 330 +++++++++++++ src/moveToAngularCom/Model.js | 65 +++ src/moveToAngularCom/Server.js | 68 +++ src/moveToAngularCom/Users.js | 35 ++ src/widgets2.js | 129 ------ 29 files changed, 2853 insertions(+), 2765 deletions(-) delete mode 100644 src/API.js delete mode 100644 src/Binder.js delete mode 100644 src/ControlBar.js delete mode 100644 src/DataStore.js delete mode 100644 src/Model.js delete mode 100644 src/Server.js create mode 100644 src/UrlWatcher.js delete mode 100644 src/Users.js create mode 100644 src/apis.js create mode 100644 src/delete/Binder.js create mode 100644 src/delete/Scope.js create mode 100644 src/delete/Widgets.js delete mode 100644 src/markup.js create mode 100644 src/markups.js create mode 100644 src/moveToAngularCom/ControlBar.js create mode 100644 src/moveToAngularCom/DataStore.js create mode 100644 src/moveToAngularCom/Model.js create mode 100644 src/moveToAngularCom/Server.js create mode 100644 src/moveToAngularCom/Users.js delete mode 100644 src/widgets2.js (limited to 'src') diff --git a/src/API.js b/src/API.js deleted file mode 100644 index ce690ad1..00000000 --- a/src/API.js +++ /dev/null @@ -1,325 +0,0 @@ -var angularGlobal = { - 'typeOf':function(obj){ - if (obj === null) return "null"; - var type = typeof obj; - if (type == "object") { - if (obj instanceof Array) return "array"; - if (obj instanceof Date) return "date"; - if (obj.nodeType == 1) return "element"; - } - return type; - } -}; - -var angularCollection = {}; -var angularObject = {}; -var angularArray = { - '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 = 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 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; - } - merge(mergeValue, value); - return array; - } -}; - -var angularString = { - '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; - } -}; - -var angularDate = { - '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'; - } - }; - -var angularFunction = { - 'compile':function(expression) { - if (_.isFunction(expression)){ - return expression; - } else if (expression){ - var scope = new Scope(); - return function($) { - scope.state = $; - return scope.eval(expression); - }; - } else { - return function($){return $;}; - } - } -}; - -function defineApi(dst, chain, underscoreNames){ - var lastChain = _.last(chain); - foreach(underscoreNames, function(name){ - lastChain[name] = _[name]; - }); - angular[dst] = angular[dst] || {}; - foreach(chain, function(parent){ - extend(angular[dst], parent); - }); -} -defineApi('Global', [angularGlobal], - ['extend', 'clone','isEqual', - 'isElement', 'isArray', 'isFunction', 'isUndefined']); -defineApi('Collection', [angularGlobal, angularCollection], - ['each', 'map', 'reduce', 'reduceRight', 'detect', - 'select', 'reject', 'all', 'any', 'include', - 'invoke', 'pluck', 'max', 'min', 'sortBy', - 'sortedIndex', 'toArray', 'size']); -defineApi('Array', [angularGlobal, angularCollection, angularArray], - ['first', 'last', 'compact', 'flatten', 'without', - 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); -defineApi('Object', [angularGlobal, angularCollection, angularObject], - ['keys', 'values']); -defineApi('String', [angularGlobal, angularString], []); -defineApi('Date', [angularGlobal, angularDate], []); -//IE bug -angular['Date']['toString'] = angularDate['toString']; -defineApi('Function', [angularGlobal, angularCollection, angularFunction], - ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); diff --git a/src/Angular.js b/src/Angular.js index 0cb89bbe..c3562e84 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1,22 +1,5 @@ if (typeof document.getAttribute == 'undefined') document.getAttribute = function() {}; -if (typeof Node == 'undefined') { - //TODO: can we get rid of this? - Node = { - ELEMENT_NODE : 1, - ATTRIBUTE_NODE : 2, - TEXT_NODE : 3, - CDATA_SECTION_NODE : 4, - ENTITY_REFERENCE_NODE : 5, - ENTITY_NODE : 6, - PROCESSING_INSTRUCTION_NODE : 7, - COMMENT_NODE : 8, - DOCUMENT_NODE : 9, - DOCUMENT_TYPE_NODE : 10, - DOCUMENT_FRAGMENT_NODE : 11, - NOTATION_NODE : 12 - }; -} function noop() {} function identity($) {return $;} @@ -32,9 +15,11 @@ function extensionMap(angular, name) { }); } -var consoleNode, msie, +var consoleNode, NOOP = 'noop', jQuery = window['jQuery'] || window['$'], // weirdness to make IE happy + _ = window['_'], + jqLite = jQuery, slice = Array.prototype.slice, angular = window['angular'] || (window['angular'] = {}), angularTextMarkup = extensionMap(angular, 'textMarkup'), @@ -77,6 +62,7 @@ function extend(dst, obj) { return dst; } +function isUndefined(value){ return typeof value == 'undefined'; } function isDefined(value){ return typeof value != 'undefined'; } function isObject(value){ return typeof value == 'object';} function isString(value){ return typeof value == 'string';} @@ -85,6 +71,12 @@ function isFunction(value){ return typeof value == 'function';} function lowercase(value){ return isString(value) ? value.toLowerCase() : value; } function uppercase(value){ return isString(value) ? value.toUpperCase() : value; } function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }; +function includes(array, obj) { + for ( var i = 0; i < array.length; i++) { + if (obj === array[i]) return true; + } + return false; +} function log(a, b, c){ var console = window['console']; @@ -154,18 +146,18 @@ function copy(source, destination){ if (!destination) { if (!source) { return source; - } else if (_.isArray(source)) { + } else if (isArray(source)) { return copy(source, []); } else { return copy(source, {}); } } else { - if (_.isArray(source)) { + if (isArray(source)) { while(destination.length) { destination.pop(); } } else { - _(destination).each(function(value, key){ + foreach(function(value, key){ delete destination[key]; }); } @@ -236,201 +228,19 @@ function merge(src, dst) { } } -// //////////////////////////// -// UrlWatcher -// //////////////////////////// - -function UrlWatcher(location) { - this.location = location; - this.delay = 25; - this.setTimeout = function(fn, delay) { - window.setTimeout(fn, delay); - }; - this.listener = function(url) { - return url; - }; - this.expectedUrl = location.href; -} - -UrlWatcher.prototype = { - listen: function(fn){ - this.listener = fn; - }, - watch: function() { - var self = this; - var pull = function() { - if (self.expectedUrl !== self.location.href) { - var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/); - if (notify) { - if (!self.expectedUrl.match(/#/)) { - self.expectedUrl += "#"; - } - self.location.href = self.expectedUrl; - var id = '_iframe_notify_' + notify[1]; - var notifyFn = angularCallbacks[id]; - delete angularCallbacks[id]; - try { - (notifyFn||noop)(); - } catch (e) { - alert(e); - } - } else { - self.listener(self.location.href); - self.expectedUrl = self.location.href; - } - } - self.setTimeout(pull, self.delay); - }; - pull(); - }, - - set: function(url) { - var existingURL = this.location.href; - if (!existingURL.match(/#/)) - existingURL += '#'; - if (existingURL != url) - this.location.href = url; - this.existingURL = url; - }, - - get: function() { - return window.location.href; - } -}; - ///////////////////////////////////////////////// -function configureJQueryPlugins() { - var fn = jQuery['fn']; - fn['scope'] = function() { - var element = this; - while (element && element.get(0)) { - var scope = element.data("scope"); - if (scope) - return scope; - element = element.parent(); - } - return null; - }; - fn['controller'] = function() { - return this.data('controller') || NullController.instance; - }; -} - -function configureLogging(config) { - if (config.debug == 'console' && !consoleNode) { - consoleNode = document.createElement("div"); - consoleNode.id = 'ng-console'; - document.getElementsByTagName('body')[0].appendChild(consoleNode); - log = function() { - consoleLog('ng-console-info', arguments); - }; - console.error = function() { - consoleLog('ng-console-error', arguments); - }; - } -} - -function exposeMethods(obj, methods){ - var bound = {}; - foreach(methods, function(fn, name){ - bound[name] = _(fn).bind(obj); - }); - return bound; -} - -function wireAngular(element, config) { - var widgetFactory = new WidgetFactory(config['server'], config['database']); - var binder = new Binder(element[0], widgetFactory, datastore, config['location'], config); - binder.updateListeners.push(config.onUpdateView); - var controlBar = new ControlBar(element.find('body'), config['server'], config['database']); - var onUpdate = function(){binder.updateView();}; - var server = config['database'] =="$MEMORY" ? - new FrameServer(window) : - new Server(config['server'], jQuery['getScript']); - server = new VisualServer(server, new NullStatus(element.find('body')), onUpdate); - var users = new Users(server, controlBar); - var databasePath = '/data/' + config['database']; - var post = function(request, callback){ - server.request("POST", databasePath, request, callback); - }; - var datastore = new DataStore(post, users, binder.anchor); - binder.datastore = datastore; - binder.updateListeners.push(function(){datastore.flush();}); - var scope = new Scope({ - '$anchor' : binder.anchor, - '$updateView': _(binder.updateView).bind(binder), - '$config' : config, - '$invalidWidgets': [], - '$console' : window.console, - '$datastore' : exposeMethods(datastore, { - 'load': datastore.load, - 'loadMany': datastore.loadMany, - 'loadOrCreate': datastore.loadOrCreate, - 'loadAll': datastore.loadAll, - 'save': datastore.save, - 'remove': datastore.remove, - 'flush': datastore.flush, - 'query': datastore.query, - 'entity': datastore.entity, - 'entities': datastore.entities, - 'documentCountsByUser': datastore.documentCountsByUser, - 'userDocumentIdsByEntity': datastore.userDocumentIdsByEntity, - 'join': datastore.join - }), - '$save' : function(callback) { - datastore.saveScope(scope.state, callback, binder.anchor); - }, - '$window' : window, - '$uid' : function() { - return "" + new Date().getTime(); - }, - '$users' : users - }, "ROOT"); - - element.data('scope', scope); - binder.entity(scope); - binder.compile(); - controlBar.bind(); - - //TODO: remove this code - new PopUp(element).bind(); - - var self = _(exposeMethods(scope, { - 'set': scope.set, - 'get': scope.get, - 'eval': scope.eval - })).extend({ - 'init':function(){ - config['location']['listen'](_(binder.onUrlChange).bind(binder)); - binder.parseAnchor(); - binder.executeInit(); - binder.updateView(); - return self; - }, - 'element':element[0], - 'updateView': _(binder.updateView).bind(binder), - 'config':config - }); - return self; -} - -angular['startUrlWatcher'] = function(){ - var watcher = new UrlWatcher(window['location']); - watcher.watch(); - return exposeMethods(watcher, {'listen':watcher.listen, 'set':watcher.set, 'get':watcher.get}); -}; angular['compile'] = function(element, config) { - jQuery = window['jQuery']; - msie = jQuery['browser']['msie']; - config = _({ + config = extend({ 'onUpdateView': noop, 'server': "", 'location': {'get':noop, 'set':noop, 'listen':noop} - }).extend(config||{}); - - configureLogging(config); - configureJQueryPlugins(); - - return wireAngular(jQuery(element), config); + }, config||{}); + + var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); + $element = jqLite(element), + rootScope = { + '$window': window + }; + return rootScope['$root'] = compiler.compile($element)($element, rootScope); }; diff --git a/src/Binder.js b/src/Binder.js deleted file mode 100644 index 9fc32513..00000000 --- a/src/Binder.js +++ /dev/null @@ -1,356 +0,0 @@ -function Binder(doc, widgetFactory, datastore, location, config) { - this.doc = doc; - this.location = location; - this.datastore = datastore; - this.anchor = {}; - this.widgetFactory = widgetFactory; - this.config = config || {}; - this.updateListeners = []; -} - -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; -}; - -Binder.hasBinding = function(string) { - var bindings = Binder.parseBindings(string); - return bindings.length > 1 || Binder.binding(bindings[0]) !== null; -}; - -Binder.binding = function(string) { - var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); - return binding ? binding[1] : null; -}; - - -Binder.prototype = { - parseQueryString: function(query) { - var params = {}; - query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, - function (match, left, right) { - if (left) params[decodeURIComponent(left)] = decodeURIComponent(right); - }); - return params; - }, - - parseAnchor: function() { - var self = this, url = this.location['get']() || ""; - - var anchorIndex = url.indexOf('#'); - if (anchorIndex < 0) return; - var anchor = url.substring(anchorIndex + 1); - - var anchorQuery = this.parseQueryString(anchor); - foreach(self.anchor, function(newValue, key) { - delete self.anchor[key]; - }); - foreach(anchorQuery, function(newValue, key) { - self.anchor[key] = newValue; - }); - }, - - onUrlChange: function() { - this.parseAnchor(); - this.updateView(); - }, - - updateAnchor: function() { - var url = this.location['get']() || ""; - 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.location['set'](url); - return url; - }, - - updateView: function() { - var start = new Date().getTime(); - var scope = jQuery(this.doc).scope(); - scope.clearInvalid(); - scope.updateView(); - var end = new Date().getTime(); - this.updateAnchor(); - foreach(this.updateListeners, function(fn) {fn();}); - }, - - docFindWithSelf: function(exp){ - var doc = jQuery(this.doc); - var selection = doc.find(exp); - if (doc.is(exp)){ - selection = selection.andSelf(); - } - return selection; - }, - - executeInit: function() { - this.docFindWithSelf("[ng-init]").each(function() { - var jThis = jQuery(this); - var scope = jThis.scope(); - try { - scope.eval(jThis.attr('ng-init')); - } catch (e) { - alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); - } - }); - }, - - entity: function (scope) { - var self = this; - this.docFindWithSelf("[ng-entity]").attr("ng-watch", function() { - try { - var jNode = jQuery(this); - var decl = scope.entity(jNode.attr("ng-entity"), self.datastore); - return decl + (jNode.attr('ng-watch') || ""); - } catch (e) { - log(e); - alert(e); - } - }); - }, - - compile: function() { - var jNode = jQuery(this.doc); - if (this.config['autoSubmit']) { - var submits = this.docFindWithSelf(":submit").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(), ""); - this.docFindWithSelf("a[ng-action]").live('click', function (event) { - var jNode = jQuery(this); - var scope = jNode.scope(); - try { - scope.eval(jNode.attr('ng-action')); - jNode.removeAttr('ng-error'); - jNode.removeClass("ng-exception"); - } catch (e) { - jNode.addClass("ng-exception"); - jNode.attr('ng-error', toJson(e, true)); - } - scope.get('$updateView')(); - return false; - }); - }, - - translateBinding: function(node, parentPath, factories) { - var path = parentPath.concat(); - var offset = path.pop(); - var parts = Binder.parseBindings(node.nodeValue); - if (parts.length > 1 || Binder.binding(parts[0])) { - var parent = node.parentNode; - if (isLeafNode(parent)) { - parent.setAttribute('ng-bind-template', node.nodeValue); - factories.push({path:path, fn:function(node, scope, prefix) { - return new BindUpdater(node, node.getAttribute('ng-bind-template')); - }}); - } else { - for (var i = 0; i < parts.length; i++) { - var part = parts[i]; - var binding = 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:this.ng_bind}); - } - } else if (msie && part.charAt(0) == ' ') { - newNode = document.createElement("span"); - newNode.innerHTML = ' ' + part.substring(1); - } else { - newNode = document.createTextNode(part); - } - parent.insertBefore(newNode, node); - } - } - parent.removeChild(node); - } - }, - - 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) { - alert(e); - } - } - }; - }, - - 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 ? 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 = msie && attrName == 'href' ? - decodeURI(node.getAttribute(attrName, 2)) : attr.value; - if (Binder.hasBinding(attrValue)) { - bindings[attrName] = attrValue; - } - } - var json = toJson(bindings); - if (json.length > 2) { - node.setAttribute("ng-bind-attr", json); - } - } - - if (!node.getAttribute) 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); - function template(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 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('' + - '' + - '' + - '' + - ''); -}; - -extend(FileController.prototype, { - 'cancel': noop, - 'complete': noop, - 'httpStatus': function(status) { - alert("httpStatus:" + this.scopeName + " status:" + status); - }, - 'ioError': function() { - alert("ioError:" + this.scopeName); - }, - 'open': function() { - alert("open:" + this.scopeName); - }, - 'progress':noop, - 'securityError': function() { - alert("securityError:" + this.scopeName); - }, - 'uploadCompleteData': function(data) { - var value = fromJson(data); - value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; - this.view.find("input").attr('checked', true); - var scope = this.view.scope(); - this.value = value; - this.updateModel(scope); - this.value = null; - }, - 'select': function(name, size, type) { - this.name = name; - this.view.find("a").text(name).attr('href', name); - this.view.find("span").text(angular['filter']['bytes'](size)); - this.upload(); - }, - - updateModel: function(scope) { - var isChecked = this.view.find("input").attr('checked'); - var value = isChecked ? this.value : null; - if (this.lastValue === value) { - return false; - } else { - scope.set(this.scopeName, value); - return true; - } - }, - - updateView: function(scope) { - var modelValue = scope.get(this.scopeName); - if (modelValue && this.value !== modelValue) { - this.value = modelValue; - this.view.find("a"). - attr("href", this.value.url). - text(this.value.text); - this.view.find("span").text(angular['filter']['bytes'](this.value.size)); - } - this.view.find("input").attr('checked', !!modelValue); - }, - - upload: function() { - if (this.name) { - this.uploader['uploadFile'](this.attachmentsPath); - } - } -}); - -/////////////////////// -// NullController -/////////////////////// -function NullController(view) {this.view = view;}; -NullController.prototype = { - updateModel: function() { return true; }, - updateView: noop -}; -NullController.instance = new NullController(); - - -/////////////////////// -// ButtonController -/////////////////////// -var ButtonController = NullController; - -/////////////////////// -// TextController -/////////////////////// -function TextController(view, exp, formatter) { - this.view = view; - this.formatter = formatter; - this.exp = exp; - this.validator = view.getAttribute('ng-validate'); - this.required = typeof view.attributes['ng-required'] != "undefined"; - this.lastErrorText = null; - this.lastValue = undefined; - this.initialValue = this.formatter['parse'](view.value); - var widget = view.getAttribute('ng-widget'); - if (widget === 'datepicker') { - jQuery(view).datepicker(); - } -}; - -TextController.prototype = { - updateModel: function(scope) { - var value = this.formatter['parse'](this.view.value); - if (this.lastValue === value) { - return false; - } else { - scope.setEval(this.exp, value); - this.lastValue = value; - return true; - } - }, - - updateView: function(scope) { - var view = this.view; - var value = scope.get(this.exp); - if (typeof value === "undefined") { - value = this.initialValue; - scope.setEval(this.exp, value); - } - value = value ? value : ''; - if (!_(this.lastValue).isEqual(value)) { - view.value = this.formatter['format'](value); - this.lastValue = value; - } - - var isValidationError = false; - view.removeAttribute('ng-error'); - if (this.required) { - isValidationError = !(value && $.trim("" + value).length > 0); - } - var errorText = isValidationError ? "Required Value" : null; - if (!isValidationError && this.validator && value) { - errorText = scope.validate(this.validator, value, view); - isValidationError = !!errorText; - } - if (this.lastErrorText !== errorText) { - this.lastErrorText = isValidationError; - if (errorText && isVisible(view)) { - view.setAttribute('ng-error', errorText); - scope.markInvalid(this); - } - jQuery(view).toggleClass('ng-validation-error', isValidationError); - } - } -}; - -/////////////////////// -// CheckboxController -/////////////////////// -function CheckboxController(view, exp, formatter) { - this.view = view; - this.exp = exp; - this.lastValue = undefined; - this.formatter = formatter; - this.initialValue = this.formatter['parse'](view.checked ? view.value : ""); -}; - -CheckboxController.prototype = { - updateModel: function(scope) { - var input = this.view; - var value = input.checked ? input.value : ''; - value = this.formatter['parse'](value); - value = this.formatter['format'](value); - if (this.lastValue === value) { - return false; - } else { - scope.setEval(this.exp, this.formatter['parse'](value)); - this.lastValue = value; - return true; - } - }, - - updateView: function(scope) { - var input = this.view; - var value = scope.eval(this.exp); - if (typeof value === "undefined") { - value = this.initialValue; - scope.setEval(this.exp, value); - } - input.checked = this.formatter['parse'](input.value) == value; - } -}; - -/////////////////////// -// SelectController -/////////////////////// -function SelectController(view, exp) { - this.view = view; - this.exp = exp; - this.lastValue = undefined; - this.initialValue = view.value; -}; - -SelectController.prototype = { - updateModel: function(scope) { - var input = this.view; - if (input.selectedIndex < 0) { - scope.setEval(this.exp, null); - } else { - var value = this.view.value; - if (this.lastValue === value) { - return false; +function modelAccessor(scope, element) { + var expr = element.attr('name'), + farmatterName = element.attr('ng-format') || NOOP, + formatter = angularFormatter(farmatterName); + if (!expr) throw "Required field 'name' not found."; + if (!formatter) throw "Formatter named '" + farmatterName + "' not found."; + return { + get: function() { + return formatter['format'](scope.$eval(expr)); + }, + set: function(value) { + scope.$eval(expr + '=' + toJson(formatter['parse'](value))); + } + }; +} + +function compileValidator(expr) { + return new Parser(expr).validator()(); +} + +function valueAccessor(element) { + var validatorName = element.attr('ng-validate') || NOOP, + validator = compileValidator(validatorName), + required = element.attr('ng-required'), + lastError; + required = required || required == ''; + if (!validator) throw "Validator named '" + validatorName + "' not found."; + function validate(value) { + var error = required && !trim(value) ? "Required" : validator.call(this, value); + if (error !== lastError) { + if (error) { + element.addClass(NG_VALIDATION_ERROR); + element.attr(NG_ERROR, error); } else { - scope.setEval(this.exp, value); - this.lastValue = value; - return true; - } - } - }, - - updateView: function(scope) { - var input = this.view; - var value = scope.get(this.exp); - if (typeof value === 'undefined') { - value = this.initialValue; - scope.setEval(this.exp, value); - } - if (value !== this.lastValue) { - input.value = value ? value : ""; - this.lastValue = value; - } - } -}; - -/////////////////////// -// MultiSelectController -/////////////////////// -function MultiSelectController(view, exp) { - this.view = view; - this.exp = exp; - this.lastValue = undefined; - this.initialValue = this.selected(); -}; - -MultiSelectController.prototype = { - selected: function () { - var value = []; - var options = this.view.options; - for ( var i = 0; i < options.length; i++) { - var option = options[i]; - if (option.selected) { - value.push(option.value); + element.removeClass(NG_VALIDATION_ERROR); + element.removeAttr(NG_ERROR); } + lastError = error; } return value; - }, - - updateModel: function(scope) { - var value = this.selected(); - // TODO: This is wrong! no caching going on here as we are always comparing arrays - if (this.lastValue === value) { - return false; - } else { - scope.setEval(this.exp, value); - this.lastValue = value; - return true; - } - }, - - updateView: function(scope) { - var input = this.view; - var selected = scope.get(this.exp); - if (typeof selected === "undefined") { - selected = this.initialValue; - scope.setEval(this.exp, selected); - } - if (selected !== this.lastValue) { - var options = input.options; - for ( var i = 0; i < options.length; i++) { - var option = options[i]; - option.selected = _.include(selected, option.value); - } - this.lastValue = selected; - } - } -}; - -/////////////////////// -// RadioController -/////////////////////// -function RadioController(view, exp) { - this.view = view; - this.exp = exp; - this.lastChecked = undefined; - this.lastValue = undefined; - this.inputValue = view.value; - this.initialValue = view.checked ? view.value : null; -}; - -RadioController.prototype = { - updateModel: function(scope) { - var input = this.view; - if (this.lastChecked) { - return false; - } else { - input.checked = true; - this.lastValue = scope.setEval(this.exp, this.inputValue); - this.lastChecked = true; - return true; - } - }, - - updateView: function(scope) { - var input = this.view; - var value = scope.get(this.exp); - if (this.initialValue && typeof value === "undefined") { - value = this.initialValue; - scope.setEval(this.exp, value); - } - if (this.lastValue != value) { - this.lastChecked = input.checked = this.inputValue == (''+value); - this.lastValue = value; - } - } -}; - -/////////////////////// -//ElementController -/////////////////////// -function BindUpdater(view, exp) { - this.view = view; - this.exp = Binder.parseBindings(exp); - this.hasError = false; -}; - -BindUpdater.toText = function(obj) { - var e = escapeHtml; - switch(typeof obj) { - case "string": - case "boolean": - case "number": - return e(obj); - case "function": - return BindUpdater.toText(obj()); - case "object": - if (isNode(obj)) { - return outerHTML(obj); - } else if (obj instanceof angular.filter.Meta) { - switch(typeof obj.html) { - case "string": - case "number": - return obj.html; - case "function": - return obj.html(); - case "object": - if (isNode(obj.html)) - return outerHTML(obj.html); - default: - break; - } - switch(typeof obj.text) { - case "string": - case "number": - return e(obj.text); - case "function": - return e(obj.text()); - default: - break; - } - } - if (obj === null) - return ""; - return e(toJson(obj, true)); - default: - return ""; - } -}; - -BindUpdater.prototype = { - updateModel: noop, - updateView: function(scope) { - var html = []; - var parts = this.exp; - var length = parts.length; - for(var i=0; i iteratorCounter; --r) { - this.children.pop().element.remove(); - } - // Special case for option in select - if (child && child.element[0].nodeName === "OPTION") { - var select = jQuery(child.element[0].parentNode); - var cntl = select.data('controller'); - if (cntl) { - cntl.lastValue = undefined; - cntl.updateView(scope); - } - } - }); - } -}; - -////////////////////////////////// -// PopUp -////////////////////////////////// - -function PopUp(doc) { - this.doc = doc; -}; - -PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup"; - -PopUp.onOver = function(e) { - PopUp.onOut(); - var jNode = jQuery(this); - jNode.bind(PopUp.OUT_EVENT, PopUp.onOut); - var position = jNode.position(); - var de = document.documentElement; - var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; - var hasArea = w - position.left; - var width = 300; - var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."; - var msg = jNode.attr("ng-error"); - - var x; - var arrowPos = hasArea>(width+75) ? "left" : "right"; - var tip = jQuery( - "
" + - "
" + - "
"+title+"
" + - "
"+msg+"
" + - "
"); - jQuery("body").append(tip); - if(arrowPos === 'left'){ - x = position.left + this.offsetWidth + 11; - }else{ - x = position.left - (width + 15); - tip.find('.ng-arrow-right').css({left:width+1}); - } - - tip.css({left: x+"px", top: (position.top - 3)+"px"}); - return true; -}; - -PopUp.onOut = function() { - jQuery('#ng-callout'). - unbind(PopUp.OUT_EVENT, PopUp.onOut). - remove(); - return true; -}; - -PopUp.prototype = { - bind: function () { - var self = this; - this.doc.find('.ng-validation-error,.ng-exception'). - live("mouseover", PopUp.onOver); - } -}; - -////////////////////////////////// -// Status -////////////////////////////////// - -function NullStatus(body) { -}; - -NullStatus.prototype = { - beginRequest:function(){}, - endRequest:function(){} -}; - -function Status(body) { - this.requestCount = 0; - this.body = body; -}; - -Status.DOM ='
loading....
'; - -Status.prototype = { - beginRequest: function () { - if (this.requestCount === 0) { - (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show(); } - this.requestCount++; - }, + }; +} + +function noopAccessor() { return { get: noop, set: noop }; } + +var NG_ERROR = 'ng-error', + NG_VALIDATION_ERROR = 'ng-validation-error', + textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''), + buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined), + INPUT_TYPE = { + 'text': textWidget, + 'textarea': textWidget, + 'hidden': textWidget, + 'password': textWidget, + 'button': buttonWidget, + 'submit': buttonWidget, + 'reset': buttonWidget, + 'image': buttonWidget, + 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false), + 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined), + 'select-one': inputWidget('click', modelAccessor, valueAccessor, null), + 'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, []) +// 'file': fileWidget??? + }; - endRequest: function () { - this.requestCount--; - if (this.requestCount === 0) { - this.loader.hide("fold"); - } - } -}; +function inputWidget(events, modelAccessor, viewAccessor, initValue) { + return function(element) { + var scope = this, + model = modelAccessor(scope, element), + view = viewAccessor(element), + action = element.attr('ng-action') || '', + value = view.get() || copy(initValue); + if (isDefined(value)) model.set(value); + this.$eval(element.attr('ng-init')||''); + element.bind(events, function(){ + model.set(view.get()); + scope.$tryEval(action, element); + scope.$root.$eval(); + // if we have no initValue than we are just a button, + // therefore we want to prevent default action + return isDefined(initValue); + }); + scope.$watch(model.get, view.set); + }; +} + +function inputWidgetSelector(element){ + return INPUT_TYPE[lowercase(element[0].type)] || noop; +} + +angularWidget('INPUT', inputWidgetSelector); +angularWidget('TEXTAREA', inputWidgetSelector); +angularWidget('BUTTON', inputWidgetSelector); +angularWidget('SELECT', function(element){ + this.descend(true); + return inputWidgetSelector.call(this, element); +}); diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index d2b2ff9c..7798afa5 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -1,18 +1,18 @@ /** * The MIT License - * + * * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com - * + * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,35 +22,58 @@ * THE SOFTWARE. */ (function(previousOnLoad){ - var filename = /(.*)\/angular-(.*).js/; - var scripts = document.getElementsByTagName("script"); + var filename = /(.*)\/angular-(.*).js(#(.*))?/; + var scripts = document.getElementsByTagName("SCRIPT"); var serverPath; + var config = {}; for(var j = 0; j < scripts.length; j++) { var match = (scripts[j].src || "").match(filename); if (match) { serverPath = match[1]; + parseConfig(match[4]); + } + } + + function parseConfig(args) { + var keyValues = args.split('&'), keyValue, i = 0; + for (; i < keyValues.length; i++) { + keyValue = keyValues[i].split('='); + config[keyValue[0]] = keyValue[1] || true; } } function addScript(file){ document.write(''); - }; + } addScript("/Angular.js"); - addScript("/API.js"); - addScript("/Binder.js"); - addScript("/ControlBar.js"); - addScript("/DataStore.js"); - addScript("/Filters.js"); - addScript("/Formatters.js"); addScript("/JSON.js"); - addScript("/Model.js"); + addScript("/Compiler.js"); + addScript("/Scope.js"); + addScript("/jqlite.js"); addScript("/Parser.js"); addScript("/Resource.js"); - addScript("/Scope.js"); - addScript("/Server.js"); - addScript("/Users.js"); - addScript("/Validators.js"); - addScript("/Widgets.js"); + addScript("/URLWatcher.js"); + + // Extension points + addScript("/apis.js"); + addScript("/filters.js"); + addScript("/formatters.js"); + addScript("/validators.js"); + addScript("/directives.js"); + addScript("/markups.js"); + addScript("/widgets.js"); + + if (config.autobind) { + window.onload = function(){ + try { + if (previousOnLoad) previousOnLoad(); + } catch(e) {} + var scope = angular.compile(window.document, config); + if (config.rootScope) window[config.rootScope] = scope; + scope.$init(); + }; + } + })(window.onload); diff --git a/src/apis.js b/src/apis.js new file mode 100644 index 00000000..e375e8fc --- /dev/null +++ b/src/apis.js @@ -0,0 +1,328 @@ +var angularGlobal = { + 'typeOf':function(obj){ + if (obj === null) return "null"; + var type = typeof obj; + if (type == "object") { + if (obj instanceof Array) return "array"; + if (obj instanceof Date) return "date"; + if (obj.nodeType == 1) return "element"; + } + return type; + } +}; + +var angularCollection = {}; +var angularObject = {}; +var angularArray = { + '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 = 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 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; + } + merge(mergeValue, value); + return array; + } +}; + +var angularString = { + '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; + } +}; + +var angularDate = { + 'toString':function(date){ + function pad(n) { return n < 10 ? "0" + n : n; } + return !date ? date : + date.getUTCFullYear() + '-' + + pad(date.getUTCMonth() + 1) + '-' + + pad(date.getUTCDate()) + 'T' + + pad(date.getUTCHours()) + ':' + + pad(date.getUTCMinutes()) + ':' + + pad(date.getUTCSeconds()) + 'Z' ; + } + }; + +var angularFunction = { + 'compile':function(expression) { + if (_.isFunction(expression)){ + return expression; + } else if (expression){ + var scope = new Scope(); + return function($) { + scope.state = $; + return scope.eval(expression); + }; + } else { + return function($){return $;}; + } + } +}; + +function defineApi(dst, chain, underscoreNames){ + if (_) { + var lastChain = _.last(chain); + foreach(underscoreNames, function(name){ + lastChain[name] = _[name]; + }); + } + angular[dst] = angular[dst] || {}; + foreach(chain, function(parent){ + extend(angular[dst], parent); + }); +} +defineApi('Global', [angularGlobal], + ['extend', 'clone','isEqual', + 'isElement', 'isArray', 'isFunction', 'isUndefined']); +defineApi('Collection', [angularGlobal, angularCollection], + ['each', 'map', 'reduce', 'reduceRight', 'detect', + 'select', 'reject', 'all', 'any', 'include', + 'invoke', 'pluck', 'max', 'min', 'sortBy', + 'sortedIndex', 'toArray', 'size']); +defineApi('Array', [angularGlobal, angularCollection, angularArray], + ['first', 'last', 'compact', 'flatten', 'without', + 'uniq', 'intersect', 'zip', 'indexOf', 'lastIndexOf']); +defineApi('Object', [angularGlobal, angularCollection, angularObject], + ['keys', 'values']); +defineApi('String', [angularGlobal, angularString], []); +defineApi('Date', [angularGlobal, angularDate], []); +//IE bug +angular['Date']['toString'] = angularDate['toString']; +defineApi('Function', [angularGlobal, angularCollection, angularFunction], + ['bind', 'bindAll', 'delay', 'defer', 'wrap', 'compose']); diff --git a/src/delete/Binder.js b/src/delete/Binder.js new file mode 100644 index 00000000..9fc32513 --- /dev/null +++ b/src/delete/Binder.js @@ -0,0 +1,356 @@ +function Binder(doc, widgetFactory, datastore, location, config) { + this.doc = doc; + this.location = location; + this.datastore = datastore; + this.anchor = {}; + this.widgetFactory = widgetFactory; + this.config = config || {}; + this.updateListeners = []; +} + +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; +}; + +Binder.hasBinding = function(string) { + var bindings = Binder.parseBindings(string); + return bindings.length > 1 || Binder.binding(bindings[0]) !== null; +}; + +Binder.binding = function(string) { + var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); + return binding ? binding[1] : null; +}; + + +Binder.prototype = { + parseQueryString: function(query) { + var params = {}; + query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, + function (match, left, right) { + if (left) params[decodeURIComponent(left)] = decodeURIComponent(right); + }); + return params; + }, + + parseAnchor: function() { + var self = this, url = this.location['get']() || ""; + + var anchorIndex = url.indexOf('#'); + if (anchorIndex < 0) return; + var anchor = url.substring(anchorIndex + 1); + + var anchorQuery = this.parseQueryString(anchor); + foreach(self.anchor, function(newValue, key) { + delete self.anchor[key]; + }); + foreach(anchorQuery, function(newValue, key) { + self.anchor[key] = newValue; + }); + }, + + onUrlChange: function() { + this.parseAnchor(); + this.updateView(); + }, + + updateAnchor: function() { + var url = this.location['get']() || ""; + 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.location['set'](url); + return url; + }, + + updateView: function() { + var start = new Date().getTime(); + var scope = jQuery(this.doc).scope(); + scope.clearInvalid(); + scope.updateView(); + var end = new Date().getTime(); + this.updateAnchor(); + foreach(this.updateListeners, function(fn) {fn();}); + }, + + docFindWithSelf: function(exp){ + var doc = jQuery(this.doc); + var selection = doc.find(exp); + if (doc.is(exp)){ + selection = selection.andSelf(); + } + return selection; + }, + + executeInit: function() { + this.docFindWithSelf("[ng-init]").each(function() { + var jThis = jQuery(this); + var scope = jThis.scope(); + try { + scope.eval(jThis.attr('ng-init')); + } catch (e) { + alert("EVAL ERROR:\n" + jThis.attr('ng-init') + '\n' + toJson(e, true)); + } + }); + }, + + entity: function (scope) { + var self = this; + this.docFindWithSelf("[ng-entity]").attr("ng-watch", function() { + try { + var jNode = jQuery(this); + var decl = scope.entity(jNode.attr("ng-entity"), self.datastore); + return decl + (jNode.attr('ng-watch') || ""); + } catch (e) { + log(e); + alert(e); + } + }); + }, + + compile: function() { + var jNode = jQuery(this.doc); + if (this.config['autoSubmit']) { + var submits = this.docFindWithSelf(":submit").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(), ""); + this.docFindWithSelf("a[ng-action]").live('click', function (event) { + var jNode = jQuery(this); + var scope = jNode.scope(); + try { + scope.eval(jNode.attr('ng-action')); + jNode.removeAttr('ng-error'); + jNode.removeClass("ng-exception"); + } catch (e) { + jNode.addClass("ng-exception"); + jNode.attr('ng-error', toJson(e, true)); + } + scope.get('$updateView')(); + return false; + }); + }, + + translateBinding: function(node, parentPath, factories) { + var path = parentPath.concat(); + var offset = path.pop(); + var parts = Binder.parseBindings(node.nodeValue); + if (parts.length > 1 || Binder.binding(parts[0])) { + var parent = node.parentNode; + if (isLeafNode(parent)) { + parent.setAttribute('ng-bind-template', node.nodeValue); + factories.push({path:path, fn:function(node, scope, prefix) { + return new BindUpdater(node, node.getAttribute('ng-bind-template')); + }}); + } else { + for (var i = 0; i < parts.length; i++) { + var part = parts[i]; + var binding = 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:this.ng_bind}); + } + } else if (msie && part.charAt(0) == ' ') { + newNode = document.createElement("span"); + newNode.innerHTML = ' ' + part.substring(1); + } else { + newNode = document.createTextNode(part); + } + parent.insertBefore(newNode, node); + } + } + parent.removeChild(node); + } + }, + + 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) { + alert(e); + } + } + }; + }, + + 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 ? 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 = msie && attrName == 'href' ? + decodeURI(node.getAttribute(attrName, 2)) : attr.value; + if (Binder.hasBinding(attrValue)) { + bindings[attrName] = attrValue; + } + } + var json = toJson(bindings); + if (json.length > 2) { + node.setAttribute("ng-bind-attr", json); + } + } + + if (!node.getAttribute) 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); + function template(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 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('' + + '' + + '' + + '' + + ''); +}; + +extend(FileController.prototype, { + 'cancel': noop, + 'complete': noop, + 'httpStatus': function(status) { + alert("httpStatus:" + this.scopeName + " status:" + status); + }, + 'ioError': function() { + alert("ioError:" + this.scopeName); + }, + 'open': function() { + alert("open:" + this.scopeName); + }, + 'progress':noop, + 'securityError': function() { + alert("securityError:" + this.scopeName); + }, + 'uploadCompleteData': function(data) { + var value = fromJson(data); + value.url = this.attachmentsPath + '/' + value.id + '/' + value.text; + this.view.find("input").attr('checked', true); + var scope = this.view.scope(); + this.value = value; + this.updateModel(scope); + this.value = null; + }, + 'select': function(name, size, type) { + this.name = name; + this.view.find("a").text(name).attr('href', name); + this.view.find("span").text(angular['filter']['bytes'](size)); + this.upload(); + }, + + updateModel: function(scope) { + var isChecked = this.view.find("input").attr('checked'); + var value = isChecked ? this.value : null; + if (this.lastValue === value) { + return false; + } else { + scope.set(this.scopeName, value); + return true; + } + }, + + updateView: function(scope) { + var modelValue = scope.get(this.scopeName); + if (modelValue && this.value !== modelValue) { + this.value = modelValue; + this.view.find("a"). + attr("href", this.value.url). + text(this.value.text); + this.view.find("span").text(angular['filter']['bytes'](this.value.size)); + } + this.view.find("input").attr('checked', !!modelValue); + }, + + upload: function() { + if (this.name) { + this.uploader['uploadFile'](this.attachmentsPath); + } + } +}); + +/////////////////////// +// NullController +/////////////////////// +function NullController(view) {this.view = view;}; +NullController.prototype = { + updateModel: function() { return true; }, + updateView: noop +}; +NullController.instance = new NullController(); + + +/////////////////////// +// ButtonController +/////////////////////// +var ButtonController = NullController; + +/////////////////////// +// TextController +/////////////////////// +function TextController(view, exp, formatter) { + this.view = view; + this.formatter = formatter; + this.exp = exp; + this.validator = view.getAttribute('ng-validate'); + this.required = typeof view.attributes['ng-required'] != "undefined"; + this.lastErrorText = null; + this.lastValue = undefined; + this.initialValue = this.formatter['parse'](view.value); + var widget = view.getAttribute('ng-widget'); + if (widget === 'datepicker') { + jQuery(view).datepicker(); + } +}; + +TextController.prototype = { + updateModel: function(scope) { + var value = this.formatter['parse'](this.view.value); + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, value); + this.lastValue = value; + return true; + } + }, + + updateView: function(scope) { + var view = this.view; + var value = scope.get(this.exp); + if (typeof value === "undefined") { + value = this.initialValue; + scope.setEval(this.exp, value); + } + value = value ? value : ''; + if (!_(this.lastValue).isEqual(value)) { + view.value = this.formatter['format'](value); + this.lastValue = value; + } + + var isValidationError = false; + view.removeAttribute('ng-error'); + if (this.required) { + isValidationError = !(value && $.trim("" + value).length > 0); + } + var errorText = isValidationError ? "Required Value" : null; + if (!isValidationError && this.validator && value) { + errorText = scope.validate(this.validator, value, view); + isValidationError = !!errorText; + } + if (this.lastErrorText !== errorText) { + this.lastErrorText = isValidationError; + if (errorText && isVisible(view)) { + view.setAttribute('ng-error', errorText); + scope.markInvalid(this); + } + jQuery(view).toggleClass('ng-validation-error', isValidationError); + } + } +}; + +/////////////////////// +// CheckboxController +/////////////////////// +function CheckboxController(view, exp, formatter) { + this.view = view; + this.exp = exp; + this.lastValue = undefined; + this.formatter = formatter; + this.initialValue = this.formatter['parse'](view.checked ? view.value : ""); +}; + +CheckboxController.prototype = { + updateModel: function(scope) { + var input = this.view; + var value = input.checked ? input.value : ''; + value = this.formatter['parse'](value); + value = this.formatter['format'](value); + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, this.formatter['parse'](value)); + this.lastValue = value; + return true; + } + }, + + updateView: function(scope) { + var input = this.view; + var value = scope.eval(this.exp); + if (typeof value === "undefined") { + value = this.initialValue; + scope.setEval(this.exp, value); + } + input.checked = this.formatter['parse'](input.value) == value; + } +}; + +/////////////////////// +// SelectController +/////////////////////// +function SelectController(view, exp) { + this.view = view; + this.exp = exp; + this.lastValue = undefined; + this.initialValue = view.value; +}; + +SelectController.prototype = { + updateModel: function(scope) { + var input = this.view; + if (input.selectedIndex < 0) { + scope.setEval(this.exp, null); + } else { + var value = this.view.value; + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, value); + this.lastValue = value; + return true; + } + } + }, + + updateView: function(scope) { + var input = this.view; + var value = scope.get(this.exp); + if (typeof value === 'undefined') { + value = this.initialValue; + scope.setEval(this.exp, value); + } + if (value !== this.lastValue) { + input.value = value ? value : ""; + this.lastValue = value; + } + } +}; + +/////////////////////// +// MultiSelectController +/////////////////////// +function MultiSelectController(view, exp) { + this.view = view; + this.exp = exp; + this.lastValue = undefined; + this.initialValue = this.selected(); +}; + +MultiSelectController.prototype = { + selected: function () { + var value = []; + var options = this.view.options; + for ( var i = 0; i < options.length; i++) { + var option = options[i]; + if (option.selected) { + value.push(option.value); + } + } + return value; + }, + + updateModel: function(scope) { + var value = this.selected(); + // TODO: This is wrong! no caching going on here as we are always comparing arrays + if (this.lastValue === value) { + return false; + } else { + scope.setEval(this.exp, value); + this.lastValue = value; + return true; + } + }, + + updateView: function(scope) { + var input = this.view; + var selected = scope.get(this.exp); + if (typeof selected === "undefined") { + selected = this.initialValue; + scope.setEval(this.exp, selected); + } + if (selected !== this.lastValue) { + var options = input.options; + for ( var i = 0; i < options.length; i++) { + var option = options[i]; + option.selected = _.include(selected, option.value); + } + this.lastValue = selected; + } + } +}; + +/////////////////////// +// RadioController +/////////////////////// +function RadioController(view, exp) { + this.view = view; + this.exp = exp; + this.lastChecked = undefined; + this.lastValue = undefined; + this.inputValue = view.value; + this.initialValue = view.checked ? view.value : null; +}; + +RadioController.prototype = { + updateModel: function(scope) { + var input = this.view; + if (this.lastChecked) { + return false; + } else { + input.checked = true; + this.lastValue = scope.setEval(this.exp, this.inputValue); + this.lastChecked = true; + return true; + } + }, + + updateView: function(scope) { + var input = this.view; + var value = scope.get(this.exp); + if (this.initialValue && typeof value === "undefined") { + value = this.initialValue; + scope.setEval(this.exp, value); + } + if (this.lastValue != value) { + this.lastChecked = input.checked = this.inputValue == (''+value); + this.lastValue = value; + } + } +}; + +/////////////////////// +//ElementController +/////////////////////// +function BindUpdater(view, exp) { + this.view = view; + this.exp = Binder.parseBindings(exp); + this.hasError = false; +}; + +BindUpdater.toText = function(obj) { + var e = escapeHtml; + switch(typeof obj) { + case "string": + case "boolean": + case "number": + return e(obj); + case "function": + return BindUpdater.toText(obj()); + case "object": + if (isNode(obj)) { + return outerHTML(obj); + } else if (obj instanceof angular.filter.Meta) { + switch(typeof obj.html) { + case "string": + case "number": + return obj.html; + case "function": + return obj.html(); + case "object": + if (isNode(obj.html)) + return outerHTML(obj.html); + default: + break; + } + switch(typeof obj.text) { + case "string": + case "number": + return e(obj.text); + case "function": + return e(obj.text()); + default: + break; + } + } + if (obj === null) + return ""; + return e(toJson(obj, true)); + default: + return ""; + } +}; + +BindUpdater.prototype = { + updateModel: noop, + updateView: function(scope) { + var html = []; + var parts = this.exp; + var length = parts.length; + for(var i=0; i iteratorCounter; --r) { + this.children.pop().element.remove(); + } + // Special case for option in select + if (child && child.element[0].nodeName === "OPTION") { + var select = jQuery(child.element[0].parentNode); + var cntl = select.data('controller'); + if (cntl) { + cntl.lastValue = undefined; + cntl.updateView(scope); + } + } + }); + } +}; + +////////////////////////////////// +// PopUp +////////////////////////////////// + +function PopUp(doc) { + this.doc = doc; +}; + +PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup"; + +PopUp.onOver = function(e) { + PopUp.onOut(); + var jNode = jQuery(this); + jNode.bind(PopUp.OUT_EVENT, PopUp.onOut); + var position = jNode.position(); + var de = document.documentElement; + var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth; + var hasArea = w - position.left; + var width = 300; + var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error..."; + var msg = jNode.attr("ng-error"); + + var x; + var arrowPos = hasArea>(width+75) ? "left" : "right"; + var tip = jQuery( + "
" + + "
" + + "
"+title+"
" + + "
"+msg+"
" + + "
"); + jQuery("body").append(tip); + if(arrowPos === 'left'){ + x = position.left + this.offsetWidth + 11; + }else{ + x = position.left - (width + 15); + tip.find('.ng-arrow-right').css({left:width+1}); + } + + tip.css({left: x+"px", top: (position.top - 3)+"px"}); + return true; +}; + +PopUp.onOut = function() { + jQuery('#ng-callout'). + unbind(PopUp.OUT_EVENT, PopUp.onOut). + remove(); + return true; +}; + +PopUp.prototype = { + bind: function () { + var self = this; + this.doc.find('.ng-validation-error,.ng-exception'). + live("mouseover", PopUp.onOver); + } +}; + +////////////////////////////////// +// Status +////////////////////////////////// + +function NullStatus(body) { +}; + +NullStatus.prototype = { + beginRequest:function(){}, + endRequest:function(){} +}; + +function Status(body) { + this.requestCount = 0; + this.body = body; +}; + +Status.DOM ='
loading....
'; + +Status.prototype = { + beginRequest: function () { + if (this.requestCount === 0) { + (this.loader = this.loader || this.body.append(Status.DOM).find("#ng-loading")).show(); + } + this.requestCount++; + }, + + endRequest: function () { + this.requestCount--; + if (this.requestCount === 0) { + this.loader.hide("fold"); + } + } +}; diff --git a/src/directives.js b/src/directives.js index 10476c77..c54c89e9 100644 --- a/src/directives.js +++ b/src/directives.js @@ -11,9 +11,15 @@ angularDirective("ng-eval", function(expression){ }); angularDirective("ng-bind", function(expression){ + var templateFn = compileBindTemplate("{{" + expression + "}}"); return function(element) { - this.$watch(expression, function(value){ - element.text(value); + var lastValue; + this.$onEval(function() { + var value = templateFn.call(this); + if (value != lastValue) { + element.text(value); + lastValue = value; + } }, element); }; }); @@ -34,7 +40,9 @@ function compileBindTemplate(template){ bindTemplateCache[template] = fn = function(){ var parts = [], self = this; foreach(bindings, function(fn){ - parts.push(fn.call(self)); + var value = fn.call(self); + if (isObject(value)) value = toJson(value, true); + parts.push(value); }); return parts.join(''); }; @@ -125,6 +133,7 @@ angularDirective("ng-action", function(expression, element){ var self = this; element.click(function(){ self.$tryEval(expression, element); + self.$eval(); }); }; }); diff --git a/src/jqLite.js b/src/jqLite.js index 7646bf98..a5014354 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -38,7 +38,8 @@ function JQLite(element) { this[0] = element; } -function jqLite(element) { + +function jqLiteWrap(element) { if (typeof element == 'string') { var div = document.createElement('div'); div.innerHTML = element; @@ -47,6 +48,8 @@ function jqLite(element) { return element instanceof JQLite ? element : new JQLite(element); } +jqLite = jqLite || jqLiteWrap; + JQLite.prototype = { data: function(key, value) { var element = this[0], @@ -85,12 +88,15 @@ JQLite.prototype = { foreach(type.split(' '), function(type){ eventHandler = bind[type]; if (!eventHandler) { - bind[type] = eventHandler = function() { - var value = false; + bind[type] = eventHandler = function(event) { + var bubbleEvent = false; foreach(eventHandler.fns, function(fn){ - value = value || fn.apply(self, arguments); + bubbleEvent = bubbleEvent || fn.apply(self, arguments); }); - return value; + if (!bubbleEvent) { + event.preventDefault(); + event.stopPropagation(); + } }; eventHandler.fns = []; addEventListener(element, type, eventHandler); diff --git a/src/markup.js b/src/markup.js deleted file mode 100644 index 5fb10779..00000000 --- a/src/markup.js +++ /dev/null @@ -1,76 +0,0 @@ -function parseBindings(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; -}; - -function binding(string) { - var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); - return binding ? binding[1] : null; -}; - -function hasBindings(bindings) { - return bindings.length > 1 || Binder.binding(bindings[0]) !== null; -}; - -angularTextMarkup('{{}}', function(text, textNode, parentElement) { - var bindings = parseBindings(text), - self = this; - if (hasBindings(bindings)) { - if (isLeafNode(parentElement[0])) { - parentElement.attr('ng-bind-template', text); - } else { - var cursor = textNode, newElement; - foreach(parseBindings(text), function(text){ - var exp = binding(text); - if (exp) { - newElement = self.element('span'); - newElement.attr('ng-bind', exp); - } else { - newElement = self.text(text); - } - cursor.after(newElement); - cursor = newElement; - }); - } - textNode.remove(); - } -}); - -angularTextMarkup('OPTION', function(text, textNode, parentElement){ - if (parentElement[0].nodeName == "OPTION") { - var select = document.createElement('select'); - select.insertBefore(parentElement[0].cloneNode(true), null); - if (!select.innerHTML.match(/.*<\/\s*option\s*>/gi)) { - parentElement.attr('value', text); - } - } -}); - -var NG_BIND_ATTR = 'ng-bind-attr'; -angularAttrMarkup('{{}}', function(value, name, element){ - if (name.substr(0, 3) != 'ng-') { - var bindings = parseBindings(value), - bindAttr; - if (hasBindings(bindings)) { - element.removeAttr(name); - bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); - bindAttr[name] = value; - element.attr(NG_BIND_ATTR, toJson(bindAttr)); - } - } -}); diff --git a/src/markups.js b/src/markups.js new file mode 100644 index 00000000..6bc27c85 --- /dev/null +++ b/src/markups.js @@ -0,0 +1,76 @@ +function parseBindings(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; +}; + +function binding(string) { + var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); + return binding ? binding[1] : null; +}; + +function hasBindings(bindings) { + return bindings.length > 1 || binding(bindings[0]) !== null; +}; + +angularTextMarkup('{{}}', function(text, textNode, parentElement) { + var bindings = parseBindings(text), + self = this; + if (hasBindings(bindings)) { + if (isLeafNode(parentElement[0])) { + parentElement.attr('ng-bind-template', text); + } else { + var cursor = textNode, newElement; + foreach(parseBindings(text), function(text){ + var exp = binding(text); + if (exp) { + newElement = self.element('span'); + newElement.attr('ng-bind', exp); + } else { + newElement = self.text(text); + } + cursor.after(newElement); + cursor = newElement; + }); + } + textNode.remove(); + } +}); + +angularTextMarkup('OPTION', function(text, textNode, parentElement){ + if (parentElement[0].nodeName == "OPTION") { + var select = document.createElement('select'); + select.insertBefore(parentElement[0].cloneNode(true), null); + if (!select.innerHTML.match(/.*<\/\s*option\s*>/gi)) { + parentElement.attr('value', text); + } + } +}); + +var NG_BIND_ATTR = 'ng-bind-attr'; +angularAttrMarkup('{{}}', function(value, name, element){ + if (name.substr(0, 3) != 'ng-') { + var bindings = parseBindings(value), + bindAttr; + if (hasBindings(bindings)) { + element.removeAttr(name); + bindAttr = fromJson(element.attr(NG_BIND_ATTR) || "{}"); + bindAttr[name] = value; + element.attr(NG_BIND_ATTR, toJson(bindAttr)); + } + } +}); diff --git a/src/moveToAngularCom/ControlBar.js b/src/moveToAngularCom/ControlBar.js new file mode 100644 index 00000000..685beeb2 --- /dev/null +++ b/src/moveToAngularCom/ControlBar.js @@ -0,0 +1,72 @@ +function ControlBar(document, serverUrl, database) { + this._document = document; + this.serverUrl = serverUrl; + this.database = database; + this._window = window; + this.callbacks = []; +}; + +ControlBar.HTML = + '
' + + '
' + + '
' + + '' + + '
' + + '
'; + + +ControlBar.FORBIDEN = + '
' + + 'Sorry, you do not have permission for this!'+ + '
'; + +ControlBar.prototype = { + bind: function () { + }, + + login: function (loginSubmitFn) { + this.callbacks.push(loginSubmitFn); + if (this.callbacks.length == 1) { + this.doTemplate("/user_session/new.mini?database="+encodeURIComponent(this.database)+"&return_url=" + encodeURIComponent(this.urlWithoutAnchor())); + } + }, + + logout: function (loginSubmitFn) { + this.callbacks.push(loginSubmitFn); + if (this.callbacks.length == 1) { + this.doTemplate("/user_session/do_destroy.mini"); + } + }, + + urlWithoutAnchor: function (path) { + return this._window['location']['href'].split("#")[0]; + }, + + doTemplate: function (path) { + var self = this; + var id = new Date().getTime(); + var url = this.urlWithoutAnchor() + "#$iframe_notify=" + id; + var iframeHeight = 330; + var loginView = jQuery('
' + '
'); - 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 scenario.Runner(this.console, this.testFrame); - _.stepper(this.scenarios, function(next, scenarioObj, name){ - new scenario.Scenario(name, scenarioObj).run(runner, next); - }, function(){ - } - ); - } -}; - -scenario.Runner = function(console, frame){ - this.console = console; - this.current = null; - this.tests = []; - this.frame = frame; -}; -scenario.Runner.prototype = { - start:function(name){ - var current = this.current = { - name:name, - start:new Date().getTime(), - scenario:jQuery('
') + var console = body.find('#runner .console'); + this.testFrame = body.find('#testView iframe'); + this.testWindow = this.testFrame[0].contentWindow; + this.beginSpec = function(name){ + var specElement = jQuery('
  • '); + var stepContainer = jQuery('
      '); + console.append(specElement); + specElement.text(name); + specElement.append(stepContainer); + return function(name){ + var stepElement = jQuery('
    • '); + var logContainer = jQuery('
        '); + stepContainer.append(stepElement); + stepElement.text(name); + stepElement.append(logContainer); + return function(message) { + var logElement = jQuery('
      • '); + logContainer.append(logElement); + logElement.text(message); + }; + }; }; - current.run = current.scenario.append( - '
        ' + - '.' + - '.' + - '.' + - '').find(".run"); - current.log = current.scenario.append('
        ').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('').text(current.error); - current.scenario.find(".log").hide(); + this.execute("widgets: it should verify that basic widgets work"); }, - log:function(level) { - var buf = []; - for ( var i = 1; i < arguments.length; i++) { - var arg = arguments[i]; - buf.push(typeof arg == "string" ?arg:toJson(arg)); - } - var log = jQuery('
        '); - log.text(buf.join(" ")); - this.current.log.append(log); - this.console.scrollTop(this.console[0].scrollHeight); - if (level == "error") - this.current.error = buf.join(" "); - } -}; -scenario.Scenario = function(name, scenario){ - this.name = name; - this.scenario = scenario; -}; -scenario.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); + addStep: function(name, step) { + this.currentSpec.steps.push({name:name, fn:step}); }, - - verb:function(step){ - var fn = null; - if (!step) fn = function (){ throw "Step is null!"; }; - else if (step.Given) fn = scenario.GIVEN[step.Given]; - else if (step.When) fn = scenario.WHEN[step.When]; - else if (step.Then) fn = scenario.THEN[step.Then]; - return fn || function (){ - throw "ERROR: Need Given/When/Then got: " + toJson(step); + execute: function(name, callback) { + var spec = this.specs[name], + result = { + passed: false, + failed: false, + finished: false, + fail: function(error) { + result.passed = false; + result.failed = true; + result.error = error; + result.log(angular.isString(error) ? error : angular.toJson(error)); + } + }; + specThis = { + result: result, + testWindow: this.testWindow, + testFrame: this.testFrame }; - }, - - - 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", 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: " + toJson(e)); - } + var beginStep = this.beginSpec(name); + spec.nextStepIndex = 0; + function done() { + result.finished = true; + (callback||angular.noop).call(specThis); + } + function next(){ + var step = spec.steps[spec.nextStepIndex]; + if (step) { + spec.nextStepIndex ++; + result.log = beginStep(step.name); + try { + step.fn.call(specThis, next); + } catch (e) { + result.fail(e); + done(); + } + } else { + result.passed = !result.failed; + done(); + } + }; + next(); + return specThis; } -}; +}; \ No newline at end of file diff --git a/src/scenario/Steps.js b/src/scenario/Steps.js deleted file mode 100644 index ffe75933..00000000 --- a/src/scenario/Steps.js +++ /dev/null @@ -1,57 +0,0 @@ -angular.scenario.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:" + toJson({dataset:this.dataset}); - } -}; -angular.scenario.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.trigger('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.scenario.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/scenario/_namespace.js b/src/scenario/_namespace.js deleted file mode 100644 index 7da3a5d8..00000000 --- a/src/scenario/_namespace.js +++ /dev/null @@ -1,6 +0,0 @@ -if (!angular) var angular = window['angular'] = {}; -if (!angular['scenario']) var angularScenario = angular['scenario'] = {}; -if (!angular['scenarioDef']) var scenarioDef = angular['scenarioDef'] = {}; -if (!angular['scenario']['GIVEN']) angularScenario['GIVEN'] = {}; -if (!angular['scenario']['WHEN']) angularScenario['WHEN'] = {}; -if (!angular['scenario']['THEN']) angularScenario['THEN'] = {}; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 169f1860..81272bdd 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -19,22 +19,8 @@ } window.onload = function(){ - if (!_.stepper) { - _.stepper = function(collection, iterator, done){ - var keys = _.keys(collection); - function next() { - if (keys.length) { - var key = keys.shift(); - iterator(next, collection[key], key); - } else { - (done||_.identity)(); - } - } - next(); - }; - } _.defer(function(){ - new angular.scenario.SuiteRunner(angular.scenarioDef, jQuery(document.body)).run(); + $scenarioRunner.run(jQuery(document.body)); }); (onLoadDelegate||function(){})(); }; @@ -42,8 +28,7 @@ addScript("../../lib/underscore/underscore.js"); addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); - addScript("_namespace.js"); - addScript("Steps.js"); addScript("Runner.js"); + document.write(''); })(window.onload); -- cgit v1.2.3 From e3368e12a6207706d8a08b18f9958db3b86ca4e5 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 20 May 2010 16:55:47 -0700 Subject: semi working state --- src/scenario/Runner.js | 63 +++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index eeb4330d..970d0c66 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -20,8 +20,8 @@ angular.scenario.Runner = function(scope){ body(); self.currentSpec = null; }; - this.beginSpec = function returnNoop(){ - return returnNoop; + this.logger = function returnNoop(){ + return angular.extend(returnNoop, {close:angular.noop, fail:angular.noop});; }; }; @@ -29,33 +29,45 @@ angular.scenario.Runner.prototype = { run: function(body){ body.append( '
        ' + - '
          ' + + '
          ' + '
          ' + '
          ' + '' + '
          '); var console = body.find('#runner .console'); + console.find('li').live('click', function(){ + jQuery(this).toggleClass('collapsed'); + }); this.testFrame = body.find('#testView iframe'); this.testWindow = this.testFrame[0].contentWindow; - this.beginSpec = function(name){ - var specElement = jQuery('
        • '); - var stepContainer = jQuery('
            '); - console.append(specElement); - specElement.text(name); - specElement.append(stepContainer); - return function(name){ - var stepElement = jQuery('
          • '); - var logContainer = jQuery('
              '); - stepContainer.append(stepElement); - stepElement.text(name); - stepElement.append(logContainer); - return function(message) { - var logElement = jQuery('
            • '); - logContainer.append(logElement); - logElement.text(message); - }; + function logger(parent) { + var container; + return function(type, text) { + if (!container) { + container = jQuery('
                '); + parent.append(container); + } + var element = jQuery(''); + element.find('span').text(text); + container.append(element); + return angular.extend(logger(element), { + close: function(){ + element.removeClass('running'); + }, + fail: function(){ + element.removeClass('running'); + var current = element; + while (current[0] != console[0]) { + if (current.is('li')) + current.addClass('fail'); + current.removeClass('collapsed'); + current = current.parent(); + } + } + });; }; - }; + } + this.logger = logger(console); this.execute("widgets: it should verify that basic widgets work"); }, @@ -73,7 +85,7 @@ angular.scenario.Runner.prototype = { result.passed = false; result.failed = true; result.error = error; - result.log(angular.isString(error) ? error : angular.toJson(error)); + result.log('fail', angular.isString(error) ? error : angular.toJson(error)).fail(); } }; specThis = { @@ -81,17 +93,20 @@ angular.scenario.Runner.prototype = { testWindow: this.testWindow, testFrame: this.testFrame }; - var beginStep = this.beginSpec(name); + var stepLogger = this.logger('spec', name); spec.nextStepIndex = 0; function done() { result.finished = true; + stepLogger.close(); (callback||angular.noop).call(specThis); } function next(){ var step = spec.steps[spec.nextStepIndex]; + (result.log || {close:angular.noop}).close(); + result.log = null; if (step) { spec.nextStepIndex ++; - result.log = beginStep(step.name); + result.log = stepLogger('step', step.name); try { step.fn.call(specThis, next); } catch (e) { -- cgit v1.2.3 From f6c67e28c94033edf6a16eb6508de54679cb49db Mon Sep 17 00:00:00 2001 From: Andres Ornelas Mesta Date: Mon, 24 May 2010 13:54:32 -0700 Subject: happy --- src/scenario/Runner.js | 44 +++++++++++++++++++++++++++++--------------- src/scenario/bootstrap.js | 6 ++++-- 2 files changed, 33 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 970d0c66..9e20d394 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,8 +1,9 @@ angular['scenario'] = (angular['scenario'] = {}); -angular.scenario.Runner = function(scope){ +angular.scenario.Runner = function(scope, jQuery){ var self = scope.$scenario = this; this.scope = scope; + this.jQuery = jQuery; var specs = this.specs = {}; var path = []; @@ -27,6 +28,7 @@ angular.scenario.Runner = function(scope){ angular.scenario.Runner.prototype = { run: function(body){ + var jQuery = this.jQuery; body.append( '
                ' + '
                ' + @@ -68,7 +70,19 @@ angular.scenario.Runner.prototype = { }; } this.logger = logger(console); - this.execute("widgets: it should verify that basic widgets work"); + var specNames = []; + angular.foreach(this.specs, function(spec, name){ + specNames.push(name); + }, this); + specNames.sort(); + var self = this; + function callback(){ + var next = specNames.shift(); + if(next) { + self.execute(next, callback); + } + }; + callback(); }, addStep: function(name, step) { @@ -102,21 +116,21 @@ angular.scenario.Runner.prototype = { } function next(){ var step = spec.steps[spec.nextStepIndex]; - (result.log || {close:angular.noop}).close(); - result.log = null; - if (step) { - spec.nextStepIndex ++; - result.log = stepLogger('step', step.name); - try { - step.fn.call(specThis, next); - } catch (e) { - result.fail(e); - done(); - } - } else { - result.passed = !result.failed; + (result.log || {close:angular.noop}).close(); + result.log = null; + if (step) { + spec.nextStepIndex ++; + result.log = stepLogger('step', step.name); + try { + step.fn.call(specThis, next); + } catch (e) { + result.fail(e); done(); } + } else { + result.passed = !result.failed; + done(); + } }; next(); return specThis; diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 81272bdd..51d24c38 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -20,7 +20,7 @@ window.onload = function(){ _.defer(function(){ - $scenarioRunner.run(jQuery(document.body)); + $scenarioRunner.run(jQuery(window.document.body)); }); (onLoadDelegate||function(){})(); }; @@ -29,6 +29,8 @@ addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); addScript("Runner.js"); - document.write(''); + document.write(''); })(window.onload); -- cgit v1.2.3 From 3fab5d9879272b9f991a67c8135754f00c055834 Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Mon, 24 May 2010 15:25:30 -0700 Subject: added error handling on scenario definition --- src/scenario/DSL.js | 47 +++++++++++++++++++++++++++++++++++++++++++++++ src/scenario/Runner.js | 15 ++++++++++++--- src/scenario/bootstrap.js | 1 + 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/scenario/DSL.js (limited to 'src') diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js new file mode 100644 index 00000000..4bc21d6c --- /dev/null +++ b/src/scenario/DSL.js @@ -0,0 +1,47 @@ +angular.scenario.dsl.browser = { + navigateTo: function(url){ + $scenario.addStep('Navigate to: ' + url, function(done){ + var self = this; + self.testFrame.load(function(){ + self.testFrame.unbind(); + self.testDocument = jQuery(self.testWindow.document); + done(); + }); + if (this.testFrame.attr('src') == url) { + this.testWindow.location.reload(); + } else { + this.testFrame.attr('src', url); + } + }); + } +}; + +angular.scenario.dsl.input = function(selector) { + return { + enter: function(value){ + $scenario.addStep("Set input text of '" + selector + "' to value '" + + value + "'", function(done){ + var input = this.testDocument.find('input[name=' + selector + ']'); + input.val(value); + input.trigger('change'); + this.testWindow.angular.element(input[0]).trigger('change'); + done(); + }); + } + }; +}; + +angular.scenario.dsl.expect = function(selector) { + return { + toEqual: function(expected) { + $scenario.addStep("Expect that " + selector + " equals '" + expected + "'", function(done){ + var attrName = selector.substring(2, selector.length - 2); + var binding = this.testDocument.find('span[ng-bind=' + attrName + ']'); + if (binding.text() != expected) { + this.result.fail("Expected '" + expected + "' but was '" + binding.text() + "'"); + } + done(); + }); + } + }; +}; diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 9e20d394..003ce487 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,9 +1,11 @@ -angular['scenario'] = (angular['scenario'] = {}); +angular['scenario'] = (angular['scenario'] = {}); +angular.scenario['dsl'] = (angular.scenario['dsl'] = {}); angular.scenario.Runner = function(scope, jQuery){ var self = scope.$scenario = this; this.scope = scope; this.jQuery = jQuery; + angular.extend(scope, angular.scenario.dsl); var specs = this.specs = {}; var path = []; @@ -18,7 +20,13 @@ angular.scenario.Runner = function(scope, jQuery){ name: specName, steps:[] }; - body(); + try { + body(); + } catch(err) { + self.addStep(err.message || 'ERROR', function(){ + throw err; + }); + } self.currentSpec = null; }; this.logger = function returnNoop(){ @@ -55,6 +63,7 @@ angular.scenario.Runner.prototype = { return angular.extend(logger(element), { close: function(){ element.removeClass('running'); + console.scrollTop(console[0].scrollHeight); }, fail: function(){ element.removeClass('running'); @@ -66,7 +75,7 @@ angular.scenario.Runner.prototype = { current = current.parent(); } } - });; + }); }; } this.logger = logger(console); diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 51d24c38..4c9cdc8d 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -29,6 +29,7 @@ addScript("../../lib/jquery/jquery-1.4.2.js"); addScript("../angular-bootstrap.js"); addScript("Runner.js"); + addScript("DSL.js"); document.write(''); -- cgit v1.2.3 From 55c0767f16e60e77e9d1b4d46698ddbf343ed8b1 Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Mon, 24 May 2010 17:48:17 -0700 Subject: added dsl tests and select method --- src/scenario/DSL.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index 4bc21d6c..842f7c7a 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -19,14 +19,24 @@ angular.scenario.dsl.browser = { angular.scenario.dsl.input = function(selector) { return { enter: function(value){ - $scenario.addStep("Set input text of '" + selector + "' to value '" + + $scenario.addStep("Set input text of '" + selector + "' to '" + value + "'", function(done){ var input = this.testDocument.find('input[name=' + selector + ']'); input.val(value); - input.trigger('change'); this.testWindow.angular.element(input[0]).trigger('change'); done(); }); + }, + select: function(value){ + $scenario.addStep("Select radio '" + selector + "' to '" + + value + "'", function(done){ + var input = this.testDocument. + find(':radio[name$=@' + selector + '][value=' + value + ']'); + var event = this.testWindow.document.createEvent('MouseEvent'); + event.initMouseEvent('click', true, true, this.testWindow, 0,0,0,0,0, false, false, false, false, 0, null); + input[0].dispatchEvent(event); + done(); + }); } }; }; -- cgit v1.2.3 From 2cce1ffc15ae6483da9cf354f7a5d2d26317427e Mon Sep 17 00:00:00 2001 From: Andres Ornelas Date: Tue, 25 May 2010 13:05:23 -0700 Subject: fixed collapsed issue --- src/scenario/Runner.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 003ce487..8669f56b 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -57,12 +57,14 @@ angular.scenario.Runner.prototype = { container = jQuery('
                  '); parent.append(container); } - var element = jQuery(''); + var element = jQuery('
                • '); element.find('span').text(text); container.append(element); return angular.extend(logger(element), { close: function(){ element.removeClass('running'); + if(!element.hasClass('fail')) + element.addClass('collapsed'); console.scrollTop(console[0].scrollHeight); }, fail: function(){ @@ -71,7 +73,6 @@ angular.scenario.Runner.prototype = { while (current[0] != console[0]) { if (current.is('li')) current.addClass('fail'); - current.removeClass('collapsed'); current = current.parent(); } } -- cgit v1.2.3 From 5992e81b2e302c3b3375567e347227f6a9496585 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Tue, 25 May 2010 14:23:52 -0700 Subject: added rake task to create a single file for scenario runner --- src/scenario/DSL.js | 1 + src/scenario/Runner.js | 18 +++++++++--------- src/scenario/angular.prefix | 30 ++++++++++++++++++++++++++++++ src/scenario/angular.suffix | 11 +++++++++++ src/scenario/bootstrap.js | 9 ++++++++- 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 src/scenario/angular.prefix create mode 100644 src/scenario/angular.suffix (limited to 'src') diff --git a/src/scenario/DSL.js b/src/scenario/DSL.js index 842f7c7a..8cbb256d 100644 --- a/src/scenario/DSL.js +++ b/src/scenario/DSL.js @@ -5,6 +5,7 @@ angular.scenario.dsl.browser = { self.testFrame.load(function(){ self.testFrame.unbind(); self.testDocument = jQuery(self.testWindow.document); + self.testWindow = self.testFrame[0].contentWindow; done(); }); if (this.testFrame.attr('src') == url) { diff --git a/src/scenario/Runner.js b/src/scenario/Runner.js index 8669f56b..01e16e79 100644 --- a/src/scenario/Runner.js +++ b/src/scenario/Runner.js @@ -1,11 +1,10 @@ -angular['scenario'] = (angular['scenario'] = {}); -angular.scenario['dsl'] = (angular.scenario['dsl'] = {}); +angular['scenario'] = angular['scenario'] || (angular['scenario'] = {}); +angular.scenario['dsl'] = angular.scenario['dsl'] || (angular.scenario['dsl'] = {}); angular.scenario.Runner = function(scope, jQuery){ var self = scope.$scenario = this; this.scope = scope; this.jQuery = jQuery; - angular.extend(scope, angular.scenario.dsl); var specs = this.specs = {}; var path = []; @@ -30,7 +29,7 @@ angular.scenario.Runner = function(scope, jQuery){ self.currentSpec = null; }; this.logger = function returnNoop(){ - return angular.extend(returnNoop, {close:angular.noop, fail:angular.noop});; + return _(returnNoop).extend({close:_.identity, fail:_.identity});; }; }; @@ -60,7 +59,7 @@ angular.scenario.Runner.prototype = { var element = jQuery('
                • '); element.find('span').text(text); container.append(element); - return angular.extend(logger(element), { + return _(logger(element)).extend({ close: function(){ element.removeClass('running'); if(!element.hasClass('fail')) @@ -81,7 +80,7 @@ angular.scenario.Runner.prototype = { } this.logger = logger(console); var specNames = []; - angular.foreach(this.specs, function(spec, name){ + _(this.specs).each(function(spec, name){ specNames.push(name); }, this); specNames.sort(); @@ -109,7 +108,7 @@ angular.scenario.Runner.prototype = { result.passed = false; result.failed = true; result.error = error; - result.log('fail', angular.isString(error) ? error : angular.toJson(error)).fail(); + result.log('fail', _(error).isString() ? error : toJson(error)).fail(); } }; specThis = { @@ -122,11 +121,11 @@ angular.scenario.Runner.prototype = { function done() { result.finished = true; stepLogger.close(); - (callback||angular.noop).call(specThis); + (callback||_.identity).call(specThis); } function next(){ var step = spec.steps[spec.nextStepIndex]; - (result.log || {close:angular.noop}).close(); + (result.log || {close:_.identity}).close(); result.log = null; if (step) { spec.nextStepIndex ++; @@ -134,6 +133,7 @@ angular.scenario.Runner.prototype = { try { step.fn.call(specThis, next); } catch (e) { + console.error(e); result.fail(e); done(); } diff --git a/src/scenario/angular.prefix b/src/scenario/angular.prefix new file mode 100644 index 00000000..5b44e17c --- /dev/null +++ b/src/scenario/angular.prefix @@ -0,0 +1,30 @@ +/** + * The MIT License + * + * Copyright (c) 2010 Adam Abrons and Misko Hevery http://getangular.com + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +(function(window, document, previousOnLoad){ + window.angular = { + scenario: { + dsl: window + } + }; + diff --git a/src/scenario/angular.suffix b/src/scenario/angular.suffix new file mode 100644 index 00000000..fc861cbf --- /dev/null +++ b/src/scenario/angular.suffix @@ -0,0 +1,11 @@ + + var $scenarioRunner = new angular.scenario.Runner(window, jQuery); + + window.onload = function(){ + try { + if (previousOnLoad) previousOnLoad(); + } catch(e) {} + $scenarioRunner.run(jQuery(window.document.body)); + }; + +})(window, document, window.onload); diff --git a/src/scenario/bootstrap.js b/src/scenario/bootstrap.js index 4c9cdc8d..694d0e97 100644 --- a/src/scenario/bootstrap.js +++ b/src/scenario/bootstrap.js @@ -18,6 +18,12 @@ document.write(''); } + window.angular = { + scenario: { + dsl: window + } + }; + window.onload = function(){ _.defer(function(){ $scenarioRunner.run(jQuery(window.document.body)); @@ -27,8 +33,9 @@ addCSS("../../css/angular-scenario.css"); addScript("../../lib/underscore/underscore.js"); addScript("../../lib/jquery/jquery-1.4.2.js"); - addScript("../angular-bootstrap.js"); addScript("Runner.js"); + addScript("../Angular.js"); + addScript("../JSON.js"); addScript("DSL.js"); document.write('