Create a Simple Widget

In this section we will complete the simple widget we began in the last section - a clear view into a single Thng property’s current value, useful for a glanceable summary of some important property.

📘

Note

Make sure you have completed the previous section before continuing.


Preparing the Template

As it was created, the src/components/property-view/property-view.js file will have the HTML structure of the widget included inside the module’s export. Since it will grow larger and more complex soon, move it to its own file in the same directory - property-view.html, and include it in the JavaScript file using import. Then update the export to refer to the imported template in the template field, as the following two samples show:

<div>
  <h1>{{ $ctrl.name }}</h1>
</div>
import './property-view.scss';

import template from './property-view.html';

export class PropertyViewController {

  constructor() {
    this.name = 'property-view';
  }

}

export default {
  bindings: {},
  template,
  controller: PropertyViewController
};

To extend the basic Dashboard widget component’s styles and automatically include some basic layout features, replace the contents of property-view.html with the following:

<evtx-widget-base evt-widget="$ctrl.evtWidget" loading="false">
  
  <widget-body layout="column">
    
  </widget-body>
  
  <widget-footer layout="row" class="truncate">
    
  </widget-footer>

</evtx-widget-base>

As you can see, the outermost element is evtx-widget-base, and has attributes that enable binding to the evtWidget property available at runtime (more on this later) and an initial loading state.

Within this element are two more with suitably intuitable purposes - widget-body and widget-footer. These two are used to contain the remaining elements in either the body of the widget or the footer area.


Preparing the JavaScript

Go back to the property-view.js file and update the export as follows to include evtWidget in bindings, as well as the defaultConfig object describing configurable values of evtx-widget-base. Later on we will add more items to this object to allow the user to select the Thng and property they would like to show:

export default {
  bindings: { evtWidget: '<' },
  template,
  evtWidget: {
    defaultConfig: {
      title: { value: 'Property View' },
      description: { value: 'This widget shows the state of a Thng property.' },
    }
  },
  controller: PropertyViewController,
};

These modifications enable the binding of evtWidget to the HTML (also required for the evtx-widget-base component to show the title and description from the defaultConfig), and also a place to specify default values for that user configuration.

Lastly, update the PropertyViewController class definition as follows:

export class PropertyViewController {

  constructor($scope) {
    'ngInject';
    
    this.$scope = $scope;
  }

}

This tells Angular to provide us with a reference to the class instance that can be used in other methods which we will use later on.

Now we are ready to test out this new version of the widget. Repeat the build step:

# Build the bundle
./node_modules/.bin/gulp build

Then repeat the steps to update the public URL using the storage mechanism or service of your choice. The URL should remain the same, but if this is not possible make sure you remove and re-add the bundle in the dashboard page’s ‘Edit Dashboard’ modal.

Click ‘Update’ to save the changes to the dashboard section and wait for the Dashboard to reload. Since we added the configuration capability to a widget you may already have in the dashboard section, you may need to remove it and re-add it as well before the title and description are visible.

After this, you will be able to see that the widget not only looks like the built-in widgets, but that is also has the defaultConfig values you specified earlier displayed in the base widget:


Enhancing the Widget

The template HTML file should now be modified to contain three elements to display the property name, property value, and Thng ID. This give the widget its ability to serve as a quick glance view of that property value. Update your copy of the property-view.html file as shown below, with the new elements added in widget-body and widget-footer:

<evtx-widget-base evt-widget="$ctrl.evtWidget" loading="false">

  <widget-body layout="column">
    <div class="wrapper">
      <div class="property-name">temperature</div>
      <div class="property-value">23.235</div>
    </div>
  </widget-body>
  
  <widget-footer layout="row" class="truncate">
    <div class="thng-details">Thng: UK4sACmwqGax92aaa2rm8kar</div>
  </widget-footer>

</evtx-widget-base>

Note the following changes:

  • The wrapper class div that controls overall alignment of its children.
  • The property-name class div that contains the property name.
  • The property-value class div that contains the property value.
  • The thng-details class div in the footer, that shows the Thng ID.

The values of these are hardcoded for now so we can verify the styles are correct, but they will shortly be replaced with dynamic placeholders to be populated by the JavaScript controller later, in this section.

To ensure the classes used above are defined, update the property-view.scss file with the following:

.wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
}

.property-name {
  font-size: 4rem;
  margin-bottom: 10px;
}

.property-value {
  font-size: 8rem;
  font-weight: bold;
}

.thng-details {
  color: grey;
}

Perform the build and upload steps once again, but this time you won’t have to remove or re-add anything - the Dashboard will automatically get the changes with a simple reload. Now you will be able to see the new layout and styles and get a glimpse of what the widget will soon be able to do:


Showing Real Data

