diff options
Diffstat (limited to 'infrastructure/ace/www/magicdom.js')
-rw-r--r-- | infrastructure/ace/www/magicdom.js | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/infrastructure/ace/www/magicdom.js b/infrastructure/ace/www/magicdom.js new file mode 100644 index 0000000..4bad3d4 --- /dev/null +++ b/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; +} |