Skip to content
Snippets Groups Projects
Commit 4df99683 authored by Tim Otten's avatar Tim Otten
Browse files

Merge pull request #2920 from totten/master-casetype-ang

CRM-14479, CRM-14480 - First pass at "Edit Case Type" screen
parents afe66508 a7bcf9d0
No related tags found
No related merge requests found
Showing with 405 additions and 13 deletions
......@@ -52,6 +52,19 @@ class CRM_Case_Info extends CRM_Core_Component_Info {
);
}
/**
* {@inheritDoc}
*/
public function getAngularModules() {
$result = array();
$result['crmCaseType'] = array(
'ext' => 'civicrm',
'js' => array('js/angular-crmCaseType.js'),
'css' => array('css/angular-crmCaseType.css'),
);
return $result;
}
// docs inherited from interface
public function getManagedEntities() {
// Use hook_civicrm_caseTypes to build a list of OptionValues
......
......@@ -13,7 +13,11 @@ class CRM_Core_Page_Angular extends CRM_Core_Page {
const DEFAULT_MODULE_WEIGHT = 200;
function run() {
$res = CRM_Core_Resources::singleton();
$this->registerResources(CRM_Core_Resources::singleton());
return parent::run();
}
public function registerResources(CRM_Core_Resources $res) {
$modules = self::getAngularModules();
$res->addSettingsFactory(function () use (&$modules) {
......@@ -26,24 +30,30 @@ class CRM_Core_Page_Angular extends CRM_Core_Page {
);
});
$res->addScriptFile('civicrm', 'packages/bower_components/angular/angular.js', 100, 'html-header', FALSE);
$res->addScriptFile('civicrm', 'packages/bower_components/angular-route/angular-route.js', 110, 'html-header', FALSE);
$res->addScriptFile('civicrm', 'packages/bower_components/angular/angular.min.js', 100, 'html-header', FALSE);
$res->addScriptFile('civicrm', 'packages/bower_components/angular-route/angular-route.min.js', 110, 'html-header', FALSE);
foreach ($modules as $module) {
if (!empty($module['file'])) {
$res->addScriptFile($module['ext'], $module['file'], self::DEFAULT_MODULE_WEIGHT, 'html-header', TRUE);
if (!empty($module['css'])) {
foreach ($module['css'] as $file) {
$res->addStyleFile($module['ext'], $file, self::DEFAULT_MODULE_WEIGHT, 'html-header', TRUE);
}
}
if (!empty($module['js'])) {
foreach ($module['js'] as $file) {
$res->addScriptFile($module['ext'], $file, self::DEFAULT_MODULE_WEIGHT, 'html-header', TRUE);
}
}
}
return parent::run();
}
/**
* Get a list of AngularJS modules which should be autoloaded
*
* @return array (string $name => array('ext' => string $key, 'file' => string $path))
* @return array (string $name => array('ext' => string $key, 'js' => array $paths, 'css' => array $paths))
*/
public static function getAngularModules() {
$angularModules = array();
$angularModules['ui.utils'] = array('ext' => 'civicrm', 'js' => array('packages/bower_components/angular-ui-utils/ui-utils.min.js'));
foreach (CRM_Core_Component::getEnabledComponents() as $component) {
$angularModules = array_merge($angularModules, $component->getAngularModules());
}
......
......@@ -128,6 +128,8 @@ class CRM_Core_Resources {
* Construct a resource manager
*
* @param CRM_Extension_Mapper $extMapper Map extension names to their base path or URLs.
* @param CRM_Utils_Cache_Interface $cache JS-localization cache
* @param string|null $cacheCodeKey Random code to append to resource URLs; changing the code forces clients to reload resources
*/
public function __construct($extMapper, $cache, $cacheCodeKey = NULL) {
$this->extMapper = $extMapper;
......@@ -239,7 +241,7 @@ class CRM_Core_Resources {
/**
* Add JavaScript variables to the global CRM object via a callback function.
*
* @param $callable function
* @param callable $callable
* @return CRM_Core_Resources
*/
public function addSettingsFactory($callable) {
......@@ -384,7 +386,7 @@ class CRM_Core_Resources {
* @param $ext string, extension name; use 'civicrm' for core
* @param $file string, file path -- relative to the extension base dir
*
* @return (string|bool), full file path or FALSE if not found
* @return string|bool full file path or FALSE if not found
*/
public function getPath($ext, $file) {
// TODO consider caching results
......@@ -400,7 +402,8 @@ class CRM_Core_Resources {
*
* @param $ext string, extension name; use 'civicrm' for core
* @param $file string, file path -- relative to the extension base dir
* @return string, URL
* @param bool $addCacheCode
* @return string URL
*/
public function getUrl($ext, $file = NULL, $addCacheCode = FALSE) {
if ($file === NULL) {
......@@ -435,6 +438,7 @@ class CRM_Core_Resources {
* TODO: Separate the functional code (like addStyle/addScript) from the policy code
* (like addCoreResources/addCoreStyles).
*
* @param string $region
* @return CRM_Core_Resources
* @access public
*/
......
......@@ -1528,7 +1528,8 @@ abstract class CRM_Utils_Hook {
*
* @code
* function mymod_civicrm_angularModules(&$angularModules) {
* $angularModules['myAngularModule'] = array('ext' => 'org.example.mymod', 'file' => 'js/myAngularModule.js');
* $angularModules['myAngularModule'] = array('ext' => 'org.example.mymod', 'js' => array('js/myAngularModule.js'));
* $angularModules['myBigAngularModule'] = array('ext' => 'org.example.mymod', 'js' => array('js/part1.js', 'js/part2.js'), 'css' => array('css/myAngularModule.css'));
* }
* @endcode
*/
......
.crmCaseType .ui-icon {
margin: 0.4em 0.2em 0 0;
cursor: pointer;
}
.crmCaseType .ui-tabs-nav li .ui-icon {
float: left;
}
.crmCaseType .ui-tabs-nav select {
float: right;
}
(function(angular, $, _) {
var partialUrl = function(relPath) {
return CRM.resourceUrls['civicrm'] + '/partials/crmCaseType/' + relPath;
} ;
var crmCaseType = angular.module('crmCaseType', ['ngRoute', 'ui.utils']);
crmCaseType.config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/caseType/:id', {
templateUrl: partialUrl('edit.html'),
controller: 'CaseTypeCtrl'
});
}
]);
crmCaseType.controller('CaseTypeCtrl', function($scope) {
$scope.partialUrl = partialUrl;
$scope.workflows = {
'timeline': 'Timeline',
'pipeline': 'Sequence'
};
$scope.caseType = {
id: 123,
label: 'Adult Day Care Referral',
description: 'Superkalafragalisticexpialitotious',
is_active: '1', // Angular won't bind checkbox correctly with numeric 1
definition: { // This is the serialized field
name: 'Adult Day Care Referral',
activityTypes: [
{name: 'Open Case', max_instances: 1 },
{name: 'Medical evaluation'}
],
activitySets: [
{
name: 'standard_timeline',
label: 'Standard Timeline',
timeline: '1', // Angular won't bind checkbox correctly with numeric 1
activityTypes: [
{name: 'Open Case', status: 'Completed' },
{name: 'Medical evaluation', reference_activity: 'Open Case', reference_offset: 3, reference_select: 'newest'}
]
},
{
name: 'my_sequence',
label: 'My Sequence',
pipeline: '1', // Angular won't bind checkbox correctly with numeric 1
activityTypes: [
{name: 'Medical evaluation'},
{name: 'Meeting'},
{name: 'Phone Call'}
]
}
],
caseRoles: [
{ name: 'Senior Services Coordinator', creator: '1', manager: '1' },
{ name: 'Health Services Coordinator' },
{ name: 'Benefits Specialist' }
]
}
};
$scope.addActivitySet = function(workflow) {
var activitySet = {};
activitySet[workflow] = '1';
activitySet.activityTypes = [];
var offset = 1;
var names = _.pluck($scope.caseType.definition.activitySets, 'name');
while (_.contains(names, workflow + '_' + offset)) offset++;
activitySet.name = workflow + '_' + offset;
activitySet.label = (offset == 1 ) ? $scope.workflows[workflow] : ($scope.workflows[workflow] + ' #' + offset);
$scope.caseType.definition.activitySets.push(activitySet);
_.defer(function() {
$('.crmCaseType-acttab').tabs('refresh').tabs({active: -1});
});
};
$scope.onManagerChange = function(managerRole) {
angular.forEach($scope.caseType.definition.caseRoles, function(caseRole) {
if (caseRole != managerRole) {
caseRole.manager = '0';
}
});
};
$scope.removeItem = function(array, item) {
var idx = _.indexOf(array, item);
if (idx != -1) {
array.splice(idx, 1);
}
};
$scope.getWorkflowName = function(activitySet) {
var result = 'Unknown';
_.each($scope.workflows, function(value, key) {
if (activitySet[key]) result = value;
});
return result;
};
/**
* Determine which HTML partial to use for a particular
*
* @return string URL of the HTML partial
*/
$scope.activityTableTemplate = function(activitySet) {
if (activitySet.timeline) {
return partialUrl('timelineTable.html');
} else if (activitySet.pipeline) {
return partialUrl('pipelineTable.html');
} else {
return '';
}
};
$scope.dump = function() {
console.log($scope.caseType);
};
$scope.$watchCollection('caseType.definition.activitySets', function() {
_.defer(function() {
$('.crmCaseType-acttab').tabs('refresh');
});
});
});
})(angular, CRM.$, CRM._);
\ No newline at end of file
<!--
Controller: CaseTypeCtrl
Required vars: activitySet
-->
<table class="form-layout-compressed">
<tbody>
<tr>
<td class="label">Label</td>
<td>
<input type="text" name="label" ng-model="activitySet.label"/>
</td>
</tr>
<tr>
<td class="label">Name</td>
<td>
<input type="text" name="name" ng-model="activitySet.name"/><!-- FIXME lock -->
</td>
</tr>
<tr>
<td class="label">Workflow</td>
<td>
{{ getWorkflowName(activitySet) }}
</td>
</tr>
</tbody>
</table>
<!--
Controller: CaseTypeCtrl
Required vars: caseType
-->
<table>
<thead>
<tr>
<th>Activity Type</th>
<th>Max Instances</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="activityType in caseType.definition.activityTypes">
<td>
{{ activityType.name }}
</td>
<td>
{{ activityType.max_instances }}
</td>
<td>
<a class="ui-icon ui-icon-trash" title="Remove"
ng-click="removeItem(caseType.definition.activityTypes, activityType)"></a>
</td>
</tr>
</tbody>
</table>
<!--
Controller: CaseTypeCtrl
Required vars: caseType
The original form used table layout; don't know if we have an alternative, CSS-based layout
-->
<table class="form-layout-compressed">
<tbody>
<tr>
<td class="label">Label</td>
<td>
<input type="text" ng-model="caseType.label"/>
</td>
</tr>
<tr>
<td class="label">Name</td>
<td>
<input type="text" ng-model="caseType.definition.name"/> <!-- FIXME lock -->
</td>
</tr>
<tr>
<td class="label">Description</td>
<td>
<textarea ng-model="caseType.description"></textarea>
</td>
</tr>
<tr>
<td class="label">Enabled?</td>
<td>
<input type="checkbox" ng-model="caseType.is_active" ng-true-value="1" ng-false-value="0"/>
</td>
</tr>
</tbody>
</table>
<!--
Controller: CaseTypeCtrl
Required vars: caseType
-->
<div class="crm-block crm-form-block crmCaseType">
<div ng-include="partialUrl('caseTypeDetails.html')"></div>
<h2>Roles</h2>
<div ng-include="partialUrl('rolesTable.html')"></div>
<h2>Activities</h2>
<div class="crmCaseType-acttab" ui-jq="tabs" ui-options="{show: true, hide: true}">
<ul>
<li><a href="#acttab-actType">Activity Types</a></li>
<li ng-repeat="activitySet in caseType.definition.activitySets">
<a href="#acttab-{{$index}}">{{ activitySet.label }}</a>
<span class="ui-icon ui-icon-trash" title="Remove"
ng-click="removeItem(caseType.definition.activitySets, activitySet)">Remove</span>
</li>
<select ng-model="newActivitySetWorkflow" ng-change="addActivitySet(newActivitySetWorkflow); newActivitySetWorkflow='';">
<option value="">(Add)</option>
<option value="timeline">Timeline</option>
<option value="pipeline">Sequence</option>
</select>
</ul>
<div id="acttab-actType">
<div ng-include="partialUrl('activityTypesTable.html')"></div>
</div>
<div ng-repeat="activitySet in caseType.definition.activitySets" id="acttab-{{$index}}">
<div ng-include="activityTableTemplate(activitySet)"></div>
<div ui-jq="accordion" ui-options="{collapsible: true, active: false, heightStyle: 'fill'}">
<h3>Advanced</h3>
<div ng-include="partialUrl('activitySetDetails.html')" style="display:none;"></div>
</div>
</div>
</div>
<button ng-click="dump()">Log</button>
</div>
<!--
Controller: CaseTypeCtrl
Required vars: activitySet
-->
<table>
<thead>
<tr>
<th>Activity Type</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="activity in activitySet.activityTypes">
<td>{{ activity.name }}</td>
<td>
<a class="ui-icon ui-icon-trash" title="Remove"
ng-click="removeItem(activitySet.activityTypes, activity)"></a>
</td>
</tr>
</tbody>
</table>
<!--
Controller: CaseTypeCtrl
Required vars: caseType
-->
<table>
<thead>
<tr>
<th>Name</th>
<th>Assign to Creator</th>
<th>Is Manager</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relType in caseType.definition.caseRoles | orderBy:'name'">
<td>{{relType.name}}</td>
<td><input type="checkbox" ng-model="relType.creator" ng-true-value="1" ng-false-value="0"/></td>
<td><input type="radio" ng-model="relType.manager" value="1" ng-change="onManagerChange(relType)"/></td>
<td>
<a class="ui-icon ui-icon-trash" title="Remove"
ng-click="removeItem(caseType.definition.caseRoles, relType)"></a>
</td>
</tr>
</tbody>
</table>
<!--
Controller: CaseTypeCtrl
Required vars: activitySet
-->
<table>
<thead>
<tr>
<th>Activity Type</th>
<th>Status</th>
<th>Reference</th>
<th>Offset</th>
<th>Select</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="activity in activitySet.activityTypes">
<td>
{{ activity.name }}
</td>
<td>
{{ activity.status }}
</td>
<td>
{{ activity.reference_activity }}
</td>
<td>
{{ activity.reference_offset }}
</td>
<td>
{{ activity.reference_select }}
</td>
<td>
<a class="ui-icon ui-icon-trash" title="Remove"
ng-click="removeItem(activitySet.activityTypes, activity)"></a>
</td>
</tr>
</tbody>
</table>
......@@ -108,5 +108,5 @@ function angularex_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
}
function angularex_civicrm_angularModules(&$angularModule) {
$angularModule['example'] = array('ext' => 'org.civicrm.angularex', 'file' => 'js/example.js');
$angularModule['example'] = array('ext' => 'org.civicrm.angularex', 'js' => array('js/example.js'));
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment