Let's admit it, HTML and Javascript really suck for making applications. They have separate purposes (UI and logic) with no real glue to bind them. Enter jQuery, Backbone, Ember, Angular, React, the list goes on and on. All of these improve the glue, but still rely on some complicated mapping model between the HTML and Javascript.
With NOML there is no HTML (or JSX, or Xml-ish-ness). NOML renders javascript objects into html. This technique allows for dynamic content to be rendered on the fly, modified, and bound with logic, without ever touching HTML. The entire application is written in literal, readable, unconverted Javascript. The glue is solved (chemistry pun intended).
see working examples at https://github.com/adamduffy/noml-examples
results when noml.render() is called on the object.
ui.span('hello world!');
<span>hello world!</span>
ui.div(
ui.span('span 1'),
ui.span('span 2')
)
<div><span>span 1</span><span>span 2</span></div>
NOML Element classes have a prop
method.
ui.span('welcome to noml').prop({id: 'mySpanId'})
<span id="mySpanId">welcome to noml</span>
since this is javascript to begin with, variables work naturally
var data = {name: 'adam'};
ui.span('my name is ' + data.name)
<span>my name is adam</span>
ui.span('a classy span').class('myClass')
<span class='myClass'>a classy span</span>
the NOML Element class handles de-duping of class names.
ui.span('a very classy span')
.class('myClass1 myClass3')
.class('myClass1 myClass2')
.class('myClass3');
<span class='myClass1 myClass2 myClass3'>a very classy span</span>
Class names can be added, removed, or conditional, even after the initial render.
All props can be re-synced with the dom using .ref()
at creation, and .syncProps()
to update.
class
adds a classunclass
removes a classclassIf
adds the class if condition is true, removes if false.toggle
swaps the class based on condition.- all classname methods perform de-duping.
const span = ui.span('a re-classed span')
.class('myClass1 myClass2')
.classIf(true, 'conditionalClass')
.toggle(true, 'trueClass', 'falseClass')
.ref(); // .ref() tells noml to keep a reference.
// render the span
render(span);
// at this point, className='myClass1 myClass2 conditionalClass trueClass'
span.unclass('myClass2')
.classIf(false, 'conditionalClass')
.toggle(false, 'trueClass', 'falseClass')
.syncProps(); // this will re-sync with the dom.
// now, className='myClass1 falseClass'
<span id="_0" class='myClass1'>a re-classed span</span>
NOML will wire to DOM events during render. the Element class wraps a few common events
ui.span('click here').onClick(() => alert('hi!'));
or you can use any other event name
ui.span('mouse enter here').event({mouseenter: () => alert('mouse entered!')})
###components Noml will render modular ui elements as components that allow for special interaction such as independent re-rendering and promise resolution. to do this, expose a getBody() method.
TODO: make example
you can give the component your own id or noml will auto-assign one so it can reference it for re-rendering, etc.
if your code maintains a reference to this object, the ui can be refreshed later by calling c.render() or c.syncProps(). this technique is best for low-level components where you want immediate response. For most cases, just re-rendering the whole page is much easier to manage. (and should still be very snappy)
###Asynchronous component loading
to make loading, ready, and failure states, simply return a promise from getBody and expose a getLoadingBody() method. noml will re-render the ui when the promise is resolved or fails.
...
getBody() {
return Promise.resolve(span('this is a resolved promise'));
},
getLoadingBody() {
return span('loading');
}
...
'loading' will render until the promise is resolved.
<component id="_0"><span>this is a resolved promise</span></component>
###Reading input data
To keep the abstraction away from html, the best way to read user input is by mapping it back to your javascript data objects. You can do this with the change or other similar element events. Noml will wire up native dom element events if you begin a property with $. Another option is to give the element a unique id and find it later.
TODO: make example
##Styles
Noml also uses javascript to define CSS (optional). The general format is as follows:
{
id: {
class: {
property: value,
state: {
property: value, ...
}, ...
}, ...
}, ...
}
Noml uses c.id and c.getStyle() to maintain the composite css object. Class names can be prepended with $ in place of . when desired (other selectors work in place of class name). Included examples show techniques to define and override default styles.
getDefaultStyle() {
return {
$clickable: {
cursor: 'pointer',
':hover': {
'text-decoration': 'underline'
}
}
};
}
#page .clickable {
cursor:pointer;
}
#page .clickable:hover {
text-decoration:underline;
}
##Putting it all together
That is the extent of the Noml library itself. This leaves a lot of room for project architecture around it. Here is a basic example of how to begin:
start with a simple html file:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="readme.js"></script>
<title>Noml example</title>
</head>
<body>
</body>
</html>
and a simple js file:
import {renderPage, ui} from 'noml';
window.onload = bodyOnLoad();
function bodyOnLoad() {
renderPage(ui.span("my first noml app"));
}
The included example shows a technique of structuring an application with ui, data, logic, and state all managed.