mirror of
https://github.com/thedjinn/js303.git
synced 2025-08-17 15:58:13 +02:00
770 lines
26 KiB
JavaScript
770 lines
26 KiB
JavaScript
define("morph",
|
|
["./morph/morph","./morph/dom-helper","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Morph = __dependency1__["default"];
|
|
var Morph;
|
|
__exports__.Morph = Morph;
|
|
var DOMHelper = __dependency2__["default"];
|
|
var DOMHelper;
|
|
__exports__.DOMHelper = DOMHelper;
|
|
});
|
|
define("morph/dom-helper",
|
|
["../morph/morph","./dom-helper/build-html-dom","exports"],
|
|
function(__dependency1__, __dependency2__, __exports__) {
|
|
"use strict";
|
|
var Morph = __dependency1__["default"];
|
|
var buildHTMLDOM = __dependency2__.buildHTMLDOM;
|
|
var svgNamespace = __dependency2__.svgNamespace;
|
|
var svgHTMLIntegrationPoints = __dependency2__.svgHTMLIntegrationPoints;
|
|
|
|
var deletesBlankTextNodes = (function(){
|
|
var element = document.createElement('div');
|
|
element.appendChild( document.createTextNode('') );
|
|
var clonedElement = element.cloneNode(true);
|
|
return clonedElement.childNodes.length === 0;
|
|
})();
|
|
|
|
var ignoresCheckedAttribute = (function(){
|
|
var element = document.createElement('input');
|
|
element.setAttribute('checked', 'checked');
|
|
var clonedElement = element.cloneNode(false);
|
|
return !clonedElement.checked;
|
|
})();
|
|
|
|
function isSVG(ns){
|
|
return ns === svgNamespace;
|
|
}
|
|
|
|
// This is not the namespace of the element, but of
|
|
// the elements inside that elements.
|
|
function interiorNamespace(element){
|
|
if (
|
|
element &&
|
|
element.namespaceURI === svgNamespace &&
|
|
!svgHTMLIntegrationPoints[element.tagName]
|
|
) {
|
|
return svgNamespace;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// The HTML spec allows for "omitted start tags". These tags are optional
|
|
// when their intended child is the first thing in the parent tag. For
|
|
// example, this is a tbody start tag:
|
|
//
|
|
// <table>
|
|
// <tbody>
|
|
// <tr>
|
|
//
|
|
// The tbody may be omitted, and the browser will accept and render:
|
|
//
|
|
// <table>
|
|
// <tr>
|
|
//
|
|
// However, the omitted start tag will still be added to the DOM. Here
|
|
// we test the string and context to see if the browser is about to
|
|
// perform this cleanup.
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#optional-tags
|
|
// describes which tags are omittable. The spec for tbody and colgroup
|
|
// explains this behavior:
|
|
//
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-tbody-element
|
|
// http://www.whatwg.org/specs/web-apps/current-work/multipage/tables.html#the-colgroup-element
|
|
//
|
|
|
|
var omittedStartTagChildTest = /<([\w:]+)/;
|
|
function detectOmittedStartTag(string, contextualElement){
|
|
// Omitted start tags are only inside table tags.
|
|
if (contextualElement.tagName === 'TABLE') {
|
|
var omittedStartTagChildMatch = omittedStartTagChildTest.exec(string);
|
|
if (omittedStartTagChildMatch) {
|
|
var omittedStartTagChild = omittedStartTagChildMatch[1];
|
|
// It is already asserted that the contextual element is a table
|
|
// and not the proper start tag. Just see if a tag was omitted.
|
|
return omittedStartTagChild === 'tr' ||
|
|
omittedStartTagChild === 'col';
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildSVGDOM(html, dom){
|
|
var div = dom.document.createElement('div');
|
|
div.innerHTML = '<svg>'+html+'</svg>';
|
|
return div.firstChild.childNodes;
|
|
}
|
|
|
|
/*
|
|
* A class wrapping DOM functions to address environment compatibility,
|
|
* namespaces, contextual elements for morph un-escaped content
|
|
* insertion.
|
|
*
|
|
* When entering a template, a DOMHelper should be passed:
|
|
*
|
|
* template(context, { hooks: hooks, dom: new DOMHelper() });
|
|
*
|
|
* TODO: support foreignObject as a passed contextual element. It has
|
|
* a namespace (svg) that does not match its internal namespace
|
|
* (xhtml).
|
|
*
|
|
* @class DOMHelper
|
|
* @constructor
|
|
* @param {HTMLDocument} _document The document DOM methods are proxied to
|
|
*/
|
|
function DOMHelper(_document){
|
|
this.document = _document || window.document;
|
|
this.namespace = null;
|
|
}
|
|
|
|
var prototype = DOMHelper.prototype;
|
|
prototype.constructor = DOMHelper;
|
|
|
|
prototype.insertBefore = function(element, childElement, referenceChild) {
|
|
return element.insertBefore(childElement, referenceChild);
|
|
};
|
|
|
|
prototype.appendChild = function(element, childElement) {
|
|
return element.appendChild(childElement);
|
|
};
|
|
|
|
prototype.appendText = function(element, text) {
|
|
return element.appendChild(this.document.createTextNode(text));
|
|
};
|
|
|
|
prototype.setAttribute = function(element, name, value) {
|
|
element.setAttribute(name, value);
|
|
};
|
|
|
|
if (document.createElementNS) {
|
|
// Only opt into namespace detection if a contextualElement
|
|
// is passed.
|
|
prototype.createElement = function(tagName, contextualElement) {
|
|
var namespace = this.namespace;
|
|
if (contextualElement) {
|
|
if (tagName === 'svg') {
|
|
namespace = svgNamespace;
|
|
} else {
|
|
namespace = interiorNamespace(contextualElement);
|
|
}
|
|
}
|
|
if (namespace) {
|
|
return this.document.createElementNS(namespace, tagName);
|
|
} else {
|
|
return this.document.createElement(tagName);
|
|
}
|
|
};
|
|
} else {
|
|
prototype.createElement = function(tagName) {
|
|
return this.document.createElement(tagName);
|
|
};
|
|
}
|
|
|
|
prototype.setNamespace = function(ns) {
|
|
this.namespace = ns;
|
|
};
|
|
|
|
prototype.detectNamespace = function(element) {
|
|
this.namespace = interiorNamespace(element);
|
|
};
|
|
|
|
prototype.createDocumentFragment = function(){
|
|
return this.document.createDocumentFragment();
|
|
};
|
|
|
|
prototype.createTextNode = function(text){
|
|
return this.document.createTextNode(text);
|
|
};
|
|
|
|
prototype.repairClonedNode = function(element, blankChildTextNodes, isChecked){
|
|
if (deletesBlankTextNodes && blankChildTextNodes.length > 0) {
|
|
for (var i=0, len=blankChildTextNodes.length;i<len;i++){
|
|
var textNode = this.document.createTextNode(''),
|
|
offset = blankChildTextNodes[i],
|
|
before = element.childNodes[offset];
|
|
if (before) {
|
|
element.insertBefore(textNode, before);
|
|
} else {
|
|
element.appendChild(textNode);
|
|
}
|
|
}
|
|
}
|
|
if (ignoresCheckedAttribute && isChecked) {
|
|
element.setAttribute('checked', 'checked');
|
|
}
|
|
};
|
|
|
|
prototype.cloneNode = function(element, deep){
|
|
var clone = element.cloneNode(!!deep);
|
|
return clone;
|
|
};
|
|
|
|
prototype.createMorph = function(parent, start, end, contextualElement){
|
|
if (!contextualElement && parent.nodeType === 1) {
|
|
contextualElement = parent;
|
|
}
|
|
return new Morph(parent, start, end, this, contextualElement);
|
|
};
|
|
|
|
// This helper is just to keep the templates good looking,
|
|
// passing integers instead of element references.
|
|
prototype.createMorphAt = function(parent, startIndex, endIndex, contextualElement){
|
|
var childNodes = parent.childNodes,
|
|
start = startIndex === -1 ? null : childNodes[startIndex],
|
|
end = endIndex === -1 ? null : childNodes[endIndex];
|
|
return this.createMorph(parent, start, end, contextualElement);
|
|
};
|
|
|
|
prototype.insertMorphBefore = function(element, referenceChild, contextualElement) {
|
|
var start = this.document.createTextNode('');
|
|
var end = this.document.createTextNode('');
|
|
element.insertBefore(start, referenceChild);
|
|
element.insertBefore(end, referenceChild);
|
|
return this.createMorph(element, start, end, contextualElement);
|
|
};
|
|
|
|
prototype.appendMorph = function(element, contextualElement) {
|
|
var start = this.document.createTextNode('');
|
|
var end = this.document.createTextNode('');
|
|
element.appendChild(start);
|
|
element.appendChild(end);
|
|
return this.createMorph(element, start, end, contextualElement);
|
|
};
|
|
|
|
prototype.parseHTML = function(html, contextualElement) {
|
|
var isSVGContent = (
|
|
isSVG(this.namespace) &&
|
|
!svgHTMLIntegrationPoints[contextualElement.tagName]
|
|
);
|
|
|
|
if (isSVGContent) {
|
|
return buildSVGDOM(html, this);
|
|
} else {
|
|
var nodes = buildHTMLDOM(html, contextualElement, this);
|
|
if (detectOmittedStartTag(html, contextualElement)) {
|
|
var node = nodes[0];
|
|
while (node && node.nodeType !== 1) {
|
|
node = node.nextSibling;
|
|
}
|
|
return node.childNodes;
|
|
} else {
|
|
return nodes;
|
|
}
|
|
}
|
|
};
|
|
|
|
__exports__["default"] = DOMHelper;
|
|
});
|
|
define("morph/dom-helper/build-html-dom",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
var svgHTMLIntegrationPoints = {foreignObject: 1, desc: 1, title: 1};
|
|
__exports__.svgHTMLIntegrationPoints = svgHTMLIntegrationPoints;var svgNamespace = 'http://www.w3.org/2000/svg';
|
|
__exports__.svgNamespace = svgNamespace;
|
|
// Safari does not like using innerHTML on SVG HTML integration
|
|
// points (desc/title/foreignObject).
|
|
var needsIntegrationPointFix = document.createElementNS && (function() {
|
|
// In FF title will not accept innerHTML.
|
|
var testEl = document.createElementNS(svgNamespace, 'title');
|
|
testEl.innerHTML = "<div></div>";
|
|
return testEl.childNodes.length === 0 || testEl.childNodes[0].nodeType !== 1;
|
|
})();
|
|
|
|
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
|
|
// is a "zero-scope" element. This problem can be worked around by making
|
|
// the first node an invisible text node. We, like Modernizr, use ­
|
|
var needsShy = (function() {
|
|
var testEl = document.createElement('div');
|
|
testEl.innerHTML = "<div></div>";
|
|
testEl.firstChild.innerHTML = "<script><\/script>";
|
|
return testEl.firstChild.innerHTML === '';
|
|
})();
|
|
|
|
// IE 8 (and likely earlier) likes to move whitespace preceeding
|
|
// a script tag to appear after it. This means that we can
|
|
// accidentally remove whitespace when updating a morph.
|
|
var movesWhitespace = document && (function() {
|
|
var testEl = document.createElement('div');
|
|
testEl.innerHTML = "Test: <script type='text/x-placeholder'><\/script>Value";
|
|
return testEl.childNodes[0].nodeValue === 'Test:' &&
|
|
testEl.childNodes[2].nodeValue === ' Value';
|
|
})();
|
|
|
|
// IE 9 and earlier don't allow us to set innerHTML on col, colgroup, frameset,
|
|
// html, style, table, tbody, tfoot, thead, title, tr. Detect this and add
|
|
// them to an initial list of corrected tags.
|
|
//
|
|
// Here we are only dealing with the ones which can have child nodes.
|
|
//
|
|
var tagNamesRequiringInnerHTMLFix, tableNeedsInnerHTMLFix;
|
|
var tableInnerHTMLTestElement = document.createElement('table');
|
|
try {
|
|
tableInnerHTMLTestElement.innerHTML = '<tbody></tbody>';
|
|
} catch (e) {
|
|
} finally {
|
|
tableNeedsInnerHTMLFix = (tableInnerHTMLTestElement.childNodes.length === 0);
|
|
}
|
|
if (tableNeedsInnerHTMLFix) {
|
|
tagNamesRequiringInnerHTMLFix = {
|
|
colgroup: ['table'],
|
|
table: [],
|
|
tbody: ['table'],
|
|
tfoot: ['table'],
|
|
thead: ['table'],
|
|
tr: ['table', 'tbody']
|
|
};
|
|
}
|
|
|
|
// IE 8 doesn't allow setting innerHTML on a select tag. Detect this and
|
|
// add it to the list of corrected tags.
|
|
//
|
|
var selectInnerHTMLTestElement = document.createElement('select');
|
|
selectInnerHTMLTestElement.innerHTML = '<option></option>';
|
|
if (selectInnerHTMLTestElement) {
|
|
tagNamesRequiringInnerHTMLFix = tagNamesRequiringInnerHTMLFix || {};
|
|
tagNamesRequiringInnerHTMLFix.select = [];
|
|
}
|
|
|
|
function scriptSafeInnerHTML(element, html) {
|
|
// without a leading text node, IE will drop a leading script tag.
|
|
html = '­'+html;
|
|
|
|
element.innerHTML = html;
|
|
|
|
var nodes = element.childNodes;
|
|
|
|
// Look for ­ to remove it.
|
|
var shyElement = nodes[0];
|
|
while (shyElement.nodeType === 1 && !shyElement.nodeName) {
|
|
shyElement = shyElement.firstChild;
|
|
}
|
|
// At this point it's the actual unicode character.
|
|
if (shyElement.nodeType === 3 && shyElement.nodeValue.charAt(0) === "\u00AD") {
|
|
var newValue = shyElement.nodeValue.slice(1);
|
|
if (newValue.length) {
|
|
shyElement.nodeValue = shyElement.nodeValue.slice(1);
|
|
} else {
|
|
shyElement.parentNode.removeChild(shyElement);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
|
|
function buildDOMWithFix(html, contextualElement){
|
|
var tagName = contextualElement.tagName;
|
|
|
|
// Firefox versions < 11 do not have support for element.outerHTML.
|
|
var outerHTML = contextualElement.outerHTML || new XMLSerializer().serializeToString(contextualElement);
|
|
if (!outerHTML) {
|
|
throw "Can't set innerHTML on "+tagName+" in this browser";
|
|
}
|
|
|
|
var wrappingTags = tagNamesRequiringInnerHTMLFix[tagName.toLowerCase()];
|
|
var startTag = outerHTML.match(new RegExp("<"+tagName+"([^>]*)>", 'i'))[0];
|
|
var endTag = '</'+tagName+'>';
|
|
|
|
var wrappedHTML = [startTag, html, endTag];
|
|
|
|
var i = wrappingTags.length;
|
|
var wrappedDepth = 1 + i;
|
|
while(i--) {
|
|
wrappedHTML.unshift('<'+wrappingTags[i]+'>');
|
|
wrappedHTML.push('</'+wrappingTags[i]+'>');
|
|
}
|
|
|
|
var wrapper = document.createElement('div');
|
|
scriptSafeInnerHTML(wrapper, wrappedHTML.join(''));
|
|
var element = wrapper;
|
|
while (wrappedDepth--) {
|
|
element = element.firstChild;
|
|
while (element && element.nodeType !== 1) {
|
|
element = element.nextSibling;
|
|
}
|
|
}
|
|
while (element && element.tagName !== tagName) {
|
|
element = element.nextSibling;
|
|
}
|
|
return element ? element.childNodes : [];
|
|
}
|
|
|
|
var buildDOM;
|
|
if (needsShy) {
|
|
buildDOM = function buildDOM(html, contextualElement, dom){
|
|
contextualElement = dom.cloneNode(contextualElement, false);
|
|
scriptSafeInnerHTML(contextualElement, html);
|
|
return contextualElement.childNodes;
|
|
};
|
|
} else {
|
|
buildDOM = function buildDOM(html, contextualElement, dom){
|
|
contextualElement = dom.cloneNode(contextualElement, false);
|
|
contextualElement.innerHTML = html;
|
|
return contextualElement.childNodes;
|
|
};
|
|
}
|
|
|
|
|
|
var buildIESafeDOM;
|
|
if (tagNamesRequiringInnerHTMLFix.length > 0 || movesWhitespace) {
|
|
buildIESafeDOM = function buildIESafeDOM(html, contextualElement, dom) {
|
|
// Make a list of the leading text on script nodes. Include
|
|
// script tags without any whitespace for easier processing later.
|
|
var spacesBefore = [];
|
|
var spacesAfter = [];
|
|
html = html.replace(/(\s*)(<script)/g, function(match, spaces, tag) {
|
|
spacesBefore.push(spaces);
|
|
return tag;
|
|
});
|
|
|
|
html = html.replace(/(<\/script>)(\s*)/g, function(match, tag, spaces) {
|
|
spacesAfter.push(spaces);
|
|
return tag;
|
|
});
|
|
|
|
// Fetch nodes
|
|
var nodes;
|
|
if (tagNamesRequiringInnerHTMLFix[contextualElement.tagName.toLowerCase()]) {
|
|
// buildDOMWithFix uses string wrappers for problematic innerHTML.
|
|
nodes = buildDOMWithFix(html, contextualElement);
|
|
} else {
|
|
nodes = buildDOM(html, contextualElement, dom);
|
|
}
|
|
|
|
// Build a list of script tags, the nodes themselves will be
|
|
// mutated as we add test nodes.
|
|
var i, j, node, nodeScriptNodes;
|
|
var scriptNodes = [];
|
|
for (i=0;node=nodes[i];i++) {
|
|
if (node.nodeType !== 1) {
|
|
continue;
|
|
}
|
|
if (node.tagName === 'SCRIPT') {
|
|
scriptNodes.push(node);
|
|
} else {
|
|
nodeScriptNodes = node.getElementsByTagName('script');
|
|
for (j=0;j<nodeScriptNodes.length;j++) {
|
|
scriptNodes.push(nodeScriptNodes[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Walk the script tags and put back their leading text nodes.
|
|
var scriptNode, textNode, spaceBefore, spaceAfter;
|
|
for (i=0;scriptNode=scriptNodes[i];i++) {
|
|
spaceBefore = spacesBefore[i];
|
|
if (spaceBefore && spaceBefore.length > 0) {
|
|
textNode = dom.document.createTextNode(spaceBefore);
|
|
scriptNode.parentNode.insertBefore(textNode, scriptNode);
|
|
}
|
|
|
|
spaceAfter = spacesAfter[i];
|
|
if (spaceAfter && spaceAfter.length > 0) {
|
|
textNode = dom.document.createTextNode(spaceAfter);
|
|
scriptNode.parentNode.insertBefore(textNode, scriptNode.nextSibling);
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
};
|
|
} else {
|
|
buildIESafeDOM = buildDOM;
|
|
}
|
|
|
|
var buildHTMLDOM;
|
|
if (needsIntegrationPointFix) {
|
|
buildHTMLDOM = function buildHTMLDOM(html, contextualElement, dom){
|
|
if (svgHTMLIntegrationPoints[contextualElement.tagName]) {
|
|
return buildIESafeDOM(html, document.createElement('div'), dom);
|
|
} else {
|
|
return buildIESafeDOM(html, contextualElement, dom);
|
|
}
|
|
};
|
|
} else {
|
|
buildHTMLDOM = buildIESafeDOM;
|
|
}
|
|
|
|
__exports__.buildHTMLDOM = buildHTMLDOM;
|
|
});
|
|
define("morph/morph",
|
|
["exports"],
|
|
function(__exports__) {
|
|
"use strict";
|
|
var splice = Array.prototype.splice;
|
|
|
|
function ensureStartEnd(start, end) {
|
|
if (start === null || end === null) {
|
|
throw new Error('a fragment parent must have boundary nodes in order to detect insertion');
|
|
}
|
|
}
|
|
|
|
function ensureContext(contextualElement) {
|
|
if (!contextualElement || contextualElement.nodeType !== 1) {
|
|
throw new Error('An element node must be provided for a contextualElement, you provided ' +
|
|
(contextualElement ? 'nodeType ' + contextualElement.nodeType : 'nothing'));
|
|
}
|
|
}
|
|
|
|
// TODO: this is an internal API, this should be an assert
|
|
function Morph(parent, start, end, domHelper, contextualElement) {
|
|
if (parent.nodeType === 11) {
|
|
ensureStartEnd(start, end);
|
|
this.element = null;
|
|
} else {
|
|
this.element = parent;
|
|
}
|
|
this._parent = parent;
|
|
this.start = start;
|
|
this.end = end;
|
|
this.domHelper = domHelper;
|
|
ensureContext(contextualElement);
|
|
this.contextualElement = contextualElement;
|
|
this.reset();
|
|
}
|
|
|
|
Morph.prototype.reset = function() {
|
|
this.text = null;
|
|
this.owner = null;
|
|
this.morphs = null;
|
|
this.before = null;
|
|
this.after = null;
|
|
this.escaped = true;
|
|
};
|
|
|
|
Morph.prototype.parent = function () {
|
|
if (!this.element) {
|
|
var parent = this.start.parentNode;
|
|
if (this._parent !== parent) {
|
|
this.element = this._parent = parent;
|
|
}
|
|
}
|
|
return this._parent;
|
|
};
|
|
|
|
Morph.prototype.destroy = function () {
|
|
if (this.owner) {
|
|
this.owner.removeMorph(this);
|
|
} else {
|
|
clear(this.element || this.parent(), this.start, this.end);
|
|
}
|
|
};
|
|
|
|
Morph.prototype.removeMorph = function (morph) {
|
|
var morphs = this.morphs;
|
|
for (var i=0, l=morphs.length; i<l; i++) {
|
|
if (morphs[i] === morph) {
|
|
this.replace(i, 1);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
Morph.prototype.update = function (nodeOrString) {
|
|
this._update(this.element || this.parent(), nodeOrString);
|
|
};
|
|
|
|
Morph.prototype.updateNode = function (node) {
|
|
var parent = this.element || this.parent();
|
|
if (!node) return this._updateText(parent, '');
|
|
this._updateNode(parent, node);
|
|
};
|
|
|
|
Morph.prototype.updateText = function (text) {
|
|
this._updateText(this.element || this.parent(), text);
|
|
};
|
|
|
|
Morph.prototype.updateHTML = function (html) {
|
|
var parent = this.element || this.parent();
|
|
if (!html) return this._updateText(parent, '');
|
|
this._updateHTML(parent, html);
|
|
};
|
|
|
|
Morph.prototype._update = function (parent, nodeOrString) {
|
|
if (nodeOrString === null || nodeOrString === undefined) {
|
|
this._updateText(parent, '');
|
|
} else if (typeof nodeOrString === 'string') {
|
|
if (this.escaped) {
|
|
this._updateText(parent, nodeOrString);
|
|
} else {
|
|
this._updateHTML(parent, nodeOrString);
|
|
}
|
|
} else if (nodeOrString.nodeType) {
|
|
this._updateNode(parent, nodeOrString);
|
|
} else if (nodeOrString.string) { // duck typed SafeString
|
|
this._updateHTML(parent, nodeOrString.string);
|
|
} else {
|
|
this._updateText(parent, nodeOrString.toString());
|
|
}
|
|
};
|
|
|
|
Morph.prototype._updateNode = function (parent, node) {
|
|
if (this.text) {
|
|
if (node.nodeType === 3) {
|
|
this.text.nodeValue = node.nodeValue;
|
|
return;
|
|
} else {
|
|
this.text = null;
|
|
}
|
|
}
|
|
var start = this.start, end = this.end;
|
|
clear(parent, start, end);
|
|
parent.insertBefore(node, end);
|
|
if (this.before !== null) {
|
|
this.before.end = start.nextSibling;
|
|
}
|
|
if (this.after !== null) {
|
|
this.after.start = end.previousSibling;
|
|
}
|
|
};
|
|
|
|
Morph.prototype._updateText = function (parent, text) {
|
|
if (this.text) {
|
|
this.text.nodeValue = text;
|
|
return;
|
|
}
|
|
var node = this.domHelper.createTextNode(text);
|
|
this.text = node;
|
|
clear(parent, this.start, this.end);
|
|
parent.insertBefore(node, this.end);
|
|
if (this.before !== null) {
|
|
this.before.end = node;
|
|
}
|
|
if (this.after !== null) {
|
|
this.after.start = node;
|
|
}
|
|
};
|
|
|
|
Morph.prototype._updateHTML = function (parent, html) {
|
|
var start = this.start, end = this.end;
|
|
clear(parent, start, end);
|
|
this.text = null;
|
|
var childNodes = this.domHelper.parseHTML(html, this.contextualElement);
|
|
appendChildren(parent, end, childNodes);
|
|
if (this.before !== null) {
|
|
this.before.end = start.nextSibling;
|
|
}
|
|
if (this.after !== null) {
|
|
this.after.start = end.previousSibling;
|
|
}
|
|
};
|
|
|
|
Morph.prototype.append = function (node) {
|
|
if (this.morphs === null) this.morphs = [];
|
|
var index = this.morphs.length;
|
|
return this.insert(index, node);
|
|
};
|
|
|
|
Morph.prototype.insert = function (index, node) {
|
|
if (this.morphs === null) this.morphs = [];
|
|
var parent = this.element || this.parent();
|
|
var morphs = this.morphs;
|
|
var before = index > 0 ? morphs[index-1] : null;
|
|
var after = index < morphs.length ? morphs[index] : null;
|
|
var start = before === null ? this.start : (before.end === null ? parent.lastChild : before.end.previousSibling);
|
|
var end = after === null ? this.end : (after.start === null ? parent.firstChild : after.start.nextSibling);
|
|
var morph = new Morph(parent, start, end, this.domHelper, this.contextualElement);
|
|
|
|
morph.owner = this;
|
|
morph._update(parent, node);
|
|
|
|
if (before !== null) {
|
|
morph.before = before;
|
|
before.end = start.nextSibling;
|
|
before.after = morph;
|
|
}
|
|
|
|
if (after !== null) {
|
|
morph.after = after;
|
|
after.before = morph;
|
|
after.start = end.previousSibling;
|
|
}
|
|
|
|
this.morphs.splice(index, 0, morph);
|
|
return morph;
|
|
};
|
|
|
|
Morph.prototype.replace = function (index, removedLength, addedNodes) {
|
|
if (this.morphs === null) this.morphs = [];
|
|
var parent = this.element || this.parent();
|
|
var morphs = this.morphs;
|
|
var before = index > 0 ? morphs[index-1] : null;
|
|
var after = index+removedLength < morphs.length ? morphs[index+removedLength] : null;
|
|
var start = before === null ? this.start : (before.end === null ? parent.lastChild : before.end.previousSibling);
|
|
var end = after === null ? this.end : (after.start === null ? parent.firstChild : after.start.nextSibling);
|
|
var addedLength = addedNodes === undefined ? 0 : addedNodes.length;
|
|
var args, i, current;
|
|
|
|
if (removedLength > 0) {
|
|
clear(parent, start, end);
|
|
}
|
|
|
|
if (addedLength === 0) {
|
|
if (before !== null) {
|
|
before.after = after;
|
|
before.end = end;
|
|
}
|
|
if (after !== null) {
|
|
after.before = before;
|
|
after.start = start;
|
|
}
|
|
morphs.splice(index, removedLength);
|
|
return;
|
|
}
|
|
|
|
args = new Array(addedLength+2);
|
|
if (addedLength > 0) {
|
|
for (i=0; i<addedLength; i++) {
|
|
args[i+2] = current = new Morph(parent, start, end, this.domHelper, this.contextualElement);
|
|
current._update(parent, addedNodes[i]);
|
|
current.owner = this;
|
|
if (before !== null) {
|
|
current.before = before;
|
|
before.end = start.nextSibling;
|
|
before.after = current;
|
|
}
|
|
before = current;
|
|
start = end === null ? parent.lastChild : end.previousSibling;
|
|
}
|
|
if (after !== null) {
|
|
current.after = after;
|
|
after.before = current;
|
|
after.start = end.previousSibling;
|
|
}
|
|
}
|
|
|
|
args[0] = index;
|
|
args[1] = removedLength;
|
|
|
|
splice.apply(morphs, args);
|
|
};
|
|
|
|
function appendChildren(parent, end, nodeList) {
|
|
var ref = end;
|
|
var i = nodeList.length;
|
|
var node;
|
|
|
|
while (i--) {
|
|
node = nodeList[i];
|
|
parent.insertBefore(node, ref);
|
|
ref = node;
|
|
}
|
|
}
|
|
|
|
function clear(parent, start, end) {
|
|
var current, previous;
|
|
if (end === null) {
|
|
current = parent.lastChild;
|
|
} else {
|
|
current = end.previousSibling;
|
|
}
|
|
|
|
while (current !== null && current !== start) {
|
|
previous = current.previousSibling;
|
|
parent.removeChild(current);
|
|
current = previous;
|
|
}
|
|
}
|
|
|
|
__exports__["default"] = Morph;
|
|
}); |