开发者中心

Angular JS Web SDK

概述

Smart Apps 工具包 允许创建连接到QuarkIoE生态系统的 AngularJS 组件。通过 c8y.sdk,可以使用 AngularJS 服务,文档在 这里。本文档中,我们描述如何一步一步用 c8y.sdk 创建一个简单的 AngularJS 应用程序。QuarkIoE应用程序的基本概念,参见 开发应用程序。参考资料,参见 插件参考

本教材资料可以在 https://bitbucket.org/m2m/cumulocity-examples 的目录 hello-core-api下获取。如果希望运行例子,克隆代码库或从bitbucket下载:

打开 hello-core-api/js/app.js 找到包含 c8yCumulocityProvider那些行。 用 c8yCumulocityProvider.setAppKeyc8yCumulocityProvider.setBaseUrl 设置应用程序密钥和 QuarkIoE域。 然后:


        $ cd hello-core-api
        $ npm install
        $ bower install
        $ grunt server
                      

前提条件

你应该熟悉以下技术:

具备以下的前提条件后,可以开发插件和执行例子:

  • 需要安装 Node.js (0.10 或以上稳定版) 和 Grunt
  • 需要能够访问QuarkIoE账号。即需要租户名,用户名和密码。

如果确信已经安装了 node, gruntbower,可以跳到步骤 1.

步骤

  1. 检查依赖版本
  2. 设置项目结构
  3. 设置依赖关系
  4. 创建 index.html
  5. 创建一个 AngularJS 应用
  6. 创建登录页面
  7. 创建主页面
  8. 创建 设备/报警/事件 列表
  9. 实现过滤
  10. 创建刷新按钮

0. 检查依赖版本

node

从检查node版本开始,确保是 0.10 或更新版:


   ~ $ node --version
    v0.10.39
                    
bower

需要全局安装 bower 。首先检查是否已经安装:


  ~ $ bower --version
    1.4.1
                    

如果找不到 bower 命令:


  ~ $ npm install bower -g
                    

更新 bower 到最新版:


  ~ $ npm update bower -g
                    
grunt-cli

需要全局安装 grunt-cli 。首先检查是否已经安装:


  ~ $ grunt --version
                      grunt-cli v0.1.13
                      grunt v0.4.5
                    

如果找不到 grunt 命令:


  ~ $ npm install grunt-cli -g
                    

更新 grunt-cli 到最新版:


  ~ $ npm update grunt-cli -g
                    

1. 设置项目结构

为项目创建下面的目录结构:


  hello-core-api
    .
    ├── Gruntfile.js
    ├── bower.json
    ├── css
    ├── index.html
    ├── js
    │   ├── alarms_ctrl.js
    │   ├── app.js
    │   ├── login_ctrl.js
    │   ├── main_ctrl.js
    │   └── section_dir.js
    ├── login.html
    ├── main.html
    ├── package.json
    ├── section.html
    └── sections
    ├── alarms.html
    ├── devices.html
    └── events.html
                    

示例项目 的目录 hello-core-api/css复制css文件。

2. 设置依赖关系

复制下面内容到 bower.json:


    {
      "name": "hello-core-api",
      "dependencies": {
      "bootstrap": "~3.3.5",
      "angular-route": "1.2.20",
      "cumulocity-clients-javascript": "latest"
    }
  }
                    

这个 json 文件定义了示例项目依赖的模块/库。现在,运行下面的命令:


hello-core-api $ bower install
                    

复制下面内容到 package.json:


    {
      "name": "hello-core-api",
      "devDependencies": {
      "grunt": "^0.4.5",
      "grunt-http-server": "^1.4.0",
      "http-server": "^0.8.0"
    }
  }
                    

我们将可以使用 http-server 作为小型http服务器,用于提供静态页面服务(因为浏览器禁止从file://域发出AJAX请求)。现在,安装依赖关系:


  hello-core-api $ npm install
                    

复制下面内容到 Gruntfile.js:


  module.exports = function(grunt) {
    grunt.config('http-server.dev', {
    port: 8080,
    host: "0.0.0.0",
    ext: "html",
    runInBackground: false,
  });
  grunt.loadNpmTasks('grunt-http-server');
  grunt.registerTask('server', ['http-server:dev']);
};
                    

注册了一个 grunt 任务以启动http服务器。

3. 创建 index.html

现在,我们创建 index.html 文件:


    <html>
    <head>
    <link rel="stylesheet" type="text/css" href="bower_components/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" type="text/css" href="bower_components/bootstrap/dist/css/bootstrap-theme.css">
    <link href="css/login.css" rel="stylesheet">
    <link href="css/dashboard.css" rel="stylesheet">

    <!-- Put cumulocity Javascript dependencies here such as angular, lodash etc. You can copy them from the example project. -->
    <!--<script src="bower_components/angular/angular.js"></script>-->

    <script src="bower_components/cumulocity-clients-javascript/build/main.js"></script>
    <script src="js/app.js"></script>
    <script src="js/login_ctrl.js"></script>
    <script src="js/main_ctrl.js"></script>
    <script src="js/section_ctrl.js"></script>
    </head>
    <body ng-app="helloCoreApi">
    <ng-view />
    </body>
    </html>
                    

现在,运行以下命令:


    hello-core-api $ grunt server
                    

如果从浏览器访问http://localhost:8080,应该加载一个空页面,浏览器控制台显示一堆错误信息,这是因为找不到有些Javascript文件。

  • ng-app: 从给定名字的模块引导AngularJS应用。
  • ng-view: angular-route 指令加载路由配置中定义的局部 HTML文件。

4. 创建一个 AngularJS 应用

让我们创建AngularJS应用。 在 js/app.js文件中:


    var app = angular.module('helloCoreApi', [
    'c8y.sdk',
    'ngRoute',
    'ui.bootstrap'
    ]);
                    

helloCoreApi 是有ng-app 指令的模块名。 中括号中是其他模块的依赖关系。 QuarkIoE服务定义在 c8y.core中。


   app.config([
    '$routeProvider',
    configRoutes
    ]);
    function configRoutes(
    $routeProvider
    ) {
    $routeProvider
    .when('/login', {
    templateUrl: 'login.html',
    controller: 'LoginCtrl',
    controllerAs: 'login'
  })
  .when('/', {
  templateUrl: 'main.html',
  controller: 'MainCtrl',
  controllerAs: 'main'
  })
  .when('/:section', {
  templateUrl: 'main.html',
  controller: 'MainCtrl',
  controllerAs: 'main'
  });
  }
                    

AngularJS使用类似于AMD的语法声明依赖关系。更多信息,参见AngularJS DI 指南$routeProvider 让你根据路由选择加载那个HTML文件,执行哪个控制器。例如,如果通过浏览器访问 localhost:8080/index.html/#/login,则加载login.html并执行 LoginCtrl.when('/:section') 允许URL的 :section 部分是任意值,并且可以从控制器访问此值。 controllerAs 的值很重要,因为这是HTML文件中访问控制器值的变量名(如 login.username)。


 app.config([
  'c8yCumulocityProvider',
  configCumulocity
  ]);
  function configCumulocity(
  c8yCumulocityProvider
  ) {
  c8yCumulocityProvider.setAppKey('core-application-key');
  c8yCumulocityProvider.setBaseUrl('https://my-tenant.cumulocity.com/');
}
                    

这是怎样配置 c8y.core 以设置QuarkIoE应用程序使用的应用程序key,租户名和域。

5. 创建登录页面

登录页面

让我们创建一个登录页面。不设置租户名,用户名和密码 c8y.core 不会工作;因此需要设置这些。

src/login_ctrl.js:


    angular.module('helloCoreApi').controller('LoginCtrl', [
    '$location',
    'c8yUser',
    LoginCtrl
    ]);

    function LoginCtrl(
    $location,
    c8yUser
    ) {
    c8yUser.current().then(function () {
    $location.path('/');
  });
  this.onSuccess = function () {
  $location.path('/');
  };
  }
                    

很容易吧? c8yUser.current 返回当前登录用户的一个 Promise。如果用户已经登录,会触发重定向到 /。我们定义 onSuccess 函数重定向到 /


    <div class="container">
    <form class="form-signin">
    <h2 class="form-signin-heading">Please login</h2>
    <label for="inputTenant" class="sr-only">Tenant</label>
    <input type="text" id="inputTenant" class="form-control" placeholder="Tenant" autofocus="" ng-model="login.tenant">
    <label for="inputUsername" class="sr-only">Username</label>
    <input type="text" id="inputUsername" class="form-control" placeholder="Username" required="" autofocus="" ng-model="login.username">
    <label for="inputPassword" class="sr-only">Password</label>
    <input type="password" id="inputPassword" class="form-control" placeholder="Password" required="" ng-model="login.password">
    <div class="checkbox">
    <label>
    <input type="checkbox" ng-model="login.rememberMe"> Remember me
    </label>
    </div>
    <button class="btn btn-lg btn-primary btn-block" type="submit" c8y-login
    data-tenant="login.tenant"
    data-user="login.username"
    data-password="login.password"
    data-remember-me="login.rememberMe"
    on-success="login.onSuccess()"
    >Sign in</button>
    </form>
    </div>
                    

ng-model 在AngularJS 这里有良好的文档。 c8y-login 指令在 c8y.core 模块中定义。 签名如下:


    <ANY c8y-login
    tenant="tenantName"
    user="username"
    password="password"
    remember-me="true|false"
    on-success="onSuccessCallback"
    on-failure="onFailureCallback">
    </ANY>
                    

如果访问 localhost:8080/index.html/#/login,应该可以看到登录页面。可以输入凭证并登录,但localhost:8080/#/依然是空的。

如果想在登录页面省略租户名,可以用在设置阶段用 c8yCumulocityProvider.setTenant 函数设置。

6. 创建主页面

主页面

主页面由顶层导航器,左侧导航器和一个内容区域构成。当实现设备/报警/事件 页面后,内容区域将可见,但现在,让我们专注手头的任务:

js/main_ctrl.js:


     angular.module('helloCoreApi').controller('MainCtrl', [
      '$location',
      '$routeParams',
      'c8yUser',
      MainCtrl
      ]);

      function MainCtrl(
      $location,
      $routeParams,
      c8yUser
      ) {
      c8yUser.current().catch(function () {
      $location.path('/login');
    });

    if (!$routeParams.section) {
    $location.path('/devices');
    }

    this.currentSection = $routeParams.section;
    this.sections = {
    Devices: 'devices',
    Alarms: 'alarms',
    Events: 'events'
    };
    this.filter = {};

    this.logout = function () {
    $location.path('/login');
    };
    }
                    

类似于我们在登录时所做的,如果当前用户promise失败,我们重定向到登录页面。此外,我们检查 $routeParams.section 是否存在 (回忆 when('/:section')?)。如果不存在,我们重定向到设备页面没有意义。我们将 currentSection 挂在 this上,以便可以从 main.html访问。 this.sections 是菜单标签,section名称对的key-value字典。 'this.filter' 是整个section的同步过滤器对象。this.logout用于登出回调。现在看 main.html:


    <div ng-if="c8y.user">
    <nav class="navbar navbar-inverse navbar-fixed-top">
    <div class="container-fluid">
    <div class="navbar-header" ng-init="isCollapsed = true">
    <button type="button" class="navbar-toggle collapsed" ng-click="isCollapsed = !isCollapsed" aria-expanded="false" aria-controls="navbar">
    <span class="sr-only">Toggle navigation</span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    </button>
    <a class="navbar-brand" ng-href="#/">Cumulocity</a>
    </div>
    <div id="navbar" class="navbar-collapse" collapse="isCollapsed">
    <ul class="nav navbar-nav navbar-right">
    <li><a href="" ng-click="main.logout()" c8y-logout>Logout</a></li>
    </ul>
    <p class="navbar-text navbar-right">Hello {{c8y.user.firstName}}</p>
    </div>
    </div>
    </nav>
    <div class="container-fluid">
    <div class="row">
    <div class="col-sm-3 col-md-2 sidebar">
    <ul class="nav nav-sidebar">
    <li
    ng-repeat="(sectionLabel, section) in main.sections"
    ng-class="{'active': main.currentSection === section}">
    <a href="" ng-href="#/{{section}}">{{sectionLabel}}</a>
    </li>
    </ul>
    </div>
    <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main" ng-switch="!!main.currentSection">
    <div ng-switch-when="true">
    <eg-section service="{{main.currentSection}}" filter="main.filter" refresh="main.refresh">
    <ng-include src="['sections/', main.currentSection, '.html'].join('')">
    </ng-include>
    </eg-section>
    </div>
    </div>
    </div>
    </div>
    </div>
                    

c8y-logout 指令点击后,从c8y.core移除用户和会话信息。可以和 ng-click联合使用,ng-click在登出完成后执行。 签名如下:

<
                    ANY c8y-logout>
                      </ANY>
                    

eg-section 指令将在 步骤 7创建。它根据main.currentSection的值通过表格显示设备,报警或者事件列表。使用ng-transclude,标签之间的内容将呈现在表格上。 我们使用该功能渲染过滤器列表组件。

有登录的QuarkIoE用户时c8y.user对象可用。这个对象定义在 $rootScope。想了解更多用户对象的结构,参看,c8yUser 文档

剩下的就是简单的AngularJS指令,如ng-switchng-repeat,可以通过AngularJS文档获取更多信息。

现在,我们试一下localhost:8080/index.html/#/。

7. 创建 设备/报警/事件 列表

设备页面

所有页面;即设备,报警和事件,共享通用功能,我们从创建 js/section_dir.js开始:


    angular.module('helloCoreApi').controller('SectionCtrl', [
    '$scope',
    SectionCtrl
    ]).directive('egSection', [
    egSection
    ]);

    function SectionCtrl(
    $scope
    ) {
    this.filter = $scope.filter || {};
    this.filter.pageSize = 10;
    this.service = $scope.service;
    $scope.$watch('section.refresh', function (val) {
    $scope.refresh = val;
  });
  }

  function egSection(
  ) {
  return {
  restrict: 'AE',
  templateUrl: 'section.html',
  controller: 'SectionCtrl',
  controllerAs: 'section',
  transclude: true,
  replace: true,
  scope: {
  service: '@',
  filter: '=?',
  refresh: '=?'
  }
  };
  }
                    

section.html:


    <div>
    <div ng-transclude></div>
    <p class="text-warning">Page size is {{section.filter.pageSize}} by default. See <code>pageSize</code> filter.</p>
    <table class="table">
    <h2>List</h2>
    <tr c8y-repeat="x in {{section.service}}" filter="section.filter" refresh="section.refresh">
    <td>{{x.id}}</td>
    <td>{{x.type}}</td>
    <td>{{x.text}}</td>
    <td>{{x.name}}</td>
    <td>{{x.severity}}</td>
    </tr>
    </table>
    </div>
                    

我们定义里一个指令 eg-section ,用于所有页面。 它使用 ngTransclude$watchcontroller as 语法。 它将 filter.pageSize 赋值 10。如果你熟悉 QuarkIoE REST API,你可能已经注意到我们在限制从 GET 请求返回对象的数量。

这里关键是 c8y-repeat 指令。 它的签名如下:


    <ANY
    c8y-repeat="repeat_expression"
    filter="optionalFilter"
    refresh="optionalFunction">
    ...
    </ANY>
                    

repeat_expression: someVar in * where * 可以是支持的设备中的一个。参看文档下部。

这里我们使用 controller as 语法定义路由。 refreshc8y-repeat设置,用它刷新数据。 注意,必须遵守点规则,它使用双向绑定。

对于支持的过滤器,参见相应的服务文档 resources.cumulocity.com/documentation/jssdk/latest/#/core

现在我们有了一个功能齐全的web应用程序,可以列出设备,报警和事件。

8. 实现过滤

在这一部分中,我们将实现按文本过滤设备,按严重程度过滤告警。

设备搜索
设备搜索页面

sections/devices.html 开始部分, <div ng-controller=...里添加以下内容, :


    <form ng-submit="main.filter.text = main.textFilter">
    <div class="input-group">
    <input type="text" ng-model="main.textFilter" class="form-control" placeholder="Filter with device name...">
    <span class="input-group-btn">
    <button type="submit" class="btn btn-default" type="button">Submit</button>
    </span>
    </div>
    </form>
                    

变量 main.filter.textmain.textFilter 几乎一样,只有很少不一样的地方。 过滤器变化时,c8y-repeat 刷新数据。因为我们不想在搜索框中每次输入一个字符都刷新,我们使用两个独立的变量并在 ng-submit同步。

现在再次检查localhost:8080/index.html/#/devices。

按严重程度过滤警告

按严重程度过滤警告将更加详细,我们在js/alarms_ctrl.js中先创建一个控制器:


    angular.module('helloCoreApi').controller('AlarmsCtrl', [
      AlarmsCtrl
      ]);

      function AlarmsCtrl(
      ) {
      this.severities = [
      {name: 'Critical', value: 'CRITICAL', cls: 'btn-danger'},
      {name: 'Major', value: 'MAJOR', cls: 'btn-warning'},
      {name: 'Minor', value: 'MINOR', cls: 'btn-primary'},
      {name: 'Warning', value: 'WARNING', cls: 'btn-info'}
      ];

      this.onClick = function (filter, severity) {
      if (filter.severity === severity.value) {
      filter.severity = undefined;
    } else {
    filter.severity = severity.value;
    }
    };

    this.isActive = function (filter, severity) {
    return filter.severity === severity.value;
    };
    }
                    

HTML:


    <div ng-controller="AlarmsCtrl as alarms" class="btn-group alarm-severity" role="group" aria-label="...">
      <style>
      .alarm-severity .btn:focus {
      outline: none;
    }
    </style>
    <button
    ng-repeat="severity in alarms.severities"
    class="btn {{severity.cls}}"
    ng-class="{'active': alarms.isActive(main.filter, severity)}"
    ng-click="alarms.onClick(main.filter, severity)">
    {{severity.name}}
    </button>
    </div>
                    

为了实现这种过滤,我们定义代表告警严重程度的对象数组。使用 ng-repeat 迭代是很简单的。 当单击其中一个,或者把filter.severity设置成 undefined,或者设置成实际的严重程度。因为过滤器变化时 c8y-repeat 自动刷新,这正是我们要做的。

9. 创建刷新按钮

在最后的例子中,我们不创建过滤器。由于没有过滤器,我们需要另外的方法刷新数据。下面是 sections/events.html:


    <div>
    <button class="btn btn-default pull-right" ng-click="main.refresh()" class="margin-bottom:2em">Refresh</button>
    </div>
                    

如果还不清楚,这里有个双向绑定二级链的例子。 eg-section 指令把 main.refreshsection.refresh绑定在一起。 c8y-repeat 绑定 section.refresh 到它专有的刷新函数。 在 events.html中,我们无法访问 section ,因为它 ngIncluded 在 main.html 而不是在 section.html中。

结论

我们已经使用c8y.core API(我们称之为 Smart Apps 工具包)从头开始创建了一个AngularJS应用。恭喜!

c8y-repeat支持的服务
  • 设备
  • 告警
  • 事件
  • 设备清单