AngularのEnsembleワークフローのUI





InterSystems Ensembleアプリケーション統合および開発プラットフォームに精通している人は、Ensembleワークフローサブシステムとは何か、それが人々の対話の自動化にどのように役立つかを知っています。 Ensemble(および/またはワークフロー)に慣れていない人のために、その機能について簡単に説明します(他の人はこの部分をスキップして、Angular.jsのワークフローユーザーインターフェイスの使用方法を見つけることができます)。



インターシステムズアンサンブル


統合とアプリケーション開発のプラットフォームInterSystems Ensembleは、異種システムを統合し、ビジネスプロセスを自動化し、統合アプリケーションの機能を新しいビジネスロジックまたはユーザーインターフェイスで補完する新しい複合アプリケーションを作成するように設計されています。 Ensembleは、EAI、SOA、BPM、BAM、さらにはBIまでの問題に対するソリューションを提供します(InterSystems DeepSeeの分析アプリケーションを開発するための組み込みテクノロジーによる)。



Ensembleの主なコンポーネントは次のとおりです。







ワークフロー管理に戻り、Ensembleワークフローサブシステムの機能をさらに詳しく検討してみましょう。



ワークフロー管理とアンサンブルワークフロー


Workflow Management Coalition(www.WfMC.org)の定義によれば、「ワークフローとは、一連の手続きルールに従って、ドキュメント、情報、またはタスクが参加者間で転送されるビジネスプロセスの全体または一部の自動化です。 」



主要なワークフロー要素:





Ensembleワークフロー管理サブシステムを使用すると、次のことができます。





ワークフロー管理の自動化の最も簡単な例は、Ensembleサンプル配信の一部であり、Ensdemoエリアにあるカスタマーサポートチームワークを自動化するEnsemble HelpDeskアプリケーションです。 Ensembleは問題レポートを受け取り、HelpDeskビジネスプロセスを起動します。



HelpDeskビジネスプロセスアルゴリズムフラグメント



ビジネスプロセスは、可能なアクション(「固定」または「無視」)と「コメント」フィールドを定義するEnsLib.Workflow.TaskRequestクラスのメッセージを使用して、Demo-Developmentロールのユーザーにタスクを送信します。 メッセージの本文には、エラーに関する情報と問題を報告するユーザーも含まれます。 その後、デモ開発ロールのユーザーのワークフローポータルに対応するタスクが表示されます。





最初は(これがTaskRequestメッセージで指定されていない場合)、タスクはどのユーザーにも関連付けられていません(ただし、ロールにのみ関連付けられている)ため、ユーザーは対応するボタンをクリックして承認する必要があります。 また、いつでも「Give up」ボタンをクリックしてタスクを中止できます。



その後、特定のタスクで使用可能なアクションを実行できます。 この場合、対応するフィールドにコメントを指定した後、「固定」ボタンをクリックします。 HelpDeskビジネスプロセスは、このイベントを処理し、Demo-Testingロールのユーザーに新しいメッセージを送信して、行われた修正をテストする必要があることを通知します。 「無視」ボタンをクリックすると、タスクは単に「問題なし」としてマークされ、処理は終了します。



この例からわかるように、Ensemble Workflowは、ユーザーワークフローを整理するためのシンプルで直感的なシステムです。 Ensemble Workflowサブシステムの詳細については、「 ワークフロー定義」セクションのEnsembleのドキュメントを参照してください。



Ensemble Workflowサブシステムの機能は、InterSystems Ensembleの外部複合アプリケーションに簡単に拡張および統合できます。 例として、Angular.js + REST APIで開発された外部複合アプリケーションでのEnsemble Workflowユーザーインターフェイス機能の実装を検討してください。



Angular.jsのEnsembleワークフローインターフェイス。


ワークフローユーザーインターフェイスをAngular.jsで機能させるには、Ensembleサーバーに次のアプリケーションをインストールする必要があります。



インストールプロセスは、指定されたリポジトリのReadmeに記載されています。



現時点では、アプリケーションはEnsemble Workflowのすべての基本機能を実装しています。タスクのリストの表示、追加のフィールドとアクション、並べ替え、タスクの全文検索。 ユーザーはタスクを承認/拒否でき、タスクに関する詳細情報がモーダルウィンドウに表示されます。



