UNPKG

node-red-contrib-uibuilder

Version:

Easily create data-driven web UI's for Node-RED. Single- & Multi-page. Multiple UI's. Work with existing web development workflows or mix and match with no-code/low-code features.

5 lines (4 loc) 13.1 kB
(()=>{var b=Object.defineProperty;var y=(a,e,t)=>e in a?b(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var l=(a,e,t)=>(y(a,typeof e!="symbol"?e+"":e,t),t),w=(a,e,t)=>{if(!e.has(a))throw TypeError("Cannot "+t)};var m=(a,e,t)=>(w(a,e,"read from private field"),t?t.call(a):e.get(a)),p=(a,e,t)=>{if(e.has(a))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(a):e.set(a,t)},g=(a,e,t,r)=>(w(a,e,"write to private field"),r?r.call(a,t):e.set(a,t),t);var d,c,u=class u{constructor(e){l(this,"config");l(this,"routeContainerEl");l(this,"currentRouteId");l(this,"previousRouteId");l(this,"routeIds",[]);p(this,c,!1);l(this,"safety",0);l(this,"uibuilder",!1);if(m(u,d))throw new Error("[uibrouter:constructor] Only 1 instance of a UibRouter may exist on the page.");if(!fetch)throw new Error("[uibrouter:constructor] UibRouter requires `fetch`. Please use a current browser or load a fetch polyfill.");if(!e)throw new Error("[uibrouter:constructor] No config provided");if(!e.routes)throw new Error("[uibrouter:constructor] No routes provided in routerConfig");this.config=e,this.config.routeContainer||(this.config.routeContainer="#uibroutecontainer"),!this.config.defaultRoute&&this.config.routes[0]&&this.config.routes[0].id&&(this.config.defaultRoute=this.config.routes[0].id),this.config.hide||(this.config.hide=!1),this.config.templateLoadAll||(this.config.templateLoadAll=!1),this.config.templateUnload||(this.config.templateUnload=!0),this._normaliseRouteDefns(this.config.routes),window.markdownit&&this._markdownIt(),window.uibuilder&&(this.uibuilder=!0,uibuilder.set("uibrouterinstance",this)),this._setRouteContainer(),this.config.otherLoad&&this.loadOther(this.config.otherLoad),this._updateRouteIds(),this.config.templateLoadAll===!1?this._start():(console.info("[uibrouter] Pre-loading all external templates"),Promise.allSettled(Object.values(e.routes).filter(t=>t.type&&t.type==="url").map(this._loadExternal)).then(t=>(t.filter(r=>r.status==="rejected").forEach(r=>{console.error(r.reason)}),t.filter(r=>r.status==="fulfilled").forEach(r=>{console.log("allSettled results",r,t),this._appendExternalTemplates(r.value)}),this._start(),!0)).catch(t=>{console.error(t)})),g(u,d,!0)}_setRouteContainer(){let e=document.getElementsByTagName("body")[0],t=this.routeContainerEl=document.querySelector(this.config.routeContainer);if(!t){let r=document.createElement("div");r.setAttribute("id",this.config.routeContainer.replace("#","")),e.append(r),t=this.routeContainerEl=document.querySelector(this.config.routeContainer)}}_appendExternalTemplates(e){Array.isArray(e)||(e=[e]);let t=document.getElementsByTagName("head")[0],r=0;return e.forEach(o=>{Array.isArray(o)?(console.error(...o),r++):t.append(o)}),r}async _start(){m(this,c)!==!0&&(await this.doRoute(this.keepHashFromUrl(window.location.hash)),window.addEventListener("hashchange",e=>this._hashChange(e)),document.dispatchEvent(new CustomEvent("uibrouter:loaded")),this.uibuilder&&uibuilder.set("uibrouter","loaded"),g(this,c,!0))}_hashChange(e){this.doRoute(e)}async _loadExternal(e){if(!e)throw new Error("[uibrouter:loadExternal] Error loading route template. No route definition provided.");if(!e.src)if(!e.type||e.type&&e.type!=="url")e.src=e.id;else throw new Error("[uibrouter:loadExternal] Error loading route template. `src` property not defined");let t=e.id,r;try{r=await fetch(e.src)}catch(s){throw new Error(`[uibrouter:loadExternal] Error loading route template HTML for route: ${e.id}, src: ${e.src}. Error: ${s.message}`,s)}if(r.ok===!1)throw new Error(`[uibrouter:loadExternal] Fetch failed to return data for route: ${e.id}, src: ${e.src}. Status: ${r.statusText} (${r.status})`,[e.id,e.src,r.status,r.statusText]);let o=await r.text();window.markdownit&&e.format==="md"&&(o=this.renderMarkdown(o));try{let s=document.querySelector(`#${t}`);s&&s.remove()}catch{}let i=document.createElement("template");return i.innerHTML=o,i.setAttribute("id",t),i}_applyScripts(e){e.querySelectorAll("script").forEach(r=>{let o=document.createElement("script");o.textContent=r.innerText,e.append(o),r.remove()})}_markdownIt(){if(window.markdownit&&(!this.config.mdPlugins&&window.uibuilder&&window.uibuilder.ui_md_plugins&&(this.config.mdPlugins=window.uibuilder.ui_md_plugins),u.mdOpts={html:!0,xhtmlOut:!1,linkify:!0,_highlight:!0,_strict:!1,_view:"html",langPrefix:"language-",highlight:function(e,t){if(window.hljs)if(t&&window.hljs.getLanguage(t))try{return`<pre><code class="hljs border language-${t}" data-language="${t}" title="Source language: '${t}'">${window.hljs.highlight(e,{language:t,ignoreIllegals:!0}).value}</code></pre>`}finally{}else try{let r=window.hljs.highlightAuto(e);return`<pre><code class="hljs border language-${r.language}" data-language="${r.language}" title="Source language estimated by HighlightJS: '${r.language}'">${r.value}</code></pre>`}finally{}return`<pre><code class="border">${Ui.md.utils.escapeHtml(e).trim()}</code></pre>`}},u.md=window.markdownit(u.mdOpts),this.config.mdPlugins)){if(!Array.isArray(this.config.mdPlugins)){console.error("[uibrouter:_markDownIt:plugins] Could not load plugins, config.mdPlugins is not an array");return}this.config.mdPlugins.forEach(e=>{if(typeof e=="string")u.md.use(window[e]);else{let t=Object.keys(e)[0];u.md.use(window[t],e[t])}})}}_normaliseRouteDefns(e){Array.isArray(e)||(e=[e]),e.forEach(t=>{let r=t.format||"html";r=r.toLowerCase(),r==="markdown"&&(r="md"),t.format=r})}_updateRouteIds(){this.routeIds=new Set(Object.values(this.config.routes).map(e=>e.id))}_uibRouteChange(e){!this.uibuilder||!e||(uibuilder.set("uibrouter","route changed"),uibuilder.set("uibrouter_CurrentRoute",e),uibuilder.set("uibrouter_CurrentTitle",this.routeTitle()),uibuilder.set("uibrouter_CurrentDescription",this.routeDescription()),uibuilder.set("uibrouter_CurrentDetails",this.getRouteConfigById(e)),uibuilder.sendCtrl({uibuilderCtrl:"route change",routeId:e,title:this.routeTitle(),description:this.routeDescription(),details:this.getRouteConfigById(e)}))}async doRoute(e){if(this.safety>10)throw new Error("\u{1F6AB} [uibrouter:doRoute] Safety protocol triggered, too many route bounces");if(!this.config.routes||this.config.routes<1)return;e||(e=this.config.defaultRoute);let t=this.routeContainerEl;if(!t)throw new Error("[uibrouter:doRoute] Cannot route, has router.setup() been called yet?");let r=this.keepHashFromUrl(window.location.hash);e||(e=r);let o,i;if(typeof e=="string"){if(o=this.keepHashFromUrl(e),i=r,o===""&&this.config.defaultRoute&&(o=this.config.defaultRoute),o!==r){window.location.hash=`#${o}`;return}}else if(e.type==="hashchange"){let n=e.newURL;if(n.includes("#"))i=this.keepHashFromUrl(e.oldURL),o=this.keepHashFromUrl(n);else return}else{i=r;try{o=this.keepHashFromUrl(e.target.attributes.href.value)}catch{throw new Error("[uibrouter:doRoute] No valid route found. Event.target does not have an href attribute")}}let s=!1;if(!o||!this.routeIds.has(o)){document.dispatchEvent(new CustomEvent("uibrouter:route-change-failed",{detail:{newRouteId:o,oldRouteId:i}})),this.uibuilder&&uibuilder.set("uibrouter","route change failed"),o===i&&(i=""),console.error(`[uibrouter:doRoute] No valid route found. Either pass a valid route name or an event from an element having an href of '#${o}'. Route id requested: '${o}'`),this.safety++,this.doRoute(i||"");return}if(this.config.hide){if(i){let h=document.querySelector(`div[data-route="${i}"]`);h&&(h.style.display="none")}let n=document.querySelector(`div[data-route="${o}"]`);if(n)n.style.removeProperty("display"),s=!0;else try{s=await this.loadRoute(o)}catch(h){console.error("[uibrouter:doRoute] ",h),s=!1}}else{t.replaceChildren();try{s=await this.loadRoute(o)}catch(n){console.error("[uibrouter:doRoute] ",n),s=!1}}if(s===!1){document.dispatchEvent(new CustomEvent("uibrouter:route-change-failed",{detail:{newRouteId:o,oldRouteId:i}})),this.uibuilder&&uibuilder.set("uibrouter","route change failed"),o===i&&(i=""),console.error(`[uibrouter:doRoute] Route content for '${o}' could not be shown, reverting to old route '${i}'`),this.safety++,this.doRoute(i||"");return}this.safety=0,this.config.templateUnload&&this.unloadTemplate(i),this.currentRouteId=o,this.previousRouteId=i,t.dataset.currentRoute=o,this.setCurrentMenuItems(),document.dispatchEvent(new CustomEvent("uibrouter:route-changed",{detail:{newRouteId:o,oldRouteId:i}})),this._uibRouteChange(o)}loadOther(e){if(!e)throw new Error("[uibrouter:loadOther] At least 1 load definition must be provided");Array.isArray(e)||(e=[e]),e.forEach(async t=>{let r=document.querySelector(t.container);if(!r)return;let o;try{o=await fetch(t.src)}catch(n){throw new Error(`[uibrouter:loadOther] Error loading template HTML for '${t.id}', src: '${t.src}'. Error: ${n.message}`,n)}if(o.ok===!1)throw new Error(`[uibrouter:loadOther] Fetch failed to return data '${t.id}', src: '${t.src}'. Status: ${o.statusText} (${o.status})`,[t.id,t.src,o.status,o.statusText]);let i=await o.text(),s=document.createElement("div");s.innerHTML=i,s.id=t.id,r.append(s),this._applyScripts(r.lastChild)})}async loadRoute(e,t){t||(t=this.routeContainerEl);let r;try{r=await this.ensureTemplate(e)}catch(s){throw new Error(`[uibrouter:loadRoute] No template for route id '${e}'. ${s.message}`)}let o=r.content.cloneNode(!0);this.isRouteExternal(e)&&this._applyScripts(o);let i=document.createElement("div");i.dataset.route=e,i.append(o);try{t.append(i)}catch(s){throw new Error(`[uibrouter:loadRoute] Failed to apply route id '${e}'. ${s.message}`)}return document.dispatchEvent(new CustomEvent("uibrouter:route-loaded",{routeId:e})),!0}async ensureTemplate(e){if(!e||!this.routeIds.has(e))throw new Error(`[uibrouter:ensureTemplate] No valid route id provided. Route ID: '${e}'`);let t=document.querySelector(`#${e}`);if(!t){let r=this.getRouteConfigById(e);if(r.type&&r.type==="url"){let o;try{o=await this._loadExternal(r)}catch(i){throw new Error(i.message,i)}if(!o)throw new Error(`[uibrouter:ensureTemplate] No route template found for route selector '#${e}'. Does the link url match a defined route id?`);if(this._appendExternalTemplates(o),t=document.querySelector(`#${e}`),!t)throw new Error(`[uibrouter:ensureTemplate] No valid route template found for external route selector '#${e}'`)}else throw new Error(`[uibrouter:ensureTemplate] No route template found for internal route selector '#${e}'. Ensure that a template element with the matching ID exists in the HTML.`)}return t}getRouteConfigById(e){return Object.values(this.config.routes).filter(t=>t.id===e)[0]}isRouteExternal(e){let t=this.getRouteConfigById(e);return!!(t&&t.type==="url")}defaultRoute(){this.config.defaultRoute&&this.doRoute(this.config.defaultRoute)}removeHash(){history.pushState("",document.title,window.location.pathname+window.location.search)}noRoute(){this.removeHash(),this.routeContainerEl.replaceChildren()}keepHashFromUrl(e){return e?e.replace(/^.*#(.*)/,"$1").replace(/\?.*$/,""):""}routeList(e){return e===!0?this.routeIds.map(t=>e===!0?`#${t.id}`:t.id):this.routeIds}addRoutes(e){Array.isArray(e)||(e=[e]),this._normaliseRouteDefns(e),this.config.routes.push(...e),this._updateRouteIds(),document.dispatchEvent(new CustomEvent("uibrouter:routes-added",{detail:e})),this.uibuilder&&uibuilder.set("uibrouter","routes added"),this.config.templateLoadAll&&Promise.allSettled(Object.values(e).filter(t=>t.type&&t.type==="url").map(this._loadExternal)).then(t=>(t.filter(r=>r.status==="rejected").forEach(r=>{console.error(r.reason)}),this.config.routes.push(...e),this._updateRouteIds(),document.dispatchEvent(new CustomEvent("uibrouter:routes-added",{detail:e})),this.uibuilder&&uibuilder.set("uibrouter","routes added"),!0)).catch(t=>{console.error(t)})}unloadTemplate(e,t){if(t||(t=!0),!e||!this.isRouteExternal(e)||t===!0&&!this.isRouteExternal(e))return;let r=document.querySelector(`#${e}`);r&&r.remove()}deleteTemplates(e,t){t||(t=!0),(!e||e==="*")&&(e=[...this.routeIds]),Array.isArray(e)||(e=[e]),e.forEach(r=>{t===!0&&!this.isRouteExternal(r)||this.unloadTemplate(r,t)})}setCurrentMenuItems(){document.querySelectorAll("li[data-route]").forEach(t=>{t.dataset.route===this.currentRouteId?(t.classList.add("currentRoute"),t.setAttribute("aria-current","page")):(t.classList.remove("currentRoute"),t.removeAttribute("aria-current"))})}routeTitle(){let e=this.currentRoute()||{};return e.title||e.id||"[ROUTE NOT FOUND]"}routeDescription(){let e=this.currentRoute()||{};return e.description||e.id||"[ROUTE NOT FOUND]"}currentRoute(){return this.getRouteConfigById(this.currentRouteId)}renderMarkdown(e){if(window.markdownit){u.md||this._markdownIt();try{return u.md.render(e.trim())}catch(t){return console.error(`[uibrouter:renderMarkdown] Could not render Markdown. ${t.message}`,t),'<p class="border error">Could not render Markdown<p>'}}}};d=new WeakMap,c=new WeakMap,l(u,"version","1.4.0"),p(u,d,!1),l(u,"mdOpts"),l(u,"md");var f=u;var R=f;window.UibRouter?console.warn("`UibRouter` already assigned to window. Have you tried to load it more than once?"):window.UibRouter=f;})(); //# sourceMappingURL=uibrouter.iife.min.js.map