Create an Interactive Widget

This last section of the walkthrough will cover how to create a widget that is more than just a data view. A common task when testing an EVRYTHNG Platform integration is to create an action (useful for testing a Reactor script, Redirector rule, populating a Thng’s history etc.). This can be done through the ‘Testing’ section of the Dashboard but it could also be a useful tool to have elsewhere in the application, and it also shows a simple enough example functionality for walkthrough purposes.

The final product will look like the following:

Let’s get started!

📘

Note

Make sure you have completed the last section before starting this one.


 Initial Setup

Either use the component gulp task to create new files for this widget (called quick-actions), or adapt the one from the last section. The three files should begin as shown below:

<evtx-widget-base evt-widget="$ctrl.evtWidget" loading="false">
  
  <widget-body layout="column">
    <div class="action-wrapper">

    </div>
  </widget-body>

  <widget-footer layout="row" class="truncate">
    
  </widget-footer>

</evtx-widget-base>
.action-wrapper {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
}
import './quick-actions.scss';

import template from './quick-actions.html';

export class QuickActionsController {

  constructor($scope, EVT) {
    'ngInject';

    this.$scope = $scope;
    this.EVT = EVT;
  }

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

  refresh() {
    
  }

  createAction() {
    
  }

}

export default {
  bindings: { evtWidget: '<' },
  template,
  evtWidget: {
    defaultConfig: {
      title: { value: 'Quick Actions' },
      description: { value: 'Create a simple action on a Thng.' },
    }
  },
  controller: QuickActionsController,
};

As you can see, the HTML template starts out almost the same as the widget from the last section, with an initial div with a wrapper class that controls overall alignment of the elements that will be added shortly. The JavaScript controller also has a similar structure including the defaultConfig from the beginning, as well as some other empty methods that will be implemented fully later on.


Adding the Inputs

As seen at the top of this page, the widget itself will have a few interactive elements:

  • A text input to allow the user to specify the action type to use for the created action.
  • A select allowing choice of the 30 most recently created Thngs (though this should be replaced with a type-ahead search for a real-use case) on which the action will be created.
  • A div that will behave like a button and run the createAction() on the controller, performing the actual resource creation.

In addition, there are also two div elements to label each input element. Add all these to your quick-actions.html (or equivalent) file using the example below. The CSS class definitions are also shown, and should be copied to quick-actions.scss:

<widget-body layout="column">
  <div class="action-wrapper">
    <div class="action-label">Action Type</div>
    <input class="action-input" type="text" ng-model="$ctrl.state.actionType"/>
    
    <div class="action-label">Thng</div>
    <select class="action-select" ng-model="$ctrl.state.selectedThng">
      
    </select>
    
    <div class="action-button" ng-click="$ctrl.createAction()">Create</div>
  </div>
</widget-body>
.action-label {
  font-size: 1.5rem;
  color: black;
  font-weight: bold;
}

.action-input {
  width: 200px;
  outline: 0;
  border: none;
  text-align: center;
  border-bottom: 2px solid #bbb;
  margin-bottom: 20px;
}

.action-input:hover {
  border-bottom: 2px solid #888;
}

.action-input:focus {
  border-bottom: 2px solid black;
}

.action-select {
  height: 30px;
  background-color: white;
  border: 1px solid #eee;
  margin-bottom: 5px;
}

.action-button {
  padding-top: 5px;
  padding-bottom: 5px;
  padding-left: 12px;
  padding-right: 12px;
  margin-top: 5px;
  background-color: white;
  color: black;
  font-size: 1.5rem;
  border: 2px solid white;
  border-radius: 5px;
}

.action-button:hover {
  background-color: #eee;
  cursor: pointer;
}

.action-button:focus {
  outline: none;
}

Note the use of Angular bindings to connect the ng-click event on the button to the createAction() method from the controller class. There are also model bindings (using ng-model) of the action type input and Thng select to the controller classes state property - which does not yet exist! Let’s fix that now by adding the state in the controller’s constructor in quick-actions.js:

