Template widget - tutorial & examples - make your own widget!

Hey all,

just a little tipp regarding widget or even total dashboard design:
Presentation Tools like Powerpoint or Keynote really do a fine job to play around with colors, fonts and styles. Here is an example I did to create my personal Weather-Widget and try out different designs/transparencies/shadows:

Did you find a resolution this issue?
I also have a JSON string stored in an item which I loop over in habpanel.

My issues are:
Data doesn’t load on a hard refresh of the page in Chrome. Reading in item states in an ng-init results in “NaN”. Once I change the page and return, it works just fine.
I have tried to resolve it with this with no luck:

<div ng-if="getItem('WeatherHourly_json').toString() != 'NaN'" ng-init="hours = $eval(itemState('WeatherHourly_json'))">

I get thousands or errors in the console (i have read that this is related to Chrome loading the DOM before the data is loaded). In the following case, Chrome is complaining that the has CW as the length instead of a number. But this fixes itself.

Error: <svg> attribute width: Expected length, "{{CW * config.Nu…".
html @ vendor.js:196

I also get errors related to String Vs. Numbers which I can’t identify:

TypeError: [sprintf] expecting number but found string
    at b.format (vendor.js:365)
    at b (vendor.js:365)
    at vendor.js:524
    at vendor.js:255
    at c (vendor.js:130)
    at vendor.js:128
    at m.$digest (vendor.js:144)
    at m.$apply (vendor.js:147)
    at l (vendor.js:99)
    at K (vendor.js:103)

Does anybody know what “$exec(…)” does? Can’t find any documentation on it. I have only found a few examples where people do that. Removing it doesn’t work.

<div ng-init="hours = $eval(itemState('WeatherHourly_json'))">
...
<td class="hours" width={{CW}} ng-repeat="h in hours track by $index"><div>{{h.FCTTIME.weekday_name_abbrev}}<br />{{h.FCTTIME.civil}}</div></td>

Finally (sorry for all the questions), my widget doesn’t refresh over time when WeatherHourly_json updates in OH.

For those who want to try my Widget, it downloads the hourly weather data from WUnderground as JSON string, stores it in an Item. Then a rule parses the JSON and calculates the MAX/MIN values for the temperature. The Habpanel widget displays a table with the data including a graph for the temperature.
Requires the HTTP Binding and the JSONPath Transformation.

Widget:
http://faure.ca/~paul/WeatherHourly.widget.json

Items:

String  WeatherHourly_json  "JSON [%s]"       {http="<[weatherHourly:3000000:JSONPATH($.hourly_forecast)]"}
Number  WeatherHourly_max   "MAX [%.2f]"
Number  WeatherHourly_min   "MIN [%.2f]"
Number  WeatherHourly_avg   "AVG [%.2f]"

Rules:

rule "WUndergound hourly change"
when
        Time cron "0 */15 * * * ?" or
        Item WeatherHourly_json changed
then
        var String WeatherHourly_temp = "NULL"
        var String WeatherHourly_ftemp = "NULL"
        logInfo("HourlyWeather", "Data changed, recalculate min/max")
        if(WeatherHourly_json.state.toString == "NULL"){
          logInfo("HourlyWeather", "Data1 is NULL")
          //WeatherHourly_temp.sendCommand("NULL")
          //WeatherHourly_ftemp.sendCommand("NULL")
        }else{
          WeatherHourly_temp = transform("JSONPATH", "$.[0:13].temp.metric", WeatherHourly_json.state.toString)
          WeatherHourly_ftemp = transform("JSONPATH", "$.[0:13].feelslike.metric", WeatherHourly_json.state.toString)
          if(WeatherHourly_temp == "NULL" || WeatherHourly_ftemp == "NULL"){
            logInfo("HourlyWeather", "Data2 is NULL")
            WeatherHourly_max.sendCommand(0)
            WeatherHourly_min.sendCommand(0)
            WeatherHourly_avg.sendCommand(0)
          }else{
            var temp='{temp:[' + WeatherHourly_temp.replace('"', '').replace('[','').replace(']','') + ',' + WeatherHourly_ftemp.replace('"', '').replace('[','').replace(']','') + ']}'
            logInfo("HourlyWeather", "Data: " + temp)
            var max=transform("JSONPATH", "temp.max()", temp)
            var min=transform("JSONPATH", "temp.min()", temp)
            var avg=transform("JSONPATH", "temp.avg()", temp)
            WeatherHourly_max.sendCommand(max)
            WeatherHourly_min.sendCommand(min)
            WeatherHourly_avg.sendCommand(avg)
            logInfo("HourlyWeather", "MIN="+min+":MAX="+max)
          }
        }
end

HTTP Binding:

weatherHourly.url=http://api.wunderground.com/api/[APIKEY]/hourly/q/45.4347982,-75.5739861.json
weatherHourly.updateInterval=3000000

Hi!

