diff options
Diffstat (limited to 'src/delete/Binder.js')
| -rw-r--r-- | src/delete/Binder.js | 356 |
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 = ' ' + 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')); + } +}; |