constructor($scope, EVT) {
  'ngInject';

  this.$scope = $scope;
  this.EVT = EVT;

  this.state = {
    actionType: 'scans',
    thngs: [],
    selectedThng: '',
    footerText: '',
  };
}

This state will allow the widget to keep track of the user’s chosen action type and Thng, as well as the list of Thngs downloaded. This also includes the footerText property that will be used later on to show the state of the action creation. The ng-model binding in the HTML template will update these automatically as the user performs their input keystrokes or selects an item from the Thng select.

The Thngs that will be available to choose from will be read from the account when the widget loads, using the refresh() method, which is currently empty. The completed version of this method is shown below, and should be added to your quick-actions.js:

refresh() {
  this.EVT.operator.thng().read()
    .then((thngs) => {
      this.state.thngs = thngs;
      this.state.selectedThng = thngs[0].id;
    })
    .catch((err) => {
      console.log(err);
      alert(err);
    })
    .then(() => this.$scope.$apply());
}

This will ensure that the state is given the list of read Thngs, and the first one chosen as the currently selected Thng. The Angular ng-model bindings will take care of updating the widget template, once $apply() is called.

Now that the list of Thngs to choose from is available in code, it should also be presented to the user. This is achieved by adding an option element inside the select for each item in the list. Angular makes this possible by using the ng-repeat directive. The select in the HTML template should be updated as follows:

<select class="action-select" ng-model="$ctrl.state.selectedThng">
  <option ng-repeat="item in $ctrl.state.thngs" value="{{ item.id }}">
    {{ item.name }}
  </option>
</select>

Thus the select will show and update the selectedThng property of the widget’s state, and will contain an option for each item in state.thngs, with each having the Thng’s ID as its value and displaying the Thng’s name as the option’s visible text. This is all made possible through the ng-model bindings and template values.


Creating the Action

The next piece of missing functionality is the actual creation of the action when the button is pressed. Since the ng-click directive is already in place on the button in the HTML template, all that remains is to implement the method it refers to (createAction()) in the JavaScript controller:

createAction() {
  // Create payload from state
  const { selectedThng, actionType } = this.state;
  const payload = { type: actionType, thng: selectedThng };
  
  // Create action and update template
  this.EVT.operator.thng(selectedThng).action(actionType).create(payload)
    .then((action) => {
      this.state.footerText = `Action created successfully!`;
    })
    .catch((err) => {
      console.log(err);
      alert(err);
    })
    .then(() => this.$scope.$apply());
}

As you can see, the method uses the values of the selectedThng and actionType from the widget state (as bound to the HTML elements earlier) to create a payload for the Create an Action API operation. Then, the operator scope made available by the Dashboard is used to perform the actual action creation using the evrythng.js SDK, also provided by the Dashboard.

If the operation was successful, the value of footerText is updated and applied, to show the user that their action was created. If it fails, the error is shown as an alert, and also printed to the console for debugging.

Add this final element (widget-footer) to the HTML template now, below widget-body. Note it uses the same template value mechanism to show the value present in the widget - state.footerText:

<widget-footer layout="row" class="truncate">
  <span flex>{{ $ctrl.state.footerText }}</span>
</widget-footer>

That is it for the building of the widget - time to use it!


Using the Widget

As in the last section, the widget will need to be added to src/components/components.js to be included in the built bundle. Do this now:

import propertyView from './property-view/property-view';
import quickActions from './quick-actions/quick-actions';

export default angular.module('propertyView.components', [])
  .component('propertyView', propertyView)
  .component('quickActions', quickActions);

Next, use the gulp task to assemble the component bundle, then add it to your hosting service of choice once more.

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

📘

Note

If the public URL of the bundle changed, make sure to update the list of bundles in the dashboard section’s ‘Edit dashboard’ dialog as before.

Add this new widget to your dashboard section of choice by opening the ‘Edit dashboard’ dialog, choosing a section, and adding the quick-actions widget. Save the configuration and let the page reload, then you should see the new widget shown and ready for use!

