Angular向けWeb SDK

概要

Web SDKの使用により以下が可能となります。

Web SDKは @c8y というスコープでnpmに公開されている以下のパッケージから構成されています:

overview

これらのパッケージは下部から上部までそれぞれが依存関係にあります。@c8y/client はほとんど依存関係のない低レベルなAPIインタフェースである一方、@c8y/apps@c8y/ngx-components@c8y/client を含む、機能が豊富なアプリケーションを提供しています。

このように分割している目的は、全てのユースケースに対して適切なパッケージを提供するためです。例えば、Reactで規模の小さなアプリケーションを構築したい場合、APIの疎通に @c8y/client を使用できます。コックピットやデバイス管理アプリケーションに近いブランディング可能な機能豊富なアプリケーションが必要な場合は @c8y/stlyes と一緒に @c8y/ngx-components を使用できます。

以下は各パッケージのユースケースの一覧です。

npm では全てのパッケージを見つけることができます。これらのパッケージで素早くブートストラップするために @c8y/cli というCLIツールを開発しました。次はコマンドラインツール @c8y/cli の始め方を紹介します。

始め方

始めにアプリケーションのブートストラップをサポートするコマンドラインインタフェース(CLI)をインストールします:

$ npm install -g @c8y/cli

次に new コマンドで新しい空のアプリケーションをブートストラップします:

$ c8ycli new myApp

参考: Web SDKの可能性や実装の詳細を見たい場合、チュートリアルアプリケーションを試してください。以下 c8ycli new <<your-app-name>> tutorial を実行することでインストールできます。

CLIのインストールが完了したら、新たに作成されたフォルダに移動しnpm installを実行してください:

$ cd myapp
$ npm install

全てのパッケージががインストールされたら以下を実行することでアプリケーションを開始できます:

$ npm start

ブラウザで http://localhost:9000/apps/myapp/ へアクセスすると、startスクリプトで定義したテナントへプロキシするログイン画面を取得します。ログインできない場合は、指定しているインスタンスが誤っている可能性があります。テナントURLへのプロキシを変更するには、新しく作成されたpackage.jsonの start スクリプトを変更してください:

{
  "start": "c8ycli server -u http://your-tenant.my-provider.com"
}

ログイン後、ほとんど空のスターターアプリケーションが確認できるでしょう。より複雑な例に取り組みたい場合、@c8y/apps に関するう文書をご覧ください。アプリケーションを開発しデプロイしたい場合、コマンドラインツール の必要なコマンドの詳細をご覧ください。

参考: コックピットのような既存アプリケーションを拡張したい場合、ハイブリッドアプリケーションを使用できます。これにより新しいWeb SDK内に既存のAngularJSのプラグインを結合できます。詳しくは 移行 をご覧ください。

参考: ログイン時はテナント名もしくはテナントIDを提供する必要があります。(アプリケーションはlocalhostのURLからテナント名などをたどることができないため)テナント名・テナントIDを知らない場合は REST API を利用して取得できます。

最初のルートとコンポーネント

空のブートストラップアプリケーションを作成後、コンテンツを作成しましょう。まず、プロジェクトに新しいコンポーネントを追加し hello.component.ts という名前で保存します:

import { Component } from '@angular/core';

@Component({
  selector: 'app-hello',
  template: `
    <c8y-title>Hello World</c8y-title>
    <p>My first content.</p>
  `
})
export class HelloComponent {
  constructor() {}
}

アプリケーションに新しいコンポーネントを追加するには、新しいコンポーネントを宣言して app.module.ts 内でルートに追加する必要があります:

import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule as ngRouterModule } from '@angular/router';
import { CoreModule, BootstrapComponent, RouterModule } from '@c8y/ngx-components';
import { HelloComponent } from './hello.component'  // don't forget to import the new component

@NgModule({
  imports: [
    BrowserAnimationsModule,
    RouterModule.forRoot(),
    ngRouterModule.forRoot(
      [{ path: '', component: HelloComponent }], // hook the route here
      { enableTracing: false, useHash: true }
    ),
    CoreModule.forRoot()
  ],
  declarations: [HelloComponent], // add deceleration here
  bootstrap: [BootstrapComponent]
})
export class AppModule {}

このアプリケーションを起動しログインすると、以下のスクリーンショットと似たアプリケーション画面が現れます。このアプリケーションを拡張するには Angular Router@c8y/ngx-components を組み合わせて使用できます。

An Angular based application

アプリケーションオプション

UI アプリケーションは一連のオプションを定義することでカスタマイズできます。 オプションのオブジェクトは JSON 内で定義され、以下の順序で実行時に読み込まれマージされます:

様々なオプションを収集し、マージするこのプロセスは @c8y/cli に含まれるブートストラップレイヤーによって実行されます。

全てのオプションは 3 段階のどの段階でも定義できますが、ビルド時にのみ影響するオプションであったり、URL クエリパラメータとして記述するのが難しい複雑なオブジェクトを必要とするため、実行時では意味をなさないものもあります。

静的オプション

package.jsonc8y.application フラグメント内で定義されます:

{
  "c8y": {
    "application": {
      "name": "cockpit",
      "contextPath": "cockpit",
      "key": "cockpit-application-key",
      "tabsHorizontal": true,
      "upgrade": true,
      "rightDrawer": true,
      "contentSecurityPolicy": "default-src 'self' 'unsafe-inline' http: https: ws: wss:; script-src 'self' *.mapquestapi.com 'unsafe-inline' 'unsafe-eval' data:; style-src * 'unsafe-inline' blob:; img-src * data:; font-src * data:; frame-src *;"
    }
  }
}

c8ycli コマンド実行時に渡すことも可能です:

c8ycli build --app.contextPath=cockpit2 --app.dynamicOptionsUrl="/apps/public/public-options/options.json"

動的に取得されるオプション

