
Summary: Add comments everywhere! Also fix some small bugs like using Style.id instead of Style.size, and rename some variables to be more descriptive. Fixes #22 Test Plan: - Make sure the huxley screenshots didn't change - Make sure the tests still pass Reviewers: alpert Reviewed By: alpert Differential Revision: http://phabricator.khanacademy.org/D13158
242 lines
6.2 KiB
JavaScript
242 lines
6.2 KiB
JavaScript
/**
|
|
* These objects store the data about the DOM nodes we create, as well as some
|
|
* extra data. They can then be transformed into real DOM nodes with the toNode
|
|
* function or HTML markup using toMarkup. They are useful for both storing
|
|
* extra properties on the nodes, as well as providing a way to easily work
|
|
* with the DOM.
|
|
*/
|
|
|
|
var utils = require("./utils");
|
|
|
|
/**
|
|
* Create an HTML className based on a list of classes. In addition to joining
|
|
* with spaces, we also remove null or empty classes.
|
|
*/
|
|
var createClass = function(classes) {
|
|
classes = classes.slice();
|
|
for (var i = classes.length - 1; i >= 0; i--) {
|
|
if (!classes[i]) {
|
|
classes.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
return classes.join(" ");
|
|
};
|
|
|
|
/**
|
|
* This node represents a span node, with a className, a list of children, and
|
|
* an inline style. It also contains information about its height, depth, and
|
|
* maxFontSize.
|
|
*/
|
|
function span(classes, children, height, depth, maxFontSize, style) {
|
|
this.classes = classes || [];
|
|
this.children = children || [];
|
|
this.height = height || 0;
|
|
this.depth = depth || 0;
|
|
this.maxFontSize = maxFontSize || 0;
|
|
this.style = style || {};
|
|
}
|
|
|
|
/**
|
|
* Convert the span into an HTML node
|
|
*/
|
|
span.prototype.toNode = function() {
|
|
var span = document.createElement("span");
|
|
|
|
// Apply the class
|
|
span.className = createClass(this.classes);
|
|
|
|
// Apply inline styles
|
|
for (var style in this.style) {
|
|
if (this.style.hasOwnProperty(style)) {
|
|
span.style[style] = this.style[style];
|
|
}
|
|
}
|
|
|
|
// Append the children, also as HTML nodes
|
|
for (var i = 0; i < this.children.length; i++) {
|
|
span.appendChild(this.children[i].toNode());
|
|
}
|
|
|
|
return span;
|
|
};
|
|
|
|
/**
|
|
* Convert the span into an HTML markup string
|
|
*/
|
|
span.prototype.toMarkup = function() {
|
|
var markup = "<span";
|
|
|
|
// Add the class
|
|
if (this.classes.length) {
|
|
markup += " class=\"";
|
|
markup += utils.escape(createClass(this.classes));
|
|
markup += "\"";
|
|
}
|
|
|
|
var styles = "";
|
|
|
|
// Add the styles, after hyphenation
|
|
for (var style in this.style) {
|
|
if (this.style.hasOwnProperty(style)) {
|
|
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
|
|
}
|
|
}
|
|
|
|
if (styles) {
|
|
markup += " style=\"" + utils.escape(styles) + "\"";
|
|
}
|
|
|
|
markup += ">";
|
|
|
|
// Add the markup of the children, also as markup
|
|
for (var i = 0; i < this.children.length; i++) {
|
|
markup += this.children[i].toMarkup();
|
|
}
|
|
|
|
markup += "</span>";
|
|
|
|
return markup;
|
|
};
|
|
|
|
/**
|
|
* This node represents a document fragment, which contains elements, but when
|
|
* placed into the DOM doesn't have any representation itself. Thus, it only
|
|
* contains children and doesn't have any HTML properties. It also keeps track
|
|
* of a height, depth, and maxFontSize.
|
|
*/
|
|
function documentFragment(children, height, depth, maxFontSize) {
|
|
this.children = children || [];
|
|
this.height = height || 0;
|
|
this.depth = depth || 0;
|
|
this.maxFontSize = maxFontSize || 0;
|
|
}
|
|
|
|
/**
|
|
* Convert the fragment into a node
|
|
*/
|
|
documentFragment.prototype.toNode = function() {
|
|
// Create a fragment
|
|
var frag = document.createDocumentFragment();
|
|
|
|
// Append the children
|
|
for (var i = 0; i < this.children.length; i++) {
|
|
frag.appendChild(this.children[i].toNode());
|
|
}
|
|
|
|
return frag;
|
|
};
|
|
|
|
/**
|
|
* Convert the fragment into HTML markup
|
|
*/
|
|
documentFragment.prototype.toMarkup = function() {
|
|
var markup = "";
|
|
|
|
// Simply concatenate the markup for the children together
|
|
for (var i = 0; i < this.children.length; i++) {
|
|
markup += this.children[i].toMarkup();
|
|
}
|
|
|
|
return markup;
|
|
};
|
|
|
|
/**
|
|
* A symbol node contains information about a single symbol. It either renders
|
|
* to a single text node, or a span with a single text node in it, depending on
|
|
* whether it has CSS classes, styles, or needs italic correction.
|
|
*/
|
|
function symbolNode(value, height, depth, italic, skew, classes, style) {
|
|
this.value = value || "";
|
|
this.height = height || 0;
|
|
this.depth = depth || 0;
|
|
this.italic = italic || 0;
|
|
this.skew = skew || 0;
|
|
this.classes = classes || [];
|
|
this.style = style || {};
|
|
this.maxFontSize = 0;
|
|
}
|
|
|
|
/**
|
|
* Creates a text node or span from a symbol node. Note that a span is only
|
|
* created if it is needed.
|
|
*/
|
|
symbolNode.prototype.toNode = function() {
|
|
var node = document.createTextNode(this.value);
|
|
var span = null;
|
|
|
|
if (this.italic > 0) {
|
|
span = document.createElement("span");
|
|
span.style.marginRight = this.italic + "em";
|
|
}
|
|
|
|
if (this.classes.length > 0) {
|
|
span = span || document.createElement("span");
|
|
span.className = createClass(this.classes);
|
|
}
|
|
|
|
for (var style in this.style) {
|
|
if (this.style.hasOwnProperty(style)) {
|
|
span = span || document.createElement("span");
|
|
span.style[style] = this.style[style];
|
|
}
|
|
}
|
|
|
|
if (span) {
|
|
span.appendChild(node);
|
|
return span;
|
|
} else {
|
|
return node;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates markup for a symbol node.
|
|
*/
|
|
symbolNode.prototype.toMarkup = function() {
|
|
// TODO(alpert): More duplication than I'd like from
|
|
// span.prototype.toMarkup and symbolNode.prototype.toNode...
|
|
var needsSpan = false;
|
|
|
|
var markup = "<span";
|
|
|
|
if (this.classes.length) {
|
|
needsSpan = true;
|
|
markup += " class=\"";
|
|
markup += utils.escape(createClass(this.classes));
|
|
markup += "\"";
|
|
}
|
|
|
|
var styles = "";
|
|
|
|
if (this.italic > 0) {
|
|
styles += "margin-right:" + this.italic + "em;";
|
|
}
|
|
for (var style in this.style) {
|
|
if (this.style.hasOwnProperty(style)) {
|
|
styles += utils.hyphenate(style) + ":" + this.style[style] + ";";
|
|
}
|
|
}
|
|
|
|
if (styles) {
|
|
needsSpan = true;
|
|
markup += " style=\"" + utils.escape(styles) + "\"";
|
|
}
|
|
|
|
var escaped = utils.escape(this.value);
|
|
if (needsSpan) {
|
|
markup += ">";
|
|
markup += escaped;
|
|
markup += "</span>";
|
|
return markup;
|
|
} else {
|
|
return escaped;
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
span: span,
|
|
documentFragment: documentFragment,
|
|
symbolNode: symbolNode
|
|
};
|