开发者中心

小部件插件

概述

在以下文档中,您将了解如何开发一个插件,将新的小部件添加到仪表盘。

  • Iconmap插件:创建一个插件,将设备显示为图像,而不是地图上的标记。
  • 天气插件:创建一个插件,显示基于某个设备的位置的天气信息。

在开始处理小部件插件之前,我们建议您查看介绍应用程序和插件的基本概念的介绍以及"Hello world!"演示插件。

您可以在存储库cumulocity-ui-plugin-examples中的文档中找到相关插件。

Iconmap插件

使用以下插件,新的小部件将可用于显示设备作为地图上的图标的仪表盘。新的小部件如下所示:

Iconmap小部件

为了实现这个目标,您需要执行以下步骤:

  • 创建一个插件。
  • 应用程序清单的导入列表上声明插件。
  • 将项目添加到小部件菜单列表。
  • 获取设备的图像。
  • 为小部件创建视图。

我们假设您已经创建了一个可以添加新插件的应用程序。如果没有,您可以使用上面提到的存储库中提供的应用程序。您还可以在文件夹"plugins/iconmap"中找到此处描述的示例。

创建一个插件

在应用程序文件夹中,运行命令:


 $ c8y create:plugin iconmap
                  

然后在/plugins/iconmap中编辑插件清单以添加以下信息:


  {
    "name": "Icon Map",
    "description": "Shows devices on a map using an icon for the device type.",
  }
                  

然后在插件的根文件夹中创建一个文件"iconmap.module.js",包含以下内容:


  (function () {
    'use strict';

    angular.module('myapp.iconmap', []);
  }());
                  

更新应用程序清单以将此新插件添加到导入列表。


    {
    ...
    "imports": [
    ...
    "myapplication/iconmap"
    ]
  }
                  
将项目添加到小部件菜单列表

接下来,我们必须创建一个配置文件,它将一个菜单项添加到小部件菜单列表中。 为此,我们可以使用由QuarkIoE JavaScript API提供的服务"c8yComponentsProvider"。 将服务注入到您的配置并调用以下功能:

(function () {
                    'use strict';

                    angular
                    .module('myapp.iconmap')
                    .config(configure);

                    configure.$inject = [
                    'c8yComponentsProvider',
                    'gettext'
                    ];

                    function configure(
                    c8yComponentsProvider,
                    gettext
                    ) {
                    c8yComponentsProvider.add({ // adds a menu item to the widget menu list with ...
                    name: 'Icon Map', // ... the name *"Icon Map"*
                    nameDisplay: gettext('Icon Map'), // ... the displayed name *"Icon Map"*
                    description: gettext('Displays a map with icons for devices instead of markers'), // ... a description
                    templateUrl: ':::PLUGIN_PATH:::/views/iconmap.main.html', // ... displaying *"iconmap.main.html"* when added to the dashboard
                    options: { noDeviceTarget: true }
                  });
                }
              }());
                    
获取设备的图像

首先,我们需要定义一个数组"markers",其中包含要在地图上显示的每个设备的标记。 在此示例中,我们将根据设备的硬件模型为其分配图像。 要获取图像,我们需要借助"c8yBinary"服务获取设备清单中的所有二进制对象。 然后,我们必须过滤代表某个硬件模型的图像的二进制对象。 之后,设备将根据其"c8y_Position"片段与图像(如果有用于硬件模型的图像)或与常用标记(如果没有)一起放置在地图上。