静的オプションである dynamicOptionsUrl を使用し、アプリケーションは起動時に特定の URL から json を読み込もうとします。Things Cloud の組み込みアプリケーションでは、このオプションはインスタンスレベルとエンタープライズテナントのカスタマイズを提供するメカニズムとして  /apps/public/public-options/options.json に設定されます。 このプロパティはビルド時に静的に定義されるため、アプリケーションが実行時に動的に取得されるオプションを読み込むべきかどうか、またどこから読み込むべきかを開発者が決定できます。

URL オプション

URL オプションはクエリパラメータとしてアプリケーションの URL に追加するだけです。

https://<instance domain>/apps/cockpit?dynamicOptionsUrl=/apps/my-options/options.json&rightDrawer:false

組み込み済みオプション

以下は組み込み済みのオプションリストです。以下は JavaScript オプジェクトであるため、このリストは開発者が自身のアプリケーションや拡張機能に組み込みたいプロパティで容易に拡張できます。

export class ApplicationOptions {
  name: string; // To be saved to the server
  contextPath: string; // To be saved to the server
  key: string; // To be saved to the server
  upgrade?: boolean; // true if the application is hybrid using Angular and angularJS simultaneously
  brandingEntry?: string; // the entry path to the branding
  tsConfigPath?: string; // path to tsCconfig if typescript is used, defaults to ./tsconfig.json
  entryModule?: string;
  indexTemplate?: string; // path to the index.html to be used, otherwise the default will be used
  dynamicOptionsUrl?: string; // URL to load the dynamic fetched options
  faviconUrl?: string; // URL for the favicon
  brandingUrl?: string; // URL for a CSS that will replace the default branding
  brandingCssVars?: { // Object with properties that will be converted to CSS custom properties
    [key: string]: string
  };
  languages?: { // Object with properties to add and change the languages available in the applications
    [langCode: string]: {
      name: string;
      nativeName: string;
      url: string;
    }
  };
  localePath?: string; // The folder where the translations po files are loaded from
  extraCssUrls?: string[]; // URLs for extra CSS files to be loaded at runtime
  docs?: {
    noDefault: boolean, // Hide default links to documentation
    excludeDefault: string[], // The list of regex strings to be matched with the default docs url
    links: Array<{ // Additional documentation links to be displayed
        icon: string;
        label: string;
        url: string;
    }>
  };
  icon?: { // Application icon to be displayed in app switcher and header bar
    class?: string,
    url?: string
  };
  noAppSwitcher?: boolean; // Hides the application from the application switched (saved to the server)
  globalTitle?: string; // HTML page title
  hidePowered?: boolean; // Hide powered by at the bottom of the navigator
  supportUrl?: boolean | string; // URL for support link
  supportUserString?: string;
  rightDrawer?: boolean; // Show or hide the right drawer
  hideNavigator?: boolean; // Show or hide the navigator
  tabsHorizontal?: boolean;  // Show tabs horizontally or vertically
  loginExtraLink?: { // Extra link to add to login screen
    url: string,
    label: string
  };
  storageLimitationFeatureEnabled?: boolean;
  companyName?: string; // Company name used to prompt the user about support staff
  guideHrefTemplate?: string; // The full URL for documentation, by default it's ${docsBaseUrl}${partialUrl}
  docsBaseUrl?: string; // The base URL for documentation
  contentSecurityPolicy?: string; // CSP string added to the index.html
  imports?: string[]; // legacy plugin imports
}

開発者向けコマンドラインツール

アプリケーションのブートストラップ、実行、デプロイをサポートするために CLI(コマンドラインインタフェース)が用意されています。この CLI は cumulocity-node-tools の後継です。2 つの CLI の衝突を防ぐために、CLI では c8y の代わりに c8ycli という新しいコマンドを使用します。以下のように npm でインストールできます:

npm install -g @c8y/cli

使用方法

c8ycli [options] [command]

オプション

    -u, --url <url>                 リモートインスタンスのURL
    --version                       バージョン番号の表示
    -h, --help                      使用法の表示

コマンド

new 以外の全てのコマンドは glob patterns の配列をとります。これらはフォルダもしくはエントリーポイントマニフェストによって解決されます。

    new [name] [template]                   新しいアプリケーションもしくは既存のアプリケーション拡張のためにフォルダを作成する
    serve [options] [appPaths...]           ローカルの開発サーバを立ち上げる
    build [options] [appPaths...]           特定のアプリケーションをビルドする
    deploy [options] [appPaths...]          特定のパスからアプリケーションをデプロイする
    locale-extract [options] [srcPaths...]  翻訳ファイルから全ての文字列を抽出し、定義済みのフォルダへ.poファイルを出力する

new コマンド

c8ycli new [name] [template] では空のアプリケーションを作成する、もしくは既存のアプリケーション(コックピット、デバイス管理、管理)を拡張できます。既存のアプリケーションを拡張するには、以下のように、既存のアプリケーションの名前を [name] および [template] として使用します:

$ c8ycli new cockpit cockpit

アプリケーションオプション

アプリケーションオプションは --app.<option>=<value> で定義されます。これらは [appPaths...] に存在する全てのアプリケーションに適用されます。

    --app.name="My Application"
    --app.key=myapp-key
    --app.contextPath=myapplication
    --app.brandingEntry="./branding/mybranding.less"

Webpack オプション

Webpack オプションは --env.<option>=<value> により定義されます。これらは直接 webpack の設定へ渡されます。

    --env.mode="production"
    --env.hmr

Angular CLI

純粋なAngularアプリケーションを作成する場合、Angular CLIプロジェクトを作成し、Cumulocity CLIを追加してください。 この機能は1004.2.0以降で利用可能です。

Angular CLIのインストール

@c8y/cliをグローバルインストールするには 公式ページの方法 に従ってください。

npm install -g @angular/cli

新しいプロジェクトを作成する

ng new my-first-iot-project
cd my-first-iot-project

