콘텐츠로 이동

위젯 동작

위젯 동작을 쓰면 사용자가 버튼, 행, 위젯 제목을 클릭하는 등의 상호작용으로 위젯에서 미리 정의한 작업을 실행할 수 있습니다.

동작은 위젯마다 설정하며, 액션 소스라는 특정 상호작용으로 실행됩니다. 쓸 수 있는 소스는 위젯 유형에 따라 다르지만, 동작 유형은 모든 위젯에서 같습니다.

동작으로 대시보드 사이를 이동하거나, HMI 장치와 상호작용하거나, 엔티티 데이터를 갱신하거나, 사용자 지정 로직을 실행할 수 있습니다.


MX On Cloud 위젯에는 편집 모드가 두 가지 있으며, 동작을 추가하는 단계가 조금 다릅니다.

  • 기본 — 위젯의 연필 아이콘을 클릭하면 열리는 간단한 설정 패널입니다. 동작은 패널 안의 액션 영역에 있으며, 모달 목록으로 관리합니다.
  • 고급 — 기본 패널에서 여는 전체 화면 탭 편집기입니다. 동작은 별도의 액션 탭에 평면 목록과 + 버튼으로 있고, 중간 모달이 없습니다.

두 모드 모두 같은 액션 추가 대화상자에서 소스, 이름, 아이콘, 유형을 설정합니다.

기본 위젯 설정

  1. 위젯의 연필 아이콘을 클릭해 설정 패널을 엽니다.
  2. 액션 영역까지 스크롤하고 액션 추가를 클릭해 액션 목록을 엽니다. 첫 동작을 저장하면 이 버튼이 + 아이콘으로 바뀝니다.
  3. 액션 목록 오른쪽 위의 + 버튼을 클릭해 액션 추가 대화상자를 엽니다. 액션 소스, 이름, 아이콘, 유형을 설정하고 추가를 클릭합니다.
  4. 목록에서 동작을 확인하고 저장을 클릭해 목록을 닫습니다.
  5. 적용을 클릭해 위젯 설정을 저장한 다음 대시보드 툴바의 저장을 클릭합니다.

고급 위젯 설정

  1. 위젯의 연필 아이콘을 클릭해 설정 패널을 연 다음 고급 모드로 바꿔 전체 위젯 편집기를 엽니다.
  2. 액션 탭으로 가서 + 버튼을 클릭해 액션 추가 대화상자를 엽니다.
  3. 액션 소스, 이름, 아이콘, 유형을 설정하고 추가를 클릭합니다.
  4. 액션 탭에 동작이 나타납니다. 적용을 클릭해 위젯 설정을 저장한 다음 대시보드 툴바의 저장을 클릭합니다.

동작 유형설명
새로운 대시보드 상태 탐색현재 대시보드 안의 지정한 상태로 이동합니다.
현재 대시보드 상태 업데이트이동하지 않고 현재 대시보드의 엔티티 컨텍스트를 갱신합니다.
다른 대시보드 탐색다른 대시보드로 이동합니다. 그 안의 특정 상태로 갈 수도 있습니다.
Custom action사용자 지정 JavaScript 함수를 실행합니다(예: HMI 장치나 자산 삭제).
Custom action (with HTML template)HTML 템플릿 안에서 그려지는 사용자 지정 JavaScript 함수를 실행합니다(예: 만들기·편집 대화상자).
URL 열기URL을 엽니다. 새 브라우저 탭에서 열 수도 있습니다.
맵 항목 배치새 엔티티를 만들어 지도에 바로 놓습니다. 지도 위젯에서만 쓸 수 있습니다.

선택한 대시보드 상태로 이동합니다. 기본적으로 그 상태가 현재 화면을 대신하지만, 오버레이 대화상자나 실행한 요소에 붙은 팝오버로 열 수도 있습니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스를 선택합니다(예: 동작 셀 버튼).
  3. 액션 이름을 입력하고 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

별도 대화상자나 팝오버로 열기

현재 화면을 대신하지 않고 대상 상태를 오버레이로 열 수 있습니다. 동작 설정의 대시보드 상태 표시 옵션 필드에서 세 가지 모드 중 하나를 고릅니다.

  • 일반 — 상태로 이동해 현재 화면을 대신합니다(기본값).
  • 별도 다이얼로그로 열기 — 상태를 모달 대화상자 오버레이로 엽니다. 제목, 툴바 표시 여부, 대화상자 크기를 설정할 수 있습니다.
  • 팝오버로 열기 — 실행한 요소 근처에 붙은 팝업으로 상태를 엽니다. 위치, 너비, 높이를 정할 수 있습니다.
  1. 동작 설정에서 대시보드 상태 표시 옵션 드롭다운을 열고 별도 다이얼로그로 열기를 선택합니다.
  2. 대화상자 안에 시간 창, 내보내기, 전체 화면 컨트롤을 보이려면 다이얼로그에서 대시보드 툴바 숨기기를 끕니다.
  3. 필요하면 대화상자 너비와 높이(뷰포트 대비 백분율)를 정합니다.
  4. 모든 변경 사항을 저장합니다.

다른 곳으로 이동하지 않고 현재 대시보드 상태의 엔티티 컨텍스트를 갱신합니다. 선택한 엔티티의 상세 정보를 같은 대시보드의 차트나 지도 위젯에 보여 줄 때 흔히 씁니다.

이 예에서는 Timeseries Line Chart 위젯을 추가하고, 데이터 소스가 “대시보드 상태로 부터의 개체” 별칭을 쓰도록 설정해 차트가 현재 선택한 엔티티를 반영하게 합니다.

