diff --git a/README.md b/README.md
index db6f0ee..fa6dbab 100644
--- a/README.md
+++ b/README.md
@@ -463,4 +463,17 @@ Finds first closest Fez node.
Fez('#icon-1').setColor('red')
```
+* ### this.reactiveStore({})
+ Creates reactive store that updates this.html() on change. You can pass another reactivity function.
+
+ Used in default TODO example.
+
+ ```js
+ this.reactiveStore({}, (o, k, v)=>{
+ window.requestAnimationFrame(()=>{
+ console.log(`key "${k}" changed to ${v}`)
+ this.html())
+ })
+ })
+ ```
diff --git a/demo/fez/todo.html b/demo/fez/todo.html
index 29c626e..76be3b6 100644
--- a/demo/fez/todo.html
+++ b/demo/fez/todo.html
@@ -1,3 +1,3 @@
-Todo app demo, ToDo MVC candidate (React, Vue, Angular)
+Todo app demo, features reactiveStore, fez-use, runtime scss. ToDo MVC candidate (React, Vue, Angular)
No tasks found
{{/if}} - {{#for task, index in @tasks}} - {{#if task.animate}} + {{#for task, index in @data.tasks}} + {{#if task.animate}}@@ -31,51 +32,66 @@ Fez('ui-todo', class extends FezBase { ⋅
-{{ JSON.stringify(this.tasks, null, 2) }}+
{{ JSON.stringify(this.data.tasks, null, 2) }}` toggleComplete(index) { - const task = this.tasks[index] + const task = this.data.tasks[index] task.done = !task.done - this.html() } clearCompleted() { - this.tasks = this.tasks.filter((t) => !t.done) - this.html() + this.data.tasks = this.data.tasks.filter((t) => !t.done) } removeTask(index) { - this.tasks = this.tasks.filter((_, i) => i !== index); - this.html() + this.data.tasks = this.data.tasks.filter((_, i) => i !== index); } setName(index, name) { - this.tasks[index].name = name - this.html() // refresh full component on every key stroke. done for render speed demo purposes + this.data.tasks[index].name = name } addTask() { + // no need to force update template, this is automatic because we are using reactiveStore() this.counter ||= 0 - this.tasks.push({name: `new task ${++this.counter}`, done: false, animate: true}) - this.html() + this.data.tasks.push({ + name: `new task ${++this.counter}`, + done: false, + animate: true + }) } animate(node) { + // same as in svelte, uf you define fez-use="methodName", method will be called when node is added to dom. + // in this case, we animate show new node $(node) .css('display', 'block') .animate({height: '33px', opacity: 1}, 200, () => { - delete this.tasks[this.tasks.length-1].animate + delete this.data.tasks[this.data.tasks.length-1].animate $(node).css('height', 'auto') - this.html() }) } connect() { - this.tasks = [ + // creates reactive store, that calls this.html() state refresh after every data set + // you can pass function as argument to change default reactive behaviour + this.data = this.reactiveStore({}) + + this.data.tasks = [ {name: 'First task', done: false}, {name: 'Second task', done: false}, {name: 'Third task', done: true }, ] + + for (const i in [1,2,3,4,5]) { + this.data.i = i + } + + window.requestAnimationFrame(()=>{ + for (const i in [1,2,3,4,5]) { + this.data.i = i + } + }) } }) diff --git a/dist/fez.js b/dist/fez.js index a7fa11a..c20827d 100644 --- a/dist/fez.js +++ b/dist/fez.js @@ -1,4 +1,4 @@ -(()=>{function q(l,e){let s=/(\r\n|\r|\n)/g,f=/{{\s*(.+?)\s*}}/g,p=/^each\s+(.*)\s+as\s+(.*)$/,h=/^if\s+(.*)$/,c=/^else if\s+(.*)$/;function o(A){let S=T=>T.replaceAll("@","this."),L=` +(()=>{function N(l,e){let i=/(\r\n|\r|\n)/g,a=/{{\s*(.+?)\s*}}/g,p=/^each\s+(.*)\s+as\s+(.*)$/,d=/^if\s+(.*)$/,u=/^else if\s+(.*)$/;function o(A){let S=T=>T.replaceAll("@","this."),L=` let _strings = [], _sequence = [], _values = []; function htmlEscape(text) { @@ -13,18 +13,18 @@ } } - _sequence.push('${A.trim().replace(s,"\\n").replace(f,(T,v)=>{if(v=v.replace(/^[#:]/,""),v.startsWith("each")||v.startsWith("for")){let _=v.split(/\s+/);if(_.shift()==="for"){let N=_.pop();_.pop(),v=`each ${N} as ${_.join(" ")}`}let j=p.exec(v);if(j)return j[1]=S(j[1]),`'); - (!Array.isArray(${j[1]}) ? Array.from(Object.entries(${j[1]} || []), ([key, value]) => [key, value]) : ${j[1]}).forEach((${j[2]}) => { _sequence.push('`}else if(v.startsWith("if")){let _=h.exec(v);if(_)return _[1]=S(_[1]),`'); - if (${_[1]}) { _sequence.push('`}else if(v.startsWith("else if")){let _=c.exec(v);if(_)return _[1]=S(_[1]),`'); - } else if (${_[1]}) { _sequence.push('`}else{if(v==="else")return`'); - } else { _sequence.push('`;if(v==="/each"||v==="/for")return`'); - }); _sequence.push('`;if(v==="/if")return`'); - } _sequence.push('`}let I=v.split(/^\@html\s+/);return I[1]?v=S(I[1]):v=`htmlEscape(${S(v)})`,`'); + _sequence.push('${A.trim().replace(i,"\\n").replace(a,(T,y)=>{if(y=y.replace(/^[#:]/,""),y.startsWith("each")||y.startsWith("for")){let _=y.split(/\s+/);if(_.shift()==="for"){let q=_.pop();_.pop(),y=`each ${q} as ${_.join(" ")}`}let R=p.exec(y);if(R)return R[1]=S(R[1]),`'); + (!Array.isArray(${R[1]}) ? Array.from(Object.entries(${R[1]} || []), ([key, value]) => [key, value]) : ${R[1]}).forEach((${R[2]}) => { _sequence.push('`}else if(y.startsWith("if")){let _=d.exec(y);if(_)return _[1]=S(_[1]),`'); + if (${_[1]}) { _sequence.push('`}else if(y.startsWith("else if")){let _=u.exec(y);if(_)return _[1]=S(_[1]),`'); + } else if (${_[1]}) { _sequence.push('`}else{if(y==="else")return`'); + } else { _sequence.push('`;if(y==="/each"||y==="/for")return`'); + }); _sequence.push('`;if(y==="/if")return`'); + } _sequence.push('`}let I=y.split(/^\@html\s+/);return I[1]?y=S(I[1]):y=`htmlEscape(${S(y)})`,`'); _strings.push(_sequence.join('')); _sequence = []; - _values.push(${v}); + _values.push(${y}); _sequence.push('`})}'); _strings.push(_sequence.join('')); return [_strings, _values]; - `;return new Function("_data",L)}function m(A){let S=o(A);return()=>{let[L,T]=S.bind(e)();return L.reduce((v,I,_)=>v+T[_-1]+I)}}function g(A){let S=new Set(["area","base","br","col","embed","hr","img","input","link","meta","source","track","wbr"]);return A.replace(/<([a-z-]+)\b([^>]*)\/>/g,(L,T,v)=>S.has(T)?L:`<${T}${v}>${T}>`)}return l=g(l),m(l)}var Y=function(){"use strict";let l=new Set,e={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:T,afterNodeAdded:T,beforeNodeMorphed:T,afterNodeMorphed:T,beforeNodeRemoved:T,afterNodeRemoved:T,beforeAttributeUpdated:T},head:{style:"merge",shouldPreserve:function(t){return t.getAttribute("im-preserve")==="true"},shouldReAppend:function(t){return t.getAttribute("im-re-append")==="true"},shouldRemove:T,afterHeadMorphed:T}};function s(t,r,i={}){t instanceof Document&&(t=t.documentElement),typeof r=="string"&&(r=re(r));let a=ie(r),u=I(t,a,i);return f(t,a,u)}function f(t,r,i){if(i.head.block){let a=t.querySelector("head"),u=r.querySelector("head");if(a&&u){let d=S(u,a,i);Promise.all(d).then(function(){f(t,r,Object.assign(i,{head:{block:!1,ignore:!0}}))});return}}if(i.morphStyle==="innerHTML")return c(r,t,i),t.children;if(i.morphStyle==="outerHTML"||i.morphStyle==null){let a=se(r,t,i),u=a?.previousSibling,d=a?.nextSibling,b=h(t,a,i);return a?ne(u,b,d):[]}else throw"Do not understand how to morph style "+i.morphStyle}function p(t,r){return r.ignoreActiveValue&&t===document.activeElement&&t!==document.body}function h(t,r,i){if(!(i.ignoreActive&&t===document.activeElement))return r==null?i.callbacks.beforeNodeRemoved(t)===!1?t:(t.remove(),i.callbacks.afterNodeRemoved(t),null):j(t,r)?(i.callbacks.beforeNodeMorphed(t,r)===!1||(t instanceof HTMLHeadElement&&i.head.ignore||(t instanceof HTMLHeadElement&&i.head.style!=="morph"?S(r,t,i):(m(r,t,i),p(t,i)||c(r,t,i))),i.callbacks.afterNodeMorphed(t,r)),t):i.callbacks.beforeNodeRemoved(t)===!1||i.callbacks.beforeNodeAdded(r)===!1?t:(t.parentElement.replaceChild(r,t),i.callbacks.afterNodeAdded(r),i.callbacks.afterNodeRemoved(t),r)}function c(t,r,i){let a=t.firstChild,u=r.firstChild,d;for(;a;){if(d=a,a=d.nextSibling,u==null){if(i.callbacks.beforeNodeAdded(d)===!1)return;r.appendChild(d),i.callbacks.afterNodeAdded(d),w(i,d);continue}if(_(d,u,i)){h(u,d,i),u=u.nextSibling,w(i,d);continue}let b=ee(t,r,d,u,i);if(b){u=N(u,b,i),h(b,d,i),w(i,d);continue}let M=te(t,r,d,u,i);if(M){u=N(u,M,i),h(M,d,i),w(i,d);continue}if(i.callbacks.beforeNodeAdded(d)===!1)return;r.insertBefore(d,u),i.callbacks.afterNodeAdded(d),w(i,d)}for(;u!==null;){let b=u;u=u.nextSibling,U(b,i)}}function o(t,r,i,a){return t==="value"&&a.ignoreActiveValue&&r===document.activeElement?!0:a.callbacks.beforeAttributeUpdated(t,r,i)===!1}function m(t,r,i){let a=t.nodeType;if(a===1){let u=t.attributes,d=r.attributes;for(let b of u)o(b.name,r,"update",i)||r.getAttribute(b.name)!==b.value&&r.setAttribute(b.name,b.value);for(let b=d.length-1;0<=b;b--){let M=d[b];o(M.name,r,"remove",i)||t.hasAttribute(M.name)||r.removeAttribute(M.name)}}(a===8||a===3)&&r.nodeValue!==t.nodeValue&&(r.nodeValue=t.nodeValue),p(r,i)||A(t,r,i)}function g(t,r,i,a){if(t[i]!==r[i]){let u=o(i,r,"update",a);u||(r[i]=t[i]),t[i]?u||r.setAttribute(i,t[i]):o(i,r,"remove",a)||r.removeAttribute(i)}}function A(t,r,i){if(t instanceof HTMLInputElement&&r instanceof HTMLInputElement&&t.type!=="file"){let a=t.value,u=r.value;g(t,r,"checked",i),g(t,r,"disabled",i),t.hasAttribute("value")?a!==u&&(o("value",r,"update",i)||(r.setAttribute("value",a),r.value=a)):o("value",r,"remove",i)||(r.value="",r.removeAttribute("value"))}else if(t instanceof HTMLOptionElement)g(t,r,"selected",i);else if(t instanceof HTMLTextAreaElement&&r instanceof HTMLTextAreaElement){let a=t.value,u=r.value;if(o("value",r,"update",i))return;a!==u&&(r.value=a),r.firstChild&&r.firstChild.nodeValue!==a&&(r.firstChild.nodeValue=a)}}function S(t,r,i){let a=[],u=[],d=[],b=[],M=i.head.style,z=new Map;for(let E of t.children)z.set(E.outerHTML,E);for(let E of r.children){let C=z.has(E.outerHTML),F=i.head.shouldReAppend(E),P=i.head.shouldPreserve(E);C||P?F?u.push(E):(z.delete(E.outerHTML),d.push(E)):M==="append"?F&&(u.push(E),b.push(E)):i.head.shouldRemove(E)!==!1&&u.push(E)}b.push(...z.values());let X=[];for(let E of b){let C=document.createRange().createContextualFragment(E.outerHTML).firstChild;if(i.callbacks.beforeNodeAdded(C)!==!1){if(C.href||C.src){let F=null,P=new Promise(function(ue){F=ue});C.addEventListener("load",function(){F()}),X.push(P)}r.appendChild(C),i.callbacks.afterNodeAdded(C),a.push(C)}}for(let E of u)i.callbacks.beforeNodeRemoved(E)!==!1&&(r.removeChild(E),i.callbacks.afterNodeRemoved(E));return i.head.afterHeadMorphed(r,{added:a,kept:d,removed:u}),X}function L(){}function T(){}function v(t){let r={};return Object.assign(r,e),Object.assign(r,t),r.callbacks={},Object.assign(r.callbacks,e.callbacks),Object.assign(r.callbacks,t.callbacks),r.head={},Object.assign(r.head,e.head),Object.assign(r.head,t.head),r}function I(t,r,i){return i=v(i),{target:t,newContent:r,config:i,morphStyle:i.morphStyle,ignoreActive:i.ignoreActive,ignoreActiveValue:i.ignoreActiveValue,idMap:fe(t,r),deadIds:new Set,callbacks:i.callbacks,head:i.head}}function _(t,r,i){return t==null||r==null?!1:t.nodeType===r.nodeType&&t.tagName===r.tagName?t.id!==""&&t.id===r.id?!0:k(i,t,r)>0:!1}function j(t,r){return t==null||r==null?!1:t.nodeType===r.nodeType&&t.tagName===r.tagName}function N(t,r,i){for(;t!==r;){let a=t;t=t.nextSibling,U(a,i)}return w(i,r),r.nextSibling}function ee(t,r,i,a,u){let d=k(u,i,r),b=null;if(d>0){let M=a,z=0;for(;M!=null;){if(_(i,M,u))return M;if(z+=k(u,M,t),z>d)return null;M=M.nextSibling}}return b}function te(t,r,i,a,u){let d=a,b=i.nextSibling,M=0;for(;d!=null;){if(k(u,d,t)>0)return null;if(j(i,d))return d;if(j(b,d)&&(M++,b=b.nextSibling,M>=2))return null;d=d.nextSibling}return d}function re(t){let r=new DOMParser,i=t.replace(/