aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/infrastructure/ace/www/magicdom.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/infrastructure/ace/www/magicdom.js')
-rw-r--r--trunk/infrastructure/ace/www/magicdom.js293
1 files changed, 293 insertions, 0 deletions
diff --git a/trunk/infrastructure/ace/www/magicdom.js b/trunk/infrastructure/ace/www/magicdom.js
new file mode 100644
index 0000000..4bad3d4
--- /dev/null
+++ b/trunk/infrastructure/ace/www/magicdom.js
@@ -0,0 +1,293 @@
+/**
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS-IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+function makeMagicDom(rootDomNode, contentWindow){
+ function nodeToString(node) {
+ if (isNodeText(node)) return '"'+node.nodeValue+'"';
+ else return '<'+node.tagName+'>';
+ }
+
+ var doc = rootDomNode.ownerDocument || rootDomNode.document;
+
+ function childIndex(dnode) {
+ var idx = 0;
+ var n = dnode;
+ while (n.previousSibling) {
+ idx++;
+ n = n.previousSibling;
+ }
+ return idx;
+ }
+
+ function ensureNormalized(dnode) {
+ function mergePair(text1, text2) {
+ var theParent = text1.parentNode;
+ var newTextNode = mdom.doc.createTextNode(text1.nodeValue+""+text2.nodeValue);
+ theParent.insertBefore(newTextNode, text1);
+ theParent.removeChild(text1);
+ theParent.removeChild(text2);
+ return newTextNode;
+ }
+
+ var n = dnode;
+ if (!isNodeText(n)) return;
+ while (n.previousSibling && isNodeText(n.previousSibling)) {
+ n = mergePair(n.previousSibling, n);
+ }
+ while (n.nextSibling && isNodeText(n.nextSibling)) {
+ n = mergePair(n, n.nextSibling);
+ }
+ }
+
+ function nextUniqueId() {
+ // returns new unique identifier string;
+ // not actually checked for uniqueness, but unique
+ // wrt magicdom.
+ // is document-unique to allow document.getElementById even
+ // in theoretical case of multiple magicdoms per doc
+ var doc = mdom.doc;
+ var nextId = (getAssoc(doc, "nextId") || 1);
+ setAssoc(doc, "nextId", nextId+1);
+ return "magicdomid"+nextId;
+ }
+
+ var nodeProto = {
+ parent: function() {
+ return wrapDom(((! this.isRoot) && this.dom.parentNode) || null);
+ },
+ index: function() {
+ return childIndex(this.dom);
+ },
+ equals: function (otherNode) {
+ return otherNode && otherNode.dom && (this.dom == otherNode.dom);
+ },
+ prev: function() {
+ return wrapDom(this.dom.previousSibling || null);
+ },
+ next: function() {
+ return wrapDom(this.dom.nextSibling || null);
+ },
+ remove: function() {
+ if (! this.isRoot) {
+ var dnode = this.dom;
+ var prevSib = dnode.previousSibling;
+ var nextSib = dnode.nextSibling;
+ var normalizeNeeded = (prevSib && isNodeText(prevSib) && nextSib && isNodeText(nextSib));
+ var theParent = dnode.parentNode;
+ theParent.removeChild(dnode);
+ if (normalizeNeeded) {
+ ensureNormalized(prevSib);
+ }
+ }
+ },
+ addNext: function (newNode) {
+ var dnode = this.dom;
+ var nextSib = dnode.nextSibling;
+ if (nextSib) {
+ dnode.parentNode.insertBefore(newNode.dom, nextSib);
+ }
+ else {
+ dnode.parentNode.appendChild(newNode.dom);
+ }
+ if (newNode.isText) ensureNormalized(newNode.dom);
+ },
+ addPrev: function (newNode) {
+ var dnode = this.dom;
+ dnode.parentNode.insertBefore(newNode.dom, dnode);
+ if (newNode.isText) ensureNormalized(newNode.dom);
+ },
+ replaceWith: function (newNodes) { // var-args
+ this.replaceWithArray(arguments);
+ },
+ replaceWithArray: function (newNodes) {
+ var addFunc;
+ if (this.next()) {
+ var next = this.next();
+ addFunc = function (n) { next.addPrev(n); };
+ }
+ else {
+ var parent = this.parent();
+ addFunc = function (n) { parent.appendChild(n); };
+ }
+ // when using "this" functions, have to keep text
+ // nodes from merging inappropriately
+ var tempNode = mdom.newElement("span");
+ this.addNext(tempNode);
+ this.remove();
+ forEach(newNodes, function (n) {
+ addFunc(n);
+ });
+ tempNode.remove();
+ },
+ getProp: function (propName) {
+ return getAssoc(this.dom, propName);
+ },
+ setProp: function (propName, value) {
+ setAssoc(this.dom, propName, value);
+ },
+ // not consistent between browsers in how line-breaks are handled
+ innerText: function() {
+ var dnode = this.dom;
+ if ((typeof dnode.innerText) == "string") return dnode.innerText;
+ if ((typeof dnode.textContent) == "string") return dnode.textContent;
+ if ((typeof dnode.nodeValue) == "string") return dnode.nodeValue;
+ return "";
+ },
+ depth: function() {
+ try { // ZZZ
+ var d = 0;
+ var n = this;
+ while (! n.isRoot) {
+ d++;
+ n = n.parent();
+ }
+ return d;
+ }
+ catch (e) {
+ parent.BAD_NODE = this.dom;
+ throw e;
+ }
+ }
+ };
+
+ var textNodeProto = extend(object(nodeProto), {
+ isText: true,
+ text: function() {
+ return this.dom.nodeValue;
+ },
+ eachChild: function() {},
+ childCount: function() { return 0; },
+ eachDescendant: function() {},
+ // precondition: 0 <= start < end <= length
+ wrapRange: function(start, end, newNode) {
+ var origText = this.text();
+ var text1 = null;
+ if (start > 0) {
+ text1 = mdom.newText(origText.substring(0, start));
+ }
+ var text2 = mdom.newText(origText.substring(start, end));
+ var text3 = null;
+ if (end < origText.length) {
+ text3 = mdom.newText(origText.substring(end, origText.length));
+ }
+ newNode.appendChild(text2);
+ var nodesToUse = []
+ if (text1) nodesToUse.push(text1);
+ nodesToUse.push(newNode);
+ if (text3) nodesToUse.push(text3);
+ this.replaceWithArray(nodesToUse);
+ return [text1, newNode, text3];
+ }
+ });
+
+ var elementNodeProto = extend(object(nodeProto), {
+ isText: false,
+ childCount: function() {
+ return this.dom.childNodes.length;
+ },
+ child: function (i) {
+ return wrapDom(this.dom.childNodes.item(i));
+ },
+ firstChild: function() {
+ return ((this.childCount() > 0) && this.child(0)) || null;
+ },
+ lastChild: function() {
+ return ((this.childCount() > 0) && this.child(this.childCount()-1)) || null;
+ },
+ appendChild: function (newNode) {
+ this.dom.appendChild(newNode.dom);
+ if (newNode.isText) {
+ ensureNormalized(newNode.dom);
+ }
+ },
+ prependChild: function (newNode) {
+ if (this.childCount() > 0) {
+ this.child(0).addPrev(newNode);
+ }
+ else {
+ this.appendChild(newNode);
+ }
+ },
+ eachChild: function (func) {
+ for(var i=0;i<this.childCount();i++) {
+ var result = func(this.child(i), i);
+ if (result) break;
+ }
+ },
+ eachDescendant: function (func) {
+ this.eachChild(function (n) {
+ var result = func(n);
+ if (! result) n.eachDescendant(func);
+ });
+ },
+ dumpContents: function() {
+ var mnode = this, dnode = this.dom;
+ if (mnode.childCount() < 1) {
+ mnode.remove();
+ }
+ else {
+ var theParent = dnode.parentNode;
+ var n;
+ while ((n = dnode.firstChild)) {
+ dnode.removeChild(n);
+ theParent.insertBefore(n, dnode);
+ ensureNormalized(n);
+ }
+ mnode.remove();
+ }
+ },
+ uniqueId: function() {
+ // not actually guaranteed to be unique, e.g. if user copy-pastes
+ // nodes with ids
+ var dnode = this.dom;
+ if (dnode.id) return dnode.id;
+ dnode.id = nextUniqueId();
+ return dnode.id;
+ }
+ });
+
+ function wrapDom(dnode) {
+ if (! dnode) return dnode;
+ var mnode;
+ if (isNodeText(dnode)) {
+ mnode = object(textNodeProto);
+ }
+ else {
+ mnode = object(elementNodeProto);
+ }
+ mnode.isRoot = (dnode == rootDomNode);
+ mnode.dom = dnode;
+ return mnode;
+ }
+
+ var mdom = {};
+ mdom.root = wrapDom(rootDomNode);
+ mdom.doc = doc;
+ mdom.win = contentWindow;
+ mdom.byId = function (id) {
+ return wrapDom(mdom.doc.getElementById(id));
+ }
+ mdom.newText = function (txt) {
+ return wrapDom(mdom.doc.createTextNode(txt));
+ }
+ mdom.newElement = function (tagName) {
+ return wrapDom(mdom.doc.createElement(tagName));
+ }
+ mdom.wrapDom = wrapDom;
+
+ return mdom;
+}