seems to work (not fully tested)

This commit is contained in:
David Greenspan
2013-12-09 22:52:20 -08:00
parent 87921cf178
commit cb2d331dbf
7 changed files with 16 additions and 167 deletions

View File

@@ -1,5 +1,9 @@
HTML.Special = function (value) {
if (! (this instanceof HTML.Special))
// called without `new`
return new HTML.Special(value);
this.value = value;
};
HTML.Special.prototype.toJS = function (options) {

View File

@@ -1,151 +0,0 @@
// A Tag is an array of zero or more content items, with additional properties
// `tagName` (String, required) and `attrs` (Object or `null`). In addition to
// be arrays, Tags are `instanceof HTML.Tag`.
//
// An attribute value may be null, a string, a CharRef tag, or an array of
// strings, CharRef tags, arrays, and nulls.
//
// Tags are created using tag functions, e.g. `DIV(P({id:'foo'}, "Hello"))`.
// If a tag function is given a first argument that is an object and not a
// Tag or an array, that object is used as element attributes (`attrs`).
// The attrs argument may be of the form `{$attrs: function () { ... }}` in
// which case the function becomes the `attrs` attribute of the tag.
//
// Tag functions for all known tags are available as `HTML.Tag.DIV`,
// `HTML.Tag.SPAN`, etc., and you can define new ones with
// `HTML.defineTag("FOO")`, which makes a tag function available at
// `HTML.Tag.FOO` henceforth. You can also use `HTML.getTag("FOO")` to
// define (if necessary) and return a tag function.
Tag = function (tagName, attrs) {
this.tagName = tagName;
this.attrs = attrs; // may be falsy
};
Tag.prototype = [];
makeTagFunc = function (name) {
// Do a little dance so that tags print nicely in the Chrome console.
// First make tag name suitable for insertion into evaluated JS code,
// for security reasons mainly.
var sanitizedName = String(name).replace(/^[^a-zA-Z_]|[^a-zA-Z_0-9]+/g,
'') || 'Tag';
// Generate a constructor function whose name is the tag name.
// We try to choose generic-sounding variable names in case V8 infers
// them as type names and they show up in the developer console.
// HTMLTag is the constructor function for our specific tag type,
// while Tag is the super constructor.
var HTMLTag = (new Function('return function ' +
sanitizedName +
'(attrs) { this.attrs = attrs; };'))();
HTMLTag.prototype = new Tag(name);
return function (optAttrs/*, children*/) {
// see whether first argument is truthy and not an Array or Tag
var attrsGiven = (optAttrs && (typeof optAttrs === 'object') &&
(typeof optAttrs.splice !== 'function'));
var attrs = (attrsGiven ? optAttrs : null);
if (attrsGiven && (typeof attrs.$attrs === 'function'))
attrs = attrs.$attrs;
var tag = new HTMLTag(attrs);
tag.push.apply(tag, (attrsGiven ?
Array.prototype.slice.call(arguments, 1) :
arguments));
return tag;
};
};
defineTag = function (name) {
// XXX maybe sanity-check name? Like no whitespace.
name = name.toUpperCase();
Tag[name] = makeTagFunc(name);
return Tag[name];
};
getTag = function (name) {
name = name.toUpperCase();
return Tag[name] || defineTag(name);
};
// checks that a pseudoDOM node with tagName "CharRef" is well-formed.
var checkCharRef = function (charRef) {
if (typeof charRef.attrs === 'function')
throw new Error("Can't have a reactive character reference (CharRef)");
var attrs = charRef.attrs;
if ((! attrs) || (typeof attrs.html !== 'string') ||
(typeof attrs.str !== 'string') || (! attrs.html) || (! attrs.str))
throw new Error("CharRef should have simple string attributes " +
"`html` and `str`.");
if (charRef.length)
throw new Error("CharRef should have no content");
};
// checks that a pseudoDOM node with tagName "Comment" is well-formed.
var checkComment = function (comment) {
if (comment.attrs)
throw new Error("Comment can't have attributes");
if (comment.length !== 1 || (typeof comment[0] !== 'string'))
throw new Error("Comment should have exactly one content item, a simple string");
};
// checks that a pseudoDOM node with tagName "Raw" is well-formed.
var checkRaw = function (raw) {
if (raw.attrs)
throw new Error("Raw can't have attributes");
if (raw.length !== 1 || (typeof raw[0] !== 'string'))
throw new Error("Raw should have exactly one content item, a simple string");
};
// checks that a pseudoDOM node with tagName "Comment" is well-formed.
var checkEmitCode = function (node) {
if (node.attrs)
throw new Error("EmitCode can't have attributes");
if (node.length !== 1 || (typeof node[0] !== 'string'))
throw new Error("EmitCode should have exactly one content item, a simple string");
};
var checkSpecial = function (node) {
if (! node.attrs)
throw new Error("Special tag must have attributes");
if (node.length > 0)
throw new Error("Special tag must not have content");
};
typeOf = function (node) {
if (node && (typeof node === 'object') &&
(typeof node.splice === 'function')) {
// Tag or array
if (node.tagName) {
if (node.tagName === 'CharRef') {
checkCharRef(node);
return 'charref';
} else if (node.tagName === 'Comment') {
checkComment(node);
return 'comment';
} else if (node.tagName === 'EmitCode') {
checkEmitCode(node);
return 'emitcode';
} else if (node.tagName === 'Special') {
checkSpecial(node);
return 'special';
} else if (node.tagName === 'Raw') {
checkRaw(node);
return 'raw';
} else {
return 'tag';
}
} else {
return 'array';
}
} else if (typeof node === 'string') {
return 'string';
} else if (typeof node === 'function') {
return 'function';
} else if (node == null) {
return 'null';
} else {
throw new Error("Unexpected item in HTML tree: " + node);
}
};

View File

@@ -139,7 +139,7 @@ HTML.Raw = function (value) {
HTML.EmitCode = function (value) {
if (! (this instanceof HTML.EmitCode))
// called without `new`
return new HTML.Raw(value);
return new HTML.EmitCode(value);
if (typeof value !== 'string')
throw new Error('HTML.EmitCode must be constructed with a string');

View File

@@ -448,7 +448,7 @@ var optimize = function (tree) {
var pushRawHTML = function (array, html) {
var N = array.length;
if (N > 0 && (array[N-1] instanceof HTML.Raw)) {
array[N-1][0] += html;
array[N-1] = HTML.Raw(array[N-1].value + html);
} else {
array.push(HTML.Raw(html));
}
@@ -485,9 +485,9 @@ var optimize = function (tree) {
// clean up unnecessary HTML.Raw wrappers around pure character data
for (var j = 0; j < result.length; j++) {
if ((result[j] instanceof HTML.Raw) &&
isPureChars(result[j][0]))
isPureChars(result[j].value))
// replace HTML.Raw with simple string
result[j] = result[j][0];
result[j] = result[j].value;
}
}
return result;
@@ -785,17 +785,14 @@ Spacebars._handleSpecialAttributes = function (oldAttrs) {
//
// If specials are found, sets `foundSpecials` to true.
var convertSpecialToEmitCode = function (v) {
var type = HTML.typeOf(v);
if (type === 'null' || type === 'string' || type === 'charref') {
return v;
} else if (type === 'special') {
if (v instanceof HTML.Special) {
foundSpecials = true;
return HTML.EmitCode('function () { return ' +
codeGenMustache(v.attrs) + '; }');
} else if (type === 'array') {
codeGenMustache(v.value) + '; }');
} else if (v instanceof Array) {
return _.map(v, convertSpecialToEmitCode);
} else {
throw new Error("Unexpected node in attribute value: " + v);
return v;
}
};

View File

@@ -165,7 +165,7 @@ html_scanner = {
contents, { sourceName: "<body>" });
// We may be one of many `<body>` tags.
results.js += "\nUI.body2.contentParts.push(UI.Component.extend({render: " + renderFuncCode + "}));\nMeteor.startup(function () { if (! UI.body2.INSTANTIATED) { UI.materialize(UI.body2, document.body); } });\n";
results.js += "\nUI.body.contentParts.push(UI.Component.extend({render: " + renderFuncCode + "}));\nMeteor.startup(function () { if (! UI.body.INSTANTIATED) { UI.materialize(UI.body, document.body); } });\n";
}
}
};

View File

@@ -26,7 +26,7 @@ Tinytest.add("templating - html scanner", function (test) {
// where content is something simple like the string "Hello"
// (passed in as a source string including the quotes).
var simpleBody = function (content) {
return "\nUI.body2.contentParts.push(UI.Component.extend({render: (function() {\n var self = this;\n return " + content + ";\n})}));\nMeteor.startup(function () { if (! UI.body2.INSTANTIATED) { UI.materialize(UI.body2, document.body); } });\n";
return "\nUI.body.contentParts.push(UI.Component.extend({render: (function() {\n var self = this;\n return " + content + ";\n})}));\nMeteor.startup(function () { if (! UI.body.INSTANTIATED) { UI.materialize(UI.body, document.body); } });\n";
};
// arguments are quoted strings like '"hello"'

View File

@@ -213,11 +213,10 @@ var updateAttributes = function(elem, newAttrs, handlers) {
for (var k in newAttrs) {
var handler = null;
var oldValue;
var value = attributeValueToString(newAttrs[k]);
var value = newAttrs[k];
if ((! handlers) || (! handlers.hasOwnProperty(k))) {
if (value !== null) {
// make new handler
checkAttributeName(k);
handler = makeAttributeHandler(k, value);
if (handlers)
handlers[k] = handler;
@@ -359,7 +358,7 @@ var materialize = function (node, parent, before, parentComponent) {
// for example, maybe some of them go in the HTML package.
UI.materialize = materialize;
UI.body2 = UI.Component.extend({
UI.body = UI.Component.extend({
kind: 'body',
contentParts: [],
render: function () {