Commit 2fdb372c893c842afdc5884d14a20a6e120b9d13

Authored by Igor Kulikov
1 parent 755b6d36

TB-64: Implement widget actions.

... ... @@ -171,6 +171,48 @@ export default class Subscription {
171 171 return deferred.promise;
172 172 }
173 173
  174 + getFirstEntityInfo() {
  175 + var entityId;
  176 + var entityName;
  177 + if (this.type === this.ctx.types.widgetType.rpc.value) {
  178 + if (this.targetDeviceId) {
  179 + entityId = {
  180 + entityType: this.ctx.entityType.device,
  181 + id: this.targetDeviceId
  182 + }
  183 + entityName = this.targetDeviceName;
  184 + }
  185 + } else if (this.type == this.ctx.types.widgetType.alarm.value) {
  186 + if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) {
  187 + entityId = {
  188 + entityType: this.alarmSource.entityType,
  189 + id: this.alarmSource.entityId
  190 + }
  191 + entityName = this.alarmSource.entityName;
  192 + }
  193 + } else {
  194 + for (var i=0;i<this.datasources.length;i++) {
  195 + var datasource = this.datasources[i];
  196 + if (datasource && datasource.entityType && datasource.entityId) {
  197 + entityId = {
  198 + entityType: datasource.entityType,
  199 + id: datasource.entityId
  200 + }
  201 + entityName = datasource.entityName;
  202 + break;
  203 + }
  204 + }
  205 + }
  206 + if (entityId) {
  207 + return {
  208 + entityId: entityId,
  209 + entityName: entityName
  210 + };
  211 + } else {
  212 + return null;
  213 + }
  214 + }
  215 +
174 216 initAlarmSubscription() {
175 217 var deferred = this.ctx.$q.defer();
176 218 if (!this.ctx.aliasController) {
... ... @@ -342,6 +384,7 @@ export default class Subscription {
342 384 function success(aliasInfo) {
343 385 if (aliasInfo.currentEntity && aliasInfo.currentEntity.entityType == subscription.ctx.types.entityType.device) {
344 386 subscription.targetDeviceId = aliasInfo.currentEntity.id;
  387 + subscription.targetDeviceName = aliasInfo.currentEntity.name;
345 388 if (subscription.targetDeviceId) {
346 389 subscription.rpcEnabled = true;
347 390 } else {
... ...
... ... @@ -626,7 +626,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
626 626 }
627 627 for (var actionSourceId in types.widgetActionSources) {
628 628 result.actionSources[actionSourceId] = angular.copy(types.widgetActionSources[actionSourceId]);
629   - result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name);
  629 + result.actionSources[actionSourceId].name = $translate.instant(result.actionSources[actionSourceId].name) + '';
630 630 }
631 631
632 632 return result;
... ...
... ... @@ -400,8 +400,9 @@ export default angular.module('thingsboard.types', [])
400 400 }
401 401 },
402 402 widgetActionSources: {
403   - 'headerButton': {
  403 + headerButton: {
404 404 name: 'widget-action.header-button',
  405 + value: 'headerButton',
405 406 multiple: true
406 407 }
407 408 },
... ...
... ... @@ -38,7 +38,11 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
38 38 materialColors = [],
39 39 materialIcons = [];
40 40
41   - var commonUsedMaterialIcons = [ 'more_horiz', 'close', 'play_arrow' ];
  41 + var commonMaterialIcons = [ 'more_horiz', 'more_vert', 'open_in_new', 'visibility', 'play_arrow', 'arrow_back', 'arrow_downward',
  42 + 'arrow_forward', 'arrow_upwards', 'close', 'refresh', 'menu', 'show_chart', 'multiline_chart', 'pie_chart', 'insert_chart', 'people',
  43 + 'person', 'domain', 'devices_other', 'now_widgets', 'dashboards', 'map', 'pin_drop', 'my_location', 'extension', 'search',
  44 + 'settings', 'notifications', 'notifications_active', 'info', 'info_outline', 'warning', 'list', 'file_download', 'import_export',
  45 + 'share', 'add', 'edit', 'done' ];
42 46
43 47 predefinedFunctions['Sin'] = "return Math.round(1000*Math.sin(time/5000));";
44 48 predefinedFunctions['Cos'] = "return Math.round(1000*Math.cos(time/5000));";
... ... @@ -148,7 +152,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
148 152 validateDatasources: validateDatasources,
149 153 createKey: createKey,
150 154 createLabelFromDatasource: createLabelFromDatasource,
151   - insertVariable: insertVariable
  155 + insertVariable: insertVariable,
  156 + customTranslation: customTranslation
152 157 }
153 158
154 159 return service;
... ... @@ -188,7 +193,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
188 193 }
189 194
190 195 function getCommonMaterialIcons() {
191   - return commonUsedMaterialIcons;
  196 + return commonMaterialIcons;
192 197 }
193 198
194 199 function genMaterialColor(str) {
... ... @@ -469,4 +474,16 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
469 474 return result;
470 475 }
471 476
  477 + function customTranslation(translationValue, defaultValue) {
  478 + var result = '';
  479 + var translationId = types.translate.customTranslationsPrefix + translationValue;
  480 + var translation = $translate.instant(translationId);
  481 + if (translation != translationId) {
  482 + result = translation + '';
  483 + } else {
  484 + result = defaultValue;
  485 + }
  486 + return result;
  487 + }
  488 +
472 489 }
... ...
... ... @@ -87,23 +87,32 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
87 87 dashboardService.getDashboardInfo(ngModelCtrl.$viewValue).then(
88 88 function success(dashboard) {
89 89 scope.dashboard = dashboard;
  90 + startWatchers();
90 91 },
91 92 function fail() {
92 93 scope.dashboard = null;
  94 + scope.updateView();
  95 + startWatchers();
93 96 }
94 97 );
95 98 } else {
96 99 scope.dashboard = null;
  100 + startWatchers();
97 101 }
98 102 }
99 103
100   - scope.$watch('dashboard', function () {
101   - scope.updateView();
102   - });
103   -
104   - scope.$watch('disabled', function () {
105   - scope.updateView();
106   - });
  104 + function startWatchers() {
  105 + scope.$watch('dashboard', function (newVal, prevVal) {
  106 + if (!angular.equals(newVal, prevVal)) {
  107 + scope.updateView();
  108 + }
  109 + });
  110 + scope.$watch('disabled', function (newVal, prevVal) {
  111 + if (!angular.equals(newVal, prevVal)) {
  112 + scope.updateView();
  113 + }
  114 + });
  115 + }
