Merge pull request #22918 from demeritcowboy/guzzle7
[civicrm-core.git] / ang / crmCaseType.js
1 (function(angular, $, _) {
2
3 var crmCaseType = angular.module('crmCaseType', CRM.angRequires('crmCaseType'));
4
5 // Note: This template will be passed to cloneDeep(), so don't put any funny stuff in here!
6 var newCaseTypeTemplate = {
7 title: "",
8 name: "",
9 is_active: "1",
10 weight: "1",
11 definition: {
12 activityTypes: [
13 {name: 'Open Case', max_instances: 1},
14 {name: 'Email'},
15 {name: 'Follow up'},
16 {name: 'Meeting'},
17 {name: 'Phone Call'}
18 ],
19 activitySets: [
20 {
21 name: 'standard_timeline',
22 label: 'Standard Timeline',
23 timeline: '1', // Angular won't bind checkbox correctly with numeric 1
24 activityTypes: [
25 {name: 'Open Case', status: 'Completed' }
26 ]
27 }
28 ],
29 caseRoles: [
30 { name: 'Case Coordinator', creator: '1', manager: '1'}
31 ]
32 }
33 };
34
35 crmCaseType.config(['$routeProvider',
36 function($routeProvider) {
37 $routeProvider.when('/caseType', {
38 templateUrl: '~/crmCaseType/list.html',
39 controller: 'CaseTypeListCtrl',
40 resolve: {
41 caseTypes: function($route, crmApi) {
42 return crmApi('CaseType', 'get', {options: {limit: 0}});
43 }
44 }
45 });
46 $routeProvider.when('/caseType/:id', {
47 templateUrl: '~/crmCaseType/edit.html',
48 controller: 'CaseTypeCtrl',
49 resolve: {
50 apiCalls: function($route, crmApi) {
51 var reqs = {};
52 reqs.actStatuses = ['OptionValue', 'get', {
53 option_group_id: 'activity_status',
54 sequential: 1,
55 options: {limit: 0}
56 }];
57 reqs.caseStatuses = ['OptionValue', 'get', {
58 option_group_id: 'case_status',
59 sequential: 1,
60 options: {
61 sort: 'weight',
62 limit: 0
63 }
64 }];
65 reqs.actTypes = ['OptionValue', 'get', {
66 option_group_id: 'activity_type',
67 sequential: 1,
68 options: {
69 sort: 'name',
70 limit: 0
71 }
72 }];
73 reqs.defaultAssigneeTypes = ['OptionValue', 'get', {
74 option_group_id: 'activity_default_assignee',
75 sequential: 1,
76 options: {
77 limit: 0
78 }
79 }];
80 reqs.relTypes = ['RelationshipType', 'get', {
81 sequential: 1,
82 options: {
83 sort: 'label_a_b',
84 limit: 0
85 }
86 }];
87 if ($route.current.params.id !== 'new') {
88 reqs.caseType = ['CaseType', 'getsingle', {
89 id: $route.current.params.id
90 }];
91 }
92 return crmApi(reqs);
93 }
94 }
95 });
96 }
97 ]);
98
99 // Add a new record by name.
100 // Ex: <crmAddName crm-options="['Alpha','Beta','Gamma']" crm-var="newItem" crm-on-add="callMyCreateFunction(newItem)" />
101 crmCaseType.directive('crmAddName', function() {
102 return {
103 restrict: 'AE',
104 template: '<input class="add-activity crm-action-menu fa-plus" type="hidden" />',
105 link: function(scope, element, attrs) {
106
107 var input = $('input', element);
108
109 scope._resetSelection = function() {
110 $(input).select2('close');
111 $(input).select2('val', '');
112 scope[attrs.crmVar] = '';
113 };
114
115 $(input).crmSelect2({
116 data: function () {
117 return { results: scope[attrs.crmOptions] };
118 },
119 createSearchChoice: function(term) {
120 return {id: term, text: term + ' (' + ts('new') + ')'};
121 },
122 createSearchChoicePosition: 'bottom',
123 placeholder: attrs.placeholder
124 });
125 $(input).on('select2-selecting', function(e) {
126 scope[attrs.crmVar] = e.val;
127 scope.$evalAsync(attrs.crmOnAdd);
128 scope.$evalAsync('_resetSelection()');
129 e.preventDefault();
130 });
131 }
132 };
133 });
134
135 crmCaseType.directive('crmEditableTabTitle', function($timeout) {
136 return {
137 restrict: 'AE',
138 link: function(scope, element, attrs) {
139 element.addClass('crm-editable crm-editable-enabled');
140 var titleLabel = $(element).find('span');
141 var penIcon = $('<i class="crm-i fa-pencil crm-editable-placeholder" aria-hidden="true"></i>').prependTo(element);
142 var saveButton = $('<button type="button"><i class="crm-i fa-check" aria-hidden="true"></i></button>').appendTo(element);
143 var cancelButton = $('<button type="cancel"><i class="crm-i fa-times" aria-hidden="true"></i></button>').appendTo(element);
144 $('button', element).wrapAll('<div class="crm-editable-form" style="display:none" />');
145 var buttons = $('.crm-editable-form', element);
146 titleLabel.on('click', startEditMode);
147 penIcon.on('click', startEditMode);
148
149 function detectEscapeKeyPress (event) {
150 var isEscape = false;
151
152 if ("key" in event) {
153 isEscape = (event.key == "Escape" || event.key == "Esc");
154 } else {
155 isEscape = (event.keyCode == 27);
156 }
157
158 return isEscape;
159 }
160
161 function detectEnterKeyPress (event) {
162 var isEnter = false;
163
164 if ("key" in event) {
165 isEnter = (event.key == "Enter");
166 } else {
167 isEnter = (event.keyCode == 13);
168 }
169
170 return isEnter;
171 }
172
173 function startEditMode () {
174 if (titleLabel.is(":focus")) {
175 return;
176 }
177
178 penIcon.hide();
179 buttons.show();
180
181 saveButton.click(function () {
182 updateTextValue();
183 stopEditMode();
184 });
185
186 cancelButton.click(function () {
187 revertTextValue();
188 stopEditMode();
189 });
190
191 $(element).addClass('crm-editable-editing');
192
193 titleLabel
194 .attr("contenteditable", "true")
195 .focus()
196 .focusout(function (event) {
197 $timeout(function () {
198 revertTextValue();
199 stopEditMode();
200 }, 500);
201 })
202 .keydown(function(event) {
203 event.stopImmediatePropagation();
204
205 if(detectEscapeKeyPress(event)) {
206 revertTextValue();
207 stopEditMode();
208 } else if(detectEnterKeyPress(event)) {
209 event.preventDefault();
210 updateTextValue();
211 stopEditMode();
212 }
213 });
214 }
215
216 function stopEditMode () {
217 titleLabel.removeAttr("contenteditable").off("focusout");
218 titleLabel.off("keydown");
219 saveButton.off("click");
220 cancelButton.off("click");
221 $(element).removeClass('crm-editable-editing');
222
223 penIcon.show();
224 buttons.hide();
225 }
226
227 function revertTextValue () {
228 titleLabel.text(scope.activitySet.label);
229 }
230
231 function updateTextValue () {
232 var updatedTitle = titleLabel.text();
233
234 scope.$evalAsync(function () {
235 scope.activitySet.label = updatedTitle;
236 });
237 }
238 }
239 };
240 });
241
242 crmCaseType.controller('CaseTypeCtrl', function($scope, $timeout, crmApi, apiCalls, crmUiHelp) {
243 var defaultAssigneeDefaultValue, ts;
244
245 (function init () {
246
247 ts = $scope.ts = CRM.ts(null);
248 $scope.hs = crmUiHelp({file: 'CRM/Case/CaseType'});
249 $scope.locks = { caseTypeName: true, activitySetName: true };
250 $scope.workflows = { timeline: 'Timeline', sequence: 'Sequence' };
251 defaultAssigneeDefaultValue = _.find(apiCalls.defaultAssigneeTypes.values, { is_default: '1' }) || {};
252
253 storeApiCallsResults();
254 initCaseType();
255 initCaseTypeDefinition();
256 initSelectedStatuses();
257
258 $timeout(function() {
259 $('form[name=editCaseTypeForm] .crmCaseType-acttab').tabs({show: true, hide: true});
260 });
261 })();
262
263 /// Stores the api calls results in the $scope object
264 function storeApiCallsResults() {
265 $scope.activityStatuses = apiCalls.actStatuses.values;
266 $scope.caseStatuses = _.indexBy(apiCalls.caseStatuses.values, 'name');
267 $scope.activityTypes = _.indexBy(apiCalls.actTypes.values, 'name');
268 $scope.activityTypeOptions = _.map(apiCalls.actTypes.values, formatActivityTypeOption);
269 $scope.defaultAssigneeTypes = apiCalls.defaultAssigneeTypes.values;
270 // for dropdown lists, only include enabled choices
271 $scope.relationshipTypeOptions = getRelationshipTypeOptions(true);
272 // for comparisons, include disabled
273 $scope.relationshipTypeOptionsAll = getRelationshipTypeOptions(false);
274 // stores the default assignee values indexed by their option name:
275 $scope.defaultAssigneeTypeValues = _.chain($scope.defaultAssigneeTypes)
276 .indexBy('name').mapValues('value').value();
277 }
278
279 // Returns the relationship type options. If the relationship is
280 // bidirectional (Ex: Spouse of) it adds a single option otherwise it adds
281 // two options representing the relationship type directions (Ex: Employee
282 // of, Employer of).
283 //
284 // The relationship dropdown needs to be given IDs with direction,
285 // while the role name in the xml needs values that are names (with
286 // implicit direction).
287 //
288 // At any rate, the labels should follow the convention in the UI of
289 // describing case roles from the perspective of the client, while the
290 // names must follow the convention in the XML of describing case roles
291 // from the perspective of the non-client.
292 //
293 // @param onlyActive bool
294 // If true, only include enabled relationship types.
295 // @return array[object]
296 // object: {
297 // xmlName: The name corresponding to what's stored in xml/caseRoles.
298 // text: The text in dropdowns, i.e. <option value="id">text</option>
299 // It's called text because that is what select2 is expecting.
300 // id: The id value in dropdowns, i.e. <option value="id">text</option>
301 // Is the concatenation of id+direction, e.g. 2_a_b.
302 // It's called id because that is what select2 is expecting.
303 // }
304 function getRelationshipTypeOptions(onlyActive) {
305 var relationshipTypesToUse;
306 if (onlyActive) {
307 relationshipTypesToUse = _.filter(apiCalls.relTypes.values, {is_active: "1"});
308 } else {
309 relationshipTypesToUse = apiCalls.relTypes.values;
310 }
311 return _.transform(relationshipTypesToUse, function(result, relType) {
312 var isBidirectionalRelationship = relType.label_a_b === relType.label_b_a;
313
314 // The order here of a's and b's here is important regarding
315 // unidirectional and bidirectional. Because the xml spec DOES support
316 // direction for activity auto-assignees, if this is changed you might
317 // end up with activity assignees in existing xml that then come
318 // through as blank in the dropdown on the timelines tab if it's a
319 // bidirectional relationship. E.g. if spouse is stored in an existing
320 // xml definition as 2_a_b, but we only push 2_b_a, then it won't match
321 // in the dropdown.
322 //
323 // This has some implications for when we add a new type on the fly
324 // later, but it works out ok as long as we also do it in the same
325 // direction there. See notes in addRoleOnTheFly().
326 result.push({
327 // This is what we want to store in the caseRoles.name field,
328 // which corresponds to the xml file, when we send it back to
329 // the server.
330 xmlName: relType.name_a_b,
331 // This has to be called text because select2 is expecting it.
332 // And yes it's the opposite direction from name.
333 text: relType.label_b_a,
334 // This has to be called id because select2 is expecting it.
335 id: relType.id + '_a_b'
336 });
337
338 if (!isBidirectionalRelationship) {
339 result.push({
340 xmlName: relType.name_b_a,
341 text: relType.label_a_b,
342 id: relType.id + '_b_a'
343 });
344 }
345 }, []);
346 }
347
348 /// initializes the case type object
349 function initCaseType() {
350 var isNewCaseType = !apiCalls.caseType;
351
352 if (isNewCaseType) {
353 $scope.caseType = _.cloneDeep(newCaseTypeTemplate);
354 } else {
355 $scope.caseType = apiCalls.caseType;
356 }
357 }
358
359 /// initializes the case type definition object
360 function initCaseTypeDefinition() {
361 $scope.caseType.definition = $scope.caseType.definition || [];
362 $scope.caseType.definition.activityTypes = $scope.caseType.definition.activityTypes || [];
363 $scope.caseType.definition.activitySets = $scope.caseType.definition.activitySets || [];
364 $scope.caseType.definition.caseRoles = $scope.caseType.definition.caseRoles || [];
365 $scope.caseType.definition.statuses = $scope.caseType.definition.statuses || [];
366 $scope.caseType.definition.timelineActivityTypes = $scope.caseType.definition.timelineActivityTypes || [];
367 $scope.caseType.definition.restrictActivityAsgmtToCmsUser = $scope.caseType.definition.restrictActivityAsgmtToCmsUser || 0;
368 $scope.caseType.definition.activityAsgmtGrps = $scope.caseType.definition.activityAsgmtGrps || [];
369
370 _.each($scope.caseType.definition.activitySets, function (set) {
371 _.each(set.activityTypes, function (type, name) {
372 var isDefaultAssigneeTypeUndefined = _.isUndefined(type.default_assignee_type);
373 var typeDefinition = $scope.activityTypes[type.name];
374 type.label = (typeDefinition && typeDefinition.label) || type.name;
375
376 if (isDefaultAssigneeTypeUndefined) {
377 type.default_assignee_type = defaultAssigneeDefaultValue.value;
378 }
379 });
380 });
381
382 // Go lookup and add client-perspective labels for
383 // $scope.caseType.definition.caseRoles, since the xml doesn't have them
384 // and we need to display them in the roles table.
385 _.each($scope.caseType.definition.caseRoles, function (set) {
386 _.each($scope.relationshipTypeOptionsAll, function (relationshipTypeOption) {
387 if (relationshipTypeOption.xmlName == set.name) {
388 // relationshipTypeOption.text here corresponds to one of the
389 // apiCalls.relTypes label fields (i.e. civicrm_relationship_type
390 // label database fields). It has to be called text because
391 // it's used in select2 which expects it to be called text.
392 set.displayLabel = relationshipTypeOption.text;
393 // break out of inner `each` loop
394 return false;
395 }
396 });
397 });
398 }
399
400 /// initializes the selected statuses
401 function initSelectedStatuses() {
402 $scope.selectedStatuses = {};
403
404 _.each(apiCalls.caseStatuses.values, function (status) {
405 $scope.selectedStatuses[status.name] = !$scope.caseType.definition.statuses.length || $scope.caseType.definition.statuses.indexOf(status.name) > -1;
406 });
407 }
408
409 $scope.addActivitySet = function(workflow) {
410 var activitySet = {};
411 activitySet[workflow] = '1';
412 activitySet.activityTypes = [];
413
414 var offset = 1;
415 var names = _.pluck($scope.caseType.definition.activitySets, 'name');
416 while (_.contains(names, workflow + '_' + offset)) offset++;
417 activitySet.name = workflow + '_' + offset;
418 activitySet.label = (offset == 1 ) ? $scope.workflows[workflow] : ($scope.workflows[workflow] + ' #' + offset);
419
420 $scope.caseType.definition.activitySets.push(activitySet);
421 _.defer(function() {
422 $('.crmCaseType-acttab').tabs('refresh').tabs({active: -1});
423 });
424 };
425
426 function formatActivityTypeOption(type) {
427 return {id: type.name, text: type.label, icon: type.icon};
428 }
429
430 function addActivityToSet(activitySet, activityTypeName) {
431 activitySet.activityTypes = activitySet.activityTypes || [];
432 var activity = {
433 name: activityTypeName,
434 label: $scope.activityTypes[activityTypeName].label,
435 status: 'Scheduled',
436 reference_activity: 'Open Case',
437 reference_offset: '1',
438 reference_select: 'newest',
439 default_assignee_type: $scope.defaultAssigneeTypeValues.NONE
440 };
441 activitySet.activityTypes.push(activity);
442 if(typeof activitySet.timeline !== "undefined" && activitySet.timeline == "1") {
443 $scope.caseType.definition.timelineActivityTypes.push(activity);
444 }
445 }
446
447 function resetTimelineActivityTypes() {
448 $scope.caseType.definition.timelineActivityTypes = [];
449 angular.forEach($scope.caseType.definition.activitySets, function(activitySet) {
450 angular.forEach(activitySet.activityTypes, function(activityType) {
451 $scope.caseType.definition.timelineActivityTypes.push(activityType);
452 });
453 });
454 }
455
456 function createActivity(name, callback) {
457 CRM.loadForm(CRM.url('civicrm/admin/options/activity_type', {action: 'add', reset: 1, label: name, component_id: 7}))
458 .on('crmFormSuccess', function(e, data) {
459 $scope.activityTypes[data.optionValue.name] = data.optionValue;
460 $scope.activityTypeOptions.push(formatActivityTypeOption(data.optionValue));
461 callback(data.optionValue);
462 $scope.$digest();
463 });
464 }
465
466 // Add a new activity entry to an activity-set
467 $scope.addActivity = function(activitySet, activityType) {
468 if ($scope.activityTypes[activityType]) {
469 addActivityToSet(activitySet, activityType);
470 } else {
471 createActivity(activityType, function(newActivity) {
472 addActivityToSet(activitySet, newActivity.name);
473 });
474 }
475 };
476
477 /// Add a new top-level activity-type entry
478 $scope.addActivityType = function(activityType) {
479 var names = _.pluck($scope.caseType.definition.activityTypes, 'name');
480 if (!_.contains(names, activityType)) {
481 // Add an activity type that exists
482 if ($scope.activityTypes[activityType]) {
483 $scope.caseType.definition.activityTypes.push({name: activityType});
484 } else {
485 createActivity(activityType, function(newActivity) {
486 $scope.caseType.definition.activityTypes.push({name: newActivity.name});
487 });
488 }
489 }
490 };
491
492 /// Clears the activity's default assignee values for relationship and contact
493 $scope.clearActivityDefaultAssigneeValues = function(activity) {
494 activity.default_assignee_relationship = null;
495 activity.default_assignee_contact = null;
496 };
497
498 // Add a new role.
499 // Called from the select2 dropdown when a selection is made.
500 //
501 // @param roles array
502 // The roles currently in the table.
503 // @param roleIdOrLabel string
504 // The trick here is that since you can add roles on the fly, the
505 // roleIdOrLabel parameter can be two different types of things. It can be
506 // the id, like '2_a_b' if it's an existing choice that was selected, or
507 // it can be a LABEL if they typed something that isn't in the list. If
508 // the latter the select2 has no choice but to give us a label because
509 // there is no id yet.
510 $scope.addRole = function(roles, roleIdOrLabel) {
511 var matchingRole;
512 // First check does what we've been given match up to any relationship
513 // type, based on id, which is the id from the select2 (i.e. html
514 // <option value="id">)
515 var matchingRoles = _.filter($scope.relationshipTypeOptions, {id: roleIdOrLabel});
516 if (matchingRoles.length) {
517 matchingRole = matchingRoles.shift();
518 }
519 // If found, is the corresponding machine name in the list of existing
520 // roles for the case type. Unfortunately, caseRoles only stores name,
521 // which doesn't indicate the id or direction, because the xml spec
522 // doesn't support those.
523 var names = _.pluck($scope.caseType.definition.caseRoles, 'name');
524 if (matchingRole) {
525 // If it's not in the table already, add it, otherwise do nothing since
526 // don't want to add it twice.
527 if (!_.contains(names, matchingRole.xmlName)) {
528 roles.push({name: matchingRole.xmlName, displayLabel: matchingRole.text});
529 }
530 } else {
531 // Not a known relationship type, so create on-the-fly.
532 // At this point roleIdOrLabel must be the new label they just typed.
533 CRM.loadForm(CRM.url('civicrm/admin/reltype', {action: 'add', reset: 1, label_a_b: roleIdOrLabel}))
534 .on('crmFormSuccess', function(e, data) {
535 var newType = _.values(data.relationshipType)[0];
536 $scope.$apply(function() {
537 $scope.addRoleOnTheFly(roles, newType);
538 });
539 });
540 }
541 };
542
543 // Add a newly created relationship type as a role to the table and
544 // update the list of options.
545 //
546 // @param roles array
547 // The roles currently in the table.
548 // @param newType array
549 // The array returned from the api call that created the new type
550 // earlier.
551 $scope.addRoleOnTheFly = function(roles, newType) {
552 // Add it to the roles table. Assume they want the A-B direction since
553 // that's what they would have typed.
554 // Name and label are opposites here because name represents the value
555 // in the xml here which historically is the opposite.
556 roles.push({name: newType.name_b_a, displayLabel: newType.label_a_b});
557
558 // But now add both directions as option choices for future dropdown
559 // selections.
560 // Note that to keep in line with the original population on init,
561 // we're pushing a different direction here than we just added to the
562 // table, but there's only two possibilities:
563 // 1. Labels are the same, and since it's a new type name_a_b and
564 // name_b_a will therefore be the same, and so it doesn't matter which
565 // name is stored in caseRoles.
566 // 2. Labels are different, in which case we're also going to push the
567 // other direction below.
568 // So either way we're covered.
569 // See also note in getRelationshipTypeOptions().
570 var newRelTypeOption = {
571 xmlName: newType.name_a_b,
572 // Yes text is the opposite direction from name here.
573 text: newType.label_b_a,
574 id: newType.id + '_a_b'
575 };
576 $scope.relationshipTypeOptions.push(newRelTypeOption);
577 $scope.relationshipTypeOptionsAll.push(newRelTypeOption);
578 // Add the other direction if different.
579 if (newType.label_a_b != newType.label_b_a) {
580 newRelTypeOption = {
581 xmlName: newType.name_b_a,
582 text: newType.label_a_b,
583 id: newType.id + '_b_a'
584 };
585 $scope.relationshipTypeOptions.push(newRelTypeOption);
586 $scope.relationshipTypeOptionsAll.push(newRelTypeOption);
587 }
588 };
589
590 $scope.onManagerChange = function(managerRole) {
591 angular.forEach($scope.caseType.definition.caseRoles, function(caseRole) {
592 if (caseRole != managerRole) {
593 caseRole.manager = '0';
594 }
595 });
596 };
597
598 $scope.removeItem = function(array, item) {
599 var idx = _.indexOf(array, item);
600 if (idx != -1) {
601 array.splice(idx, 1);
602 resetTimelineActivityTypes();
603 }
604 };
605
606 $scope.isForkable = function() {
607 return !$scope.caseType.id || $scope.caseType.is_forkable;
608 };
609
610 $scope.newStatus = function() {
611 CRM.loadForm(CRM.url('civicrm/admin/options/case_status', {action: 'add', reset: 1}))
612 .on('crmFormSuccess', function(e, data) {
613 $scope.caseStatuses[data.optionValue.name] = data.optionValue;
614 $scope.selectedStatuses[data.optionValue.name] = true;
615 $scope.$digest();
616 });
617 };
618
619 $scope.isNewActivitySetAllowed = function(workflow) {
620 switch (workflow) {
621 case 'timeline':
622 return true;
623 case 'sequence':
624 return 0 === _.where($scope.caseType.definition.activitySets, {sequence: '1'}).length;
625 default:
626 CRM.console('warn', 'Denied access to unrecognized workflow: (' + workflow + ')');
627 return false;
628 }
629 };
630
631 $scope.isActivityRemovable = function(activitySet, activity) {
632 return true;
633 };
634
635 $scope.isValidName = function(name) {
636 return !name || name.match(/^[a-zA-Z0-9_]+$/);
637 };
638
639 $scope.getWorkflowName = function(activitySet) {
640 var result = 'Unknown';
641 _.each($scope.workflows, function(value, key) {
642 if (activitySet[key]) result = value;
643 });
644 return result;
645 };
646
647 /**
648 * Determine which HTML partial to use for a particular
649 *
650 * @return string URL of the HTML partial
651 */
652 $scope.activityTableTemplate = function(activitySet) {
653 if (activitySet.timeline) {
654 return '~/crmCaseType/timelineTable.html';
655 } else if (activitySet.sequence) {
656 return '~/crmCaseType/sequenceTable.html';
657 } else {
658 return '';
659 }
660 };
661
662 $scope.dump = function() {
663 console.log($scope.caseType);
664 };
665
666 $scope.save = function() {
667 // Add selected statuses
668 var selectedStatuses = [];
669 _.each($scope.selectedStatuses, function(v, k) {
670 if (v) selectedStatuses.push(k);
671 });
672 // Ignore if ALL or NONE selected
673 $scope.caseType.definition.statuses = selectedStatuses.length == _.size($scope.selectedStatuses) ? [] : selectedStatuses;
674
675 if ($scope.caseType.definition.activityAsgmtGrps) {
676 $scope.caseType.definition.activityAsgmtGrps = $scope.caseType.definition.activityAsgmtGrps.toString().split(",");
677 }
678
679 function dropDisplaylabel (v) {
680 delete v.displayLabel;
681 }
682
683 // strip out labels from $scope.caseType.definition.caseRoles
684 _.map($scope.caseType.definition.caseRoles, dropDisplaylabel);
685
686 var result = crmApi('CaseType', 'create', $scope.caseType, true);
687 result.then(function(data) {
688 if (data.is_error === 0 || data.is_error == '0') {
689 $scope.caseType.id = data.id;
690 window.location.href = '#/caseType';
691 }
692 });
693 };
694
695 $scope.$watchCollection('caseType.definition.activitySets', function() {
696 _.defer(function() {
697 $('.crmCaseType-acttab').tabs('refresh');
698 });
699 });
700
701 var updateCaseTypeName = function () {
702 if (!$scope.caseType.id && $scope.locks.caseTypeName) {
703 // Should we do some filtering? Lowercase? Strip whitespace?
704 var t = $scope.caseType.title ? $scope.caseType.title : '';
705 $scope.caseType.name = t.replace(/ /g, '_').replace(/[^a-zA-Z0-9_]/g, '').toLowerCase();
706 }
707 };
708 $scope.$watch('locks.caseTypeName', updateCaseTypeName);
709 $scope.$watch('caseType.title', updateCaseTypeName);
710
711 if (!$scope.isForkable()) {
712 CRM.alert(ts('The CiviCase XML file for this case-type prohibits editing the definition.'));
713 }
714
715 });
716
717 crmCaseType.controller('CaseTypeListCtrl', function($scope, crmApi, caseTypes) {
718 var ts = $scope.ts = CRM.ts(null);
719
720 $scope.caseTypes = caseTypes.values;
721 $scope.toggleCaseType = function (caseType) {
722 caseType.is_active = (caseType.is_active == '1') ? '0' : '1';
723 crmApi('CaseType', 'create', caseType, true)
724 .catch(function (data) {
725 caseType.is_active = (caseType.is_active == '1') ? '0' : '1'; // revert
726 $scope.$digest();
727 });
728 };
729 $scope.deleteCaseType = function (caseType) {
730 crmApi('CaseType', 'delete', {id: caseType.id}, {
731 error: function (data) {
732 CRM.alert(data.error_message, ts('Error'), 'error');
733 }
734 })
735 .then(function (data) {
736 delete caseTypes.values[caseType.id];
737 });
738 };
739 $scope.revertCaseType = function (caseType) {
740 caseType.definition = 'null';
741 caseType.is_forked = '0';
742 crmApi('CaseType', 'create', caseType, true)
743 .catch(function (data) {
744 caseType.is_forked = '1'; // restore
745 $scope.$digest();
746 });
747 };
748 });
749
750 })(angular, CRM.$, CRM._);