UNPKG

lightview

Version:

Small, simple, powerful web UI and micro front end creation ... Great ideas from Svelte, React, Vue and Riot combined.

507 lines (490 loc) 85.3 kB
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="//codepen.io/CodePenTemplates/pen/GNoNGy.css" /> <link rel="stylesheet" href="//codepen.io/CodePenTemplates/pen/XNXpgM.css" /> </head> <body> <section class="markdown-body"> <meta name="viewport" content="width=device-width, initial-scale=1"> <base target="_tab"> <style> .markdown-body { margin:opx; padding: 0px; max-width: 100%; } </style> <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/styles/default.min.css"> <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.4.0/build/highlight.min.js"></script> <script src="https://kit.fontawesome.com/aee33b06ff.js" crossorigin="anonymous"></script> <div style="position:fixed;min-width:100%;opacity:1;background:white;margin-top:0px;height:1.50em;z-index:10;"><a href="https://lightview.dev">lightview.dev</a> v1.8.1b (BETA)</div> <div id="TOC" style="position:fixed;top:3em;max-height:97%;height:97%;opacity:1;max-width:275px;"> <div id="header" "font-size:125%;font-weight:bold;"> &nbsp;<span id="toggle-button" style="display:none;float:right;font-weight:bold;margin-top:0px">&lt;&lt;</span> </div> <div class="toc" style="border:1px solid grey;margin-right:5px;border-radius:5px;overflow-x:hidden;overflow-y:auto;background:whitesmoke"> </div> </div> <div id="content" style="float:right;padding-top:0px;padding-right:10px;max-height:100vh;overflow:auto;opacity:1;position:relative;left:10px;top:3px"> <h3 id="introduction-to-lightview">Introduction to Lightview</h3> <p>Small, simple, powerful web UI and micro front end creation ...</p> <p>Great ideas from Svelte, React, Vue and Riot plus more combined into one small tool: 7.5K (minified/gzipped)</p></p> <p style="width:99%;"> A ToDo demo does not get much simpler than this: <span style="float:right"> <i class="fa-brands fa-codepen"></i> <a href="https://codepen.io/anywhichway/details/ExQPYZL">lightview-todo</a> </span> </p> <pre height="100"><code src="./examples/todo.html"></code></pre> <h4 id="what-you-get">What You Get</h4> <ol> <li><p>Single file and <a href="#local-templates" target=_self>template</a> components.</p> </li> <li><p><a href="#sandboxed-components">Sandboxed remote components</a> and micro front ends</a>.</p> </li> <li><p><a href="#unit-testing">Unit testable</a> components and a <a href="#debugging">debug mode</a> for using standard JavaScript debuggers</a>. </p> </li> <li><p>No pre-deployment transpilation/compilation required. </p> </li> <li><p>Svelte like variable usage, i.e. write your state modifying code like normal code.</p> </li> <li><p>Extended variable type declarations including <code>min</code>, <code>max</code> and <code>step</code> on <code>number</code> or limits on <code>string</code> and <code>array</code> lengths.</p> </li> <li><p><a href="#variables">TypeScript like</a> runtime type checking of variables in components.</p> </li> <li><p>Automatic server retrieval and update of variables declared as <code>remote</code>.</p> </li> <li><p>Automatic import, export, cross-component sync, or reactive response to attributes/props/variables. See <a href="#super-variable">superVariable</a>.</p> </li> <li><p><a href="#auto-binding-inputs-and-forms">Automatic input field variable creation and binding</a>.</p> </li> <li><p><a href="#attribute-directives">Attribute directives</a> like <code>l-if</code>, and a single powerful <code>l-for</code> that handles array and object keys, values, and entries.</p> </li> <li><p>Reactive string template literals for content and attribute value replacement.</p> </li> <li><p>No virtual DOM. The Lightview dependency tracker laser targets just those nodes that need updates.</p> </li> <li><p>SPA, and MPA friendly ... somewhat SEO friendly and short steps away from fully SEO friendly.</p> </li> <li><p>A <a href="components">component library</a> including charts and gauges that work in Markdown files.</p> </li> <li><p>Lots of live <a href="#examples">editable examples</a>.</p> <p>If you like what you get on the front-end with <code>Lightview</code>, check out our back-end reactive framework <code>Watchlight</code> at <a href="https://watchlight.dev">https://watchlight.dev</a>.</p> </li> </ol> <h4 id="usage">Usage</h4> <p>Install it from <a href="https://www.npmjs.com/package/lightview">NPMJS</a>. Or, visit the repository on <a href="https://www.github.com/anywhichway/lightview">GitHub</a>. Note, we will actively iterate on CodePen and the NPM or GitHub versions may not be the most recent. If you want the bleeding edge <a href="https://000686818.codepen.website/lightview.js">download it from CodePen</a> and include it in your build pipeline.</p> <p>It should be evident from the todo example above, Lightview components are just HTML files similar to Riot or Vue&#39;s SFCs. And, for the most part, the code you write looks like regular JavaScript ... we modeled this on Svelte.</p> <p>The script blocks in these files are set to type <code>lightview/module</code> and inside them the variable <code>self</code> is bound to the component.</p> <p>The HTML in the files is rendered in a shadow DOM node and the style blocks and scripts are isolated to that shadow DOM.</p> <p>You use the components in other files by inserting the Lightview script in the head of the file where you want ot use the component and load the components using <code>link</code> tags, e.g.</p> <pre><code>&lt;head&gt; &lt;<span class="hljs-meta">link</span> href=<span class="hljs-string">&quot;../components/gantt/gantt.html&quot;</span> rel=<span class="hljs-string">&quot;module&quot;</span>&gt; &lt;script src=<span class="hljs-string">&quot;./lightview.js&quot;</span>&gt;&lt;/script&gt; &lt;/head&gt; &lt;body&gt; &lt;l-gantt id=<span class="hljs-string">&quot;myChart&quot;</span> style=<span class="hljs-string">&quot;height:500px;&quot;</span> <span class="hljs-meta">title</span>=<span class="hljs-string">&quot;Research Project&quot;</span> hidden l-unhide&gt; { <span class="hljs-meta">options</span>: { }, rows: [ [<span class="hljs-string">&#x27;Research&#x27;</span>, <span class="hljs-string">&#x27;Find sources&#x27;</span>,<span class="hljs-string">&quot;2015-01-01&quot;</span>, <span class="hljs-string">&quot;2015-01-05&quot;</span>, <span class="hljs-meta">null</span>, 100, <span class="hljs-meta">null</span>], [<span class="hljs-string">&#x27;Write&#x27;</span>, <span class="hljs-string">&#x27;Write paper&#x27;</span>,<span class="hljs-meta">null</span>,<span class="hljs-string">&quot;2015-01-09&quot;</span>, <span class="hljs-string">&quot;3d&quot;</span>, 25, <span class="hljs-string">&#x27;Research,Outline&#x27;</span>], [<span class="hljs-string">&#x27;Cite&#x27;</span>, <span class="hljs-string">&#x27;Create bibliography&#x27;</span>,<span class="hljs-meta">null</span>, <span class="hljs-string">&quot;2015-01-07&quot;</span>,<span class="hljs-string">&quot;1d&quot;</span> , 20, <span class="hljs-string">&#x27;Research&#x27;</span>], [<span class="hljs-string">&#x27;Complete&#x27;</span>, <span class="hljs-string">&#x27;Hand in paper&#x27;</span>, <span class="hljs-meta">null</span>, <span class="hljs-string">&quot;2015-01-10&quot;</span>, <span class="hljs-string">&quot;1d&quot;</span> , 0, <span class="hljs-string">&#x27;Cite,Write&#x27;</span>], [<span class="hljs-string">&#x27;Outline&#x27;</span>, <span class="hljs-string">&#x27;Outline paper&#x27;</span>, <span class="hljs-meta">null</span>, <span class="hljs-string">&quot;2015-01-06&quot;</span>, <span class="hljs-string">&quot;1d&quot;</span> , 100, <span class="hljs-string">&#x27;Research&#x27;</span>] ] } &lt;/l-gantt&gt; &lt;/body&gt; </code></pre> <p>You can do the same in a Markdown file, just leave out the <code>head</code> and <code>body</code> tags. You can see the <a href="examples/markdown.md">source of a Markdown file</a>, or <a href="examples/markdown.html">see it rendered</a>.</p> <h3 id="api">API</h3> <p><a id="variables"></a></p> <h4 id="variables">Variables</h4> <p>Much of the power of Lightview comes from its expressive variable declarations.</p> <p>You can use the normal declarations <code>var</code>, <code>let</code>, and <code>const</code> for data that only needs to be present within a script block. For other data, you need to declare variables using <code>self.variables(options)</code>.</p> <pre><code class="language-javascript">Object <span class="hljs-keyword">self</span>.variables( {[<span class="hljs-symbol">name:</span>string]<span class="hljs-symbol">:dataType</span><span class="hljs-symbol">:string</span>, [ ...]}, [{[functionalType]<span class="hljs-symbol">:boolean|string|object</span>, ...}] ); </code></pre> <p>The available <code>dataTypes</code> are <code>&quot;any&quot;</code>, <code>&quot;array&quot;</code>, <code>&quot;boolean&quot;</code>, <code>&quot;number&quot;</code>, <code>&quot;object&quot;</code>, <code>&quot;string&quot;</code>. You can also provide any object constructor like, <code>Array</code>.</p> <p><code>FunctionalTypes</code> apply behaviors to variables. The available <code>functionalTypes</code> are <code>shared</code>, <code>reactive</code>, <code>imported</code>, <code>exported</code>, <code>observed</code>, <code>remote</code>, <code>set</code>, <code>constant</code>. These are reserved words and, with the exception of <code>set</code> and <code>constant</code>, are themselves functions that configure variable behavior.</p> <p>If a varibable is <code>required</code>, <code>set</code> or <code>constant</code> DO NOT need to be present. The <code>required</code> is enforced when attempts are made to set the variable.</p> <p><code>set</code> can take any value that is consistent with the <code>dataType</code> of the variable(s) it is used with.</p> <p><code>constant</code> can take any value that is consistent with the <code>dataType</code> of the variable(s) it is used with.</p> <p><code>set</code> and <code>constant</code> can&#39;t be used together.</p> <p>See <a href="#more-on-remote-variables">More On Remote Variables</a> for details on the <code>string</code> and <code>object</code> value configuration options for <code>remote</code>.</p> <p>As a result of the use of the variable definition approach, Lightview variable declarations look very much like <code>TypeScript</code>.</p> <p><a id="super-variable"></a> You would probably never make a variable this powerful, but the below illustrates the use of most of the types of variables.</p> <pre><code class="language-javascript">self.variables({superVariable:<span class="hljs-string">&quot;number&quot;</span>}, { set:<span class="hljs-number">1</span>, imported, exported, shared, reactive, observed, remote:remote(<span class="hljs-string">&quot;./superVariable&quot;</span>)}); observe(<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> { <span class="hljs-built_in">console</span>.log(<span class="hljs-string">&quot;superVariable changed to:&quot;</span>,superVariable); }); &lt;/script&gt; </code></pre> <p><strong><em>Note</em></strong>: <code>lightview/module</code> code is sensitive to the use/lack of of semi-colons. When in doubt, terminate your statements with semi-colons. Also, since since Lightview compiles your code into an asynchronous function, top level <code>await</code> is supported. </p> <p>Runtime type checking will be applied to ensure <code>superVariable</code> is always a number or can be coerced to a number. There is an example for <a href="#type-checking">type checking</a>.</p> <p>It will initially be set to 1 and then an attempt to import a value from the attributes of the component will be made. If no attribute by the name of the variable exists, the value will remain 1. Any time it changes, its value will be <code>exported</code> back up to the attributes. The value will also be <code>shared</code> with all instances of the same component and <code>reactive</code> HTML will be re-rendered. Also see the example <a href="#sharing-state">Sharing State</a>.</p> <p>Reactive HTML is any HTML, including attribute values (with the exception of the <code>name</code> attribute on input elements), that contains a reference or references to a variable inside a template literal.</p> <p>Because <code>superVariable</code> is <code>observed</code> you can also call <code>addEventListener(callback)</code> to register a listener that will be called any time the attribute <code>superVariable</code> changes. Attributes that are <code>observed</code> are automatically <code>imported</code>. If your needs are simple, you can just rely on <code>reactive</code> rather than define a callback using <code>addEventListener</code>.</p> <p>Because you declared <code>superVariable</code> as <code>reactive</code> and wrapped a function referencing it in <code>observe</code>, the function will be called every time <code>superVariable</code> changes.</p> <p>And, because you declared <code>superVariable</code> as <code>remote</code> at the relative URL <code>./superVariable</code>, its value will be retrieved from the server. Also, since you made it <code>reactive</code>, changes will be sent to the server as <code>PATCH</code> requests.</p> <h5 id="extended-type-definitions">Extended Type Definitions</h5> <p>There are extended type definitions that use the symbolic names of all the basic types, e.g. <code>string</code> vs <code>&quot;string&quot;</code>. These extended types can be used like their string named counterparts except they do not automatically coerce, or they can be used as functions for more sophisticated type checking, e.g. the basic types always attempt coercion, with extended types this is turned off by default, although you can turn in on. The declaration of <code>superVariable</code> above, might look like this:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> {<span class="hljs-built_in">number</span>} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables( {<span class="hljs-attr">superVariable</span>:<span class="hljs-built_in">number</span>}, <span class="hljs-comment">// note, number is not quoted</span> {<span class="hljs-attr">imported</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">exported</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">shared</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">reactive</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">observed</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">remote</span>:<span class="hljs-string">&quot;./superVariable&quot;</span>} ); <span class="hljs-keyword">await</span> superVariable; </code></pre> <p>Or, if you want to coerce and constrain the value to a range, like this:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> {<span class="hljs-built_in">number</span>} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables( {<span class="hljs-attr">superVariable</span>:<span class="hljs-built_in">number</span>({<span class="hljs-attr">coerce</span>:<span class="hljs-literal">true</span>,<span class="hljs-attr">min</span>:<span class="hljs-number">0</span>,<span class="hljs-attr">max</span>:<span class="hljs-number">1000</span>})}, <span class="hljs-comment">// note, how number is used as a function</span> {<span class="hljs-attr">imported</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">exported</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">shared</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">reactive</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">observed</span>:<span class="hljs-literal">true</span>, <span class="hljs-attr">remote</span>:remote(<span class="hljs-string">&quot;./superVariable&quot;</span>)} ); <span class="hljs-keyword">await</span> superVariable; </code></pre> <p>An example showing errors should help:</p> <pre><code><span class="hljs-keyword">const</span> {<span class="hljs-built_in">string</span>,<span class="hljs-built_in">number</span>} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables({<span class="hljs-attr">name</span>:<span class="hljs-built_in">string</span>({<span class="hljs-attr">minlength</span>:<span class="hljs-number">2</span>,<span class="hljs-attr">maxlength</span>:<span class="hljs-number">20</span>,<span class="hljs-attr">required</span>:<span class="hljs-literal">true</span>,<span class="hljs-attr">default</span>:<span class="hljs-string">&quot;anonymous&quot;</span>}),<span class="hljs-attr">age</span>:<span class="hljs-built_in">number</span>}); <span class="hljs-built_in">console</span>.log(name); <span class="hljs-comment">// will log &quot;anonymous&quot;</span> <span class="hljs-keyword">try</span> { name = <span class="hljs-string">&quot;J&quot;</span>; <span class="hljs-comment">// will throw an error since the value is too short</span> } <span class="hljs-keyword">catch</span>(e) { } <span class="hljs-keyword">try</span> { name = <span class="hljs-literal">null</span>; <span class="hljs-comment">// will throw an error since a value is required</span> } <span class="hljs-keyword">catch</span>(e) { } <span class="hljs-keyword">try</span> { age = <span class="hljs-string">&quot;10&quot;</span>; <span class="hljs-comment">// will throw an error since the value is not a number</span> } <span class="hljs-keyword">catch</span>(e) { } name = <span class="hljs-string">&quot;joe&quot;</span>; <span class="hljs-comment">// will succeed</span> age = <span class="hljs-number">10</span>; <span class="hljs-comment">// will succeed</span> </code></pre> <p>The extended types include:</p> <p><strong><code>any({required?:boolean,whenInvalid?:function,default?:any})</code></strong></p> <p><strong><code>array({coerce?:boolean,required?:boolean,whenInvalid?:function,minlength?:number,maxlength?:number,default?:Array})</code></strong></p> <ul> <li><code>minlength</code> defaults to <code>0</code></li> <li><code>maxlength</code> defaults to <code>Infinity</code></li> </ul> <p><strong><code>boolean({coerce?:boolean,required?:boolean,whenInvalid?:function,default?:boolean})</code></strong></p> <p><strong><code>number({coerce?:boolean,required?:boolean,whenInvalid?:function,min?:number,max?:number,step?:number,allowNaN?:boolean,default?:number})</code></strong></p> <ul> <li><code>min</code> defaults to <code>-Infinity</code></li> <li><code>max</code> defaults to <code>Infinity</code></li> <li><code>step</code> defaults to <code>1</code></li> <li><code>allowNaN</code> defaults to <code>true</code></li> </ul> <p><strong><code>object({coerce?:boolean,required?:boolean,whenInvalid?:function,default?:object})</code></strong></p> <p><strong><code>remote(string|object)</code></strong></p> <p>Remote must be imported from <code>types.js</code> and SHOULD be called with a configuration. See <a href="#more-on-remote-variables">More On Remote Variables</a>.</p> <p><strong><code>string({coerce?:boolean,required?:boolean,whenInvalid?:function,minlength?:number,maxlength?:number,pattern?:RegExp,default?:string})</code></strong></p> <ul> <li><code>minlength</code> defaults to <code>0</code></li> <li><code>maxlength</code> defaults to <code>Infinity</code></li> <li><code>pattern</code> ensures the value matches the RegExp prior to assignment</li> </ul> <p><strong><code>symbol({coerce?:boolean,required?:boolean,whenInvalid?:function,default?:symbol})</code></strong></p> <p>All extended types have a default <code>whenInvalid</code> parameter which throws an error when an attempt to set the varibale to an invalid value is made. A custom function can be passed in that swallows the error and returns the existing value for the variable, or <code>undefined</code>, or some other value, for example:</p> <pre><code class="language-javascript">const whenInvalid = <span class="hljs-function"><span class="hljs-params">(variable)</span> =&gt;</span> { <span class="hljs-keyword">return</span> variable.value; } </code></pre> <p>you could even go ahead and make the assignment but log a warning:</p> <pre><code class="language-javascript"><span class="hljs-keyword">const</span> whenInvalid = <span class="hljs-function">(<span class="hljs-params">variable,invalidValue</span>) =&gt;</span> { <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">`Assigning <span class="hljs-subst">${variable.name}</span>:<span class="hljs-subst">${variable.<span class="hljs-keyword">type</span>.name||variable.<span class="hljs-keyword">type</span>}</span> invalid value <span class="hljs-subst">${invalidValue}</span>); return newValue; }</span> </code></pre> <h5 id="more-on-remote-variables">More On Remote Variables</h5> <p>Below is an example of how simple it can be to set up remote sensor monitoring using remote variables. This example is talking to a simulator running on a Cloudflare Worker. The guages themselves are available in the Lightview <a href="components">component library</a>.</p> <pre height="140"><code src="./examples/sensors/index.html" contentonly></code></pre> <iframe width="100%" height="225px" src="examples/sensors/index.html"></iframe> <p>The easiest way to configure remote variables is to provide the absolute or relative unique URL to access the variable value, e.g.</p> <pre><code class="language-javascript">const {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables( {sensor1:<span class="hljs-built_in">object</span>}, {remote:remote(<span class="hljs-string">&quot;./sensors/sensor1&quot;</span>)} ); <span class="hljs-keyword">await</span> sensor1; </code></pre> <p><strong>Note</strong>: You MAY need to await the remote variable after it is declared. Future use, e.g. in template literals, will NEVER need to be awaited.</p> <p>If you do not call <code>remote</code> during your variable declaration, the assumed path to the variable will be the current file path plus the variable name, e.g.</p> <pre><code>const {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables({sensor1:<span class="hljs-built_in">object</span>}, {remote}); </code></pre> <p>is the same as</p> <pre><code>const {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables({sensor1:<span class="hljs-built_in">object</span>}, {remote:remote(<span class="hljs-string">&quot;./sensor1&quot;</span>)}); </code></pre> <p>If you call <code>remote</code> with a path that is terminates by a slash, then the variable name is appended to the path.</p> <pre><code>const {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables({sensor1:<span class="hljs-built_in">object</span>}, {remote:remote(<span class="hljs-string">&quot;https://mysite.com/sensors/&quot;</span>)}); </code></pre> <p>is the same as:</p> <pre><code>const {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables({sensor1:<span class="hljs-built_in">object</span>}, {remote:remote(<span class="hljs-string">&quot;https://mysite.com/sensors/sensor1&quot;</span>)}); </code></pre> <p>In some cases you may have an existing application that does not provide an easily addressable unique URL for each variable, in this case you can provide a configuration object providing a <code>get</code> method (as well as <code>patch</code> and <code>put</code> if the variable is reactive and sending updates to the server), along with an optional <code>path</code> and <code>ttl</code>.</p> <p>The <code>get</code> method should have the signature <code>get(path,variable)</code>. You can use the <code>path</code>, the variable definition contained in <code>variable</code>, and any variables within the closure of your method to create a URL and do your own <code>fetch</code>. Your <code>patch</code> method must parse the <code>fetch</code> response and return a Promise for JSON.</p> <p>The <code>patch</code> method should have the signature <code>patch({target,property,value,oldValue},path,variable)</code>. (Currently, remotely patched variables must be objects, in the future <code>{value,oldValue}</code> will also be legal for primitive variables).</p> <p>You can use data from the <code>target</code> object along with the <code>path</code>, the variable definition contained in <code>variable</code>, and any variables within the closure of your method to create a URL and do your own <code>fetch</code>. Your <code>patch</code> method must parse the <code>fetch</code> response and return a Promise for JSON.</p> <p>The <code>put</code> method should have the signature <code>put(target,path,variable)</code>. You can use data from the <code>target</code> object along with the <code>path</code>, the variable definition contained in <code>variable</code>, and any variables within the closure of your method to create a URL and do your own <code>fetch</code>. Your <code>put</code> method must parse the <code>fetch</code> response and return a Promise for JSON which is the current state of the variable on the server.</p> <p>The <code>ttl</code> is the number of milliseconds between server polls to refresh data. If you do not wish to poll the server, you could also implement <code>get</code> so that it establishes a websocket connection and update your variables in realtime.</p> <p>Here is an example of a custom remote variable configuration for polling sensor data:</p> <pre><code class="language-javacript"><span class="hljs-keyword">const</span> {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); self.variables( { <span class="hljs-attr">sensor1</span>:<span class="hljs-built_in">object</span>, <span class="hljs-attr">sensor2</span>:<span class="hljs-built_in">object</span> }, { <span class="hljs-attr">remote</span>: remote({ <span class="hljs-attr">path</span>: <span class="hljs-string">&quot;./sensors/&quot;</span>, <span class="hljs-attr">ttl</span>: <span class="hljs-number">10000</span>, <span class="hljs-comment">// get new data every 10 seconds</span> <span class="hljs-function"><span class="hljs-title">get</span>(<span class="hljs-params">path,variable</span>)</span> { <span class="hljs-comment">// create a normalized full path to the sensor data</span> <span class="hljs-keyword">const</span> href = <span class="hljs-keyword">new</span> URL(path + <span class="hljs-built_in">object</span>.id,<span class="hljs-built_in">window</span>.location.href).href; <span class="hljs-keyword">return</span> fetch(href) .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> { <span class="hljs-keyword">if</span>(response.status===<span class="hljs-number">200</span>) <span class="hljs-keyword">return</span> response.json(); }) } }) } ); <span class="hljs-keyword">await</span> sensor1; <span class="hljs-keyword">await</span> sensor2; </code></pre> <p>Here is partial example of a custom remote variable configuration for streaming data over a websocket:</p> <pre><code class="language-javacript"><span class="hljs-keyword">const</span> {remote} = <span class="hljs-keyword">await</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">&quot;./types.js&quot;</span>); <span class="hljs-comment">// use these in the UI so that it automatically updates</span> self.variables({<span class="hljs-attr">sensor1</span>:<span class="hljs-built_in">object</span>,<span class="hljs-attr">sensor2</span>:<span class="hljs-built_in">object</span>},{reactive}); <span class="hljs-comment">// use a variable to hold the websocket</span> self.variables( { <span class="hljs-attr">ws</span>: <span class="hljs-built_in">object</span> }, { <span class="hljs-attr">remote</span>: remote({ <span class="hljs-attr">path</span>: <span class="hljs-string">&quot;./sensors&quot;</span>, <span class="hljs-attr">ttl</span>: <span class="hljs-number">10000</span>, <span class="hljs-comment">// get new data every 10 seconds</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-title">get</span>(<span class="hljs-params">path,variable</span>)</span> { <span class="hljs-comment">// only create one socket</span> <span class="hljs-keyword">if</span>(!ws) { <span class="hljs-comment">// create a normalized full path to the sensor data</span> <span class="hljs-keyword">const</span> href = <span class="hljs-keyword">new</span> URL(path,<span class="hljs-built_in">window</span>.location.href).href.replace(<span class="hljs-string">&quot;https://&quot;</span>,<span class="hljs-string">&quot;wss://&quot;</span>); ws = <span class="hljs-keyword">new</span> WebSocket(href); <span class="hljs-comment">// do websocketty stuff, ideally in a more robust way than this!</span> ws.onmessage = <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> { <span class="hljs-keyword">const</span> {sensorName,value} = event.data; <span class="hljs-comment">// assumes sensor1 and sensor2 are the names</span> self.setVariableValue(sensorName,value); } <span class="hljs-comment">// end websockety stuff</span> <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve(ws); <span class="hljs-comment">// you must return a Promise for the socket</span> } }) } ); <span class="hljs-keyword">await</span> ws; </code></pre> <p>Since using remote variables requires running a custom server, it is not possible to demonstrate on this CodePen hosted site. Below is the source code for a very basic custom NodeJS server that will respond appropriately to remote variable requests and updates for data stored in JSON files. The full demo can be found in the <a href="https://github.com/anywhichway/lightview">GitHub repository</a>.</p> <pre height="100"><code src="./examples/remote-server.js" contentonly></code></pre> <h4 id="functions">Functions</h4> <p>We already introduced <code>observe</code> above. There are several more functions available with Lightview and components created using Lightview.</p> <h5 id="lightview">Lightview</h5> <p><strong><code>Class Lightview.createComponent(name:string, node:HTMLElement [, {framed:boolean, observer:MutationObsever}])</code></strong></p> <ul> <li>Creates a component, i.e. <code>customElement</code>, using the <code>node</code> contents as a template.</li> <li>See the example <a href="#local-templates">Local Templates</a>.</li> </ul> <p><strong><code>HTMLCustomElement Lightview.bodyAsComponent([{as = &quot;x-body&quot;, unhide:boolean, framed:boolean}])</code></strong></p> <ul> <li>This function treats the body of an HTML document as a component and is typically called in a <code>DOMContentLoaded</code> event handler.</li> <li>A shorthand way of using this is to just passs the query string ``?as=x-body` to the Lightview loading script in the head of your document.</li> <li><strong>Note</strong>, once a body is turned into a componet, its contents are in a <code>shadoDOM</code>, so using <code>document.getElementById</code> will not work. Instead, use <code>document.body.getElementById</code>. All components implement <code>getElementById</code>.</li> <li>The use of this method or the query string approach is ignored when a component is loaded as a subcomponet. Hence, you can use it to support the creation of unit testable components.</li> <li>See the example <a href="reactive-variables-and-encapsultated-style">Reactive Variables and Encapsultated Style</a>.</li> </ul> <p><strong><code>void Lightview.whenFramed(callback:function [, {isolated:boolean}])</code></strong></p> <ul> <li>Invokes <code>callback</code> when a component file detects it is being loaded in an <code>iframe</code>. </li> <li>If <code>isolated</code> is set to true, then there will be no communication with the parent window. Otherwise, message handling is automatically implemented.</li> <li>See the example <a href="#sandboxed-components">Sandboxed Components and Micro Front Ends</a>.</li> </ul> <h5 id="lifecycle-event-handlers">Lifecycle Event Handlers</h5> <p><strong><code>self.addEventListener(eventName:string,callback:function)</code></strong></p> <ul> <li><p>Adds the <code>callback</code> to be invoked when the <code>eventName</code> occurs.</p> </li> <li><p>This is actually just the standard <code>HTMLElement.addEventLister</code>, as such, it is availabled as a method on the element when created with <code>document.create</code>.</p> </li> <li><p>Valid <code>eventNames</code> include:</p> <ul> <li><code>adopted</code> which will be invoked when a component is adopted by a document with the callback as <code>callback({type:&quot;adopted&quot;,target:component})</code>.</li> <li><code>connected</code> which will be invoked when a component is added to a document DOM with the callback as <code>callback({type:&quot;connected&quot;,target:component})</code>.</li> </ul> <blockquote> <p>In order to prevent blocking, a number of Lightview component initialization functions are asynchronous. As a result, when a component is connected by the DOM, there may still be initialization work in flight. Once the Lightview <code>connected</code> event has fired, you can be sure all initialization work is complete.</p> </blockquote> <blockquote> <p>If you encouter errors that say custom methods on your components are not available to other components or external scripts, then try wrapping the code that accesses these methods in a <code>connected</code> event listener.</p> </blockquote> <ul> <li><code>disconnected</code> which will be invoked when a component is removed from a DOM with the callback as <code>callback({type:&quot;disconnected&quot;,target:component})</code>.</li> </ul> </li> </ul> <p><strong><code>addEventListener(eventName:string,callback:function)</code></strong></p> <ul> <li><p>Available in the context of <code>lightview/script</code>. Adds the <code>callback</code> to be invoked when the <code>eventName</code> occurs.</p> </li> <li><p>This is a special <code>addEventListener</code> that deals with data and events internal to the component.</p> </li> <li><p>Not preceded by <code>self.</code>. Just call directly. This is the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Worker/message_event">same way it works for web workers</a>.</p> </li> <li><p>Valid <code>eventNames</code> include:</p> <ul> <li><code>change</code> which will be invoked when variable values change with the callback as <code>callback({type:&quot;change&quot;,target:component,variableName:string,value:any,oldValue:any})</code>.</li> </ul> </li> <li><p>See the example <a href="#sharing-xor-state">Sharing Exclusive Or State</a>.</p> </li> </ul> <h5 id="component-variable-access">Component Variable Access</h5> <p>You should be careful not to overload and shadow these functions by redefining them on your component.</p> <p><strong><code>Array self.getVariable()</code></strong></p> <ul> <li>Returns an a copy of the internal structure of a variable or <code>undefined</code>. See <code>self.variables</code> below.</li> </ul> <p><strong><code>Array self.getVariableNames()</code></strong></p> <ul> <li>Returns an array of names of the currently defined variables for a component.</li> </ul> <p><strong><code>any self.getVariableValue(variableName:string)</code></strong></p> <ul> <li>Gets the current value of <code>variableName</code>. Returns <code>undefined</code> if the variable does not exist.</li> </ul> <p><strong><code>boolean self.setVariableValue(variableName:string, value:any[, {coerceTo:string|function}])</code></strong></p> <ul> <li>Sets a value for a <code>variableName</code>. Returns <code>true</code> if the variable already existed and <code>false</code> if not.</li> <li>If the variable already existed, the existing type is used and <code>coerceTo</code> is ignored.</li> <li>If the variable is created, the type is infered from the value or set to <code>coerceTo</code> (in case you want to use <code>any</code>).</li> </ul> <p><strong><code>object self.variables({[variableName]:variableType,...}[,...]})</code></strong></p> <ul> <li>Used to declare variables as described in the <a href="#variables">Variables</a> section above.</li> <li>Returns an <code>object</code>, the keys of which are variable names with the values being copies of the internal structure of the variable, e.g.</li> </ul> <pre><code class="language-javascript">self.variables({v1:<span class="hljs-string">&quot;string&quot;</span>},{imported,shared}); /* <span class="hljs-keyword">returns</span> { v1: {<span class="hljs-literal">name</span>: <span class="hljs-string">&quot;v1&quot;</span>, <span class="hljs-built_in">type</span>: <span class="hljs-string">&quot;string&quot;</span>, imported:<span class="hljs-keyword">true</span>, <span class="hljs-literal">shared</span>:<span class="hljs-keyword">true</span>} } */ self.variables({v2:<span class="hljs-string">&quot;number&quot;</span>},{exported,reactive}); /* <span class="hljs-keyword">returns</span> { v2: {<span class="hljs-literal">name</span>: <span class="hljs-string">&quot;v2&quot;</span>, value:<span class="hljs-number">2</span>, <span class="hljs-built_in">type</span>: <span class="hljs-string">&quot;number&quot;</span>, exported:<span class="hljs-keyword">true</span>, reactive:<span class="hljs-keyword">true</span>} } */ </code></pre> <h5 id="other-component-methods">Other Component Methods</h5> <p>Components are DOM nodes with a <code>shadowRoot</code> and have the standard DOM node capability, e.g. <code>getQuerySelector</code>. They also implement <code>getElementById</code> (which is normally only on a <code>document</code>).</p> <p><a id="examples"></a></p> <h3 id="examples-of-use">Examples of Use</h3> <p>All of the code examples on this site with a Reset button and LIVE label can be edited directly on the site. Or, you can open them in CodePen using the link above the example toward the right margin.</p> <p>Some examples are not implemented as Pens and can&#39;t be edited.</p> <h4 id="reactive-variables-and-encapsultated-style">Reactive Variables and Encapsultated Style</h4> <p>A basic example of Lightview to show how simple reactive UI development can be. </p> <p>If you declare variables as reactive, with the exception of the <code>name</code> attribute for input elements, any HTML referencing the variables will be automatically re-rendered. If you inspect the content of the rendered view, you will also see that this particular page has turned the body into a self rendering component called &#39;x-body&#39;.</p> <p>The <code>name</code> attribute is excluded because making dynamic substitutions for <code>name</code> can result in VERY obscure code.</p> <p> <button onclick="resetPen('QWaOyXQ')">Reset</button> <span style="float:right"> <i class="fa-brands fa-codepen"></i> <a href="https://codepen.io/anywhichway/details/QWaOyXQ">lightview-counter</a> </span></p> <ul> <li>Try modifying to put the count outside the button and change the button color.</li> <li>Try adding a reactive variable <code>color</code>, setting it to &quot;red&quot;, and putting <code>background: ${color}</code> in the style.</li> </ul> <p class="codepen" data-height="390" data-theme-id="40735" data-default-tab="html,result" data-slug-hash="QWaOyXQ" data-editable="true" data-user="anywhichway" style="height: 390px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"> <span>See the Pen <a href="https://codepen.io/anywhichway/pen/QWaOyXQ"> hcx-counter</a> by Simon Y. Blackwell (<a href="https://codepen.io/anywhichway">@anywhichway</a>) on <a href="https://codepen.io">CodePen</a>.</span> </p> <script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script> <h4 id="auto-binding-inputs-and-forms">Auto Binding Inputs And Forms</h4> <p>Lightview can automatically create variables based on form input field names and types.</p> <p>Any time you use a string literal in the <code>value</code> attribute of form inputs, Lightview checks to see if the entirety of the literal is a declared or undeclared variable. If it is undeclared, a reactive variable is automatically created. The variables are then bound to the form input. For radio buttons, it just checks to see if the <code>name</code> attribute matches a variable. (This is one of the reasons the <code>name</code> attribute on inputs can&#39;t also be a dynamically bound variable.)</p> <p>Note how the only variable declared in the Pen below is <code>color</code>, because it is used outside the context of an input value in the style attribute.</p> <p> <button onclick="resetPen('ExobKwx')">Reset</button> <span style="float:right"> <i class="fa-brands fa-codepen"></i> <a href="https://codepen.io/anywhichway/details/ExobKwx">lightview-form</a> </span></p> <p class="codepen" data-height="1070" data-theme-id="40735" data-default-tab="html,result" data-slug-hash="ExobKwx" data-editable="true" data-user="anywhichway" style="height: 1070px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"> <span>See the Pen <a href="https://codepen.io/anywhichway/pen/ExobKwx"> hcx-form</a> by Simon Y. Blackwell (<a href="https://codepen.io/anywhichway">@anywhichway</a>) on <a href="https://codepen.io">CodePen</a>.</span> </p> <script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script> <p>You can bind or autobind an object to the <code>value</code> attribute of a form and then bind the inputs to properties on the object using dot notation paths. Also note in this example the use of <code>observe</code> in the demo instrumentation rather than a <code>change</code> event listener.</p> <p class="codepen" data-height="604.7999267578125" data-theme-id="40735" data-default-tab="html,result" data-slug-hash="XWZmpwX" data-editable="true" data-user="anywhichway" style="height: 604.7999267578125px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"> <span>See the Pen <a href="https://codepen.io/anywhichway/pen/XWZmpwX"> lightview-form</a> by Simon Y. Blackwell (<a href="https://codepen.io/anywhichway">@anywhichway</a>) on <a href="https://codepen.io">CodePen</a>.</span> </p> <script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script> <h4 id="attribute-directives">Attribute Directives</h4> <p>Lightview supports <code>l-if</code>, <code>l-for</code>, and <code>:</code> (to handle custom boolean attributes).</p> <p><button onclick="resetPen('WNdXgrw')">Reset</button> <span style="float:right"> <i class="fa-brands fa-codepen"></i> <a href="https://codepen.io/anywhichway/details/WNdXgrw">lightview-directives</a> </span></p> <p class="codepen" data-height="1375" data-theme-id="40735" data-default-tab="html,result" data-slug-hash="WNdXgrw" data-editable="true" data-user="anywhichway" style="height: 1375px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"> <span>See the Pen <a href="https://codepen.io/anywhichway/pen/WNdXgrw"> lightview--directives</a> by Simon Y. Blackwell (<a href="https://codepen.io/anywhichway">@anywhichway</a>) on <a href="https://codepen.io">CodePen</a>.</span> </p> <script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script> <h4 id="targeted-anchorhref-imports">Targeted Anchor/HREF Imports</h4> <p>Anchor elements that include an element <code>id</code> as a target can be used to import components and place them at the target. For security, the current implementation requires the components be hosted on the same server as the requesting file or you must set the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin">CORS attribute</a> <code>crossorigin</code> on your <code>&lt;a&gt;</code> element. (This is not a typical location for the <code>crossorigin</code> attribute). </p> <p>You MUST ABSOLUTELY TRUST the server from which you are loading anchor imported components. Components can navigate up out of the <code>shadowDom</code> and modify other areas of a page. For more on components that are loaded across origins, see <a href="#link-imports-and-nested-components">Link imports and Nested Components</a> and <a href="#sandboxed-components">Sandboxed Components</a>.</p> <h5 id="same-origin-import">Same Origin Import</h5> <p>Since importing through a Pen requires cross origin activity, there is no Pen, just this included example.</p> <pre height="140"><code src="./examples/anchors.html"></code></pre> <h5 id="cross-origin-import">Cross Origin Import</h5> <p>Pens are hosted on a separate server, so the <code>crossorigin</code> attribute is used below.</p> <p><button onclick="resetPen('BaJYXRE')">Reset</button> <span style="float:right"> <i class="fa-brands fa-codepen"></i> <a href="https://codepen.io/anywhichway/details/BaJYXRE">lightview-anchor-crossdomain</a> </span></p> <p cla