Dashborg Documentation / Examples / Templates and Components

Templates and Components

Dashborg provides two ways to package up your template code for re-use within your panel or between panels. Both provide the same power and inteface, the difference is just how they are used. In general, using templates is great for extracting common functionality inside of a panel (DRY “Don’t Repeat Yourself), whereas components can be packaged up into libraries and used across panels and accounts. Most of the Dashborg components (d-button, d-message, etc.) are implemented as components.

  • Templates are great for extracting common functionality (DRY “Don’t Repeat Yourself”) from your panels. They are easier to use and don’t require an “import”.
  • Components Provide a way to define your own custom tags and libraries of reuseable tags (like the Dashborg d-table or d-message tags). They require an “import” to use.

Templates

Templates are defined using the <template> tag as siblings to your <panel> tag. You “call” your template using the <include> tag. Here’s a simple example:

<panel>
  <h1>Test Templates</h1>
  <include template="my-button"/>
</panel>

<template name="my-button">
    <d-button button-class="primary" handler="/test-handler">Click Me!</d-button>
</template>

The template is passed the data context of the include tag. So, if you use a template within a loop (d-foreach or d-table), the template will have . set to different values.

Components

Components are defined using the define-component tag as siblings to your <panel> tag. In your panel, you can then “import” the component, and use it as a custom component. Note that in HTML custom tags must include a “-” as part of their name. Components can also be built into re-useable libraries using <define-library> – in fact, this is how some of the Dashborg UI Components are built.

If you’re interested in helping to build out Dashborg components for different UI libraries or various custom controls, please contact us! We’d love to help and/or publish your components on the Dashborg site to allow others to import them.
<panel>
  <import component="local:test-button" as="my-button"/>
  <h1>Test Component</h1>
  <my-button/>
</panel>

<define-component name="test-button">
  <d-button button-class="primary" handler="/test-handler">Click Me!</d-button>
</define-component>

Importing Components

Any component that is defined inside of your panel’s HTML file is automatically included into a special “local” library. The <import> tag allows you to bind a component to a custom tag name inside of your panel. Here are some example imports:

<import component="local:test-button" as="my-button"/>
<import lib="local" as-prefix="my"/>
<import lib="dashborg/base" as-prefix="d"/>
<import lib="dashborg/semui" as-prefix="d"/>

The first version you can import a single component. A component has two parts, a library and a name separated by a :. So here we are importing from the special “local” library, the component named “test-button” and binding it to the custom tag “my-button”.

In the second version we are importing an entire library. We are importing the entire “local” library and they will all have the custom tag prefix of “my-”. So in this case since our component was named “test-button”, you would access it as <my-test-button/>. Be careful about conflicting imports. Imports cannot overwrite names, so once a name is defined (e.g. d-foreach) you cannot use an import to change it.

The 3rd/4th lines are automatic Dashborg imports. The “dashborg/base” library is always imported no matter what. It includes the base Dashborg controls like d-text and d-foreach. Other Dashborg libraries can be imported manually or automatically depending on your “ui” mode. In the default “ui” mode, the “dashborg/semui” library is automtically imported and provides Semantic UI controls like d-button, d-message, d-stat, etc.

@children

Templates and components are passed a special context variable @children. You can use this variable to access the children nodes in the call to your custom component or template include. Children are automatically split by tag and slot:

  • @children.all – an array of all tags
  • @children.bytag[TAGNAME] – bytag provides a dictionary from tagname to an array of tags
  • @children.byslot[SLOTNAME] – byslot provides a dictionary from slotname to an array of tags

Here’s a trivial example:

<panel>
    <h1>Test Children</h1>
    <include template="para">
      This is some <b>paragraph</b> content.
    </include>
</panel>

<template name="para">
    <p>
        <d-children bind="@children.all"/>
    </p>
</template>

Here’s a more complex example using slots to set header nodes and content nodes (using Semantic UI’s message element):

<panel>
    <h1>Test Children</h1>
    <include template="message">
        <div slot="header">My Header!</div>
        <div slot="content">... this is some content</div>
    </include>
</panel>