I tried to keep lists as json in String item. It working well, but only when I open page first time. After refresh they vanish. May be it’s related to ng-init. It’s hard to say for me, because I am almost not familiar with Angular. What can be wrong?

            <div class="sceneGroup" ng-init="deviceList = $eval(itemValue('spotify_device_list'))">
                <div class="scene" ng-class="{true:'on', false:''}[itemValue('spotify_current_device_id')==device.id]"
                     ng-repeat="device in deviceList"
                     ng-click="sendCmd('spotify_current_device_id', device.id);sendCmd('spotify_action', 'transfer_playback')">
                    {{device.name}}
                </div>
            </div>

Hi All

EDIT: Got it working!

Here is my code so far:

<div class="section" ng-init="hueColors = [ { hsb: '0,0,100', hex: '#fff' }, { hsb: '74,78,100', hex: '#fecc2f' }, { hsb: '46,100,100', hex: '#f9a228' }, { hsb: '26,100,100', hex: '#f6621f' }, { hsb: '0,100,100', hex: '#db3838' }, { hsb: '273,100,100', hex: '#a363d9' }, { hsb: '201,100,100', hex: '#40a4d8' }, { hsb: '177,100,100', hex: '#33beb8' }, { hsb: '140,100,100', hex: '#b2c225' } ]">
  <div class="sectionIconContainer"><div class="sectionIcon"><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/matrixicons.svg#light_bulb"></use></svg></div></div>
  <div class="title"><div class="name">Lights</div><div class="summary"></div></div>
  <div class="controls">
      
     <div class="widget" ng-if="itemValue('LivingRoomSw1')=='OFF'" ng-click="sendCmd('LivingRoomSw1', 'ON')">
      <div class="icon on" ><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/matrixicons.svg#on"></use></svg></div>
      <div class="name">Living Room {{itemValue('LivingRoomSw1')}}</div>
    </div>
      <div class="widget" ng-if="itemValue('LivingRoomSw1')=='ON'" ng-click="sendCmd('LivingRoomSw1', 'OFF')">
      <div class="icon on" ><svg viewBox="0 0 48 48"><use xlink:href="/static/matrix-theme/matrixicons.svg#on"></use></svg></div>
      <div class="name">Living Room {{itemValue('LivingRoomSw1')}}</div>
    </div>
    
<div class="slider-div" ng-init='LivingRoomDim1={"name": "Brightness", "item": "LivingRoomDim1", 
                    "floor" : 0, "ceil": 100, "step": 1, "hidelabel" : "true", "hidelimits": "true" }'>
  <widget-slider ng-model="LivingRoomDim1"/>
</div>
    
    
    

Hey @ysc
Thanks for this! I asked this in another thread, but thought I’d ask here too.

I am working on a custom theme and noted in the HABPanel Development thread that there was access to the Bootstrap Grid, but that doesn’t seem to be the case?

Bootstrap classes don’t seem to have any impact, such as rows or columns or classes like btn , btn-primary , etc, or at least don’t render properly. Is that the case or am I missing something?

I also see people using control and controlGroup CSS classes - are those base classes in HABPanel?

Thanks everyone

I have weather station data coming into my habpanel. I’m not quite sure how to go about it, but I’d like to have a dynamically rotating arrow the shows wind direction. I’ve done a lot of reading and dabbling trying to find a way without success. The wind direction data is a value of 0.0 to 259.99 degrees. Any guidance or starter code to make this work is greatly appreciated. Thanks in advance.

If you are a bit experienced with SVG you can apply the rotation via “transform” and then update the rotation angle according to the item value you get.

Though it is a bit tricky as center rotation is not straighforward. See here for more information

Does that help as an idea?
Stefan

Hi to all.
Can i show here item units like dbm or %?

<div class="row" ng-repeat="item in itemsInGroup('RSSI')">
  <div class="col-xs-8 text-right">{{item.name}}</div>
  <div class="col-xs-4 text-left" ng-style="{ color: itemValue(item.name)=='OFF'?'red':'green' }">
    {{itemValue(item.name)}}
  </div>
</div>

What value do i need for “‘OFF’”?
Can i sort the items alphabetical by item name or for example after the value (highest to lowest value)?
And is it possible to take a item-widget at the top of the widget with the group state?
Thanks for helping.
Greetings,
Markus

Hi all + @ysc

I have another slider template issue.

<div ng-init=\"slider = {'item': 'LivingRoomBoom_CurrentPlayingTime','vertical': false,'floor': 0,'ceil': itemValue(config.duration),'step': 1,'precision': 1,'hidelabel': false,'hidelimits': false,'hidepointer': false,'showticks': false,'bigslider': false}\"></div><widget-slider ng-model=\"slider\"/></div>

This version acts like a slider, provides control over the music player etc. There are two problems:

  1. The ceiling value loads once but never changes unless I reload the widget.
  2. I’d like to use a config instead of hard-coding the player for the item.
<div ng-init=\"slider = {'item': itemValue(config.playingTime),'vertical': false,'floor': 0,'ceil': itemValue(config.duration),'step': 1,'precision': 1,'hidelabel': false,'hidelimits': false,'hidepointer': false,'showticks': false,'bigslider': false}\"></div><widget-slider ng-model=\"slider\"/></div>

