Skip to content

Releases: gramener/uifactory

Better vega charts

13 Apr 11:28
Compare
Choose a tag to compare

The <vega-chart> component is now significantly revamped, and supports standard attributes for most of the Vega spec.

In addition:

  • Adding <script src="uifactory.js"> to the <head> tag is valid and no longer causes an error
  • Setting an attribute value to 0 (e.g. `min:number="0") no longer ignores the attribute

1.23.0 (10 Nov 2021)

10 Nov 10:52
Compare
Choose a tag to compare

<script $inline> runs scripts while rendering

To add logic to your component, add any JavaScript inside <script $inline>. This runs when the component is rendered.

<template $name="repeat-script" icon="X" value="30">
  <script $inline>
    let count = +value
    let result = isNaN(count) ? 'error' : icon.repeat(count)
  </script>
  ${result}
</template>

When you add the component to your page:

<repeat-script icon="" value="8"></repeat-html>
<repeat-script icon="" value="a"></repeat-html>

... it renders this output:

★★★★★★★★ error

Note:

Security fix

Earlier, x:number, y:boolean, etc were parsed as JavaScript. If you component.setAttribute('x', userInput.value), and userInput.value had JavaScript, it would be executed. Now, the values for :number, :boolean, etc must be JSON. Only the :js type is evaluated as JavaScript.

1.22.1 (9 Oct 2021)

09 Oct 05:03
Compare
Choose a tag to compare

uifactory.gramener.com

UIFactory docs are now hosted at uifactory.gramener.com

image

New features

  • If the <template> is empty, the instance's contents are used as the template.
  • Slot contents are also available as this.$slot[slotName]. ${this.$slot.good} is just like
    <slot name="good"></slot>.

1.21.0 (4 Oct 2021)

08 Oct 09:05
Compare
Choose a tag to compare

Add dynamic classes and styles with :=

For dynamic classes, set the class:= attribute to a array, object, or string:

  • class:="['x', 'y']" becomes class="x y"
  • class:="{x: true, y: false}" becomes class="x"
  • class:="['x', {y: true, z: false}]" becomes class="x y"
  • class:="${active ? 'yes' : 'no'}" becomes class="yes" is active is true, else class="no"

For dynamic styles, set the style:= attribute to an object or string:

  • style:="{'font-size': `${size}px`, color: 'red'}" becomes style="font-size:20px;color:red" (when size=20).
  • style:="font-size="${size}px; color: red" also becomes style="font-size:20px;color:red" (when size=20).

For dynamic attributes, set the <attr>:= attribute to any string or boolean expression:

  • disabled:="true" becomes "disabled"
  • disabled:="false" does not add the disabled attribute
  • type:="isNumeric ? 'number' : 'text'" sets type="number" if isNumeric is truthy, else type="text"

For example, this defines an <add-class> component:

<template $name="custom-input" active:boolean="true">
  <style>
    .round { border-radius: 20px; }
    .active { border: 1px solid red; }
  </style>
  <input
    class:="['round', {active: active}]"
    style:="{background-color: active ? 'lightblue' : 'white'}"
    disabled:="!active">
</template>

When you add this to your page:

Active: <custom-input active="true"></custom-input>
Inactive: <custom-input active="false"></custom-input>

... it renders:

custom-input

1.20.0 (28 Sep 2021)

08 Oct 09:07
Compare
Choose a tag to compare

Add re-usable blocks with <script type="text/html" $block="...">

To re-use HTML later, add it into a <script type="text/html" $block="blockname">...</script>. For example:

<template $name="block-example" greeting="hello">
  <script type="text/html" $block="one">one says ${greeting}.</script>
  <script type="text/html" $block="two">two says ${greeting}.</script>

  <%= one() %>
  <%= two({ greeting: 'Ola' }) %>
</template>

When you add the component to your page:

<block-example></block-example>

... it renders this output:

one says hello. two says Ola.

Note:

  • You can use this and
    all properties as variables.
  • If multiple <script type="text/html"> have the same $block value, the last one is used

this.$id hold a unique ID for each component

If you generate an id= attribute in your component, you need a unique identifier for each
component. this.$id has a string that's unique for each component instance.

For example, this creates a label-input combination with a unique ID for each input:

<template $name="label-input" type="text" label="">
  <div style="display: flex; gap: 10px">
    <label for="${this.$id}-input">${label} <small>ID: ${this.$id}-input</small></label>
    <input id="${this.$id}-input" type="${type}">
  </div>
</template>

Now, if you repeatedly use this component in a page:

<label-input label="X"></label-input>
<label-input label="Y"></label-input>

... it creates elements with different IDs:

this.$id generates unique IDs

1.19.0 (25 Sep 2021)

08 Oct 09:11
Compare
Choose a tag to compare

Add events with <script $on...>

To add an click event listener to your component, write the code inside a
<script $onclick>...</script>, like this:

<template $name="count-items" count:number="0" step:number="2">
  Click to count ${count} items in steps of ${step}.
  <script $onclick>
    this.count += step
  </script>
</template>

When you add the component to your page:

<count-items></count-items>

... it renders this output:

Click event demo

To add a click event listener to a child, use <script $onclick="child-selector">...</script>:

<template $name="count-button" count:number="0" step:number="2">
  <button>Click here</button>
  <span>Count: ${count}</span>
  <script $onclick="button">
    this.count += step
  </script>
</template>

Now, <count-button></count-button> renders:

Click event on child demo

Listeners can use these variables:

To call the listener only once, add the $once attribute to <script>:

<template $name="count-once" count:number="0" step:number="2">
  <button>Click here once</button>
  <span>Count: ${count}</span>
  <script $onclick="button" $once>
    this.count += step
  </script>
</template>

Now, <count-once></count-once> renders:

$once demo

Styles only affect the component

You can't pollute styles outside the component. UIFactory adds the component name before every
selector (if it's missing). For example:

  • repeat-style.highlight {...} stays as-is -- it already has repeat-style
  • .highlight b {...} becomes repeat-style .highlight b {...}
  • b { color: green} becomes repeat-style b { color:green; }

So any <b> outside the component does not change color.

Note: This isn't foolproof. It's simply to prevent accidental pollution.

1.18.0 (19 Sep 2021)

08 Oct 09:13
Compare
Choose a tag to compare

Release v1 with Major API rewrite

To migrate your component from UIFactory v0.x, make these changes in your component (where applicable):

  1. Replace <template component="..."> with <template $name="...">
  2. Replace <template @render:js="..."> with <template $render:js="...">
  3. Replace this with this.$contents
  4. Replace $target with this
  5. Replace this.data with this.$data
  6. Replace this.ui.ready with this.$ready
  7. Rewrite any <style scoped> styles without the scoped attribute -- by prefixing all selectors with component-name
  8. Rewrite properties specified as <script type="application/json"> as attributes.
    For example:
    <template component="...">
      <script type="application/json">
        { "properties": [ { "name": "list", "type": "array", "value": [] } ] }
      </script>
    </template>
    should be written as:
    <template $name="..." list:array="[]">
    </template>
  9. Rewrite the properties in uifactory.register({properties}) as a dict, not list.
    For example:
    uifactory.register({
      properties: [
        { "name": "list", "type": "array", "value": [] }
      ]
    })
    should be written as:
    uifactory.register({
      properties: {
        "list": { "type": "array", "value": [] }
      }
    })

Lifecycle events are supported

Components fire these events at different stages of their lifecycle:

  • preconnect: before the instance is created and properties are defined
  • connect: after the instance is created and properties are defined
  • prerender: before the instance is rendered
  • render: after the instance is rendered
  • disconnect: after the element is disconnected from the DOM

Add <script $onpreconnect>...</script>, <script $onrender>...</script>, etc to create listeners.
For example:

<template $name="repeat-events" icon="" value:number="1">
  <script $onrender>
    this.innerHTML = icon.repeat(value)
  </script>
</template>

Now, <repeat-events icon="★" value="8"><repeat-events> renders this output:

8 stars

NOTE:

  • <script $onrender $once> creates a listener that runs only once
  • <script $onprerender $onrender> runs the listener both on prerender AND render
  • Multiple <script $onrender>...</script> creates multiple listeners
  • this.addEventListener('render', ...) is exactly the same as <script $onrender>

0.0.17 (18 Sep 2021)

08 Oct 09:13
Compare
Choose a tag to compare

Remove lodash dependency

UIFactory no longer needs lodash. It's a standalone library

0.0.16 (1 Sep 2021)

08 Oct 09:14
Compare
Choose a tag to compare
  • <style scoped> applies style only to component
  • el.property = value re-renders el
  • el.update({'attr:type': ...}) supported
  • <vega-chart> component added with signals support

0.0.15 (11 Aug 2021)

08 Oct 09:15
Compare
Choose a tag to compare

@render:js attribute supports custom renderers