JSCS cleanup - Angular framework widgets (partial2)

We need to do cleanup before we can enable JSCS globally
(https://review.openstack.org/#/c/185725/).

Widgets included: modal, modal-wait-spinner, table, toast,
transfer-table, wizard

Change-Id: Ifa53fde6e0c4119a092e657fc07627ffb68dd248
Partially-Implements: blueprint jscs-cleanup
This commit is contained in:
Aaron Sahlin 2015-06-02 15:26:00 -05:00 committed by Cindy Lu
parent 5b17c69a9a
commit 985680d60d
12 changed files with 188 additions and 175 deletions

View File

@ -17,44 +17,45 @@
(function () {
'use strict';
/**
@ngdoc overview
@name horizon.framework.widgets.modal-wait-spinner
@description
A "global" wait spinner that displays a line of text followed by "...".
Requires {@link http://angular-ui.github.io/bootstrap/ `Angular-bootstrap`}
Used when the user must wait before any additional action is possible. Can be launched from modal dialogs.
@example
<example>
<pre>
.controller('MyController', [
'$scope',
'horizon.framework.widgets.modal.modal-wait-spinner.service',
function (modalWaitSpinnerService) {
$scope.showSpinner = function () {
modalWaitSpinnerService.showModalSpinner(gettext("Loading"));
}
$scope.hideSpinner = function () {
modalWaitSpinnerService.hideModalSpinner();
}
}
])
</pre>
</example>
In order to provide a seamless transition to a Horizon that uses more Angular
based pages, the service is currently implemented using the existing
Spin.js library and the corresponding JQuery plugin (jquery.spin.js). This widget looks and feels
the same as the existing spinner we are familiar with in Horizon. Over time, uses of the existing
Horizon spinner ( horizon.modals.modal_spinner() ) can be phased out, or refactored to use this
component.
*/
/*
* @ngdoc overview
* @name horizon.framework.widgets.modal-wait-spinner
* @description
* A "global" wait spinner that displays a line of text followed by "...".
*
* Requires {@link http://angular-ui.github.io/bootstrap/ `Angular-bootstrap`}
*
* Used when the user must wait before any additional action is possible. Can be
* launched from modal dialogs.
*
* @example
* <example>
* <pre>
* .controller('MyController', [
* '$scope',
* 'horizon.framework.widgets.modal.modal-wait-spinner.service',
* function (modalWaitSpinnerService) {
* $scope.showSpinner = function () {
* modalWaitSpinnerService.showModalSpinner(gettext("Loading"));
* }
* $scope.hideSpinner = function () {
* modalWaitSpinnerService.hideModalSpinner();
* }
* }
* ])
* </pre>
* </example>
*
* In order to provide a seamless transition to a Horizon that uses more Angular
* based pages, the service is currently implemented using the existing
* Spin.js library and the corresponding JQuery plugin (jquery.spin.js). This widget
* looks and feels the same as the existing spinner we are familiar with in Horizon.
* Over time, uses of the existing Horizon spinner ( horizon.modals.modal_spinner() )
* can be phased out, or refactored to use this component.
*/
angular.module('horizon.framework.widgets.modal-wait-spinner', [
'ui.bootstrap',
'ui.bootstrap'
])
.factory('horizon.framework.widgets.modal-wait-spinner.service', [
'$modal',
@ -63,13 +64,13 @@
var service = {
showModalSpinner: function (spinnerText) {
var modalOptions = {
backdrop: 'static',
backdrop: 'static',
/*
Using <div> for wait-spinner instead of a wait-spinner element
because the existing Horizon spinner CSS styling expects a div
for the modal-body
* Using <div> for wait-spinner instead of a wait-spinner element
* because the existing Horizon spinner CSS styling expects a div
* for the modal-body
*/
template: '<div wait-spinner class="modal-body" text="' + spinnerText + '"></div>',
template: '<div wait-spinner class="modal-body" text="' + spinnerText + '"></div>',
windowClass: 'modal-wait-spinner modal_wrapper loading'
};
this.modalInstance = $modal.open(modalOptions);
@ -89,26 +90,26 @@
.directive('waitSpinner', [
'horizon.framework.conf.spinner_options',
function (spinner_options) {
function (spinnerOptions) {
return {
scope: {
text: '@text' // One-direction binding (reads from parent)
},
restrict: 'A',
link: link,
template: '<p><i>{$text$}&hellip;</i></p>'
};
return {
scope: {
text: '@text' // One-direction binding (reads from parent)
},
restrict: 'A',
link: link,
template: '<p><i>{$text$}&hellip;</i></p>'
};
function link($scope, element) {
element.spin(spinner_options.modal);
/*
At the time link is executed, element may not have been sized by the browser.
Spin.js may mistakenly places the spinner at 50% of 0 (left:0, top:0). To work around
this, explicitly set 50% left and top to center the spinner in the parent
container
*/
element.find('.spinner').css({'left': '50%', 'top': '50%'});
}
}]);
function link($scope, element) {
element.spin(spinnerOptions.modal);
/*
* At the time link is executed, element may not have been sized by the browser.
* Spin.js may mistakenly places the spinner at 50% of 0 (left:0, top:0). To work around
* this, explicitly set 50% left and top to center the spinner in the parent
* container
*/
element.find('.spinner').css({'left': '50%', 'top': '50%'});
}
}]);
})();

View File

@ -37,8 +37,8 @@
.controller('simpleModalCtrl', [ '$scope', '$modalInstance', 'context',
function($scope, $modalInstance, context) {
$scope.context = context;
$scope.submit = function(){ $modalInstance.close(); };
$scope.cancel = function(){ $modalInstance.dismiss('cancel'); };
$scope.submit = function() { $modalInstance.close(); };
$scope.cancel = function() { $modalInstance.dismiss('cancel'); };
} // end of function
]) // end of controller
@ -74,7 +74,7 @@
'horizon.framework.util.i18n.gettext',
function($modal, path, gettext) {
return function(params) {
if (params && params.title && params.body){
if (params && params.title && params.body) {
var options = {
controller: 'simpleModalCtrl',
templateUrl: path + 'modal/simple-modal.html',

View File

@ -30,32 +30,33 @@
* </search-bar>
* ```
*/
.directive('searchBar', [ 'horizon.framework.widgets.table.filterPlaceholderText', 'horizon.framework.widgets.basePath',
.directive('searchBar', ['horizon.framework.widgets.table.filterPlaceholderText',
'horizon.framework.widgets.basePath',
function(FILTER_PLACEHOLDER_TEXT, path) {
return {
restrict: 'E',
templateUrl: path + 'table/search-bar.html',
transclude: true,
link: function (scope, element, attrs, ctrl, transclude) {
if (angular.isDefined(attrs.groupClasses)) {
element.find('.input-group').addClass(attrs.groupClasses);
}
if (angular.isDefined(attrs.iconClasses)) {
element.find('.fa').addClass(attrs.iconClasses);
}
var searchInput = element.find('[st-search]');
return {
restrict: 'E',
templateUrl: path + 'table/search-bar.html',
transclude: true,
link: function (scope, element, attrs, ctrl, transclude) {
if (angular.isDefined(attrs.groupClasses)) {
element.find('.input-group').addClass(attrs.groupClasses);
}
if (angular.isDefined(attrs.iconClasses)) {
element.find('.fa').addClass(attrs.iconClasses);
}
var searchInput = element.find('[st-search]');
if (angular.isDefined(attrs.inputClasses)) {
searchInput.addClass(attrs.inputClasses);
if (angular.isDefined(attrs.inputClasses)) {
searchInput.addClass(attrs.inputClasses);
}
var placeholderText = attrs.placeholder || FILTER_PLACEHOLDER_TEXT;
searchInput.attr('placeholder', placeholderText);
transclude(scope, function(clone) {
element.find('.input-group').append(clone);
});
}
var placeholderText = attrs.placeholder || FILTER_PLACEHOLDER_TEXT;
searchInput.attr('placeholder', placeholderText);
};
}]);
transclude(scope, function(clone){
element.find('.input-group').append(clone);
});
}
};
}]);
}());
}());

View File

@ -57,4 +57,4 @@
});
})();
})();

View File

@ -295,46 +295,47 @@
* ```
*
*/
app.directive('hzExpandDetail', ['horizon.framework.widgets.table.expandSettings', function(settings) {
return {
restrict: 'A',
scope: {
icons: '@hzExpandDetail',
duration: '@'
},
link: function(scope, element) {
element.on('click', function() {
var iconClasses = scope.icons || settings.expandIconClasses;
element.toggleClass(iconClasses);
app.directive('hzExpandDetail', ['horizon.framework.widgets.table.expandSettings',
function(settings) {
return {
restrict: 'A',
scope: {
icons: '@hzExpandDetail',
duration: '@'
},
link: function(scope, element) {
element.on('click', function() {
var iconClasses = scope.icons || settings.expandIconClasses;
element.toggleClass(iconClasses);
var summaryRow = element.closest('tr');
var detailCell = summaryRow.next('tr').find('.detail');
var duration = scope.duration ? parseInt(scope.duration) : settings.duration;
var summaryRow = element.closest('tr');
var detailCell = summaryRow.next('tr').find('.detail');
var duration = scope.duration ? parseInt(scope.duration) : settings.duration;
if (summaryRow.hasClass('expanded')) {
var options = {
duration: duration,
complete: function() {
// Hide the row after the slide animation finishes
summaryRow.toggleClass('expanded');
if (summaryRow.hasClass('expanded')) {
var options = {
duration: duration,
complete: function() {
// Hide the row after the slide animation finishes
summaryRow.toggleClass('expanded');
}
};
detailCell.find('.detail-expanded').slideUp(options);
} else {
summaryRow.toggleClass('expanded');
if (detailCell.find('.detail-expanded').length === 0) {
// Slide down animation doesn't work on table cells
// so a <div> wrapper needs to be added
detailCell.wrapInner('<div class="detail-expanded"></div>');
}
};
detailCell.find('.detail-expanded').slideUp(options);
} else {
summaryRow.toggleClass('expanded');
if (detailCell.find('.detail-expanded').length === 0) {
// Slide down animation doesn't work on table cells
// so a <div> wrapper needs to be added
detailCell.wrapInner('<div class="detail-expanded"></div>');
detailCell.find('.detail-expanded').slideDown(duration);
}
detailCell.find('.detail-expanded').slideDown(duration);
}
});
}
};
}]);
});
}
};
}]);
})();

View File

@ -107,18 +107,20 @@
expect($element.scope().numSelected).toBe(1);
});
it('should have numSelected === 0 when first checkbox is clicked, then unclicked', function() {
var checkbox = checkboxes.first();
checkbox[0].checked = true;
checkbox.triggerHandler('click');
it('should have numSelected === 0 when first checkbox is clicked, then unclicked',
function() {
var checkbox = checkboxes.first();
checkbox[0].checked = true;
checkbox.triggerHandler('click');
expect($element.scope().numSelected).toBe(1);
expect($element.scope().numSelected).toBe(1);
checkbox[0].checked = false;
checkbox.triggerHandler('click');
checkbox[0].checked = false;
checkbox.triggerHandler('click');
expect($element.scope().numSelected).toBe(0);
});
expect($element.scope().numSelected).toBe(0);
}
);
it('should have numSelected === 3 and select-all checked when all rows selected', function() {
angular.forEach(checkboxes, function(checkbox) {
@ -130,25 +132,27 @@
expect($element.find('input[hz-select-all]')[0].checked).toBe(true);
});
it('should have select-all unchecked when all rows selected, then one deselected', function() {
angular.forEach(checkboxes, function(checkbox) {
checkbox.checked = true;
angular.element(checkbox).triggerHandler('click');
});
it('should have select-all unchecked when all rows selected, then one deselected',
function() {
angular.forEach(checkboxes, function(checkbox) {
checkbox.checked = true;
angular.element(checkbox).triggerHandler('click');
});
// all checkboxes selected so check-all should be checked
expect($element.scope().numSelected).toBe(3);
expect($element.find('input[hz-select-all]')[0].checked).toBe(true);
// all checkboxes selected so check-all should be checked
expect($element.scope().numSelected).toBe(3);
expect($element.find('input[hz-select-all]')[0].checked).toBe(true);
// deselect one checkbox
var firstCheckbox = checkboxes.first();
firstCheckbox[0].checked = false;
firstCheckbox.triggerHandler('click');
// deselect one checkbox
var firstCheckbox = checkboxes.first();
firstCheckbox[0].checked = false;
firstCheckbox.triggerHandler('click');
// check-all should be unchecked
expect($element.scope().numSelected).toBe(2);
expect($element.find('input[hz-select-all]')[0].checked).toBe(false);
});
// check-all should be unchecked
expect($element.scope().numSelected).toBe(2);
expect($element.find('input[hz-select-all]')[0].checked).toBe(false);
}
);
});
@ -244,4 +248,4 @@
});
});
}());
}());