また、近い将来、アプリケーションに領域を変更する機能を追加する予定です(現時点では、アプリケーションはインストールされている領域でのみ動作します)。



執筆時点でのアプリケーションは次のとおりです。







必要に応じて、その後インターフェースを変更するために、Twitter Bootstrapが使用されました。



技術的な実装の詳細


UIでは、Angular.js jsフレームワーク、Twitter Bootstrap cssフレームワーク、jQuery jsライブラリ、およびFontAwesomeアイコンフォントのライブラリとフレームワークが使用されます。



アプリケーションには、4つのAngularサービス(RESTSrvc、SessionSrvc、UtilSrvc、およびWorklistSrvc)、3つのコントローラー(MainCtrl、TaskCtrl、TasksGridCtrl)、メインページ(index.csp)、2つのテンプレート(task.cspおよびtasks.csp)があります。



RESTSrvcサービスにはgetPromiseメソッドが1つしかなく、$ http Angular.jsサービスのラッパーです。 RESTSrvcの唯一の目的は、HTTP要求をサーバーに送信し、これらの要求のpromiseオブジェクトを返すことです。 残りのサービスはRESTSrvcを使用して要求を行い、それらの分離は基本的に機能します。

RESTSrvc.js
'use strict'; function RESTSrvc($http, $q) { return { getPromise: function(config) { var deferred = $q.defer(); $http(config). success(function(data, status, headers, config) { deferred.resolve(data); }). error(function(data, status, headers, config) { deferred.reject(data, status, headers, config); }); return deferred.promise; } } }; // resolving minification problems RESTSrvc.$inject = ['$http', '$q']; servicesModule.factory('RESTSrvc', RESTSrvc);
      
      







