зеркало из https://github.com/mozilla/pjs.git
1892 строки
48 KiB
JavaScript
1892 строки
48 KiB
JavaScript
/*
|
|
* Software License Agreement (BSD License)
|
|
*
|
|
* Copyright (c) 2007, Parakey Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use of this software in source and binary forms, with or without modification,
|
|
* are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other
|
|
* materials provided with the distribution.
|
|
*
|
|
* * Neither the name of Parakey Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior
|
|
* written permission of Parakey Inc.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
|
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Creator:
|
|
* Joe Hewitt
|
|
* Contributors:
|
|
* John J. Barton (IBM Almaden)
|
|
* Jan Odvarko (Mozilla Corp.)
|
|
* Max Stepanov (Aptana Inc.)
|
|
* Rob Campbell (Mozilla Corp.)
|
|
* Hans Hillen (Paciello Group, Mozilla)
|
|
* Curtis Bartley (Mozilla Corp.)
|
|
* Mike Collins (IBM Almaden)
|
|
* Kevin Decker
|
|
* Mike Ratcliffe (Comartis AG)
|
|
* Hernan Rodríguez Colmeiro
|
|
* Austin Andrews
|
|
* Christoph Dorn
|
|
* Steven Roussey (AppCenter Inc, Network54)
|
|
* Firefox Port Contributors:
|
|
* Rob Campbell
|
|
* Julian Viereck
|
|
* Mihai Sucan
|
|
*/
|
|
|
|
var EXPORTED_SYMBOLS = ["domplate", "HTMLTemplates", "domplateUtils"];
|
|
|
|
const Ci = Components.interfaces;
|
|
const Cu = Components.utils;
|
|
|
|
const invisibleTags = {
|
|
"head": true,
|
|
"base": true,
|
|
"basefont": true,
|
|
"isindex": true,
|
|
"link": true,
|
|
"meta": true,
|
|
"script": true,
|
|
"style": true,
|
|
"title": true,
|
|
};
|
|
|
|
// End tags for void elements are forbidden
|
|
// http://wiki.whatwg.org/wiki/HTML_vs._XHTML
|
|
const selfClosingTags = {
|
|
"meta": 1,
|
|
"link": 1,
|
|
"area": 1,
|
|
"base": 1,
|
|
"col": 1,
|
|
"input": 1,
|
|
"img": 1,
|
|
"br": 1,
|
|
"hr": 1,
|
|
"param": 1,
|
|
"embed": 1
|
|
};
|
|
|
|
const reNotWhitespace = /[^\s]/;
|
|
const showTextNodesWithWhitespace = false;
|
|
|
|
var DOM = {};
|
|
var domplateUtils = {};
|
|
|
|
/**
|
|
* Utility function to allow outside caller to set a global scope within
|
|
* domplate's DOM object. Specifically for access to DOM constants and classes.
|
|
* @param aGlobal
|
|
* The global object whose scope we wish to capture.
|
|
*/
|
|
domplateUtils.setDOM = function(aGlobal)
|
|
{
|
|
DOM = aGlobal;
|
|
if (!aGlobal) {
|
|
womb = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* main domplate constructor function.
|
|
*/
|
|
|
|
let domplate = function()
|
|
{
|
|
let lastSubject;
|
|
for (let i = 0; i < arguments.length; ++i)
|
|
lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i];
|
|
|
|
for (let name in lastSubject) {
|
|
let val = lastSubject[name];
|
|
if (isTag(val))
|
|
val.tag.subject = lastSubject;
|
|
}
|
|
|
|
return lastSubject;
|
|
};
|
|
|
|
var womb = null;
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// Base functions
|
|
|
|
function DomplateTag(tagName)
|
|
{
|
|
this.tagName = tagName;
|
|
}
|
|
|
|
function DomplateEmbed()
|
|
{
|
|
}
|
|
|
|
function DomplateLoop()
|
|
{
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// Definitions
|
|
|
|
domplate.context = function(context, fn)
|
|
{
|
|
let lastContext = domplate.lastContext;
|
|
domplate.topContext = context;
|
|
fn.apply(context);
|
|
domplate.topContext = lastContext;
|
|
};
|
|
|
|
domplate.TAG = function()
|
|
{
|
|
let embed = new DomplateEmbed();
|
|
return embed.merge(arguments);
|
|
};
|
|
|
|
domplate.FOR = function()
|
|
{
|
|
let loop = new DomplateLoop();
|
|
return loop.merge(arguments);
|
|
};
|
|
|
|
DomplateTag.prototype =
|
|
{
|
|
/**
|
|
* Initializer for DOM templates. Called to create new Functions objects
|
|
* like TR, TD, OBJLINK, etc. See defineTag
|
|
* @param args keyword argments for the template, the {} brace stuff after
|
|
* the tag name, eg TR({...}, TD(...
|
|
* @param oldTag a nested tag, eg the TD tag in TR({...}, TD(...
|
|
*/
|
|
merge: function(args, oldTag)
|
|
{
|
|
if (oldTag)
|
|
this.tagName = oldTag.tagName;
|
|
|
|
this.context = oldTag ? oldTag.context : null; // normally null on construction
|
|
this.subject = oldTag ? oldTag.subject : null;
|
|
this.attrs = oldTag ? copyObject(oldTag.attrs) : {};
|
|
this.classes = oldTag ? copyObject(oldTag.classes) : {};
|
|
this.props = oldTag ? copyObject(oldTag.props) : null;
|
|
this.listeners = oldTag ? copyArray(oldTag.listeners) : null;
|
|
this.children = oldTag ? copyArray(oldTag.children) : [];
|
|
this.vars = oldTag ? copyArray(oldTag.vars) : [];
|
|
|
|
let attrs = args.length ? args[0] : null;
|
|
let hasAttrs = typeof(attrs) == "object" && !isTag(attrs);
|
|
|
|
// Do not clear children, they can be copied from the oldTag.
|
|
//this.children = [];
|
|
|
|
if (domplate.topContext)
|
|
this.context = domplate.topContext;
|
|
|
|
if (args.length)
|
|
parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children);
|
|
|
|
if (hasAttrs)
|
|
this.parseAttrs(attrs);
|
|
|
|
return creator(this, DomplateTag);
|
|
},
|
|
|
|
/**
|
|
* Parse node attributes.
|
|
* @param args
|
|
* Object of arguments to process.
|
|
*/
|
|
parseAttrs: function(args)
|
|
{
|
|
for (let name in args) {
|
|
let val = parseValue(args[name]);
|
|
readPartNames(val, this.vars);
|
|
|
|
if (name.indexOf("on") == 0) {
|
|
let eventName = name.substr(2);
|
|
if (!this.listeners)
|
|
this.listeners = [];
|
|
this.listeners.push(eventName, val);
|
|
} else if (name[0] == "_") {
|
|
let propName = name.substr(1);
|
|
if (!this.props)
|
|
this.props = {};
|
|
this.props[propName] = val;
|
|
} else if (name[0] == "$") {
|
|
let className = name.substr(1);
|
|
if (!this.classes)
|
|
this.classes = {};
|
|
this.classes[className] = val;
|
|
} else {
|
|
if (name == "class" && this.attrs.hasOwnProperty(name))
|
|
this.attrs[name] += " " + val;
|
|
else
|
|
this.attrs[name] = val;
|
|
}
|
|
}
|
|
},
|
|
|
|
compile: function()
|
|
{
|
|
if (this.renderMarkup)
|
|
return;
|
|
|
|
this.compileMarkup();
|
|
this.compileDOM();
|
|
},
|
|
|
|
compileMarkup: function()
|
|
{
|
|
this.markupArgs = [];
|
|
let topBlock = [], topOuts = [], blocks = [],
|
|
info = {args: this.markupArgs, argIndex: 0};
|
|
|
|
this.generateMarkup(topBlock, topOuts, blocks, info);
|
|
this.addCode(topBlock, topOuts, blocks);
|
|
|
|
let fnBlock = ['(function (__code__, __context__, __in__, __out__'];
|
|
for (let i = 0; i < info.argIndex; ++i)
|
|
fnBlock.push(', s', i);
|
|
fnBlock.push(') {\n');
|
|
|
|
if (this.subject)
|
|
fnBlock.push('with (this) {\n');
|
|
if (this.context)
|
|
fnBlock.push('with (__context__) {\n');
|
|
fnBlock.push('with (__in__) {\n');
|
|
|
|
fnBlock.push.apply(fnBlock, blocks);
|
|
|
|
if (this.subject)
|
|
fnBlock.push('}\n');
|
|
if (this.context)
|
|
fnBlock.push('}\n');
|
|
|
|
fnBlock.push('}})\n');
|
|
|
|
function __link__(tag, code, outputs, args)
|
|
{
|
|
tag.tag.compile();
|
|
|
|
let tagOutputs = [];
|
|
let markupArgs = [code, tag.tag.context, args, tagOutputs];
|
|
markupArgs.push.apply(markupArgs, tag.tag.markupArgs);
|
|
tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs);
|
|
|
|
outputs.push(tag);
|
|
outputs.push(tagOutputs);
|
|
}
|
|
|
|
function __escape__(value)
|
|
{
|
|
function replaceChars(ch)
|
|
{
|
|
switch (ch) {
|
|
case "<":
|
|
return "<";
|
|
case ">":
|
|
return ">";
|
|
case "&":
|
|
return "&";
|
|
case "'":
|
|
return "'";
|
|
case '"':
|
|
return """;
|
|
}
|
|
return "?";
|
|
};
|
|
return String(value).replace(/[<>&"']/g, replaceChars);
|
|
}
|
|
|
|
function __loop__(iter, outputs, fn)
|
|
{
|
|
let iterOuts = [];
|
|
outputs.push(iterOuts);
|
|
|
|
if (iter instanceof Array)
|
|
iter = new ArrayIterator(iter);
|
|
|
|
try {
|
|
while (1) {
|
|
let value = iter.next();
|
|
let itemOuts = [0, 0];
|
|
iterOuts.push(itemOuts);
|
|
fn.apply(this, [value, itemOuts]);
|
|
}
|
|
} catch (exc) {
|
|
if (exc != StopIteration)
|
|
throw exc;
|
|
}
|
|
}
|
|
|
|
let js = fnBlock.join("");
|
|
this.renderMarkup = eval(js);
|
|
},
|
|
|
|
getVarNames: function(args)
|
|
{
|
|
if (this.vars)
|
|
args.push.apply(args, this.vars);
|
|
|
|
for (let i = 0; i < this.children.length; ++i) {
|
|
let child = this.children[i];
|
|
if (isTag(child))
|
|
child.tag.getVarNames(args);
|
|
else if (child instanceof Parts) {
|
|
for (let i = 0; i < child.parts.length; ++i) {
|
|
if (child.parts[i] instanceof Variable) {
|
|
let name = child.parts[i].name;
|
|
let names = name.split(".");
|
|
args.push(names[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
generateMarkup: function(topBlock, topOuts, blocks, info)
|
|
{
|
|
topBlock.push(',"<', this.tagName, '"');
|
|
|
|
for (let name in this.attrs) {
|
|
if (name != "class") {
|
|
let val = this.attrs[name];
|
|
topBlock.push(', " ', name, '=\\""');
|
|
addParts(val, ',', topBlock, info, true);
|
|
topBlock.push(', "\\""');
|
|
}
|
|
}
|
|
|
|
if (this.listeners) {
|
|
for (let i = 0; i < this.listeners.length; i += 2)
|
|
readPartNames(this.listeners[i+1], topOuts);
|
|
}
|
|
|
|
if (this.props) {
|
|
for (let name in this.props)
|
|
readPartNames(this.props[name], topOuts);
|
|
}
|
|
|
|
if (this.attrs.hasOwnProperty("class") || this.classes) {
|
|
topBlock.push(', " class=\\""');
|
|
if (this.attrs.hasOwnProperty("class"))
|
|
addParts(this.attrs["class"], ',', topBlock, info, true);
|
|
topBlock.push(', " "');
|
|
for (let name in this.classes) {
|
|
topBlock.push(', (');
|
|
addParts(this.classes[name], '', topBlock, info);
|
|
topBlock.push(' ? "', name, '" + " " : "")');
|
|
}
|
|
topBlock.push(', "\\""');
|
|
}
|
|
topBlock.push(',">"');
|
|
|
|
this.generateChildMarkup(topBlock, topOuts, blocks, info);
|
|
topBlock.push(',"</', this.tagName, '>"');
|
|
},
|
|
|
|
generateChildMarkup: function(topBlock, topOuts, blocks, info)
|
|
{
|
|
for (let i = 0; i < this.children.length; ++i) {
|
|
let child = this.children[i];
|
|
if (isTag(child))
|
|
child.tag.generateMarkup(topBlock, topOuts, blocks, info);
|
|
else
|
|
addParts(child, ',', topBlock, info, true);
|
|
}
|
|
},
|
|
|
|
addCode: function(topBlock, topOuts, blocks)
|
|
{
|
|
if (topBlock.length)
|
|
blocks.push('__code__.push(""', topBlock.join(""), ');\n');
|
|
if (topOuts.length)
|
|
blocks.push('__out__.push(', topOuts.join(","), ');\n');
|
|
topBlock.splice(0, topBlock.length);
|
|
topOuts.splice(0, topOuts.length);
|
|
},
|
|
|
|
addLocals: function(blocks)
|
|
{
|
|
let varNames = [];
|
|
this.getVarNames(varNames);
|
|
|
|
let map = {};
|
|
for (let i = 0; i < varNames.length; ++i) {
|
|
let name = varNames[i];
|
|
if ( map.hasOwnProperty(name) )
|
|
continue;
|
|
|
|
map[name] = 1;
|
|
let names = name.split(".");
|
|
blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';\n');
|
|
}
|
|
},
|
|
|
|
compileDOM: function()
|
|
{
|
|
let path = [];
|
|
let blocks = [];
|
|
this.domArgs = [];
|
|
path.embedIndex = 0;
|
|
path.loopIndex = 0;
|
|
path.staticIndex = 0;
|
|
path.renderIndex = 0;
|
|
let nodeCount = this.generateDOM(path, blocks, this.domArgs);
|
|
|
|
let fnBlock = ['(function (root, context, o'];
|
|
|
|
for (let i = 0; i < path.staticIndex; ++i)
|
|
fnBlock.push(', ', 's'+i);
|
|
|
|
for (let i = 0; i < path.renderIndex; ++i)
|
|
fnBlock.push(', ', 'd'+i);
|
|
|
|
fnBlock.push(') {\n');
|
|
for (let i = 0; i < path.loopIndex; ++i)
|
|
fnBlock.push('var l', i, ' = 0;\n');
|
|
for (let i = 0; i < path.embedIndex; ++i)
|
|
fnBlock.push('var e', i, ' = 0;\n');
|
|
|
|
if (this.subject)
|
|
fnBlock.push('with (this) {\n');
|
|
if (this.context)
|
|
fnBlock.push('with (context) {\n');
|
|
|
|
fnBlock.push(blocks.join(""));
|
|
|
|
if (this.subject)
|
|
fnBlock.push('}\n');
|
|
if (this.context)
|
|
fnBlock.push('}\n');
|
|
|
|
fnBlock.push('return ', nodeCount, ';\n');
|
|
fnBlock.push('})\n');
|
|
|
|
function __bind__(object, fn)
|
|
{
|
|
return function(event) { return fn.apply(object, [event]); }
|
|
}
|
|
|
|
function __link__(node, tag, args)
|
|
{
|
|
if (!tag || !tag.tag)
|
|
return;
|
|
|
|
tag.tag.compile();
|
|
|
|
let domArgs = [node, tag.tag.context, 0];
|
|
domArgs.push.apply(domArgs, tag.tag.domArgs);
|
|
domArgs.push.apply(domArgs, args);
|
|
|
|
return tag.tag.renderDOM.apply(tag.tag.subject, domArgs);
|
|
}
|
|
|
|
function __loop__(iter, fn)
|
|
{
|
|
let nodeCount = 0;
|
|
for (let i = 0; i < iter.length; ++i) {
|
|
iter[i][0] = i;
|
|
iter[i][1] = nodeCount;
|
|
nodeCount += fn.apply(this, iter[i]);
|
|
}
|
|
return nodeCount;
|
|
}
|
|
|
|
function __path__(parent, offset)
|
|
{
|
|
let root = parent;
|
|
|
|
for (let i = 2; i < arguments.length; ++i) {
|
|
let index = arguments[i];
|
|
if (i == 3)
|
|
index += offset;
|
|
|
|
if (index == -1)
|
|
parent = parent.parentNode;
|
|
else
|
|
parent = parent.childNodes[index];
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
let js = fnBlock.join("");
|
|
// Exceptions on this line are often in the eval
|
|
this.renderDOM = eval(js);
|
|
},
|
|
|
|
generateDOM: function(path, blocks, args)
|
|
{
|
|
if (this.listeners || this.props)
|
|
this.generateNodePath(path, blocks);
|
|
|
|
if (this.listeners) {
|
|
for (let i = 0; i < this.listeners.length; i += 2) {
|
|
let val = this.listeners[i+1];
|
|
let arg = generateArg(val, path, args);
|
|
blocks.push('node.addEventListener("', this.listeners[i],
|
|
'", __bind__(this, ', arg, '), false);\n');
|
|
}
|
|
}
|
|
|
|
if (this.props) {
|
|
for (let name in this.props) {
|
|
let val = this.props[name];
|
|
let arg = generateArg(val, path, args);
|
|
blocks.push('node.', name, ' = ', arg, ';\n');
|
|
}
|
|
}
|
|
|
|
this.generateChildDOM(path, blocks, args);
|
|
return 1;
|
|
},
|
|
|
|
generateNodePath: function(path, blocks)
|
|
{
|
|
blocks.push("var node = __path__(root, o");
|
|
for (let i = 0; i < path.length; ++i)
|
|
blocks.push(",", path[i]);
|
|
blocks.push(");\n");
|
|
},
|
|
|
|
generateChildDOM: function(path, blocks, args)
|
|
{
|
|
path.push(0);
|
|
for (let i = 0; i < this.children.length; ++i) {
|
|
let child = this.children[i];
|
|
if (isTag(child))
|
|
path[path.length - 1] += '+' + child.tag.generateDOM(path, blocks, args);
|
|
else
|
|
path[path.length - 1] += '+1';
|
|
}
|
|
path.pop();
|
|
},
|
|
|
|
/*
|
|
* We are just hiding from javascript.options.strict. For some reasons it's
|
|
* ok if we return undefined here.
|
|
* @return null or undefined or possibly a context.
|
|
*/
|
|
getContext: function()
|
|
{
|
|
return this.context;
|
|
}
|
|
};
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
DomplateEmbed.prototype = copyObject(DomplateTag.prototype,
|
|
{
|
|
merge: function(args, oldTag)
|
|
{
|
|
this.value = oldTag ? oldTag.value : parseValue(args[0]);
|
|
this.attrs = oldTag ? oldTag.attrs : {};
|
|
this.vars = oldTag ? copyArray(oldTag.vars) : [];
|
|
|
|
let attrs = args[1];
|
|
for (let name in attrs) {
|
|
let val = parseValue(attrs[name]);
|
|
this.attrs[name] = val;
|
|
readPartNames(val, this.vars);
|
|
}
|
|
|
|
return creator(this, DomplateEmbed);
|
|
},
|
|
|
|
getVarNames: function(names)
|
|
{
|
|
if (this.value instanceof Parts)
|
|
names.push(this.value.parts[0].name);
|
|
|
|
if (this.vars)
|
|
names.push.apply(names, this.vars);
|
|
},
|
|
|
|
generateMarkup: function(topBlock, topOuts, blocks, info)
|
|
{
|
|
this.addCode(topBlock, topOuts, blocks);
|
|
|
|
blocks.push('__link__(');
|
|
addParts(this.value, '', blocks, info);
|
|
blocks.push(', __code__, __out__, {\n');
|
|
|
|
let lastName = null;
|
|
for (let name in this.attrs) {
|
|
if (lastName)
|
|
blocks.push(',');
|
|
lastName = name;
|
|
|
|
let val = this.attrs[name];
|
|
blocks.push('"', name, '":');
|
|
addParts(val, '', blocks, info);
|
|
}
|
|
|
|
blocks.push('});\n');
|
|
},
|
|
|
|
generateDOM: function(path, blocks, args)
|
|
{
|
|
let embedName = 'e' + path.embedIndex++;
|
|
|
|
this.generateNodePath(path, blocks);
|
|
|
|
let valueName = 'd' + path.renderIndex++;
|
|
let argsName = 'd' + path.renderIndex++;
|
|
blocks.push(embedName + ' = __link__(node, ', valueName, ', ',
|
|
argsName, ');\n');
|
|
|
|
return embedName;
|
|
}
|
|
});
|
|
|
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
|
|
DomplateLoop.prototype = copyObject(DomplateTag.prototype,
|
|
{
|
|
merge: function(args, oldTag)
|
|
{
|
|
this.isLoop = true;
|
|
this.varName = oldTag ? oldTag.varName : args[0];
|
|
this.iter = oldTag ? oldTag.iter : parseValue(args[1]);
|
|
this.vars = [];
|
|
|
|
this.children = oldTag ? copyArray(oldTag.children) : [];
|
|
|
|
let offset = Math.min(args.length, 2);
|
|
parseChildren(args, offset, this.vars, this.children);
|
|
|
|
return creator(this, DomplateLoop);
|
|
},
|
|
|
|
getVarNames: function(names)
|
|
{
|
|
if (this.iter instanceof Parts)
|
|
names.push(this.iter.parts[0].name);
|
|
|
|
DomplateTag.prototype.getVarNames.apply(this, [names]);
|
|
},
|
|
|
|
generateMarkup: function(topBlock, topOuts, blocks, info)
|
|
{
|
|
this.addCode(topBlock, topOuts, blocks);
|
|
|
|
let iterName;
|
|
if (this.iter instanceof Parts) {
|
|
let part = this.iter.parts[0];
|
|
iterName = part.name;
|
|
|
|
if (part.format) {
|
|
for (let i = 0; i < part.format.length; ++i)
|
|
iterName = part.format[i] + "(" + iterName + ")";
|
|
}
|
|
} else
|
|
iterName = this.iter;
|
|
|
|
blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {\n');
|
|
this.generateChildMarkup(topBlock, topOuts, blocks, info);
|
|
this.addCode(topBlock, topOuts, blocks);
|
|
blocks.push('}]);\n');
|
|
},
|
|
|
|
generateDOM: function(path, blocks, args)
|
|
{
|
|
let iterName = 'd' + path.renderIndex++;
|
|
let counterName = 'i' + path.loopIndex;
|
|
let loopName = 'l' + path.loopIndex++;
|
|
|
|
if (!path.length)
|
|
path.push(-1, 0);
|
|
|
|
let preIndex = path.renderIndex;
|
|
path.renderIndex = 0;
|
|
|
|
let nodeCount = 0;
|
|
|
|
let subBlocks = [];
|
|
let basePath = path[path.length-1];
|
|
for (let i = 0; i < this.children.length; ++i) {
|
|
path[path.length - 1] = basePath + '+' + loopName + '+' + nodeCount;
|
|
|
|
let child = this.children[i];
|
|
if (isTag(child))
|
|
nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args);
|
|
else
|
|
nodeCount += '+1';
|
|
}
|
|
|
|
path[path.length - 1] = basePath + '+' + loopName;
|
|
|
|
blocks.push(loopName,' = __loop__.apply(this, [',
|
|
iterName, ', function(', counterName, ',', loopName);
|
|
for (let i = 0; i < path.renderIndex; ++i)
|
|
blocks.push(',d' + i);
|
|
blocks.push(') {\n');
|
|
blocks.push(subBlocks.join(""));
|
|
blocks.push('return ', nodeCount, ';\n');
|
|
blocks.push('}]);\n');
|
|
|
|
path.renderIndex = preIndex;
|
|
|
|
return loopName;
|
|
}
|
|
});
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
function Variable(name, format)
|
|
{
|
|
this.name = name;
|
|
this.format = format;
|
|
}
|
|
|
|
function Parts(parts)
|
|
{
|
|
this.parts = parts;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
|
|
function parseParts(str)
|
|
{
|
|
let re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g;
|
|
let index = 0;
|
|
let parts = [];
|
|
|
|
let m;
|
|
while (m = re.exec(str)) {
|
|
let pre = str.substr(index, (re.lastIndex-m[0].length)-index);
|
|
if (pre)
|
|
parts.push(pre);
|
|
|
|
let expr = m[1].split("|");
|
|
parts.push(new Variable(expr[0], expr.slice(1)));
|
|
index = re.lastIndex;
|
|
}
|
|
|
|
if (!index)
|
|
return str;
|
|
|
|
let post = str.substr(index);
|
|
if (post)
|
|
parts.push(post);
|
|
|
|
return new Parts(parts);
|
|
}
|
|
|
|
function parseValue(val)
|
|
{
|
|
return typeof(val) == 'string' ? parseParts(val) : val;
|
|
}
|
|
|
|
function parseChildren(args, offset, vars, children)
|
|
{
|
|
for (let i = offset; i < args.length; ++i) {
|
|
let val = parseValue(args[i]);
|
|
children.push(val);
|
|
readPartNames(val, vars);
|
|
}
|
|
}
|
|
|
|
function readPartNames(val, vars)
|
|
{
|
|
if (val instanceof Parts) {
|
|
for (let i = 0; i < val.parts.length; ++i) {
|
|
let part = val.parts[i];
|
|
if (part instanceof Variable)
|
|
vars.push(part.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
function generateArg(val, path, args)
|
|
{
|
|
if (val instanceof Parts) {
|
|
let vals = [];
|
|
for (let i = 0; i < val.parts.length; ++i) {
|
|
let part = val.parts[i];
|
|
if (part instanceof Variable) {
|
|
let varName = 'd' + path.renderIndex++;
|
|
if (part.format) {
|
|
for (let j = 0; j < part.format.length; ++j)
|
|
varName = part.format[j] + '(' + varName + ')';
|
|
}
|
|
|
|
vals.push(varName);
|
|
}
|
|
else
|
|
vals.push('"' + part.replace(/"/g, '\\"') + '"');
|
|
}
|
|
|
|
return vals.join('+');
|
|
} else {
|
|
args.push(val);
|
|
return 's' + path.staticIndex++;
|
|
}
|
|
}
|
|
|
|
function addParts(val, delim, block, info, escapeIt)
|
|
{
|
|
let vals = [];
|
|
if (val instanceof Parts) {
|
|
for (let i = 0; i < val.parts.length; ++i) {
|
|
let part = val.parts[i];
|
|
if (part instanceof Variable) {
|
|
let partName = part.name;
|
|
if (part.format) {
|
|
for (let j = 0; j < part.format.length; ++j)
|
|
partName = part.format[j] + "(" + partName + ")";
|
|
}
|
|
|
|
if (escapeIt)
|
|
vals.push("__escape__(" + partName + ")");
|
|
else
|
|
vals.push(partName);
|
|
}
|
|
else
|
|
vals.push('"' + part + '"');
|
|
}
|
|
} else if (isTag(val)) {
|
|
info.args.push(val);
|
|
vals.push('s' + info.argIndex++);
|
|
} else
|
|
vals.push('"' + val + '"');
|
|
|
|
let parts = vals.join(delim);
|
|
if (parts)
|
|
block.push(delim, parts);
|
|
}
|
|
|
|
function isTag(obj)
|
|
{
|
|
return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// creator
|
|
|
|
function creator(tag, cons)
|
|
{
|
|
let fn = new Function(
|
|
"var tag = arguments.callee.tag;" +
|
|
"var cons = arguments.callee.cons;" +
|
|
"var newTag = new cons();" +
|
|
"return newTag.merge(arguments, tag);");
|
|
|
|
fn.tag = tag;
|
|
fn.cons = cons;
|
|
extend(fn, Renderer);
|
|
|
|
return fn;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// Utility functions
|
|
|
|
function arrayInsert(array, index, other)
|
|
{
|
|
for (let i = 0; i < other.length; ++i)
|
|
array.splice(i+index, 0, other[i]);
|
|
|
|
return array;
|
|
}
|
|
|
|
function cloneArray(array, fn)
|
|
{
|
|
let newArray = [];
|
|
|
|
if (fn)
|
|
for (let i = 0; i < array.length; ++i)
|
|
newArray.push(fn(array[i]));
|
|
else
|
|
for (let i = 0; i < array.length; ++i)
|
|
newArray.push(array[i]);
|
|
|
|
return newArray;
|
|
}
|
|
|
|
// fn, thisObject, args => thisObject.fn(args, arguments);
|
|
function bind()
|
|
{
|
|
let args = cloneArray(arguments), fn = args.shift(), object = args.shift();
|
|
return function bind()
|
|
{
|
|
return fn.apply(object, arrayInsert(cloneArray(args), 0, arguments));
|
|
}
|
|
}
|
|
|
|
function copyArray(oldArray)
|
|
{
|
|
let array = [];
|
|
if (oldArray)
|
|
for (let i = 0; i < oldArray.length; ++i)
|
|
array.push(oldArray[i]);
|
|
return array;
|
|
}
|
|
|
|
function copyObject(l, r)
|
|
{
|
|
let m = {};
|
|
extend(m, l);
|
|
extend(m, r);
|
|
return m;
|
|
}
|
|
|
|
function escapeNewLines(value)
|
|
{
|
|
return value.replace(/\r/gm, "\\r").replace(/\n/gm, "\\n");
|
|
}
|
|
|
|
function extend(l, r)
|
|
{
|
|
for (let n in r)
|
|
l[n] = r[n];
|
|
}
|
|
|
|
function cropString(text, limit, alterText)
|
|
{
|
|
if (!alterText)
|
|
alterText = "..."; //…
|
|
|
|
text = text + "";
|
|
|
|
if (!limit)
|
|
limit = 88; // todo
|
|
var halfLimit = (limit / 2);
|
|
halfLimit -= 2; // adjustment for alterText's increase in size
|
|
|
|
if (text.length > limit)
|
|
return text.substr(0, halfLimit) + alterText +
|
|
text.substr(text.length - halfLimit);
|
|
else
|
|
return text;
|
|
}
|
|
|
|
function cropMultipleLines(text, limit)
|
|
{
|
|
return escapeNewLines(this.cropString(text, limit));
|
|
}
|
|
|
|
function isVisible(elt)
|
|
{
|
|
if (elt.localName) {
|
|
return elt.offsetWidth > 0 || elt.offsetHeight > 0 ||
|
|
elt.localName.toLowerCase() in invisibleTags;
|
|
} else {
|
|
return elt.offsetWidth > 0 || elt.offsetHeight > 0;
|
|
}
|
|
// || isElementSVG(elt) || isElementMathML(elt);
|
|
}
|
|
|
|
// Local Helpers
|
|
|
|
function isElementXHTML(node)
|
|
{
|
|
return node.nodeName == node.nodeName.toLowerCase();
|
|
}
|
|
|
|
function isContainerElement(element)
|
|
{
|
|
let tag = element.localName.toLowerCase();
|
|
switch (tag) {
|
|
case "script":
|
|
case "style":
|
|
case "iframe":
|
|
case "frame":
|
|
case "tabbrowser":
|
|
case "browser":
|
|
return true;
|
|
case "link":
|
|
return element.getAttribute("rel") == "stylesheet";
|
|
case "embed":
|
|
return element.getSVGDocument();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
domplateUtils.isWhitespace = function isWhitespace(text)
|
|
{
|
|
return !reNotWhitespace.exec(text);
|
|
};
|
|
|
|
domplateUtils.isWhitespaceText = function isWhitespaceText(node)
|
|
{
|
|
if (node instanceof DOM.HTMLAppletElement)
|
|
return false;
|
|
return node.nodeType == DOM.Node.TEXT_NODE && this.isWhitespace(node.nodeValue);
|
|
}
|
|
|
|
function isSelfClosing(element)
|
|
{
|
|
//if (isElementSVG(element) || isElementMathML(element))
|
|
// return true;
|
|
var tag = element.localName.toLowerCase();
|
|
return (selfClosingTags.hasOwnProperty(tag));
|
|
};
|
|
|
|
function isEmptyElement(element)
|
|
{
|
|
if (showTextNodesWithWhitespace) {
|
|
return !element.firstChild && isSelfClosing(element);
|
|
} else {
|
|
for (let child = element.firstChild; child; child = child.nextSibling) {
|
|
if (!domplateUtils.isWhitespaceText(child))
|
|
return false;
|
|
}
|
|
}
|
|
return isSelfClosing(element);
|
|
}
|
|
|
|
function getEmptyElementTag(node)
|
|
{
|
|
let isXhtml= isElementXHTML(node);
|
|
if (isXhtml)
|
|
return HTMLTemplates.XEmptyElement.tag;
|
|
else
|
|
return HTMLTemplates.EmptyElement.tag;
|
|
}
|
|
|
|
/**
|
|
* Determines if the given node has any children which are elements.
|
|
*
|
|
* @param {Element} element Element to test.
|
|
* @return true if immediate children of type Element exist, false otherwise
|
|
*/
|
|
function hasNoElementChildren(element)
|
|
{
|
|
if (element.childElementCount != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
domplateUtils.getNodeTag = function getNodeTag(node, expandAll)
|
|
{
|
|
if (node instanceof DOM.Element) {
|
|
if (node instanceof DOM.HTMLHtmlElement && node.ownerDocument
|
|
&& node.ownerDocument.doctype)
|
|
return HTMLTemplates.HTMLHtmlElement.tag;
|
|
else if (node instanceof DOM.HTMLAppletElement)
|
|
return getEmptyElementTag(node);
|
|
else if (isContainerElement(node))
|
|
return HTMLTemplates.Element.tag;
|
|
else if (isEmptyElement(node))
|
|
return getEmptyElementTag(node);
|
|
else if (hasNoElementChildren(node))
|
|
return HTMLTemplates.TextElement.tag;
|
|
else
|
|
return HTMLTemplates.Element.tag;
|
|
}
|
|
else if (node instanceof DOM.Text)
|
|
return HTMLTemplates.TextNode.tag;
|
|
else if (node instanceof DOM.CDATASection)
|
|
return HTMLTemplates.CDATANode.tag;
|
|
else if (node instanceof DOM.Comment)
|
|
return HTMLTemplates.CommentNode.tag;
|
|
else if (node instanceof DOM.SourceText)
|
|
return HTMLTemplates.SourceText.tag;
|
|
else
|
|
return HTMLTemplates.Nada.tag;
|
|
}
|
|
|
|
function getNodeBoxTag(nodeBox)
|
|
{
|
|
let re = /([^\s]+)NodeBox/;
|
|
let m = re.exec(nodeBox.className);
|
|
if (!m)
|
|
return null;
|
|
|
|
let nodeBoxType = m[1];
|
|
if (nodeBoxType == "container")
|
|
return HTMLTemplates.Element.tag;
|
|
else if (nodeBoxType == "text")
|
|
return HTMLTemplates.TextElement.tag;
|
|
else if (nodeBoxType == "empty")
|
|
return HTMLTemplates.EmptyElement.tag;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// ArrayIterator
|
|
|
|
function ArrayIterator(array)
|
|
{
|
|
let index = -1;
|
|
|
|
this.next = function()
|
|
{
|
|
if (++index >= array.length)
|
|
throw StopIteration;
|
|
|
|
return array[index];
|
|
};
|
|
}
|
|
|
|
function StopIteration() {}
|
|
|
|
domplate.$break = function()
|
|
{
|
|
throw StopIteration;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// Renderer
|
|
|
|
var Renderer =
|
|
{
|
|
renderHTML: function(args, outputs, self)
|
|
{
|
|
let code = [];
|
|
let markupArgs = [code, this.tag.getContext(), args, outputs];
|
|
markupArgs.push.apply(markupArgs, this.tag.markupArgs);
|
|
this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs);
|
|
return code.join("");
|
|
},
|
|
|
|
insertRows: function(args, before, self)
|
|
{
|
|
if (!args)
|
|
args = {};
|
|
|
|
this.tag.compile();
|
|
|
|
let outputs = [];
|
|
let html = this.renderHTML(args, outputs, self);
|
|
|
|
let doc = before.ownerDocument;
|
|
let table = doc.createElement("table");
|
|
table.innerHTML = html;
|
|
|
|
let tbody = table.firstChild;
|
|
let parent = before.localName.toLowerCase() == "tr" ? before.parentNode : before;
|
|
let after = before.localName.toLowerCase() == "tr" ? before.nextSibling : null;
|
|
|
|
let firstRow = tbody.firstChild, lastRow;
|
|
while (tbody.firstChild) {
|
|
lastRow = tbody.firstChild;
|
|
if (after)
|
|
parent.insertBefore(lastRow, after);
|
|
else
|
|
parent.appendChild(lastRow);
|
|
}
|
|
|
|
// To save the next poor soul:
|
|
// In order to properly apply properties and event handlers on elements
|
|
// constructed by a FOR tag, the tag needs to be able to iterate up and
|
|
// down the tree, meaning if FOR is the root element as is the case with
|
|
// many insertRows calls, it will need to iterator over portions of the
|
|
// new parent.
|
|
//
|
|
// To achieve this end, __path__ defines the -1 operator which allows
|
|
// parent traversal. When combined with the offset that we calculate
|
|
// below we are able to iterate over the elements.
|
|
//
|
|
// This fails when applied to a non-loop element as non-loop elements
|
|
// Do not generate to proper path to bounce up and down the tree.
|
|
let offset = 0;
|
|
if (this.tag.isLoop) {
|
|
let node = firstRow.parentNode.firstChild;
|
|
for (; node && node != firstRow; node = node.nextSibling)
|
|
++offset;
|
|
}
|
|
|
|
// strict warning: this.tag.context undefined
|
|
let domArgs = [firstRow, this.tag.getContext(), offset];
|
|
domArgs.push.apply(domArgs, this.tag.domArgs);
|
|
domArgs.push.apply(domArgs, outputs);
|
|
|
|
this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
|
|
return [firstRow, lastRow];
|
|
},
|
|
|
|
insertBefore: function(args, before, self)
|
|
{
|
|
return this.insertNode(args, before.ownerDocument,
|
|
function(frag) {
|
|
before.parentNode.insertBefore(frag, before);
|
|
}, self);
|
|
},
|
|
|
|
insertAfter: function(args, after, self)
|
|
{
|
|
return this.insertNode(args, after.ownerDocument,
|
|
function(frag) {
|
|
after.parentNode.insertBefore(frag, after.nextSibling);
|
|
}, self);
|
|
},
|
|
|
|
insertNode: function(args, doc, inserter, self)
|
|
{
|
|
if (!args)
|
|
args = {};
|
|
|
|
this.tag.compile();
|
|
|
|
let outputs = [];
|
|
let html = this.renderHTML(args, outputs, self);
|
|
|
|
let range = doc.createRange();
|
|
range.selectNode(doc.body);
|
|
let frag = range.createContextualFragment(html);
|
|
|
|
let root = frag.firstChild;
|
|
root = inserter(frag) || root;
|
|
|
|
let domArgs = [root, this.tag.context, 0];
|
|
domArgs.push.apply(domArgs, this.tag.domArgs);
|
|
domArgs.push.apply(domArgs, outputs);
|
|
|
|
this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
|
|
|
|
return root;
|
|
},
|
|
|
|
replace: function(args, parent, self)
|
|
{
|
|
if (!args)
|
|
args = {};
|
|
|
|
this.tag.compile();
|
|
|
|
let outputs = [];
|
|
let html = this.renderHTML(args, outputs, self);
|
|
|
|
let root;
|
|
if (parent.nodeType == DOM.Node.ELEMENT_NODE) {
|
|
parent.innerHTML = html;
|
|
root = parent.firstChild;
|
|
} else {
|
|
if (!parent || parent.nodeType != DOM.Node.DOCUMENT_NODE)
|
|
return;
|
|
|
|
if (!womb || womb.ownerDocument != parent)
|
|
womb = parent.createElement("div");
|
|
|
|
womb.innerHTML = html;
|
|
|
|
root = womb.firstChild;
|
|
}
|
|
|
|
let domArgs = [root, this.tag.context, 0];
|
|
domArgs.push.apply(domArgs, this.tag.domArgs);
|
|
domArgs.push.apply(domArgs, outputs);
|
|
this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
|
|
|
|
return root;
|
|
},
|
|
|
|
append: function(args, parent, self)
|
|
{
|
|
if (!args)
|
|
args = {};
|
|
|
|
this.tag.compile();
|
|
|
|
let outputs = [];
|
|
let html = this.renderHTML(args, outputs, self);
|
|
|
|
if (!womb || womb.ownerDocument != parent.ownerDocument)
|
|
womb = parent.ownerDocument.createElement("div");
|
|
womb.innerHTML = html;
|
|
|
|
let root = womb.firstChild;
|
|
while (womb.firstChild)
|
|
parent.appendChild(womb.firstChild);
|
|
|
|
let domArgs = [root, this.tag.context, 0];
|
|
domArgs.push.apply(domArgs, this.tag.domArgs);
|
|
domArgs.push.apply(domArgs, outputs);
|
|
|
|
this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
|
|
|
|
return root;
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// defineTags macro
|
|
|
|
/**
|
|
* Create default tags for a list of tag names.
|
|
* @param Arguments
|
|
* list of string arguments
|
|
*/
|
|
|
|
function defineTags()
|
|
{
|
|
for (let i = 0; i < arguments.length; ++i) {
|
|
let tagName = arguments[i];
|
|
let fn = new Function("var newTag = new DomplateTag('" + tagName +
|
|
"'); return newTag.merge(arguments);");
|
|
|
|
let fnName = tagName.toUpperCase();
|
|
domplate[fnName] = fn;
|
|
}
|
|
}
|
|
|
|
defineTags(
|
|
"a", "button", "br", "canvas", "col", "colgroup", "div", "fieldset", "form",
|
|
"h1", "h2", "h3", "hr", "img", "input", "label", "legend", "li", "ol",
|
|
"optgroup", "option", "p", "pre", "select", "b", "span", "strong", "table",
|
|
"tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul", "iframe",
|
|
"code"
|
|
);
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// HTMLTemplates
|
|
|
|
let HTMLTemplates = {
|
|
showTextNodesWithWhitespace: false
|
|
};
|
|
|
|
let BaseTemplates = {
|
|
showTextNodesWithWhitespace: false
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// HTMLTemplates.Reps
|
|
|
|
BaseTemplates.OBJECTLINK = domplate.A({
|
|
"class": "objectLink objectLink-$className a11yFocus",
|
|
_repObject: "$object"
|
|
});
|
|
|
|
BaseTemplates.Rep = domplate(
|
|
{
|
|
className: "",
|
|
inspectable: true,
|
|
|
|
supportsObject: function(object, type)
|
|
{
|
|
return false;
|
|
},
|
|
|
|
inspectObject: function(object, context)
|
|
{
|
|
// Firebug.chrome.select(object); // todo
|
|
},
|
|
|
|
browseObject: function(object, context)
|
|
{
|
|
},
|
|
|
|
persistObject: function(object, context)
|
|
{
|
|
},
|
|
|
|
getRealObject: function(object, context)
|
|
{
|
|
return object;
|
|
},
|
|
|
|
/**
|
|
* Return a sensible string title for the given object, removing any wrapper
|
|
* information from it.
|
|
* @param aObject
|
|
* The object to get the title of.
|
|
* @returns string
|
|
*/
|
|
|
|
getTitle: function(aObject)
|
|
{
|
|
// e.g., [object XPCWrappedNative [object foo]]
|
|
let label = safeToString(aObject);
|
|
|
|
const re =/\[object ([^\]]*)/;
|
|
let objectMatch = re.exec(label);
|
|
let secondObjectMatch = null;
|
|
if (objectMatch) {
|
|
// e.g., XPCWrappedNative [object foo
|
|
secondObjectMatch = re.exec(objectMatch[1]);
|
|
}
|
|
|
|
if (secondObjectMatch)
|
|
return secondObjectMatch[1]; // eg foo
|
|
else
|
|
return objectMatch ? objectMatch[1] : label;
|
|
},
|
|
|
|
getTooltip: function(object)
|
|
{
|
|
return null;
|
|
},
|
|
|
|
/**
|
|
* Called by chrome.onContextMenu to build the context menu when the
|
|
* underlying object has this rep.
|
|
* See also Panel for a similar function also called by onContextMenu.
|
|
* Extensions may monkey patch and chain off this call.
|
|
* @param object: the 'realObject', a model value, eg a DOM property
|
|
* @param target: the HTML element clicked on.
|
|
* @param context: the context, probably FirebugContext
|
|
* @returns an array of menu items.
|
|
*/
|
|
getContextMenuItems: function(object, target, context)
|
|
{
|
|
return [];
|
|
},
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Convenience for domplates
|
|
|
|
STR: function(name)
|
|
{
|
|
return name; // todo getproperty?
|
|
},
|
|
|
|
cropString: function(text)
|
|
{
|
|
return cropString(text);
|
|
},
|
|
|
|
cropMultipleLines: function(text, limit)
|
|
{
|
|
return cropMultipleLines(text, limit);
|
|
},
|
|
|
|
toLowerCase: function(text)
|
|
{
|
|
return text ? text.toLowerCase() : text;
|
|
},
|
|
|
|
plural: function(n)
|
|
{
|
|
return n == 1 ? "" : "s";
|
|
}
|
|
});
|
|
|
|
BaseTemplates.Element = domplate(BaseTemplates.Rep,
|
|
{
|
|
tag:
|
|
BaseTemplates.OBJECTLINK(
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.localName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator",
|
|
" $attr.localName="",
|
|
domplate.SPAN({"class": "nodeValue"},
|
|
"$attr.nodeValue"),
|
|
"""
|
|
),
|
|
">"
|
|
),
|
|
|
|
shortTag:
|
|
BaseTemplates.OBJECTLINK(
|
|
domplate.SPAN({"class": "$object|getVisible"},
|
|
domplate.SPAN({"class": "selectorTag"},
|
|
"$object|getSelectorTag"),
|
|
domplate.SPAN({"class": "selectorId"},
|
|
"$object|getSelectorId"),
|
|
domplate.SPAN({"class": "selectorClass"},
|
|
"$object|getSelectorClass"),
|
|
domplate.SPAN({"class": "selectorValue"},
|
|
"$object|getValue")
|
|
)
|
|
),
|
|
|
|
getVisible: function(elt)
|
|
{
|
|
return isVisible(elt) ? "" : "selectorHidden";
|
|
},
|
|
|
|
getSelectorTag: function(elt)
|
|
{
|
|
return elt.localName.toLowerCase();
|
|
},
|
|
|
|
getSelectorId: function(elt)
|
|
{
|
|
return elt.id ? ("#" + elt.id) : "";
|
|
},
|
|
|
|
getSelectorClass: function(elt)
|
|
{
|
|
return elt.getAttribute("class")
|
|
? ("." + elt.getAttribute("class").split(" ")[0])
|
|
: "";
|
|
},
|
|
|
|
getValue: function(elt)
|
|
{ // todo getFileName
|
|
let value;
|
|
/*
|
|
if (elt instanceof HTMLImageElement)
|
|
value = getFileName(elt.getAttribute("src"));
|
|
else if (elt instanceof HTMLAnchorElement)
|
|
value = getFileName(elt.getAttribute("href"));
|
|
else if (elt instanceof HTMLInputElement)
|
|
value = elt.getAttribute("value");
|
|
else if (elt instanceof HTMLFormElement)
|
|
value = getFileName(elt.getAttribute("action"));
|
|
else if (elt instanceof HTMLScriptElement)
|
|
value = getFileName(elt.getAttribute("src"));
|
|
|
|
return value ? " " + cropMultipleLines(value, 20) : ""; */
|
|
// trying a simplified version from above commented section
|
|
// todo
|
|
if (elt instanceof DOM.HTMLImageElement)
|
|
value = elt.getAttribute("src");
|
|
else if (elt instanceof DOM.HTMLAnchorElement)
|
|
value = elt.getAttribute("href");
|
|
else if (elt instanceof DOM.HTMLInputElement)
|
|
value = elt.getAttribute("value");
|
|
else if (elt instanceof DOM.HTMLFormElement)
|
|
value = elt.getAttribute("action");
|
|
else if (elt instanceof DOM.HTMLScriptElement)
|
|
value = elt.getAttribute("src");
|
|
|
|
return value ? " " + cropMultipleLines(value, 20) : "";
|
|
},
|
|
|
|
attrIterator: function(elt)
|
|
{
|
|
let attrs = [];
|
|
let idAttr, classAttr;
|
|
if (elt.attributes) {
|
|
for (let i = 0; i < elt.attributes.length; ++i) {
|
|
var attr = elt.attributes[i];
|
|
if (attr.localName.indexOf("-moz-math") != -1)
|
|
continue;
|
|
else if (attr.localName == "id")
|
|
idAttr = attr;
|
|
else if (attr.localName == "class")
|
|
classAttr = attr;
|
|
else
|
|
attrs.push(attr);
|
|
}
|
|
}
|
|
if (classAttr)
|
|
attrs.unshift(classAttr);
|
|
if (idAttr)
|
|
attrs.unshift(idAttr);
|
|
return attrs;
|
|
},
|
|
|
|
shortAttrIterator: function(elt)
|
|
{
|
|
let attrs = [];
|
|
if (elt.attributes) {
|
|
for (let i = 0; i < elt.attributes.length; ++i) {
|
|
let attr = elt.attributes[i];
|
|
if (attr.localName == "id" || attr.localName == "class")
|
|
attrs.push(attr);
|
|
}
|
|
}
|
|
|
|
return attrs;
|
|
},
|
|
|
|
getHidden: function(elt)
|
|
{
|
|
return isVisible(elt) ? "" : "nodeHidden";
|
|
},
|
|
|
|
/* getXPath: function(elt)
|
|
{
|
|
return getElementTreeXPath(elt); // todo
|
|
}, */
|
|
|
|
getNodeTextGroups: function(element)
|
|
{
|
|
let text = element.textContent;
|
|
return [{str: text, 'class': '', extra: ''}];
|
|
},
|
|
|
|
className: "element",
|
|
|
|
supportsObject: function(object, type)
|
|
{
|
|
return object instanceof DOM.Element;
|
|
},
|
|
|
|
browseObject: function(elt, context)
|
|
{
|
|
let tag = elt.localName.toLowerCase();
|
|
return true;
|
|
},
|
|
});
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// HTMLTemplates.tags
|
|
|
|
BaseTemplates.AttrTag =
|
|
domplate.SPAN({"class": "nodeAttr editGroup"},
|
|
" ",
|
|
domplate.SPAN({"class": "nodeName editable"}, "$attr.nodeName"),
|
|
"="",
|
|
domplate.SPAN({"class": "nodeValue editable"}, "$attr.nodeValue"),
|
|
""");
|
|
|
|
BaseTemplates.TextTag =
|
|
domplate.SPAN({"class": "nodeText editable"},
|
|
domplate.FOR("chr", "$object|getNodeTextGroups",
|
|
domplate.SPAN({"class": "$chr.class $chr.extra"},
|
|
"$chr.str")));
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//// HTMLTemplates
|
|
|
|
|
|
|
|
HTMLTemplates.CompleteElement = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class":
|
|
"nodeBox open $object|getHidden repIgnore",
|
|
_repObject: "$object", role : 'presentation'},
|
|
domplate.DIV({"class": "nodeLabel", role: "presentation"},
|
|
domplate.SPAN({"class": "nodeLabelBox repTarget repTarget",
|
|
role : 'treeitem', 'aria-expanded' : 'false'},
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator", BaseTemplates.AttrTag),
|
|
domplate.SPAN({"class": "nodeBracket"}, ">")
|
|
)
|
|
),
|
|
domplate.DIV({"class": "nodeChildBox", role :"group"},
|
|
domplate.FOR("child", "$object|childIterator",
|
|
domplate.TAG("$child|getNodeTag", {object: "$child"})
|
|
)
|
|
),
|
|
domplate.DIV({"class": "nodeCloseLabel", role:"presentation"},
|
|
"</",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
">"
|
|
)
|
|
),
|
|
|
|
getNodeTag: function(node)
|
|
{
|
|
return domplateUtils.getNodeTag(node, true);
|
|
},
|
|
|
|
childIterator: function(node)
|
|
{
|
|
if (node.contentDocument)
|
|
return [node.contentDocument.documentElement];
|
|
|
|
if (this.showTextNodesWithWhitespace)
|
|
return cloneArray(node.childNodes);
|
|
else {
|
|
let nodes = [];
|
|
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
if (child.nodeType != DOM.Node.TEXT_NODE || !domplateUtils.isWhitespaceText(child))
|
|
nodes.push(child);
|
|
}
|
|
return nodes;
|
|
}
|
|
}
|
|
});
|
|
|
|
HTMLTemplates.SoloElement = domplate(HTMLTemplates.CompleteElement,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class": "soloElement",
|
|
onmousedown: "$onMouseDown"},
|
|
HTMLTemplates.CompleteElement.tag),
|
|
|
|
onMouseDown: function(event)
|
|
{
|
|
for (let child = event.target; child; child = child.parentNode) {
|
|
if (child.repObject) { // todo
|
|
// let panel = Firebug.getElementPanel(child);
|
|
// Firebug.chrome.select(child.repObject);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
HTMLTemplates.Element = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class": "nodeBox containerNodeBox $object|getHidden repIgnore",
|
|
_repObject: "$object", role: "presentation"},
|
|
domplate.DIV({"class": "nodeLabel", role: "presentation"},
|
|
domplate.IMG({"class": "twisty", role: "presentation"}),
|
|
domplate.SPAN({"class": "nodeLabelBox repTarget",
|
|
role: 'treeitem', 'aria-expanded': 'false'},
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator", BaseTemplates.AttrTag),
|
|
domplate.SPAN({"class": "nodeBracket editable insertBefore"},
|
|
">")
|
|
)
|
|
),
|
|
domplate.DIV({"class": "nodeChildBox", role: "group"}), /* nodeChildBox is special signal in insideOutBox */
|
|
domplate.DIV({"class": "nodeCloseLabel", role: "presentation"},
|
|
domplate.SPAN({"class": "nodeCloseLabelBox repTarget"},
|
|
"</",
|
|
domplate.SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
|
|
">"
|
|
)
|
|
)
|
|
)
|
|
});
|
|
|
|
HTMLTemplates.HTMLHtmlElement = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class":
|
|
"nodeBox htmlNodeBox containerNodeBox $object|getHidden repIgnore",
|
|
_repObject: "$object", role: "presentation"},
|
|
domplate.DIV({"class": "docType"},
|
|
"$object|getDocType"),
|
|
domplate.DIV({"class": "nodeLabel", role: "presentation"},
|
|
domplate.IMG({"class": "twisty", role: "presentation"}),
|
|
domplate.SPAN({"class": "nodeLabelBox repTarget",
|
|
role: 'treeitem', 'aria-expanded' : 'false'},
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator", BaseTemplates.AttrTag),
|
|
domplate.SPAN({"class":
|
|
"nodeBracket editable insertBefore"}, ">")
|
|
)
|
|
), /* nodeChildBox is special signal in insideOutBox */
|
|
domplate.DIV({"class": "nodeChildBox", role: "group"}),
|
|
domplate.DIV({"class": "nodeCloseLabel", role: "presentation"},
|
|
domplate.SPAN({"class": "nodeCloseLabelBox repTarget"},
|
|
"</",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
">"
|
|
)
|
|
)
|
|
),
|
|
|
|
getDocType: function(obj)
|
|
{
|
|
let doctype = obj.ownerDocument.doctype;
|
|
return '<!DOCTYPE ' + doctype.name + (doctype.publicId ? ' PUBLIC "' +
|
|
doctype.publicId + '"': '') + (doctype.systemId ? ' "' +
|
|
doctype.systemId + '"' : '') + '>';
|
|
}
|
|
});
|
|
|
|
HTMLTemplates.TextElement = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class":
|
|
"nodeBox textNodeBox $object|getHidden repIgnore",
|
|
_repObject: "$object", role: 'presentation'},
|
|
domplate.DIV({"class": "nodeLabel", role: "presentation"},
|
|
domplate.SPAN({"class": "nodeLabelBox repTarget",
|
|
role: 'treeitem'},
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator", BaseTemplates.AttrTag),
|
|
domplate.SPAN({"class":
|
|
"nodeBracket editable insertBefore"}, ">"),
|
|
BaseTemplates.TextTag,
|
|
"</",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
">"
|
|
)
|
|
)
|
|
)
|
|
});
|
|
|
|
HTMLTemplates.EmptyElement = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class":
|
|
"nodeBox emptyNodeBox $object|getHidden repIgnore",
|
|
_repObject: "$object", role: 'presentation'},
|
|
domplate.DIV({"class": "nodeLabel", role: "presentation"},
|
|
domplate.SPAN({"class": "nodeLabelBox repTarget",
|
|
role: 'treeitem'},
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator", BaseTemplates.AttrTag),
|
|
domplate.SPAN({"class":
|
|
"nodeBracket editable insertBefore"}, ">")
|
|
)
|
|
)
|
|
)
|
|
});
|
|
|
|
HTMLTemplates.XEmptyElement = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class":
|
|
"nodeBox emptyNodeBox $object|getHidden repIgnore",
|
|
_repObject: "$object", role: 'presentation'},
|
|
domplate.DIV({"class": "nodeLabel", role: "presentation"},
|
|
domplate.SPAN({"class": "nodeLabelBox repTarget",
|
|
role : 'treeitem'},
|
|
"<",
|
|
domplate.SPAN({"class": "nodeTag"},
|
|
"$object.nodeName|toLowerCase"),
|
|
domplate.FOR("attr", "$object|attrIterator", BaseTemplates.AttrTag),
|
|
domplate.SPAN({"class":
|
|
"nodeBracket editable insertBefore"}, "/>")
|
|
)
|
|
)
|
|
)
|
|
});
|
|
|
|
HTMLTemplates.AttrNode = domplate(BaseTemplates.Element,
|
|
{
|
|
tag: BaseTemplates.AttrTag
|
|
});
|
|
|
|
HTMLTemplates.TextNode = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class": "nodeBox", _repObject: "$object",
|
|
role: 'presentation'}, BaseTemplates.TextTag)
|
|
});
|
|
|
|
HTMLTemplates.CDATANode = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class": "nodeBox", _repObject: "$object",
|
|
role: 'presentation'},
|
|
"<![CDATA[",
|
|
domplate.SPAN({"class": "nodeText nodeCDATA editable"},
|
|
"$object.nodeValue"),
|
|
"]]>")
|
|
});
|
|
|
|
HTMLTemplates.CommentNode = domplate(BaseTemplates.Element,
|
|
{
|
|
tag:
|
|
domplate.DIV({"class": "nodeBox nodeComment",
|
|
_repObject: "$object", role : 'presentation'},
|
|
"<!--",
|
|
domplate.SPAN({"class": "nodeComment editable"},
|
|
"$object.nodeValue"),
|
|
"-->")
|
|
});
|
|
|
|
HTMLTemplates.Nada = domplate(BaseTemplates.Rep,
|
|
{
|
|
tag: domplate.SPAN(""),
|
|
className: "nada"
|
|
});
|
|
|