HABPanel Development & Advanced Features: Start Here!

This Wiki post aims to be a centralized resource to help new users (and experienced users alike!) with the advanced features in HABPanel. It will contain relevant examples and links to help you with common issues and getting the most out of these features!

Users with the “Member” badge (and levels above) can edit this post, don’t hesitate to do so if you deem it appropriate :thumbsup:

Templates & Custom Widgets

What are templates?

If the standard widgets don’t fit your needs or your taste, HABPanel allows you to write your own code and build your own widget inside your dashboards.
It will require some level of familiarity with HTML, CSS, as well as basic knowledge of the Bootstrap and AngularJS frameworks.

W3Schools provide great tutorials with a lot of examples, as well as comprehensive references to get you started:

Note that everything presented in these tutorials and examples is not necessarily relevant for developing templates in HABPanel: still, you can learn a lot by following them so be sure to do it and build up your skills! :slight_smile:

In particular, inline Javascript code (inside a <script> element) is NOT allowed in templates: this means you cannot for instance write associated AngularJS controllers inside the templates like shown in some tutorials.
You can however “lazy-load” a separate file, which is an advanced technique (see below).

How do I write a template?

The simplest way to write a template and display it on dashboard is by simply adding a Template widget, the settings dialog with contain a large text box where you can code your template.

You can share the same template among different widgets with the Custom Widgets feature (see below).

You will find valuable information in the original thread announcing the feature:

What can I use inside my template?

Your template is in essence a snippet of HTML with optional CSS attached and most likely containing AngularJS expressions and directives to make it dynamic.

Inside your template you can use:

- Boostrap classes

You can notably use Bootstrap grid system, and classes for tables, wells, buttons, badges/labels, and so on. For dropdowns and menus, use the ui-bootstrap dropdown directive (see below).

Bootstrap features requiring plugins, like tooltips, modals etc. cannot be used since inline Javascript is not allowed in a template.

- AngularJS expressions

Expressions are usually surrounded by double curly braces…

{{ 5 + 5 }}
{{ 'foo then bar'.replace(' then ', ',').split(',').join('') }}
{{ condition ? 'green' : 'red' }}

… except when used in directives (there are exceptions):

<div ng-if="itemState('Test') != 'ON'">

- Most of the AngularJS standard directives:

These are the most useful:

  • ng-show/ng-hide display or hide an element depending on a condition;
  • ng-if will not render the element at all if the expression is not true;
  • ng-style/ng-class modify the appearence of the element on a expression;
  • ng-repeat allows you build dynamic tables or repeated items;

Some directives respond to events, like ng-click.

- AngularJS filters

All standard AngluarJS filters listed above are available, as well as those below:

  • pattern | sprintf:expression allows you to format a value with sprintf;
  • expression | themeValue:'theme-variable' is a HABPanel filter to fallback e.g. a color value to the default provided by the theme variable (see below) if the expression is “falsy” (not set, null or empty, among others)

- Some directives of the UI Bootstrap library

Those requiring a controller cannot be used (most notably Modal).
This post has examples of a carousel, a progress bar and a dropdown.
Tip: For dropdown and date pickers, set the “append-to-body” attribute to true.

- HABPanel functions

The functions below can be used in expressions:

  • itemState(itemname, [ignoreTransform]): retrieves the state of an item by its name;
    ignoreTransform (optional, false by default) will return the raw state (e.g. ‘CLOSED’) instead of the transformed state (‘zu’);
    Note: itemValue is the original name and an alias for itemState, still working for compatibility reasons.
  • sendCmd(itemname, command): sends the command to the item with the provided name.
    Useful in events like ng-click;
  • getItem(itemname): retrieves the complete structure of the item as formatted by openHAB’s /rest/items API;
  • itemsInGroup(groupname): retrieves an array of items belonging to the specified group.
    Useful with ng-repeat.
  • itemsWithTag(tagname): retrieves an array of items having the specified tag.
    Requires an HABPanel snapshot version from 25 Jul 2017 or later:
  • openModal(templateUrl, [noAnimation, [size]]): opens a modal window. Check ui-bootstrap’s Modal documentation (and the example below) to learn how to write your modal template inside a <script type="text/ng-template"> element.

- HABPanel directives

Render icons: <widget-icon>

Renders an icon from HABPanel’s built-in iconsets, or an ESH icon.

Syntax/example:

<widget-icon iconset="'eclipse-smarthome-classic'" icon="'light'" size="50" backdrop="false" center="true" inline="false" state="itemState(itemname)">

