Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ module.exports = function (grunt) {
src: ['filters/**/*.html'],
dest: 'templates/filters.js'
},
'patternfly.modals': {
cwd: 'src/',
src: ['modals/**/*.html'],
dest: 'templates/modals.js'
},
'patternfly.sort': {
cwd: 'src/',
src: ['sort/**/*.html'],
Expand Down
143 changes: 143 additions & 0 deletions src/modals/about-modal.directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* @ngdoc directive
* @name patternfly.modals.directive:pfAboutModal
*
* @description
* Directive for rendering modal windows.
*
* @param {string=} additionalInfo Text explaining the version or copyright
* @param {string=} copyright Product copyright information
* @param {string=} imgAlt The alt text for the corner grahpic
* @param {string=} imgSrc The source for the corner grahpic
* @param {boolean=} isOpen Flag indicating that the modal should be opened
* @param {function=} onClose Function to call when modal is closed
* @param {object=} productInfo data for the modal:<br/>
* <ul style='list-style-type: none'>
* <li>.product - the product label
* <li>.version - the product version
* </ul>
* @param {string=} title The product title for the modal
*
* @example
<example module="patternfly.modals">
<file name="index.html">
<div ng-controller="ModalCtrl">
<button ng-click="open()" class="btn btn-default">Launch About Modal</button>
<div pf-about-modal is-open="isOpen" on-close="onClose()" additional-info="additionalInfo"
product-info="productInfo" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"></div>
</div>
</file>
<file name="script.js">
angular.module('patternfly.modals').controller('ModalCtrl', function ($scope) {
$scope.additionalInfo = "Donec consequat dignissim neque, sed suscipit quam egestas in. Fusce bibendum " +
"laoreet lectus commodo interdum. Vestibulum odio ipsum, tristique et ante vel, iaculis placerat nulla. " +
"Suspendisse iaculis urna feugiat lorem semper, ut iaculis risus tempus.";
$scope.copyright = "Trademark and Copyright Information";
$scope.imgAlt = "Patternfly Symbol";
$scope.imgSrc = "img/logo-alt.svg";
$scope.title = "Product Title";
$scope.productInfo = [
{ name: 'Version', value: '1.0.0.0.20160819142038_51be77c' },
{ name: 'Server Name', value: 'Localhost' },
{ name: 'User Name', value: 'admin' },
{ name: 'User Role', value: 'Administrator' }];
$scope.open = function () {
$scope.isOpen = true;
}
$scope.onClose = function() {
$scope.isOpen = false;
}
});
</file>
</example>
*/
angular.module('patternfly.modals')

.directive("pfAboutModalTransclude", function ($parse) {
'use strict';
return {
link: function (scope, element, attrs) {
element.append($parse(attrs.pfAboutModalTransclude)(scope));
}
};
})

.directive('pfAboutModal', function () {
'use strict';
return {
restrict: 'A',
scope: {
additionalInfo: '=?',
copyright: '=?',
close: "&onClose",
imgAlt: '=?',
imgSrc: '=?',
isOpen: '=?',
productInfo: '=',
title: '=?'
},
templateUrl: 'modals/about-modal.html',
transclude: true,
controller: ['$scope', '$modal', '$transclude', function ($scope, $modal, $transclude) {
if ($scope.isOpen === undefined) {
$scope.isOpen = false;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should remove the $watch listener when the directive is destroyed:

var listener = $watch(...
$scope.$on('destroy', listener);

// The ui-bootstrap modal only supports either template or templateUrl as a way to specify the content.
// When the content is retrieved, it is compiled and linked against the provided scope by the $modal service.
// Unfortunately, there is no way to provide transclusion there.
//
// The solution below embeds a placeholder directive (i.e., pfAboutModalTransclude) to append the transcluded DOM.
// The transcluded DOM is from a different location than the modal, so it needs to be handed over to the
// placeholder directive. Thus, we're passing the actual DOM, not the parsed HTML.
$scope.openModal = function () {
$modal.open({
controller: ['$scope', '$modalInstance', 'content', function ($scope, $modalInstance, content) {
$scope.template = content;
$scope.close = function () {
$modalInstance.close();
};
$scope.$watch(
function () {
return $scope.isOpen;
},
function (newValue) {
if (newValue === false) {
$modalInstance.close();
}
}
);
}],
resolve: {
content: function () {
var transcludedContent;
$transclude(function (clone) {
transcludedContent = clone;
});
return transcludedContent;
}
},
scope: $scope,
templateUrl: "about-modal-template.html"
})
.result.then(
function () {
$scope.close(); // closed
},
function () {
$scope.close(); // dismissed
}
);
};
}],
link: function (scope, element, attrs) {
// watching isOpen attribute to dispay modal when needed
var isOpenListener = scope.$watch('isOpen', function (newVal, oldVal) {
if (newVal === true) {
scope.openModal();
}
});
scope.$on('$destroy', isOpenListener);
}
};
});
23 changes: 23 additions & 0 deletions src/modals/about-modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script type="text/ng-template" id="about-modal-template.html">
<div class="about-modal-pf">
<div class="modal-header">
<button type="button" class="close" ng-click="close()" aria-hidden="true">
<span class="pficon pficon-close"></span>
</button>
</div>
<div class="modal-body">
<h1 ng-if="title">{{title}}</h1>
<div ng-if="productInfo && productInfo.length > 0" class="product-versions-pf">
<ul class="list-unstyled">
<li ng-repeat="info in productInfo"><strong>{{info.name}}</strong> {{info.value}}</li>
</ul>
</div>
<div pf-about-modal-transclude="template" class="product-versions-pf"></div>
<div ng-if="additionalInfo" class="product-versions-pf">{{additionalInfo}}</div>
<div ng-if="copyright" class="trademark-pf">{{copyright}}</div>
</div>
<div class="modal-footer">
<img ng-if="imgSrc" ng-src="{{imgSrc}}" alt="{{imgAlt}}"/>
</div>
</div>
</script>
8 changes: 8 additions & 0 deletions src/modals/modals.module.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* @name patternfly
*
* @description
* Modal module for patternfly.
*
*/
angular.module('patternfly.modals', ['ui.bootstrap.modal', 'ui.bootstrap.tpls']);
1 change: 1 addition & 0 deletions src/patternfly.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ angular.module('patternfly', [
'patternfly.card',
'patternfly.filters',
'patternfly.form',
'patternfly.modals',
'patternfly.navigation',
'patternfly.notification',
'patternfly.select',
Expand Down
181 changes: 181 additions & 0 deletions test/modals/about-modal.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
describe('Directive: pfABoutModal', function () {
var $scope;
var $compile;

// load the controller's module
beforeEach(module(
'patternfly.modals',
'modals/about-modal.html'
));

beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$scope = _$rootScope_;
}));

var compileHtml = function (markup, scope) {
var element = angular.element(markup);
$compile(element)(scope);
scope.$digest();
return element;
};

var closeModal = function(scope) {
scope.isOpen = false;
scope.$digest();

// Although callbacks are executed properly, the modal is not removed in this
// environment -- must remove it manually to mimic UI Bootstrap.
var modal = getModal();
if (modal) {
modal.remove();
}
var modalBackdrop = angular.element(document.querySelector('.modal-backdrop'));
if (modalBackdrop) {
modalBackdrop.remove();
}
};

// Modal elements are located in a template, so wait until modal is shown.
var getModal = function () {
return angular.element(document.querySelector('.modal'));
};

var openModal = function(scope) {
scope.isOpen = true;
scope.$digest();
};

beforeEach(function () {
closeModal($scope);
$scope.copyright = "Copyright Information";
$scope.imgAlt = "Patternfly Symbol";
$scope.imgSrc = "img/logo-alt.svg";
$scope.title = "Product Title";
$scope.isOpen = true;
$scope.productInfo = [
{ product: 'Label', version: 'Version' },
{ product: 'Label', version: 'Version' },
{ product: 'Label', version: 'Version' },
{ product: 'Label', version: 'Version' },
{ product: 'Label', version: 'Version' },
{ product: 'Label', version: 'Version' },
{ product: 'Label', version: 'Version' }];
$scope.open = function () {
$scope.isOpen = true;
}
$scope.onClose = function() {
$scope.isOpen = false;
}
});

it('should invoke the onClose callback when close button is clicked', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var closeButton = angular.element(getModal()).find('button');
eventFire(closeButton[0], 'click');
$scope.$digest();
expect($scope.isOpen).toBe(false);
});

it('should open the about modal via an external button click', function () {
$scope.isOpen = false;
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var buttonHtml = '<button ng-click="open()" class="btn btn-default">Launch about modal</button>';
var closeButton = compileHtml(buttonHtml, $scope);
eventFire(closeButton[0], 'click');
$scope.$digest();
expect($scope.isOpen).toBe(true);
expect(angular.element(getModal()).find('h1').length).toBe(1);
});

it('should open the about modal programmatically', function () {
$scope.isOpen = false;
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
expect(angular.element(getModal()).find('h1').length).toBe(0);
openModal($scope);
expect(angular.element(getModal()).find('h1').length).toBe(1);
});

it('should not open the about modal', function () {
var modalHtml = '<div pf-about-modal is-open="false" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
expect(angular.element(getModal()).find('h1').length).toBe(0);
expect(angular.element(getModal()).find('.trademark-pf').length).toBe(0);
});

it('should set the product title', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
expect(angular.element(getModal()).find('h1').html()).toBe('Product Title');
});

it('should not show product title when a title is not supplied', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
expect(angular.element(getModal()).find('h1').length).toBe(0);
});

it('should set the product copyright', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
expect(angular.element(getModal()).find('.trademark-pf').html()).toBe('Copyright Information');
});

