From 30db75351ff8f1003106db64dccdecbd88f80101 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Sat, 26 Apr 2014 11:30:37 -0700 Subject: [PATCH] Start of new html.js readme (via doctool script) --- packages/htmljs/README-old.md | 239 +++++++++++++++++++++++++++++ packages/htmljs/README.md | 279 ++++++++++------------------------ packages/htmljs/html.js | 177 +++++++++++++++++---- 3 files changed, 470 insertions(+), 225 deletions(-) create mode 100644 packages/htmljs/README-old.md diff --git a/packages/htmljs/README-old.md b/packages/htmljs/README-old.md new file mode 100644 index 0000000000..499b37832a --- /dev/null +++ b/packages/htmljs/README-old.md @@ -0,0 +1,239 @@ +# HTMLjs + +A small library for expressing HTML trees in a concise +syntax. This library is used at compile time and run time by Meteor UI. + +``` +var UL = HTML.UL, LI = HTML.LI, B = HTML.B; + +HTML.toHTML( + UL({id: 'mylist'}, + LI({'class': 'item'}, "Hello ", B("world"), "!"), + LI({'class': 'item'}, "Goodbye, world"))) +``` + +``` + +``` + +The functions `UL`, `LI`, and so on are "tag constructors" which +return an object representation that can be used to generate HTML, or, +via other packages, be used to generate DOM (`ui`), be parsed from +HTML (`html-tools`), or serve as the backbone of the intermediate +representation for a template compiler (`spacebars-compiler`). + +# This document is out of date and will be revised soon. + +## Syntax + +Tag constructors take an optional first argument `attrs` followed by +zero or more arguments, the `children`. The first argument is taken +to be `attrs` if it is a "vanilla" JavaScript object such as an object +literal. + +> Ideally, a "vanilla" object would be one whose direct prototype is +> `Object.prototype`. Since this test is impossible in IE 8, we test +> `obj.constructor === Object`, which is true for object literals +> (except ones like `{constructor: blah}`!) and false for most objects +> with custom prototypes (because JavaScript sets +> `MyClass.prototype.constructor = MyClass` when you create a function +> `MyClass`). + +Children of a tag may be of any of several built-in types: + +* Tag (HTML.Tag) +* HTML.CharRef +* HTML.Comment +* HTML.Raw +* String +* Boolean or Number (which will be converted to String) +* Array (which will be flattened) +* Null or undefined (which will be ignored) +* Template/Component +* Function returning one of these types + +The set of allowed types is *open* in that any object may be included +in the tree as long as the code consuming the tree can handle it. + +Character references (like `&`) are *not* interpreted in strings. +To include a character reference, use `HTML.CharRef({html: +'&', str: '&'})`, specifying both the raw HTML form and the string +form of the character. + +> In other words, string values are of the form you would pass to +`document.createTextNode`, not of the form you would see in an HTML +document. The intent here is to only need to parse and interpret +character references at compile time, making the representation +maximally flexible easy to consume at runtime. +> +> The reason we represent character references at all, rather than +> simply converting them to Unicode when parsing the source HTML +> (and then escaping `&` and `<` at the very end) +> is 1) to preserve the HTML author's intent, and 2) in case there +> is a character-encoding-related reason that a character reference +> is being used. + +Attribute values can contain character references, using arrays to +hold the string and CharRef parts: + +``` +var amp = HTML.CharRef({html: '&', str: '&'}); + +HTML.toHTML(HTML.SPAN({title: ['M', amp, 'Ms']}, + 'M', amp, 'M candies')) +``` + +``` +M&M candies +``` + +A comment looks like `HTML.Comment("value here")`, where the value +should not contain two consecutive hyphen (`-`) characters or an +initial or final hyphen (or they will be stripped out). + +A "raw" object like `HTML.Raw("
")` represents raw HTML to insert +into the document. The HTML should be known to be safe and contain +balanced tags! It will be injected without any parsing or checking +when the representation is converted to an HTML string. If the +representation is used to generate DOM directly, the "raw" node will +be materialized using an innerHTML-like method. + +Functions in the tree are used as reactivity boundaries when +generating DOM directly. When generating HTML, they are simply called +for their return value. Functions are passed no arguments and are +given no particular value of `this`. + +Templates/components like `Template.foo` can also be included in the +representation. HTMLjs has very limited knowledge of what a component +is. It knows components have an `instantiate` method that returns +something with a `render` method. Operations that realize an HTMLjs +tree as HTML, DOM, or some other form have a bit of boilerplate that +they use to detect and instantiate components: + +``` +HTML.toHTML = function (node, parentComponent) { + // ... handle various types of `node` + if (typeof node.instantiate === 'function') { + // component + var instance = node.instantiate(parentComponent || null); + var content = instance.render(); + // recurse with a new value for parentComponent + return HTML.toHTML(content, instance); + } + // ... +}; +``` + +The argument `parentComponent` is used to set a pointer that points +from each component to its parent, used for name lookups. + +## "Known" and Custom Tags + +All the usual HTML and HTML5 tags are available as `HTML.A`, +`HTML.ABBR`, `HTML.ADDRESS`, etc. These tags are called "known" tags +and have predefined tag constructors. If you want to use a custom +tag, you'll have to create the tag constructor using `getTag` or `ensureTag`. + +``` +var SPAN = HTML.SPAN; +var FOO = HTML.getTag('FOO'); +``` + +``` +HTML.ensureTag('FOO'); +var SPAN = HTML.SPAN; +var FOO = HTML.FOO; +``` + +All of these functions handle case conversion of `tagName` as +appropriate, so whether you provide `foo` or `Foo` or `FOO`, the +symbol on `HTML` will be `HTML.FOO`, while generated HTML and DOM will use +the lowercase name `foo`. + +`HTML.getTag(tagName)` - Returns a tag constructor for `tagName`, calling `ensureTag` if it doesn't exist. + +`HTML.ensureTag(tagName)` - Creates a tag constructor for `tagName` if one doesn't exist and attaches it to the `HTML` object. + +`HTML.isTagEnsured(tagName)` - Returns true if `tagName` has a built-in, predefined constructor. Useful for code generators that want to know if they should emit a call to `ensureTag`. + +## Object Representation + +Tag constructors follow an object-oriented paradigm with optional +`new`. The returned objects are `instanceof` the tag constructor and +also of `HTML.Tag`. In other words, all of the following are true: + +``` +HTML.P() instanceof HTML.P +HTML.P() instanceof HTML.Tag +(new HTML.P) instanceof HTML.P +(new HTML.P) instanceof HTML.Tag +``` + +Similarly, objects constructed with `HTML.Comment` are instances of `HTML.Comment`, and so on. + +In general, HTMLjs objects should be considered immutable. + +HTML.Tag objects have these properties: + +* `tagName` - the uppercase tag name +* `attrs` - an object or null +* `children` - an array of zero or more children + +HTML.CharRef objects have `html` and `str` properties, specified by +the object passed to the constructor. + +HTML.Comment and HTML.Raw objects have a `value` property. + +### Attributes + +Attribute values can contain most kinds of HTMLjs content, but not Tag, Comment, or Raw. They may contain functions and components, even though these functions won't ever establish reactivity boundaries at a finer level than an entire attribute value. + +The attributes dictionary of a tag can have a special entry `$dynamic`, which holds additional attributes dictionaries to combine with the main dictionary. These additional dictionaries may be computed by functions, lending generality to the calculation of the attributes dictionary that would not otherwise be present. + +Specifically, the value of `$dynamic` must be an array, each element of which is either an attributes dictionary or a function returning an attributes dictionary. (These dictionaries may not themselves have a `$dynamic` key.) When calculating the final attributes dictionary for a tag, each dictionary obtained from the `$dynamic` array is used to modify the existing dictionary by copying the new attribute entries over it, except for entries with a "nully" value. A "nully" value is one that is either `null`, `undefined`, `[]`, or an array of nully values. Before checking if the dynamic attribute value is nully, all functions and components are evaluated (i.e. the functions are called and the components are instantiated, such that no functions or components remain). + +The `$dynamic` feature is designed to support writing `
` in templates. + +`HTML.evaluateDynamicAttributes(attrs, parentComponent)` - Returns the final attributes dictionary for a tag after interpreting the `$dynamic` property, if present. Takes a tag's `attrs` property and a `parentComponent` (used to instantiate any components in the attributes). `attrs` may be null. + +`tag.evaluateDynamicAttributes(parentComponent)` - Shorthand for `HTML.evaluateDynamicAttributes(tag.attrs, parentComponent)`. + +`HTML.isNully(value)` - Returns true if `value` is a nully value, i.e. one of `null`, `undefined`, `[]`, or an array of nully values. + +`HTML.evaluate(node, parentComponent)` - Calls all functions and instantiates all components in an HTMLjs tree. + +## toHTML and toText + +`HTML.toHTML(node, parentComponent)` - Converts the HTMLjs content `node` to an HTML string, using `parentComponent` as the parent scope pointer when instantiating components. + +`HTML.toText(node, textMode, parentComponent)` - Converts the HTMLjs content `node` into text, suitable for being included as part of an attribute value or textarea, for example. `node` must not contain Tags or Comments, but may contain CharRefs, functions, and components. The required argument `textMode` specifies what sort of text to generate, which affects how charater references are handled and which characters are escaped. + +* `HTML.TEXTMODE.STRING` - JavaScript string suitable for `document.createTextNode` or `element.setAttribute`. Character references are replaced by the characters they represent. No escaping is performed. + +* `HTML.TEXTMODE.ATTRIBUTE` - HTML string suitable for a quoted attribute. Character references are included in raw HTML form (i.e. `&foo;`). `&` and `"` are escaped when found in strings in the HTMLjs tree. + +* `HTML.TEXTMODE.RCDATA` - HTML string suitable for the content of a TEXTAREA element, for example. (RCDATA stands for "replaced character data" as in the HTML syntax spec.) Character references are included in raw HTML form. `&` and `<` are escaped when found in strings in the HTMLjs tree. + +> The reason to perform the escaping as part of `HTML.toText` rather than as a post-processing step is in order to support `HTML.CharRef`, allowing the HTML author's choice of character reference encoding to be passed through. If we only had `STRING` mode, we would lose the original form of the character references. If we only had `RCDATA` mode, say, we would have to interpret the character references at runtime to use the DOM API. On a related note, we don't allow `HTML.Raw` because character references are the only "raw" thing there is in text mode (and, again, we don't want to interpret them at runtime). `HTML.CharRef` is sort of like a one-character version of `Raw`. + +## Name Utilities + +All of these functions take case-insensitive input. + +`HTML.properCaseTagName(tagName)` - Case-convert a tag name for inclusion in HTML or passing to `document.createElement`. Most tags belong in lowercase, but there are some camel-case SVG tags. HTML processors must know the proper case for tag names, because HTML is case-insensitive but the DOM is sometimes case-sensitive. + +`HTML.properCaseAttributeName(name)` - Case-convert an attribute name for inclusion in HTML or passing to `element.setAttribute`. See `HTML.properCaseTagName`. + +`HTML.isValidAttributeName(name)` - Returns true if `name` conforms to a restricted set of legal characters known to work both in HTML and the DOM APIs. Allows at least ASCII numbers and letters, hyphens, and underscores, where the first character can't be a number or a hyphen. + +`HTML.isKnownElement(tagName)` - Returns true if `tagName` is a known HTML/HTML5 element, excluding SVG and other foreign elements. + +`HTML.isKnownSVGElement(tagName)` - Returns true if `tagName` is a known SVG element. + +`HTML.isVoidElement(tagName)` - Returns true if `tagName` is a known void element such as `BR`, `HR`, or `INPUT`. Void elements are output as `
` instead of `