(function () {
  'use strict';

    angular
    .module('myapp.iconmap')
    .controller('iconmapController', iconmapController);

    iconmapController.$inject = [
    '$scope',
    '$q',
    'c8yInventory',
    'c8yBinary'
    ];

    function iconmapController(
    $scope,
    $q,
    c8yInventory,
    c8yBinary
    ) {
    $scope.markers = [];

    var getDevicesAndBinaries = {
    devices: getDevicesWithLocation(),
    binaries: c8yBinary.list({})
  };
  $q.all(getDevicesAndBinaries).then(placeTypes);

  function getDevicesWithLocation() {
  var filters = {fragmentType: 'c8y_Position' };
  return c8yInventory.list(filters);
}
  function placeTypes(devicesAndBinaries) {
  var devicesOfType = createTypeMap(devicesAndBinaries.devices);
  var iconOfType = createIconMap(devicesAndBinaries.binaries);
  angular.forEach(devicesOfType, _.curry(placeType)(iconOfType));
  }

  function placeType(iconOfType, devices, type) {
  var icon = iconOfType[type];
  if (icon) {
  var placeDevices = _.curry(place)(devices);
  c8yBinary.downloadAsDataUri(icon).then(placeDevices);
} else {
place(devices);
}
}

function createTypeMap(devices) {
var typeMap = {};
angular.forEach(devices, _.curry(addDeviceToTypeMap)(typeMap));
return typeMap;
}

function addDeviceToTypeMap(typeMap, device) {
var hw = 'default';
if (device.c8y_Hardware && device.c8y_Hardware.model) {
hw = device.c8y_Hardware.model;
}

if (!typeMap[hw]) {
typeMap[hw] = [];
}

typeMap[hw].push(device);
}

function createIconMap(binaries) {
var iconMap = {};
angular.forEach(binaries, _.curry(addIconToIconMap)(iconMap));
return iconMap;
}

function addIconToIconMap(iconMap, icon) {
if (c8yBinary.isImage(icon)) {
var name = icon.name;
name = name.substring(0, name.lastIndexOf('.'));
iconMap[name] = icon;
}
}

function place(devices, uri) {
angular.forEach(devices, _.curry(placeDevice)(_, uri));
}

function placeDevice(device, uri) {
var pos = device.c8y_Position;
var marker = {
lat: pos.lat,
lng: pos.lng,
message: '<a href="#/device/' + device.id + '">' + device.name + '</a>'
};

if (uri) {
marker.icon = { iconUrl: uri };
}

$scope.markers.push(marker);
}
}
}());
                    

现在我们已经将模块,config和controller添加到我们的插件,我们必须指定"myapp.iconmap"作为我们的模块,并将每个javascript文件添加到我们的插件清单:


    {
    "name": "Icon Map",
    "description": "Shows devices on a map using an icon for the device type.",
    "ngModules": [
    "myapp.iconmap"
    ],
    "js": [
    "iconmap.module.js"
    "iconmap.config.js",
    "iconmap.controller.js"
    ]
    }
                    
为小部件创建视图

在我们的配置中,我们已经指定了.html文件,其中包含我们的小部件的视图。 在这个例子中,我们的小部件应该显示一个简单的地图。 要向视图添加地图,请在插件文件夹中创建一个文件夹"views",创建一个文件"iconmap.main.html",并添加以下内容:


  <div ng-controller="iconmapController">
  <leaflet markers="markers" ></leaflet>
  </div>
                    

"leaflet"标签向我们的小部件添加了一个交互地图。 要在地图上显示设备,我们只需要将我们在控制器中定义的数组分配给"leaflet"标签的"markers"属性。

测试插件

在将插件部署到您的租户后,您应该能够创建一个小部件"图标地图"。 请注意,为了查看设备的图像,您必须将设备类型为文件名的图像上传到租户的文件存储库

天气插件

使用以下插件,新的小部件将可用于显示设备位置的当前天气的仪表盘。 新的小部件如下所示:

天气小部件

为了实现这个目标,您需要执行以下步骤:

  • 创建使用Dark Sky API的插件。
  • 创建用于输入API密钥的插件。
  • 将项目添加到导航器菜单。
  • 创建一个视图,用户可以在其中保存API密钥。
  • 为小部件创建一个插件。
  • 应用程序清单的导入列表上声明插件。
  • 将项目添加到小部件菜单列表。
  • 获取设备的天气。
  • 为小部件创建视图。