Cumulocity CLIを追加する

cd my-first-iot-project
ng add @c8y/cli

アプリケーションを実行する

ng serve

アプリケーションが実行されているかを確かめるには、ブラウザで http://localhost:4200/ にアクセスしてください。

package.json内の アプリケーションオプション を設定し、LESSやCSSのカスタム変数で ブランディングをカスタマイズ することもできます。

ブランディング

アプリケーションのスタイルには LESS で作成されたグローバル CSS が使用されています。これらのスタイルは Bootstrap3 に基づいており、元の LESS は npm パッケージの @c8y/style で配布されています。 既存のスタイルを拡張することによって、アプリケーション設定の詳細まで変更できますが、多くの開発者は色やロゴ、フォントなど数個の変数を置き換えるだけで簡単に実現したいと思っているでしょう。

変数を上書きするには以下を使用できます:

CSS カスタムプロパティ

CSS のカスタムプロパティを介して公開されているのは、利用可能な LESS 変数の一部のみです。 以下が利用可能な変数の一覧です。

:root {
  --brand-primary: gold;
  --brand-complementary: darkgreen;
  --brand-dark: red;
  --brand-light: purple;
  --gray-text: #333;
  --link-color: var(--brand-primary);
  --link-hover-color: var(--brand-complementary);
  --body-background-color: #f2f3f4;
  --brand-logo-img: url("/apps/ui-assets-management/logo-nav.svg");
  --brand-logo-img-height: 20%;
  --navigator-platform-logo: url("/apps/ui-assets-management/logo-nav.svg");
  --navigator-platform-logo-height: 36px; /* height of the logo set to 0 to hide the element */

  --navigator-font-family: inherit;
  --navigator-app-name-size: 16px; /* font size of the application name set to 0 to hide app's name */
  --navigator-app-icon-size: 46px; /* size of the application icon. set to 0 to hide the application icon.*/
  --navigator-bg-color: var(--brand-primary);
  --navigator-header-bg: var(--navigator-bg-color);
  --navigator-text-color: #ffffff;
  --navigator-separator-color: rgba(0, 0, 0, 0.05);
  --navigator-active-color: var(--navigator-text-color);
  --navigator-active-bg: var(--brand-complementary);

  --header-color: #ffffff;
  --header-text-color: var(--brand-dark);
  --header-hover-color: var(--brand-primary);
  --header-border-color: rgba(57, 72, 82, 0.05);

  --font-family-base: "Roboto", Helvetica, Arial, sans-serif;
  --headings-font-family: var(--font-family-base);
}

これらは brandingCssVars プロパティを利用した アプリケーションオプション を使用して実行時にカスタマイズできることに注意してください。このオプションは 9.22.0 以降のバージョンでのみ使用可能です。

LESS の使用

前提条件

@c8y/cli を使用していなければ、npm パッケージから元となるスタイルをインストールしてください。

npm install @c8y/style
  1. 例えば branding.less という名前の LESS ファイルを作成する
  2. 好きな名前の新しいフォルダに保存する
  3. フォルダ内に画像用のサブフォルダを作成する
my-application
│   app.modules.ts
│   index.ts
│   packages.json
|   ...
└───branding
│   │   branding.less
│   └───img
│       │   favicon.ico
│       │   main-logo.svg
│       │   tenant-brand.svg
│

branding.less の一行目は以下である必要があります:

@import "~@c8y/style/extend.less";

カスタマイズ例

現時点では必要に応じて好きな変数を変更することが可能です。

例として、ブランディングで最も重要な brand-color と呼ばれるメインカラーを変更してみましょう。

それぞれの LESS 変数を新しい色に変更することでメインカラーを変更できます。

@brand-color: red;

ボタンや有効になっているナビゲーションノード、有効になっているタブ、またホバー状態のボタンのようなユーザインターフェース要素が赤色に変化しました。

ログインダイアログの上部に位置しているメインロゴを変更するのはどうでしょうか?以下をご覧ください:

@{logo-login} {
  background-image: url("./img/logo-main.svg");
}
@brand-logo-height: 48%;

@c8y/cli を使用してブランディングの変更もできます。

c8ycli server --app.brandingEntry="<path-to-your-branding.less>"

ブランディング例が適用されたチュートリアルアプリケーションもご覧ください。

c8ycli new <appName> tutorial

ブランディングの詳細

ブランディングには簡単に制御できる 3 つの項目があります。

編集できる色は以下のような複数のカテゴリに分けられます:

Brand colors
@brand-color: #53cd61;
@brand-primary: @brand-color;
@brand-complementary: #a8b3b5;
@brand-primary-light: lighten(@brand-primary, 20%);
Status colors
@brand-success: #5cb85c;
@brand-info: @brand-color;
@brand-warning: #f0ad4e;
@brand-danger: #d9534f;
@danger: #d90000;
@warning: #fdc000;
@dark-warning: #ff8000;
@success: #5cb85c;
Gray shades
@gray-text: #444;
@gray-darker: #2c3637;
@gray-dark: #3b4748;
@gray-medium-dark: #49595b;
@gray-medium: #6d7a7c;
@gray: #8a9596;
@gray-light: #cacece;
@gray-lighter: #f8f8f8;
@gray-white: #fcfcfc;
@text-muted: @gray;
Component colors

ヘッダとナビゲーターの 2 つのコンポーネントは常にユーザから見えています。このため、注意してこれらの見た目を決める必要があります。

/* HEADER */
@headerColor: white;
@header-text-color: @gray-medium-dark;
@header-text-color-hover: @brand-primary;
@header-active-color: darken(@gray-medium-dark, 15%);

/* NAVIGATOR */
@navColor: @gray-darker;
@navColorHeader: transparent;
@navigator-title-color: white;
@navigator-text-color: @gray-lighter;
@navigator-separator-color: fade(white, 5%);
@navigator-font-family: @headings-font-family;
@navigator-font-size: 13px;
@navigator-active-color: white;
@navigator-active-bg: @brand-primary;

