Create selected unit card component

Showing the unit information when select it from the map

July 9th, 2020


This is the 4th article of a Dashboard development series. You can check all the articles by clicking here

In this article we are going to build a card like the next one:

Selected unit card component

It will be showed below the map when we select a unit in it (click here to see how to build the map component).

Let's start by creating the selectedUnitCard.js and selectedUnitCard.html files in the components/selectedUnitCard directory.

selectedUnitCard.js

/**
 * @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
 * @author Luis Güette
 */

define(['angular', 'require'], (angular, require) => {
    'use strict';

    class SelectedUnitCardController {
        static get $$ngIsClass() {
            return true;
        }

        static get $inject() {
            return [];
        }

        constructor() {

        }

        $onInit() {

        }
    }

    return {
        bindings: {
            unit: '<?'
        },
        controller: SelectedUnitCardController,
        templateUrl: require.toUrl('./selectedUnitCard.html')
    };
});

selectedUnitCard.html

<md-card>
    <md-card-content>
        <p>Selected Unit</p>
    </md-card-content>
</md-card>

Then, we need to import it in hvac.js module:

hvac.js

/**
 * @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
 * @author Luis Güette
 */

define([
    'angular',
    'require',

    './pages/overview/overview.js',

    './components/map/map.js',
    './components/selectedUnitCard/selectedUnitCard.js',

    './services/unit.js'
], (
    angular,
    require,
    overview,
    map,
    selectedUnitCard,
    unitService
) => {
    'use strict';

    const hvacModule = angular
        .module('hvacModule', ['maUiApp'])
        .component('hvacOverview', overview)
        .component('hvacMap', map)
        .component('hvacSelectedUnitCard', selectedUnitCard)

        .factory('hvacUnit', unitService);

    hvacModule.config([
        'maUiMenuProvider',
        (maUiMenuProvider) => {
            maUiMenuProvider.registerMenuItems([
                {
                    name: 'ui.overview',
                    url: '/overview',
                    menuIcon: 'map',
                    template: '<hvac-overview></hvac-overview>',
                    menuText: 'Overview',
                    weight: 100
                },
            ]);
        }
    ]);

    return hvacModule;
}); // define

And, let's call the hvac-selected-unit-card component in the overview.html file:

overview.html

<div layout="row" layout-wrap layout-align="space-between start">
    <div flex="100" flex-gt-sm="50" flex-gt-md="60">
        <md-card>
            <md-card-header>
                <p md-colors="::{color: 'accent'}">Active Alarms</p>
            </md-card-header>

            <md-card-content>
                <hvac-map units="$ctrl.units"></hvac-map>
            </md-card-content>
        </md-card>

        <hvac-selected-unit-card></hvac-selected-unit-card>
    </div>
    <div flex="100" flex-gt-sm="50" flex-gt-md="40">
        <p>Column 2</p>
    </div>
</div>

When you reload the page, you will see something like this:

Overview page

We need to share the selected unit in the map component with the selectedUnitCard component. First, we need to update code on our map component.

map.js

/**
 * @copyright 2020 {@link http://infiniteautomation.com|Infinite Automation Systems, Inc.} All rights reserved.
 * @author Luis Güette
 */

define(['angular', 'require'], (angular, require) => {
    'use strict';

    const DEFAULT_CENTER = {
        lat: 35.618379,
        lon: -78.413052
    }

    const DEFAULT_ZOOM = 7

    class MapController {
        static get $$ngIsClass() {
            return true;
        }

        static get $inject() {
            return [];
        }

        constructor() {}

        $onInit() {
            if (!this.center) {
                this.center = DEFAULT_CENTER;
            }

            if (!this.zoom) {
                this.zoom = DEFAULT_ZOOM;
            }
        }

        addEventListener(map) {
            map.on('popupclose', event => {
                this.selectUnit(null);
            });
        }

        selectUnit(unit) {
            this.onSelectUnit({unit: unit});
        }
    }

    return {
        bindings: {
            center: '<?',
            zoom: '<?',
            options: '<?',
            units: '<',
            onSelectUnit: '&'
        },
        controller: MapController,
        templateUrl: require.toUrl('./map.html')
    };
});
  • We added an addEventListener() method to set the selected unit to null when on popupclose event.
  • We added a new callback binding called onSelectUnit.
  • We added a selectUnit() method, which call this.onSelectUnit({unit: unit}) to share the selected unit with the parent component.

map.html

<ma-tile-map
        center="$ctrl.center"
        zoom="$ctrl.zoom"
        options="$ctrl.options"
        on-move="$ctrl.center = $center; $ctrl.zoom = $zoom"
>
    <div ng-init="$ctrl.onlineUnitIcon = $leaflet.icon({iconUrl: '/rest/v2/file-stores/public/hvacDashboards/img/online-unit.svg', iconSize: [32,32]})"></div>
    <div ng-init="$ctrl.offlineUnitIcon = $leaflet.icon({iconUrl: '/rest/v2/file-stores/public/hvacDashboards/img/offline-unit.svg', iconSize: [32,32]})"></div>
    <div ng-init="$ctrl.addEventListener($map)"></div>

    <div ng-repeat="unit in $ctrl.units track by unit.name">
        <ma-tile-map-marker
                coordinates="[unit.lat, unit.lon]"
                riseonhover="true"
                options="{riseOnHover: true}"
                icon="unit.points.status.value ? $ctrl.onlineUnitIcon : $ctrl.offlineUnitIcon"
                on-click="$ctrl.selectUnit(unit)"
        >
            <p class="title" md-colors="{color: 'primary-700'}" ng-bind="unit.name"></p>

            <div class="data-container">
                <div>
                    <p md-colors="{color: 'accent'}">Occupancy</p>
                    <ma-point-value point="unit.points.occupancy"></ma-point-value>
                </div>
                <div>
                    <p md-colors="{color: 'accent'}">Status</p>
                    <ma-point-value point="unit.points.status"></ma-point-value>
                </div>
            </div>
        </ma-tile-map-marker>
    </div>