View File

@ -147,15 +147,17 @@
* @scope true
*
*/
.directive('toast', ['horizon.framework.widgets.toast.service', 'horizon.framework.widgets.basePath', function(toastService, path) {
return {
restrict: 'EA',
templateUrl: path + 'toast/toast.html',
scope: {},
link: function(scope) {
scope.toast = toastService;
}
};
}]);
.directive('toast', ['horizon.framework.widgets.toast.service',
'horizon.framework.widgets.basePath',
function(toastService, path) {
return {
restrict: 'EA',
templateUrl: path + 'toast/toast.html',
scope: {},
link: function(scope) {
scope.toast = toastService;
}
};
}]);
})();

View File

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(){
(function() {
'use strict';
describe('horizon.framework.widgets.toast module', function() {
it('should have been defined', function () {
expect(angular.module('horizon.framework.widgets.toast')).toBeDefined();
});
});
});
describe('toast factory', function() {
@ -147,4 +147,4 @@
expect(toasts().length).toBe(0);
});
});
})();
})();

View File

@ -414,4 +414,4 @@
}
]);
})();
})();

View File

@ -72,7 +72,7 @@
}));
it('returns expected text', function() {
var items = [1,2,3];
var items = [1, 2, 3];
expect(filter(items, 6)).toBe('Found 3 of 6');
});
@ -112,6 +112,7 @@
displayedAllocated: []
};
// jscs:disable maximumLineLength
var markup = '<transfer-table tr-model="tableData">' +
'<allocated>' +
'<table st-table="tableData.displayedAllocated" st-safe-src="tableData.allocated" hz-table>' +
@ -138,6 +139,7 @@
'</table>' +
'</available>' +
'</transfer-table>';
// jscs:enable maximumLineLength
$element = angular.element(markup);
$compile($element)($scope);
@ -209,6 +211,7 @@
maxAllocation: 2
};
// jscs:disable maximumLineLength
var markup = '<transfer-table tr-model="tableData" limits="limits">' +
'<available>' +
'<table st-table="tableData.available" hz-table>' +
@ -235,6 +238,7 @@
'</table>' +
'</allocated>' +
'</transfer-table>';
// jscs:enable maximumLineLength
$element = angular.element(markup);
$compile($element)($scope);