上記からわかるように、変数の一部は他の変数を再利用します。エラー発生を避けるために、これらの変数は全て宣言されている必要があることに注意してください。

ロゴ

ロゴ無しではブランディングとは言えません。

ログインダイアログの上部にあるロゴ、テナントブランドロゴ、ファビコンは変更可能です。

ファビコンを変更するには、以下を追加してください:

// to be loaded by webpack
.favicon-webpack-loader {
  background: url("./img/favicon.ico");
}

メインロゴを変更するには以下を追加してください:

@{logo-login} {
  background-image: url("./img/main-logo.svg");
}
@brand-logo-height: 48%;

ナビゲーター内のテナントブランドロゴを変更するには以下を追加してください:

@{logo-navigator} {
  background-image: url("./img/tenant-brand.svg");
}
@navigator-platform-logo-height: 100px;

タイポグラフィー

アプリケーションの見た目にはタイポグラフィーも重要です。フォントも変更可能です。

@font-family-sans-serif: "Lato", Arial, Verdana, sans-serif;
@font-family-base: @font-family-sans-serif;
@headings-font-family: "Roboto", Arial, Verdana, sans-serif;

ブランディング例

上記では、カスタムブランディング作成についての使用可能なオプションについて詳細に説明しました。全てのアプリケーションで 0 からブランディング設定をしたくない場合は、スニペットとして以下のブランディング例を使用してください。以下の例では最も重要な変数が定義されています。

@import "~@c8y/style/extend.less";

// Replace and uncomment each variable as you need them
/* LOGOS */
.favicon-webpack-loader {
  background: url("./img/favicon.ico");
} // to be loaded by webpack
@{logo-login} {
  background-image: url("./img/logo-main.svg");
}
@brand-logo-height: 48%; // percentage - height / width * 100
@{logo-navigator} {
  background-image: url("./img/logo.svg");
}
@navigator-platform-logo-height: 100px;

/* COLORS */
@brand-color: #53cd61; // main color
@brand-primary: @brand-color;
@brand-complementary: #a8b3b5;
@brand-primary-light: lighten(@brand-primary, 20%);
// status colors
@brand-success: #5cb85c;
@brand-info: @brand-color;
@brand-warning: #f0ad4e;
@brand-danger: #d9534f;
@danger: #d90000;
@warning: #fdc000;
@dark-warning: #ff8000;
@success: #5cb85c;
// grays
@gray-text: #444;
@gray-darker: #2c3637;
@gray-dark: #3b4748;
@gray-medium-dark: #49595b;
@gray-medium: #6d7a7c;
@gray: #8a9596;
@gray-light: #cacece;
@gray-lighter: #f8f8f8;
@gray-white: #fcfcfc;
@text-muted: @gray;

@body-background-color: #f8f8f8; // page background color - always use a light background

/* HEADER */
@headerColor: white;
@header-text-color: @gray-medium-dark;
@header-text-color-hover: @brand-primary;
@header-active-color: darken(@gray-medium-dark, 15%);

/* NAVIGATOR */
@navColor: @gray-darker;
@navColorHeader: transparent;
@navigator-title-color: white;
@navigator-text-color: @gray-lighter;
@navigator-separator-color: fade(white, 5%);
@navigator-font-family: @headings-font-family;
@navigator-font-size: 13px;
@navigator-active-color: white;
@navigator-active-bg: @brand-primary;
// when set adds a vertical gradient in the navigator background
// @grad-top:                    "";
// @grad-bottom:                 "";

/* TYPOGRAPHY */
// @font-family-sans-serif:      "Lato",Arial, Verdana, sans-serif;
// @font-family-base:            @font-family-sans-serif;
// @headings-font-family:        "Roboto",Arial, Verdana, sans-serif;

/* BUTTONS */
// @btn-border-radius-base:      2px;
// @btn-border-radius-large:     @btn-border-radius-base;
// @btn-border-radius-small:     @btn-border-radius-base;
// @btn-shadow:                  none;

/* COMPONENTS */
// @spinner-color:               lighten(@brand-primary, 30%);
// @link-color:                  #337ab7;
// @link-hover-color:            darken(@link-color, 15%);
// @input-focus-color:           #66afe9;

// @body-background-pattern:     "";
// @darker-header:               @gray-dark;
// @appswitcher-background:      none;
// @table-bg-hover:              fade(black, 1.5%);
// @header-app-name:             @header-text-color;
// @image-path:                  'img/';

ngx-components

ngx-components は Angular アプリケーションのためのコンポーネントコレクションかつデータアクセスレイヤです。コアコンポーネントを提供するのと同様に、Angular アプリケーションから Things Cloud へのアクセスを可能にします。これを実現するために、2 つの基本的なインポートによって構成されています:

前提条件

新しいアプリケーションをブートストラップするために @c8y/cli を使用していない場合、はじめにパッケージをインストールする必要があります:

$ npm install @c8y/ngx-components

次に、app モジュール(例: app.module.ts)に ngx-components モジュールを追加します:

import { NgModule } from "@angular/core";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { RouterModule as ngRouterModule } from "@angular/router";
import {
  CoreModule,
  BootstrapComponent,
  RouterModule
} from "@c8y/ngx-components";

@NgModule({
  imports: [
    BrowserAnimationsModule,
    RouterModule.forRoot(),
    ngRouterModule.forRoot([], { enableTracing: false, useHash: true }), // 1
    CoreModule.forRoot() // 2
  ],
  bootstrap: [BootstrapComponent] // 3
})
export class AppModule {}
  1. Things Cloud は pushState をサポートしていないため、useHash ナビゲーションを true にセットしてください。
  2. c8y- プレフィックスが付くコンポーネントを使用するために CoreModule をインポートします。
  3. ルートアプリケーションを初期化するために <c8y-bootstrap> コンポーネントを使用する BootstrapComponent でアプリケーションをブートストラップします。もしくは、好きなコンポーネントをブートストラップしてそのタグをテンプレートに含めるか、指定されたコンポーネントのみを再利用できます。