`. Note that neither HTML4 nor HTML5 has true self-closing tags (except when parsing SVG). `
` is the same as `
` and `
` is the same as `
`. It was only the now-abandoned XHTML standard that said otherwise, which was a backwards-incompatible change. Modern browsers refer to the list of void elements instead. + +`HTML.asciiLowerCase(str)` - "ASCII-lowercases" `str`, converting `A-Z` to `a-z`. The case-insensitive parts of the HTML spec use this operation for case folding. + diff --git a/packages/htmljs/README.md b/packages/htmljs/README.md index 499b37832a..aef8f3b6a9 100644 --- a/packages/htmljs/README.md +++ b/packages/htmljs/README.md @@ -1,239 +1,120 @@ +*This file is automatically generated from [`html.js`](html.js).* + # HTMLjs -A small library for expressing HTML trees in a concise -syntax. This library is used at compile time and run time by Meteor UI. +HTMLjs is a small library for expressing HTML trees with a concise +syntax. It is used to render content in Blaze and to represent +templates during compilation. ``` var UL = HTML.UL, LI = HTML.LI, B = HTML.B; HTML.toHTML( - UL({id: 'mylist'}, - LI({'class': 'item'}, "Hello ", B("world"), "!"), - LI({'class': 'item'}, "Goodbye, world"))) + UL({id: 'mylist'}, + LI({'class': 'item'}, "Hello ", B("world"), "!"), + LI({'class': 'item'}, "Goodbye, world"))) ``` ```
    -
  • Hello world!
  • -
  • Goodbye, world
  • +
  • Hello world!
  • +
  • Goodbye, world