我们假设您已经创建了一个可以添加新插件的应用程序。 如果没有,您可以使用上面提到的存储库中提供的应用程序。 您还可以在文件夹"plugins/weather", "plugins/weatherAdmin"和"plugins/weatherService"中找到此处描述的示例。

创建使用Dark Sky API的插件

在这种情况下,我们建议您从存储库下载"weatherService"插件并将其保存在应用程序中。 此插件提供保存和加载API密钥以及检索天气信息。

不要忘记将该插件包含在应用程序清单中:


    {
    ...
    "imports": [
    ...
    "myapplication/weatherService"
    ]
    }
                    
创建用于输入API密钥的插件

在应用程序文件夹中,运行命令:

$ c8y create:plugin weatherAdmin
                    

然后在/plugins/weatherAdmin中编辑插件清单以添加以下信息:


  {
  "name": "Weather settings",
  "description": "Configure the API key for weather forecasts",
  "icon": "cloud",
  "category": "Administrator",
  "imports": [
  "myapplication/weatherService"
  ]
  }
                    

我们将导入"weatherService"插件,因为它提供了加载API密钥或保存用户输入的API密钥的可能性。

更新应用程序清单以将此新插件添加到导入列表。


    {
    ...
    "imports": [
    ...
    "myapplication/weatherAdmin"
    ]
    }
                    
将项目添加到导航器菜单

接下来,我们必须创建一个配置文件,它将一个项目添加到导航菜单。 为此,我们可以使用由QuarkIoE JavaScript API提供的服务"c8yNavigatorProvider"和"c8yViewsProvider"。 将服务注入到您的配置并调用以下功能:


    (function () {
    'use strict';

    angular
    .module('myapp.weatherAdmin', [ 'myapp.weatherService' ])
    .config(configure);

    configure.$inject = [
    'c8yNavigatorProvider',
    'c8yViewsProvider',
    'gettext'
    ];

    function configure(c8yNavigatorProvider, c8yViewsProvider, gettext) {
    c8yNavigatorProvider.addNavigation({ // adds a menu item to the navigator with ...
    parent: gettext('Settings'), // ... the category *"Settings"*
    name: gettext('Weather'), // ... the name *"Weather"*
    path: 'weather', // ... */weather* as path
    icon: 'cloud' // ... the cloud icon (icons are provided by the great Font Awesome library and you can use any of their [icon names](http://fontawesome.io/icons/) without the *fa-* prefix here
    });

    c8yViewsProvider.when('/weather', { // when the path "/weather" is accessed ...
    templateUrl: ':::PLUGIN_PATH:::/views/weatherAdmin.html' //  ... display our html file "weatherAdmin.html" inside the "views" folder of our plugin (the plugin's folder is represented using the magic string ```:::PLUGIN_PATH:::```, which is replaced by the actual path during the build process)
    });
    }
    }());
                    

在我们的控制器中,我们只需要实现一个函数来加载API密钥和保存用户输入的API密钥。 为了加载API密钥,我们使用"weatherService"插件提供的"load"方法。


    (function () {
    'use strict';

    angular
    .module('myapp.weatherAdmin')
    .controller('weatherAdminController', weatherAdminController);

    weatherAdminController.$inject = [
    '$scope',
    'c8yTitle',
    'weatherService',
    'gettext'
    ];

    function weatherAdminController($scope, c8yTitle, weatherService, gettext) {
    $scope.updateKey = updateKey;
    weatherService.load().then(function setOpt(key) {
    $scope.key = key;
    });

    c8yTitle.changeTitle({
    title: gettext('Weather provider settings')
    });

    function updateKey() {
    weatherService.save($scope.key);
    }
    }
    }());
                    

现在我们已经添加了配置和控制器到我们的插件,我们必须指定"myapp.weatherAdmin"作为我们的模块,并将每个javascript文件添加到我们的插件清单:


    {
    "name": "Weather settings",
    "description": "Configure the API key for weather forecasts",
    "icon": "cloud",
    "category": "Administrator",
    "imports": [
    "myapplication/weatherService"
    ],
    "ngModules": [
    "myapp.weatherAdmin"
    ],
    "js": [
    "weatheradmin.config.js"
    "weatheradmin.controller.js"
    ]
    }
                    