Extensions points

アプリケーションを拡張し構成するために ngx-components は Extensions points と呼ばれるコアアーキテクチャの概念を提供しています:

  1. コンテンツ投影(CP):
    この概念によりあるコンポーネントから別のコンポーネントへ内容を投影できます。例えば、別のコ ンポーネントで <c8y-title> を設定することでページのタイトルを設定できます。<c8y-title> タグの内容は、次にヘッダのバーに配置されているアウトレットコンポーネントに投影されます。この概念の利点は、投影された内容にどんな内容でも配置できることです。例えば、タイトルに別のカスタムコンポーネントを投影できます。
    この概念を利用した良い例として、Angular の routerLink ディレクティブを利用して異なるコンテキストにルーティングする c8y-action-bar-item があります:

    <c8y-action-bar-item [placement]="'right'">
      <a class="btn btn-link" routerLink="add">
        <i class="fa fa-plus-square"></i> {{'Add' | translate}}
      </a>
    </c8y-action-bar-item>
    

    上記の例は定義するコンポーネントに関係なく、ヘッダバーにアクション項目が表示されます。コンポーネントが初期化されると項目が表示され、コンポーネントの破棄時に削除されます。

  2. マルチプロバイダー(MP):
    マルチプロバイダーにより、宣言型のアプローチでアプリケーションを拡張できます。テンプレートに定義する代わりに、HOOK を通してすでに定義している factory を拡張します。このフックはアプリケーションの状態が変化した際に実行されます。返り値はページに挿入されます。Angular の通常の依存性注入を利用でき、結果として特定のタイプの Observable、Promise もしくは Array を返却できます。例えば、HOOK_TABS provider に追加することで特定のタブを定義できます:

    import { Injectable } from '@angular/core';
    import { Router } from '@angular/router';
    import { Tab, TabFactory, _ } from '@c8y/ngx-components';
    
    @Injectable()
    export class ExampleTabFactory implements TabFactory { // 1
    
      constructor(public router: Router) { }
    
      get() {
        const tabs: Tab[] = [];
        if (this.router.url.match(/world/g)) {            // 2
          tabs.push({
            path: 'world/awesome',
            label: 'Awesome',
            icon: 'angellist'
          } as Tab);
        }
        return tabs;                                      // 3
      }
    }
    

    TabFactory を実装している Injectable() サービスを定義することで、どのページにどのタブを表示するのかを定義できます(1)。この例では、Angular の Router サービスを使用することでルートの URL が world という名前を含むかどうかを確認でき(2)、 これが Awesome というラベルがついたタグと一致する時のみ返却します(3)。これをモジュールのプロバイダの定義に追加することでルート変更の度に get() 関数がチェックされることを確認します:

    @NgModule({
      declarations: [
        /* ... */
      ],
      imports: [
        BrowserAnimationsModule,
        RouterModule.forRoot(),
        ngRouterModule.forRoot(
          [
            /* ... */
          ],
          { enableTracing: false, useHash: true }
        ),
        CoreModule.forRoot()
      ],
      providers: [
        { provide: HOOK_TABS, useClass: ExampleTabFactory, multi: true } // hook the ExampleTabFactory defined earlier
      ],
      bootstrap: [BootstrapComponent]
    })
    export class AppModule {}
    

    通常、コンテキストが複数のルートで共有されている、もしくはコンテンツを解決するためにより複雑なロジックが必要となる場合は、ルートおよびマルチプロバイダー内でコンテンツ投影を利用します。 例: タイトルは一つのルートに対してのみ有効 -> コンテンツ投影を利用。タブは特定の状況下で特定のルートのみ表示される必要がある -> マルチプロバイダーを利用。 以下のフックが現時点でサポートされています:

    • HOOK_TABS: 特定の条件下でタブを表示させます。
    • HOOK_NAVIGATOR_NODES: ナビゲータノードを表示できるようにします。
    • HOOK_ACTION: 特定の条件で表示、もしくは有効化するべきグローバルアクションを定義できます。
    • HOOK_BREADCRUMB: ヘッダバーにパンくずリストを表示するために使用します。
    • HOOK_SEARCH: 検索を表示するかどうかを定義できます。
    • HOOK_ONCE_ROUTE: ルートを定義できます。デバイス詳細画面に新しいタブを追加したいなど、コンテキストルートを使いたい場合、こちらを使用してください。その他の全てのルートにはデフォルトの Angular のルーターを使用してください。
  3. サービス
    サービスは ngx-components の大部分のコンポーネントで定義されています。これらのコンポーネントは Angular の依存性注入により使用できます。つまり、これらのサービスはコンポーネントのコンストラクタに注入することができ、特定の UI 要素を追加したり削除したりできます。以下の例はアラートでこの概念を使う方法を示しています:

    constructor(private alert: AlertService) {
      try {
        // do something that might throw an exception
      } catch(ex) {
        this.alert.add({
          text: 'Something bad happened!'
          type: 'danger';
          detailedData: ex;
        } as Alert);
      }
    }
    
  4. レガシープラグイン
    デフォルトアプリケーション(コックピット、デバイス管理、管理)を拡張する場合、ng1.ts というファイルが作成されます。Angular にまだ移行されておらず、AngularJS を使用しているプラグインが存在します。addImports: [] もしくは removeImports: [] プロパティによってターゲットファイルで以前行なっていたように、アプリケーションの見た目をカスタマイズするためにこれらのプラグインを追加したり削除したりできます。以下は AngularJS のターゲットファイルでデフォルトのインポートを削除している例です:

    {
      "name": "example",
      "applications": [
        {
          "contextPath": "cockpit",
          "addImports": ["my-plugin/cockpit-home"],
          "removeImports": ["core/cockpit-home"]
        }
      ]
    }
    

    コックピットアプリの ng1.ts ファイルを編集することで新しい Angular フレームワークでも同様の結果が得られます:

    import "@c8y/ng1-modules/core";
    // [...] more imports removed for readability
    import "@c8y/ng1-modules/alarmAssets/cumulocity.json";
    // import '@c8y/ng1-modules/cockpit-home/cumulocity.json';              // 1
    import "@c8y/ng1-modules/deviceControlMessage/cumulocity.json";
    import "@c8y/ng1-modules/deviceControlRelay/cumulocity.json";
    // [...] more imports removed for readability
    import "my-plugin/cumulocity.json"; // 2
    

    上記では、単純に元のウェルカム画面プラグインのインポートを削除し(1)、カスタムで実装したものに置き換えています(2)。全ての AngularJS のプラグインは、レガシープラグインがインポートされているということを webpack に伝えるために /cumulocity.json を追加する必要があります。

    カスタムのデフォルトでないアプリケーションでレガシープラグインを使うには、package.json ファイルに upgrade フラグを設定し、前述と同じインポートアプローチを使用する必要があります:

    "c8y": {
      "application": {
        "name": "myapp",
        "contextPath": "myapp",
        "key": "myapp-application-key",
        "upgrade": true
      }
    }
    

    また、アプリケーションのモジュール定義は、これらのプラグインをサポートするために変更する必要があります:

    import { NgModule } from '@angular/core';
    import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
    import { RouterModule as NgRouterModule } from '@angular/router';
    import { UpgradeModule as NgUpgradeModule } from '@angular/upgrade/static';
    import { CoreModule, RouterModule } from '@c8y/ngx-components';
    import { UpgradeModule, HybridAppModule, UPGRADE_ROUTES } from '@c8y/ngx-components/upgrade';
    import { AssetsNavigatorModule } from '@c8y/ngx-components/assets-navigator';
    
    @NgModule({
      imports: [
        BrowserAnimationsModule,
        RouterModule.forRoot(),
        NgRouterModule.forRoot([
          ...UPGRADE_ROUTES,
        ], { enableTracing: false, useHash: true }),
        CoreModule.forRoot(),
        AssetsNavigatorModule,
        NgUpgradeModule,
        // Upgrade module must be the last
        UpgradeModule
      ]
    })
    export class AppModule extends HybridAppModule {
      constructor(protected upgrade: NgUpgradeModule) {
        super();
      }
    }
    

    これにより、AngularJS と Angular のプラグイン、もしくはモジュールを使用できるようになるハイブリッドモードでアプリケーションを開始することができます。

