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.

3 lines (2 loc) 16.3 kB
"use strict";(()=>{var C=Object.defineProperty;var y=a=>{throw TypeError(a)};var R=(a,e,t)=>e in a?C(a,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):a[e]=t;var l=(a,e,t)=>R(a,typeof e!="symbol"?e+"":e,t),v=(a,e,t)=>e.has(a)||y("Cannot "+t);var w=(a,e,t)=>(v(a,e,"read from private field"),t?t.call(a):e.get(a)),b=(a,e,t)=>e.has(a)?y("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(a):e.set(a,t),E=(a,e,t,r)=>(v(a,e,"write to private field"),r?r.call(a,t):e.set(a,t),t);var f,p,s=class s{constructor(e){l(this,"config");l(this,"routeContainerEl");l(this,"currentRouteId");l(this,"previousRouteId");l(this,"routeIds",[]);b(this,p,!1);l(this,"safety",0);l(this,"uibuilder",!1);if(w(s,f))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.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.routeMenus&&this.createMenus(this.config.routeMenus),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)})),E(s,f,!0)}_setRouteContainer(){if(this.config.routeContainer||(this.config.routeContainer="#uibdefaultroutecontainer",console.warn("[uibrouter:constructor] No route container defined in config, using default: `#uibdefaultroutecontainer`")),!(this.routeContainerEl=document.querySelector(this.config.routeContainer)))if(this.config.routeContainer==="#uibdefaultroutecontainer"){let t=document.createElement("div");t.setAttribute("id",this.config.routeContainer.replace("#","")),document.body.append(t),console.warn("[uibrouter:_setRouteContainer] Route container element with CSS selector '".concat(this.config.routeContainer,"' not found in HTML. Created a new element attached to body."))}else throw new Error("[uibrouter] Route container element with CSS selector '".concat(this.config.routeContainer,"' not found in HTML. Cannot proceed."));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(){w(this,p)!==!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"),E(this,p,!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(n){throw new Error("[uibrouter:loadExternal] Error loading route template HTML for route: ".concat(e.id,", src: ").concat(e.src,". Error: ").concat(n.message),n)}if(r.ok===!1)throw new Error("[uibrouter:loadExternal] Fetch failed to return data for route: ".concat(e.id,", src: ").concat(e.src,". Status: ").concat(r.statusText," (").concat(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 n=document.querySelector("#".concat(t));n&&n.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),s.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))return'<pre><code class="hljs border language-'.concat(t,'" data-language="').concat(t,'" title="Source language: \'').concat(t,"'\">").concat(window.hljs.highlight(e,{language:t,ignoreIllegals:!0}).value,"</code></pre>");{let r=window.hljs.highlightAuto(e);return'<pre><code class="hljs border language-'.concat(r.language,'" data-language="').concat(r.language,'" title="Source language estimated by HighlightJS: \'').concat(r.language,"'\">").concat(r.value,"</code></pre>")}}return'<pre><code class="border">'.concat(Ui.md.utils.escapeHtml(e).trim(),"</code></pre>")}},s.md=window.markdownit(s.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")s.md.use(window[e]);else{let t=Object.keys(e)[0];s.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)}))}createMenus(e){if(!Array.isArray(e)||e.length<1){console.warn("[uibrouter:createMenus] No valid routeMenus array provided or is empty");return}e.forEach(t=>{if(!(t!=null&&t.id)){console.warn("[uibrouter:createMenus] Invalid menu definition: ".concat(JSON.stringify(t)));return}let r=document.getElementById(t.id);if(!r){console.warn("[uibrouter:createMenus] Menu container with id '".concat(t.id,"' not found."));return}r.style.position="relative",r.innerHTML="";let o=document.createElement("nav");t!=null&&t.label&&o.setAttribute("aria-label",t==null?void 0:t.label),(t==null?void 0:t.menuType)!=="vertical"?o.classList.add("horizontal"):o.classList.add("vertical");let i=document.createElement("button");i.classList.add("menu-toggle"),i.innerHTML='\n <svg viewBox="0 0 0.8 0.8" xmlns="http://www.w3.org/2000/svg">\n <path d="M0.1 0.15h0.6a0.05 0.05 0 0 1 0 0.1H0.1a0.05 0.05 0 1 1 0 -0.1m0 0.2h0.6a0.05 0.05 0 0 1 0 0.1H0.1a0.05 0.05 0 1 1 0 -0.1m0 0.2h0.6a0.05 0.05 0 0 1 0 0.1H0.1a0.05 0.05 0 0 1 0 -0.1"/>\n </svg>\n ',o.appendChild(i);let n=document.createElement("ul");n.classList.add("routemenu"),n.setAttribute("role","menubar"),this.config.routes.forEach(d=>{if(!(d!=null&&d.id))return;let g=document.createElement("li");g.setAttribute("role","none");let h=document.createElement("a");h.setAttribute("role","menuitem"),h.setAttribute("href","#".concat(d.id)),h.setAttribute("data-route",d.id),h.innerText=(d==null?void 0:d.title)||d.id,g.appendChild(h),n.appendChild(g)}),o.appendChild(n),r.appendChild(o),this.setCurrentMenuItems(),o.addEventListener("mouseup",d=>{window.innerWidth>600||n.contains(d.target)||u()}),window.addEventListener("resize",()=>{window.innerWidth>600&&c()});function u(){o.getAttribute("aria-expanded")==="true"?c():setTimeout(()=>{o.setAttribute("aria-expanded",!0),i.setAttribute("aria-expanded",!0),document.addEventListener("mouseup",c)},0)}function c(){o.setAttribute("aria-expanded",!1),i.setAttribute("aria-expanded",!1),document.addEventListener("mouseup",c)}})}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="#".concat(o);return}}else if(e.type==="hashchange"){let u=e.newURL;if(u.includes("#"))i=this.keepHashFromUrl(e.oldURL),o=this.keepHashFromUrl(u);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 n=!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 '#".concat(o,"'. Route id requested: '").concat(o,"'")),this.safety++,this.doRoute(i||"");return}if(this.config.hide){if(i){let c=document.querySelector('div[data-route="'.concat(i,'"]'));c&&(c.style.display="none")}let u=document.querySelector('div[data-route="'.concat(o,'"]'));if(u)u.style.removeProperty("display"),n=!0;else try{n=await this.loadRoute(o)}catch(c){console.error("[uibrouter:doRoute] ",c),n=!1}}else{t.replaceChildren();try{n=await this.loadRoute(o)}catch(u){console.error("[uibrouter:doRoute] ",u),n=!1}}if(n===!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 '".concat(o,"' could not be shown, reverting to old route '").concat(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){console.warn("[uibrouter:loadOther] Parent container '".concat(t.container,"' not found for '").concat(t.id,"'"));return}let o;try{o=await fetch(t.src)}catch(u){throw new Error("[uibrouter:loadOther] Error loading template HTML for '".concat(t.id,"', src: '").concat(t.src,"'. Error: ").concat(u.message),u)}if(o.ok===!1)throw new Error("[uibrouter:loadOther] Fetch failed to return data '".concat(t.id,"', src: '").concat(t.src,"'. Status: ").concat(o.statusText," (").concat(o.status,")"),[t.id,t.src,o.status,o.statusText]);let i=await o.text(),n=document.createElement("div");n.innerHTML=i,n.id=t.id,r.append(n),this._applyScripts(r.lastChild)})}async loadRoute(e,t){t||(t=this.routeContainerEl);let r;try{r=await this.ensureTemplate(e)}catch(n){throw new Error("[uibrouter:loadRoute] No template for route id '".concat(e,"'. \n ").concat(n.message))}let o=r.content.cloneNode(!0);this._applyScripts(o);let i=document.createElement("div");i.dataset.route=e,i.append(o);try{t.append(i)}catch(n){throw new Error("[uibrouter:loadRoute] Failed to apply route id '".concat(e,"'. \n ").concat(n.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: '".concat(e,"'"));let t=document.querySelector("#".concat(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 '#".concat(e,"'. Does the link url match a defined route id?"));if(this._appendExternalTemplates(o),t=document.querySelector("#".concat(e)),!t)throw new Error("[uibrouter:ensureTemplate] No valid route template found for external route selector '#".concat(e,"'"))}else throw new Error("[uibrouter:ensureTemplate] No route template found for internal route selector '#".concat(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){let t=[...this.routeIds];return e===!0?t.map(r=>e===!0?"#".concat(r.id):r.id):[...this.routeIds]}addRoutes(e){Array.isArray(e)||(e=[e]),this._normaliseRouteDefns(e),this.config.routes.push(...e),this._updateRouteIds(),this.config.routeMenus&&this.createMenus(this.config.routeMenus),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("#".concat(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], a[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==null?void 0:e.title)||(e==null?void 0: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){s.md||this._markdownIt();try{return s.md.render(e.trim())}catch(t){return console.error("[uibrouter:renderMarkdown] Could not render Markdown. ".concat(t.message),t),'<p class="border error">Could not render Markdown<p>'}}}};f=new WeakMap,p=new WeakMap,l(s,"version","7.7.4-src"),b(s,f,!1),l(s,"mdOpts"),l(s,"md");var m=s;var T=m;window.UibRouter?console.warn("`UibRouter` already assigned to window. Have you tried to load it more than once?"):window.UibRouter=m;})(); //# sourceMappingURL=uibrouter.iife.min.js.map