创建一个视图,用户可以在其中保存API密钥

在我们的配置中,我们已经指定了包含导航项目视图的.html文件。 在此示例中,我们的视图应显示一个简单的文本和输入字段以及一个用于保存的按钮。 要将此视图添加到插件,请在插件文件夹中创建一个文件夹"views",创建一个文件"weatherAdmin.main.html"并添加以下内容:


    <div ng-controller="weatherAdminController">
    <div class="col-lg-6 panel panel-clean">
    <p translate>Weather functionality is based on the <a href="https://darksky.net" target="_blank">Dark Sky</a> service. Usage of Dark Sky requires an API key that can be obtained by registering at <a href="https://darksky.net/dev/" target="_blank">https://darksky.net/dev/</a>. Paste the API key below.</p>
    <form class="form-horizontal" name="weatherAdminForm" novalidate>
    <div class="form-group">
    <label for="key" class="control-label" translate>API Key</label>
    <div ng-class="{'has-error': invalid('license')}">
    <input type="text" class="form-control" required name="key" id="key" ng-model="key" c8y-autocomplete="off">
    </div>
    </div>
    <div class="form-group ">
    <button type="submit" class="btn btn-primary" ng-click="updateKey()"
    ng-disabled="weatherAdminForm.$invalid||weatherAdminForm.$pristine" translate>
    Save
    </button>
    </div>
    </form>
    </div>
    </div>
                    
为小部件创建插件

在应用程序文件夹中,运行命令:


  $ c8y create:plugin weather
                    

然后在/plugins/weather中编辑插件清单以添加以下信息:


    {
    "name": "Weather",
    "description": "Shows the current weather at the location of a device.",
    "category": "Widgets",
    "icon": "cloud",
    "imports": [
    "myapplication/weatherService"
    ]
    }
                    

我们将导入"weatherService"插件,因为它为我们提供了获取某个位置的天气信息的能力。

更新应用程序清单以将此新插件添加到导入列表。


    {
    ...
    "imports": [
    ...
    "myapplication/weather"
    ]
    }
                    
将项目添加到小部件菜单列表

接下来,我们必须创建一个配置文件,它将一个菜单项添加到小部件菜单列表中。 为此,我们可以使用由QuarkIoE JavaScript API提供的服务"c8yComponentsProvider"。 将服务注入到您的配置并调用以下功能:


    (function () {
    'use strict';

    angular
    .module('myapp.weather', [ 'myapp.weatherService' ])
    .config(configure);

    configure.$inject = [
    'c8yComponentsProvider',
    'gettext'
    ];

    function configure(c8yComponentsProvider, gettext) {
    c8yComponentsProvider.add({ // adds a menu item to the widget menu list with ...
    name: 'weather', // ... the name *"weather"*
    nameDisplay: gettext('Weather'), // ... the displayed name *"weather"*
    description: gettext('Shows the current weather at the location of a device'), // ... a description
    templateUrl: ':::PLUGIN_PATH:::/views/weather.main.html' // ... displaying *"iconmap.main.html"* when added to the dashboard
    });
    }
    }());
                    
获取设备的天气

在我们的控制器中,我们基于在小部件对话框中选择的设备的位置来获得天气信息。 如果设备更改,小部件也将更新。


    (function () {
    'use strict';

    angular
    .module('myapp.weather')
    .controller('weatherController', weatherController);

    weatherController.$inject = [
    '$scope',
    '$q',
    'weatherService',
    'gettext',
    'c8yInventory'
    ];

    function weatherController($scope, $q, weatherService, gettext, c8yInventory) {
    $scope.$watch('child.config.device', function reInit(newVal, oldVal) {
    if (newVal && !angular.equals(newVal, oldVal)) {
    init();
    }
    }, true);
    init();

    function init() {
    getDevice().then(tryGetWeather).then(showWeather, printError);
    }

    function getDevice() {
    var deviceId = $scope.child.config.device.id;
    $scope.status = gettext('Retrieving device ...');
    return c8yInventory.detail(deviceId);
    }

    function tryGetWeather(res) {
    $scope.device = res.data;

    if (locationAvailable($scope.device)) {
    $scope.status = gettext('Retrieving weather ...');
    return getWeather($scope.device.c8y_Position);
    }

    $scope.status = gettext('Device has not reported a location, cannot retrieve weather.');
    return $q.reject();
    }

    function locationAvailable(device) {
    return device && device.c8y_Position && device.c8y_Position.lat && device.c8y_Position.lng;
    }

    function getWeather(coordinate) {
    return weatherService.weather.getCurrent(coordinate.lat, coordinate.lng);
    }

    function showWeather(weather) {
    $scope.weather = weather;
    $scope.windDirection = {
    'display': 'inline-block',
    '-ms-transform': rotate(weather),
    '-webkit-transform': rotate(weather),
    'transform': rotate(weather)
    };
    $scope.status = 'ready';
    }

    function printError() {
    $scope.status = gettext('Error retrieving weather information.');
    }

    function rotate(weather) {
    var direction = (weather.currently.windBearing + 180) % 360;
    return 'rotate(' + direction + 'deg)';
    }
    }
    }());
                    

现在我们已经添加了配置和控制器到我们的插件,我们必须指定"myapp.weather"作为我们的模块,并将每个javascript文件添加到我们的插件清单:


  {
    "name": "Weather",
    "description": "Shows the current weather at the location of a device.",
    "category": "Widgets",
    "icon": "cloud",
    "imports": [
    "myapplication/weatherService"
    ],
    "ngModules": [
    "myapplication.weather"
    ],
    "js": [
    "weather.config.js",
    "weather.controller.js"
    ]
  }
                    
为小部件创建视图

在我们的配置中,我们已经指定了.html文件,其中包含我们的小部件的视图。 在这个例子中,我们的小部件应该显示一个简单的表,其中包含关于设备位置的温度,压力,湿度和风的信息。 要将表添加到视图,请在插件文件夹中创建一个文件夹"views",创建一个文件"weather.main.html",并添加以下内容:


    <div ng-controller="weatherController" style="padding: 10px">
    <div ng-show="status != 'ready'" class="alert alert-info">{{ status }}</div>
    <div ng-show="status == 'ready'">
    <table class="table">
    <tbody>
    <tr>
    <td>{{ 'Weather' | translate }}</td>
    <td>
    <dark-sky-icon icon="{{ weather.currently.icon }}" uib-tooltip="{{weather.currently.summary | translate }}" tooltip-append-to-body="true"></dark-sky-icon>
    </td>
    </tr>
    <tr>
    <td>{{ 'Temperature' | translate }}</td>
    <td>{{weather.currently.temperature}} C</td>
    </tr>
    <tr>
    <td>{{ 'Pressure' | translate }}</td>
    <td>{{weather.currently.pressure}} hPa</td>
    </tr>
    <tr>
    <td>{{ 'Humidity' | translate }}</td>
    <td>{{weather.currently.humidity}} %</td>
    </tr>
    <tr>
    <td>{{ 'Wind' | translate }}</td>
    <td>{{weather.currently.windSpeed}} {{ 'm/s' | translate }}
    <span class="direction" ng-style="windDirection" uib-tooltip="{{weather.currently.windBearing}} {{ 'deg' | translate }}">↑</span>
    </td>
    </tr>
    </tbody>
    </table>
    <a href="https://darksky.net/poweredby/" target="_blank">Powered by Dark Sky</a>
    </div>
    </div>
                    
测试插件

部署插件到您的租户后,您应该能够创建一个小部件"天气"。 请注意,您必须先输入API密钥才能查看天气信息。