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:
parent
5b17c69a9a
commit
985680d60d
@ -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$}…</i></p>'
|
||||
};
|
||||
return {
|
||||
scope: {
|
||||
text: '@text' // One-direction binding (reads from parent)
|
||||
},
|
||||
restrict: 'A',
|
||||
link: link,
|
||||
template: '<p><i>{$text$}…</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%'});
|
||||
}
|
||||
}]);
|
||||
})();
|
||||
|
@ -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',
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
}());
|
||||
}());
|
||||
|
@ -57,4 +57,4 @@
|
||||
|
||||
});
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
|
@ -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 @@
|
||||
|
||||
});
|
||||
});
|
||||
}());
|
||||
}());
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
||||
})();
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
})();
|
||||
})();
|
||||
|
@ -414,4 +414,4 @@
|
||||
}
|
||||
]);
|
||||
|
||||
})();
|
||||
})();
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user