Skip to content

Commit

Permalink
work on HtmlBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
dgreensp committed Jun 17, 2013
1 parent f1a0fcd commit 9eced0a
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 1 deletion.
138 changes: 138 additions & 0 deletions packages/ui/html_builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@

HtmlBuilder = function () {
this.builderId = Random.id();
this.nextElementNum = 1;
this.htmlBuf = [];
this.elemKeyToNum = {};
this.currentComponent = null;
};

var TAG_NAME_REGEX = /^[a-zA-Z0-9]+$/;
var ATTRIBUTE_NAME_REGEX = /^[^\s"'>/=/]+$/;
var ESCAPED_CHARS_UNQUOTED_REGEX = /[&<>]/g;
var ESCAPED_CHARS_QUOTED_REGEX = /[&<>"]/g;

var escapeMap = {
"<": "&lt;",
">": "&gt;",
"&": "&amp;",
'"': "&quot;"
};
var escapeOne = function(c) {
return escapeMap[c];
};

var evaluateStringOrHelper = function (stringOrHelper, component) {
if ((typeof stringOrHelper) === 'string')
return stringOrHelper;

if (! (component instanceof Component))
throw new Error("Can only use a helper from a Component");
if (! component.evaluateHelper)
throw new Error("Enclosing Component does not support helpers");

return component.evaluateHelper(stringOrHelper);
};

_.extend(HtmlBuilder.prototype, {
encodeEntities: function (text, isQuoted) {
// XXX
// All HTML entities in templates are decoded by the template
// parser and given to HtmlBuilder as Unicode. We then re-encode
// some characters into entities here, but not most characters.
// If you're trying to use entities to send ASCII representations
// of non-ASCII characters to the client, you'll need a different
// policy here.
return text.replace(isQuoted ? ESCAPED_CHARS_QUOTED_REGEX :
ESCAPED_CHARS_UNQUOTED_REGEX, escapeOne);
},
computeAttributeValue: function (expression) {
var self = this;

if ((typeof expression) === 'string')
return expression;

var initialValue;
Deps.autorun(function (c) {
if (c.firstRun) {
c.expression = expression;
c.component = self.currentComponent;
} else {
return; // XXX
}

initialValue =
_.map(c.expression, evaluateStringOrHelper).join('');
});

return initialValue;
},
openTag: function (tagName, attrs, options) {
var self = this;

if ((typeof tagName) !== 'string' ||
! TAG_NAME_REGEX.test(tagName))
throw new Error("Illegal HTML tag name: " + tagName);

attrs = attrs || {};
options = options || {};

var buf = this.htmlBuf;
buf.push('<', tagName);
_.each(attrs, function (attrValue, attrName) {
if ((typeof attrName) !== 'string' ||
! ATTRIBUTE_NAME_REGEX.test(attrName))
throw new Error("Illegal HTML attribute name: " + attrName);

buf.push(' ', attrName, '="');
buf.push(self.encodeEntities(self.computeAttributeValue(attrValue),
true));
buf.push('"');
});
if (options.selfClose)
buf.push('/');
buf.push('>');
},
closeTag: function (tagName) {
if ((typeof tagName) !== 'string' ||
! TAG_NAME_REGEX.test(tagName))
throw new Error("Illegal HTML tag name: " + tagName);
this.htmlBuf.push('</', tagName, '>');
},
text: function (stringOrHelper) {
var text = evaluateStringOrHelper(stringOrHelper);
this.htmlBuf.push(this.encodeEntities(text));
},
rawHtml: function (stringOrHelper) {
var html = evaluateStringOrHelper(stringOrHelper);
this.htmlBuf.push(html);
},
finish: function () {
return this.htmlBuf.join('');
}
});

// openChunk, closeChunk
//
// Drop comemnts at start and finish. Comments may have
// to be fished out due to missing close tags (some fun
// logic there). Eventually, can produce cleaner HTML
// using attributes in some cases instead of comments.
// Chunks bound components, and also text/raw inclusions.
// Consecutive openChunks or closeChunks create Chunks
// defined in terms of each other.
//
// When building is finished, it produces HTML and some
// other data. We don't do materialization, because in
// the server-side rendering case, the browser does that!
//
// After materialization, we want to somehow:
// - recalculate all the helpers with deps tracking
// - assign elements to components
// - set bounds of components
//
// ... based on walking the DOM.
//
// The HtmlBuilder probably has a tree of components
// with children and elements, if only to track comment
// references.
3 changes: 2 additions & 1 deletion packages/ui/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ Package.describe({
Package.on_use(function (api) {
api.use('underscore', 'client');

api.add_files(['chunk.js', 'component.js'], 'client');
api.add_files(['chunk.js', 'component.js', 'html_builder.js'],
'client');
});

Package.on_test(function (api) {
Expand Down

0 comments on commit 9eced0a

Please sign in to comment.