View File

@ -1,8 +1,8 @@
(function () {
'use strict';
var extend = angular.extend,
forEach = angular.forEach;
var extend = angular.extend;
var forEach = angular.forEach;
angular.module('horizon.framework.widgets.wizard', ['ui.bootstrap'])
@ -29,8 +29,8 @@
'horizon.framework.widgets.wizard.labels',
'horizon.framework.widgets.wizard.events',
function ($scope, $q, wizardLabels, wizardEvents) {
var viewModel = $scope.viewModel = {},
initTask = $q.defer();
var viewModel = $scope.viewModel = {};
var initTask = $q.defer();
$scope.initPromise = initTask.promise;
$scope.currentIndex = -1;
@ -176,9 +176,9 @@
checkAllReadiness().then(always, always);
}],
templateUrl: path + 'wizard/wizard.html'
};
}
templateUrl: path + 'wizard/wizard.html'
};
}
])
.controller('ModalContainerCtrl', ['$scope', '$modalInstance', 'launchContext',

View File

@ -172,7 +172,7 @@
steps: [{}, checkedStep, {}]
};
spyOn(checkedStep, 'checkReadiness').and.returnValue({then: function(){}});
spyOn(checkedStep, 'checkReadiness').and.returnValue({then: function() {}});
$scope.$digest();
expect(checkedStep.checkReadiness).toHaveBeenCalled();
});