rasti 3.0.0-alpha.3 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -24
- package/dist/rasti.js +204 -170
- package/dist/rasti.min.js +1 -1
- package/es/Component.js +178 -144
- package/es/View.js +26 -26
- package/lib/Component.cjs +178 -144
- package/lib/View.cjs +26 -26
- package/package.json +2 -2
- package/src/Component.js +178 -144
- package/src/View.js +26 -26
package/dist/rasti.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i={el:!0,tag:!0,attributes:!0,events:!0,model:!0,template:!0,onDestroy:!0};class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="uid"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],Object.keys(t).forEach((e=>{i[e]&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const o=(t,e)=>s(t,e,e),l=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let l;if(n){if(t!==n)return h;const s=a(u(i,e),e);l=function(){return r(s.map((t=>o(t,this))))}}return e.push((function(){const e=c(s,(t=>o(t,this))).all;return l&&(e.renderChildren=l.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,o,l,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=o,E.close=void 0!==a?e[a]:l,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f={key:!0,state:!0,onCreate:!0,onChange:!0,onRender:!0};class p extends n{constructor(t={}){super(...arguments),Object.keys(t).forEach((e=>{f[e]&&(this[e]=t[e])})),this.options=t,this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())return this.children[0].recycle(t);return this.findElement(t).replaceWith(this.el),this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(l(t,e),e),e).map((t=>o(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.isContainer()){if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el&&this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(l(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:l,close:u}=d(g,e);if(i=function(){return p.sanitize(o(t,this))},n=function(){return c(s,(t=>o(t,this))).attributes},E=function(){const t=c(s,(t=>o(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=o(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=l?a(l,e):[];f=function(e){return r(t.map((t=>o(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(`${t}`):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(o(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,t,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const o=Object.keys(h);if(o.length&&(this._pending=["change",this,h,...n]),o.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Rasti={})}(this,(function(t){"use strict";class e{on(t,e){if("function"!=typeof e)throw new TypeError("Listener must be a function");return this.listeners||(this.listeners={}),this.listeners[t]||(this.listeners[t]=[]),this.listeners[t].push(e),()=>this.off(t,e)}once(t,e){if("function"==typeof e){const s=this,i=e;e=function(...n){i(...n),s.off(t,e)}}return this.on(t,e)}off(t,e){this.listeners&&(t?this.listeners[t]&&(e?(this.listeners[t]=this.listeners[t].filter((t=>t!==e)),this.listeners[t].length||delete this.listeners[t]):delete this.listeners[t],Object.keys(this.listeners).length||delete this.listeners):delete this.listeners)}emit(t,...e){this.listeners&&this.listeners[t]&&this.listeners[t].slice().forEach((function(t){t(...e)}))}}var s=(t,e,...s)=>"function"!=typeof t?t:t.apply(e,s);const i=["el","tag","attributes","events","model","template","onDestroy"];class n extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments),this.uid="rasti-"+ ++n.uid,this.delegatedEventListeners=[],this.children=[],this.destroyQueue=[],i.forEach((e=>{e in t&&(this[e]=t[e])})),this.ensureElement()}preinitialize(){}$(t){return this.el.querySelector(t)}$$(t){return this.el.querySelectorAll(t)}destroy(){return this.destroyChildren(),this.undelegateEvents(),this.off(),this.destroyQueue.forEach((t=>t())),this.destroyQueue=[],this.onDestroy.apply(this,arguments),this}onDestroy(){}addChild(t){return this.children.push(t),t}destroyChildren(){this.children.forEach((t=>t.destroy())),this.children=[]}ensureElement(){if(this.el)this.el=s(this.el,this);else{const t=s(this.tag,this),e=s(this.attributes,this);this.el=this.createElement(t,e)}this.delegateEvents()}createElement(t="div",e={}){let s=document.createElement(t);return Object.keys(e).forEach((t=>s.setAttribute(t,e[t]))),s}removeElement(){return this.el.parentNode.removeChild(this.el),this}delegateEvents(t){if(t||(t=s(this.events,this)),!t)return this;this.delegatedEventListeners.length&&this.undelegateEvents();let e={};return Object.keys(t).forEach((s=>{const i=s.split(" "),n=i.shift(),r=i.join(" ");let h=t[s];h=("string"==typeof h?this[h]:h).bind(this),e[n]||(e[n]=[]),e[n].push({selector:r,listener:h})})),Object.keys(e).forEach((t=>{const s=s=>{e[t].forEach((({selector:t,listener:e})=>{t&&!s.target.closest(t)||e(s,this)}))};this.delegatedEventListeners.push({type:t,listener:s}),this.el.addEventListener(t,s)})),this}undelegateEvents(){return this.delegatedEventListeners.forEach((({type:t,listener:e})=>{this.el.removeEventListener(t,e)})),this.delegatedEventListeners=[],this}render(){return this.template&&(this.el.innerHTML=this.template(this.model)),this}static sanitize(t){return`${t}`.replace(/[&<>"']/g,(t=>({"&":"&","<":"<",">":">",'"':""","'":"'"}[t])))}}n.uid=0;const r=t=>t.reduce(((t,e)=>(Array.isArray(e)?t.push(...r(e)):t.push(e),t)),[]);class h{constructor(t){this.value=t}toString(){return this.value}}const l=(t,e)=>s(t,e,e),o=(t,e)=>t.reduce(((t,s,i)=>(t.push(s),void 0!==e[i]&&t.push(p.PLACEHOLDER_EXPRESSION(i)),t)),[]).join(""),a=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),i=new RegExp(`${s}`,"g"),n=[];let r,h=0;for(;null!==(r=i.exec(t));){const s=t.slice(h,r.index);n.push(p.markAsSafeHTML(s),e[r[1]]),h=r.index+r[0].length}return n.push(p.markAsSafeHTML(t.slice(h))),n},c=(t,e)=>{const s=t.reduce(((t,s)=>{const i=e(s[0]);if(1===s.length)"object"==typeof i?t.all=Object.assign(t.all,i):"string"==typeof i&&(t.all[i]=!0);else{const n=e(s[1]);t.all[i]=n}return t}),{all:{},events:{},attributes:{}});return Object.keys(s.all).forEach((t=>{const e=t.match(/on(([A-Z]{1}[a-z]+)+)/);e&&e[1]?s.events[e[1].toLowerCase()]=s.all[t]:s.attributes[t]=s.all[t]})),s},u=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");return t.replace(new RegExp(`<(${s})([^>]*)>([\\s\\S]*?)</(${s})>|<(${s})([^>]*)/>`,"g"),(function(){const{tag:t,attributes:s,inner:i,close:n,raw:h}=d(arguments,e);if(!(t.prototype instanceof p))return h;let o;if(n){if(t!==n)return h;const s=a(u(i,e),e);o=function(){return r(s.map((t=>l(t,this))))}}return e.push((function(){const e=c(s,(t=>l(t,this))).all;return o&&(e.renderChildren=o.bind(this)),t.mount(e)})),p.PLACEHOLDER_EXPRESSION(e.length-1)}))},d=(t,e)=>{const s=p.PLACEHOLDER_EXPRESSION("(\\d+)"),[i,n,r,h,l,o,a,c,u,d]=t,E={raw:i,attributes:[]};let f;n?(E.tag=void 0!==r?e[r]:n,E.inner=l,E.close=void 0!==a?e[a]:o,f=h):(E.tag=void 0!==u?e[u]:c,f=d);const y=new RegExp(`(${s}|[\\w-]+)(?:=(["']?)(?:${s}|((?:.?(?!["']?\\s+(?:\\S+)=|\\s*/?[>"']))+.))\\3)?`,"g");let g;for(;null!==(g=y.exec(f));){const[,t,s,,i,n]=g,r=void 0!==s?e[s]:t,h=void 0!==i?e[i]:n;void 0!==h?E.attributes.push([r,h]):E.attributes.push([r])}return E},E={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,link:!0,meta:!0,source:!0,track:!0,wbr:!0},f=["key","state","onCreate","onChange","onRender"];class p extends n{constructor(t={}){super(...arguments),f.forEach((e=>{e in t&&(this[e]=t[e])})),this.options=t,this.partial=this.partial.bind(this),this.onCreate.apply(this,arguments)}subscribe(t){if(!t.on)return;const e=this.onChange.bind(this),s=t.on("change",e);return this.destroyQueue.push("function"==typeof s?s:()=>t.off("change",e)),this}isContainer(){return!(this.tag||!this.template)}ensureElement(){this.el&&(this.el=s(this.el,this),this.delegateEvents())}findElement(t){return(t||document).querySelector(`[${p.DATA_ATTRIBUTE_UID}="${this.uid}"]`)}getAttributes(){const t={},e={},i=[],n={[p.DATA_ATTRIBUTE_UID]:this.uid};this.attributes&&Object.assign(n,s(this.attributes,this));const r=this.previousAttributes||{};return this.previousAttributes=n,Object.keys(n).forEach((s=>{let r=n[s];!1===r?e[s]=!0:!0===r?(t[s]="",i.push(s)):(null==r&&(r=""),t[s]=r,i.push(`${p.sanitize(s)}="${p.sanitize(r)}"`))})),Object.keys(r).forEach((t=>{t in n||(e[t]=!0)})),{add:t,remove:e,html:i.join(" ")}}hydrate(t){return this.model&&this.subscribe(this.model),this.state&&this.subscribe(this.state),this.isContainer()?(this.children[0].hydrate(t),this.el=this.children[0].el):(this.el=this.findElement(t),this.delegateEvents(),this.children.forEach((t=>t.hydrate(this.el)))),this.onRender.call(this,p.RENDER_TYPE_HYDRATE),this}recycle(t){if(this.isContainer())this.children[0].recycle(t);else{this.findElement(t).replaceWith(this.el)}return this.onRender.call(this,p.RENDER_TYPE_RECYCLE),this}destroy(){return super.destroy.apply(this,arguments),this.destroyed=!0,this}onCreate(){}onChange(){this.render()}onRender(){}onDestroy(){}partial(t,...e){return r(a(u(o(t,e),e),e).map((t=>l(t,this))))}getRecyclePlaceholder(){if(this.isContainer())return this.children[0].getRecyclePlaceholder();const t=s(this.tag,this)||"div",e=`${p.DATA_ATTRIBUTE_UID}="${this.uid}"`;return this.template||!E[t]?`<${t} ${e}></${t}>`:`<${t} ${e} />`}toString(){if(this.destroyChildren(),this.isContainer())return this.template.call(this,this.addChild.bind(this));const t=s(this.tag,this)||"div",e=this.getAttributes().html,i=this.template?this.template.call(this,this.addChild.bind(this)):"";return this.template||!E[t]?`<${t} ${e}>${i}</${t}>`:`<${t} ${e} />`}render(){if(this.destroyed)return this;if(!this.el){const t=this.createElement("template");return t.innerHTML=this,this.hydrate(t.content),this}if(!this.isContainer()){const t=this.getAttributes();Object.keys(t.remove).forEach((t=>{this.el.removeAttribute(t)})),Object.keys(t.add).forEach((e=>{this.el.setAttribute(e,t.add[e])}))}if(this.template){const t=document.activeElement,e=[],s=[],i=this.children;this.children=[];const n=this.template.call(this,(t=>{let n=t;const r=t.key&&i.find((e=>e.key===t.key));return r?(n=r.getRecyclePlaceholder(),s.push(r),t.destroy()):e.push(t),n}));if(this.isContainer())if(e[0]){const t=this.createElement("template");t.innerHTML=n,this.addChild(e[0]).hydrate(t.content);const s=t.content.children[0];this.el.replaceWith(s),this.el=s}else{if(!s[0])throw new Error("Container component must have a child component");this.addChild(s[0])}else this.el.innerHTML=n,e.forEach((t=>{this.addChild(t).hydrate(this.el)})),s.forEach((t=>{this.addChild(t).recycle(this.el)}));i.forEach((t=>{s.indexOf(t)>-1||t.destroy()})),this.el.contains(t)&&t.focus()}return this.onRender.call(this,p.RENDER_TYPE_RENDER),this}static markAsSafeHTML(t){return new h(t)}static extend(t){const e=this;class s extends e{}return Object.assign(s.prototype,"function"==typeof t?t(e.prototype):t),s}static mount(t,e,s){const i=new this(t);return e&&(s?(i.toString(),i.hydrate(e)):e.appendChild(i.render().el)),i}static create(t,...e){const s=p.PLACEHOLDER_EXPRESSION("(\\d+)");let i,n,E,f;"function"==typeof t&&(e=[t],t=["",""]);const y=u(o(t,e),e);let g=y.match(new RegExp(`^\\s*<([a-z]+[1-6]?|${s})([^>]*)>([\\s\\S]*?)</(\\1|${s})>\\s*$|^\\s*<([a-z]+[1-6]?|${s})([^>]*)/>\\s*$`));if(g){const{tag:t,attributes:s,inner:o,close:u}=d(g,e);if(i=function(){return p.sanitize(l(t,this))},n=function(){return c(s,(t=>l(t,this))).attributes},E=function(){const t=c(s,(t=>l(t,this))).events;return Object.keys(t).reduce(((e,s)=>{const i=l(t[s],this);return Object.keys(i).forEach((t=>{e[`${s}${"&"===t?"":` ${t}`}`]=i[t]})),e}),{})},u){const t=o?a(o,e):[];f=function(e){return r(t.map((t=>l(t,this)))).map((t=>null!=t&&!1!==t&&!0!==t?t instanceof h?t:t instanceof p?e(t):p.sanitize(t):"")).join("")}}}else{if(g=y.match(new RegExp(`^\\s*${s}\\s*$`)),!g)throw new SyntaxError("Invalid component");f=function(t){return t(l(e[g[1]],this)).toString()}}return this.extend({tag:i,attributes:n,events:E,template:f})}}p.PLACEHOLDER_EXPRESSION=t=>`__RASTI_{${t}}__`,p.DATA_ATTRIBUTE_UID="data-rasti-uid",p.RENDER_TYPE_HYDRATE="hydrate",p.RENDER_TYPE_RECYCLE="recycle",p.RENDER_TYPE_RENDER="render",t.Component=p,t.Emitter=e,t.Model=class extends e{constructor(t={}){super(),this.preinitialize.apply(this,arguments);const e=s(this.defaults,this)||{};this.attributes=Object.assign({},e,t),this.previous={},Object.keys(this.attributes).forEach(this.defineAttribute.bind(this))}preinitialize(){}defineAttribute(t){Object.defineProperty(this,t,{get:()=>this.get(t),set:e=>{this.set(t,e)}})}get(t){return this.attributes[t]}set(t,e,...s){let i,n;"object"==typeof t?(i=t,n=[e,...s]):(i={[t]:e},n=s);const r=this._changing;this._changing=!0;const h={};r||(this.previous=Object.assign({},this.attributes)),Object.keys(i).forEach((t=>{i[t]!==this.attributes[t]&&(h[t]=i[t],this.attributes[t]=i[t])}));const l=Object.keys(h);if(l.length&&(this._pending=["change",this,h,...n]),l.forEach((t=>{this.emit(`change:${t}`,this,i[t],...n)})),r)return this;for(;this._pending;){const t=this._pending;this._pending=null,this.emit.apply(this,t)}return this._pending=null,this._changing=!1,this}toJSON(){return Object.assign({},this.attributes)}},t.View=n}));
|
package/es/Component.js
CHANGED
|
@@ -3,8 +3,12 @@ import getResult from './utils/getResult.js';
|
|
|
3
3
|
import deepFlat from './utils/deepFlat.js';
|
|
4
4
|
import './Emitter.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
7
|
* Wrapper class for HTML strings marked as safe.
|
|
8
|
+
* @class SafeHTML
|
|
9
|
+
* @param {string} value The HTML string to be marked as safe.
|
|
10
|
+
* @property {string} value The HTML string.
|
|
11
|
+
* @private
|
|
8
12
|
*/
|
|
9
13
|
class SafeHTML {
|
|
10
14
|
constructor(value) {
|
|
@@ -16,20 +20,22 @@ class SafeHTML {
|
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
/**
|
|
20
24
|
* Same as getResult, but pass context as argument to the expression.
|
|
21
25
|
* Used to evaluate expressions in the context of a component.
|
|
22
26
|
* @param {any} expression The expression to be evaluated.
|
|
23
27
|
* @param {any} context The context to call the expression with.
|
|
24
28
|
* @return {any} The result of the evaluated expression.
|
|
29
|
+
* @private
|
|
25
30
|
*/
|
|
26
31
|
const getExpressionResult = (expression, context) => getResult(expression, context, context);
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
/**
|
|
29
34
|
* Generate string with placeholders for interpolated expressions.
|
|
30
35
|
* @param strings {array} Array of strings.
|
|
31
36
|
* @param expressions {array} Array of expressions.
|
|
32
37
|
* @return {string} String with placeholders.
|
|
38
|
+
* @private
|
|
33
39
|
*/
|
|
34
40
|
const addPlaceholders = (strings, expressions) =>
|
|
35
41
|
strings.reduce((out, string, i) => {
|
|
@@ -42,11 +48,12 @@ const addPlaceholders = (strings, expressions) =>
|
|
|
42
48
|
return out;
|
|
43
49
|
}, []).join('');
|
|
44
50
|
|
|
45
|
-
|
|
51
|
+
/**
|
|
46
52
|
* Generate one dimensional array with strings and expressions.
|
|
47
53
|
* @param main {string} The main template containing placeholders.
|
|
48
54
|
* @param expressions {array} Array of expressions to replace placeholders.
|
|
49
55
|
* @return {array} Array containing strings and expressions.
|
|
56
|
+
* @private
|
|
50
57
|
*/
|
|
51
58
|
const splitPlaceholders = (main, expressions) => {
|
|
52
59
|
const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
|
|
@@ -66,7 +73,7 @@ const splitPlaceholders = (main, expressions) => {
|
|
|
66
73
|
return out;
|
|
67
74
|
};
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
/**
|
|
70
77
|
* Expand attributes.
|
|
71
78
|
* @param attributes {array} Array of attributes as key, value pairs.
|
|
72
79
|
* @param getExpressionResult {function} Function to render expressions.
|
|
@@ -74,6 +81,7 @@ const splitPlaceholders = (main, expressions) => {
|
|
|
74
81
|
* @property {object} all All attributes.
|
|
75
82
|
* @property {object} events Event listeners.
|
|
76
83
|
* @property {object} attributes Attributes.
|
|
84
|
+
* @private
|
|
77
85
|
*/
|
|
78
86
|
const expandAttributes = (attributes, getExpressionResult) => {
|
|
79
87
|
const out = attributes.reduce((out, pair) => {
|
|
@@ -111,7 +119,7 @@ const expandAttributes = (attributes, getExpressionResult) => {
|
|
|
111
119
|
return out;
|
|
112
120
|
};
|
|
113
121
|
|
|
114
|
-
|
|
122
|
+
/**
|
|
115
123
|
* Replace component tags with expressions.
|
|
116
124
|
* `<${Component} />` or `<${Component}></${Component}>` will be replaced
|
|
117
125
|
* by a function that mounts the component.
|
|
@@ -120,6 +128,7 @@ const expandAttributes = (attributes, getExpressionResult) => {
|
|
|
120
128
|
* @param main {string} The main template.
|
|
121
129
|
* @return {string} The template with components tags replaced by expressions
|
|
122
130
|
* placeholders.
|
|
131
|
+
* @private
|
|
123
132
|
*/
|
|
124
133
|
const expandComponents = (main, expressions) => {
|
|
125
134
|
const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
|
|
@@ -159,7 +168,7 @@ const expandComponents = (main, expressions) => {
|
|
|
159
168
|
);
|
|
160
169
|
};
|
|
161
170
|
|
|
162
|
-
|
|
171
|
+
/**
|
|
163
172
|
* Parse match data to get tag, attributes, inner html and close tag.
|
|
164
173
|
* @param match {array}
|
|
165
174
|
* @return {object}
|
|
@@ -168,6 +177,7 @@ const expandComponents = (main, expressions) => {
|
|
|
168
177
|
* @property {string} close The closing tag.
|
|
169
178
|
* @property {array} attributes Array of attributes as key, value pairs.
|
|
170
179
|
* @property {string} raw The whole match.
|
|
180
|
+
* @private
|
|
171
181
|
*/
|
|
172
182
|
const parseMatch = (match, expressions) => {
|
|
173
183
|
const PH = Component.PLACEHOLDER_EXPRESSION('(\\d+)');
|
|
@@ -220,13 +230,7 @@ const selfClosingTags = {
|
|
|
220
230
|
/*
|
|
221
231
|
* These option keys will be extended on the component instance.
|
|
222
232
|
*/
|
|
223
|
-
const componentOptions =
|
|
224
|
-
key : true,
|
|
225
|
-
state : true,
|
|
226
|
-
onCreate : true,
|
|
227
|
-
onChange : true,
|
|
228
|
-
onRender : true
|
|
229
|
-
};
|
|
233
|
+
const componentOptions = ['key', 'state', 'onCreate', 'onChange', 'onRender'];
|
|
230
234
|
|
|
231
235
|
/**
|
|
232
236
|
* Components are a special kind of `View` that is designed to be easily composable,
|
|
@@ -259,12 +263,14 @@ const componentOptions = {
|
|
|
259
263
|
class Component extends View {
|
|
260
264
|
constructor(options = {}) {
|
|
261
265
|
super(...arguments);
|
|
262
|
-
// Extend "this" with options
|
|
263
|
-
|
|
264
|
-
if (
|
|
266
|
+
// Extend "this" with options.
|
|
267
|
+
componentOptions.forEach(key => {
|
|
268
|
+
if (key in options) this[key] = options[key];
|
|
265
269
|
});
|
|
266
270
|
// Store options by default.
|
|
267
271
|
this.options = options;
|
|
272
|
+
// Bind `partial` method to `this`.
|
|
273
|
+
this.partial = this.partial.bind(this);
|
|
268
274
|
// Call lifecycle method.
|
|
269
275
|
this.onCreate.apply(this, arguments);
|
|
270
276
|
}
|
|
@@ -294,20 +300,22 @@ class Component extends View {
|
|
|
294
300
|
return this;
|
|
295
301
|
}
|
|
296
302
|
|
|
297
|
-
|
|
298
|
-
* Tell if
|
|
303
|
+
/**
|
|
304
|
+
* Tell if `Component` is a container.
|
|
299
305
|
* In which case, it will not have an element by itself.
|
|
300
306
|
* It will render a single expression which is expected to return a single component as child.
|
|
301
307
|
* `this.el` will be a reference to that child component's element.
|
|
302
308
|
* @return {boolean}
|
|
309
|
+
* @private
|
|
303
310
|
*/
|
|
304
311
|
isContainer() {
|
|
305
312
|
return !!(!this.tag && this.template);
|
|
306
313
|
}
|
|
307
314
|
|
|
308
|
-
|
|
309
|
-
* Override. We don't want to ensure an element on instantiation.
|
|
315
|
+
/**
|
|
316
|
+
* Override super method. We don't want to ensure an element on instantiation.
|
|
310
317
|
* We will provide it later.
|
|
318
|
+
* @private
|
|
311
319
|
*/
|
|
312
320
|
ensureElement() {
|
|
313
321
|
// If el is provided, delegate events.
|
|
@@ -318,18 +326,25 @@ class Component extends View {
|
|
|
318
326
|
}
|
|
319
327
|
}
|
|
320
328
|
|
|
321
|
-
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
* @
|
|
329
|
+
/**
|
|
330
|
+
* Locate the root element of the `Component` within a specified parent node.
|
|
331
|
+
* This is achieved by searching for the element using the unique data attribute assigned to the `Component`.
|
|
332
|
+
* @param {Node} parent - The parent node to search within.
|
|
333
|
+
* @return {Node} The root element of the component, or `null` if not found.
|
|
334
|
+
* @private
|
|
325
335
|
*/
|
|
326
336
|
findElement(parent) {
|
|
327
337
|
return (parent || document).querySelector(`[${Component.DATA_ATTRIBUTE_UID}="${this.uid}"]`);
|
|
328
338
|
}
|
|
329
339
|
|
|
330
|
-
|
|
331
|
-
*
|
|
332
|
-
*
|
|
340
|
+
/**
|
|
341
|
+
* Retrieve the attributes to be applied to the element.
|
|
342
|
+
* This includes attributes to be added, removed, and their HTML representation.
|
|
343
|
+
* @return {object} An object containing the following properties:
|
|
344
|
+
* @property {object} add - Attributes to be added to the element, with their values.
|
|
345
|
+
* @property {object} remove - Attributes to be removed from the element.
|
|
346
|
+
* @property {string} html - A string representation of the attributes for use in HTML.
|
|
347
|
+
* @private
|
|
333
348
|
*/
|
|
334
349
|
getAttributes() {
|
|
335
350
|
const add = {};
|
|
@@ -368,11 +383,13 @@ class Component extends View {
|
|
|
368
383
|
return { add, remove, html : html.join(' ') };
|
|
369
384
|
}
|
|
370
385
|
|
|
371
|
-
|
|
386
|
+
/**
|
|
372
387
|
* Used internally on the render process.
|
|
373
|
-
* Attach the
|
|
388
|
+
* Attach the `Component` to the dom element providing `this.el`, delegate events,
|
|
389
|
+
* subscribe to model changes and call `onRender` lifecycle method with `Component.RENDER_TYPE_HYDRATE` as argument.
|
|
374
390
|
* @param parent {node} The parent node.
|
|
375
391
|
* @return {Rasti.Component} The component instance.
|
|
392
|
+
* @private
|
|
376
393
|
*/
|
|
377
394
|
hydrate(parent) {
|
|
378
395
|
// Listen to model changes and call onChange.
|
|
@@ -380,13 +397,13 @@ class Component extends View {
|
|
|
380
397
|
// Listen to state changes and call onChange.
|
|
381
398
|
if (this.state) this.subscribe(this.state);
|
|
382
399
|
|
|
383
|
-
if (
|
|
400
|
+
if (this.isContainer()) {
|
|
401
|
+
this.children[0].hydrate(parent);
|
|
402
|
+
this.el = this.children[0].el;
|
|
403
|
+
} else {
|
|
384
404
|
this.el = this.findElement(parent);
|
|
385
405
|
this.delegateEvents();
|
|
386
406
|
this.children.forEach(child => child.hydrate(this.el));
|
|
387
|
-
} else {
|
|
388
|
-
this.children[0].hydrate(parent);
|
|
389
|
-
this.el = this.children[0].el;
|
|
390
407
|
}
|
|
391
408
|
// Call `onRender` lifecycle method.
|
|
392
409
|
this.onRender.call(this, Component.RENDER_TYPE_HYDRATE);
|
|
@@ -394,30 +411,38 @@ class Component extends View {
|
|
|
394
411
|
return this;
|
|
395
412
|
}
|
|
396
413
|
|
|
397
|
-
|
|
398
|
-
* Used internally
|
|
399
|
-
* Reuse a
|
|
414
|
+
/**
|
|
415
|
+
* Used internally on the render process.
|
|
416
|
+
* Reuse a `Component` that has `key` when its parent is rendered.
|
|
417
|
+
* Call `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as argument.
|
|
400
418
|
* @param parent {node} The parent node.
|
|
401
419
|
* @return {Rasti.Component} The component instance.
|
|
420
|
+
* @private
|
|
402
421
|
*/
|
|
403
422
|
recycle(parent) {
|
|
404
423
|
// If component is a container, call recycle on its child.
|
|
405
|
-
if (this.isContainer())
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
424
|
+
if (this.isContainer()) {
|
|
425
|
+
this.children[0].recycle(parent);
|
|
426
|
+
} else {
|
|
427
|
+
// Find placeholder element to be replaced. It has same data attribute as this component.
|
|
428
|
+
const toBeReplaced = this.findElement(parent);
|
|
429
|
+
// Replace it with this.el.
|
|
430
|
+
toBeReplaced.replaceWith(this.el);
|
|
431
|
+
}
|
|
410
432
|
// Call `onRender` lifecycle method.
|
|
411
433
|
this.onRender.call(this, Component.RENDER_TYPE_RECYCLE);
|
|
412
434
|
// Return `this` for chaining.
|
|
413
435
|
return this;
|
|
414
436
|
}
|
|
415
437
|
|
|
416
|
-
|
|
417
|
-
*
|
|
438
|
+
/**
|
|
439
|
+
* Destroy the `Component`.
|
|
440
|
+
* Destroy children components if any, undelegate events, stop listening to events, call `onDestroy` lifecycle method.
|
|
418
441
|
* @param {object} options Options object or any arguments passed to `destroy` method will be passed to `onDestroy` method.
|
|
442
|
+
* @return {Rasti.View} Return `this` for chaining.
|
|
419
443
|
*/
|
|
420
444
|
destroy() {
|
|
445
|
+
// Call super destroy method.
|
|
421
446
|
super.destroy.apply(this, arguments);
|
|
422
447
|
// Set destroyed flag to prevent a last render after destroyed.
|
|
423
448
|
this.destroyed = true;
|
|
@@ -446,8 +471,11 @@ class Component extends View {
|
|
|
446
471
|
}
|
|
447
472
|
|
|
448
473
|
/**
|
|
449
|
-
* Lifecycle method. Called
|
|
450
|
-
*
|
|
474
|
+
* Lifecycle method. Called after the component is rendered.
|
|
475
|
+
* - When the component is rendered for the first time, this method is called with `Component.RENDER_TYPE_HYDRATE` as the argument.
|
|
476
|
+
* - When the component is updated or re-rendered, this method is called with `Component.RENDER_TYPE_RENDER` as the argument.
|
|
477
|
+
* - When the component is recycled (reused with the same key), this method is called with `Component.RENDER_TYPE_RECYCLE` as the argument.
|
|
478
|
+
* @param {string} type - The render type. Possible values are: `Component.RENDER_TYPE_HYDRATE`, `Component.RENDER_TYPE_RENDER` and `Component.RENDER_TYPE_RECYCLE`.
|
|
451
479
|
*/
|
|
452
480
|
onRender() {}
|
|
453
481
|
|
|
@@ -461,7 +489,9 @@ class Component extends View {
|
|
|
461
489
|
* Tagged template helper method.
|
|
462
490
|
* Used to create a partial template.
|
|
463
491
|
* It will return a one-dimensional array with strings and expressions.
|
|
464
|
-
* Components will be added as children by the parent component. Template strings
|
|
492
|
+
* Components will be added as children by the parent component. Template strings literals
|
|
493
|
+
* will be marked as safe HTML to be rendered.
|
|
494
|
+
* This method is bound to the component instance by default.
|
|
465
495
|
* @param {TemplateStringsArray} strings - Template strings.
|
|
466
496
|
* @param {...any} expressions - Template expressions.
|
|
467
497
|
* @return {Array} Array containing strings and expressions.
|
|
@@ -482,7 +512,7 @@ class Component extends View {
|
|
|
482
512
|
* renderHeader() {
|
|
483
513
|
* return this.partial`
|
|
484
514
|
* <header>
|
|
485
|
-
* <${Title}>${
|
|
515
|
+
* <${Title}>${({ model }) => model.title}</${Title}>
|
|
486
516
|
* </header>
|
|
487
517
|
* `;
|
|
488
518
|
* }
|
|
@@ -527,22 +557,26 @@ class Component extends View {
|
|
|
527
557
|
`<${tag} ${attributes} />`;
|
|
528
558
|
}
|
|
529
559
|
|
|
530
|
-
|
|
531
|
-
*
|
|
560
|
+
/**
|
|
561
|
+
* Render the `Component`.
|
|
562
|
+
* - If `this.el` is not present, the `Component` will be rendered as a string inside a `DocumentFragment` and hydrated, making `this.el` available. The `onRender` lifecycle method will be called with `Component.RENDER_TYPE_HYDRATE` as an argument.
|
|
563
|
+
* - If `this.el` is present, the method will update the attributes and inner HTML of the element, or recreate its child component in the case of a container. The `onRender` lifecycle method will be called with `Component.RENDER_TYPE_RENDER` as an argument.
|
|
564
|
+
* - When rendering child components, if the new children have the same key as the previous ones, they will be recycled. A recycled `Component` will call the `onRender` lifecycle method with `Component.RENDER_TYPE_RECYCLE` as an argument.
|
|
565
|
+
* - If the active element is inside the component, it will retain focus after the render.
|
|
566
|
+
* @return {Rasti.Component} The component instance.
|
|
532
567
|
*/
|
|
533
568
|
render() {
|
|
534
569
|
// Prevent a last re render if view is already destroyed.
|
|
535
570
|
if (this.destroyed) return this;
|
|
536
|
-
|
|
571
|
+
// If `this.el` is not present, render the view as a string and hydrate it.
|
|
572
|
+
if (!this.el) {
|
|
573
|
+
const fragment = this.createElement('template');
|
|
574
|
+
fragment.innerHTML = this;
|
|
575
|
+
this.hydrate(fragment.content);
|
|
576
|
+
return this;
|
|
577
|
+
}
|
|
578
|
+
// Update attributes.
|
|
537
579
|
if (!this.isContainer()) {
|
|
538
|
-
// If `this.el` is not present, render the view as a string and hydrate it.
|
|
539
|
-
if (!this.el) {
|
|
540
|
-
const fragment = this.createElement('template');
|
|
541
|
-
fragment.innerHTML = this;
|
|
542
|
-
this.hydrate(fragment.content);
|
|
543
|
-
return this;
|
|
544
|
-
}
|
|
545
|
-
// Set `this.el` attributes.
|
|
546
580
|
const attributes = this.getAttributes();
|
|
547
581
|
// Remove attributes.
|
|
548
582
|
Object.keys(attributes.remove).forEach(key => {
|
|
@@ -553,7 +587,7 @@ class Component extends View {
|
|
|
553
587
|
this.el.setAttribute(key, attributes.add[key]);
|
|
554
588
|
});
|
|
555
589
|
}
|
|
556
|
-
// Check for `template` to see if view has innerHTML.
|
|
590
|
+
// Check for `template` to see if view has innerHTML or a child component.
|
|
557
591
|
if (this.template) {
|
|
558
592
|
// Store active element.
|
|
559
593
|
const activeElement = document.activeElement;
|
|
@@ -594,8 +628,8 @@ class Component extends View {
|
|
|
594
628
|
this.addChild(nextChildren[0]).hydrate(fragment.content);
|
|
595
629
|
// Get next child element.
|
|
596
630
|
const nextEl = fragment.content.children[0];
|
|
597
|
-
//
|
|
598
|
-
|
|
631
|
+
// Replace `this.el` with nextEl.
|
|
632
|
+
this.el.replaceWith(nextEl);
|
|
599
633
|
// Set `this.el` to nextEl.
|
|
600
634
|
this.el = nextEl;
|
|
601
635
|
} else if (recycledChildren[0]) {
|
|
@@ -631,10 +665,10 @@ class Component extends View {
|
|
|
631
665
|
}
|
|
632
666
|
|
|
633
667
|
/**
|
|
634
|
-
* Mark a string as safe HTML to be rendered.
|
|
635
|
-
* Normally you don't need to use this method, as Rasti will automatically mark
|
|
636
|
-
* as safe HTML when the component is @link
|
|
637
|
-
* using the @link
|
|
668
|
+
* Mark a string as safe HTML to be rendered.
|
|
669
|
+
* Normally you don't need to use this method, as Rasti will automatically mark string literals
|
|
670
|
+
* as safe HTML when the component is {@link #module_component_create created} and when
|
|
671
|
+
* using the {@link #module_component__partial Component.partial} method.
|
|
638
672
|
* Be sure that the string is safe to be rendered, as it will be inserted into the DOM without any sanitization.
|
|
639
673
|
* @static
|
|
640
674
|
* @param {string} value
|
|
@@ -695,89 +729,89 @@ class Component extends View {
|
|
|
695
729
|
* Takes a tagged template string or a function that returns another component, and returns a new `Component` class.
|
|
696
730
|
* - The template outer tag and attributes will be used to create the view's root element.
|
|
697
731
|
* - The template inner HTML will be used as the view's template.
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
732
|
+
* ```javascript
|
|
733
|
+
* const Button = Component.create`<button class="button">Click me</button>`;
|
|
734
|
+
* ```
|
|
701
735
|
* - Template interpolations that are functions will be evaluated during the render process, receiving the view instance as an argument and being bound to it. If the function returns `null`, `undefined`, `false`, or an empty string, the interpolation won't render any content.
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
*
|
|
708
|
-
*
|
|
736
|
+
* ```javascript
|
|
737
|
+
* const Button = Component.create`
|
|
738
|
+
* <button class="${({ options }) => options.className}">
|
|
739
|
+
* ${({ options }) => options.renderChildren()}
|
|
740
|
+
* </button>
|
|
741
|
+
* `;
|
|
742
|
+
* ```
|
|
709
743
|
* - Event handlers should be passed, at the root element as camelized attributes, in the format `onEventName=${{'selector' : listener }}`. They will be transformed to an event object and delegated to the root element. See {@link #module_view__delegateevents View.delegateEvents}.
|
|
710
|
-
* - Boolean attributes should be passed in the
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
715
|
-
*
|
|
744
|
+
* - Boolean attributes should be passed in the format `attribute="${() => true}"`. `false` attributes won't be rendered. `true` attributes will be rendered without a value.
|
|
745
|
+
* ```javascript
|
|
746
|
+
* const Input = Component.create`
|
|
747
|
+
* <input type="text" disabled=${({ options }) => options.disabled} />
|
|
748
|
+
* `;
|
|
749
|
+
* ```
|
|
716
750
|
* - If the interpolated function returns a component instance, it will be added as a child component.
|
|
717
751
|
* - If the interpolated function returns an array, each item will be evaluated as above.
|
|
718
|
-
*
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
738
|
-
*
|
|
739
|
-
*
|
|
752
|
+
* ```javascript
|
|
753
|
+
* // Create a button component.
|
|
754
|
+
* const Button = Component.create`
|
|
755
|
+
* <button class="button">
|
|
756
|
+
* ${({ options }) => options.renderChildren()}
|
|
757
|
+
* </button>
|
|
758
|
+
* `;
|
|
759
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
760
|
+
* const Navigation = Component.create`
|
|
761
|
+
* <nav>
|
|
762
|
+
* ${({ options }) => options.items.map(
|
|
763
|
+
* item => Button.mount({ renderChildren: () => item.label })
|
|
764
|
+
* )}
|
|
765
|
+
* </nav>
|
|
766
|
+
* `;
|
|
767
|
+
* // Create a header component. Add navigation as a child.
|
|
768
|
+
* const Header = Component.create`
|
|
769
|
+
* <header>
|
|
770
|
+
* ${({ options }) => Navigation.mount({ items : options.items})}
|
|
771
|
+
* </header>
|
|
772
|
+
* `;
|
|
773
|
+
* ```
|
|
740
774
|
* - Child components can be added using a component tag.
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
757
|
-
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
775
|
+
* ```javascript
|
|
776
|
+
* // Create a button component.
|
|
777
|
+
* const Button = Component.create`
|
|
778
|
+
* <button class="button">
|
|
779
|
+
* ${({ options }) => options.renderChildren()}
|
|
780
|
+
* </button>
|
|
781
|
+
* `;
|
|
782
|
+
* // Create a navigation component. Add buttons as children. Iterate over items.
|
|
783
|
+
* const Navigation = Component.create`
|
|
784
|
+
* <nav>
|
|
785
|
+
* ${({ options, partial }) => options.items.map(
|
|
786
|
+
* item => partial`<${Button}>${item.label}</${Button}>`
|
|
787
|
+
* )}
|
|
788
|
+
* </nav>
|
|
789
|
+
* `;
|
|
790
|
+
* // Create a header component. Add navigation as a child.
|
|
791
|
+
* const Header = Component.create`
|
|
792
|
+
* <header>
|
|
793
|
+
* <${Navigation} items="${({ options }) => options.items}" />
|
|
794
|
+
* </header>
|
|
795
|
+
* `;
|
|
796
|
+
* ```
|
|
763
797
|
* - If the tagged template contains only one expression that mounts a component, or the tags are references to a component, the component will be considered a <b>container</b>. It will render a single component as a child. `this.el` will be a reference to that child component's element.
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
770
|
-
*
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
*
|
|
798
|
+
* ```javascript
|
|
799
|
+
* // Create a button component.
|
|
800
|
+
* const Button = Component.create`
|
|
801
|
+
* <button class="${({ options }) => options.className}">
|
|
802
|
+
* ${self => self.renderChildren()}
|
|
803
|
+
* </button>
|
|
804
|
+
* `;
|
|
805
|
+
* // Create a container that renders a Button component.
|
|
806
|
+
* const ButtonOk = Component.create`
|
|
807
|
+
* <${Button} className="ok">Ok</${Button}>
|
|
808
|
+
* `;
|
|
809
|
+
* // Create a container that renders a Button component, using a function.
|
|
810
|
+
* const ButtonCancel = Component.create(() => Button.mount({
|
|
811
|
+
* className: 'cancel',
|
|
812
|
+
* renderChildren: () => 'Cancel'
|
|
813
|
+
* }));
|
|
814
|
+
* ```
|
|
781
815
|
* @static
|
|
782
816
|
* @param {string|function} strings - HTML template for the component or a function that mounts a sub component.
|
|
783
817
|
* @param {...*} expressions - The expressions to be interpolated within the template.
|
|
@@ -832,7 +866,7 @@ class Component extends View {
|
|
|
832
866
|
if (typeof item !== 'undefined' && item !== null && item !== false && item !== true) {
|
|
833
867
|
if (item instanceof SafeHTML) return item;
|
|
834
868
|
if (item instanceof Component) return addChild(item);
|
|
835
|
-
return Component.sanitize(
|
|
869
|
+
return Component.sanitize(item);
|
|
836
870
|
}
|
|
837
871
|
return '';
|
|
838
872
|
}).join('');
|