``` -The functions `UL`, `LI`, and so on are "tag constructors" which -return an object representation that can be used to generate HTML, or, -via other packages, be used to generate DOM (`ui`), be parsed from -HTML (`html-tools`), or serve as the backbone of the intermediate -representation for a template compiler (`spacebars-compiler`). +The functions `UL`, `LI`, and `B` are constructors which +return instances of `HTML.Tag`. These tag objects can +then be converted to an HTML string or directly into DOM nodes. -# This document is out of date and will be revised soon. +The flexible structure of HTMLjs allows different kinds of Blaze +directives to be embedded in the tree. HTMLjs does not know about +these directives, which are considered "foreign objects." -## Syntax +# Built-in Types -Tag constructors take an optional first argument `attrs` followed by -zero or more arguments, the `children`. The first argument is taken -to be `attrs` if it is a "vanilla" JavaScript object such as an object -literal. +The following types are built into HTMLjs. Built-in methods like +`HTML.toHTML` expect a tree consisting only of these types. -> Ideally, a "vanilla" object would be one whose direct prototype is -> `Object.prototype`. Since this test is impossible in IE 8, we test -> `obj.constructor === Object`, which is true for object literals -> (except ones like `{constructor: blah}`!) and false for most objects -> with custom prototypes (because JavaScript sets -> `MyClass.prototype.constructor = MyClass` when you create a function -> `MyClass`). +* __`null`, `undefined`__ - Render to nothing. -Children of a tag may be of any of several built-in types: +* __boolean, number__ - Render to the string form of the boolean or number. -* Tag (HTML.Tag) -* HTML.CharRef -* HTML.Comment -* HTML.Raw -* String -* Boolean or Number (which will be converted to String) -* Array (which will be flattened) -* Null or undefined (which will be ignored) -* Template/Component -* Function returning one of these types +* __string__ - Renders to a text node (or part of an attribute value). All characters are safe, and no HTML injection is possible. The string `""` renders `<a>` in HTML, and `document.createTextNode("")` in DOM. -The set of allowed types is *open* in that any object may be included -in the tree as long as the code consuming the tree can handle it. +* __Array__ - Renders to its elements in order. An array may be empty. Arrays are detected using `HTML.isArray(...)`. -Character references (like `&`) are *not* interpreted in strings. -To include a character reference, use `HTML.CharRef({html: -'&', str: '&'})`, specifying both the raw HTML form and the string -form of the character. +* __`HTML.Tag`__ - Renders to an HTML element (including start tag, contents, and end tag). -> In other words, string values are of the form you would pass to -`document.createTextNode`, not of the form you would see in an HTML -document. The intent here is to only need to parse and interpret -character references at compile time, making the representation -maximally flexible easy to consume at runtime. -> -> The reason we represent character references at all, rather than -> simply converting them to Unicode when parsing the source HTML -> (and then escaping `&` and `<` at the very end) -> is 1) to preserve the HTML author's intent, and 2) in case there -> is a character-encoding-related reason that a character reference -> is being used. +* __`HTML.CharRef({html: ..., str: ...})`__ - Renders to a character reference (such as ` `) when generating HTML. -Attribute values can contain character references, using arrays to -hold the string and CharRef parts: +* __`HTML.Comment(text)`__ - Renders to an HTML comment. + +* __`HTML.Raw(html)`__ - Renders to a string of HTML to include verbatim. + +The `new` keyword is not required before constructors of HTML object types. + +All objects and arrays should be considered immutable. Their properties +are public, but they should only be read, not written. Arrays should not +be spliced in place. This convention allows for clean patterns of +processing and transforming HTMLjs trees. + + +## HTML.Tag + +An `HTML.Tag` is created using a tag-specific constructor, like +`HTML.P` for a `

