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

View File

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

View File

@ -30,32 +30,33 @@
* </search-bar> * </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) { function(FILTER_PLACEHOLDER_TEXT, path) {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: path + 'table/search-bar.html', templateUrl: path + 'table/search-bar.html',
transclude: true, transclude: true,
link: function (scope, element, attrs, ctrl, transclude) { link: function (scope, element, attrs, ctrl, transclude) {
if (angular.isDefined(attrs.groupClasses)) { if (angular.isDefined(attrs.groupClasses)) {
element.find('.input-group').addClass(attrs.groupClasses); element.find('.input-group').addClass(attrs.groupClasses);
} }
if (angular.isDefined(attrs.iconClasses)) { if (angular.isDefined(attrs.iconClasses)) {
element.find('.fa').addClass(attrs.iconClasses); element.find('.fa').addClass(attrs.iconClasses);
} }
var searchInput = element.find('[st-search]'); var searchInput = element.find('[st-search]');
if (angular.isDefined(attrs.inputClasses)) { if (angular.isDefined(attrs.inputClasses)) {
searchInput.addClass(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) { app.directive('hzExpandDetail', ['horizon.framework.widgets.table.expandSettings',
return { function(settings) {
restrict: 'A', return {
scope: { restrict: 'A',
icons: '@hzExpandDetail', scope: {
duration: '@' icons: '@hzExpandDetail',
}, duration: '@'
link: function(scope, element) { },
element.on('click', function() { link: function(scope, element) {
var iconClasses = scope.icons || settings.expandIconClasses; element.on('click', function() {
element.toggleClass(iconClasses); var iconClasses = scope.icons || settings.expandIconClasses;
element.toggleClass(iconClasses);
var summaryRow = element.closest('tr'); var summaryRow = element.closest('tr');
var detailCell = summaryRow.next('tr').find('.detail'); var detailCell = summaryRow.next('tr').find('.detail');
var duration = scope.duration ? parseInt(scope.duration) : settings.duration; var duration = scope.duration ? parseInt(scope.duration) : settings.duration;
if (summaryRow.hasClass('expanded')) { if (summaryRow.hasClass('expanded')) {
var options = { var options = {
duration: duration, duration: duration,
complete: function() { complete: function() {
// Hide the row after the slide animation finishes // Hide the row after the slide animation finishes
summaryRow.toggleClass('expanded'); 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); detailCell.find('.detail-expanded').slideDown(duration);
} 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); }
} };
}); }]);
}
};
}]);
})(); })();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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