이제 Entities table 위젯에 현재 대시보드 상태 업데이트 동작을 추가합니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 동작 셀 버튼을 선택하고, 이름(예: Chart temperature)을 입력한 다음 아이콘을 고릅니다.
  3. 유형으로 현재 대시보드 상태 업데이트를 선택합니다.
  4. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  5. 대시보드 툴바의 저장을 클릭합니다.

엔티티 옆의 동작 아이콘을 클릭하면 Timeseries Line Chart가 그 엔티티의 데이터로 바뀝니다.

미리 만든 대시보드로 이동합니다. 그 안의 특정 상태로 갈 수도 있습니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스를 선택하고(예: 위젯 헤더 버튼), 이름을 입력한 다음 아이콘을 고릅니다.
  3. 유형으로 다른 대시보드 탐색을 선택한 다음 대상 대시보드를 고릅니다. 그 대시보드 안의 특정 상태도 고를 수 있습니다.
  4. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  5. 대시보드 툴바의 저장을 클릭합니다.

실행되면 사용자 지정 JavaScript 함수를 실행합니다. 표 위젯에서 바로 HMI 장치나 자산을 삭제하는 등의 작업에 유용합니다. 예 — HMI 장치 삭제:

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 동작 셀 버튼을 선택하고, 이름(예: Delete HMI Device)을 입력한 다음 아이콘을 고릅니다.
  3. 유형으로 Custom action을 선택합니다. JavaScript 함수 편집기가 나타납니다.
  4. 사용자 지정 함수(아래 예시)를 붙여 넣은 다음 추가, 저장, 적용을 클릭하고 대시보드를 저장합니다.

HMI 장치를 삭제하는 함수 예시:

let $injector = widgetContext.$scope.$injector;
let dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
openDeleteDeviceDialog();
function openDeleteDeviceDialog() {
let title = "Are you sure you want to delete the device " + entityName + "?";
let content = "Be careful, after the confirmation, the HMI Device and all related data will become unrecoverable!";
dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(
function(result) {
if (result) {
deleteDevice();
}
}
);
}
function deleteDevice() {
deviceService.deleteDevice(entityId.id).subscribe(
function() {
widgetContext.updateAliases();
}
);
}

추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장하고, 대시보드 툴바의 저장을 클릭합니다.

엔티티 옆의 휴지통 아이콘을 클릭하면 확인 대화상자가 열립니다.

Custom action과 같지만 JavaScript 함수가 HTML 템플릿 안에서 그려집니다. 엔티티 만들기·편집 대화상자를 만들 때 유용합니다. 탭은 네 가지입니다.

  • 리소스 — 외부 JavaScript/CSS 리소스.
  • CSS — 사용자 지정 스타일.
  • HTML — 대화상자 마크업.
  • JS — 동작 로직.

이 예에서는 동작 버튼을 두 개 추가합니다. 위젯 헤더에는 엔티티를 만드는 버튼을, 행마다 엔티티를 편집하는 버튼을 둡니다. 먼저 추가 동작부터 만듭니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 위젯 헤더 버튼을 선택하고, 이름(예: Add device)을 입력한 다음 아이콘을 고릅니다.
  3. Custom action (with HTML template) 유형을 선택합니다.
  4. JS 탭에 새 엔티티를 추가하는 함수(아래 예시)를 붙여 넣습니다.
  5. HTML 탭에 대화상자 마크업(아래 예시)을 붙여 넣습니다.
  6. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.

HMI 장치나 자산을 추가하는 기능을 넣는 JavaScript 함수 예시:

let $injector = widgetContext.$scope.$injector;
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));
openAddEntityDialog();
function openAddEntityDialog() {
customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();
}
function AddEntityDialogController(instance) {
let vm = instance;
vm.allowedEntityTypes = ['ASSET', 'DEVICE'];
vm.entitySearchDirection = {
from: "FROM",
to: "TO"
}
vm.addEntityFormGroup = vm.fb.group({
entityName: ['', [vm.validators.required]],
entityType: ['DEVICE'],
entityLabel: [null],
type: ['', [vm.validators.required]],
attributes: vm.fb.group({
latitude: [null],
longitude: [null],
address: [null],
owner: [null],
number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]],
booleanValue: [null]
}),
relations: vm.fb.array([])
});
vm.cancel = function() {
vm.dialogRef.close(null);
};
vm.relations = function() {
return vm.addEntityFormGroup.get('relations');
};
vm.addRelation = function() {
vm.relations().push(vm.fb.group({
relatedEntity: [null, [vm.validators.required]],
relationType: [null, [vm.validators.required]],
direction: [null, [vm.validators.required]]
}));
};
vm.removeRelation = function(index) {
vm.relations().removeAt(index);
vm.relations().markAsDirty();
};
vm.save = function() {
vm.addEntityFormGroup.markAsPristine();
saveEntityObservable().subscribe(
function (entity) {
widgetContext.rxjs.forkJoin([
saveAttributes(entity.id),
saveRelations(entity.id)
]).subscribe(
function () {
widgetContext.updateAliases();
vm.dialogRef.close(null);
}
);
}
);
};
function saveEntityObservable() {
const formValues = vm.addEntityFormGroup.value;
let entity = {
name: formValues.entityName,
type: formValues.type,
label: formValues.entityLabel
};
if (formValues.entityType == 'ASSET') {
return assetService.saveAsset(entity);
} else if (formValues.entityType == 'DEVICE') {
return deviceService.saveDevice(entity);
}
}
function saveAttributes(entityId) {
let attributes = vm.addEntityFormGroup.get('attributes').value;
let attributesArray = [];
for (let key in attributes) {
if(attributes[key] !== null) {
attributesArray.push({key: key, value: attributes[key]});
}
}
if (attributesArray.length > 0) {
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
}
return widgetContext.rxjs.of([]);
}
function saveRelations(entityId) {
let relations = vm.addEntityFormGroup.get('relations').value;
let tasks = [];
for(let i=0; i < relations.length; i++) {
let relation = {
type: relations[i].relationType,
typeGroup: 'COMMON'
};
if (relations[i].direction == 'FROM') {
relation.to = relations[i].relatedEntity;
relation.from = entityId;
} else {
relation.to = entityId;
relation.from = relations[i].relatedEntity;
}
tasks.push(entityRelationService.saveRelation(relation));
}
if (tasks.length > 0) {
return widgetContext.rxjs.forkJoin(tasks);
}
return widgetContext.rxjs.of([]);
}
}