` tag or `HTML.INPUT` for an `` tag. The +resulting object is `instanceof HTML.Tag`. (The `HTML.Tag` +constructor should not be called directly.) + +Tag constructors take an optional attributes dictionary followed +by zero or more children: ``` -var amp = HTML.CharRef({html: '&', str: '&'}); +HTML.HR() -HTML.toHTML(HTML.SPAN({title: ['M', amp, 'Ms']}, - 'M', amp, 'M candies')) +HTML.DIV(HTML.P("First paragraph"), + HTML.P("Second paragraph")) + +HTML.INPUT({type: "text"}) + +HTML.SPAN({'class': "foo"}, "Some text") ``` -``` -M&M candies -``` +### Tag properties -A comment looks like `HTML.Comment("value here")`, where the value -should not contain two consecutive hyphen (`-`) characters or an -initial or final hyphen (or they will be stripped out). +Tags have the following properties: -A "raw" object like `HTML.Raw("
")` represents raw HTML to insert -into the document. The HTML should be known to be safe and contain -balanced tags! It will be injected without any parsing or checking -when the representation is converted to an HTML string. If the -representation is used to generate DOM directly, the "raw" node will -be materialized using an innerHTML-like method. +* `tagName` - The tag name in lowercase (or camelCase) +* `children` - An array of children (always present) +* `attrs` - An attributes dictionary, `null`, or an array (see below) -Functions in the tree are used as reactivity boundaries when -generating DOM directly. When generating HTML, they are simply called -for their return value. Functions are passed no arguments and are -given no particular value of `this`. +### Special forms of attributes -Templates/components like `Template.foo` can also be included in the -representation. HTMLjs has very limited knowledge of what a component -is. It knows components have an `instantiate` method that returns -something with a `render` method. Operations that realize an HTMLjs -tree as HTML, DOM, or some other form have a bit of boilerplate that -they use to detect and instantiate components: +The attributes of a Tag may be an array of dictionaries. In order +for a tag constructor to recognize an array as the attributes argument, +it must be written as `HTML.Attrs(attrs1, attrs2, ...)`, as in this +example: ``` -HTML.toHTML = function (node, parentComponent) { - // ... handle various types of `node` - if (typeof node.instantiate === 'function') { - // component - var instance = node.instantiate(parentComponent || null); - var content = instance.render(); - // recurse with a new value for parentComponent - return HTML.toHTML(content, instance); - } - // ... -}; +var extraAttrs = {'class': "container"}; + +var div = HTML.DIV(HTML.Attrs({id: "main"}, extraAttrs), + "This is the content."); + +div.attrs // => [{id: "main"}, {'class': "container"}] ``` -The argument `parentComponent` is used to set a pointer that points -from each component to its parent, used for name lookups. +`HTML.Attrs` may also be used to pass a foreign object in place of +an attributes dictionary of a tag. -## "Known" and Custom Tags +### Foreign objects -All the usual HTML and HTML5 tags are available as `HTML.A`, -`HTML.ABBR`, `HTML.ADDRESS`, etc. These tags are called "known" tags -and have predefined tag constructors. If you want to use a custom -tag, you'll have to create the tag constructor using `getTag` or `ensureTag`. - -``` -var SPAN = HTML.SPAN; -var FOO = HTML.getTag('FOO'); -``` - -``` -HTML.ensureTag('FOO'); -var SPAN = HTML.SPAN; -var FOO = HTML.FOO; -``` - -All of these functions handle case conversion of `tagName` as -appropriate, so whether you provide `foo` or `Foo` or `FOO`, the -symbol on `HTML` will be `HTML.FOO`, while generated HTML and DOM will use -the lowercase name `foo`. - -`HTML.getTag(tagName)` - Returns a tag constructor for `tagName`, calling `ensureTag` if it doesn't exist. - -`HTML.ensureTag(tagName)` - Creates a tag constructor for `tagName` if one doesn't exist and attaches it to the `HTML` object. - -`HTML.isTagEnsured(tagName)` - Returns true if `tagName` has a built-in, predefined constructor. Useful for code generators that want to know if they should emit a call to `ensureTag`. - -## Object Representation - -Tag constructors follow an object-oriented paradigm with optional -`new`. The returned objects are `instanceof` the tag constructor and -also of `HTML.Tag`. In other words, all of the following are true: - -``` -HTML.P() instanceof HTML.P -HTML.P() instanceof HTML.Tag -(new HTML.P) instanceof HTML.P -(new HTML.P) instanceof HTML.Tag -``` - -Similarly, objects constructed with `HTML.Comment` are instances of `HTML.Comment`, and so on. - -In general, HTMLjs objects should be considered immutable. - -HTML.Tag objects have these properties: - -* `tagName` - the uppercase tag name -* `attrs` - an object or null -* `children` - an array of zero or more children - -HTML.CharRef objects have `html` and `str` properties, specified by -the object passed to the constructor. - -HTML.Comment and HTML.Raw objects have a `value` property. - -### Attributes - -Attribute values can contain most kinds of HTMLjs content, but not Tag, Comment, or Raw. They may contain functions and components, even though these functions won't ever establish reactivity boundaries at a finer level than an entire attribute value. - -The attributes dictionary of a tag can have a special entry `$dynamic`, which holds additional attributes dictionaries to combine with the main dictionary. These additional dictionaries may be computed by functions, lending generality to the calculation of the attributes dictionary that would not otherwise be present. - -Specifically, the value of `$dynamic` must be an array, each element of which is either an attributes dictionary or a function returning an attributes dictionary. (These dictionaries may not themselves have a `$dynamic` key.) When calculating the final attributes dictionary for a tag, each dictionary obtained from the `$dynamic` array is used to modify the existing dictionary by copying the new attribute entries over it, except for entries with a "nully" value. A "nully" value is one that is either `null`, `undefined`, `[]`, or an array of nully values. Before checking if the dynamic attribute value is nully, all functions and components are evaluated (i.e. the functions are called and the components are instantiated, such that no functions or components remain). - -The `$dynamic` feature is designed to support writing `

` in templates. - -`HTML.evaluateDynamicAttributes(attrs, parentComponent)` - Returns the final attributes dictionary for a tag after interpreting the `$dynamic` property, if present. Takes a tag's `attrs` property and a `parentComponent` (used to instantiate any components in the attributes). `attrs` may be null. - -`tag.evaluateDynamicAttributes(parentComponent)` - Shorthand for `HTML.evaluateDynamicAttributes(tag.attrs, parentComponent)`. - -`HTML.isNully(value)` - Returns true if `value` is a nully value, i.e. one of `null`, `undefined`, `[]`, or an array of nully values. - -`HTML.evaluate(node, parentComponent)` - Calls all functions and instantiates all components in an HTMLjs tree. - -## toHTML and toText - -`HTML.toHTML(node, parentComponent)` - Converts the HTMLjs content `node` to an HTML string, using `parentComponent` as the parent scope pointer when instantiating components. - -`HTML.toText(node, textMode, parentComponent)` - Converts the HTMLjs content `node` into text, suitable for being included as part of an attribute value or textarea, for example. `node` must not contain Tags or Comments, but may contain CharRefs, functions, and components. The required argument `textMode` specifies what sort of text to generate, which affects how charater references are handled and which characters are escaped. - -* `HTML.TEXTMODE.STRING` - JavaScript string suitable for `document.createTextNode` or `element.setAttribute`. Character references are replaced by the characters they represent. No escaping is performed. - -* `HTML.TEXTMODE.ATTRIBUTE` - HTML string suitable for a quoted attribute. Character references are included in raw HTML form (i.e. `&foo;`). `&` and `"` are escaped when found in strings in the HTMLjs tree. - -* `HTML.TEXTMODE.RCDATA` - HTML string suitable for the content of a TEXTAREA element, for example. (RCDATA stands for "replaced character data" as in the HTML syntax spec.) Character references are included in raw HTML form. `&` and `<` are escaped when found in strings in the HTMLjs tree. - -> The reason to perform the escaping as part of `HTML.toText` rather than as a post-processing step is in order to support `HTML.CharRef`, allowing the HTML author's choice of character reference encoding to be passed through. If we only had `STRING` mode, we would lose the original form of the character references. If we only had `RCDATA` mode, say, we would have to interpret the character references at runtime to use the DOM API. On a related note, we don't allow `HTML.Raw` because character references are the only "raw" thing there is in text mode (and, again, we don't want to interpret them at runtime). `HTML.CharRef` is sort of like a one-character version of `Raw`. - -## Name Utilities - -All of these functions take case-insensitive input. - -`HTML.properCaseTagName(tagName)` - Case-convert a tag name for inclusion in HTML or passing to `document.createElement`. Most tags belong in lowercase, but there are some camel-case SVG tags. HTML processors must know the proper case for tag names, because HTML is case-insensitive but the DOM is sometimes case-sensitive. - -`HTML.properCaseAttributeName(name)` - Case-convert an attribute name for inclusion in HTML or passing to `element.setAttribute`. See `HTML.properCaseTagName`. - -`HTML.isValidAttributeName(name)` - Returns true if `name` conforms to a restricted set of legal characters known to work both in HTML and the DOM APIs. Allows at least ASCII numbers and letters, hyphens, and underscores, where the first character can't be a number or a hyphen. - -`HTML.isKnownElement(tagName)` - Returns true if `tagName` is a known HTML/HTML5 element, excluding SVG and other foreign elements. - -`HTML.isKnownSVGElement(tagName)` - Returns true if `tagName` is a known SVG element. - -`HTML.isVoidElement(tagName)` - Returns true if `tagName` is a known void element such as `BR`, `HR`, or `INPUT`. Void elements are output as `
` instead of `

`. Note that neither HTML4 nor HTML5 has true self-closing tags (except when parsing SVG). `
` is the same as `
` and `
` is the same as `
`. It was only the now-abandoned XHTML standard that said otherwise, which was a backwards-incompatible change. Modern browsers refer to the list of void elements instead. - -`HTML.asciiLowerCase(str)` - "ASCII-lowercases" `str`, converting `A-Z` to `a-z`. The case-insensitive parts of the HTML spec use this operation for case folding. +Arbitrary objects are allowed in HTMLjs trees, which is useful for +adapting HTMLjs to a wide variety of uses. Such objects are called +foreign objects. +The one restriction on foreign objects is that they must be +instances of a class -- so-called "constructed objects" (see +`HTML.isConstructedObject`) -- so that they can be distinguished +from the vanilla JS objects that represent attributes dictionaries +when constructing Tags. diff --git a/packages/htmljs/html.js b/packages/htmljs/html.js index 88271ed7dd..51ed21d85d 100644 --- a/packages/htmljs/html.js +++ b/packages/htmljs/html.js @@ -1,39 +1,138 @@ +///!README + +/** + * # HTMLjs + * + * HTMLjs is a small library for expressing HTML trees with a concise + * syntax. It is used to render content in Blaze and to represent + * templates during compilation. + * +``` +var UL = HTML.UL, LI = HTML.LI, B = HTML.B; + +HTML.toHTML( + UL({id: 'mylist'}, + LI({'class': 'item'}, "Hello ", B("world"), "!"), + LI({'class': 'item'}, "Goodbye, world"))) +``` + +``` +
    +
  • Hello world!
  • +
  • Goodbye, world
  • +
+``` + * + * The functions `UL`, `LI`, and `B` are constructors which + * return instances of `HTML.Tag`. These tag objects can + * then be converted to an HTML string or directly into DOM nodes. + * + * The flexible structure of HTMLjs allows different kinds of Blaze + * directives to be embedded in the tree. HTMLjs does not know about + * these directives, which are considered "foreign objects." + * + * # Built-in Types + * + * The following types are built into HTMLjs. Built-in methods like + * `HTML.toHTML` expect a tree consisting only of these types. + * + * * __`null`, `undefined`__ - Render to nothing. + * + * * __boolean, number__ - Render to the string form of the boolean or number. + * + * * __string__ - Renders to a text node (or part of an attribute value). All characters are safe, and no HTML injection is possible. The string `"
"` renders `<a>` in HTML, and `document.createTextNode("")` in DOM. + * + * * __Array__ - Renders to its elements in order. An array may be empty. Arrays are detected using `HTML.isArray(...)`. + * + * * __`HTML.Tag`__ - Renders to an HTML element (including start tag, contents, and end tag). + * + * * __`HTML.CharRef({html: ..., str: ...})`__ - Renders to a character reference (such as ` `) when generating HTML. + * + * * __`HTML.Comment(text)`__ - Renders to an HTML comment. + * + * * __`HTML.Raw(html)`__ - Renders to a string of HTML to include verbatim. + * + * The `new` keyword is not required before constructors of HTML object types. + * + * All objects and arrays should be considered immutable. Their properties + * are public, but they should only be read, not written. Arrays should not + * be spliced in place. This convention allows for clean patterns of + * processing and transforming HTMLjs trees. + */ HTML = {}; var IDENTITY = function (x) { return x; }; var SLICE = Array.prototype.slice; -// Tag instances are `instanceof HTML.Tag`. -// -// Tag objects should be considered immutable. -// -// This is a private constructor of an abstract class; don't call it. +/** + * ## HTML.Tag + * + * An `HTML.Tag` is created using a tag-specific constructor, like + * `HTML.P` for a `

` tag or `HTML.INPUT` for an `` tag. The + * resulting object is `instanceof HTML.Tag`. (The `HTML.Tag` + * constructor should not be called directly.) + * + * Tag constructors take an optional attributes dictionary followed + * by zero or more children: + * + * ``` + * HTML.HR() + * + * HTML.DIV(HTML.P("First paragraph"), + * HTML.P("Second paragraph")) + * + * HTML.INPUT({type: "text"}) + * + * HTML.SPAN({'class': "foo"}, "Some text") + * ``` + * + * ### Tag properties + * + * Tags have the following properties: + * + * * `tagName` - The tag name in lowercase (or camelCase) + * * `children` - An array of children (always present) + * * `attrs` - An attributes dictionary, `null`, or an array (see below) + * + * ### Special forms of attributes + * + * The attributes of a Tag may be an array of dictionaries. In order + * for a tag constructor to recognize an array as the attributes argument, + * it must be written as `HTML.Attrs(attrs1, attrs2, ...)`, as in this + * example: + * + * ``` + * var extraAttrs = {'class': "container"}; + * + * var div = HTML.DIV(HTML.Attrs({id: "main"}, extraAttrs), + * "This is the content."); + * + * div.attrs // => [{id: "main"}, {'class': "container"}] + * ``` + * + * `HTML.Attrs` may also be used to pass a foreign object in place of + * an attributes dictionary of a tag. + * + * ### Foreign objects + * + * Arbitrary objects are allowed in HTMLjs trees, which is useful for + * adapting HTMLjs to a wide variety of uses. Such objects are called + * foreign objects. + * + * The one restriction on foreign objects is that they must be + * instances of a class -- so-called "constructed objects" (see + * `HTML.isConstructedObject`) -- so that they can be distinguished + * from the vanilla JS objects that represent attributes dictionaries + * when constructing Tags. + */ + HTML.Tag = function () {}; HTML.Tag.prototype.tagName = ''; // this will be set per Tag subclass HTML.Tag.prototype.attrs = null; HTML.Tag.prototype.children = Object.freeze ? Object.freeze([]) : []; HTML.Tag.prototype.htmljsType = HTML.Tag.htmljsType = ['Tag']; -// Given "p", create and assign `HTML.P` if it doesn't already exist. -// Then return it. `tagName` must have proper case (usually all lowercase). -HTML.getTag = function (tagName) { - var symbolName = HTML.getSymbolName(tagName); - if (symbolName === tagName) // all-caps tagName - throw new Error("Use the lowercase or camelCase form of '" + tagName + "' here"); - - if (! HTML[symbolName]) - HTML[symbolName] = makeTagConstructor(tagName); - - return HTML[symbolName]; -}; - -// Given "p", make sure `HTML.P` exists. `tagName` must have proper case -// (usually all lowercase). -HTML.ensureTag = function (tagName) { - HTML.getTag(tagName); // don't return it -}; - // Given "p" create the function `HTML.P`. var makeTagConstructor = function (tagName) { // HTMLTag is the per-tagName constructor of a HTML.Tag subclass @@ -46,7 +145,8 @@ var makeTagConstructor = function (tagName) { var i = 0; var attrs = arguments.length && arguments[0]; if (attrs && (typeof attrs === 'object')) { - if (attrs.constructor === Object) { + // Treat vanilla JS object as an attributes dictionary. + if (! HTML.isConstructedObject(attrs)) { instance.attrs = attrs; i++; } else if (attrs instanceof HTML.Attrs) { @@ -77,6 +177,25 @@ var makeTagConstructor = function (tagName) { return HTMLTag; }; +// Given "p", create and assign `HTML.P` if it doesn't already exist. +// Then return it. `tagName` must have proper case (usually all lowercase). +HTML.getTag = function (tagName) { + var symbolName = HTML.getSymbolName(tagName); + if (symbolName === tagName) // all-caps tagName + throw new Error("Use the lowercase or camelCase form of '" + tagName + "' here"); + + if (! HTML[symbolName]) + HTML[symbolName] = makeTagConstructor(tagName); + + return HTML[symbolName]; +}; + +// Given "p", make sure `HTML.P` exists. `tagName` must have proper case +// (usually all lowercase). +HTML.ensureTag = function (tagName) { + HTML.getTag(tagName); // don't return it +}; + var CharRef = HTML.CharRef = function (attrs) { if (! (this instanceof CharRef)) // called without `new` @@ -134,6 +253,12 @@ HTML.isArray = function (x) { return (x instanceof Array); }; +HTML.isConstructedObject = function (x) { + return (x && (typeof x === 'object') && + (x.constructor !== Object) && + (! Object.prototype.hasOwnProperty.call(x, 'constructor'))); +}; + HTML.isNully = function (node) { if (node == null) // null or undefined @@ -408,7 +533,7 @@ HTML.TransformingVisitor = HTML.Visitor.extend({ return result; } - if (attrs && (attrs.constructor !== Object)) { + if (attrs && HTML.isConstructedObject(attrs)) { throw new Error("The basic HTML.TransformingVisitor does not support " + "foreign objects in attributes. Define a custom " + "visitAttributes for this case.");