<template name="message">
  <div class="ui message">
    <div class="header">
        <d-children bind="@children.byslot.header"/>
    </div>
    <d-children bind="@children.byslot.content"/>
  </div>
</template>

@node

Templates and components are also passed another special context variable @node that lets you access the attributes passed in the call to your custom component or template include. Here’s an example showing definition list. The “label” will be bolded and put into a 200px wide column, and the value can be any content (included using @children).

<panel>
  <h1>Test @node</h1>
  <include template="definition" label="ID">55a78-2837</include>
  <include template="definition" label="First Name">Michael</include>
  <include template="definition" label="Last Name">Smith</include>
</panel>

<template name="definition">
  <div style="display: flex; flex-direction: row">
    <div style="bold; width: 200px"><d-text bind="@node.attrs.label"/></div>
    <div><d-children bind="@children.all"/></div>
  </div>
</template>

Advanced Concepts - Binding Data

To make good re-useable components you do not want to bind data directly to the global data model using $. It is best for the user of your component to provide their own bindings. Let’s say I want to display a list using <ul> and <li>. I’d like to provide a bind attribute that works. If I just write my loop like <d-foreach bind="@node.attrs.bind"> I’ll just get a string value (the expression that was passed in). We need to evaluate the bind attribute. You can do this using fn:deref. Try this example without calling fn:deref to see the difference.


<panel>
  <h1>Bind Example</h1>
  <d-data bindvalue="$.data">
    [1,2,3,4,5]
  </d-data>
  <include template="simple-list" bind="$.data"/>
</panel>

<template name="simple-list">
  <ul>
    <d-foreach bind="fn:deref(@node.attrs.bind)">
      <li><d-text bind="."/></li>
    </d-foreach>
  </ul>
</template>

Advanced Concepts - Styling and Automerge

One of the biggest problems with creating UI components is how to allow for custom styling. Overriding the style of sub-components is often necessary and most libraries create a a huge list of attributes to do these overrides. But this is not a general solution, and if an attribute is not provided – e.g. bold the text, un-bold the text, change the width, add a custom class, etc. then the component may be unuseable.

Dashborg solves this with “automerge” – a way to combine default styling and classes with overrides. It is what all of the standard Dashborg components use.

Here’s a simple example. Let’s say we want to make a “definition list”. It shows labels and values. By default we’ll make the labels bold and we’ll make the labels take up 50% of the space and values 50% of the space.

<template name="simple-list">
    <div style="display: flex; flex-direction: row; width: 100%;">
        <div style="bold; width: 50%"><d-text bind="@node.attrs.label"/></div>
        <div><d-text bind="@node.attrs.value"/></div>
    </div>
</template>

But what if we sometimes don’t want the labels to take up 50%. What if I want to have the labels take up exactly 200px of space? Well, I could add a custom attribute called “width” and test it and use that to override the width:

<div style="bold; width: * @node.attrs.width || '50%';">

How about if I want the labels to be “blue”? Okay, let’s add another custom attribute called “color”:

<div style="bold; width: * @node.attrs.width || '50%'; color: * @node.attrs.color || inherit;">

What about if I want the labels to not be bold? Hmm… maybe an “unbold” attribute.

<div style="font-weight: * @node.attrs.unbold ? 'normal' : 'bold'; width: * @node.attrs.width || '50%'; color: * @node.attrs.color || inherit;">

This goes on and on and makes for ugly code and non-extensible components. How do you cover everything (font size, font weight, italics, etc.)? For complex components this is even more of a problem. Consider if I want to bold the value or change the background color of the entire component?

So let’s rewrite this with automerge:

<template name="simple-list">
    <div style="display: flex; flex-direction: row; width: 100%;" automerge>
        <div style="bold; width: 50%" automerge="label"><d-text bind="@node.attrs.label"/></div>
        <div automerge="value"><d-text bind="@node.attrs.value"/></div>
    </div>
</template>

Now to override the width of the label to 200px, and set it’s color to blue i’d write:

<include template="simple-list" label="ID" value="555" style-label="width: 200px; color: blue;"/>

When you set automerge="label" it allows style-label and class-label from the <include> tag to be merged with the styles on that node. When you just say automerge (no attribute value) it pulls from style and class (no suffixes). That’s it!