From e5b8fb8a3acc0855ab7486536d9f9eb01a4b22b0 Mon Sep 17 00:00:00 2001 From: Timur Sufiev Date: Mon, 27 Apr 2015 17:59:38 +0300 Subject: [PATCH] Rewrite and 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 --- karma-unit.conf.js | 1 + merlin/static/merlin/js/merlin.directives.js | 4 +- merlin/static/merlin/js/merlin.init.js | 2 +- merlin/static/merlin/scss/merlin.scss | 24 +---- .../merlin/templates/collapsible-group.html | 12 ++- .../merlin/templates/collapsible-panel.html | 8 +- merlin/test/js/directivesSpec.js | 99 +++++++++++++++---- 7 files changed, 98 insertions(+), 52 deletions(-) diff --git a/karma-unit.conf.js b/karma-unit.conf.js index 97b7327..f283f33 100644 --- a/karma-unit.conf.js +++ b/karma-unit.conf.js @@ -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', diff --git a/merlin/static/merlin/js/merlin.directives.js b/merlin/static/merlin/js/merlin.directives.js index c1a8049..e61a44c 100644 --- a/merlin/static/merlin/js/merlin.directives.js +++ b/merlin/static/merlin/js/merlin.directives.js @@ -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; } diff --git a/merlin/static/merlin/js/merlin.init.js b/merlin/static/merlin/js/merlin.init.js index 4ae70e1..d6edd77 100644 --- a/merlin/static/merlin/js/merlin.init.js +++ b/merlin/static/merlin/js/merlin.init.js @@ -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 diff --git a/merlin/static/merlin/scss/merlin.scss b/merlin/static/merlin/scss/merlin.scss index 8e13b30..e3134df 100644 --- a/merlin/static/merlin/scss/merlin.scss +++ b/merlin/static/merlin/scss/merlin.scss @@ -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"; - } - } } } diff --git a/merlin/static/merlin/templates/collapsible-group.html b/merlin/static/merlin/templates/collapsible-group.html index 6a9c903..35dd57f 100644 --- a/merlin/static/merlin/templates/collapsible-group.html +++ b/merlin/static/merlin/templates/collapsible-group.html @@ -1,17 +1,19 @@
-
+
-
{$ title $}
+
+ + {$ title $}
-
+
-
+
-
+
\ No newline at end of file diff --git a/merlin/static/merlin/templates/collapsible-panel.html b/merlin/static/merlin/templates/collapsible-panel.html index bc16ec0..e84d4b1 100644 --- a/merlin/static/merlin/templates/collapsible-panel.html +++ b/merlin/static/merlin/templates/collapsible-panel.html @@ -1,11 +1,11 @@

- {$ title $} + + + {$ title $}

-
-
-
+
\ No newline at end of file diff --git a/merlin/test/js/directivesSpec.js b/merlin/test/js/directivesSpec.js index da6579a..ba254f8 100644 --- a/merlin/test/js/directivesSpec.js +++ b/merlin/test/js/directivesSpec.js @@ -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('')($scope); + var panel = $compile('')($scope); + $scope.$digest(); + return panel; + } + + function makePanelWithInnerTags() { + var element = $compile('')($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('')($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('', 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( '')($scope); + $scope.$digest(); + return group; } + function makeGroupWithInnerTags() { + var group = $compile( + '' + )($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( - '')($scope); - $scope.$digest(); + var element = makeGroupWithInnerTags(); expect(getGroupBody(element).find('span').hasClass('inner')).toBe(true); });