diff --git a/README.md b/README.md
index 4d25f6e..89b1ac7 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# .dom [](https://travis-ci.org/wavesoft/dot-dom) [](https://codepen.io/anon/pen/YNdNwv?editors=0010)
-> A tiny (510 byte) virtual DOM template engine for embedded projects
+> A tiny (511 byte) virtual DOM template engine for embedded projects
|
IE / Edge |
Firefox |
Chrome |
Safari |
Opera |
iOS Safari |
Chrome for Android |
| --------- | --------- | --------- | --------- | --------- | --------- | --------- |
@@ -12,7 +12,7 @@ Why? Because with such library you can create powerful GUIs in tight space envir
### Features
-* _Tiny by design_ : The library should never exceed the 512 bytes in size. The goal is not to have yet another template engine, but to have as many features as possible in 512 bytes. If a new feature is needed, an other must be sacraficed or the scope must be reduced.
+* _Tiny by design_ : The library should never exceed the 512 bytes in size. The goal is not to have yet another template engine, but to have as many features as possible in 512 bytes. If a new feature is needed, an other must be sacraficed or the scope must be reduced.
* _Built for the future_ : The library is heavily exploiting the ES6 specifications, meaning that it's **not** supported by older borwsers. Currently it's supported by the 70% of the browsers in the market, but expect this to be 90% within the next year.
@@ -25,16 +25,16 @@ Why? Because with such library you can create powerful GUIs in tight space envir
## Installation
-For minimum footprint, include `dotdom.min.js.gz` (510b) to your project.
+For minimum footprint, include `dotdom.min.js.gz` (511b) to your project.
```html
```
-Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the following (755b):
+Alternatively you can just include the minified version of the library directly before your script. Just copy-paste the following (779b):
```js
-((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]&&k.unshift(j)&&{C:k}||(j.C=k)&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v,w,x,y=n.P[v])=>'style'==v?c.assign(u[v],y):'C'!=v&&(u[v]=y))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
+((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]?{C:[].concat(j,...k)}:(j.C=[].concat(...k))&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v)=>'style'==v?c.assign(u[v],n.P[v]):u[v]!==n.P[v]&&(u[v]=n.P[v]))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>i[j]||h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
```
## Examples
@@ -210,6 +210,29 @@ component.
Properties and children are optional and they can be omitted.
+#### Functional Components
+
+Instead of a tag name you can provide a function that returns a Virtual DOM
+according to some higher-level logic. Such function have the following signature:
+
+```js
+const Component = (props, state, setState) {
+
+ // Return your Virtual DOM
+ return div( ... )
+}
+```
+
+The `props` property contains the properties object as given when the component
+was created.
+
+The `state` is initialized to an empty object `{}` and it's updated by calling
+the `setState({ newState })` method. The latter will also trigger an update to
+the component and it's children.
+
+You can also assign properties to the `state` object directly if you don't want
+to cause an update.
+
### Tag Shorthand `tag( [properties], [children ...] )`
```js
@@ -289,8 +312,8 @@ Are you interested in contributing to **.dom**? You are more than welcome! Just
global.R = render = (
vnodes, // Flat-code comments start on column 70 and
dom, // wrap after column 120.
-
+
/* Logical separations can be commented like this */
-
+
...
```
diff --git a/dotdom.min.js b/dotdom.min.js
index 5354790..d607263 100644
--- a/dotdom.min.js
+++ b/dotdom.min.js
@@ -1,2 +1,2 @@
-((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]&&k.unshift(j)&&{C:k}||(j.C=k)&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v,w,x,y=n.P[v])=>'style'==v?c.assign(u[v],y):'C'!=v&&(u[v]=y))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
+((a,b,c,d,e,f,g,h)=>{String.prototype[d]=1,f=(i,j={},...k)=>({[d]:1,E:i,P:j[d]?{C:[].concat(j,...k)}:(j.C=[].concat(...k))&&j}),a.R=g=(i,j,k='',l=j.childNodes,m=0)=>{for((i.map?i:[i]).map((n,o,p,q=k+'.'+o,r=e[q]||[{},n.E],s=e[q]=r[1]==n.E?r:[{},n.E],t=l[m++],u)=>{n.E&&n.E.call&&(n=n.E(n.P,s[0],v=>c.assign(s[0],v)&&g(i,j,k))),u=n.trim?b.createTextNode(n):b.createElement(n.E),(u=t?t.E!=n.E&&t.data!=n?j.replaceChild(u,t)&&u:t:j.appendChild(u)).E=n.E,n.trim?u.data=n:c.keys(n.P).map((v)=>'style'==v?c.assign(u[v],n.P[v]):u[v]!==n.P[v]&&(u[v]=n.P[v]))&&g(n.P.C,u,q)});l[m];)j.removeChild(l[m])},h=i=>new Proxy(i,{get:(j,k,l)=>h((...m)=>((l=j(...m)).P.className=[l.P.className]+' '+k,l))}),a.H=new Proxy(f,{get:(i,j)=>i[j]||h(f.bind(a,j))})})(window,document,Object,Symbol(),{});
diff --git a/dotdom.min.js.gz b/dotdom.min.js.gz
index 2edc9e7..07345e1 100644
Binary files a/dotdom.min.js.gz and b/dotdom.min.js.gz differ
diff --git a/package.json b/package.json
index 30b3e60..4a358a8 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,11 @@
{
"name": "dot-dom",
- "version": "0.2.1",
+ "version": "0.2.2",
"description": "A tiny (less than 512 byte) template engine that uses virtual DOM and some of react principles",
- "main": "dotdom.min.js",
+ "main": "src/dotdom.js",
"scripts": {
"test": "./node_modules/.bin/jest",
- "build": "./node_modules/.bin/babili src/dotdom.js | tee dotdom.min.js | gzip -9 > dotdom.min.js.gz"
+ "build": "cat src/dotdom.js | perl -0pe 's/BEGIN NPM-GLUE.*END NPM-GLUE//s' | ./node_modules/.bin/babili | tee dotdom.min.js | gzip -9 > dotdom.min.js.gz"
},
"repository": {
"type": "git",
diff --git a/src/__tests__/dotdom-test.js b/src/__tests__/dotdom-test.js
index cc84684..244a37d 100644
--- a/src/__tests__/dotdom-test.js
+++ b/src/__tests__/dotdom-test.js
@@ -1,72 +1,141 @@
-require('../dotdom');
-const dd = window;
+const dd = require('../dotdom');
describe('.dom', function () {
describe('#H', function () {
- it('should create vnode without arguments', function () {
- const vdom = dd.H('div');
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({C: []});
- });
+ describe('Factory', function () {
- it('should create vnode with props', function () {
- const vdom = dd.H('div', {foo: 'bar'});
+ it('should create vnode without arguments', function () {
+ const vdom = dd.H('div');
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({foo: 'bar', C: []});
- });
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({C: []});
+ });
- it('should create vnode with props and children', function () {
- const cdom = dd.H('div');
- const vdom = dd.H('div', {foo: 'bar'}, cdom);
+ it('should create vnode with props', function () {
+ const vdom = dd.H('div', {foo: 'bar'});
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({
- foo: 'bar',
- C: [ cdom ]
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({foo: 'bar', C: []});
});
- });
- it('should create vnode with props and mixed children', function () {
- const cdom = dd.H('div');
- const vdom = dd.H('div', {foo: 'bar'}, 'foo', cdom);
+ it('should create vnode with props and children', function () {
+ const cdom = dd.H('div');
+ const vdom = dd.H('div', {foo: 'bar'}, cdom);
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({
- foo: 'bar',
- C: [ 'foo', cdom ]
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ foo: 'bar',
+ C: [ cdom ]
+ });
+ });
+
+ it('should create vnode with props and children as array', function () {
+ const cdom1 = dd.H('div');
+ const cdom2 = dd.H('div');
+ const cdom3 = dd.H('div');
+ const vdom = dd.H('div', {foo: 'bar'}, cdom1, [cdom2, cdom3]);
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ foo: 'bar',
+ C: [ cdom1, cdom2, cdom3 ]
+ });
});
- });
- it('should create vnode with props and string children', function () {
- const vdom = dd.H('div', {foo: 'bar'}, 'foo');
+ it('should create vnode with props and mixed children', function () {
+ const cdom = dd.H('div');
+ const vdom = dd.H('div', {foo: 'bar'}, 'foo', cdom);
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({
- foo: 'bar',
- C: [ 'foo' ]
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ foo: 'bar',
+ C: [ 'foo', cdom ]
+ });
});
- });
- it('should create vnode with only child', function () {
- const vdom = dd.H('div', 'foo');
+ it('should create vnode with props and string children', function () {
+ const vdom = dd.H('div', {foo: 'bar'}, 'foo');
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({
- C: [ 'foo' ]
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ foo: 'bar',
+ C: [ 'foo' ]
+ });
});
+
+ it('should create vnode with only child', function () {
+ const vdom = dd.H('div', 'foo');
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ 'foo' ]
+ });
+ });
+
+ it('should create vnode with children', function () {
+ const vdom = dd.H('div', 'foo', 'bar', 'baz');
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ 'foo', 'bar', 'baz' ]
+ });
+ });
+
+ it('should create vnode with children in arrays', function () {
+ const vdom = dd.H('div', 'foo', ['bar', 'baz']);
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ 'foo', 'bar', 'baz' ]
+ });
+ });
+
+ it('should create vnode with only mixed children', function () {
+ const cdom = dd.H('div');
+ const vdom = dd.H('div', cdom, 'foo');
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ cdom, 'foo' ]
+ });
+ });
+
});
- it('should create vnode with only mixed children', function () {
- const cdom = dd.H('div');
- const vdom = dd.H('div', cdom, 'foo');
+ describe('Proxy', function () {
+
+ it('H.apply should be proxied', function () {
+ const cdom = dd.H('div');
+ const vdom = dd.H.apply({}, ['div', cdom, 'foo']);
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ cdom, 'foo' ]
+ });
+ });
+
+ it('H.call should be proxied', function () {
+ const cdom = dd.H('div');
+ const vdom = dd.H.call({}, 'div', cdom, 'foo');
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ cdom, 'foo' ]
+ });
+ });
- expect(vdom.E).toEqual('div');
- expect(vdom.P).toEqual({
- C: [ cdom, 'foo' ]
+ it('H.tag should be a shorthand', function () {
+ const cdom = dd.H('div');
+ const vdom = dd.H.div(cdom, 'foo');
+
+ expect(vdom.E).toEqual('div');
+ expect(vdom.P).toEqual({
+ C: [ cdom, 'foo' ]
+ });
});
+
});
});
@@ -292,8 +361,8 @@ describe('.dom', function () {
}
const HostComponent = function() {
return dd.H('div',
- H(Component),
- H(Component)
+ dd.H(Component),
+ dd.H(Component)
)
}
const vdom = dd.H(HostComponent);
@@ -379,8 +448,8 @@ describe('.dom', function () {
}, `${clicks} clicks`)
}
const vdom = dd.H('div',
- H(Component),
- H(Component)
+ dd.H(Component),
+ dd.H(Component)
);
dd.R(vdom, dom)
diff --git a/src/dotdom.js b/src/dotdom.js
index 3e53e8c..f965885 100644
--- a/src/dotdom.js
+++ b/src/dotdom.js
@@ -15,6 +15,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+/* BEGIN NPM-GLUE */
+
+// This code block will be striped when building the stand-alone version.
+// When using `npm` this exports the correct functions in order to be easily
+// imported in the correct scope, without leaking to the global scope.
+
+const window = {};
+module.exports = window;
+
+/* END NPM-GLUE */
+
((global, document, Object, vnodeFlag, globalState, createElement, render, wrapClassProxy) => {
/**
@@ -41,8 +53,10 @@
// first argument
P: props[vnodeFlag] // If the props argument is a renderable VNode,
- && children.unshift(props) && {C: children} // ... prepend it to the children
- || (props.C = children) && props // ... otherwise append 'C' to the property
+ ? {C: [].concat(props, ...children)} // ... prepend it to the children
+ : (props.C = [].concat(...children)) && props // ... otherwise append 'C' to the property
+ // the .concat ensures that arrays of children
+ // will be flattened into a single array.
})
/**
@@ -140,25 +154,18 @@
? _new_dom.data = vnode // - String nodes update only the text
: Object.keys(vnode.P).map( // - Element nodes have properties
(
- key, // 1. The property name
-
- _unused2, // 2. Index is unused
- _unused3, // 3. Array is unused
-
- _value=vnode.P[key] // a. We cache the property value
-
+ key // 1. The property name
) =>
key == 'style' ? // The 'style' property is an object and must be
// applied recursively.
Object.assign(
_new_dom[key], // '[key]' is shorter than '.style'
- _value
+ vnode.P[key]
)
- : (key != 'C' && // 'C' is the children, so we skip it
-
- (_new_dom[key] = _value)) // All properties are applied directly to DOM
+ : (_new_dom[key] !== vnode.P[key] && // All properties are applied directly to DOM, as
+ (_new_dom[key] = vnode.P[key])) // long as they are different than ther value in the
// instance. This includes `onXXX` event handlers.
) &&
@@ -214,8 +221,8 @@
global.H = new Proxy(
createElement,
{
- get: (_unused4, tagName) =>
- wrapClassProxy(
+ get: (targetFn, tagName) =>
+ targetFn[tagName] || wrapClassProxy(
createElement.bind(global, tagName)
)
}