it('should not show product copyright when a copyright is not supplied', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
expect(angular.element(getModal()).find('.trademark-pf').length).toBe(0);
});

it('should set the corner graphic alt text', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var footer = angular.element(getModal()).find('.modal-footer');
expect(angular.element(footer).find('img').attr('alt')).toBe('Patternfly Symbol');
});

it('should not show alt text for corner graphic when imgAlt is not supplied', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var footer = angular.element(getModal()).find('.modal-footer');
expect(angular.element(footer).find('img').attr('alt').length).toBe(0);
});

it('should set the corner graphic src', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var footer = angular.element(getModal()).find('.modal-footer');
expect(angular.element(footer).find('img').attr('src')).toBe('img/logo-alt.svg');
});

it('should not show corner graphic when imgSrc is not supplied', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var footer = angular.element(getModal()).find('.modal-footer');
expect(angular.element(footer).find('img').length).toBe(0);
});

it('should show simple content', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc" product-info="productInfo"></div>';
compileHtml(modalHtml, $scope);
var transclude = angular.element(getModal()).find('.product-versions-pf');
expect(angular.element(transclude).find('ul').length).toBe(1);
});

it('should show custom content', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"><ul class="list-unstyled"><li><strong>Label</strong> Version</li></ul></div>';
compileHtml(modalHtml, $scope);
var transclude = angular.element(getModal()).find('.product-versions-pf');
expect(angular.element(transclude).find('ul').length).toBe(1);
});

it('should not show content', function () {
var modalHtml = '<div pf-about-modal is-open="isOpen" on-close="onClose()" title="title" copyright="copyright" img-alt="imgAlt" img-src="imgSrc"></div>';
compileHtml(modalHtml, $scope);
var transclude = angular.element(getModal()).find('.product-versions-pf');
expect(angular.element(transclude).find('ul').length).toBe(0);
});
});