107 116
108 117 if (scope.selectFirstDashboard) {
109 118 var pageLink = {limit: 1, textSearch: ''};
... ... @@ -111,6 +120,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
111 120 var dashboards = result.data;
112 121 if (dashboards.length > 0) {
113 122 scope.dashboard = dashboards[0];
  123 + scope.updateView();
114 124 }
115 125 }, function fail() {
116 126 });
... ...
... ... @@ -187,6 +187,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
187 187 vm.showWidgetActions = showWidgetActions;
188 188 vm.widgetTitleStyle = widgetTitleStyle;
189 189 vm.widgetTitle = widgetTitle;
  190 + vm.customWidgetHeaderActions = customWidgetHeaderActions;
190 191 vm.widgetActions = widgetActions;
191 192 vm.dropWidgetShadow = dropWidgetShadow;
192 193 vm.enableWidgetFullscreen = enableWidgetFullscreen;
... ... @@ -875,6 +876,15 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, $
875 876 }
876 877 }
877 878
  879 + function customWidgetHeaderActions(widget) {
  880 + var ctx = widgetContext(widget);
  881 + if (ctx && ctx.customHeaderActions && ctx.customHeaderActions.length) {
  882 + return ctx.customHeaderActions;
  883 + } else {
  884 + return [];
  885 + }
  886 + }
  887 +
