Commit d7a470db authored by Debarshi Bhaumik's avatar Debarshi Bhaumik Committed by colemanw

Add inplace edit for timeline name

parent 59b665d2
......@@ -2,8 +2,14 @@
vertical-align: middle;
cursor: move;
}
.crmCaseType .fa-pencil {
margin: 0.2em 0.2em 0 0;
cursor: pointer;
}
.crmCaseType .fa-trash {
margin: 0.4em 0.2em 0 0;
margin: 0.56em 0.2em 0 0;
cursor: pointer;
}
......
......@@ -122,6 +122,113 @@
};
});
crmCaseType.directive('crmEditableTabTitle', function($timeout) {
return {
restrict: 'AE',
link: function(scope, element, attrs) {
element.addClass('crm-editable crm-editable-enabled');
var titleLabel = $(element).find('span');
var penIcon = $('<i class="crm-i fa-pencil crm-editable-placeholder"></i>').prependTo(element);
var saveButton = $('<button type="button"><i class="crm-i fa-check"></i></button>').appendTo(element);
var cancelButton = $('<button type="cancel"><i class="crm-i fa-times"></i></button>').appendTo(element);
$('button', element).wrapAll('<div class="crm-editable-form" style="display:none" />');
var buttons = $('.crm-editable-form', element);
titleLabel.on('click', startEditMode);
penIcon.on('click', startEditMode);
function detectEscapeKeyPress (event) {
var isEscape = false;
if ("key" in event) {
isEscape = (event.key == "Escape" || event.key == "Esc");
} else {
isEscape = (event.keyCode == 27);
}
return isEscape;
}
function detectEnterKeyPress (event) {
var isEnter = false;
if ("key" in event) {
isEnter = (event.key == "Enter");
} else {
isEnter = (event.keyCode == 13);
}
return isEnter;
}
function startEditMode () {
if (titleLabel.is(":focus")) {
return;
}
penIcon.hide();
buttons.show();
saveButton.click(function () {
updateTextValue();
stopEditMode();
});
cancelButton.click(function () {
revertTextValue();
stopEditMode();
});
$(element).addClass('crm-editable-editing');
titleLabel
.attr("contenteditable", "true")
.focus()
.focusout(function (event) {
$timeout(function () {
revertTextValue();
stopEditMode();
}, 500);
})
.keydown(function(event) {
event.stopImmediatePropagation();
if(detectEscapeKeyPress(event)) {
revertTextValue();
stopEditMode();
} else if(detectEnterKeyPress(event)) {
event.preventDefault();
updateTextValue();
stopEditMode();
}
});
}
function stopEditMode () {
titleLabel.removeAttr("contenteditable").off("focusout");
titleLabel.off("keydown");
saveButton.off("click");
cancelButton.off("click");
$(element).removeClass('crm-editable-editing');
penIcon.show();
buttons.hide();
}
function revertTextValue () {
titleLabel.text(scope.activitySet.label);
}
function updateTextValue () {
var updatedTitle = titleLabel.text();
scope.$evalAsync(function () {
scope.activitySet.label = updatedTitle;
});
}
}
};
});
crmCaseType.controller('CaseTypeCtrl', function($scope, crmApi, apiCalls) {
// CRM_Case_XMLProcessor::REL_TYPE_CNAME
var REL_TYPE_CNAME = CRM.crmCaseType.REL_TYPE_CNAME,
......
<!--
Controller: CaseTypeCtrl
Required vars: activitySet
-->
<table class="form-layout-compressed">
<tbody>
<tr>
<td class="label">{{ts('Label')}}</td>
<td>
<input type="text" name="label" class="crm-form-text" ng-model="activitySet.label"/>
</td>
</tr>
<tr>
<td class="label">{{ts('Name')}}</td>
<td>
<input type="text" name="name" class="crm-form-text" ng-model="activitySet.name" ng-disabled="locks.activitySetName" />
<a crm-ui-lock binding="locks.activitySetName"></a>
</td>
</tr>
<tr>
<td class="label">{{ts('Workflow')}}</td>
<td>
{{ getWorkflowName(activitySet) }}
</td>
</tr>
</tbody>
</table>
......@@ -19,10 +19,14 @@ Required vars: caseType
<li><a href="#acttab-statuses">{{ts('Case Statuses')}}</a></li>
<li><a href="#acttab-actType">{{ts('Activity Types')}}</a></li>
<li ng-repeat="activitySet in caseType.definition.activitySets">
<a href="#acttab-{{$index}}">{{ activitySet.label }}</a>
<a href="#acttab-{{$index}}" class="crmCaseType-editable">
<div crm-editable-tab-title title="{{ts('Click to edit')}}">
<span>{{ activitySet.label }}</span>
</div>
</a>
<span class="crm-i fa-trash" title="{{ts('Remove')}}"
ng-hide="activitySet.name == 'standard_timeline'"
ng-click="removeItem(caseType.definition.activitySets, activitySet)">{{ts('Remove')}}</span>
ng-click="removeItem(caseType.definition.activitySets, activitySet)"></span>
<!-- Weird spacing:
<a class="crm-hover-button" ng-click="removeItem(caseType.definition.activitySets, activitySet)">
<span class="crm-i fa-trash" title="Remove">Remove</span>
......@@ -44,11 +48,6 @@ Required vars: caseType
<div ng-repeat="activitySet in caseType.definition.activitySets" id="acttab-{{$index}}">
<div ng-include="activityTableTemplate(activitySet)"></div>
<div class="crm-accordion-wrapper collapsed">
<div class="crm-accordion-header">{{ts('Advanced')}}</div>
<div class="crm-accordion-body" ng-include="'~/crmCaseType/activitySetDetails.html'"></div>
</div>
</div>
</div>
......
......@@ -7,6 +7,7 @@ describe('crmCaseType', function() {
var $httpBackend;
var $q;
var $rootScope;
var $timeout;
var apiCalls;
var ctrl;
var compile;
......@@ -27,12 +28,13 @@ describe('crmCaseType', function() {
});
});
beforeEach(inject(function(_$controller_, _$compile_, _$httpBackend_, _$q_, _$rootScope_) {
beforeEach(inject(function(_$controller_, _$compile_, _$httpBackend_, _$q_, _$rootScope_, _$timeout_) {
$controller = _$controller_;
$compile = _$compile_;
$httpBackend = _$httpBackend_;
$q = _$q_;
$rootScope = _$rootScope_;
$timeout = _$timeout_;
}));
describe('CaseTypeCtrl', function() {
......@@ -291,6 +293,189 @@ describe('crmCaseType', function() {
});
});
describe('crmEditableTabTitle', function () {
var element, titleLabel, penIcon, saveButton, cancelButton;
beforeEach(function() {
scope = $rootScope.$new();
element = '<div crm-editable-tab-title title="Click to edit">' +
'<span ng-keydown="$event.stopImmediatePropagation()">{{ activitySet.label }}</span>' +
'</div>';
scope.activitySet = { label: 'Title'};
element = $compile(element)(scope);
titleLabel = $(element).find('span');
penIcon = $(element).find('i.fa-pencil');
saveButton = $(element).find('button[type=button]');
cancelButton = $(element).find('button[type=cancel]');
scope.$digest();
});
describe('when initialized', function () {
it('hides the save and cancel button', function () {
expect(saveButton.parent().css('display') === 'none').toBe(true);
expect(cancelButton.parent().css('display') === 'none').toBe(true);
});
});
describe('when clicked on title label', function () {
beforeEach(function () {
titleLabel.click();
});
it('hides the pen icon', function () {
expect(penIcon.css('display') === 'none').toBe(true);
});
it('shows the save button', function () {
expect(saveButton.parent().css('display') !== 'none').toBe(true);
});
it('makes the title editable', function () {
expect(titleLabel.attr('contenteditable')).toBe('true');
});
});
describe('when clicked outside of the editable area', function () {
beforeEach(function () {
titleLabel.click();
titleLabel.text('Updated Title');
titleLabel.blur();
$timeout.flush();
scope.$digest();
});
it('shows the pen icon', function () {
expect(penIcon.css('display') !== 'none').toBe(true);
});
it('hides the save and cancel button', function () {
expect(saveButton.parent().css('display') === 'none').toBe(true);
expect(cancelButton.parent().css('display') === 'none').toBe(true);
});
it('makes the title non editable', function () {
expect(titleLabel.attr('contenteditable')).not.toBe('true');
});
it('does not update the title in angular context', function () {
expect(scope.activitySet.label).toBe('Title');
});
});
describe('when ESCAPE key is pressed while typing', function () {
beforeEach(function () {
var eventObj = $.Event('keydown');
eventObj.key = 'Escape';
titleLabel.click();
titleLabel.text('Updated Title');
titleLabel.trigger(eventObj);
scope.$digest();
});
it('shows the pen icon', function () {
expect(penIcon.css('display') !== 'none').toBe(true);
});
it('hides the save and cancel button', function () {
expect(saveButton.parent().css('display') === 'none').toBe(true);
expect(cancelButton.parent().css('display') === 'none').toBe(true);
});
it('makes the title non editable', function () {
expect(titleLabel.attr('contenteditable')).not.toBe('true');
});
it('does not update the title', function () {
expect(scope.activitySet.label).toBe('Title');
});
});
describe('when ENTER key is pressed while typing', function () {
beforeEach(function () {
var eventObj = $.Event('keydown');
eventObj.key = 'Enter';
titleLabel.click();
titleLabel.text('Updated Title');
titleLabel.trigger(eventObj);
scope.$digest();
});
it('shows the pen icon', function () {
expect(penIcon.css('display') !== 'none').toBe(true);
});
it('hides the save and cancel button', function () {
expect(saveButton.parent().css('display') === 'none').toBe(true);
expect(cancelButton.parent().css('display') === 'none').toBe(true);
});
it('makes the title non editable', function () {
expect(titleLabel.attr('contenteditable')).not.toBe('true');
});
it('updates the title in angular context', function () {
expect(scope.activitySet.label).toBe('Updated Title');
});
});
describe('when SAVE button is clicked', function () {
beforeEach(function () {
titleLabel.click();
titleLabel.text('Updated Title');
saveButton.click();
scope.$digest();
});
it('shows the pen icon', function () {
expect(penIcon.css('display') !== 'none').toBe(true);
});
it('hides the save and cancel button', function () {
expect(saveButton.parent().css('display') === 'none').toBe(true);
expect(cancelButton.parent().css('display') === 'none').toBe(true);
});
it('makes the title non editable', function () {
expect(titleLabel.attr('contenteditable')).not.toBe('true');
});
it('updates the title in angular context', function () {
expect(scope.activitySet.label).toBe('Updated Title');
});
});
describe('when CANCEL button is clicked', function () {
beforeEach(function () {
titleLabel.click();
titleLabel.text('Updated Title');
cancelButton.click();
scope.$digest();
});
it('shows the pen icon', function () {
expect(penIcon.css('display') !== 'none').toBe(true);
});
it('hides the save and cancel button', function () {
expect(saveButton.parent().css('display') === 'none').toBe(true);
expect(cancelButton.parent().css('display') === 'none').toBe(true);
});
it('makes the title non editable', function () {
expect(titleLabel.attr('contenteditable')).not.toBe('true');
});
it('does not update the title in angular context', function () {
expect(scope.activitySet.label).toBe('Title');
});
});
});
describe('CaseTypeListCtrl', function() {
var caseTypes, crmApiSpy;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment