lith
"You don't want to write HTML and you not don't want to write HTML. This is the right understanding." --Suzuki Roshi
lith is a tool for generating HTML and CSS using javascript object literals. It is meant as an alternative to:
- Writing HTML by hand.
- Using a template system.
Current status of the project
The current version of lith, v6.0.7, is considered to be stable and complete. Suggestions and patches are welcome. Besides bug fixes, there are no future changes planned.
lith is part of the ustack, a set of libraries to build web applications which aims to be fully understandable by those who use it.
Why lith instead of a template system?
I find two problems with existing template systems:
- The current logic-less approach of popular templating systems is more an obstacle than an advantage. Most often than not, I find that I need actual logic to generate proper views for my data.
- The template is a layer that's separate from my javascript code.
lith intends to skirt both of this problems because it consists of javascript object literals. This means that you can incorporate lith straight into your code, having the full power of the language while being able to operate on lith structures (called liths).
liths have the following properties:
- They can be nested.
- They can be easily generated and manipulated by javascript code.
- They can be stored and transmitted in JSON format.
- Tags are closed and strings are entityified automatically.
Usage examples
Simple tag
<br>
lith.g (['br'])
Tag with properties and contents
<p id="p3" class="remark">This is a remark</p>
lith.g (['p', {id: 'p3', class: 'remark'}, 'This is a remark']);
Nested tags
<div id="container"><p class="remark">This is a remark</p></div>
lith.g (['div', {id: 'container'}, ['p', {class: 'remark'}, 'This is a remark']]);
Table
<table>
<tr id="row1">
<td>A1</td>
<td>B1</td>
</tr>
<tr id="row2">
<td>A2</td>
<td>B2</td>
</tr>
</table>
lith.g (['table', [['A1', 'B1'], ['A2', 'B2']].map (function (v, k) {
return ['tr', {id: 'row' + (k + 1)}, v.map (function (v2) {
return ['td', v2];
})];
})]);
Installation
The dependencies of lith are two:
lith is written in Javascript. You can use it in the browser by sourcing the dependencies and the main file:
<script src="dale.js"></script>
<script src="teishi.js"></script>
<script src="lith.js"></script>
Or you can use these links to the latest version - courtesy of jsDelivr.
<script src="https://cdn.jsdelivr.net/gh/fpereiro/dale@3199cebc19ec639abf242fd8788481b65c7dc3a3/dale.js"></script>
<script src="https://cdn.jsdelivr.net/gh/fpereiro/teishi@31a9cf552dbaee79fb1c2b7d12c6fad20f987983/teishi.js"></script>
<script src="https://cdn.jsdelivr.net/gh/fpereiro/lith@206ca67469ff0a8d6dcbc28593b3978e908c6cca/lith.js"></script>
And you also can use it in node.js. To install: npm install lith
lith should work in any version of node.js (tested in v0.8.0 and above). Browser compatibility has been tested in the following browsers:
- Google Chrome 15 and above.
- Mozilla Firefox 3 and above.
- Safari 4 and above.
- Internet Explorer 6 and above.
- Microsoft Edge 14 and above.
- Opera 10.6 and above.
- Yandex 14.12 and above.
The author wishes to thank Browserstack for providing tools to test cross-browser compatibility.
Liths
An HTML element has three parts:
- Tag
- Attributes
- Contents
The only required part is the tag, since both attributes and contents are optional.
Correspondingly, each lith is an array made of one to three elements:
- Tag: a string, containing a valid HTML tag. For example,
'br'
. - Attributes:
- Case 1: An object, where each key in the object matches a string, a number or
undefined
. The keys are already strings (since that's how javascript represents object literal keys) and are expected to be so. There is an abstruse rule for validating attribute names (keys), explained in the source code, but you don't need to know it. And attribute values must be either strings, numbers or a falsy value (undefined
,null
andfalse
). If an attribute value isundefined
,null
,false
or an empty string, the entire attribute will be ignored. - Case 2:
undefined
.
- Contents:
- Case 1: a lith.
- Case 2: a lithbag.
- Case 3: a lithbag element.
A lithbag is an array containing zero or more of the following elements:
- A string.
- A number.
-
undefined
. - An array containing zero or more of 1) the above elements; 2) liths, 3) lithbags.
However, and in contrast to previous (< 4.0.0) versions of lith, a lithbag can never be an array with its first element being a valid HTML tag. The reason for this is that 1) this allows very fast distinction of liths vs lithbags when running in prod mode
; and 2) this limitation is seldom a real one and can be easily bypassed.
The recursive definition of a lithbag has the following properties:
-
The most obvious one: you can place an array of liths as the content of a given lith. This is necessary when an element has many children at the same level. For example:
<div><p></p><p></p></div>
['div', [ ['p'], ['p'] ]]
-
You can mix liths and literals (strings/numbers) at the same level:
<p>Hola!<br></p>
['p', [ 'Hola!', ['br'] ]]
-
When generating liths with your code (instead of writing them by hand), you don't have to worry about the level of nestedness of liths. For example, the following two liths generate the same code:
[ ['p'], ['div'] ]
[ ['p'], [ ['div'] ] ]
<p></p><div></div>
This allows you to create functions that return liths and place them within other liths. For example:
var dataset = [{id: 1, name: 'a'}, {id: 2, name: 'b'}]; function createRows (data) { var output = []; for (var datum in data) { output.push (['tr', [ ['td', data [datum].id], ['td', data [datum].name], ]]); } return output; } var table = ['table', [ ['tr', [ ['th', 'Id'], ['th', 'Name'] ]], createRows (dataset) ]]; lith.g (table);
This will generate the following HTML:
<table> <tr> <th>Id</th> <th>Name</th> </tr> <tr> <td>1</td> <td>a</td> </tr> <tr> <td>2</td> <td>b</td> </tr> </table>
HTML escapes
lith will escape all special characters ('&'
, <
, >
, "
, '
and `
) when generating HTML. However, the contents of style
and script
tags will not be escaped, since those special characters are expected to remain unescaped in both CSS and JS.
If you need to insert a chunk of literal HTML into a lith, you can do it by using the LITERAL
pseudo-tag:
lith.g (['div', [
['p', 'Hi'],
['LITERAL', '<p>Hello!</p>']
]]);
This will generate the following HTML:
<div>
<p>Hi</p>
<p>Hello!</p>
</div>
Note: LITERAL
pseudo-tags require their contents to be a string. The following examples are invalid and will yield an error:
['LITERAL', 2]
['LITERAL', ['hello', 'there']]
Non-ASCII characters
If you have non-ascii characters in a lith, and you're generating code in the browser, as long as the source file is invoked with the proper encoding, you will have no problem. For example, if scripts.js is saved and transmitted using utf-8, you should include it as:
<script src="scripts.js" charset="utf-8"></script>
By the way, if you're generating the HTML with lith, you can do the same with:
['script', {src: 'scripts.js', charset: 'utf-8'}]
Usage
lith is made of two core functions:
-
lith.g
: this function generates HTML from a lith. -
lith.v
: a helper function that validates a lith.
The input to both functions is either a lith or a lithbag. In either case, the input can only be a single array.
You don't need to invoke lith.v
, since lith.g
validates its own input.
If the input to lith is invalid, false
is returned. Otherwise, you get a string with HTML.
If the input is invalid, lith will print an error through teishi.
If you want to use lith.v
to detect whether an input is either lith or a lithbag and you want to receive an error message if it's neither, invoke it passing a truthy second argument.
prod mode
Performance wise, lith.g
spends about 60-80% of its processing time in validating its input. While validation is essential to shorten the debug cycle when developing, in certain cases you might want to turn it off to improve performance.
The cost of turning off validation is that if there's an invalid lith somewhere, an error will be thrown.
The performance gains of prod mode
will be only noticeable if you're generating thousands of tags.
You can use prod mode
in two ways:
- Locally, by passing a
true
second parameter to an invocation oflith.g
. - Globally, by setting
lith.prod
totrue
. This will affect every subsequent invocation oflith.g
. Even if you pass a falsy second argument tolith.g
,prod
mode will still be active if you setlith.prod
totrue
.
litcs
If liths generate HTML, what generates CSS? Well, a litc! It's unpronounceable, but I ran out of names.
Let's see a few examples:
Simple selector
div.links {
width: 50%;
height: 50%;
}
['div.links', {width: .50, height: .50}]
a, p {
font-size: 120%;
}
['a, p', {'font-size': 1.20}]
Multiple properties for a single value
p {
padding-top: 10px;
padding-bottom: 10px;
padding-left: 5px;
padding-right: 5px;
}
['p', {'padding-top, padding-bottom': 10, 'padding-left, padding-right': 5}]
Nested selector
div.links {
width: 50%;
}
div.links p {
font-size: 120%;
}
['div.links', {width: .50}, ['p', {'font-size': 1.20}]]
Nested selector with parent referencing
a {
font-size: 120%;
}
a:hover {
color: lime; /* Please don't question my aesthethic choices. */
}
['a', {'font-size': 1.20}, ['&:hover', {color: 'lime'}]]
CSS Reset
Taken from [Eric Meyer's CSS reset] (http://meyerweb.com/eric/tools/css/reset/).
[
['html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video', {
'margin, padding, border': 0,
'font-size': 1,
font: 'inherit',
'vertical-align': 'baseline'
}],
['article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section', {display: 'block'}],
['body', {'line-height': '1'}],
['ol, ul', {'list-style': 'none'}],
['blockquote, q', {quotes: 'none'}],
['blockquote:before, blockquote:after, q:before, q:after', {content: "''"}],
['blockquote:before, blockquote:after, q:before, q:after', {content: 'none'}],
['table', {
'border-collapse': 'collapse',
'border-spacing': 0
}]
];
litc structure
A litc is an array containing three elements:
- Selector
- Attributes
- Contents
The selector is merely a string and it is required. The other two elements are optional.
litc attributes
The attributes element is either undefined
or an object where every key is a CSS attribute and its values are either a number, a string or undefined. For example:
['a', {
color: 'lime',
'font-weight': 'bold'
}]
will generate the following CSS:
a {
color: lime;
font-weight: bold;
}
Actually, the generated CSS would be the above but eliminating all non-semantic whitespace: a{color:lime;font-weight:bold;}
.
Notice that in the litc above, we surrounded the font-weight
key with quotes. This is because it contains a dash, and hence you need to explicitly surround it by simple quotes, otherwise you would get a syntax error from the javascript parser. Every CSS property that contains dashes, colons, or other non-alphanumeric characters must be surrounded by quotes.
If the attributes object is undefined
, we consider the litc to have zero properties. In this case, an empty string will be generated.
['a']
// An empty string will result.
If an attribute value is set to undefined
, null
, false
or an empty string, the attribute will be ignored. For example:
['a', {'font-weight': isHeader ? 'bold' : undefined}]
will yield either a CSS rule or an empty string, depending on whether isHeader
is truthy or not:
a {font-weight: bold}
// or an empty string
If an attribute value is set to an integer, it will be considered as a pixel unit, hence the suffix px
will be added to it. This feature is added because I found out that most of the time where I used integer units, they were pixels.
['a', {height: 20}]
a {
height: 20px;
}
In the case where you actually want an actual number, without px
as the attribute value, you need to stringify it.
['a', {opacity: '1'}]
a {
opacity: 1;
}
There's a very important exception: if you use the number 1
, it will be interpreted as 100%
instead of as 1px
- because the former is much more prevalent than the latter.
['div', {width: 1}]
div {
width: 100%;
}
If you use a number that's not an integer, it will be multiplied by 100 and a %
will be appended.
['a', {width: .50}]
a {
width: 50%;
}
Because Javascript has no true distinction of floats vs integers, if you want to specify a percentage which is a multiple of 100 and larger than 100%
, like 200%
or 300%
, you will need to write it as a string, otherwise it will be interpreted as a pixel unit.
['a', {width: '200%', height: 2.0}]
a {
width: 200%;
height: 2px;
}
Of course, if you think that it is better making a percentage or pixel measure explicit, you can also do:
['a', {width: '50%', 'font-size': '22px'}]
a {
width: 50%;
font-size: 22px;
}
You can use nested attribute objects to reuse CSS properties - this pattern is usually named mixin. Let's see an example:
var fontProperties = {
'font-weight': 'bold',
'font-family': 'Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif'
}
var litc1 = ['a', {
color: 'lime',
fontProperties: fontProperties,
}];
var litc2 = ['p', {
color: 'gray',
fontProperties: fontProperties
}];
The comprehensive font-family
property above was taken from this great resource.
When we convert litc1
and litc2
to CSS, the result will be:
a {
color: lime;
font-weight: bold;
font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif;
}
p {
color: gray;
font-weight: bold;
font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif;
}
The purpose of mixins is to avoid repetition of common groups of CSS properties.
Notice that in the litc mixins, we ignore the keys of objects that have another object as its value. The element fontProperties: fontProperties
could have been written as foobar: fontProperties
and the output would have been just the same. However, it's probably a good idea to give a descriptive name to this key, for code reading purposes.
Nested mixins are also possible:
var mixin1 = {
'font-family': 'Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif'
}
var mixin2 = {
mixin1: mixin1,
'font-weight': 'bold'
}
var litc1 = ['a', {mixin2: mixin2, color: 'lime'}];
When converted to CSS, litc1
will yield the following CSS:
a {
font-family: Garamond, Baskerville, "Baskerville Old Face", "Hoefler Text", "Times New Roman", serif;
font-weight: bold;
color: lime;
}
The last thing we have to say about litc attributes is that, since litcs are javascript, you can do math within the attribute values:
['a', {
width: (960 * 0.40 / 2)
}]
a {
width: 192px;
}
litc contents
The contents of a litc can be either a litc or a litcbag. A litcbag is an array that contains litcs or litcbags. Notice that a litcbag is almost like a lithbag, only simpler, because it cannot contain simple elements (such as strings or numbers).
Unlike HTML, CSS has no nested elements. However, litcs can be nested. The reason for this is to provide a shorthand for nesting CSS selectors. Let's see an example:
div.links {
width: 100px;
}
div.links a {
font-size: 14px;
}
Notice that the css above contains two selectors. The first is 'div.links'
and the second is 'div.links a'
. The second selector will only affect <a>
tags that are within <div class="links">
.
To express more succintly this pattern of nested selectors (the actual name is descendant combinators), we can write a litc as the contents of another litc.
['div.links', {width: 100}, ['a', {'font-size': 14}]];
This litc will generate the CSS above.
Notice that you can place multiple litcs within the contents of another litc. For example:
['div.links', {width: 100}, [
['a', {'font-size': 14}],
['p', {color: 'red'}]
]];
which will generate:
div.links {
width: 100px;
}
div.links a {
font-size: 14px;
}
div.links p {
color: red;
}
Nested liths also allow to reference the parent selector by using the ampersand (&
). Like nested selectors, this feature was taken from SASS. Let's see an example:
div.links {
width: 100px;
}
div.links a {
font-size: 14px;
}
div.links a:hover {
color: red;
}
We can generate the same CSS with the following litc:
['div.links', {width: 100}, [
['a', {'font-size': 14}, ['&:hover', {color: 'red'}]]
]];
Notice how &
is replaced by div.links a
, which is the combined selector of the parent elements of the innermost litc.
The ampersand can be a prefix, a suffix or even be in the middle of a selector. In every case, it will be replaced by the selector of its ancestors.
Multiple CSS selectors will be properly nested. For example, if you want to write:
h2 span, h3 span {
color: green;
}
You can do write it with the following litc:
['h2, h3', ['span', {color: 'green'}]]
This also works if you use commas in the nested selector:
div h2, div h3 {
color: green;
}
['div', ['h2, h3', {color: 'green'}]]
You can also use the ampersand and nest multiple selectors as deeply as you want.
div h2:hover, div h3:hover {
color: green;
}
['div', ['h2, h3', ['&:hover', {color: 'green'}]]]
Litc usage
litcs are generated using two core functions:
-
lith.css.g
: this function generates CSS from a litc. -
lith.css.v
: a helper function that validates a litc.
The input to both functions is either a litc or a litcbag. In either case, the input can only be a single array.
You don't need to invoke lith.css.v
, since lith.css.g
validates its own input.
If the input to lith is invalid, false
is returned. Otherwise, you get a string with CSS.
If the input is invalid, lith will print an error through teishi.
If the input to lith.g
contains anywhere a lith of the following form: ['style', ['div.canvas', {color: 'blue'}]]
(where the second element is an array and presumably a litc), lith.g
will automatically invoke lith.css.g
on the litc. The example above, when passed to lith.g
, will generate '<style>div.canvas{color:blue;}</style>
. If the contents are an array that is not a valid litc, the entire input will be considered invalid.
As with lith.g
, if you pass true
as a second argument to lith.css.g
, prod mode
will be enabled and no validations will be performed. This will also happen if you set lith.prod
to true
.
If you want to check whether an input is valid litc without having the error be printed to the console, you can invoke lith.css.v
passing true
as its second argument, in which case an object with the error will be returned.
litc helper functions
In this section we define two helper functions, lith.css.media
and lith.css.style
.
Writing media queries with litcs is not possible - at least not directly. For this purpose, you can use lith.css.media
, which will transform your media query into a valid litc.
For example, if you want to write the following media query in the context of a litc:
@media (max-width: 600px) {
.sidebar {
display: none;
}
}
You can create it with:
var litc = [
lith.css.media ('(max-width: 600px)', ['.sidebar', {display: 'none'}]),
// rest of your litc goes here
]
lith.css.media
takes two arguments: a selector
, which is the media query selector. Notice that you should omit the @media
part, since the function automatically adds it for you. The second argument is a litc (simple or nested), which will be inserted inside the media query. If you don't pass a valid selector
, the function will return false
. The litc is not validated here since it will be validated by lith.css.g
later.
If you passed valid arguments to lith.css.media
, the output will always be a litc, which you can use standalone or nest within another one.
The second helper, lith.css.style
, is helpful if you want to generate a CSS string to directly place inside the style
attribute of a lith. This function takes an object with litc attributes and returns either false
(if the attributes are invalid) or a string with CSS which can be used as the style
attribute of an element. Let's see some examples:
// This invocation:
{style: lith.css.style ({color: 'red', margin: 'solid 1px white'})}
// will generate this `attributes` object:
{style: 'color:red;margin:solid 1px white;'}
// This invocation:
lith.css.style ({'height, width': 1});
// will generate this `attributes` object:
{style: 'height:100%;width:100%;'}
If you pass a true
second argument to this function, it will generate the CSS string in prod mode
.
Source code
The complete source code is contained in lith.js
. It is about 270 lines long.
Below is the annotated source.
/*
lith - v6.0.7
Written by Federico Pereiro (fpereiro@gmail.com) and released into the public domain.
Please refer to readme.md to read the annotated source.
*/
Setup
We wrap the entire file in a self-executing anonymous function. This practice is commonly named the javascript module pattern. The purpose of it is to wrap our code in a closure and hence avoid making the local variables we define here to be available outside of this module. A cursory test indicates that local variables exceed the scope of a script in the browser, but not in node.js. This means that this pattern is useful only on the browser.
(function () {
Since this file must run both in the browser and in node.js, we define a variable isNode
to check where we are. The exports
object only exists in node.js.
var isNode = typeof exports === 'object';
We require dale and teishi. Note that, in the browser, dale
and teishi
will be loaded as global variables.
var dale = isNode ? require ('dale') : window.dale;
var teishi = isNode ? require ('teishi') : window.teishi;
This is the most succinct form I found to export an object containing all the public members (functions and constants) of a javascript module. Note that, in the browser, we use the global variable lith
to export the library.
if (isNode) var lith = exports;
else var lith = window.lith = {};
We create an alias to teishi.type
, the function for finding out the type of an element. We do the same for teishi.clog
, a function for printing logs that also returns false
. We also do the same for teishi.inc
, a function for checking whether a given element is contained in an array.
var type = teishi.type, clog = teishi.clog, inc = teishi.inc;
Constants
We define an object lith.k
to hold some constants.
lith.k = {
lith.k.lithbagElements
will hold the possible types of a lithbag element.
lithbagElements: ['string', 'integer', 'float', 'array', 'undefined'],
lith.k.tags
contains every valid HTML5 tag. Interestingly enough, there are 108 tags.
Although '!DOCTYPE HTML'
is a declaration and not a tag, we add it to the list of tags anyway, so that we can also generate the doctype with lith. We will also add 'LITERAL'
, which is a pseudo-tag useful for inserting chunks of raw HTML into a lith.
tags: ['!DOCTYPE HTML', 'LITERAL', 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr'],
lith.k.voidTags
contains the list of tags that do not need to be closed, also known as self-closing tags. The term "void" comes from the W3C specification.
Notice we also add '!DOCTYPE HTML'
to this list.
voidTags: ['!DOCTYPE HTML', 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']
}
Below there's an if
block that ensures that all the void tags are contained in lith.k.tags
. If the check is not passed, the rest of lith will not be defined.
Every time I modify the tag constants, I run this code to ensure that there are no tag inconsistencies. However, in production this code is commented out, to save you a few milliseconds. To check that the voidTags
and the tags
are consistent, please uncomment the code and run it for yourself. Note we pass true
as the fourth argument to the invocation of teishi.stop
, to prevent teishi from validating the rule - this will save some execution time.
/*
if (teishi.stop ([['HTML void tags', 'HTML tags'], lith.k.voidTags, lith.k.tags, 'eachOf', teishi.test.equal], undefined, true)) {
return false;
}
*/
Helper functions
lith.entityify
is a function that takes a string and escapes some characters with their corresponding HTML entities.
This function was originally taken from Douglas Crockford's entityify
and modified to replace quotes and backticks, using the approach in John-David Dalton's lodash.
Notice we validate the input but only if the second argument is falsy. When prod mode
is on, we avoid the validation to improve performance. Note that, if we perform the validation, we pass true
as the fourth argument to teishi.stop
to prevent teishi from validating the rule itself (we know the rule to be correct already). The undefined
as third argument is there simply to allow us to skip that argument and pass the fourth one instead.
lith.entityify = function (string, prod) {
if (! prod && teishi.stop ('lith.entityify', ['Entityified string', string, 'string'], undefined, true)) return false;
return string
.replace (/&/g, '&')
.replace (/</g, '<')
.replace (/>/g, '>')
.replace (/"/g, '"')
.replace (/'/g, ''')
.replace (/`/g, '`');
}
Lith validation
We will now proceed to handle the validation of liths.
lith.v
is the main validation function for liths. It takes an input
, presumably a lith. This function will return Lith
if it found a lith, Lithbag
if it found a lithbag, and false
if the input is neither.
The function also takes a second argument, returnError
, that can be used to make lith.v
return an error if input
is invalid.
lith.v = function (input, returnError) {
We first note the type of the input and store it at inputType
.
var inputType = type (input);
If input
is an array and its first element is a string which also happens to be a valid HTML tag, we will consider input
to be a lith! In previous versions of lith, you could write lithbags that started with a valid HTML tag, but it was seldom useful. By explicitly prohibiting it, we can very quickly determine whether input
is a lith or a lithbag. This also will help to implement a fast prod mode
when we define lith.g
later.
if (inputType === 'array' && inc (lith.k.tags, input [0])) {
We define attributes
and contents
. attributes
must either be an object or invalid. contents
will be the second or third element of the lith
, depending on whether we found attributes
or not.
var attributes = type (input [1]) === 'object' ? input [1] : undefined;
var contents = input [attributes ? 2 : 1];
We start validating the lith in earnest and store the result in a variable result
.
var result = teishi.v ([
A lith has a length of 1 to 3 elements.
['lith length', input.length, {min: 1, max: 3}, teishi.test.range],
If, however, the lith has no attributes, its length can be at most 2, since it will only have a tag and contents.
[attributes === undefined, ['length of lith without attributes', input.length, {max: 2}, teishi.test.range]],
We already know that attributes
is either undefined or an object, because we ensured that when we defined the variable a few lines above. We now proceed to validate its keys.
Every attribute key must start with a ASCII letter, underscore or colon, and must follow with zero or more of the following:
- A letter.
- An underscore.
- A colon.
- A digit.
- A period.
- A dash.
- Any Unicode character with a code point of 129 (
0080
in hexadecimal) or above - these include all extended ASCII characters (the top half of the set) and every non-ASCII character.
This is the abstruse rule I talked about earlier in the readme. This arcana was kindly provided by this article. The regex below was taken from the article and modified to add the permitted Unicode characters.
[
['lith attribute keys', 'start with an ASCII letter, underscore or colon, and be followed by letters, digits, underscores, colons, periods, dashes, extended ASCII characters, or any non-ASCII characters.'],
dale.keys (attributes),
/^[a-zA-Z_:][a-zA-Z_:0-9.\-\u0080-\uffff]*$/,
'each', teishi.test.match
],
Attribute values can be strings, numbers (integers and floats). We also accept undefined
, null
and false
as falsy values that invalidate the property, so we will also accept undefined
, null
or boolean
.
['lith attribute values', attributes, ['string', 'integer', 'float', 'undefined', 'null', 'boolean'], 'eachOf'],
Contents can be any lithbag element: string, integer, float, array and undefined. However, if we're dealing with a LITERAL
pseudo-tag, the contents must be a string.
input [0] === 'LITERAL' ? ['lith LITERAL contents', contents, 'string'] : ['lith contents', contents, lith.k.lithbagElements, 'oneOf']
If returnError
is set, we pass true
as an apres
argument to teishi.v
, so that if there's an error, the error itself is returned instead of being printed. Otherwise, we pass an apres
function that will print an informative error. Note we print both the error and the original input, which provides more context. Note also we pass true
as the last argument to teishi.v
, to avoid validating the rules that we pass to the function and hence improve performance. We have done this with all invocations to teishi.v
and teishi.stop
, and we will keep on doing it without further comment on it.
], returnError ? true : function (error) {
clog ('lith.v - Invalid lith', {error: error, 'original input': input});
}, true);
If result
is true, we return the string 'Lith'
. Otherwise, we return either false
(if returnError
is not set) or an error message. Note we return the same error object that we printed in the apres
function.
After this, we also close the conditional that deals with the case of a lith.
return result === true ? 'Lith' : (returnError ? {error: result, 'original input': input} : false);
}
If we're here, we can only be dealing with a lithbag (or an invalid input). We proceed to check whether input
is a valid lithbag and store the result in a variable result
.
var result = teishi.v ([
We check that input
has a type matching those of a valid lithbag element.
['lithbag', inputType, lith.k.lithbagElements, 'oneOf', teishi.test.equal],
[inputType === 'array', ['lithbag elements', input, lith.k.lithbagElements, 'eachOf']]
As with the case of a lith, we pass a true
apres parameter in case returnError
is enabled. Otherwise, if case a validation error was found, we will print an informative error. Note we print both the error and the original input, which provides more context.
], returnError ? true : function (error) {
clog ('lith.v - Invalid lithbag', {error: error, 'original input': input});
}, true);
If the validation was successful, we return the string 'Lithbag'
. Otherwise, if returnError
is enabled, we return an error object, or false
otherwise. After this, there's nothing else to do, so we close the function.
}) ? 'Lithbag' : false;
return result === true ? 'Lithbag' : (returnError ? {error: result, 'original input': input} : false);
}
Lith generation
We now define lith.g
, the main function of the library. This library takes an input
(a lith or lithbag) and returns a string with the corresponding HTML. As a second optional argument, it takes an optional boolean flag, prod
, to enable prod mode
.
lith.g = function (input, prod) {
prod mode
is an option that makes lith.g
to not validate its input. If either lith.prod
is set to a truthy value (or a truthy value is passed as the second argument to lith.g
, we will consider that prod mode
is enabled.
if (prod || lith.prod) {
We check that if either of prod
or lith.prod
are truthy, they are indeed true
. We want to avoid a common usage error where multiple parameters are passed to lith.g
in the hope of generating all of them - lith.g
only accepts one input
, and multiple parameters must be wrapped in an array. With this check, we prevent this error, which is compounded by the fact that validation is unwittingly turned off if a truthy second argument is passed.
if ((prod || lith.prod) !== true) return clog ('lith.g', 'prod or lith.prod must be true or undefined.');
If we're here, bring on the prod mode
. This means that we will assume that input
is either a valid lith or a valid lithbag. We quickly determine whether input
is a lith or not. For this we check that input
is indeed an array with a valid HTML tag as its first element.
if (type (input) === 'array' && inc (lith.k.tags, input [0])) {
If we're here, input
is a lith. We return an invocation to lith.generateLith
, passing it both input
and true
. The reason we pass a second argument is that because lith.generateLith
can invoke lith.g
, we need to preserve the prod mode
flag in recursive calls.
return lith.generateLith (input, true);
}
If we're here, input
is a lithbag. We invoke lith.generateLithbag
. The second argument of the invocation is false
; we'll explain why below.
return lith.generateLithbag (input, false, true);
}
If we're here, we need to validate our input. We do so and store the result in a variable inputType
.
var inputType = lith.v (input);
Now, because of how we defined lith.v
, inputType
can only be false
, 'Lith'
or 'Lithbag'
. If it is false
, we will want to return false
, otherwise we will want to invoke the appropriate function (lith.generateLith
or lith.generateLithbag
).
return inputType ? lith ['generate' + inputType] (input) : false;
At this point we're done, so we close the function.
}
lith.generateLithbag
takes three arguments: lithbag
, dontEntityify
and prod
(the prod mode
flag). We will explain the second argument below.
lith.generateLithbag = function (lithbag, dontEntityify, prod) {
We initialize a local variable output
to an empty string, on which we will concatenate the output of the function.
var output = '';
We will now iterate through the elements of lithbag
.
Notice that if lithbag
is undefined
, the entire function below will not be executed and output
will remain being equal to an empty string.
We use dale.stop
and pass false
as the second argument because we want to detect invalid lithbag elements and stop if one is found. If an invalid element is found, the inner function passed as third argument to dale.stop
will return false
. Other return values will be ignored, because the valid outputs will be concatenated onto the output
string.
if (dale.stop (lithbag, false, function (v) {
If v
is undefined
, we return undefined
.
if (v === undefined) return;
We now deal with simple values that are not undefined
(string, integer and float).
Depending on whether dontEntityify
is truthy or not, we entityify the element. The reason for the existence of this option is that the contents of <style>
and <script>
tags should not be entityified, otherwise the inline CSS or javascript would be broken. So, the only case where dontEntityify
is truthy is when lith.generateLith
detects a script
or style
tag, and hence invokes lith.generateLithbag
with a truthy second argument. In every other case where lith.generateLithbag
is invoked, this argument should remain false
(which also explains why we set it as false
in lith.g
above.
Notice that we coerce v
into a string before entityifying it, because it may be a number and lith.entityify
only accepts strings.
if (type (v) !== 'array') return output += (dontEntityify ? v : lith.entityify (v + '', prod));
If we're here, the lithbag is an array. we will do a recursive call to lith.g
.
We will call lith.g
and store the value of this recursive call into a local variable recursiveOutput
. Note we pass prod
as a parameter.
If recursiveOutput
is false
, we will return false
from this inner function, which will in turn also make lith.generateLithbag
and lith.g
return false
. The relevant error message will already have been printed by the recursive call to lith.g
.
var recursiveOutput = lith.g (v, prod);
if (recursiveOutput === false) return false;
If the recursiveOutput
is valid, it will be a string. We will concatenate it to the output
.
output += recursiveOutput;
If the call to dale.stop
returned false
, we found an invalid (array) lithbag, so we return false
.
}) === false) return false;
If we are here, no errors were found, which means that lithbag
was valid. We return output
and close the function.
return output;
}
lith.generateLith
takes an input
, a valid lith. The reason I didn't name it lith
is that I don't want the function to lose the reference to the lith
object.
lith.generateLith = function (input, prod) {
Let's remember that if this function is called, input
is has to be a valid lith
(either because it was validated by lith.g
or because we're in prod mode
and assume that all inputs are valid).
We now initialize two variables, attributes
and contents
. attributes
will be the second element of input
if and only if it is of type object
. Since contents
can never be an object, there's no source of ambiguity here. And contents
will be the argument immediately after atttributes
, which means either the second (where no attributes
are present) or the third one (when attributes
are present).
var attributes = type (input [1]) === 'object' ? input [1] : undefined;
var contents = input [attributes ? 2 : 1];
If the tag is 'LITERAL'
, we will just return the contents of the lith without any further modifications. Notice that we discard the attributes
in this case since 'LITERAL'
is merely a pseudo-tag.
if (input [0] === 'LITERAL') return contents;
We create a local variable output
and place in it an opening angle bracket <
, plus the tag.
var output = '<' + input [0];
We iterate the attributes of the lith. If attributes
is undefined
, the inner function passed to dale.go
will not be executed.
dale.go (attributes, function (v, k) {
We will discard all attributes that have a falsy value (empty string, null
, undefined
and false
), with the exception of 0
, which can be a perfectly valid attribute value.
For every attribute which has a proper value, we will concatenate it onto output
.
Mind that we entityify both the key and the value. Also mind that we use double quotes for enclosing the value. Finally, notice that we coerce k
and v
into strings before passing them to lith.entityify
.
if (v || v === 0) output += ' ' + lith.entityify (k + '', prod) + '="' + lith.entityify (v + '', prod) + '"';
});
We close the opening tag, whether or not we added attributes.
output += '>';
If the contents of the lith are an array (which means that it must be either a lith or a lithbag):
if (type (contents) === 'array') {
We will apply either lith.g
or lith.css.g
to contents
. We'll invoke lith.css.g
only if the tag is style
(otherwise we will consider contents
to be either a lith or lithbag). We'll store its result in a variable recursiveOutput
. Note we pass prod
as a parameter to both functions.
var recursiveOutput = input [0] === 'style' ? lith.css.g (contents, prod) : lith.g (contents, prod);
If the call returns false
, we return false
as well - the output generated so far will be ignored, because the lith is invalid.
Otherwise, we concatenate the result of the call onto output
.
if (recursiveOutput === false) return false;
output += recursiveOutput;
}
If the contents are a string, integer, float or undefined
(the remaining possibilities), we invoke lith.generateLithbag
. This function already has the logic for processing the elements of a lithbag.
Notice that if the tag of the lith we are processing is <style>
or <script>
, we set the dontEntityify
argument to true
, since we don't want to escape HTML entities if we're generating inline CSS or javascript.
Also, there's no error checking here, since if any of these possible elements is passed to the function, no error is possible.
else output += lith.generateLithbag (contents, ((input [0] === 'style' || input [0] === 'script') ? true : false), prod);
We place the closing tag if the element is not a void one.
if (! inc (lith.k.voidTags, input [0])) output += '</' + input [0] + '>';
There's nothing else to do but to return output
and close the function.
return output;
}
CSS
We create a lith.css
object to hold the logic for CSS generation.
lith.css = {};
Litc validation
lith.css.v
is analogue to lith.v
: it takes an input
, and determines whether it is a valid litc or litcbag. It also takes an optional returnError
flag.
Unlike lith.v
, lith.css.v
will hold all the validation logic in itself, instead of relying on helper functions. We do this because of litcs are considerably simpler than liths.
lith.css.v = function (input, returnError) {
Either litcs
or litcbags
have to be arrays. We validate this and store the result into a variable result
. Note we print an error message in case of error, but only if returnError
is not set.
var result = teishi.v (['litc or litcbag', input, 'array'], returnError ? true : function (error) {
clog ('lith.css.v - Invalid litc or litcbag', {error: error, 'original input': input});
}, true);
If the validation failed, we return either false
(the default action) or an object with the error (if returnError
is truthy).
if (result !== true) return returnError ? {error: result, 'original input': input} : false;
If the array has length zero, we consider it an empty litcbag, so we return true
.
If the first element of the input
is also an array, input
can only be a litcbag, not a litc, since the first element of a litc is a string. Actually, this could be an invalid element, but we'll leave that check to further recursive calls, instead of doing a deep validation on the spot. In any case, we now return true
.
if (input.length === 0 || type (input [0]) === 'array') return true;
If we're here, we are then dealing with a purported litc.
We define attributes
and contents
. attributes
must either be an object or invalid. contents
will be the second or third element of the litc, depending on whether we found attributes
or not.
var attributes = type (input [1]) === 'object' ? input [1] : undefined;
var contents = input [attributes ? 2 : 1];
We now check that input
should fulfill the requirements for being a valid litc. First of all, it should have length between 1 and 3.
result = teishi.v ([
['litc length', input.length, {min: 1, max: 3}, teishi.test.range],
If, however, the litc has no attributes, its length can be at most 2, since it will only have a selector and contents.
[attributes === undefined, ['length of litc without attributes', input.length, {max: 2}, teishi.test.range]],
We ensure that the selector is a string, we validate the attributes with a helper function lith.css.vAttributes
, and we check that the contents are either undefined
or an array (with the exception of the LITERAL
pseudo-selector, in which case they must be a string).
['litc selector', input [0], 'string'],
lith.css.vAttributes (attributes),
input [0] === 'LITERAL' ? ['litc LITERAL contents', contents, 'string'] : ['litc contents', contents, ['undefined', 'array'], 'oneOf']
In case of error, we either print a message (if returnError
is not enabled) or collect the error on the variable result
(if returnError
is enabled).
], returnError ? true : function (error) {
clog ('lith.css.v - Invalid litc', {error: error, 'original input': input});
}, true);
If result
is true
, input
is valid and we return true
. If the input was invalid, we return either false
or an object containing the error. We then close the teishi.v
call and the function, since there's nothing else to do.
return result === true ? result : (returnError ? {error: result, 'original input': input} : false);
}
lith.css.vAttributes
(short for validateAttributes
) consists of a simple call to teishi.v
, which value we will return. We separate this function from the one above because we will reuse it later.
lith.css.vAttributes = function (attributes) {
return teishi.v ([
We ensure that each of the elements within attributes
are strings, integers, floats, objects or falsy values (undefined
, null
and false
). If attributes
is undefined
, this will be true because an undefined
value in the context of eachOf
is equivalent to an empty array.
We then close the call and the function.
['litc attribute values', attributes, ['string', 'integer', 'float', 'object', 'undefined', 'null', 'boolean'], 'eachOf']
], undefined, true);
}
Litc generation
lith.css.g
is analogue to lith.g
: it takes an input
, presumably a litc or litcbag, and returns either false
or a string with CSS. This function also takes a prod
argument to indicate prod mode
.
This function also takes a "private" argument selector
, used in recursive calls.
lith.css.g = function (input, prod, selector) {
prod mode
is an option that makes lith.css.g
not validate its input. If either lith.prod
is set to a truthy value (or a truthy value is passed as the second argument to lith.css.g
, we will consider that prod mode
is enabled.
if (prod || lith.prod) {
We check that if either of prod
or lith.prod
are truthy, they are indeed true
. As with lith.g
, we want to avoid a common usage error where multiple parameters are passed to lith.css.g
in the hope of generating all of them - lith.css.g
only accepts one input
, and multiple parameters must be wrapped in an array. With this check, we prevent this error, which is compounded by the fact that validation is unwittingly turned off if a truthy second argument is passed.
if ((prod || lith.prod) !== true) return clog ('lith.css.g', 'prod or lith.prod must be true or undefined.');
We set prod
to true
, in case we're in this part of the conditional because of lith.prod
being true
.
prod = true;
}
If prod
is not enabled, we invoke lith.css.v
. If it returns false
, the input
is invalid, so we just return false
.
if (! prod && lith.css.v (input) === false) return false;
If input
is an empty array, we consider it to be an empty litcbag. We return an empty string.
if (input.length === 0) return '';
If the first element of input
is LITERAL
, we consider the second element of the input to be a string, so we return it.
if (input [0] === 'LITERAL') return input [1];
We define a local variable output
where we will concatenate the output of the function.
var output = '';
If the first element of input
is also an array, it can only be a litcbag, because the first element of a litc is a selector, which is a string.
if (type (input [0]) === 'array') {
We iterate through the elements of the litcbag. If any of the results of the inner function is false
, the iteration will be stopped.
if (dale.stop (input, false, function (v, k) {
We invoke lith.css.g
recursively, passing the element (v
) and selector
.
If recursiveOutput
is false
(because v
was neither a valid litc or litcbag), we return false
.
If recursiveOutput
is not false
, we concatenate it to output
.
var recursiveOutput = lith.css.g (v, prod, selector);
if (recursiveOutput === false) return false;
output += recursiveOutput;
If the iteration returned false
, it's because we found an error, so we return false
.
}) === false) return false;
If the iteration did not return false
, we return the output
.
return output;
}
selector
is a string that holds the selectors of litcs that contain the previous litc. For example, if you have a litc a
and its contents are a litc b
, when lith.css.g
receives b
, selector
will be equal to the selector of a
.
As we saw above, the purpose of selector
is to allow us to express CSS descendant combinators.
On any call to lith.css.g
, selector
will contain a string with all the selectors of the ancestors, separated by a space.
If the litc currently being processed is not contained in any other litc, the selector will be undefined
. In this case, we initialize it to an empty string.
if (selector === undefined) selector = '';
We define attributes
and contents
. attributes
must either be an object or invalid. contents
will be the second or third element of the litc, depending on whether we found attributes
or not.
var attributes = type (input [1]) === 'object' ? input [1] : undefined;
var contents = input [attributes ? 2 : 1];
We now will generate the proper selector. To do this, we first split the selector by a comma plus whitespace. If there's no commas in the selector, this will be irrelevant, but if they are, doing this will allow us to write the following CSS:
h2 span, h3 span {
color: green;
}
With the following litc:
['h2, h3', ['span', {color: 'green'}]]
We will set the selector
to the result of this iteration on the parts of the selector.
selector = dale.go (selector.split (/,\s*/), function (v) {
We will also split input [0]
by a comma plus whitespace, the selector of the current litc and iterate them.
return dale.go (input [0].split (/,\s*/), function (v2) {
If there is an ampersand in the selector of the current litc, we make selector
equal to the current litc, and replacing the ampersand with the original selector. For example:
original selector: 'a'
selector of current litc: '&:hover'
new value of selector: 'a:hover'
if (v2.match (/&/)) return v2.replace ('&', v);
If there's no ampersand in the selector of the current litc, we make selector
equal to a string that concatenates the old selector and the new one, separated by a space.
original selector: 'a'
selector of current litc: 'span'
new value of selector: 'a span'
.
If selector
is an empty string, the new selector will be concatenated without a space.
original selector: ''
selector of current litc: 'a'
new value of selector: 'a'
.
else return v + (v.length === 0 ? '' : ' ') + v2;
We join the results of the inner loop and those of the outer loop to obtain the final selector.
}).join (', ');
}).join (', ');
We concatenate to output
the new selector plus an opening curly brace (inside which we'll place the attributes).
output += selector + '{';
The attributes of the litc can be either undefined
or an object. If the attributes are an object, it can have attributes of three kinds:
- Literal attributes, such as
color: 'lime'
or'font-size': '14px'
. What makes a key literal is that its value is a string or a number. - Nested attributes, such as
{color: 'lime', 'font-size': '14px'}
. What makes a key nested is that its value is an object which contains further attributes. - Invalid attributes, such as
color: /lime/
. What makes a key invalid is that its value is neither a string, number or object.
Since attributes can be arbitrarily nested, we need a function (instead of a loop) to process nested attributes recursively. For that purpose, we will define an inner function addAttributes
.
addAttributes
takes a single argument attributes
. It will return either false
(if it finds an error) or undefined
(if the input is valid). All output is done by side-effect, because we concatenate it to the outer variable output
.
var addAttributes = function (attributes) {
We first validate the attributes using lith.css.vAttributes
. This invocation is the reason for splitting lith.css.vAttributes
into its own function.
If attributes
is invalid, we return false
.
if (lith.css.vAttributes (attributes) === false) return false;
We are going to iterate attributes
. If the inner function returns false
, we will stop the iteration and return false
. Otherwise, we'll proceed to the last attributes
and return undefined
.
return dale.stop (attributes, false, function (v, k) {
If the attribute value is falsy, we ignore it (with the exception of 0
, which is falsy but a proper value for an attribute).
if (! v && v !== 0) return;
We note the type of v
.
var typeV = type (v);
If the attribute being iterated is an object, we invoke addAttributes
recursively and return whatever this function call returns. The recursive invocation will handle adding the nested attributes to output
.
if (type (v) === 'object') return addAttributes (v);
If the attribute is an integer, by convention we consider it to be a measure in pixels. Hence, we append px
to the number.
However, we don't do this for a 0
value since this is optional - plus, I prefer to omit it, since it looks cleaner. And we don't do it for 1
, since we consider that to represent 100%
.
if (typeV === 'integer' && (v < 0 || v > 1)) v += 'px';
If the attribute is a float (using the very lax definition that a float is something that leaves a remainder after dividing it by one), we consider it to be a percentage measure. Hence, we multiply it by 100 and add a %
after it.
if (type (v) === 'float') v = (v * 100) + '%';
If the (valid) attribute being iterated is not an object, it must be either a string or a number. This means that this is a terminal value, so we want to add its key and its value to output
, placing a colon between the key and the value and a trailing semicolon.
However, multiple properties may be contained in the same key. For example, if the key is padding-top, padding-bottom
, and the value is 0
, we want to generate two css properties, one for each type of padding, both set to the same value. The idea behind this is to take the comma notation of selectors and extrapolate it to properties.
Hence, we'll take the key, split it by a comma (plus optional trailing spaces), and then iterate those properties and add them to output
. When no commas are present, this will work anyway.
dale.go (k.split (/,\s*/), function (v2) {
output += v2 + ':' + v + ';';
});
});
}
We invoke addAttributes
passing to it the attributes of the litc. If it returns false
, we return false
.
if (addAttributes (attributes) === false) return false;
We check to see if any attributes were in fact added. If no attributes were added (and output
is still the selector plus an opening brace), we set output to an empty string. Otherwise, we place the closing brace since we're finished placing attributes.
if (output === selector + '{') output = '';
else output += '}';
If the litc has contents, we process them.
if (contents) {
We make a recursive call to lith.css.g
, passing the contents of the litc and the selector. We store that value in a local variable recursiveOutput
.
var recursiveOutput = lith.css.g (contents, prod, selector);
If the recursive call returned false
, the contents of the litc are invalid, so the whole litc is invalid. We return false
.
if (recursiveOutput === false) return false;
If the recursive call returned valid output, we concatenate it to the output
.
output += recursiveOutput;
}
We return output
and close the function.
return output;
}
Litc helpers
We define lith.css.media
, which is a helper function for creating media queries. The reason we need a special function is because media queries, unlike normal CSS, rely on nested blocks.
This function takes a selector and a litc. We validate the selector, which will comprise the core of the media query (namely, the rest of the selector after '@media'
. We will not validate the litc, since we assume that lith.css.g
will validate it later in the context of the entire litc being generated. Note that the function will return false
if selector
is not a string.
lith.css.media = function (selector, litc) {
if (teishi.stop (['selector', selector, 'string'], undefined, true)) return false;
The trick to generate the nested block is to use the LITERAL
pseudo-selector to generate the opening and the closing parts of the media query. The closing part is merely a closing curly brace. Surrounded by these two literals, we pass the litc unchanged. This will have the desired effect without modifying the logic of lith.css.g.
Note that we add '@media'
at the beginning of the selector. Note also that the output of this function will be a litc.
return [['LITERAL', '@media ' + selector + ' {'], litc, ['LITERAL', '}']];
}
We now define lith.css.style
, a helper function that is useful to generate CSS that can be placed within the style
attribute of an element.
This function takes an attributes
argument and an optional prod
flag.
lith.css.style = function (attributes, prod) {
We take attributes
and pass it to lith.css.g
, along with an empty selector. We also pass the prod
flag, in case its truthy (this will enable prod mode
for the generation of the resulting litc. We store the result of this invocation in a local variable result
.
var result = lith.css.g (['', attributes], prod);
If result
is false
, it means that attributes
was invalid, in which case we also return false
. Otherwise, we take the result
(which is a CSS string), remove its first and last characters (the opening and closing brace) and return it. We close the function.
return result === false ? result : result.slice (1, -1);
}
We close the module.
}) ();
License
lith is written by Federico Pereiro (fpereiro@gmail.com) and released into the public domain.