878 888 function widgetActions(widget) {
879 889 var ctx = widgetContext(widget);
880 890 if (ctx && ctx.widgetActions && ctx.widgetActions.length) {
... ...
... ... @@ -52,6 +52,16 @@
52 52 <tb-timewindow aggregation="{{vm.hasAggregation(widget)}}" ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
53 53 </div>
54 54 <div class="tb-widget-actions" layout="row" layout-align="start center" ng-show="vm.showWidgetActions(widget)" tb-mousedown="$event.stopPropagation()">
  55 + <md-button ng-repeat="action in vm.customWidgetHeaderActions(widget)"
  56 + aria-label="{{action.displayName}}"
  57 + ng-show="!vm.isEdit"
  58 + ng-click="action.onAction($event)"
  59 + class="md-icon-button">
  60 + <md-tooltip md-direction="top">
  61 + {{action.displayName}}
  62 + </md-tooltip>
  63 + <ng-md-icon size="20" icon="{{action.icon}}"></ng-md-icon>
  64 + </md-button>
55 65 <md-button ng-repeat="action in vm.widgetActions(widget)"
56 66 aria-label="{{ action.name | translate }}"
57 67 ng-show="!vm.isEdit && action.show"
... ...
... ... @@ -14,6 +14,8 @@
14 14 * limitations under the License.
15 15 */
16 16
  17 +import './material-icon-select.scss';
  18 +
17 19 import MaterialIconsDialogController from './material-icons-dialog.controller';
18 20
19 21 /* eslint-disable import/no-unresolved, import/default */
... ...
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +.tb-material-icon-select {
  18 + md-icon {
  19 + padding: 4px;
  20 + margin: 8px 4px 4px;
  21 + cursor: pointer;
  22 + border: solid 1px rgba(0,0,0,0.27);
  23 + }
  24 + md-input-container {
  25 + margin-bottom: 0px;
  26 + }
  27 +}
\ No newline at end of file
... ...
... ... @@ -15,12 +15,10 @@
15 15 limitations under the License.
16 16
17 17 -->
18   -<div layout="row">
  18 +<div class="tb-material-icon-select" layout="row">
19 19 <md-icon class="material-icons" ng-click="openIconDialog($event)">{{icon}}</md-icon>
20 20 <md-input-container flex>
21   - <md-input-container class="md-block">
22   - <label translate>icon.icon</label>
23   - <input ng-click="openIconDialog($event)" ng-model="icon">
24   - </md-input-container>
  21 + <label translate>icon.icon</label>
  22 + <input ng-mousedown="openIconDialog($event)" ng-model="icon">
25 23 </md-input-container>
26 24 </div>
\ No newline at end of file
... ...
... ... @@ -42,7 +42,8 @@ function ManageWidgetActions() {
42 42 scope: true,
43 43 bindToController: {
44 44 actionSources: '=',
45   - widgetActions: '='
  45 + widgetActions: '=',
  46 + fetchDashboardStates: '&',
46 47 },
47 48 controller: ManageWidgetActionsController,
48 49 controllerAs: 'vm',
... ... @@ -55,7 +56,7 @@ function ManageWidgetActions() {
55 56
56 57 /*@ngInject*/
57 58 function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog, $q, $filter,
58   - $translate, $timeout, types) {
  59 + $translate, $timeout, utils, types) {
59 60
60 61 let vm = this;
61 62
... ... @@ -165,12 +166,30 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
165 166 if (!isAdd) {
166 167 prevActionId = action.id;
167 168 }
  169 + var availableActionSources = {};
  170 + for (var id in vm.actionSources) {
  171 + var actionSource = vm.actionSources[id];
  172 + if (actionSource.multiple) {
  173 + availableActionSources[id] = actionSource;
  174 + } else {
  175 + if (!isAdd && action.actionSourceId == id) {
  176 + availableActionSources[id] = actionSource;
  177 + } else {
  178 + var result = $filter('filter')(vm.allActions, {actionSourceId: id});
  179 + if (!result || !result.length) {
  180 + availableActionSources[id] = actionSource;
  181 + }
  182 + }
  183 + }
  184 + }
168 185 $mdDialog.show({
169 186 controller: 'WidgetActionDialogController',
170 187 controllerAs: 'vm',
171 188 templateUrl: widgetActionDialogTemplate,
172 189 parent: angular.element($document[0].body),
173   - locals: {isAdd: isAdd, actionSources: vm.actionSources, action: angular.copy(action)},
  190 + locals: {isAdd: isAdd, fetchDashboardStates: vm.fetchDashboardStates,
  191 + actionSources: availableActionSources, widgetActions: vm.widgetActions,
  192 + action: angular.copy(action)},
174 193 skipHide: true,
175 194 fullscreen: true,
176 195 targetEvent: $event
... ... @@ -189,7 +208,8 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
189 208 }
190 209
191 210 function saveAction(action, prevActionId) {
192   - action.actionSourceName = vm.actionSources[action.actionSourceId].name;
  211 + var actionSourceName = vm.actionSources[action.actionSourceId].name;
  212 + action.actionSourceName = utils.customTranslation(actionSourceName, actionSourceName);
193 213 action.typeName = $translate.instant(types.widgetActionTypes[action.type].name);
194 214 var actionSourceId = action.actionSourceId;
195 215 var widgetAction = angular.copy(action);
... ... @@ -227,15 +247,10 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
227 247 var actionSourceActions = vm.widgetActions[actionSourceId];
228 248 for (var i=0;i<actionSourceActions.length;i++) {
229 249 var actionSourceAction = actionSourceActions[i];
230   - var action = {
231   - id: actionSourceAction.id,
232   - actionSourceId: actionSourceId,
233   - actionSourceName: actionSource.name,
234   - name: actionSourceAction.name,
235   - icon: actionSourceAction.icon,
236   - type: actionSourceAction.type,
237   - typeName: $translate.instant(types.widgetActionTypes[actionSourceAction.type].name)
238   - };
  250 + var action = angular.copy(actionSourceAction);
  251 + action.actionSourceId = actionSourceId;
  252 + action.actionSourceName = utils.customTranslation(actionSource.name, actionSource.name);
  253 + action.typeName = $translate.instant(types.widgetActionTypes[actionSourceAction.type].name);
239 254 vm.allActions.push(action);
240 255 }
241 256 }
... ...
... ... @@ -15,14 +15,21 @@
15 15 */
16 16
17 17 /*@ngInject*/
18   -export default function WidgetActionDialogController($scope, $mdDialog, types, utils, isAdd, actionSources, action) {
  18 +export default function WidgetActionDialogController($scope, $mdDialog, $filter, $q, dashboardService, dashboardUtils, types, utils,
  19 + isAdd, fetchDashboardStates, actionSources, widgetActions, action) {
19 20
20 21 var vm = this;
21 22
22 23 vm.types = types;
23 24
24 25 vm.isAdd = isAdd;
  26 + vm.fetchDashboardStates = fetchDashboardStates;
25 27 vm.actionSources = actionSources;
  28 + vm.widgetActions = widgetActions;
  29 +
  30 + vm.targetDashboardStateSearchText = '';
  31 +
  32 + vm.selectedDashboardStateIds = [];
26 33
27 34 if (vm.isAdd) {
28 35 vm.action = {
... ... @@ -32,15 +39,131 @@ export default function WidgetActionDialogController($scope, $mdDialog, types, u
32 39 vm.action = action;
33 40 }
34 41
  42 + vm.actionSourceName = actionSourceName;
  43 +
  44 + vm.targetDashboardStateSearchTextChanged = function() {
  45 + }
  46 +
  47 + vm.dashboardStateSearch = dashboardStateSearch;
35 48 vm.cancel = cancel;
36 49 vm.save = save;
37 50
  51 + $scope.$watch("vm.action.name", function(newVal, prevVal) {
  52 + if (!angular.equals(newVal, prevVal) && vm.action.name != null) {
  53 + checkActionName();
  54 + }
  55 + });
  56 +
  57 + $scope.$watch("vm.action.actionSourceId", function(newVal, prevVal) {
  58 + if (!angular.equals(newVal, prevVal) && vm.action.actionSourceId != null) {
  59 + checkActionName();
  60 + }
  61 + });
  62 +
  63 + $scope.$watch("vm.action.targetDashboardId", function() {
  64 + vm.selectedDashboardStateIds = [];
  65 + if (vm.action.targetDashboardId) {
  66 + dashboardService.getDashboard(vm.action.targetDashboardId).then(
  67 + function success(dashboard) {
  68 + dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
  69 + var states = dashboard.configuration.states;
  70 + vm.selectedDashboardStateIds = Object.keys(states);
  71 + }
  72 + );
  73 + }
  74 + });
  75 +
  76 + $scope.$watch('vm.action.type', function(newType) {
  77 + if (newType) {
  78 + switch (newType) {
  79 + case vm.types.widgetActionTypes.openDashboardState.value:
  80 + case vm.types.widgetActionTypes.updateDashboardState.value:
  81 + case vm.types.widgetActionTypes.openDashboard.value:
  82 + if (angular.isUndefined(vm.action.setEntityId)) {
  83 + vm.action.setEntityId = true;
  84 + }
  85 + break;
  86 + }
  87 + }
  88 + });
  89 +
  90 + function checkActionName() {
  91 + var actionNameIsUnique = true;
  92 + if (vm.action.actionSourceId && vm.action.name) {
  93 + var sourceActions = vm.widgetActions[vm.action.actionSourceId];
  94 + if (sourceActions) {
  95 + var result = $filter('filter')(sourceActions, {name: vm.action.name}, true);
  96 + if (result && result.length && result[0].id !== vm.action.id) {
  97 + actionNameIsUnique = false;
  98 + }
  99 + }
  100 + }
  101 + $scope.theForm.name.$setValidity('actionNameNotUnique', actionNameIsUnique);
  102 + }
  103 +
  104 + function actionSourceName (actionSource) {
  105 + if (actionSource) {
  106 + return utils.customTranslation(actionSource.name, actionSource.name);
  107 + } else {
  108 + return '';
  109 + }
  110 + }
  111 +
  112 + function dashboardStateSearch (query) {
  113 + if (vm.action.type == vm.types.widgetActionTypes.openDashboard.value) {
  114 + var deferred = $q.defer();
  115 + var result = query ? vm.selectedDashboardStateIds.filter(
  116 + createFilterForDashboardState(query)) : vm.selectedDashboardStateIds;
  117 + if (result && result.length) {
  118 + deferred.resolve(result);
  119 + } else {
  120 + deferred.resolve([query]);
  121 + }
  122 + return deferred.promise;
  123 + } else {
  124 + return vm.fetchDashboardStates({query: query});
  125 + }
  126 + }
  127 +
  128 + function createFilterForDashboardState (query) {
  129 + var lowercaseQuery = angular.lowercase(query);
  130 + return function filterFn(stateId) {
  131 + return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0);
  132 + };
  133 + }
  134 +
  135 + function cleanupAction(action) {
  136 + var result = {};
  137 + result.id = action.id;
  138 + result.actionSourceId = action.actionSourceId;
  139 + result.name = action.name;
  140 + result.icon = action.icon;
  141 + result.type = action.type;
  142 + switch (action.type) {
  143 + case vm.types.widgetActionTypes.openDashboardState.value:
  144 + case vm.types.widgetActionTypes.updateDashboardState.value:
  145 + result.targetDashboardStateId = action.targetDashboardStateId;
  146 + result.openRightLayout = action.openRightLayout;
  147 + result.setEntityId = action.setEntityId;
  148 + break;
  149 + case vm.types.widgetActionTypes.openDashboard.value:
  150 + result.targetDashboardId = action.targetDashboardId;
  151 + result.targetDashboardStateId = action.targetDashboardStateId;
  152 + result.setEntityId = action.setEntityId;
  153 + break;
  154 + case vm.types.widgetActionTypes.custom.value:
  155 + result.customFunction = action.customFunction;
  156 + break;
  157 + }
  158 + return result;
  159 + }
  160 +
38 161 function cancel() {
39 162 $mdDialog.cancel();
40 163 }
41 164
42 165 function save() {
43 166 $scope.theForm.$setPristine();
44   - $mdDialog.hide(vm.action);
  167 + $mdDialog.hide(cleanupAction(vm.action));
45 168 }
46 169 }
... ...
... ... @@ -31,12 +31,12 @@
31 31 <md-dialog-content>
32 32 <div class="md-dialog-content">
33 33 <md-content class="md-padding" layout="column">
34   - <fieldset ng-disabled="loading">
  34 + <fieldset ng-disabled="loading" layout="column">
35 35 <md-input-container class="md-block">
36 36 <label translate>widget-config.action-source</label>
37 37 <md-select name="actionSource" required aria-label="{{ 'widget-config.action-source' | translate }}" ng-model="vm.action.actionSourceId">
38 38 <md-option ng-repeat="(actionSourceId, actionSource) in vm.actionSources" ng-value="actionSourceId">
39   - {{actionSource.name}}
  39 + {{vm.actionSourceName(actionSource)}}
40 40 </md-option>
41 41 </md-select>
42 42 <div ng-messages="theForm.actionSource.$error">
... ... @@ -48,6 +48,7 @@
48 48 <input name="name" required ng-model="vm.action.name">
49 49 <div ng-messages="theForm.name.$error">
50 50 <div ng-message="required" translate>widget-config.action-name-required</div>
  51 + <div ng-message="actionNameNotUnique" translate>widget-config.action-name-not-unique</div>
51 52 </div>
52 53 </md-input-container>
53 54 <tb-material-icon-select ng-model="vm.action.icon">
... ... @@ -63,6 +64,57 @@
63 64 <div ng-message="required" translate>widget-config.action-type-required</div>
64 65 </div>
65 66 </md-input-container>
  67 + <div layout="column"
  68 + style="padding-bottom: 20px;"
  69 + ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboard.value">
  70 + <div class="md-caption tb-required"
  71 + style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>widget-action.target-dashboard</div>
  72 + <tb-dashboard-autocomplete the-form="theForm"
  73 + tb-required="true"
  74 + ng-model="vm.action.targetDashboardId"
  75 + select-first-dashboard="false">
  76 + </tb-dashboard-autocomplete>
  77 + </div>
  78 + <md-autocomplete ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
  79 + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
  80 + vm.action.type == vm.types.widgetActionTypes.openDashboard.value"
  81 + ng-required="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value"
  82 + md-no-cache="true"
  83 + md-input-name="targetDashboardState"
  84 + ng-model="vm.action.targetDashboardStateId"
  85 + md-selected-item="vm.action.targetDashboardStateId"
  86 + md-search-text="vm.targetDashboardStateSearchText"
  87 + md-search-text-change="vm.targetDashboardStateSearchTextChanged()"
  88 + md-items="item in vm.dashboardStateSearch(vm.targetDashboardStateSearchText)"
  89 + md-item-text="item"
  90 + md-min-length="0"
  91 + md-floating-label="{{ 'widget-action.target-dashboard-state' | translate }}"
  92 + md-select-on-match="true">
  93 + <md-item-template>
  94 + <div>
  95 + <span md-highlight-text="vm.targetDashboardStateSearchText" md-highlight-flags="^i">{{item}}</span>
  96 + </div>
  97 + </md-item-template>
  98 + <div ng-messages="theForm.targetDashboardState.$error">
  99 + <div translate ng-message="required">widget-action.target-dashboard-state-required</div>
  100 + </div>
  101 + </md-autocomplete>
  102 + <md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
  103 + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value"
  104 + flex aria-label="{{ 'widget-action.open-right-layout' | translate }}"
  105 + ng-model="vm.action.openRightLayout">{{ 'widget-action.open-right-layout' | translate }}
  106 + </md-checkbox>
  107 + <md-checkbox ng-if="vm.action.type == vm.types.widgetActionTypes.openDashboardState.value ||
  108 + vm.action.type == vm.types.widgetActionTypes.updateDashboardState.value ||
  109 + vm.action.type == vm.types.widgetActionTypes.openDashboard.value"
  110 + flex aria-label="{{ 'widget-action.set-entity-from-widget' | translate }}"
  111 + ng-model="vm.action.setEntityId">{{ 'widget-action.set-entity-from-widget' | translate }}
  112 + </md-checkbox>
  113 + <tb-js-func ng-if="vm.action.type == vm.types.widgetActionTypes.custom.value"
  114 + ng-model="vm.action.customFunction"
  115 + function-args="{{ ['$event', 'widgetContext', 'entityId'] }}"
  116 + validation-args="{{ [] }}">
  117 + </tb-js-func>
66 118 </fieldset>
67 119 </md-content>
68 120 </div>
... ...
... ... @@ -468,6 +468,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
468 468 aliasController: '=',
469 469 functionsOnly: '=',
470 470 fetchEntityKeys: '&',
  471 + fetchDashboardStates: '&',
471 472 onCreateEntityAlias: '&',
472 473 theForm: '='
473 474 },
... ...
... ... @@ -277,7 +277,10 @@
277 277 </md-tab>
278 278 <md-tab label="{{ 'widget-config.actions' | translate }}">
279 279 <md-content class="md-padding" layout="column">
280   - <tb-manage-widget-actions action-sources="actionSources" widget-actions="actions">
  280 + <tb-manage-widget-actions
  281 + action-sources="actionSources"
  282 + widget-actions="actions"
  283 + fetch-dashboard-states="fetchDashboardStates({query: query})">
281 284 </tb-manage-widget-actions>
282 285 </md-content>
283 286 </md-tab>
... ...
... ... @@ -20,7 +20,7 @@ import Subscription from '../../api/subscription';
20 20 /* eslint-disable angular/angularelement */
21 21
22 22 /*@ngInject*/
23   -export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
  23 +export default function WidgetController($scope, $state, $timeout, $window, $element, $q, $log, $injector, $filter, $compile, tbRaf, types, utils, timeService,
24 24 datasourceService, alarmService, entityService, deviceService, visibleRect, isEdit, isMobile, stDiff, dashboardTimewindow,
25 25 dashboardTimewindowApi, widget, aliasController, stateController, widgetInfo, widgetType) {
26 26
... ... @@ -44,6 +44,20 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
44 44
45 45 var cafs = {};
46 46
  47 + var actionDescriptorsBySourceId = {};
  48 + if (widget.config.actions) {
  49 + for (var actionSourceId in widget.config.actions) {
  50 + var descriptors = widget.config.actions[actionSourceId];
  51 + var actionDescriptors = [];
  52 + descriptors.forEach(function(descriptor) {
  53 + var actionDescriptor = angular.copy(descriptor);
  54 + actionDescriptor.displayName = utils.customTranslation(descriptor.name, descriptor.name);
  55 + actionDescriptors.push(actionDescriptor);
  56 + });
  57 + actionDescriptorsBySourceId[actionSourceId] = actionDescriptors;
  58 + }
  59 + }
  60 +
47 61 var widgetContext = {
48 62 inited: false,
49 63 $container: null,
... ... @@ -103,9 +117,32 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
103 117 utils: {
104 118 formatValue: formatValue
105 119 },
  120 + actionsApi: {
  121 + actionDescriptorsBySourceId: actionDescriptorsBySourceId,
  122 + getActionDescriptors: getActionDescriptors,
  123 + handleWidgetAction: handleWidgetAction
  124 + },
106 125 stateController: stateController
107 126 };
108 127
  128 + widgetContext.customHeaderActions = [];
  129 + var headerActionsDescriptors = getActionDescriptors(types.widgetActionSources.headerButton.value);
  130 + for (var i=0;i<headerActionsDescriptors.length;i++) {
  131 + var descriptor = headerActionsDescriptors[i];
  132 + var headerAction = {};
  133 + headerAction.name = descriptor.name;
  134 + headerAction.displayName = descriptor.displayName;
  135 + headerAction.icon = descriptor.icon;
  136 + headerAction.descriptor = descriptor;
  137 + headerAction.onAction = function($event) {
  138 + var entityInfo = getFirstEntityInfo();
  139 + var entityId = entityInfo ? entityInfo.entityId : null;
  140 + var entityName = entityInfo ? entityInfo.entityName : null;
  141 + handleWidgetAction($event, this.descriptor, entityId, entityName);
  142 + }
  143 + widgetContext.customHeaderActions.push(headerAction);
  144 + }
  145 +
109 146 var subscriptionContext = {
110 147 $scope: $scope,
111 148 $q: $q,
... ... @@ -376,6 +413,87 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
376 413 return deferred.promise;
377 414 }
378 415
  416 + function getActionDescriptors(actionSourceId) {
  417 + var result = widgetContext.actionsApi.actionDescriptorsBySourceId[actionSourceId];
  418 + if (!result) {
  419 + result = [];
  420 + }
  421 + return result;
  422 + }
  423 +
  424 + function handleWidgetAction($event, descriptor, entityId, entityName) {
  425 + var type = descriptor.type;
  426 + switch (type) {
  427 + case types.widgetActionTypes.openDashboardState.value:
  428 + case types.widgetActionTypes.updateDashboardState.value:
  429 + var targetDashboardStateId = descriptor.targetDashboardStateId;
  430 + var targetEntityId;
  431 + if (descriptor.setEntityId) {
  432 + targetEntityId = entityId;
  433 + }
  434 + var params = {};
  435 + if (targetEntityId) {
  436 + params.entityId = targetEntityId;
  437 + if (entityName) {
  438 + params.entityName = entityName;
  439 + }
  440 + }
  441 + if (type == types.widgetActionTypes.openDashboardState.value) {
  442 + widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout);
  443 + } else {
  444 + widgetContext.stateController.updateState(targetDashboardStateId, params, descriptor.openRightLayout);
  445 + }
  446 + break;
  447 + case types.widgetActionTypes.openDashboard.value:
  448 + var targetDashboardId = descriptor.targetDashboardId;
  449 + targetDashboardStateId = descriptor.targetDashboardStateId;
  450 + targetEntityId;
  451 + if (descriptor.setEntityId) {
  452 + targetEntityId = entityId;
  453 + }
  454 + var stateObject = {};
  455 + stateObject.params = {};
  456 + if (targetEntityId) {
  457 + stateObject.params.entityId = targetEntityId;
  458 + if (entityName) {
  459 + stateObject.params.entityName = entityName;
  460 + }
  461 + }
  462 + if (targetDashboardStateId) {
  463 + stateObject.id = targetDashboardStateId;
  464 + }
  465 + var stateParams = {
  466 + dashboardId: targetDashboardId,
  467 + state: angular.toJson([ stateObject ])
  468 + }
  469 + $state.go('home.dashboards.dashboard', stateParams);
  470 + break;
  471 + case types.widgetActionTypes.custom.value:
  472 + var customFunction = descriptor.customFunction;
  473 + if (angular.isDefined(customFunction) && customFunction.length > 0) {
  474 + try {
  475 + var customActionFunction = new Function('$event', 'widgetContext', 'entityId', 'entityName', customFunction);
  476 + customActionFunction($event, widgetContext, entityId, entityName);
  477 + } catch (e) {
  478 + //
  479 + }
  480 + }
  481 + break;
  482 + }
  483 + }
  484 +
  485 + function getFirstEntityInfo() {
  486 + var entityInfo;
  487 + for (var id in widgetContext.subscriptions) {
  488 + var subscription = widgetContext.subscriptions[id];
  489 + entityInfo = subscription.getFirstEntityInfo();
  490 + if (entityInfo) {
  491 + break;
  492 + }
  493 + }
  494 + return entityInfo;
  495 + }
  496 +
379 497 function configureWidgetElement() {
380 498
381 499 $scope.displayLegend = angular.isDefined(widget.config.showLegend) ?
... ...
... ... @@ -36,6 +36,7 @@ export default function AddWidgetController($scope, widgetService, entityService
36 36 vm.add = add;
37 37 vm.cancel = cancel;
38 38 vm.fetchEntityKeys = fetchEntityKeys;
  39 + vm.fetchDashboardStates = fetchDashboardStates;
39 40 vm.createEntityAlias = createEntityAlias;
40 41
41 42 vm.widgetConfig = {
... ... @@ -128,6 +129,26 @@ export default function AddWidgetController($scope, widgetService, entityService
128 129 return deferred.promise;
129 130 }
130 131
  132 + function fetchDashboardStates (query) {
  133 + var deferred = $q.defer();
  134 + var stateIds = Object.keys(vm.dashboard.configuration.states);
  135 + var result = query ? stateIds.filter(
  136 + createFilterForDashboardState(query)) : stateIds;
  137 + if (result && result.length) {
  138 + deferred.resolve(result);
  139 + } else {
  140 + deferred.resolve([query]);
  141 + }
  142 + return deferred.promise;
  143 + }
  144 +
  145 + function createFilterForDashboardState (query) {
  146 + var lowercaseQuery = angular.lowercase(query);
  147 + return function filterFn(stateId) {
  148 + return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0);
  149 + };
  150 + }
  151 +
131 152 function createEntityAlias (event, alias, allowedEntityTypes) {
132 153
133 154 var deferred = $q.defer();
... ...
... ... @@ -42,6 +42,7 @@
42 42 alias-controller="vm.aliasController"
43 43 functions-only="vm.functionsOnly"
44 44 fetch-entity-keys="vm.fetchEntityKeys(entityAliasId, query, type)"
  45 + fetch-dashboard-states="vm.fetchDashboardStates(query)"
45 46 on-create-entity-alias="vm.createEntityAlias(event, alias, allowedEntityTypes)"
46 47 the-form="theForm"></tb-widget-config>
47 48 </fieldset>
... ...
... ... @@ -94,6 +94,26 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
94 94 return deferred.promise;
95 95 };
96 96
  97 + scope.fetchDashboardStates = function(query) {
  98 + var deferred = $q.defer();
  99 + var stateIds = Object.keys(scope.dashboard.configuration.states);
  100 + var result = query ? stateIds.filter(
  101 + createFilterForDashboardState(query)) : stateIds;
  102 + if (result && result.length) {
  103 + deferred.resolve(result);
  104 + } else {
  105 + deferred.resolve([query]);
  106 + }
  107 + return deferred.promise;
  108 + }
  109 +
  110 + function createFilterForDashboardState (query) {
  111 + var lowercaseQuery = angular.lowercase(query);
  112 + return function filterFn(stateId) {
  113 + return (angular.lowercase(stateId).indexOf(lowercaseQuery) === 0);
  114 + };
  115 + }
  116 +
97 117 scope.createEntityAlias = function (event, alias, allowedEntityTypes) {
98 118
99 119 var deferred = $q.defer();
... ...
... ... @@ -26,6 +26,7 @@
26 26 alias-controller="aliasController"
27 27 functions-only="widgetEditMode"
28 28 fetch-entity-keys="fetchEntityKeys(entityAliasId, query, type)"
  29 + fetch-dashboard-states="fetchDashboardStates(query)"
29 30 on-create-entity-alias="createEntityAlias(event, alias, allowedEntityTypes)"
30 31 the-form="theForm"></tb-widget-config>
31 32 </fieldset>
... ...
... ... @@ -15,7 +15,7 @@
15 15 */
16 16
17 17 /*@ngInject*/
18   -export default function DefaultStateController($scope, $location, $state, $stateParams, $translate, types, dashboardUtils) {
  18 +export default function DefaultStateController($scope, $location, $state, $stateParams, utils, types, dashboardUtils) {
19 19
20 20 var vm = this;
21 21
... ... @@ -50,6 +50,9 @@ export default function DefaultStateController($scope, $location, $state, $state
50 50 }
51 51
52 52 function updateState(id, params, openRightLayout) {
  53 + if (!id) {
  54 + id = getStateId();
  55 + }
53 56 if (vm.states && vm.states[id]) {
54 57 if (!params) {
55 58 params = {};
... ... @@ -110,15 +113,7 @@ export default function DefaultStateController($scope, $location, $state, $state
110 113 }
111 114
112 115 function getStateName(id, state) {
113   - var result = '';
114   - var translationId = types.translate.customTranslationsPrefix + state.name;
115   - var translation = $translate.instant(translationId);
116   - if (translation != translationId) {
117   - result = translation + '';
118   - } else {
119   - result = id;
120   - }
121   - return result;
  116 + return utils.customTranslation(state.name, id);
122 117 }
123 118
124 119 function parseState(stateJson) {
... ...
... ... @@ -55,6 +55,9 @@ export default function EntityStateController($scope, $location, $state, $stateP
55 55 }
56 56
57 57 function updateState(id, params, openRightLayout) {
  58 + if (!id) {
  59 + id = getStateId();
  60 + }
58 61 if (vm.states && vm.states[id]) {
59 62 resolveEntity(params).then(
60 63 function success(entityName) {
... ... @@ -121,17 +124,10 @@ export default function EntityStateController($scope, $location, $state, $stateP
121 124 var result = '';
122 125 if (vm.stateObject[index]) {
123 126 var stateName = vm.states[vm.stateObject[index].id].name;
124   - var translationId = types.translate.customTranslationsPrefix + stateName;
125   - var translation = $translate.instant(translationId);
126   - if (translation != translationId) {
127   - stateName = translation + '';
128   - }
  127 + stateName = utils.customTranslation(stateName, stateName);
129 128 var params = vm.stateObject[index].params;
130   - if (params && params.entityName) {
131   - result = utils.insertVariable(stateName, 'entityName', params.entityName);
132   - } else {
133   - result = stateName;
134   - }
  129 + var entityName = params && params.entityName ? params.entityName : '';
  130 + result = utils.insertVariable(stateName, 'entityName', entityName);
135 131 }
136 132 return result;
137 133 }
... ...
... ... @@ -102,6 +102,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
102 102 if (vm.addToDashboardType === 0) {
103 103 dashboardService.getDashboard(vm.dashboardId).then(
104 104 function success(dashboard) {
  105 + dashboard = dashboardUtils.validateAndUpdateDashboard(dashboard);
105 106 selectTargetState($event, dashboard).then(
106 107 function(targetState) {
107 108 selectTargetLayout($event, dashboard, targetState).then(
... ...
... ... @@ -1104,11 +1104,16 @@ export default angular.module('thingsboard.locale', [])
1104 1104 "export": "Export widget"
1105 1105 },
1106 1106 "widget-action": {
1107   - "header-button": "Header button",
  1107 + "header-button": "Widget header button",
1108 1108 "open-dashboard-state": "Navigate to new dashboard state",
1109 1109 "update-dashboard-state": "Update current dashboard state",
1110 1110 "open-dashboard": "Navigate to other dashboard",
1111   - "custom": "Custom action"
  1111 + "custom": "Custom action",
  1112 + "target-dashboard-state": "Target dashboard state",
  1113 + "target-dashboard-state-required": "Target dashboard state is required",
  1114 + "set-entity-from-widget": "Set entity from widget",
  1115 + "target-dashboard": "Target dashboard",
  1116 + "open-right-layout": "Open right dashboard layout (mobile view)"
1112 1117 },
1113 1118 "widgets-bundle": {
1114 1119 "current": "Current bundle",
... ... @@ -1174,6 +1179,7 @@ export default angular.module('thingsboard.locale', [])
1174 1179 "action-source-required": "Action source is required.",
1175 1180 "action-name": "Name",
1176 1181 "action-name-required": "Action name is required.",
  1182 + "action-name-not-unique": "Another action with the same name already exists.<br/>Action name should be unique within the same action source.",
1177 1183 "action-icon": "Icon",
1178 1184 "action-type": "Type",
1179 1185 "action-type-required": "Action type is required.",
... ... @@ -1205,6 +1211,10 @@ export default angular.module('thingsboard.locale', [])
1205 1211 "es_ES": "Spanish"
1206 1212 },
1207 1213 "custom": {
  1214 + "widget-action": {
  1215 + "action-cell-button": "Action cell button",
  1216 + "row-click": "On row click"
  1217 + }
1208 1218 }
1209 1219 }
1210 1220 }
... ...
... ... @@ -158,13 +158,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
158 158 vm.ctx.widgetActions = [ vm.searchAction ];
159 159
160 160 if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
161   - var translationId = types.translate.customTranslationsPrefix + vm.settings.alarmsTitle;
162   - var translation = $translate.instant(translationId);
163   - if (translation != translationId) {
164   - vm.alarmsTitle = translation + '';
165   - } else {
166   - vm.alarmsTitle = vm.settings.alarmsTitle;
167   - }
  161 + vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
168 162 } else {
169 163 vm.alarmsTitle = $translate.instant('alarm.alarms');
170 164 }
... ... @@ -226,6 +220,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
226 220 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
227 221 'border-color: ' + mdDarkSecondary + ';\n'+
228 222 '}\n'+
  223 + 'table.md-table td.md-cell.tb-action-cell button.md-icon-button md-icon {\n'+
  224 + 'color: ' + mdDarkSecondary + ';\n'+
  225 + '}\n'+
229 226 'table.md-table td.md-cell.md-placeholder {\n'+
230 227 'color: ' + mdDarkDisabled + ';\n'+
231 228 '}\n'+
... ... @@ -539,13 +536,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
539 536 for (var d = 0; d < vm.alarmSource.dataKeys.length; d++ ) {
540 537 var dataKey = vm.alarmSource.dataKeys[d];
541 538
542   - var translationId = types.translate.customTranslationsPrefix + dataKey.label;
543   - var translation = $translate.instant(translationId);
544   - if (translation != translationId) {
545   - dataKey.title = translation + '';
546   - } else {
547   - dataKey.title = dataKey.label;
548   - }
  539 + dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
549 540
550 541 var keySettings = dataKey.settings;
551 542
... ...
... ... @@ -65,8 +65,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
65 65 vm.currentEntity = null;
66 66
67 67 vm.displayEntityName = true;
  68 + vm.entityNameColumnTitle = '';
68 69 vm.displayEntityType = true;
69   - vm.displayActions = false; //TODO: Widget actions
  70 + vm.actionCellDescriptors = [];
70 71 vm.displayPagination = true;
71 72 vm.defaultPageSize = 10;
72 73 vm.defaultSortOrder = 'entityName';
... ... @@ -92,6 +93,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
92 93 vm.onReorder = onReorder;
93 94 vm.onPaginate = onPaginate;
94 95 vm.onRowClick = onRowClick;
  96 + vm.onActionButtonClick = onActionButtonClick;
95 97 vm.isCurrent = isCurrent;
96 98
97 99 vm.cellStyle = cellStyle;
... ... @@ -141,14 +143,10 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
141 143
142 144 vm.ctx.widgetActions = [ vm.searchAction ];
143 145
  146 + vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
  147 +
144 148 if (vm.settings.entitiesTitle && vm.settings.entitiesTitle.length) {
145   - var translationId = types.translate.customTranslationsPrefix + vm.settings.entitiesTitle;
146   - var translation = $translate.instant(translationId);
147   - if (translation != translationId) {
148   - vm.entitiesTitle = translation + '';
149   - } else {
150   - vm.entitiesTitle = vm.settings.entitiesTitle;
151   - }
  149 + vm.entitiesTitle = utils.customTranslation(vm.settings.entitiesTitle, vm.settings.entitiesTitle);
152 150 } else {
153 151 vm.entitiesTitle = $translate.instant('entity.entities');
154 152 }
... ... @@ -157,6 +155,13 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
157 155
158 156 vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
159 157 vm.displayEntityName = angular.isDefined(vm.settings.displayEntityName) ? vm.settings.displayEntityName : true;
  158 +
  159 + if (vm.settings.entityNameColumnTitle && vm.settings.entityNameColumnTitle.length) {
  160 + vm.entityNameColumnTitle = utils.customTranslation(vm.settings.entityNameColumnTitle, vm.settings.entityNameColumnTitle);
  161 + } else {
  162 + vm.entityNameColumnTitle = $translate.instant('entity.entity-name');
  163 + }
  164 +
160 165 vm.displayEntityType = angular.isDefined(vm.settings.displayEntityType) ? vm.settings.displayEntityType : true;
161 166 vm.displayPagination = angular.isDefined(vm.settings.displayPagination) ? vm.settings.displayPagination : true;
162 167
... ... @@ -185,6 +190,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
185 190 //var mdDarkIcon = mdDarkSecondary;
186 191 var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString();
187 192
  193 + //md-icon.md-default-theme, md-icon {
  194 +
188 195 var cssString = 'table.md-table th.md-column {\n'+
189 196 'color: ' + mdDarkSecondary + ';\n'+
190 197 '}\n'+
... ... @@ -204,6 +211,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
204 211 'table.md-table td.md-cell.md-checkbox-cell md-checkbox:not(.md-checked) .md-icon {\n'+
205 212 'border-color: ' + mdDarkSecondary + ';\n'+
206 213 '}\n'+
  214 + 'table.md-table td.md-cell.tb-action-cell button.md-icon-button md-icon {\n'+
  215 + 'color: ' + mdDarkSecondary + ';\n'+
  216 + '}\n'+
207 217 'table.md-table td.md-cell.md-placeholder {\n'+
208 218 'color: ' + mdDarkDisabled + ';\n'+
209 219 '}\n'+
... ... @@ -261,11 +271,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
261 271 }
262 272
263 273 function onRowClick($event, entity) {
  274 + if ($event) {
  275 + $event.stopPropagation();
  276 + }
264 277 if (vm.currentEntity != entity) {
265 278 vm.currentEntity = entity;
  279 + var descriptors = vm.ctx.actionsApi.getActionDescriptors('rowClick');
  280 + if (descriptors.length) {
  281 + var entityId;
  282 + var entityName;
  283 + if (vm.currentEntity) {
  284 + entityId = vm.currentEntity.id;
  285 + entityName = vm.currentEntity.entityName;
  286 + }
  287 + vm.ctx.actionsApi.handleWidgetAction($event, descriptors[0], entityId, entityName);
  288 + }
266 289 }
267 290 }
268 291
  292 + function onActionButtonClick($event, entity, actionDescriptor) {
  293 + if ($event) {
  294 + $event.stopPropagation();
  295 + }
  296 + var entityId;
  297 + var entityName;
  298 + if (entity) {
  299 + entityId = entity.id;
  300 + entityName = entity.entityName;
  301 + }
  302 + vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName);
  303 + }
  304 +
269 305 function isCurrent(entity) {
270 306 return (vm.currentEntity && entity && vm.currentEntity.id && entity.id) &&
271 307 (vm.currentEntity.id.id === entity.id.id);
... ... @@ -393,13 +429,7 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
393 429 }
394 430 vm.dataKeys.push(dataKey);
395 431
396   - var translationId = types.translate.customTranslationsPrefix + dataKey.label;
397   - var translation = $translate.instant(translationId);
398   - if (translation != translationId) {
399   - dataKey.title = translation + '';
400   - } else {
401   - dataKey.title = dataKey.label;
402   - }
  432 + dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
403 433
404 434 var keySettings = dataKey.settings;
405 435
... ...
... ... @@ -41,10 +41,10 @@
41 41 <table md-table>
42 42 <thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
43 43 <tr md-row>
44   - <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span translate>entity.entity-name</span></th>
  44 + <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th>
45 45 <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
46 46 <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
47   - <th md-column ng-if="vm.displayActions"><span>&nbsp</span></th>
  47 + <th md-column ng-if="vm.actionCellDescriptors.length"><span>&nbsp</span></th>
48 48 </tr>
49 49 </thead>
50 50 <tbody md-body>
... ... @@ -57,14 +57,18 @@
57 57 ng-style="vm.cellStyle(entity, key)"
58 58 ng-bind-html="vm.cellContent(entity, key)">
59 59 </td>
60   - <td md-cell ng-if="vm.displayActions" class="tb-action-cell">
61   - <!--md-button class="md-icon-button" aria-label="{{ 'entity.details' | translate }}"
62   - ng-click="vm.openEntityDetails($event, entity)">
63   - <md-icon aria-label="{{ 'entity.details' | translate }}" class="material-icons">more_horiz</md-icon>
  60 + <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
  61 + ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
  62 + maxWidth: vm.actionCellDescriptors.length*36+'px',
  63 + width: vm.actionCellDescriptors.length*36+'px'}">
  64 + <md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
  65 + aria-label="{{ actionDescriptor.displayName }}"
  66 + ng-click="vm.onActionButtonClick($event, entity, actionDescriptor)">
  67 + <md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
64 68 <md-tooltip md-direction="top">
65   - {{ 'entity.details' | translate }}
  69 + {{ actionDescriptor.displayName }}
66 70 </md-tooltip>
67   - </md-button-->
  71 + </md-button>
68 72 </td>
69 73 </tr>
70 74 </tbody>
... ...