This version shows as undefined…

Here is the config json:

    "settings": [
        {
            "type": "item",
            "id": "title",
            "label": "Title",
            "description": "String item linked to player#title"
        },
        {
            "type": "item",
            "id": "artist",
            "label": "Artist",
            "description": "String item linked to player#artist"
        },
        {
            "type": "item",
            "id": "album",
            "label": "Album",
            "description": "String item linked to player#album"
        },
        {
            "type": "item",
            "id": "playingTime",
            "label": "Playing Time",
            "description": "String item linked to player#currentPlayingTime"
        },
        {
            "type": "item",
            "id": "duration",
            "label": "Duration",
            "description": "String item linked to player#duration"
        },
        {
            "type": "item",
            "id": "control",
            "label": "Player Control",
            "description": "Player item linked to player#control"
        }
    ]

Any ideas?

Can anyone offer any advice?

Thanks in advance! :smiley:

@ysc @Patrick_Beauchamp

That would be simply 'item': config.playingTime since you want the name of the item in the config, not its state.

That’s to be expected, the ceiling is initialized with the widget and cannot be dynamic.
One solution would be to recreate the slider every time the song changes.

There’s a trick with ng-repeat and a singleton array to achieve that, something like:

<div ng-repeat="songtitle in [itemState(config.title)]">
   (widget-slider here that will be recreated every time the song changes)
</div>
1 Like

@ysc

I realize that you said in your tutorial that all built-in widgets are not easily reusable in templates. I am trying to import a chart into a template. I reason may seem silly but I want to add a heading and a “Label” widget takes to much space. As you can imagine my chart looks fine except it starts small and expands in height all the time. Is there any trick I can use to stop this or overwrite this behaviour in some way?

The other option for me would be if you allow a heading in the chart widget. I would like if you can specify the size and the alignment if you willing to do it.

Great job on Habpanel. I have been obsessing on it for the last few days.

Hi Yannick @ysc.

Thank you very much, my slider is working now! :slight_smile:

Hi everybody,

I try to use a selection channel from the new Spotify Binding in a custom widget. Is there a any way to directly provide the values from the channel to a array used in ng-repeat for example?
I thought of something like this:

      <div class="sceneGroup" ng-init="devices = getItem('Spotify_Device').stateDescription.options">
        <div class="scene" ng-repeat="device in devices" ng-click="sendCmd('Spotify_Device', device.value)">{{device.label}}</div>
      </div>

but this doesn’t return any value.
Using

{{getItem('Spotify_Device').stateDescription.options}}

alone returns the following as expected:

[{"value":"11fab...96ccc","label":"Device1"},{"value":"36883...f7277","label":"Device2"},{"value":"543d6...51776","label":"Device3"}]

I think the double quotes in the returned array could be an issue, but maybe I’m just completly wrong on the syntax.
Any help is highly appreciated, thanks in advance!

Greetings, Hans

EDIT:
SOLVED! This was much easier than expected :sweat_smile:

<div class="sceneGroup">
  <div class="scene" ng-repeat="device in getItem('Spotify_Device').stateDescription.options" ng-click="sendCmd('Spotify_Device', device.value)">{{device.label}}</div>
</div>

I managed to scratch up something that works with your svg suggestion. However if I resize it any larger in habpanel the arrow move off center to the dial. I’ll post the code tomorrow sometime.

1 Like

Hi @vzorglub

Did you ever get the script to load in a template?

I’m trying to get this to run — (Which works as a stand alone HTML file, but I’d like to be able to call it from the root rather than as an absolute URL)

<!DOCTYPE HTML>
<html>
<head>
<Title="Bathroom"></Title>
<script type="text/javascript" src="/static/canvasjs/jquery.js"></script>
<script type="text/javascript" src="/static/canvasjs/canvasjs.min.js"></script>
<script src="/static/canvasjs/graphs.js"></script>
<META HTTP-EQUIV="refresh" CONTENT="600">
<style>
body {
    background-color: #000000;
}
</style>
</head>

<Body>

Bathroom</br>

<div id="BATH" style="height: 50%; width: 100%;"></div></br></br><hr>
<div id="BATH-30" style="height: 50%; width: 100%;"> </div></br></br><hr>

</body>
</html>

All I get is the divider

What I should get is a pair of graphs like these…

Everyone knows I’m not great with coding, so any help would be very welcomed.

Cheers

Stuart

Sorry, what are you reffering to?

I’m sorry, that was a bit of a vague question.

I meant this post #234

Where you were trying to get a script to run within a template.

Yes never got it to work
I have never bothered to learn web programming and I gave up.
Instead I use a frame widget and an item and a rule:

rule "Weather Conditions Changed"
when
    Item WeatherConditions changed
then
    var urlicon = triggeringItem.state.toString()
    urlicon = urlicon.replace("-","") + "icon.svg"
    urlicon = "http://192.168.0.12:8080/static/" + urlicon
    WeatherIcon.postUpdate(urlicon)
end