SessionSrvc-セッションを閉じるための1つのメソッドのみが含まれます。 アプリケーションでの認証は、基本アクセス認証(http://en.wikipedia.org/wiki/Basic_access_authentication)を使用して実行されます。したがって、各リクエストのヘッダーに認証トークンがあるため、認証方法は必要ありません。

SessionSrvc.js
 'use strict'; // Session service function SessionSrvc(RESTSrvc) { return { // save worklist object logout: function(baseAuthToken) { return RESTSrvc.getPromise( {method: 'GET', url: RESTWebApp.appName + '/logout', headers: {'Authorization' : baseAuthToken} }); } } }; // resolving minification problems SessionSrvc.$inject = ['RESTSrvc']; servicesModule.factory('SessionSrvc', SessionSrvc);
      
      







UtilSrvc-名前によるCookie値の取得、名前によるオブジェクトプロパティの値の取得などのヘルパーメソッドが含まれています。

UtilSrvc.js
 'use strict'; // Utils service function UtilSrvc($cookies) { return { // get cookie by name readCookie: function(name) { return $cookies[name]; }, // Function to get value of property of the object by name // Example: // var obj = {car: {body: {company: {name: 'Mazda'}}}}; // getPropertyValue(obj, 'car.body.company.name') getPropertyValue: function(item, propertyStr) { var value = item; try { var properties = propertyStr.split('.'); for (var i = 0; i < properties.length; i++) { value = value[properties[i]]; if (value !== Object(value)) break; } } catch(ex) { console.log('Something goes wrong :/'); } return value == undefined ? '' : value; } } }; // resolving minification problems UtilSrvc.$inject = ['$cookies']; servicesModule.factory('UtilSrvc', UtilSrvc);
      
      







WorklistSrvcは、タスクリストデータに関連するクエリを実行します。

WorklistSrvc.js
 'use strict'; // Worklist service function WorklistSrvc(RESTSrvc) { return { // save worklist object save: function(worklist, baseAuthToken) { return RESTSrvc.getPromise( {method: 'POST', url: RESTWebApp.appName + '/tasks/' + worklist._id, data: worklist, headers: {'Authorization' : baseAuthToken} }); }, // get worklist by id get: function(id, baseAuthToken) { return RESTSrvc.getPromise( {method: 'GET', url: RESTWebApp.appName + '/tasks/' + id,headers: {'Authorization' : baseAuthToken} }); }, // get all worklists for current user getAll: function(baseAuthToken) { return RESTSrvc.getPromise( {method: 'GET', url: RESTWebApp.appName + '/tasks', headers: {'Authorization' : baseAuthToken} }); } } }; // resolving minification problems WorklistSrvc.$inject = ['RESTSrvc']; servicesModule.factory('WorklistSrvc', WorklistSrvc);
      
      







MainCtrl-メインアプリケーションコントローラーは、ユーザー認証を担当します。

MainCtrl.js
 'use strict'; // Main controller // Controls the authentication. Loads all the worklists for user. function MainCtrl($scope, $location, $cookies, WorklistSrvc, SessionSrvc, UtilSrvc) { $scope.page = {}; $scope.page.alerts = []; $scope.utils = UtilSrvc; $scope.page.loading = false; $scope.page.loginState = $cookies['Token'] ? 1 : 0; $scope.page.authToken = $cookies['Token']; $scope.page.closeAlert = function(index) { if ($scope.page.alerts.length) { $('.alert:nth-child('+(index+1)+')').animate({opacity: 0, top: "-=150" }, 400, function() { $scope.page.alerts.splice(index, 1); $scope.$apply(); }); } }; $scope.page.addAlert = function(alert) { $scope.page.alerts.push(alert); if ($scope.page.alerts.length > 5) { $scope.page.closeAlert(0); } }; /* Authentication section */ $scope.page.makeBaseAuth = function(user, password) { var token = user + ':' + password; var hash = Base64.encode(token); return "Basic " + hash; } // login $scope.page.doLogin = function(login, password) { var authToken = $scope.page.makeBaseAuth(login, password); $scope.page.loading = true; WorklistSrvc.getAll(authToken).then( function(data) { $scope.page.alerts = []; $scope.page.loginState = 1; $scope.page.authToken = authToken; // set cookie to restore loginState after page reload $cookies['User'] = login.toLowerCase(); $cookies['Token'] = $scope.page.authToken; // refresh the data on page $scope.page.loadSuccess(data); }, function(data, status, headers, config) { if (data.Error) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); } else { $scope.page.addAlert( {type: 'danger', msg: "Login unsuccessful"} ); } }) .then(function () { $scope.page.loading = false; }) }; // logout $scope.page.doExit = function() { SessionSrvc.logout($scope.page.authToken).then( function(data) { $scope.page.loginState = 0; $scope.page.grid.items = null; $scope.page.loading = false; // clear cookies delete $cookies['User']; delete $cookies['Token']; document.cookie = "CacheBrowserId" + "=; Path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; document.cookie = "CSPSESSIONID" + "=; Path=" + RESTWebApp.appName + "; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; document.cookie = "CSPWSERVERID" + "=; Path=" + RESTWebApp.appName + "; expires=Thu, 01 Jan 1970 00:00:01 GMT;"; }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); }); }; } // resolving minification problems MainCtrl.$inject = ['$scope', '$location', '$cookies', 'WorklistSrvc', 'SessionSrvc', 'UtilSrvc']; controllersModule.controller('MainCtrl', MainCtrl);
      
      







TasksGridCtrl-タスクとアクションのリストのテーブルを管理するコントローラー。 タスクリストテーブルを初期化し、タスクのリストと特定のタスクをロードするためのメソッドと、ユーザーアクションを処理するためのメソッド(ボタンを押す、テーブルを並べ替える、テーブル行を強調表示、フィルタリング)を含みます。

TasksGridCtrl.js
 'use strict'; // TasksGrid controller // dependency injection function TasksGridCtrl($scope, $window, $modal, $cookies, WorklistSrvc) { // Initialize grid. // grid data: // grid title, css grid class, column names $scope.page.grid = { caption: 'Inbox Tasks', cssClass:'table table-condensed table-bordered table-hover', columns: [{name: '', property: 'New', align: 'center'}, {name: 'Priority', property: 'Priority'}, {name: 'Subject', property: 'Subject'}, {name: 'Message', property: 'Message'}, {name: 'Role', property: 'RoleName'}, {name: 'Assigned To', property: 'AssignedTo'}, {name: 'Time Created', property: 'TimeCreated'}, {name: 'Age', property: 'Age'}] }; // data initialization for Worklist $scope.page.dataInit = function() { if ($scope.page.loginState) { $scope.page.loadTasks(); } }; $scope.page.loadSuccess = function(data) { $scope.page.grid.items = data.children; // if we get data for other user - logout if (!$scope.page.checkUserValidity()) { $scope.page.doExit(); } var date = new Date(); var hours = (date.getHours() > 9) ? date.getHours() : '0' + date.getHours(); var minutes = (date.getMinutes() > 9) ? date.getMinutes() : '0' + date.getMinutes(); var secs = (date.getSeconds() > 9) ? date.getSeconds() : '0' + date.getSeconds(); $('#updateTime').animate({ opacity : 0 }, 100, function() { $('#updateTime').animate({ opacity : 1 }, 1000);} ); $scope.page.grid.updateTime = ' [Last Update: ' + hours; $scope.page.grid.updateTime += ':' + minutes + ':' + secs + ']'; }; // all user's tasks loading $scope.page.loadTasks = function() { $scope.page.loading = true; WorklistSrvc.getAll($scope.page.authToken).then( function(data) { $scope.page.loadSuccess(data); }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); }) .then(function () { $scope.page.loading = false; }) }; // load task (worklist) by id $scope.page.loadTask = function(id) { WorklistSrvc.get(id, $scope.page.authToken).then( function(data) { $scope.page.task = data; }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); }); }; // 'Accept' button handler. // Send worklist object with '$Accept' action to server. $scope.page.accept = function(id) { // nothing to do, if no id if (!id) return; // get full worklist, set action and submit worklist. WorklistSrvc.get(id).then( function(data) { data.Task["%Action"] = "$Accept"; $scope.page.submit(data); }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); }); }; // 'Yield' button handler. // Send worklist object with '$Relinquish' action to server. $scope.page.yield = function(id) { // nothing to do, if no id if (!id) return; // get full worklist, set action and submit worklist. WorklistSrvc.get(id).then( function(data) { data.Task["%Action"] = "$Relinquish"; $scope.page.submit(data); }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); }); }; // submit the worklist object $scope.page.submit = function(worklist) { // send object to server. If ok, refresh data on page. WorklistSrvc.save(worklist, $scope.page.authToken).then( function(data) { $scope.page.dataInit(); }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); } ); }; /* table section */ // sorting table $scope.page.sort = function(property, isUp) { $scope.page.predicate = property; $scope.page.isUp = !isUp; // change sorting icon $scope.page.sortIcon = 'fa fa-sort-' + ($scope.page.isUp ? 'up':'down') + ' pull-right'; }; // selecting row in table $scope.page.select = function(item) { if ($scope.page.grid.selected) { $scope.page.grid.selected.rowCss = ''; if ($scope.page.grid.selected == item) { $scope.page.grid.selected = null; return; } } $scope.page.grid.selected = item; // change css class to highlight the row $scope.page.grid.selected.rowCss = 'info'; }; // count currently displayed tasks $scope.page.totalCnt = function() { return $window.document.getElementById('tasksTable').getElementsByTagName('TR').length - 2; }; // if AssignedTo matches with current user - return 'true' $scope.page.isAssigned = function(selected) { if (selected) { if (selected.AssignedTo.toLowerCase() === $cookies['User'].toLowerCase()) return true; } return false; }; // watching for changes in 'Search' input // if there is change, reset the selection. $scope.$watch('query', function() { if ($scope.page.grid.selected) { $scope.page.select($scope.page.grid.selected); } }); /* modal window open */ $scope.page.modalOpen = function (size, id) { // if no id - nothing to do if (!id) return; // obtainig the full object by id. If ok - open modal. WorklistSrvc.get(id).then( function(data) { // see http://angular-ui.github.io/bootstrap/ for more options var modalInstance = $modal.open({ templateUrl: 'partials/task.csp', controller: 'TaskCtrl', size: size, backdrop: true, resolve: { task : function() { return data; }, submit: function() { return $scope.page.submit } } }); // onResult modalInstance.result.then( function (reason) { if (reason === 'save') { $scope.page.addAlert( {type: 'success', msg: 'Task saved'} ); } }, function () {}); }, function(data, status, headers, config) { $scope.page.addAlert( {type: 'danger', msg: data.Error} ); }); }; /* User's validity checking. */ // If we get the data for other user, logout immediately $scope.page.checkUserValidity = function() { var user = $cookies['User']; for (var i = 0; i < $scope.page.grid.items.length; i++) { if ($scope.page.grid.items[i].AssignedTo && (user.toLowerCase() !== $scope.page.grid.items[i].AssignedTo.toLowerCase())) { return false; } else if ($scope.page.grid.items[i].AssignedTo && (user.toLowerCase() == $scope.page.grid.items[i].AssignedTo.toLowerCase())) { return true; } } return true; }; // Check user's validity every 10 minutes. setInterval(function() { $scope.page.dataInit() }, 600000); /* Initialize */ // sort table (by Age, asc) // to change sorting column change 'columns[<index>]' $scope.page.sort($scope.page.grid.columns[7].property, true); $scope.page.dataInit(); } // resolving minification problems TasksGridCtrl.$inject = ['$scope', '$window', '$modal', '$cookies', 'WorklistSrvc']; controllersModule.controller('TasksGridCtrl', TasksGridCtrl);
      
      







TaskCtrl-タスクに関する詳細情報を含むモーダルウィンドウコントローラー。 フィールドとユーザーアクションのリストを生成し、モーダルウィンドウのボタン押下も処理します。

TaskCtrl.js
 'use strict'; // Task controller // dependency injection function TaskCtrl($scope, $routeParams, $location, $modalInstance, WorklistSrvc, task, submit) { $scope.page = { task:{} }; $scope.page.task = task; $scope.page.actions = ""; $scope.page.formFields = ""; $scope.page.formValues = task.Task['%FormValues']; if (task.Task['%TaskStatus'].Request['%Actions']) { $scope.page.actions = task.Task['%TaskStatus'].Request['%Actions'].split(','); } if (task.Task['%TaskStatus'].Request['%FormFields']) { $scope.page.formFields = task.Task['%TaskStatus'].Request['%FormFields'].split(','); } // dismiss modal $scope.page.cancel = function () { $modalInstance.dismiss('cancel'); }; // perform a specified action $scope.page.doAction = function(action) { $scope.page.task.Task["%Action"] = action; $scope.page.task.Task['%FormValues'] = $scope.page.formValues; submit($scope.page.task); $modalInstance.close(action); } } // resolving minification problems TaskCtrl.$inject = ['$scope', '$routeParams', '$location', '$modalInstance', 'WorklistSrvc', 'task', 'submit']; controllersModule.controller('TaskCtrl', TaskCtrl);
      
      







app.js-すべてのアプリケーションモジュールを含むファイル。

app.js
 'use strict'; /* Adding routes(when). [route], {[template path for ng-view], [controller for this template]} otherwise Set default route. $routeParams.id - :id parameter. */ var servicesModule = angular.module('servicesModule',[]); var controllersModule = angular.module('controllersModule', []); var app = angular.module('app', ['ngRoute', 'ngCookies', 'ui.bootstrap', 'servicesModule', 'controllersModule']); app.config([ '$routeProvider', function( $routeProvider ) { $routeProvider.when( '/tasks', {templateUrl: 'partials/tasks.csp'} ); $routeProvider.when( '/tasks/:id', {templateUrl: 'partials/task.csp', controller: 'TaskCtrl'} ); $routeProvider.otherwise( {redirectTo: '/tasks'} ); }]);
      
      







index.cspは、アプリケーションのメインページです。

index.csp
 <!doctype html> <html> <head> <title>Ensemble Workflow</title> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <!-- CSS Initialization --> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/font-awesome.min.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css"> <link rel="stylesheet" type="text/css" href="css/custom.css"> <script language="javascript"> // REST web-app name, global variable var RESTWebApp = {appName: '#($GET(^Settings("WF", "WebAppName")))#'}; </script> </head> <body ng-app="app" ng-controller="MainCtrl"> <nav class="navbar navbar-default navbar-fixed-top"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="#">Ensemble Workflow</a> </div> <div class="navbar-left"> <button ng-cloak ng-disabled="page.loginState != 1 || page.loading" type="button" class="btn btn-default navbar-btn" ng-click="page.dataInit();">Refresh Worklist</button> </div> <div class="navbar-left"> <form role="search" class="navbar-form"> <div class="form-group form-inline"> <label for="search" class="sr-only">Search</label> <input ng-cloak ng-disabled="page.loginState != 1" type="text" class="form-control" placeholder="Search" id="search" ng-model="query"> </div> </form> </div> <div class="navbar-right"> <form role="form" class="navbar-form form-inline" ng-show="page.loginState != 1" ng-model="user" ng-submit="page.doLogin(user.Login, user.PasswordSetter); user='';" ng-cloak> <div class="form-group"> <input class="form-control uc-inline" ng-model="user.Login" placeholder="Username" ng-disabled="page.loading"> <input type="password" class="form-control uc-inline" ng-model="user.PasswordSetter" placeholder="Password" ng-disabled="page.loading"> <button type="submit" class="btn btn-default" ng-disabled="page.loading">Sign In</button> </div> </form> </div> <button ng-show="page.loginState == 1" type="button" ng-click="page.doExit();" class="btn navbar-btn btn-default pull-right" ng-cloak>Logout, <span class="label label-info" ng-bind="utils.readCookie('User')"></span> </button> </div> </nav> <div class="container-fluid"> <div style="height: 20px;"> <div ng-show="page.loading" class="progress-bar progress-bar-striped progress-condensed active" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%" ng-cloak> Loading </div> </div> <!-- Alerts --> <div ng-controller="AlertController" ng-cloak> <alert title="Click to dismiss" ng-repeat="alert in page.alerts" type="{{alert.type}}" ng-click="page.closeAlert($index, alert)">{{alert.msg}}</alert> </div> <div ng-show="page.loginState != 1" class="attention" ng-cloak> <p>Please, Log In first.</p> </div> <!-- Loading template --> <div ng-view> </div> </div> </div> <!-- Hooking scripts --> <script language="javascript" src="libs/angular.min.js"></script> <script language="javascript" src="libs/angular-route.min.js"></script> <script language="javascript" src="libs/angular-cookies.min.js"></script> <script language="javascript" src="libs/ui-bootstrap-custom-tpls-0.12.0.min.js"></script> <script language="javascript" src="libs/base64.js"></script> <script language="javascript" src="js/app.js"></script> <script language="javascript" src="js/services/RESTSrvc.js"></script> <script language="javascript" src="js/services/WorklistSrvc.js"></script> <script language="javascript" src="js/services/SessionSrvc.js"></script> <script language="javascript" src="js/services/UtilSrvc.js"></script> <script language="javascript" src="js/controllers/MainCtrl.js"></script> <script language="javascript" src="js/controllers/TaskCtrl.js"></script> <script language="javascript" src="js/controllers/TasksGridCtrl.js"></script> <script language="javascript" src="libs/jquery-1.11.2.min.js"></script> <script language="javascript" src="libs/bootstrap.min.js"></script> </body> </html>
      
      







tasks.csp-タスクリストテーブルテンプレート。

tasks.csp
 <div class="row-fluid"> <div class="span1"> </div> <div ng-hide="page.loginState != 1 || (page.loading && !page.totalCnt())" ng-controller="TasksGridCtrl"> <div class="panel panel-default top-buffer"> <table class="table-tasks" ng-class="page.grid.cssClass" id="tasksTable"> <caption class="text-left"> <b ng-bind="page.grid.caption"></b><b id="updateTime" ng-bind="page.grid.updateTime"></b> </caption> <thead style="cursor: pointer; vertical-align: middle;"> <tr> <th class="text-center">#</th> <!-- In the cycle prints the name of the column, specify for each column click handler and the icon (sorting) --> <th ng-repeat="column in page.grid.columns" class="text-center" ng-click="page.sort(column.property, page.isUp)"> <span ng-bind="column.name" style="padding-right: 4px;"></span> <i style="margin-top: 3px;" ng-class="page.sortIcon" ng-show="column.property == page.predicate"></i> <i style="color: #ccc; margin-top: 3px;" class="fa fa-sort pull-right" ng-show="column.property != page.predicate"></i> </th> <th class="text-center">Action</th> </tr> </thead> <tfoot> <tr> <!-- Control buttons and messages --> <td colspan="{{page.grid.columns.length + 2}}"> <p ng-hide="page.grid.items.length">There is no task(s) for current user.</p> <span ng-show="page.grid.items.length"> Showing {{page.totalCnt()}} of {{page.grid.items.length}} task(s). </span> </td> </tr> </tfoot> <tbody style="cursor: default;"> <!-- In the cycle prints the table rows (sort by specified column) --> <tr ng-repeat="item in page.grid.items | orderBy:page.predicate:page.isUp | filter:query" ng-class="item.rowCss" > <td ng-bind="$index + 1" class="text-right"></td> <!-- In the cycle prints the table cells to each row --> <td ng-repeat="column in page.grid.columns" style="text-align: {{column.align}};" ng-click="page.select(item)"> <span class="label label-info" ng-show="$first && item.New">New</span> <span ng-hide="$first" ng-bind="utils.getPropertyValue(item, column.property)"></span> </td> <td class="text-center"> <div title="Accept task" class="button button-success fa fa-plus-circle" ng-click="page.accept(item.ID)" ng-show="!page.isAssigned(item)"></div> <div title="Details" class="button button-info fa fa-search" ng-click="page.modalOpen('lg', item.ID)" ng-show="page.isAssigned(item)"></div> <div title="Yield task" class="button button-danger fa fa-minus-circle" ng-click="page.yield(item.ID)" ng-show="page.isAssigned(item)"></div> </td> </tr> </tbody> </table> </div> </div> <div class="span1"> </div> </div> <br>
      
      







task.csp-モーダルウィンドウテンプレート。

task.csp
  <div class="modal-header"> <h3 class="modal-title">Task description</h3> </div> <div class="modal-body"> <div class="container-fluid"> <div class="row top-buffer"> <div class="col-xs-12 col-md-6"> <div class="form-group"> <label for="subject">Subject</label> <input id="subject" type="text" class="form-control task-info-input" ng-model="page.task.Task['%TaskStatus'].Request['%Subject'];" readonly> </div> </div> <div class="col-md-6"> <div class="form-group"> <label for="timeCreated">Time created</label> <input id="timeCreated" type="text" class="form-control task-info-input" ng-model="page.task.Task['%TaskStatus'].TimeCreated;" readonly> </div> </div> </div> <div class="row"> <div class="col-md-12"> <div class="form-group"> <label for="message">Message</label> <textarea id="message" class="form-control task-info-input" ng-model="page.task.Task['%TaskStatus'].Request['%Message'];" rows="3" readonly></textarea> </div> </div> </div> <div class="row"> <div class="col-md-6"> <div class="form-group"> <label for="role">Role</label> <input id="role" type="text" class="form-control task-info-input" ng-model="page.task.Task['%TaskStatus'].Role.Name;" readonly> </div> </div> <div class="col-md-3"> <div class="form-group"> <label for="assignedTo">Assigned to</label> <input id="assignedTo" type="text" class="form-control task-info-input" ng-model="page.task.Task['%TaskStatus'].AssignedTo;" readonly> </div> </div> <div class="col-md-3"> <div class="form-group"> <label for="priority">Priority</label> <input id="priority" type="text" class="form-control task-info-input" ng-model="page.task.Task['%Priority'];" readonly> </div> </div> </div> <div class="row" ng-show="page.formFields"> <div class="delimeter col-md-6 el-centered"> </div> </div> <div class="row" ng-repeat="formField in page.formFields"> <div class="col-md-12"> <div class="form-group"> <label for="form{{$index}}" ng-bind="formField"></label> <input id="form{{$index}}" type="text" class="form-control task-info-input" ng-model="page.formValues[formField]"> </div> </div> </div> </div> </div> <div class="modal-footer"> <button ng-repeat="action in page.actions" class="btn btn-primary top-buffer" ng-click="page.doAction(action)" ng-bind="action"></button> <button class="btn btn-success top-buffer" ng-click="page.doAction('$Save')">Save</button> <button class="btn btn-warning top-buffer" ng-click="page.cancel()">Cancel</button> </div>
      
      







また、REST APIをUIに使用することを禁止する人はいません。特に、非常に単純であるためです。

REST APIのURLをマップします
 <Routes> <Route Url="/logout" Method="GET" Call="Logout"/> <Route Url="/tasks" Method="GET" Call="GetTasks"/> <Route Url="/tasks/:id" Method="GET" Call="GetTask"/> <Route Url="/tasks/:id" Method="POST" Call="PostTask"/> <Route Url="/test" Method="GET" Call="Test"/> </Routes>
      
      







HelpDeskアプリケーションを実行するテストサーバーでユーザーインターフェイス試すことができます。 ログイン:dev /パス:123



All Articles