HTML 탭으로 바꾸고 대화상자의 HTML 템플릿을 입력합니다.

장치나 자산을 추가하는 기능을 넣는 HTML 코드 예시:

<form #addEntityForm="ngForm" [formGroup]="addEntityFormGroup"
(ngSubmit)="save()" class="add-entity-form">
<mat-toolbar fxLayout="row" color="primary">
<h2>Add entity</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Name</mat-label>
<input matInput formControlName="entityName" required>
<mat-error *ngIf="addEntityFormGroup.get('entityName').hasError('required')">
Entity name is required.
</mat-error>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Label</mat-label>
<input matInput formControlName="entityLabel" >
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<tb-entity-type-select
class="mat-block"
formControlName="entityType"
[showLabel]="true"
[allowedEntityTypes]="allowedEntityTypes"
></tb-entity-type-select>
<tb-entity-subtype-autocomplete
fxFlex *ngIf="addEntityFormGroup.get('entityType').value == 'ASSET'"
class="mat-block"
formControlName="type"
[required]="true"
[entityType]="'ASSET'"
></tb-entity-subtype-autocomplete>
<tb-entity-subtype-autocomplete
fxFlex *ngIf="addEntityFormGroup.get('entityType').value != 'ASSET'"
class="mat-block"
formControlName="type"
[required]="true"
[entityType]="'DEVICE'"
></tb-entity-subtype-autocomplete>
</div>
<div formGroupName="attributes" fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Latitude</mat-label>
<input type="number" step="any" matInput formControlName="latitude">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Longitude</mat-label>
<input type="number" step="any" matInput formControlName="longitude">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Address</mat-label>
<input matInput formControlName="address">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Owner</mat-label>
<input matInput formControlName="owner">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Integer Value</mat-label>
<input type="number" step="1" matInput formControlName="number">
<mat-error *ngIf="addEntityFormGroup.get('attributes.number').hasError('pattern')">
Invalid integer value.
</mat-error>
</mat-form-field>
<div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex>
<label class="checkbox-label">Boolean Value</label>
<mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;">
{{ (addEntityFormGroup.get('attributes.booleanValue').value ? "value.true" : "value.false") | translate }}
</mat-checkbox>
</div>
</div>
</div>
<div class="relations-list">
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>
<div class="body" [fxShow]="relations().length">
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;">
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;">
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label>Direction</mat-label>
<mat-select formControlName="direction" name="direction">
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value">
{{ ("relation.search-direction." + direction.value) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="relation.get('direction').hasError('required')">
Relation direction is required.
</mat-error>
</mat-form-field>
<tb-relation-type-autocomplete
fxFlex class="mat-block"
formControlName="relationType"
[required]="true">
</tb-relation-type-autocomplete>
</div>
<div fxLayout="row" fxLayout.xs="column">
<tb-entity-select
fxFlex class="mat-block"
[required]="true"
formControlName="relatedEntity">
</tb-entity-select>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center">
<button mat-icon-button color="primary"
aria-label="Remove"
type="button"
(click)="removeRelation(i)"
matTooltip="Remove relation"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
</div>
<div>
<button mat-raised-button color="primary"
type="button"
(click)="addRelation()"
matTooltip="Add Relation"
matTooltipPosition="above">
Add
</button>
</div>
</div>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty">
Create
</button>
</div>
</form>

이제 두 번째 동작으로 행마다 있는 편집 버튼을 추가합니다. 액션 목록에서 + 버튼을 클릭해 새 액션 추가 대화상자를 열고, 소스로 동작 셀 버튼을 선택하고, 이름(예: Edit device)을 입력하고, Custom action (with HTML template) 유형을 선택한 다음 같은 단계를 따릅니다. 아래 JavaScript 함수와 HTML 템플릿을 붙여 넣습니다.

장치나 자산을 편집하는 기능을 넣는 JavaScript 함수 예시:

let $injector = widgetContext.$scope.$injector;
let customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));
let entityService = $injector.get(widgetContext.servicesMap.get('entityService'));
let assetService = $injector.get(widgetContext.servicesMap.get('assetService'));
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
let entityRelationService = $injector.get(widgetContext.servicesMap.get('entityRelationService'));
openEditEntityDialog();
function openEditEntityDialog() {
customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();
}
function EditEntityDialogController(instance) {
let vm = instance;
vm.entityName = entityName;
vm.entityType = entityId.entityType;
vm.entitySearchDirection = {
from: "FROM",
to: "TO"
};
vm.attributes = {};
vm.oldRelationsData = [];
vm.relationsToDelete = [];
vm.entity = {};
vm.editEntityFormGroup = vm.fb.group({
entityName: ['', [vm.validators.required]],
entityType: [null],
entityLabel: [null],
type: ['', [vm.validators.required]],
attributes: vm.fb.group({
latitude: [null],
longitude: [null],
address: [null],
owner: [null],
number: [null, [vm.validators.pattern(/^-?[0-9]+$/)]],
booleanValue: [false]
}),
oldRelations: vm.fb.array([]),
relations: vm.fb.array([])
});
getEntityInfo();
vm.cancel = function() {
vm.dialogRef.close(null);
};
vm.relations = function() {
return vm.editEntityFormGroup.get('relations');
};
vm.oldRelations = function() {
return vm.editEntityFormGroup.get('oldRelations');
};
vm.addRelation = function() {
vm.relations().push(vm.fb.group({
relatedEntity: [null, [vm.validators.required]],
relationType: [null, [vm.validators.required]],
direction: [null, [vm.validators.required]]
}));
};
function addOldRelation() {
vm.oldRelations().push(vm.fb.group({
relatedEntity: [{value: null, disabled: true}, [vm.validators.required]],
relationType: [{value: null, disabled: true}, [vm.validators.required]],
direction: [{value: null, disabled: true}, [vm.validators.required]]
}));
}
vm.removeRelation = function(index) {
vm.relations().removeAt(index);
vm.relations().markAsDirty();
};
vm.removeOldRelation = function(index) {
vm.oldRelations().removeAt(index);
vm.relationsToDelete.push(vm.oldRelationsData[index]);
vm.oldRelations().markAsDirty();
};
vm.save = function() {
vm.editEntityFormGroup.markAsPristine();
widgetContext.rxjs.forkJoin([
saveAttributes(entityId),
saveRelations(entityId),
saveEntity()
]).subscribe(
function () {
widgetContext.updateAliases();
vm.dialogRef.close(null);
}
);
};
function getEntityAttributes(attributes) {
for (var i = 0; i < attributes.length; i++) {
vm.attributes[attributes[i].key] = attributes[i].value;
}
}
function getEntityRelations(relations) {
let relationsFrom = relations[0];
let relationsTo = relations[1];
for (let i=0; i < relationsFrom.length; i++) {
let relation = {
direction: 'FROM',
relationType: relationsFrom[i].type,
relatedEntity: relationsFrom[i].to
};
vm.oldRelationsData.push(relation);
addOldRelation();
}
for (let i=0; i < relationsTo.length; i++) {
let relation = {
direction: 'TO',
relationType: relationsTo[i].type,
relatedEntity: relationsTo[i].from
};
vm.oldRelationsData.push(relation);
addOldRelation();
}
}
function getEntityInfo() {
widgetContext.rxjs.forkJoin([
entityRelationService.findInfoByFrom(entityId),
entityRelationService.findInfoByTo(entityId),
attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE'),
entityService.getEntity(entityId.entityType, entityId.id)
]).subscribe(
function (data) {
getEntityRelations(data.slice(0,2));
getEntityAttributes(data[2]);
vm.entity = data[3];
vm.editEntityFormGroup.patchValue({
entityName: vm.entity.name,
entityType: vm.entityType,
entityLabel: vm.entity.label,
type: vm.entity.type,
attributes: vm.attributes,
oldRelations: vm.oldRelationsData
}, {emitEvent: false});
}
);
}
function saveEntity() {
const formValues = vm.editEntityFormGroup.value;
if (vm.entity.label !== formValues.entityLabel){
vm.entity.label = formValues.entityLabel;
if (formValues.entityType == 'ASSET') {
return assetService.saveAsset(vm.entity);
} else if (formValues.entityType == 'DEVICE') {
return deviceService.saveDevice(vm.entity);
}
}
return widgetContext.rxjs.of([]);
}
function saveAttributes(entityId) {
let attributes = vm.editEntityFormGroup.get('attributes').value;
let attributesArray = [];
for (let key in attributes) {
if (attributes[key] !== vm.attributes[key]) {
attributesArray.push({key: key, value: attributes[key]});
}
}
if (attributesArray.length > 0) {
return attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributesArray);
}
return widgetContext.rxjs.of([]);
}
function saveRelations(entityId) {
let relations = vm.editEntityFormGroup.get('relations').value;
let tasks = [];
for(let i=0; i < relations.length; i++) {
let relation = {
type: relations[i].relationType,
typeGroup: 'COMMON'
};
if (relations[i].direction == 'FROM') {
relation.to = relations[i].relatedEntity;
relation.from = entityId;
} else {
relation.to = entityId;
relation.from = relations[i].relatedEntity;
}
tasks.push(entityRelationService.saveRelation(relation));
}
for (let i=0; i < vm.relationsToDelete.length; i++) {
let relation = {
type: vm.relationsToDelete[i].relationType
};
if (vm.relationsToDelete[i].direction == 'FROM') {
relation.to = vm.relationsToDelete[i].relatedEntity;
relation.from = entityId;
} else {
relation.to = entityId;
relation.from = vm.relationsToDelete[i].relatedEntity;
}
tasks.push(entityRelationService.deleteRelation(relation.from, relation.type, relation.to));
}
if (tasks.length > 0) {
return widgetContext.rxjs.forkJoin(tasks);
}
return widgetContext.rxjs.of([]);
}
}

