Rewrite <panel> and <collapsible-group> directives

Use 'collapse' directive from angular-bootstrap inside them instead of
hand-written bootstrap css transitions. Clicking on panel title no
longer collapses/expands panel contents - this is done in order enable
panel entity name in-line editing in next commit.

Change-Id: Ifcc32cd74a5482a59b417333824522ebf48c73b5
Closes-Bug: #1411636
Closes-Bug: #1428719
This commit is contained in:
Timur Sufiev 2015-04-27 17:59:38 +03:00
parent 6fa8b92891
commit e5b8fb8a3a
7 changed files with 98 additions and 52 deletions

View File

@ -39,6 +39,7 @@ module.exports = function (config) {
'merlin/static/merlin/js/libs/underscore/underscore-min.js',
'merlin/static/merlin/js/libs/js-yaml/dist/js-yaml.min.js',
'merlin/static/merlin/js/custom-libs/barricade.js',
'merlin/static/merlin/js/custom-libs/ui-bootstrap-tpls-0.12.1.js',
// explicitly require first module definition file to avoid errors
'merlin/static/merlin/js/merlin.init.js',
'merlin/static/merlin/js/merlin.*.js',

View File

@ -5,7 +5,7 @@
'use strict';
function disableClickDefaultBehaviour(element) {
element.find('a[data-toggle="collapse"]')
element.find('a[ng-click]')
.on('click', function(e) {
e.preventDefault();
return true;
@ -42,6 +42,7 @@
},
link: function(scope, element, attrs) {
scope.removable = $parse(attrs.removable)();
scope.isCollapsed = false;
disableClickDefaultBehaviour(element);
}
}
@ -58,6 +59,7 @@
},
link: function(scope, element, attrs) {
disableClickDefaultBehaviour(element);
scope.isCollapsed = false;
if ( attrs.onAdd && attrs.additive !== 'false' ) {
scope.additive = true;
}

View File

@ -4,7 +4,7 @@
(function() {
'use strict';
angular.module('merlin', [])
angular.module('merlin', ['ui.bootstrap'])
.config(function($interpolateProvider) {
// Replacing the default angular symbol
// allow us to mix angular with django templates

View File

@ -45,16 +45,6 @@
background-color: inherit;
border: none;
padding-left: 20px;
a:before {
font-family: 'FontAwesome';
content: "\f0d7";
margin-left: -10px;
float: left;
color: grey;
}
a.collapsed:before {
content: "\f0da";
}
}
.panel-body {
padding-left: 20px;
@ -75,19 +65,9 @@
padding-left: 5px;
text-decoration: none;
color: black;
}
h5 {
font-weight: bold;
&[data-toggle="collapse"] {
&:before {
font-family: 'FontAwesome';
content: "\f147";
font-weight: normal;
float: left;
color: grey;
}
&.collapsed:before {
content: "\f196";
}
}
}
}

View File

@ -1,17 +1,19 @@
<div class="section">
<div class="three-columns">
<div class="section-heading three-columns">
<div class="both-columns">
<h5><a data-toggle="collapse" data-target="#elem-{$ $id $}" href="#">{$ title $}</a></h5>
<h5><a ng-click="isCollapsed = !isCollapsed" class="collapse-entries" href="#">
<i class="fa" ng-class="isCollapsed ? 'fa-plus-square-o' : 'fa-minus-square-o'"></i></a>
{$ title $}</h5>
</div>
<div ng-show="additive" class="add-btn button-column">
<div ng-show="additive" class="add-btn button-column add-entry">
<button class="btn btn-default btn-sm pull-right" ng-click="onAdd()">
<i class="fa fa-plus"></i></button>
</div>
<div ng-show="removable" class="add-btn button-column">
<div ng-show="removable" class="add-btn button-column remove-entry">
<a href="#" ng-click="onRemove()">
<i class="fa fa-times-circle pull-right"></i></a>
</div>
</div>
<div id="elem-{$ $id $}" class="collapse in" ng-transclude>
<div class="section-body" collapse="isCollapsed" ng-transclude>
</div>
</div>

View File

@ -1,11 +1,11 @@
<div class="panel panel-default merlin-panel">
<div class="panel-heading" ng-show="title">
<h4 class="panel-title">
<a data-toggle="collapse" data-target="#elem-{$ $id $}" href="#">{$ title $}</a>
<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>
</div>
<div id="elem-{$ $id $}" ng-class="title ? 'panel-collapse collapse in' : ''">
<div class="panel-body" ng-transclude>
</div>
<div collapse="isCollapsed" class="panel-body" ng-transclude>
</div>
</div>

View File

@ -42,24 +42,35 @@ describe('merlin directives', function() {
}
function getPanelRemoveButton(panelElem) {
var iTag = panelElem.find('i').eq(0);
var iTag = panelElem.find('i').eq(1);
return iTag.hasClass('fa-times-circle') && iTag;
}
function getCollapseBtn(groupElem) {
return groupElem.find('a').eq(0);
}
function getPanelBody(panelElem) {
var div = panelElem.children().children().eq(1).children();
var div = panelElem.children().children().eq(1);
return div.hasClass('panel-body') && div;
}
function makePanelElem(contents) {
return $compile('<panel ' + contents + '></panel>')($scope);
var panel = $compile('<panel ' + contents + '></panel>')($scope);
$scope.$digest();
return panel;
}
function makePanelWithInnerTags() {
var element = $compile('<panel><span class="inner"></span></panel>')($scope);
$scope.$digest();
return element;
}
it('shows panel heading when and only when title is passed via attr', function() {
var title = 'My Panel',
element1 = makePanelElem('title="' + title +'"'),
element2 = makePanelElem('');
$scope.$digest();
expect(getPanelHeading(element1).hasClass('ng-hide')).toBe(false);
expect(element1.html()).toContain(title);
@ -72,7 +83,6 @@ describe('merlin directives', function() {
element1 = makePanelElem('title="' + title +'" removable="true"');
element2 = makePanelElem('title="' + title +'"');
$scope.$digest();
expect(getPanelRemoveButton(element1).hasClass('ng-hide')).toBe(false);
expect(getPanelRemoveButton(element2).hasClass('ng-hide')).toBe(true);
@ -86,17 +96,37 @@ describe('merlin directives', function() {
element1 = makePanelElem(
'title="' + title +'" removable="true" on-remove="remove()"');
element2 = makePanelElem('title="' + title +'" on-remove="remove()"');
$scope.$digest();
expect(getPanelRemoveButton(element1).hasClass('ng-hide')).toBe(false);
expect(getPanelRemoveButton(element2).hasClass('ng-hide')).toBe(true);
});
it('contents are inserted into div.panel-body tag', function() {
var element = $compile('<panel><span class="inner"></span></panel>')($scope);
$scope.$digest();
var panel = makePanelWithInnerTags();
expect(getPanelBody(element).find('span').hasClass('inner')).toBe(true);
expect(getPanelBody(panel).find('span').hasClass('inner')).toBe(true);
});
it('starts as being expanded', function() {
var panel = makePanelWithInnerTags(),
body = getPanelBody(panel);
expect(body.hasClass('collapse')).toBe(true);
expect(body.hasClass('in')).toBe(true);
});
it('starts to collapse after pressing on triangle next to group title', function() {
// NOTE(tsufiev): I wasn't able to test the final .collapse state (without .in)
// most probably due to transition from .collapse.in -> .collapsing -> .collapse
// is made with means of CSS, not
var element = makePanelWithInnerTags(),
body = getPanelBody(element),
link = getCollapseBtn(element);
link.triggerHandler('click');
expect(body.hasClass('collapse')).toBe(false);
expect(body.hasClass('collapsing')).toBe(true);
});
});
@ -104,30 +134,65 @@ describe('merlin directives', function() {
describe('<collapsible-group>', function() {
function getGroupBody(groupElem) {
var div = groupElem.children().children().eq(1);
return div.hasClass('collapse') && div;
return div.hasClass('section-body') && div;
}
function getGroupRemoveBtn(groupElem) {
var div = groupElem.children().children().eq(0).children().eq(2);
return div.hasClass('add-btn') && div;
return div.hasClass('remove-entry') && div;
}
function getGroupAddBtn(groupElem) {
var div = groupElem.children().children().eq(0).children().eq(1);
return div.hasClass('add-btn') && div;
return div.hasClass('add-entry') && div;
}
function getCollapseBtn(groupElem) {
return groupElem.children().children().eq(0).children().eq(0).find('a');
}
function makeGroupElement(contents) {
return $compile(
var group = $compile(
'<collapsible-group ' + contents + '></collapsible-group>')($scope);
$scope.$digest();
return group;
}
function makeGroupWithInnerTags() {
var group = $compile(
'<collapsible-group><span class="inner"></span></collapsible-group>'
)($scope);
$scope.$digest();
return group;
}
it('starts as being expanded', function() {
var element = makeGroupWithInnerTags(),
body = getGroupBody(element);
expect(body.hasClass('collapse')).toBe(true);
expect(body.hasClass('in')).toBe(true);
});
it('starts to collapse after pressing on triangle next to group title', function() {
// NOTE(tsufiev): I wasn't able to test the final .collapse state (without .in)
// most probably due to transition from .collapse.in -> .collapsing -> .collapse
// is made with means of CSS, not
var element = makeGroupWithInnerTags(),
body = getGroupBody(element),
link = getCollapseBtn(element);
link.triggerHandler('click');
expect(body.hasClass('collapse')).toBe(false);
expect(body.hasClass('collapsing')).toBe(true);
});
it('requires to specify just `on-remove` to make group removable', function() {
var element1, element2;
$scope.remove = function() {};
element1 = makeGroupElement('');
element2 = makeGroupElement('on-remove="remove()"');
$scope.$digest();
expect(getGroupRemoveBtn(element1).hasClass('ng-hide')).toBe(true);
expect(getGroupRemoveBtn(element2).hasClass('ng-hide')).toBe(false);
@ -138,7 +203,6 @@ describe('merlin directives', function() {
$scope.add = function() {};
element1 = makeGroupElement('');
element2 = makeGroupElement('on-add="add()"');
$scope.$digest();
expect(getGroupAddBtn(element1).hasClass('ng-hide')).toBe(true);
expect(getGroupAddBtn(element2).hasClass('ng-hide')).toBe(false);
@ -148,15 +212,12 @@ describe('merlin directives', function() {
var element;
$scope.add = function() {};
element = makeGroupElement('on-add="add()" additive="false"');
$scope.$digest();
expect(getGroupAddBtn(element).hasClass('ng-hide')).toBe(true);
});
it('contents are inserted into div.collapse tag', function() {
var element = $compile(
'<collapsible-group><span class="inner"></span></collapsible-group>')($scope);
$scope.$digest();
var element = makeGroupWithInnerTags();
expect(getGroupBody(element).find('span').hasClass('inner')).toBe(true);
});