</ma-tile-map>
  • We call ng-init="$ctrl.addEventListener($map)" inside ma-tile-map to add the popupclose event listener to the map.
  • We added a on-click method to the ma-tile-map-marker, so when we click the marker it calls the selectUnit() method and pass the selected unit.

Then, we update the overview component to get the selected unit:

overview.js

...
onSelectUnit(unit) {
    this.selectedUnit = unit;
}
...
  • We add onSelectUnit() method to set the selectedUnit variable, which will be shared with the selectedUnitCard component.

overview.html

<div layout="row" layout-wrap layout-align="space-between start">
    <div flex="100" flex-gt-sm="50" flex-gt-md="60">
        <md-card>
            <md-card-header>
                <p md-colors="::{color: 'accent'}">Active Alarms</p>
            </md-card-header>

            <md-card-content>
                <hvac-map units="$ctrl.units" on-select-unit="$ctrl.onSelectUnit(unit)"></hvac-map>
            </md-card-content>
        </md-card>

        <hvac-selected-unit-card ng-if="$ctrl.selectedUnit" unit="$ctrl.selectedUnit"></hvac-selected-unit-card>
    </div>
    <div flex="100" flex-gt-sm="50" flex-gt-md="40">
        <p>Column 2</p>
    </div>
</div>
  • We added on-select-unit="$ctrl.onSelectUnit(unit)" to set the selectedUnit.
  • We added unit="$ctrl.selectedUnit" to pass the selectedUnit to the selectedUnitCard component.

Now that we have the unit information in the selectedUnitCard component, let's add this information, and update the style. You can download the location icon from here.

selectedUnitCard.html

<md-card md-colors="{border: 'primary-700'}">
    <md-card-content>
        <div layout="row" layout-wrap layout-align="space-between start">
            <div flex="100" flex-gt-sm="50" flex-gt-md="70">

                <div flex="100" class="title-container">
                    <ma-point-value point="$ctrl.unit.points.status"></ma-point-value>
                    <p md-colors="{color: 'primary-700'}" ng-bind="$ctrl.unit.name"></p>
                </div>

                <div class="data-container" flex="100" layout="row" layout-wrap layout-align="start start">
                    <div>
                        <p md-colors="{color: 'accent'}">Power</p>
                        <ma-point-value md-colors="{color: 'primary-700'}" point="$ctrl.unit.points.power"></ma-point-value>
                    </div>
                    <div>
                        <p md-colors="{color: 'accent'}">kW/ton</p>
                        <ma-point-value md-colors="{color: 'primary-700'}" point="$ctrl.unit.points.kwTon"></ma-point-value>
                    </div>
                    <div>
                        <p md-colors="{color: 'accent'}">Occupancy</p>
                        <ma-point-value point="$ctrl.unit.points.occupancy"></ma-point-value>
                    </div>
                </div>

            </div>

            <div class="location-container" flex="100" flex-gt-sm="50" flex-gt-md="30" layout="row" layout-align="start start">
                <img src="/rest/v2/file-stores/public/hvacDashboards/img/location.svg" alt="Location icon">
                <div>
                    <p md-colors="{color: 'accent'}">Location</p>
                    <span md-colors="{color: 'primary-700'}">{{ $ctrl.unit.lat }}, {{ $ctrl.unit.lon }}</span>
                </div>
            </div>
        </div>
    </md-card-content>
</md-card>

Finally, let's add some styles for this component in the hvac.css file:

...
hvac-selected-unit-card {
    text-transform: uppercase;
}

hvac-selected-unit-card md-card {
    border-style: solid !important;
    border-width: 2px !important;
}

hvac-selected-unit-card p {
    margin: 0;
}

hvac-selected-unit-card md-card-content > div {
    margin: 0 -1rem;
}

hvac-selected-unit-card md-card-content > div > div {
    padding: 0 1rem;
}

hvac-selected-unit-card .title-container ma-point-value {
    font-size: 1.375rem;
}

hvac-selected-unit-card .title-container p {
    font-size: 3rem;
    line-height: 1;
}

hvac-selected-unit-card .data-container {
    margin: 1rem -2rem 0 -2rem;
}

hvac-selected-unit-card .data-container div {
    padding: 2rem 2rem 0 2rem;
}

hvac-selected-unit-card .data-container p {
    font-size: 1.25rem;
    font-weight: 700;
}

hvac-selected-unit-card .location-container {
    padding: 1rem;
}

hvac-selected-unit-card .location-container div {
    margin-left: 1rem;
}

hvac-selected-unit-card .location-container p {
    font-size: 1.25rem;
    font-weight: 700;
}

hvac-selected-unit-card .location-container span {
    font-size: 1.375rem;
}
...

When you reload the browser, you should see a page like this:

Overview page

Go to Create a units table component

Copyright © 2020 Radix IoT, LLC.