どの extention point がサポートされているかや、どの概念が特定のシナリオに使われるかを判断するために、以下のセクションで全てのサポートされているコンポーネントの概要が示されており、どの場合に何を使うべきかが説明されています。

Things Cloud へのデータアクセス

CommonModuleDataModule をエクスポートしています。これは、@c8y/client を抽象化したものであり、Angular の依存性注入と共にクライアントのサービスで使用することができます。つまり、CommonModule または DataModule がインポートされているモジュールでは、Things Cloud のデータへのアクセスは依存性注入により可能になります:

import { Component } from '@angular/core';
import { AlarmService } from '@c8y/client';              // 1

@Component({selector: 'app-alerts', template: ''})
export class AlarmComponent {
  constructor(private alarmService: AlarmService) {}    // 2

  async getAllAlarms() {
    const alarms = await this.alarmService.list();      // 3
    return alarms.data;
  }
}
  1. @c8y/client パッケージからサービスをインポートする
  2. サービスを依存性注入する
  3. Things Cloud からデータをリクエストするためにサービスを使用する

利用可能なサービスとデータのフィルタ方法、選択方法に関する詳細情報は @c8y/client をご覧ください。

Clientライブラリ

@c8y/client は Things Cloud API のためのアイソモーフィックな(node とブラウザ用)JavaScript クライアントです。

インストール

npm install @c8y/client

使用方法

Things Cloud REST API から一覧データを取得する場合は client.<endpoint>.list()、詳細情報を取得する場合は client.<endpoint>.list() を使用してください。これらのメソッドは常に promise を返します。observable を取得するには、list$ もしくは detail$ を使用してください。

以下のセクションでは、これらの機能の標準の特徴について記述しています。より詳細な情報は こちらのドキュメント を参照してください。

promise で詳細と一覧情報を取得する(pull)

メソッド 詳細 パラメータ 戻り値
detail(entityOrId)                      特定のエンティティの詳細データをリクエストする entityOrId: string | number | IIdentified : id もしくは、数値または文字列としての id を含むオブジェクト Promise<IResult<TData>>: IResult にラッピングされている Promise としてのリスト。IResultList はデータとレスポンスを含みます。
list(filter) オプショナルのフィルタを用いて一覧データをリクエストする filter:object: paging もしくは filtering のリストのためのオプショナルフィルタ Promise<IResultList<TData>>: IResultList にラッピングされている Promise としてのリスト。IResultList はデータ、レスポンス、ページングの情報を含みます。

Fetch API を使用してマイクロサービスへアクセスする

client ライブラリは内部的に Fetch API を用いています。コア機能にアクセスすることにより、任意のリソースに対して認証された要求をすることができます。スタンドアロンでは core.client.fetch(url, options) を使用し、Angular では @c8y/ngx-components/dataFetchClient をインジェクトするのみです:

constructor(private fetchClient: FetchClient) {} // di

async getData() {
  const options: IFetchOptions = {
      method: 'GET',
      headers: { 'Content-Type': 'application/json' }
    };
  const response = await fetchClient.fetch('/service/my-service', options); // Fetch API Response
}

content type が正しく設定されていれば、全てのフェッチレスポンスは JSON へパース可能です。フェッチレスポンスの処理についての詳細は MDN の文書 をご覧ください。

