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