All attributes are optional except iconset and icon.
List of currently available iconsets: freepik-household, freepik-gadgets, freepik-housethings, smarthome-set, eclipse-smarthome-classic. For the name of the icon use the icon picker. The state attribute along with a supported icon (in the ESH iconset like 'rollershutter’) will allow dynamic icons depending
on the state.

Reuse built-in widgets: <widget-*>

You can add some of the standard HABPanel widgets to your template.
Use the following syntax:

<div ng-init="model={ name: 'My widget', ... }">
  <widget-{type} ng-model="model"></widget-{type}>
</div>

{type} must be replaced by the actual widget type i.e. switch, slider etc.

For instance, this code will render a standard switch widget:

  <style>.myVersionOfWidget {padding: 0;  margin: 0; border: none;}</style>
	<div class="myVersionOfWidget" ng-init='myWidget={
        						"name": ngModel.name,
                    "item": config.switch_item,
                    "type": "switch",
                    "hidelabel": false,
                    "hideonoff": false,
                    "iconset": "smarthome-set",
                    "icon": "power-button",
                    "icon_size": "32"
                      }'>
    <widget-switch ng-model="myWidget"></widget-switch>
	</div>

In the above example you need to create a config with the correct name that matches switch_item so people can select the item the switch will toggle. The rest are hard coded unless you also create configs for them manually. The style should be the absolute bare minimum needed so people can override the style in their master CSS files.

NOTE: some widgets (knobs, charts, timelines) won’t behave well in a template because they’re trying to adapt their size to the size of the surrounding “box”. You can use switches, buttons, sliders, color pickers (in aCKolor mode), images reliably.

The ng-model object describes the widget’s configuration, including the item it will control, its appearence etc. It varies from widget to widget.

Instead of documenting every possible model option for every widget, here’s a trick: add your widget to a dashboard the standard way with the designer, give it an unique name to find it back easily, adjust the options as you like, then go to HABPanel’s advanced settings and click on the “Edit the local panel configuration” link. You will then have to find your widget (using its unique name) and you’ll have the complete model to pass to ng-model this way. You can leave out the row, col, sizeX, sizeY, type attributes since they’re relating to the dashboards layouts themselves - type is the type identifier to put after widget- as the node name.

Advanced: Injecting custom JavaScript code

You can use the oc-lazy-load directive to inject additional JavaScript (and CSS) files, provided they are deployed on the same server as HABPanel (typically under /static), even AngularJS directives and controllers.

See these posts for examples: 1, 2, 3, 4, 5

Selected Examples

A table of all items in a group, with dynamic colors and a counter

<h4>
  <strong>
    {{(itemsInGroup('Windows') | filter:{state:'open'}).length}}
    /
    {{(itemsInGroup('Windows')).length}}
  </strong>
  open:
</h4>
<table class="table table-condensed">
	<tr ng-repeat="item in itemsInGroup('Windows')">
    <td class="text-right">{{item.label}}</td>
    <td class="text-left" ng-style="{color: (itemState(item.name)=='open')?'red':'green'}">
      {{itemState(item.name)}}
    </td>
  </tr>
</table>

Dropdown of radio stations


<h4>
  <widget-icon class="pull-left" iconset="'freepik-gadgets'" icon="'vintage-radio'" size="50" inline="true"></widget-icon>
  <span>Radio <br /> Station</span>
</h4>
<div class="btn-group" uib-dropdown dropdown-append-to-body="true"
     ng-init="radio_stations = ['Off', 'HR3', 'SWR3', 'FFH']">
  <button id="single-button" type="button" class="btn btn-primary" uib-dropdown-toggle>
    {{radio_stations[itemValue('Radio_Station')]}} <span class="caret"></span>
  </button>
  <ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="single-button">
    <li role="menuitem"><a ng-click="sendCmd('Radio_Station', 0)">Off</a></li>
    <!--The first item is already taken care of above, show a divider instead -->
    <li role="menuitem" ng-class="{ 'divider': $index==0 }" ng-repeat="station in radio_stations">
      <a ng-if="$index != 0" ng-click="sendCmd('Radio_Station', $index)">{{station}}</a>
    </li>
  </ul>
</div>

 

Opening a modal with sliders for each item in a group

Requires an HABPanel snapshot version from 25 Jul 2017 or later

<script type="text/ng-template" id="myModalContent.html">
    <div class="container" style="padding: 30px; border: 1px solid #456;">
      <a ng-click="$close()" class="pull-right btn btn-danger">X</a>
      <h3 style="color: white">All Lights</h3>
        <ul ng-repeat="light in itemsInGroup('Lights')" ng-init="models={}">
            <li class="col-md-4"
                ng-init="models[light.name] = { name: light.label, item: light.name, step: 1 }">
                <div class="box" style="height: 150px; margin: 10px">
                    <widget-slider ng-model="models[light.name]"></widget-slider>
                </div>
            </li>
        </ul>
    </div>
</script>
<button class="btn btn-default btn-lg"
        ng-click="openModal('myModalContent.html', true, 'lg')">
  Open Modal
</button>


Custom Widgets

What are Custom Widgets?

TODO

Creating Custom Widgets

Configuring Custom Widgets

Using Custom Widgets

Distributing Custom Widgets


Themes

Theme variables

Overriding variables and classes in a custom stylesheet

45 Likes

Links of interest

Link here any post you think provides something of value, for example a solution to a common problem:

Dynamic background color and icon

Carousel of webcam images with auto-refresh

Modal popup - Another example

Example of using the backdrop attribute of <widget-icon>

Building a floor plan using several <widget-switch> elements positioned and stylized with CSS

9 Likes