認証方法

現時点で、Things Cloud には 2 種類の認証方法があります:

すぐに使い始めることができるように、@c8y/client は常に Basic 認証を利用し、直接ログインを検証する簡易の静的関数を提供しています。

await Client.authenticate({ tenant, user, password }), url);

この関数は内部的にクライアントインスタンスを作成し、API にアクセスして与えられた認証情報が正しいかどうかを検証します。場合によっては、より詳細な認証方法を使用する必要があります。例えば、ユーザがどのどの認証方法を使用するか不明な場合があります。この場合、クライアントのインスタンスを作成し、認証情報を渡す必要があります:

const baseUrl = "https://acme.cumulocity.com";
const client = new Client(new CookieAuth(), baseUrl); // use here `new BasicAuth()` to switch to Basic Auth
try {
  const { data, paging, res } = await client.user.currentUser();
  console.log("Login with cookie successful");
} catch (ex) {
  console.log("Login failed: ", ex);
}

observable で詳細データと一覧データを購読する (push)

detail$list$ 関数により、変更の度に値を放出するリアルタイムチャンネルを購読できます:

メソッド 詳細 パラメータ 戻り値
detail$(entityOrId, options) 一つのエンティティの詳細データの observable を返却する entityOrId: string | number | IIdentified: id もしくは数値または文字列としての id を含むオブジェクト
options: IObservableOptions: observable を定義するための設定用オブジェクト(オプショナル)
Observable<TData>>: 購読可能な observable としてのリスト
list$(filter, options) エンティティのリストの observable を返却する filter: object: リストの paging または filtering のためのフィルタ(オプショナル)
options: IObservableOptions: observable を定義するための設定用オブジェクト(オプショナル)
ObservableList<TData>>: 購読可能な observable のリスト

以下にいくつかの例を示します。Angular での client の実装についてのより詳細な情報は @c8y/cli をご覧いただき、 new コマンドでサンプルアプリケーションの作成を試してみてください。

inventory からリストのデータをリクエストする:

import { Client } from "@c8y/client";

const baseUrl = "https://demos.cumulocity.com/";
const tenant = "demos";
const user = "user";
const password = "pw";

(async () => {
  const client = await Client.authenticate(
    {
      tenant,
      user,
      password
    },
    baseUrl
  );
  const { data, paging } = await client.inventory.list();
  // data = first page of inventory
  const nextPage = await paging.next();
  // nextPage.data = second page of inventory
})();

inventory エンドポイントの observable を取得する:

import { Client } from "@c8y/client";

const baseUrl = "https://demos.cumulocity.com/";
const tenant = "demos";
const user = "user";
const password = "pw";

(async () => {
  const client = await Client.authenticate(
    {
      tenant,
      user,
      password
    },
    baseUrl
  );
  client.inventory.list$().subscribe(data => {
    // request inventory data via fetch and adds realtime if data changes
    console.log(data);
  });
})();

リアルタイムの使用:

// realtime event
const subscription = client.realtime.subscribe('/alarms/*', (data) => {
  console.log(data); // logs all alarm CRUD changes
});
client.realtime.unsubscribe(subscription);

// realtime observable
const observable$ = client.realtime.observable('/alarms/*');
const observableSubscription = observable$.subscribe((data) => {
  console.log(data)); // logs all alarm CRUD changes
});
observableSubscription.unsubscribe();

node.js での認証

コンストラクタ new Client([...]) は API からデータを取得できるクライアントを初期化します。Client.authenticate([...]) とは異なり、テナント名が必要で、ログインが正しいかは検証しません。このコンストラクタは node.js でマイクロサービスを開発する際に便利です。

const auth = new BasicAuth({
   user: 'youruser',
   password: 'yourpassword',
   tenant: 'acme'
 });

 const baseUrl = 'https://acme.cumulocity.com';
 const client = new Client(auth, baseUrl);
 (async () => {
   const { data, paging, res }); =  await client.inventory.list({ pageSize: 100 });
 })();

移行

このセクションはプラグイン用AngularJS SDKを用いてプラグインを開発したことがある、もしくは既存のデフォルトアプリケーションを拡張したい開発者を対象としています。

以下のコンセプトを読み、理解しておいてください:

ハイブリッドアプリケーションをセットアップする

ハイブリッドアプリケーションでは、同時にAngularJSとAngularを使用できます。AngularJSで記述された移行されていないプラグインをAngularの文脈で使用できるようになります。テンプレートとしてコックピット、デバイス管理、管理などのデフォルトアプリケーションを使用する場合、CLIは自動的にハイブリッドアプリケーションをセットアップします。使用コマンドは c8ycli new <your-app-name> <template-name> です。

例えば、デフォルトのコックピットを上書きするには以下を実行します:

c8ycli new cockpit cockpit

このコマンドを実行すると、以下のファイルを含む単純なファイル構造が出来上がります:

AngularJSプラグインのインポート

アプリケーションへカスタムプラグインを統合するには、はじめにプラグインをインポートしたいアプリケーションのハイブリットアプリケーションをセットアップする必要があります。そしてハイブリットアプリケーションにプラグインをコピーし、 ng1.ts ファイルでプラグインの cumulocity.json をimport文で参照してください:

import 'my-custom-plugin/cumulocity.json';

webpackはマニフェストファイルを読みとり、コンテンツをimportを必要とするCommonJSへ変換します。これにより、プラグインがロードされアプリケーションに適用されます。

カスタムブートストラッピングとアップグレード

app.module.ts ファイルでは、HybridAppModule に存在する以下のコードを用いてハイブリットアプリケーションをブートストラップしています。

import { UpgradeModule as NgUpgradeModule } from '@angular/upgrade/static';
import { ng1Modules } from './ng1';

export abstract class HybridAppModule {
  ng1Modules = ng1Modules;
  protected upgrade: NgUpgradeModule;

