aboutsummaryrefslogtreecommitdiffstats
path: root/src/delete/Binder.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/delete/Binder.js')
-rw-r--r--src/delete/Binder.js356
1 files changed, 356 insertions, 0 deletions
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 = '&nbsp;' + 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('<select/>').append(jQuery(node).clone()).html();
+ if (!html.match(/<option(\s.*\s|\s)value\s*=\s*.*>.*<\/\s*option\s*>/gi)) {
+ if (Binder.hasBinding(node.text)) {
+ jQuery(node).attr('ng-bind-attr', angular.toJson({'value':node.text}));
+ } else {
+ 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'));
+ }
+};