大家应该都已经很熟悉 InterSystems Ensemble(一个集成和应用程序开发平台),每个人都知道 Ensemble Workflow 子系统是什么以及它对于自动化人类交互的作用。 对于那些不了解 Ensemble Workflow 的人,我将简要介绍它的功能(已经熟悉的朋友可以直接跳过这一部分并学习如何使用 Angular.js 中的 Workflow 接口)。
InterSystems Ensemble
InterSystems Ensemble 是一个集成和应用程序开发平台,旨在集成异构系统、自动化业务流程和创建新的复杂应用程序,这些应用程序通过新的业务逻辑或新的用户界面增强集成应用程序的功能:EAI、SOA、BPM、BAM 甚至 BI (感谢 InterSystems DeepSee:一种用于开发分析应用程序的内置技术)。
Ensemble 具有以下关键功能:
适配器:与应用程序、技术和数据源交互的组件。 Ensemble 提供技术和应用程序集成适配器(Web 和 REST 服务、文件、FTP、电子邮件、SQL、EDI、HL7、SAP、Siebel、1S Enterprise 等)。 您可以使用适配器 SDK 创建自己的适配器。
业务服务:将来自外部系统的数据转换为 Ensemble 消息并启动业务流程和/或业务运营的组件。
业务流程:用于编排服务和操作的可执行流程,以自动化系统和/或人员之间的交互(通过工作流子系统)。 流程要么用声明性业务流程语言描述,要么用 Caché 对象脚本实现。 通过服务和操作将与外界交互的逻辑与这种交互的具体实现分开。
业务运营:负责向外部系统发送/接收消息并将 Ensemble 消息转换为与此类系统兼容的格式的组件。
消息转换:使用声明性数据转换语言将消息从一种格式转换为另一种格式的集成组件。
业务规则:允许集成解决方案的管理员在特定决策点更改 Ensemble 业务流程的行为,而无需编写代码。
工作流管理:Ensemble Workflow 子系统提供任务分配的自动化。
业务指标:允许您收集和计算 KPI。 结合仪表板,它们用于实施业务活动监控 (BAM) 解决方案。
OK,让我们回到工作流管理,仔细看看 Ensemble Workflow 子系统的功能。
工作流管理和 Ensemble 工作流子系统
根据工作流管理联盟 (www.WfMC.org) 的定义,“工作流”是完全或部分自动化的业务流程,其中文档、信息或任务根据既定规则和程序从一个参与者传递给另一个参与者。”
工作流程的关键方面:
- 工作流的目的是涵盖工作的“片段”
- 工作流是一组程序性任务执行规则
- 工作流用户是在工作流管理系统中处理任务的人
- 工作流中的角色是一组从事特定类型任务的用户。
Ensemble 中的工作流管理子系统使您能够执行以下操作:
- 使用 Ensemble 业务流程自动化工作流程管理
- 灵活配置任务分配流程
- 通过 Ensemble 提供的特殊工作流门户使用工作流管理系统
- 组织工作流管理子系统与 Ensemble 的集成业务流程的交互
- 使用业务活动监控子系统,Ensemble 的管理和监控工具
- 轻松配置和扩展工作流子系统的功能
工作流管理自动化的最简单示例是 Ensemble HelpDesk 应用程序(下图为HelpDesk 业务流程算法的片段),它可以自动化支持人员的交互,并且是标准的 Ensemble 示例集(在 Ensdemo 空间中)的一部分。 Ensemble 接收问题报告并启动 HelpDesk 业务流程。
业务流程使用 EnsLib.Workflow.TaskRequest 类的消息向具有 Demo-Development 角色的用户发送任务,该类定义了所有可能的操作(“Fixed”或“Ignored”)以及“Comment”字段。 消息的正文还包含有关错误和报告错误的用户的信息。 在此之后,相应的任务会出现在每个具有演示开发角色的用户的工作流门户中。
最初(如果未在 TaskRequest 消息中定义),任务不与任何特定用户关联(仅与角色关联),因此用户必须通过单击相应按钮来接受它。 您也可以通过单击“推迟”按钮来拒绝任务。
完成后,您可以执行此任务允许的任何操作。 在我们的例子中,我们可以在相应字段中提供评论后单击“已修复”按钮。 HelpTask 业务流程将处理此事件并向具有 Demo-Testing 角色的用户发送一条新消息,从而表明需要测试更改。 如果单击“忽略”按钮,该任务将被标记为“不是问题”,并且其处理将停止。
从这个例子可以看出,Ensemble Workflow 是一个简单直观的系统,用于组织用户的工作流。 关于 Ensemble Workflow 子系统的更多详细信息可以在 Ensemble 手册的定义工作流部分找到。
Ensemble Workflow 子系统的功能可以轻松扩展并集成到基于 InterSystems Ensemble 的外部复合应用程序中。 作为一个例子,让我们看一下在使用 Angular.js + REST API(由 Eduard Lebedyuk 编写)开发的外部复合应用程序中实现 Ensemble Workflow 的用户界面。
Angular.js 中的Ensemble工作流接口
要使 Workflow 界面与 Angular.js 一起使用,您需要在服务器上安装以下 Ensemble 应用程序:
安装过程在指定存储库的自述文件中进行了描述。
目前(原帖里说),该应用程序具有 Ensemble Workflow 的所有必要功能:显示任务列表、附加字段和操作、排序、任务中的全文搜索。 用户可以接受/拒绝任务。 有关任务的详细信息显示在模式窗口中。 (实现只是概念证明,它还有很大的改进空间。它还以一种不得在生产中使用的方式使用 BasicAuth。目前我们已经有一个更复杂的例子)。
应用程序如下所示:
UI 使用以下库和框架:Angular.js、Twitter Bootstrap 以及 FontAwesome 图标字体。
您可以查看我们的测试服务器上运行的 HelpDesk 应用程序的用户界面。 账号:dev,密码:123
对于那些对源代码感兴趣的朋友们
以下这个小应用程序的结构:
该应用程序有 4 个 Angular 服务(RESTSrvc、SessionSrvc、UtilSrvc 和 WorklistSrvc)、3 个控制器(MainCtrl、TaskCtrl、TasksGridCtrl)、一个主页(index.csp)和 2 个模板(task.csp 和 tasks.csp)。
RESTSrvc 服务只有一个方法 getPromise,它是 $http Angular.js 服务的包装器。 RESTSrvc 的唯一目的是向服务器发送 HTTP 请求并返回这些请求的 Promise 对象。 其他服务使用 RESTSrvc 来发出请求,它们的分离本质上是一种功能性的(它其实可以写得更好)。点击下栏查看代码:
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;
}
}
};
RESTSrvc.$inject = ['$http', '$q']; servicesModule.factory('RESTSrvc', RESTSrvc);
SessionSrvc :包含一个负责关闭会话的方法。 此应用程序中的身份验证是使用基本访问身份验证 (http://en.wikipedia.org/wiki/Basic_access_authentication) 实现的,因此不需要单独的身份验证方法,因为每个请求的标头中都有一个授权令牌。点击下栏查看代码:
// Session service
function SessionSrvc(RESTSrvc) {
return {
// save worklist object
logout:
function (baseAuthToken) {
return RESTSrvc.getPromise({
method: 'GET', url: RESTWebApp.appName + '/logout',
headers: { 'Authorization': baseAuthToken }
});
}
}
};
SessionSrvc.$inject = ['RESTSrvc'];
servicesModule.factory('SessionSrvc', SessionSrvc);
UtilSrvc :包含辅助方法,例如按名称获取 cookie 值、按名称获取对象属性。点击下栏查看代码:
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;
}
}
};
UtilSrvc.$inject = ['$cookies'];
servicesModule.factory('UtilSrvc', UtilSrvc);
WorklistSrvc :负责与任务列表数据相关的请求。点击下栏查看代码:
// 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 } });
}
}
};
WorklistSrvc.$inject = ['RESTSrvc'];
servicesModule.factory('WorklistSrvc', WorklistSrvc);
MainCtrl :负责用户身份验证的应用程序的主控制器。点击下栏查看代码:
// 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 });
});
};
}
MainCtrl.$inject = ['$scope', '$location', '$cookies', 'WorklistSrvc', 'SessionSrvc', 'UtilSrvc'];
controllersModule.controller('MainCtrl', MainCtrl);
TasksGridCtrl :一个控制器,负责任务列表和与之关联的操作。 它初始化任务列表表,包含加载任务列表和具体任务的方法,以及处理用户动作的方法(按键、表格排序、行选择、过滤)。点击下栏查看代码:
// 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();
}
TasksGridCtrl.$inject = ['$scope', '$window', '$modal', '$cookies', 'WorklistSrvc'];
controllersModule.controller('TasksGridCtrl', TasksGridCtrl);
TaskCtrl :模式窗口的控制器,包含有关任务的详细信息。 形成字段和用户操作的列表,还处理模态窗口中的按钮单击。点击下栏查看代码:
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 :包含所有应用程序模块的文件。点击下栏查看代码:
/*
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' });
}]);
<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 :任务列表模板。点击下栏查看代码:
<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 — 模态窗口模板。点击下栏查看代码:
<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>
<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>
本文翻译自 Habrahabr InterSystems 博客(俄语)
<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>
<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>
/*
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' });
}]);
欢迎下载Ensembleworkflow 小程序