diff --git a/.gitignore b/.gitignore index 671a5be..5b53730 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/dist + # Caches /node_modules diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8e39a4c --- /dev/null +++ b/.npmignore @@ -0,0 +1,13 @@ +.github/ +.husky/ +examples/ +node_modules/ +tests/ +.editorconfig +.eslintignore +.prettierignore +mirlo.code-workspace +mirlo.png +package-lock.json +rollup.config.mjs +setup-jest.mjs diff --git a/README.md b/README.md index d09e8d7..3681a1b 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ app.registerComponent('demo01', Demo01); ### Available Services: -- requests: Do HTTP operations -- localStorage: Do local storage operations -- sessionStorage: Do session storage operations +- requests: HTTP operations (always included) +- localStorage: Local storage operations +- sessionStorage: Session storage operations ### Example @@ -66,14 +66,12 @@ app.registerComponent('demo01', Demo01); import {Component} from 'mirlo'; export default class Demo01 extends Component { - useServices = ['requests']; + useServices = ['localStorage']; onStart() { super.onStart(); - this.dom_el.innerHTML = 'Hello World!'; - this.requests - .postJSON('/demo01') - .then(result => (this.dom_el.innerHTML = result)); + const username = this.localStorage.getItem('username'); + this.dom_el.innerHTML = `Hello ${username}!`; } } ``` diff --git a/dist/mirlo.mjs b/dist/mirlo.mjs deleted file mode 100644 index 4074987..0000000 --- a/dist/mirlo.mjs +++ /dev/null @@ -1 +0,0 @@ -function e(e,t,s){document.querySelectorAll(t).forEach((t=>{t.addEventListener(e,s)}))}function t(e,t,s){document.querySelectorAll(t).forEach((t=>{t.removeEventListener(e,s)}))}function s(e){return Object.hasOwn(e,"mirlo")||(e.mirlo={}),e.mirlo}function o(e){const t=s(e);if(Object.hasOwn(t,"component_obj"))return t.component_obj}var r={component_obj:null,set(e,t,s){const o=typeof s;if(this.component_obj&&("string"===o||"number"===o)){this.component_obj.queryAll(`[data-component-state-binds*='${t}-']`).forEach((e=>{e.dataset.componentStateBinds.split(" ").forEach((o=>{const r=o.split("-");r[0]===t&&(1===r.length?e.textContent=s:"html"===r[1]?e.innerHTML=s:e.setAttribute(r[1],s))}))}))}return Reflect.set(...arguments)}};class i{useServices=[];fetchData={};data={};#e=null;#t=[];#s={};state=null;constructor(e,t,s){-1===this.useServices.indexOf("requests")&&this.useServices.push("requests"),this.options=s||{},this.setParent(e),this.setElement(t);const o=Object.assign({},r,{component_obj:this});this.state=new Proxy(this.#s,o)}onWillStart(){const e=[];for(const t in this.fetchData)e.push(this.requests.postJSON(this.fetchData[t].endpoint,this.fetchData[t].data).then((e=>(this.data[t]=e,e))));return Promise.all(e)}onStart(){for(const e in this.events){const[t,...s]=e.split(" "),o=s&&s.join(" ")||null;(o&&this.query(o)||this.dom_el).addEventListener(t,this.events[e].bind(this))}}remove(){this.dom_el.remove()}destroy(){this.#t=[];for(const e in this.events){const[t,...s]=e.split(" "),o=s&&s.join(" ")||null;(o&&this.query(o)||this.dom_el).removeEventListener(t,this.events[e].bind(this))}}setParent(e){this.#e=e,this.#e&&this.#e.addChildren(this)}getParent(){return this.#e}addChildren(e){this.#t.push(e)}removeChildren(e){this.#t=this.#t.filter((t=>t!==e))}setElement(e){this.dom_el=e}queryAll(e){return this.dom_el.querySelectorAll(e)}query(e){return this.dom_el.querySelector(e)}}const n=new class extends i{#o={components:{},services:{}};#r={};#i=null;constructor(e,t,s,o){super(e,t,s),this.__data=o}destroy(){super.destroy();const e=Object.values(this.#r);for(const t of e)t.destroy();this.#r=[],this.#i.disconnect(),t("click","[class~='dropdown']"),t("click","[data-dismiss]")}registerComponent(e,t){Object.hasOwn(this.#o.components,e)?console.warn(`Already exists a component called '${e}'!`):this.#o.components[e]=t}getComponentClass(e){return this.#o.components[e]}invokeComponent(e,...t){const s=this.getComponentClass(e);if(s){const e=new s(this,...t);this.#n(e),e.onWillStart().then((()=>{e.onStart()}))}else console.warn(`The component '${e}' don't exists!`)}registerService(e,t,s=!1){!Object.hasOwn(this.#o.services,e)||s?this.#o.services[e]=t:console.warn(`Already exists a service called '${e}'!`)}getServiceClass(e){return this.#o.services[e]}getService(e){return this.#r[e]}getComponentById(e){const t=this.query(`#${e}`);if(t)return o(t)}onWillStart(){return super.onWillStart().then((()=>this.#a())).then((()=>this.#c()))}onStart(){super.onStart(),this.#i=new MutationObserver(this.#l.bind(this)),this.#i.observe(document.body,{childList:!0,subtree:!0}),e("click","[class~='dropdown']",this.#h.bind(this)),e("click","[data-dismiss]",this.#d.bind(this))}#a(){for(const e in this.#o.services)this.#r[e]=new this.#o.services[e];const e=[],t=Object.keys(this.#r);for(const s of t){const t=this.#r[s];e.push(t.onWillStart())}return Promise.all(e)}#c(){const e=[],t=this.queryAll("[data-component]");for(const r of t){if(o(r))continue;const t=r.dataset.component,i=this.getComponentClass(t),n=r.closest("[data-component]"),a=n&&o(n),c={};if(Object.keys(r.dataset).forEach((e=>{if(e.startsWith("componentOption")){const t=e.replace(/^componentOption/,"").toLowerCase();c[t]=r.dataset[e]}})),i){const t=new i(a||this,r,c);this.#n(t);const o=s(r);Object.assign(o,{component_obj:t}),e.push(t.onWillStart().then((()=>t.onStart())))}else console.warn(`Can't found the '${t}' component!`,r)}return Promise.all(e)}#n(e){for(const t of e.useServices)e[t]=this.#r[t]}#l(e){let t=!1;const s=function(e){for(const t of e.childNodes)s(t);const t=o(e);t&&t.destroy()};e.forEach((e=>{if("childList"===e.type){e.addedNodes.length&&(t=!0);for(const t of e.removedNodes)s(t)}})),t&&this.#c()}#h(e){this.query(e.currentTarget.dataset.target).classList.toggle("hidden")}#d(e){const t=`.${e.currentTarget.datatset.dismiss}`;e.currentTarget.closest(t).remove()}}(null,document.body);class a{onWillStart(){return Promise.resolve()}destroy(){throw Error("Not Implemented!")}}class c extends a{storage=null;getItem(e){return this.storage&&JSON.parse(this.storage.getItem(e))||void 0}setItem(e,t,s){try{return this.storage.setItem(e,JSON.stringify(t))}catch(o){if(console.error(`[StorageService] Can't set the item '${e}' = '${t}'`),s){this.#u(o)&&s(o)}}return!1}removeItem(e){return this.storage&&this.storage.removeItem(e)||void 0}#u(e){return"QuotaExceededError"===e.name}}class l extends c{storage=localStorage}class h extends c{storage=sessionStorage}class d extends a{MESSAGES={e200:"200: Invalid server result!"};getHeaders(e){return e}async postJSON(e,t){const s=(await fetch(e,{method:"POST",mode:"same-origin",cache:"no-cache",credentials:"same-origin",headers:this.getHeaders({"Content-Type":"application/json"}),redirect:"follow",referrerPolicy:"same-origin",body:JSON.stringify(t)})).json();if(this.checkServerResult(s))return s;throw Error(this.MESSAGES.e200)}async post(e,t,s="default"){let o=!1;if("object"==typeof t){o=new URLSearchParams;for(const e in t)o.append(e,t[e])}else"string"==typeof t&&(o=t);const r=(await fetch(e,{method:"POST",mode:"same-origin",cache:s,credentials:"same-origin",headers:this.getHeaders({"Content-Type":"application/x-www-form-urlencoded"}),redirect:"follow",referrerPolicy:"same-origin",body:o})).json();if(this.checkServerResult(r))return r;throw Error(this.MESSAGES.e200)}async get(e,t="default"){return(await fetch(e,{method:"GET",mode:"same-origin",cache:t,credentials:"same-origin",headers:this.getHeaders(),redirect:"follow",referrerPolicy:"same-origin"})).blob()}checkServerResult(e){if(!e||void 0===e)return!0}}function u(e,t){Object.keys(t).forEach((s=>{e.setAttribute(s,t[s])}))}class m extends i{LAZY_ELEMENT_CLASS="lazy";getNormalizedAttributes(e,t=!1){const s={};return Object.keys(e.dataset).forEach((o=>{if(o.startsWith("lazy")){const r=o.replace(/^lazy/,"").toLowerCase(),i=e.getAttribute(r),n=e.dataset[o];i&&i===n||(s[r]=n,t&&e.removeAttribute(o))}})),s}applyLazyAttributes(e){e.removeClass(this.LAZY_ELEMENT_CLASS);const t=this.getNormalizedAttributes(e,!0);if(e?.dataset.createTag){const s=document.createElement(e.dataset.createTag);u(s,t),e.replaceWith(s)}else u(e,t)}getLazyElements(){return this.queryAll(`.${this.LAZY_ELEMENT_CLASS}`)}}class p extends m{events={[`click .${this.LAZY_ELEMENT_CLASS}`]:this.#m};#m(e){this.applyLazyAttributes(e.currentTarget)}}class f extends m{events={scroll:this.#p};constructor(){super(...arguments),Object.assign(this.options,{bounce_time_scroll:75,zone_y_offset:150,zone_x_offset:75},this.options),this.load=function(e,t=300){let s=null;return(...o)=>{clearTimeout(s),s=setTimeout((()=>{e.apply(this,o)}),t)}}(this.#f.bind(this),this.options.bounce_time_scroll)}onStart(){super.onStart(),this.#f()}#f(){this.getLazyElements().forEach((e=>{this.#g(e)&&this.applyLazyAttributes(e)}))}#y(e){const t=e.offset(),s=t.left,o=t.top;return[s,o,s+e.width(),o+e.height()]}#g(e){let[t,s,o,r]=this.#y(this.dom_el);t-=this.options.zone_x_offset,s-=this.options.zone_y_offset,o+=this.options.zone_x_offset,r+=this.options.zone_y_offset;const[i,n,a,c]=this.#y(e);return i<=o&&n<=r&&a>=t&&c>=s}#p(){this.load()}}n.registerService("requests",d),n.registerService("localStorage",l),n.registerService("sessionStorage",h),n.registerComponent("lazyClick",p),n.registerComponent("lazyScroll",f),window.addEventListener("load",(()=>{n.onWillStart().then((()=>{n.onStart()}))}));export{i as Component,p as LazyClickComponent,f as LazyScrollComponent,l as LocalStorageService,d as RequestsService,a as Service,h as SessionStorageService,n as default}; diff --git a/src/js/components/lazy.mjs b/examples/components/lazy.mjs similarity index 81% rename from src/js/components/lazy.mjs rename to examples/components/lazy.mjs index c1727fd..3e43639 100644 --- a/src/js/components/lazy.mjs +++ b/examples/components/lazy.mjs @@ -1,5 +1,5 @@ -import domSetAttributes from '../utils/dom_set_attributes'; -import Component from '../base/component'; +import $ from 'jquery-slim'; +import {Component} from 'mirlo'; export default class LazyComponent extends Component { LAZY_ELEMENT_CLASS = 'lazy'; @@ -29,11 +29,10 @@ export default class LazyComponent extends Component { dom_el.removeClass(this.LAZY_ELEMENT_CLASS); const attrs = this.getNormalizedAttributes(dom_el, true); if (dom_el?.dataset.createTag) { - const new_dom_elm = document.createElement(dom_el.dataset.createTag); - domSetAttributes(new_dom_elm, attrs); + const new_dom_elm = $(`<${dom_el.dataset.createTag}>`, attrs); dom_el.replaceWith(new_dom_elm); } else { - domSetAttributes(dom_el, attrs); + $(dom_el).attr(attrs); } } diff --git a/src/js/components/lazy_click.mjs b/examples/components/lazy_click.mjs similarity index 100% rename from src/js/components/lazy_click.mjs rename to examples/components/lazy_click.mjs diff --git a/src/js/components/lazy_scroll.mjs b/examples/components/lazy_scroll.mjs similarity index 93% rename from src/js/components/lazy_scroll.mjs rename to examples/components/lazy_scroll.mjs index 22eefa1..1bd5046 100644 --- a/src/js/components/lazy_scroll.mjs +++ b/examples/components/lazy_scroll.mjs @@ -1,4 +1,4 @@ -import debounce from '../utils/debounce'; +import $ from 'jquery-slim'; import LazyComponent from './lazy'; export default class LazyScrollComponent extends LazyComponent { @@ -17,7 +17,7 @@ export default class LazyScrollComponent extends LazyComponent { }, this.options, ); - this.load = debounce( + this.load = $.debounce( this.#doLazyLoad.bind(this), this.options.bounce_time_scroll, ); @@ -37,7 +37,8 @@ export default class LazyScrollComponent extends LazyComponent { }); } - #getDimensions($elm) { + #getDimensions(dom_el) { + const $elm = $(dom_el); const $offset = $elm.offset(); const left = $offset.left; const top = $offset.top; diff --git a/package.json b/package.json index f199d28..bd81da1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mirlo", - "version": "0.0.3", + "version": "0.1.0", "type": "module", "keywords": [ "initiator", diff --git a/src/js/mirlo.mjs b/src/js/mirlo.mjs index 4348bcc..ab5fee5 100644 --- a/src/js/mirlo.mjs +++ b/src/js/mirlo.mjs @@ -1,4 +1,4 @@ -// Copyright (C) 2022 Alexandre Díaz +// Copyright (C) 2024 Alexandre D. Díaz import {app} from './base/app'; import Component from './base/component'; import Service from './base/service'; @@ -6,16 +6,10 @@ import Service from './base/service'; import {LocalStorageService, SessionStorageService} from './services/storage'; import RequestsService from './services/requests'; -import LazyClickComponent from './components/lazy_click'; -import LazyScrollComponent from './components/lazy_scroll'; - app.registerService('requests', RequestsService); app.registerService('localStorage', LocalStorageService); app.registerService('sessionStorage', SessionStorageService); -app.registerComponent('lazyClick', LazyClickComponent); -app.registerComponent('lazyScroll', LazyScrollComponent); - // On Start APP window.addEventListener('load', () => { app.onWillStart().then(() => { @@ -29,7 +23,5 @@ export { LocalStorageService, SessionStorageService, RequestsService, - LazyClickComponent, - LazyScrollComponent, }; export default app; diff --git a/src/js/utils/debounce.mjs b/src/js/utils/debounce.mjs deleted file mode 100644 index 052becd..0000000 --- a/src/js/utils/debounce.mjs +++ /dev/null @@ -1,9 +0,0 @@ -export default function (func, timeout = 300) { - let timer = null; - return (...args) => { - clearTimeout(timer); - timer = setTimeout(() => { - func.apply(this, args); - }, timeout); - }; -} diff --git a/src/js/utils/dom_set_attributes.mjs b/src/js/utils/dom_set_attributes.mjs deleted file mode 100644 index 695008d..0000000 --- a/src/js/utils/dom_set_attributes.mjs +++ /dev/null @@ -1,5 +0,0 @@ -export default function (dom_el, attrs) { - Object.keys(attrs).forEach(key => { - dom_el.setAttribute(key, attrs[key]); - }); -}