HTML 탭으로 바꾸고 편집 대화상자의 HTML 템플릿을 입력합니다.

장치나 자산을 편집하는 기능을 넣는 HTML 코드 예시:

<form #editEntityForm="ngForm" [formGroup]="editEntityFormGroup"
(ngSubmit)="save()" class="edit-entity-form">
<mat-toolbar fxLayout="row" color="primary">
<h2>Edit {{entityType.toLowerCase()}} {{entityName}}</h2>
<span fxFlex></span>
<button mat-icon-button (click)="cancel()" type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Name</mat-label>
<input matInput formControlName="entityName" required readonly="">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Label</mat-label>
<input matInput formControlName="entityLabel">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Entity Type</mat-label>
<input matInput formControlName="entityType" readonly>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Type</mat-label>
<input matInput formControlName="type" readonly>
</mat-form-field>
</div>
<div formGroupName="attributes" fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Latitude</mat-label>
<input type="number" step="any" matInput formControlName="latitude">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Longitude</mat-label>
<input type="number" step="any" matInput formControlName="longitude">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Address</mat-label>
<input matInput formControlName="address">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label>Owner</mat-label>
<input matInput formControlName="owner">
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field fxFlex class="mat-block">
<mat-label>Integer Value</mat-label>
<input type="number" step="1" matInput formControlName="number">
<mat-error *ngIf="editEntityFormGroup.get('attributes.number').hasError('pattern')">
Invalid integer value.
</mat-error>
</mat-form-field>
<div class="boolean-value-input" fxLayout="column" fxLayoutAlign="center start" fxFlex>
<label class="checkbox-label">Boolean Value</label>
<mat-checkbox formControlName="booleanValue" style="margin-bottom: 40px;">
{{ (editEntityFormGroup.get('attributes.booleanValue').value ? "value.true" : "value.false") | translate }}
</mat-checkbox>
</div>
</div>
</div>
<div class="relations-list old-relations">
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">Relations</div>
<div class="body" [fxShow]="oldRelations().length">
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="oldRelations"
*ngFor="let relation of oldRelations().controls; let i = index;">
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;">
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label>Direction</mat-label>
<mat-select formControlName="direction" name="direction">
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value">
{{ ("relation.search-direction." + direction.value) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="relation.get('direction').hasError('required')">
Relation direction is required.
</mat-error>
</mat-form-field>
<tb-relation-type-autocomplete
fxFlex class="mat-block"
formControlName="relationType"
required="true">
</tb-relation-type-autocomplete>
</div>
<div fxLayout="row" fxLayout.xs="column">
<tb-entity-select
fxFlex class="mat-block"
required="true"
formControlName="relatedEntity">
</tb-entity-select>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center">
<button mat-icon-button color="primary"
aria-label="Remove"
type="button"
(click)="removeOldRelation(i)"
matTooltip="Remove relation"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="relations-list">
<div class="mat-body-1" style="padding-bottom: 10px; color: rgba(0,0,0,0.57);">New Relations</div>
<div class="body" [fxShow]="relations().length">
<div class="row" fxLayout="row" fxLayoutAlign="start center" formArrayName="relations" *ngFor="let relation of relations().controls; let i = index;">
<div [formGroupName]="i" class="mat-elevation-z2" fxFlex fxLayout="row" style="padding: 5px 0 5px 5px;">
<div fxFlex fxLayout="column">
<div fxLayout="row" fxLayoutGap="8px" fxLayout.xs="column" fxLayoutGap.xs="0">
<mat-form-field class="mat-block" style="min-width: 100px;">
<mat-label>Direction</mat-label>
<mat-select formControlName="direction" name="direction">
<mat-option *ngFor="let direction of entitySearchDirection | keyvalue" [value]="direction.value">
{{ ("relation.search-direction." + direction.value) | translate}}
</mat-option>
</mat-select>
<mat-error *ngIf="relation.get('direction').hasError('required')">
Relation direction is required.
</mat-error>
</mat-form-field>
<tb-relation-type-autocomplete
fxFlex class="mat-block"
formControlName="relationType"
[required]="true">
</tb-relation-type-autocomplete>
</div>
<div fxLayout="row" fxLayout.xs="column">
<tb-entity-select
fxFlex class="mat-block"
[required]="true"
formControlName="relatedEntity">
</tb-entity-select>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="center center">
<button mat-icon-button color="primary"
aria-label="Remove"
type="button"
(click)="removeRelation(i)"
matTooltip="Remove relation"
matTooltipPosition="above">
<mat-icon>close</mat-icon>
</button>
</div>
</div>
</div>
</div>
<div>
<button mat-raised-button color="primary"
type="button"
(click)="addRelation()"
matTooltip="Add Relation"
matTooltipPosition="above">
Add
</button>
</div>
</div>
</div>
<div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">
<button mat-button color="primary"
type="button"
[disabled]="(isLoading$ | async)"
(click)="cancel()" cdkFocusInitial>
Cancel
</button>
<button mat-button mat-raised-button color="primary"
type="submit"
[disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">
Save
</button>
</div>
</form>

추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장하고, 대시보드 툴바의 저장을 클릭합니다.

위젯 헤더의 Add device 버튼을 클릭해 만들기 대화상자를 엽니다. 엔티티 유형으로 “Device”를 선택하고 필수 필드를 채운 다음 Create를 클릭합니다.

엔티티 옆의 연필 아이콘을 클릭해 편집 대화상자를 엽니다. 필요한 내용을 바꾸고 Save를 클릭합니다.

웹 페이지, 문서, 이미지, MX On Cloud 내부 페이지 등 URL 리소스를 엽니다. 새 브라우저 탭에서 열 수도 있습니다. URL 필드는 ${entityName}, ${entityId} 같은 템플릿 변수를 지원해 엔티티 컨텍스트에 따라 동적인 링크를 만들 수 있습니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스를 선택하고(예: 위젯 헤더 버튼), 이름을 입력한 다음 아이콘을 고릅니다.
  3. 유형으로 URL 열기를 선택합니다. 대상 URL을 입력합니다. 필요하면 새 브라우저 탭에서 열기를 켭니다.
  4. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  5. 대시보드 툴바의 저장을 클릭합니다.
지도 위젯 전용

맵 항목 배치 동작은 지도 위젯에 버튼을 추가해 사용자가 새 엔티티를 만들고 바로 지도에 놓을 수 있게 합니다. 버튼을 클릭한 뒤 지도에서 위치를 클릭하면 “Create entity” 대화상자가 열리고, 확인하면 엔티티가 MX On Cloud에 저장되며 고른 좌표에 새 항목이 나타납니다.

항목 유형은 네 가지입니다.

항목 유형설명
Marker클릭한 위치에 놓이는 점 마커.
Circle클릭한 점을 중심으로 하는 원. 반지름을 설정할 수 있습니다.
Polygon꼭짓점을 여러 번 클릭해 그리고, 마지막 클릭으로 닫는 다각형.
Polyline점을 여러 번 클릭해 그리는 선 경로.

이 동작에는 표준 만들기 대화상자를 여는 기본 JavaScript 함수가 미리 들어 있습니다. Custom action (with HTML template)과 같은 JS + HTML 템플릿 방식의 사용자 지정 함수로 바꿔 대화상자에 나타날 필드를 정할 수 있습니다.

예: 새 충전소 자산을 지도에 바로 놓는 “Add EV station” 버튼을 추가합니다.

데이터 소스 설정

새로 만든 엔티티가 지도에 바로 나타나려면 위젯 데이터 소스의 별칭이 만들 엔티티 유형으로 결정되어야 합니다. 동작을 추가하기 전에 이를 설정합니다.

  1. 위젯 설정에서 오버레이 영역을 열고 마커 탭을 선택합니다.
  2. 새 엔티티 별칭을 만듭니다. 이름을 “EV stations”로 하고, 필터 유형을 자산 유형으로 정하고, 유형을 “EV station”으로 지정합니다.
  3. 변경사항 적용을 클릭합니다.

동작 설정

  1. 위젯을 편집 모드로 열고 액션 영역까지 스크롤한 다음 액션 추가를 클릭하고 + 버튼을 클릭합니다.
  2. 액션 소스로 위젯 헤더 버튼을 선택합니다. 이름(예: Add EV station)을 입력한 다음 버튼 모양(유형, 아이콘, 선택 사항인 배경색)을 설정합니다.
  3. 유형 드롭다운에서 맵 항목 배치를 선택하고 항목 유형을 고릅니다. 이 예에서는 Marker입니다.
  4. JavaScript 함수 필드에는 기본 만들기 대화상자가 미리 들어 있습니다. 그대로 두거나 사용자 지정 함수로 바꿉니다.
  5. 추가, 저장, 적용을 클릭하고 대시보드를 저장합니다.

항목 배치

위젯 헤더의 Add EV station을 클릭해 배치 모드를 켠 다음 지도에서 원하는 위치를 클릭합니다. Add entity 대화상자가 열리면 이름을 입력하고, 엔티티와 자산 유형을 선택한 다음 Create를 클릭합니다. 새 마커가 지도에 바로 나타납니다.

자산으로 이동해 새 자산이 저장되었는지 확인합니다.

액션 소스는 동작을 실행하는 사용자 행동(버튼 클릭, 행 클릭, 두 번 클릭, 지도 마커 선택 등)을 정합니다. 쓸 수 있는 소스는 위젯 유형에 따라 다릅니다. 아래 예시는 Entities table 위젯과 새로운 대시보드 상태 탐색 동작 유형을 씁니다.

위젯 헤더에 동작 버튼을 추가합니다. 개별 엔티티가 아니라 위젯 전체에 적용됩니다. 모든 위젯 유형에서 쓸 수 있으며 가장 많이 쓰는 액션 소스입니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 위젯 헤더 버튼을 선택합니다.
  3. 액션 이름을 입력하고 버튼 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

결과

엔티티 행마다 아이콘 버튼을 추가합니다. 버튼을 클릭하면 그 엔티티에 대해 설정한 동작이 실행됩니다. 상세 정보로 이동하거나 개별 항목을 편집·삭제할 때 유용합니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 동작 셀 버튼을 선택합니다.
  3. 액션 이름을 입력하고 버튼 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

결과

사용자가 엔티티 행의 아무 곳이나 클릭하면 실행됩니다. 행 전체가 클릭 대상이며 전용 버튼은 없습니다. 같은 위젯에 행 더블 클릭 시와 함께 써서 한 번 클릭과 두 번 클릭에 서로 다른 동작을 줄 수 있습니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 행 클릭 시를 선택합니다.
  3. 액션 이름을 입력합니다. 필요하면 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

결과

사용자가 엔티티 행을 두 번 클릭하면 실행됩니다. 같은 위젯에 행 클릭 시와 함께 써서 한 번 클릭과 두 번 클릭에 서로 다른 동작을 줄 수 있습니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 행 더블 클릭 시를 선택합니다.
  3. 액션 이름을 입력합니다. 필요하면 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

결과

Entities table, Alarms table, HMI Device admin table, Asset admin table 위젯 전용

특정 표 셀을 클릭하면 실행됩니다. 열마다 독립된 동작을 둘 수 있으며 열 인덱스로 설정합니다. 행 클릭 시와 달리 열마다 다른 동작을 줄 수 있습니다. 예를 들어 온도 셀을 클릭하면 온도 팝오버가, 습도 셀을 클릭하면 습도 팝오버가 열립니다.

온도 열 동작 설정:

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 셀 클릭 시를 선택합니다.
  3. 열 인덱스 드롭다운에서 temperature 열을 선택합니다.
  4. 액션 이름을 입력하고, 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 대시보드 상태 표시 옵션에서 팝오버로 열기를 선택하고, 위치를 오른쪽으로 정하고, 너비와 높이를 지정합니다.
  6. 추가를 클릭합니다.

습도 열 동작 설정:

  1. + 버튼을 클릭해 동작을 하나 더 추가합니다.
  2. 액션 소스로 셀 클릭 시를 선택합니다.
  3. 열 인덱스 드롭다운에서 humidity 열을 선택합니다.
  4. 액션 이름을 입력하고, 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 대시보드 상태 표시 옵션에서 팝오버로 열기를 선택하고, 위치를 하단으로 정하고, 너비와 높이를 지정합니다.
  6. 추가를 클릭한 다음 저장과 적용을 클릭해 위젯 설정을 저장합니다.
  7. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

이제 두 동작이 설정되었습니다. 온도나 습도 셀을 클릭하면 선택한 장치의 해당 상세 팝오버가 열립니다.

Entities Hierarchy 위젯 전용

엔티티와 그 관계를 트리로 보여 주는 Entities Hierarchy 위젯에서 사용자가 노드를 선택하면 실행됩니다. 노드를 클릭하면 그 엔티티에 대해 설정한 동작이 실행됩니다.

동작을 실행하려면 Entities hierarchy의 노드를 클릭합니다. 이 예에서는 노드를 클릭하면 선택한 노드의 상세 정보를 보여 주는 상태로 이동합니다.

HTML 위젯 전용

HTML 요소 클릭 시를 쓰면 사용자가 위젯 HTML 레이아웃의 요소를 클릭할 때 동작(상태 이동, 링크 열기, 사용자 지정 동작 실행 등)을 실행할 수 있습니다.

동작 방식

HTML 요소 클릭 시를 설정할 때 id 속성이 위젯의 HTML 코드와 위젯 액션 탭에 정의한 동작을 잇는 연결 고리입니다.

id는 어떤 HTML 요소(예: <div>, <span>, <p>, <h1>, <button>)에도 붙일 수 있습니다. 보통 id는 컨테이너 요소(<p>, <div> 등)에 붙여 블록 전체를 클릭할 수 있게 합니다.

<p id="details" class="blue-box">
Current temperature: <span>22.5 °C</span>
</p>

팁

  • HTML 위젯에서만 동작합니다. 표, 차트 등에서는 각각의 액션 소스(행 클릭 시, 셀 클릭 시, 노드 선택 시 등)를 씁니다.
  • ID는 이름과 정확히 같아야 합니다(대소문자 구분).
  • 더 복잡한 경우(대화상자, 양식)에는 이를 지원하는 위젯에서 Custom action with HTML template / widgetContext.dialogs / widgetContext.customDialog를 씁니다.

단계별 설정

  1. 위젯 편집 모드로 들어가 액션 탭으로 이동한 다음 +(액션 추가)를 클릭합니다.
  2. 액션 소스에서 HTML 요소 클릭 시를 선택합니다.
  3. 이름을 정합니다(이 값이 HTML 요소의 ID로도 쓰입니다).
  4. 유형에서 동작 유형(예: 새로운 대시보드 상태 탐색)을 고르고 대상 상태를 지정합니다.
  5. 동작을 저장합니다(추가).
  6. 모양 탭 → HTML 영역으로 가서 동작 이름과 정확히 같은 id를 가진 요소를 추가합니다.
  7. 변경 사항을 적용하고(적용) 대시보드를 저장합니다.

MX On Cloud는 저장한 뒤 그 위젯에 만든 동작의 이름과 같은 id를 가진 요소의 클릭을 “감지”합니다. 찾으면 선택한 동작 유형(이동, 상태 갱신, 사용자 지정 동작 등)을 실행합니다.

예: Markdown/HTML Card 위젯 — 파란 영역 클릭

준비:

  1. 아래 HTML과 CSS로 대시보드에 Markdown/HTML Card 위젯을 추가합니다.
  2. Temperature history용 새 대시보드 상태를 만듭니다.

대안: Markdown/HTML Card 위젯과 상세 상태가 미리 설정된 대시보드를 내려받아 MX On Cloud 인스턴스로 가져올 수 있습니다.

Markdown/HTML Card 추가

Markdown/HTML 패턴:

const entity = data[0];
const color = entity.temperature > 25 ? "red" : entity.temperature > 20 ? 'green' : 'blue'
const entityName = `### Temperature value card\n - Current entity: <span >${entity.entityName}</span>\n `
const temp = `- Current temperature: <span style="color:${color};">${entity.temperature.toFixed(1)} °C</span>\n `
return entityName + temp;

Markdown/HTML CSS:

.office-card {
box-sizing: border-box;
padding: 16px;
margin: 6px 0;
text-align: center;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, "Noto Sans";
}
.office-card h3 {
margin: 0 0 12px;
font-size: 24px;
}
.blue-box {
background: #2196F31A;
border: 2px solid #2196F3;
border-radius: 10px;
padding: 10px;
margin: 8px 0;
font-weight: 700;
}
.green-box {
background: #4CAF501A;
border: 2px solid #4CAF50;
border-radius: 10px;
padding: 10px;
margin: 6px 0;
font-weight: 700;
}
.temp-value,
.hum-value {
font-weight: 700;
font-variant-numeric: tabular-nums;
}

동작 추가

  1. 위젯 편집 모드로 들어가 액션 탭으로 이동한 다음 +(액션 추가)를 클릭합니다.
  2. 액션 소스: HTML 요소 클릭 시를 고릅니다.
  3. 이름에 details를 입력합니다.
  4. 유형에서 새로운 대시보드 상태 탐색을 선택합니다.
  5. 대상 대시보드 상태에서 앞에서 만든 상태를 선택합니다.
  6. 추가를 클릭해 동작을 저장합니다.

HTML에 id 추가

  1. 모양 탭 → HTML 영역으로 갑니다.

  2. id=details를 가진 요소를 <p class="blue-box"> 줄에 추가합니다. 다음으로 바꿉니다.

    <p id="details" class="blue-box">

    그러면 파란 상자 블록 전체를 클릭할 수 있고 details 동작과 연결됩니다.

  3. 변경 사항을 적용하고(적용) 대시보드를 저장합니다.

결과

현재 온도가 있는 파란 블록을 클릭하면 설정한 동작이 실행되어 대상 상태로 이동합니다.

지도 위젯에는 대화형 요소(마커, 원, 다각형, 폴리라인, 툴팁 링크)에 연결된 액션 소스가 더 있습니다. 아래 예시는 Map 위젯과 대상 대시보드 상태가 이미 있다고 가정합니다.

사용자가 지도의 원형 지오존을 클릭하면 실행됩니다. 원은 엔티티의 JSON 속성 {"latitude": ..., "longitude": ..., "radius": ...}(반지름 단위는 미터)로 정의합니다. 클릭한 엔티티는 대상 상태에 컨텍스트로 자동 전달됩니다. 설정 방법은 Map 위젯 설정을 참고하세요.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 원 클릭 시를 선택합니다.
  3. 액션 이름을 입력합니다. 필요하면 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

사용자가 지도의 엔티티 마커를 클릭하면 실행됩니다. 위도·경도 좌표 속성이 있는 엔티티마다 마커가 자동으로 나타납니다. 클릭한 엔티티는 대상 상태에 컨텍스트로 자동 전달됩니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 마커 클릭 시를 선택합니다.
  3. 액션 이름을 입력합니다. 필요하면 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

사용자가 지도의 다각형 지오존 안을 클릭하면 실행됩니다. 다각형은 엔티티 속성에 저장한 좌표 쌍의 JSON 배열 [[lat, lng], [lat, lng], ...]로 정의하며, 건물, 구역 등 경계가 있는 모든 영역을 나타낼 수 있습니다. 클릭한 엔티티는 대상 상태에 컨텍스트로 자동 전달됩니다. 설정 방법은 Map 위젯 설정을 참고하세요.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.
  2. 액션 소스로 다각형 클릭 시를 선택합니다.
  3. 액션 이름을 입력합니다. 필요하면 아이콘을 고릅니다.
  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.
  5. 추가를 클릭한 다음 적용을 클릭해 위젯 설정을 저장합니다.
  6. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

마커를 클릭하면 툴팁을 표시합니다. 툴팁에는 클릭할 수 있는 링크를 하나 이상 넣을 수 있고, 링크마다 <link-act> 태그로 이름 있는 동작에 연결됩니다. 그래서 마커 하나에서 여러 동작을 제공할 수 있습니다. name 속성은 동작 이름과 정확히 같아야 합니다(대소문자 구분). 엔티티마다 툴팁 내용을 동적으로 바꾸려면 지도 위젯 고급 설정의 툴팁 함수 모드를 씁니다.

  1. 위젯을 편집 모드로 열고 액션 탭으로 가서 + 버튼을 클릭합니다.

  2. 액션 소스로 툴팁 태그 동작을 선택합니다.

  3. 액션 이름을 입력합니다. 이 이름(대소문자 구분)을 그대로 <link-act> 태그에 씁니다. 필요하면 아이콘을 고릅니다.

  4. 유형으로 새로운 대시보드 상태 탐색을 선택한 다음 대상 상태를 고릅니다.

  5. 추가를 클릭합니다.

  6. 모양 탭으로 가서 툴팁 표시를 켜고 동작 이름을 가리키는 <link-act> 태그를 추가합니다.

    <link-act name='TooltipTag'>Navigate to the ${entityName}</link-act>

    여기서 TooltipTag는 동작 이름이고, 요소의 글자는 툴팁에 표시되는 링크 라벨입니다.

  7. 적용을 클릭해 위젯 설정을 저장합니다.

  8. 대시보드 툴바의 저장을 클릭해 대시보드를 저장합니다.

위젯으로 부터 객체 설정 체크박스는 선택한 엔티티를 대상 대시보드 상태에 넘깁니다. 그러면 대상 상태에서 대시보드 상태로 부터의 개체 별칭으로 그 엔티티를 쓸 수 있습니다. 예를 들어 Entities table의 행을 클릭하면 그 HMI 장치를 상태에 넣어, 다음 상태의 상세 위젯이 그 데이터를 표시하게 할 수 있습니다.

상태에 엔티티를 둘 이상 저장해야 할 때도 있습니다. 예를 들어 고객 목록에서 그 고객의 HMI 장치로, 마지막으로 특정 장치의 상세 정보로 이동하고 싶을 수 있습니다. 이때 상태를 “Main”, “Customer Devices”, “Device Details” 세 개로 둘 수 있습니다. “Device Details” 상태에서 현재 고객(예: “currentCustomer”)과 현재 장치(예: “currentDevice”)를 가리키는 서로 다른 상태 엔티티 매개변수 두 개를 쓸 수 있습니다.