  ngDoBootstrap() {
    (window as any).bootstrap();
    this.upgrade.bootstrap(document.getElementById('app'), this.ng1Modules, { strictDi: false });
  }
}

このモジュールはアップグレードモジュールをブートストラップするだけでなく、Angularアプリケーションを初期化します。

app.module.ts ではブートストラッピングを制御できます。例えば、ブートストラップされたng1モジュールに別のモジュールを追加できます:

constructor() {
  this.ng1Modules.push('my-custom-module');
}

上記のコードは my-custom-module も読み込みます。上記のコードを用いると ngDoBootstrap() 関数をアプリケーションのニーズに応じて上書きできます。(例: Angularの文書に示されているupgrade, downgradeのAngularのserviceなど)

アプリケーション

アプリケーションパッケージ(@c8y/apps)はWeb SDK用にサンプルアプリケーションを提供しています。

前提条件

@c8y/apps パッケージを利用するには @c8y/cli をインストールする必要があります。インストール方法はドキュメントの指示に従ってください。

インストール後、以下コマンドを実行できます:

$ c8ycli new [your-app-name] [example-name]

例として、 my-app という名前のチュートリアルアプリケーションを生成するには以下のコマンドを実行します:

$ c8ycli new my-app tutorial

@c8y/apps に含まれているアプリケーション

以下の表は現時点でサポートしているアプリケーションの一覧です:

名前 詳細
application 新しいアプリケーションを迅速にブートストラップするための空のアプリケーションです。アプリケーション生成時に [example-name] を指定しない場合、デフォルトでこのアプリケーションが生成されます。
tutorial @c8y/ngx-components の概念の多くを集約済みのアプリケーションです。コード例を確認するにはこちらを使用してください。
cockpit コックピット のデフォルトアプリケーションです。既存のコックピットアプリケーションを拡張する際に使用してください。
devicemanagement デバイス管理 のデフォルトアプリケーションです。既存のデバイス管理アプリケーションを拡張する際に使用してください。
administration 管理 のデフォルトアプリケーションです。既存の管理アプリケーションを拡張する際に使用してください。

ブランディングと言語のカスタマイズ

アプリケーションオプション を使用し、各テナントで組み込みアプリケーションの見た目をカスタマイズしたり、アプリケーションで利用可能な言語を追加・置き換えすることができます。アプリケーションオプション で記述されているように、根底にある仕組みは静的にホスティングされている web アプリケーションです。

このチュートリアルでは 2 つの web アプリケーションを公開します:

以下のコマンドでインストールできる node.js の @c8y/cli を利用しデプロイします:

npm install -g @c8y/cli

初期リポジトリのダウンロードもしくは複製

https://github.com/Cumulocity/ui-customization で利用可能なリポジトリをダウンロードもしくは複製できます。この文書ではブランディングや新しい言語の追加例を見ることができます。

git clone https://github.com/Cumulocity/ui-customization

このフォルダ内には以下の 2 つのフォルダが存在します:

public-options
ui-assets

ブランディングのオプション

public-options/options.json のファイルを編集し、brandingCssVars のサブプロパティを変更します。これらのプロパティは実行時に CSS カスタムプロパティ に変換されます。 brand-logo-img プロパティと navigator-platform-logo プロパティは URL であることに注意してください。このため、対応するファイルは ui-assets フォルダ内に配置しなければなりません。 ファビコンを変更するには、faviconUrl プロパティを編集する、もしくは ui-assets フォルダ内の対応するファイルを追加してください。 ブラウザのウィンドウに表示されるタイトルを変更するには、globalTitle プロパティを変更してください。 これらの設定が十分でない場合、URL のリストを extraCssUrls プロパティに追加し、実行時に追加の CSS を読み込むこともできます。

{
  "extraCssUrls": ["/apps/ui-assets/extra.css"]
}

言語

言語の国際化に使用されているプラットフォーム UI 文字列は gettext に保存されています。プラットフォームに新しい言語を追加したい場合、これらのファイルを編集するソフトウェアが必要です。(Poedit など)

翻訳されている各カタログは JSON 形式で実行時に読み込まれます。.po (gettext) 拡張子のファイルを.json ファイルに変換するには、最初のステップでインストールした @c8y/cli を使用します。

独自翻訳の追加方法

  1. @c8y/ngx-components@1004.0.6/locales/locales.pot から文字列カタログをダウンロードします。(バージョン 1004.0.6 は自身のインスタンスで実行されているバージョンに変更してください)
  2. 好きな.pot ファイルエディタでファイルを読み込み、各文字列を適切な言語に翻訳しファイルを保存します。必要な数の言語だけこの手順を繰り返します。 このステップの成果物として、各言語の.po カタログファイルが出来上がります。これらのファイルは後続のバージョンで文字列を更新する時に使うため、安全な場所に保存しておいてください。
  3. c8ycli を利用して新しく作成された.po ファイルを.json ファイルへ変換します:
c8ycli locale-compile path/to/language.po
  1. ui-assets フォルダへ生成された.json ファイルをコピーします。
  2. public-options/options.json の language フラグメントを更新します。
languages?: {
      [langCode: string]: {
        name: string;
        nativeName: string;
        url: string;
      }
}

ダウンロードしたリポジトリでは、以下のようなロシア語への翻訳例があります:

"languages": {
  "ru": {
    "name": "Russian",
    "nativeName": "русский язык",
    "url": "/apps/public/ui-assets/ru.json"
  }
}

デプロイ

public-optionsui-assets の双方を含むフォルダ内で以下のコマンドを実行します:

c8ycli deploy ui-options ui-assets

テナント/インスタンス情報を入力すると、アプリケーションがデプロイされ、特定のテナントとサブテナントで有効化されます。

参考: パフォーマンスの理由から、オプションはキャッシュされています。変更を有効にするためにはブラウザを 2 回リロードする必要があります。