⬆️ ⬇️

Ensemble Workflow UI on Angular





Those familiar with the InterSystems Ensemble platform for integration and application development know what the Ensemble Workflow subsystem is and how it can be useful for automating human interaction. For those who are not familiar with Ensemble (and / or Workflow), I will briefly describe its capabilities (the rest can skip this part and find out how they can use the Workflow user interface on Angular.js).



InterSystems Ensemble


Platform for integration and application development InterSystems Ensemble is designed to integrate disparate systems, automate business processes and create new composite applications that complement the functionality of integrated applications with new business logic or user interface. Ensemble provides a solution to the problems: EAI, SOA, BPM, BAM and even BI (due to the embedded technology for the development of InterSystems DeepSee analytical applications).



The following main components exist in Ensemble:





')

Let's return to workflow management and consider the functionality of the Ensemble Workflow subsystem in more detail.



Workflow management and Ensemble Workflow subsystem


According to the definition of the Workflow Management Coalition (www.WfMC.org), “Workflow is the automation of a business process, in whole or in part, in which documents, information or tasks are transferred from one participant to another, in accordance with a set of procedural rules . ”



Key elements of Workflow:





Ensemble's workflow subsystem allows you to:





The simplest example of automating workflow management is the Ensemble HelpDesk application for automating the interaction of support staff, which is included in the standard delivery of Ensemble examples and is located in the Ensdemo area. Ensemble receives the problem report and starts the HelpDesk business process.



Fragment of the HelpDesk business process algorithm



The business process sends a task to users of the Demo-Development role using an EnsLib.Workflow.TaskRequest class message, which defines possible actions (“Fixed” or “Ignored”), as well as the “Comment” field. The body of the message also includes information about the error and the user who reported the problem. After that, the corresponding task appears in the Workflow portal of any user of the Demo-Development role.





Initially (if this is not specified in the TaskRequest message), the task is not associated with any user (but only with the role), so the user needs to accept it by pressing the appropriate button. Also, at any time, you can cancel the task by clicking the “Give” button.



After that, you can perform actions available for a specific task. In our case, we can click the “Fixed” button, having previously specified a comment in the corresponding field. The HelpDesk business process will handle this event and send a new message to users of the Demo-Testing role, signaling the need to test the corrections made. If you click the “Ignored” button, the task will simply be marked as “Not a problem” and the processing will end.



As you can see from the example, Ensemble Workflow is a simple and intuitive system for organizing user workflows. For more information about the Ensemble Workflow subsystem, see the Ensemble documentation in the Defining Workflow section.



The functionality of the Ensemble Workflow subsystem can be easily extended and integrated into an external composite application on InterSystems Ensemble. As an example, consider the implementation of the Ensemble Workflow user interface functionality in an external composite application developed on Angular.js + REST API.



Ensemble Workflow interface on Angular.js.


For the Workflow user interface to work on Angular.js, you need to install applications on the Ensemble server:



The installation process is described in the Readme of these repositories.



At the moment, the application implements all the basic Ensemble Workflow functionality: displaying a list of tasks, additional fields and actions, sorting, full-text search by task. The user can accept / reject tasks, detailed information about the task is displayed in a modal window.



Also in the near future, plans to add to the application the ability to change the area (at the moment the application works only in the area in which it is installed).



At the time of this writing, the application is as follows:







For the subsequent modification of the interface, if necessary, Twitter Bootstrap was used.



Some technical implementation details


The UI uses the following libraries and frameworks: Angular.js js-framework, Twitter Bootstrap css-framework, jQuery js-library, and also FontAwesome icon fonts.



The application has 4 Angular services (RESTSrvc, SessionSrvc, UtilSrvc and WorklistSrvc), 3 controllers (MainCtrl, TaskCtrl, TasksGridCtrl), the main page (index.csp) and 2 templates (task.csp and tasks.csp).



The RESTSrvc service has only one getPromise method and is a wrapper around the $ http Angular.js service. The only purpose of RESTSrvc is to send HTTP requests to the server and return promise objects of these requests. The remaining services use RESTSrvc to make requests and their separation is essentially functional.

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 - contains only one method responsible for closing the session. Application authentication is done using Basic access authetication (http://en.wikipedia.org/wiki/Basic_access_authentication), so there is no need for an authentication method, since each request has an authorization token in the header.

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 - contains helper methods, such as getting the cookie value by name, getting the value of an object property by name.

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 is responsible for executing queries related to the task list data.

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 - the main controller of the application, responsible for user authentication.

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 - the controller responsible for the task list table and actions with it. It initializes the task list table, contains methods for loading the task list and a specific task, as well as methods for processing user actions (pressing buttons, sorting the table, selecting a table row, filtering).

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.imtqy.com/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 - controller of a modal window containing detailed information about the task. Generates a list of fields and user actions, and also handles button presses of a modal window

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 is a file containing all application modules.

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 - the main page of the application.

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 - task list table template.

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 - modal window template.

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> 




Also, no one forbids using our REST API for your UI, especially since it is quite simple.

URL map of our REST API
 <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> 




You can try out the user interface on our test server running HelpDesk. Login: dev / Pass: 123

Source: https://habr.com/ru/post/251611/



All Articles