The final steps necessary for this widget to show real, useful data are as follows:

  • Modify the configuration to allow specification of Thng ID and property name by the user.
  • Fetch that data using the built-in EVRYTHNG SDK.
  • Display the values within the widget’s template.

This is accomplished by making changes in property-view.js. First, update the module’s export to include two new configuration values the user will use to select which Thng property will be displayed by the widget, and give placeholder values for each:

export default {
  bindings: { evtWidget: '<' },
  template,
  evtWidget: {
    defaultConfig: {
      title: { value: 'Property View' },
      description: { value: 'This widget shows the state of a Thng property.' },
      thngId: { value: 'THNG_ID' },
      propertyName: { value: 'PROPERTY_NAME' },
    }
  },
  controller: PropertyViewController,
};

Next, update the controller class to inject a reference to the EVT SDK instance provided by the application, and set an initial state to be used to reflect values in the template:

export class PropertyViewController {
  
  constructor($scope, EVT) {
    'ngInject';

    this.$scope = $scope;
    this.EVT = EVT;
    this.state = {
      propertyName: '',
      propertyValue: '',
      thngId: '',
    }; 
  }
  
}

Now add the following method to the class to be called upon the initial loading of the widget:

$onInit() {
  this.refresh();
}

Make sure this method is also is called by adding the on-config-change attribute to the evtx-base-widget tag in the HTML file, so that the widget updates when either the Thng ID or property name specified by the user is changed:

<evtx-widget-base evt-widget="$ctrl.evtWidget" loading="false" 
  on-config-change="$ctrl.refresh()">

Next, we implement the previously mentioned refresh() method in the class as well, to perform the actual loading of data:

refresh() {
  const { thngId, propertyName } = this.evtWidget.config;

  // Reset state
  this.state.propertyName = propertyName.value;
  this.state.propertyValue = '';
  this.state.thngId = thngId.value;

  // Fetch property value
  const { operator } = this.EVT;
  operator.thng(thngId.value).property(propertyName.value).read()
    .then((history) => {
      this.state.propertyValue = history[0].value;
    })
    .catch((err) => {
      console.log(err);
      this.state.propertyValue = '!';
    })
    .then(() => this.$scope.$apply());
}

There are a few things going on here, so let’s break it down, from top to bottom:

  • The thngId and propertyName config values are read from the widget’s current configuration.
  • The state of the widget is reset to hide the old value of the property while a new one is loaded.
  • The operator object made available for our use by the Dashboard is used to read the Thng property as named by the user’s config values using the evrythng.js SDK.
    • If the read was successful, the state is updated with the most recent value of the property.
    • If the read failed, the same value placeholder is set to ‘!’ to indicate failure, and the error details printed to the console for debugging.
  • Finally, the Angular $apply() method is called to tell the Dashboard to redraw the widget with the loaded property value.

That’s all there is to the widget logic - we can now finish up by updating the HTML template to show the widget state. Go to property-view.html and update the elements contained by widget-body and widget-footer to use the controller class’s state, instead of the previously hard-coded values:

<widget-body layout="column">
  <div class="wrapper">
    <div class="property-name">{{ $ctrl.state.propertyName }}</div>
    <div class="property-value">{{ $ctrl.state.propertyValue }}</div>
  </div>
</widget-body>

<widget-footer layout="row" class="truncate">
  <div class="thng-details">Thng: {{ $ctrl.state.thngId }}</div>
</widget-footer>

Results

We are now ready to see the fruits of this labor - a custom widget that shows the latest value of a Thng property, which could be used as a key indicator, depending on the use-case.

As before, perform the build command and use your preferred public storage service to update the publicly accessible bundle. Since we added more items to the defaultConfig of the widget, it will need to be re-added again, so make sure to first remove it from any dashboard sections you previously added it to.

Finally, re-add the widget to any dashboard section and reload the Dashboard. The widget will be there, but will need to be correctly configured with a valid Thng ID and property name before it can be useful.

Use the ‘Thngs’ section of the navigation to find/create a Thng with one or more properties, such as the one below.

Make a note of the Thng’s ID and the name of one of its properties, then head back to the widget and use the three-dot menu to configure it. Enter the Thng ID into the thngId field, and the property name into the propertyName field, then save the widget configuration.

And behold - the property’s most recent value is now instantly visible!

📘

Note

This simple example will use the 'unscoped' view of the account to read the Thng, so make sure that you don't have a project selected when testing it out (select the 'None' project).

From now on the value will reflect the state of the Thng property when the page is loaded - it is left as an exercise for the reader to figure out how to make it update in real time.

Hint: it involves using the evrythng-mqtt.js SDK to add an MQTT subscription to the property!

Updated 2 years ago


Create a Simple Widget


Suggested Edits are limited on API Reference Pages

You can only suggest edits to Markdown body content, but not to the API spec.