Test the widget out by entering an existing action type and selecting a Thng, then click the ‘Create’ button and verify the result message appears in the footer. If the action creation fails, make sure the ‘None’ project scope is selected on the left, and that the action type exists (if not, create it with the ‘Action Types’ page).

You can see the action that was created by going to the ‘Actions’ page and reading the top line of the list:


Conclusion

This is the end of the walkthrough! Hopefully you gained an insight into how EVRYTHNG Dashboard widgets work and are put together, and feel confident to start experimenting with ideas of your own.

Below are the final versions of all of the Quick Actions files for you to compare with your own, or to help with identifying any mistakes.

<evtx-widget-base evt-widget="$ctrl.evtWidget" loading="false">
  
  <widget-body layout="column">
    <div class="action-wrapper">
      <div class="action-label">Action Type</div>
      <input class="action-input" type="text" ng-model="$ctrl.state.actionType"/>
      
      <div class="action-label">Thng</div>
      <select class="action-select" ng-model="$ctrl.state.selectedThng">
        <option ng-repeat="item in $ctrl.state.thngs" value="{{ item.id }}">
          {{ item.name }}
        </option>
      </select>
      
      <div class="action-button" ng-click="$ctrl.createAction()">Create</div>
    </div>
  </widget-body>

  <widget-footer layout="row" class="truncate">
    <span flex>{{ $ctrl.state.footerText }}</span>
  </widget-footer>

</evtx-widget-base>
.action-wrapper {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: 100%;
}

.action-label {
  font-size: 1.5rem;
  color: black;
  font-weight: bold;
}

.action-input {
  width: 200px;
  outline: 0;
  border: none;
  text-align: center;
  border-bottom: 2px solid #bbb;
  margin-bottom: 20px;
}

.action-input:hover {
  border-bottom: 2px solid #888;
}

.action-input:focus {
  border-bottom: 2px solid black;
}

.action-select {
  height: 30px;
  background-color: white;
  border: 1px solid #eee;
  margin-bottom: 5px;
}

.action-button {
  padding-top: 5px;
  padding-bottom: 5px;
  padding-left: 12px;
  padding-right: 12px;
  margin-top: 5px;
  background-color: white;
  color: black;
  font-size: 1.5rem;
  border: 2px solid white;
  border-radius: 5px;
}

.action-button:hover {
  background-color: #eee;
  cursor: pointer;
}

.action-button:focus {
  outline: none;
}
import './quick-actions.scss';

import template from './quick-actions.html';

export class QuickActionsController {

  constructor($scope, EVT) {
    'ngInject';

    this.$scope = $scope;
    this.EVT = EVT;
    
    this.state = {
      actionType: 'scans',
      thngs: [],
      selectedThng: '',
      footerText: '',
    };
  }

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

  refresh() {
    this.EVT.operator.thng().read()
      .then((thngs) => {
        this.state.thngs = thngs;
        this.state.selectedThng = thngs[0].id;
      })
      .catch((err) => {
        console.log(err);
        alert(err);
      })
      .then(() => this.$scope.$apply());
  }

  createAction() {
    // Create payload from state
    const { selectedThng, actionType } = this.state;
    const payload = { type: actionType, thng: selectedThng };
    
    // Create action and update template
    this.EVT.operator.thng(selectedThng).action(actionType).create(payload)
      .then((action) => {
        this.state.footerText = `Action created successfully!`;
      })
      .catch((err) => {
        console.log(err);
        alert(err);
      })
      .then(() => this.$scope.$apply());
  }

}

export default {
  bindings: { evtWidget: '<' },
  template,
  evtWidget: {
    defaultConfig: {
      title: { value: 'Quick Actions' },
      description: { value: 'Create a simple action on a Thng.' },
    }
  },
  controller: QuickActionsController,
};

Updated 2 years ago

Create an Interactive Widget


Suggested Edits are limited on API Reference Pages

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