Rewrite <editable> directive
Instead of showing popup for the value being edited it now allows editing the same value in-place, accompanied with 2 buttons - confirm & reject the edit. Only when one of 2 these buttons is pressed (or equivalend Enter/Esc keys are pressed) the value will be applied. The <editable> directive could be used both for changing Action/Workflow entity names, for changing dictionary and varlist keys. Change-Id: I4e7e1c0fcaed71aa649b1c9925a9b53005ff9a2d Closes-Bug: #1411649
This commit is contained in:
parent
e5b8fb8a3a
commit
0efc6355f7
|
@ -37,7 +37,7 @@
|
|||
<div class="right-column">
|
||||
<div class="form-group">
|
||||
<label for="elem-{$ $id $}.{$ key $}">
|
||||
<editable value="key" label="New Name"></editable>
|
||||
<editable ng-model="value.keyValue" ng-model-options="{getterSetter: true}"></editable>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="elem-{$ $id $}.{$ key $}" class="form-control" ng-model="value.value"
|
||||
|
|
|
@ -63,8 +63,7 @@
|
|||
<div class="two-panels">
|
||||
<div class="left-panel">
|
||||
<panel ng-repeat="panel in workbook | extractPanels track by panel.id"
|
||||
title="{$ panel.getTitle() $}" removable="{$ panel.removable $}"
|
||||
on-remove="panel.remove()">
|
||||
content="panel">
|
||||
<div ng-repeat="row in panel | extractRows track by row.id">
|
||||
<div ng-class="{'two-columns': row.index !== undefined }">
|
||||
<div ng-repeat="item in row | extractItems track by item.id"
|
||||
|
|
|
@ -34,6 +34,7 @@ module.exports = function (config) {
|
|||
],
|
||||
|
||||
files: [
|
||||
'bower_components/jquery/dist/jquery.min.js',
|
||||
'bower_components/angular/angular.js',
|
||||
'bower_components/angular-mocks/angular-mocks.js',
|
||||
'merlin/static/merlin/js/libs/underscore/underscore-min.js',
|
||||
|
|
|
@ -16,29 +16,72 @@
|
|||
.directive('editable', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/static/merlin/templates/editable-popup.html',
|
||||
scope: {
|
||||
label: '@',
|
||||
value: '='
|
||||
},
|
||||
link: function(scope, element) {
|
||||
angular.element(element).find('a[data-toggle="popover"]')
|
||||
.popover({html: true})
|
||||
.on('click', function(e) {
|
||||
e.preventDefault();
|
||||
return true;
|
||||
});
|
||||
templateUrl: '/static/merlin/templates/editable.html',
|
||||
require: 'ngModel',
|
||||
scope: true,
|
||||
link: function(scope, element, attrs, ngModelCtrl) {
|
||||
var hiddenSpan = element.find('span.width-detector'),
|
||||
input = element.find('input'),
|
||||
maxWidth = 400;
|
||||
|
||||
function adjustWidth() {
|
||||
var width;
|
||||
hiddenSpan.html(scope.editableValue);
|
||||
width = hiddenSpan.width();
|
||||
input.width(width <= maxWidth ? width : maxWidth);
|
||||
}
|
||||
|
||||
function accept() {
|
||||
ngModelCtrl.$setViewValue(scope.editableValue);
|
||||
scope.isEdited = false;
|
||||
}
|
||||
|
||||
function reject() {
|
||||
ngModelCtrl.$rollbackViewValue();
|
||||
scope.isEdited = false;
|
||||
}
|
||||
|
||||
scope.isEdited = false;
|
||||
scope.$watch('editableValue', function() {
|
||||
adjustWidth();
|
||||
});
|
||||
input.on('keyup', function(e) {
|
||||
if ( e.keyCode == 13 ) {
|
||||
accept();
|
||||
scope.$apply();
|
||||
} else if (e.keyCode == 27 ) {
|
||||
reject();
|
||||
scope.$apply();
|
||||
}
|
||||
});
|
||||
ngModelCtrl.$render = function() {
|
||||
if ( !ngModelCtrl.$viewValue ) {
|
||||
ngModelCtrl.$viewValue = ngModelCtrl.$modelValue;
|
||||
}
|
||||
scope.editableValue = ngModelCtrl.$viewValue;
|
||||
adjustWidth();
|
||||
};
|
||||
scope.accept = accept;
|
||||
scope.reject = reject;
|
||||
}
|
||||
};
|
||||
})
|
||||
.directive('showFocus', function($timeout) {
|
||||
return function(scope, element, attrs) {
|
||||
scope.$watch(attrs.showFocus, function(newValue) {
|
||||
$timeout(function() {
|
||||
newValue && element.focus();
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.directive('panel', function($parse) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: '/static/merlin/templates/collapsible-panel.html',
|
||||
transclude: true,
|
||||
scope: {
|
||||
title: '@',
|
||||
onRemove: '&'
|
||||
panel: '=content'
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
scope.removable = $parse(attrs.removable)();
|
||||
|
|
|
@ -188,6 +188,13 @@
|
|||
if ( !Object.keys(_items).length ) {
|
||||
self.getIDs().forEach(function(id) {
|
||||
_items[id] = self.getByID(id);
|
||||
_items[id].keyValue = function() {
|
||||
if ( !arguments.length ) {
|
||||
return this.getID();
|
||||
} else {
|
||||
this.setID(arguments[0]);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
return _items;
|
||||
|
|
|
@ -36,9 +36,15 @@
|
|||
}
|
||||
return this;
|
||||
},
|
||||
getTitle: function() {
|
||||
title: function() {
|
||||
var entity;
|
||||
if ( this._barricadeContainer ) {
|
||||
return this._barricadeContainer.getByID(this._barricadeId).get('name');
|
||||
entity = this._barricadeContainer.getByID(this._barricadeId).get('name');
|
||||
if ( arguments.length ) {
|
||||
entity.set(arguments[0]);
|
||||
} else {
|
||||
return entity.get();
|
||||
}
|
||||
}
|
||||
},
|
||||
remove: function() {
|
||||
|
|
|
@ -54,6 +54,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
editable .fa-pencil {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.section {
|
||||
.form-group {
|
||||
padding-left: 15px;
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<div class="panel panel-default merlin-panel">
|
||||
<div class="panel-heading" ng-show="title">
|
||||
<div class="panel-heading" ng-show="panel.title()">
|
||||
<h4 class="panel-title">
|
||||
<a ng-click="isCollapsed = !isCollapsed" href="#">
|
||||
<i class="fa fa-lg" ng-class="isCollapsed ? 'fa-caret-right' : 'fa-caret-down'"></i></a>
|
||||
{$ title $}
|
||||
<a href="#" ng-click="onRemove()"><i ng-show="removable" class="fa fa-times-circle pull-right"></i></a></h4>
|
||||
<editable ng-model="panel.title" ng-model-options="{getterSetter: true}"></editable>
|
||||
<a href="#" ng-show="panel.removable" ng-click="panel.remove()">
|
||||
<i class="fa fa-times-circle pull-right"></i></a>
|
||||
</h4>
|
||||
</div>
|
||||
<div collapse="isCollapsed" class="panel-body" ng-transclude>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
{$ value $}
|
||||
<a href="#" data-toggle="popover" data-trigger="click" data-container="body"
|
||||
data-content="<label>{$ label $}</label>
|
||||
<input type='text' class='form-control' value='{$ value $}'>
|
||||
<button class='btn btn-default btn-sm cancel'>Cancel</button>
|
||||
<button class='btn btn-primary btn-sm'>Save</button>">
|
||||
<i class="fa fa-pencil"></i></a>
|
|
@ -0,0 +1,10 @@
|
|||
<span class="width-detector" style="display:none"></span>
|
||||
<span ng-show="!isEdited">
|
||||
<span ng-bind="editableValue"></span>
|
||||
<a ng-click="isEdited = true" href="#"><i class="fa fa-pencil"></i></a>
|
||||
</span>
|
||||
<span ng-show="isEdited">
|
||||
<input type="text" ng-model="editableValue" show-focus="isEdited">
|
||||
<button ng-click="accept()"><i class="fa fa-check"></i></button>
|
||||
<button ng-click="reject()"><i class="fa fa-close"></i></button>
|
||||
</span>
|
|
@ -3,7 +3,7 @@
|
|||
<div class="left-column">
|
||||
<div class="form-group">
|
||||
<label for="elem-{$ $id $}.{$ key $}">
|
||||
<editable value="key" label="New Name"></editable>
|
||||
<editable ng-model="subvalue.keyValue" ng-model-options="{getterSetter: true}"></editable>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input id="elem-{$ $id $}.{$ key $}" type="text" class="form-control" ng-model="subvalue.value"
|
||||
|
|
|
@ -42,12 +42,11 @@ describe('merlin directives', function() {
|
|||
}
|
||||
|
||||
function getPanelRemoveButton(panelElem) {
|
||||
var iTag = panelElem.find('i').eq(1);
|
||||
return iTag.hasClass('fa-times-circle') && iTag;
|
||||
return panelElem.find('a').eq(2);
|
||||
}
|
||||
|
||||
function getCollapseBtn(groupElem) {
|
||||
return groupElem.find('a').eq(0);
|
||||
function getCollapseBtn(panelElem) {
|
||||
return panelElem.find('a').eq(0);
|
||||
}
|
||||
|
||||
function getPanelBody(panelElem) {
|
||||
|
@ -55,8 +54,8 @@ describe('merlin directives', function() {
|
|||
return div.hasClass('panel-body') && div;
|
||||
}
|
||||
|
||||
function makePanelElem(contents) {
|
||||
var panel = $compile('<panel ' + contents + '></panel>')($scope);
|
||||
function makePanelElem(content) {
|
||||
var panel = $compile('<panel content="' + content + '"></panel>')($scope);
|
||||
$scope.$digest();
|
||||
return panel;
|
||||
}
|
||||
|
@ -67,35 +66,35 @@ describe('merlin directives', function() {
|
|||
return element;
|
||||
}
|
||||
|
||||
it('shows panel heading when and only when title is passed via attr', function() {
|
||||
it('shows panel heading when and only when its title() is not false', function() {
|
||||
var title = 'My Panel',
|
||||
element1 = makePanelElem('title="' + title +'"'),
|
||||
element2 = makePanelElem('');
|
||||
element1, element2;
|
||||
|
||||
$scope.panel1 = {
|
||||
title: function() { return title; }
|
||||
};
|
||||
$scope.panel2 = {};
|
||||
element1 = makePanelElem('panel1');
|
||||
element2 = makePanelElem('');
|
||||
|
||||
expect(getPanelHeading(element1).hasClass('ng-hide')).toBe(false);
|
||||
expect(element1.html()).toContain(title);
|
||||
expect(getPanelHeading(element2).hasClass('ng-hide')).toBe(true);
|
||||
});
|
||||
|
||||
it('requires both `title` and `removable` to be removable', function() {
|
||||
it('requires both `.title()` and `.removable` to be removable', function() {
|
||||
var title = 'My Panel',
|
||||
element1, element2;
|
||||
|
||||
element1 = makePanelElem('title="' + title +'" removable="true"');
|
||||
element2 = makePanelElem('title="' + title +'"');
|
||||
|
||||
expect(getPanelRemoveButton(element1).hasClass('ng-hide')).toBe(false);
|
||||
expect(getPanelRemoveButton(element2).hasClass('ng-hide')).toBe(true);
|
||||
});
|
||||
|
||||
it('with `on-remove`, but without `removable` is not removable', function() {
|
||||
var title = 'My Panel',
|
||||
element1, element2;
|
||||
|
||||
$scope.remove = function() {};
|
||||
element1 = makePanelElem(
|
||||
'title="' + title +'" removable="true" on-remove="remove()"');
|
||||
element2 = makePanelElem('title="' + title +'" on-remove="remove()"');
|
||||
$scope.panel1 = {
|
||||
title: function() { return title; },
|
||||
removable: true
|
||||
};
|
||||
$scope.panel2 = {
|
||||
title: function() { return title; }
|
||||
};
|
||||
element1 = makePanelElem('panel1');
|
||||
element2 = makePanelElem('panel2');
|
||||
|
||||
expect(getPanelRemoveButton(element1).hasClass('ng-hide')).toBe(false);
|
||||
expect(getPanelRemoveButton(element2).hasClass('ng-hide')).toBe(true);
|
||||
|
@ -253,4 +252,67 @@ describe('merlin directives', function() {
|
|||
expect(element.html()).toContain('<textarea');
|
||||
})
|
||||
});
|
||||
|
||||
xdescribe("'show-focus'", function() {
|
||||
var element;
|
||||
|
||||
beforeEach(function() {
|
||||
element = $compile(
|
||||
'<div><input type="text" ng-show="show" show-focus="show"></div>')($scope);
|
||||
$scope.$digest();
|
||||
});
|
||||
|
||||
it('allows to immediately set focus on element after it was shown', function() {
|
||||
expect(element.is(':focus')).toBe(false);
|
||||
|
||||
$scope.show = true;
|
||||
$scope.$apply();
|
||||
|
||||
expect(element.is(':focus')).toBe(true);
|
||||
})
|
||||
});
|
||||
|
||||
describe('<editable>', function() {
|
||||
it('starts with the value not being edited', function() {
|
||||
|
||||
});
|
||||
|
||||
it("enters the editing mode once user clicks 'fa-pencil' icon", function() {
|
||||
|
||||
});
|
||||
|
||||
describe('during editing', function() {
|
||||
it("pressing any key except 'Enter' or 'Esc' neither exits editing state, nor changes model", function() {
|
||||
|
||||
});
|
||||
|
||||
it("pressing 'Enter' key changes model and exits editing state", function() {
|
||||
|
||||
});
|
||||
|
||||
it("clicking 'fa-check' icon changes model and exits editing state", function() {
|
||||
|
||||
});
|
||||
|
||||
it("pressing 'Esc' key exits editing state without changing model", function() {
|
||||
|
||||
});
|
||||
|
||||
it("clicking 'fa-close' icon exits editing state without changing model", function() {
|
||||
|
||||
});
|
||||
|
||||
describe('edit box automatically enlarges', function() {
|
||||
it('to fit the value being edited', function() {
|
||||
|
||||
});
|
||||
|
||||
it('up to the limit', function() {
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue