Skip to content
This repository was archived by the owner on Oct 2, 2019. It is now read-only.

Commit 10a60fa

Browse files
Jefiozieaaronroberson
authored andcommitted
feat(spinner) Added spinner on refresh and select2 (#1779)
* Added the select2 spinner on updating * Added bootstrap refreshicon * changed func. and added tests * docs(README): fix angular-sanitize typo "i" was missing in the name of "Angular-Sanitze" * feat(selectize): add support for multiple selection Closes #295, closes #1787 * Fix: Quotation mark error * Update CHANGELOG.md (#1816) fix 0.17.0 version in changelog * fix: ensure aria-activedescendant is correct This is a fix for a severe error found using the Google Accessibility Developer Tool audit: ARIA attributes which refer to other elements by ID should refer to elements which exist in the DOM. * fix: only apply listbox role when open This is a fix for a severe error found using the Google Accessibility Developer Tool audit: Elements with ARIA roles must ensure required owned elements are present * fix(bootstrap): add search role This is a fix for a severe error found using the Google Accessibility Developer Tool audit: Elements with ARIA roles must ensure required owned elements are present * feature(touch): set clickTriggeredSelect to true for touchend events Setting clickTriggeredSelect to true for "touchend" events fixes an issue I've encoutered when using this property in a search box (using ui-select-match and ui-select-choices), where we couldn't distinguish typing from clicking on an autocomplete result on mobile devices. * Added the select2 spinner on updating * Added bootstrap refreshicon * changed func. and added tests * Fix: Quotation mark error
1 parent 7ef10d4 commit 10a60fa

8 files changed

+145
-10
lines changed

Diff for: src/bootstrap/select-multiple.tpl.html

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
role="combobox"
1515
aria-expanded="{{$select.open}}"
1616
aria-label="{{$select.baseTitle}}"
17+
ng-class="{'spinner': $select.refreshing}"
1718
ondrop="return false;">
1819
</div>
1920
<div class="ui-select-choices"></div>

Diff for: src/bootstrap/select.tpl.html

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<div class="ui-select-container ui-select-bootstrap dropdown" ng-class="{open: $select.open}">
22
<div class="ui-select-match"></div>
3-
<input type="search" role="search" autocomplete="off" tabindex="-1"
3+
<span ng-show="$select.open && $select.refreshing && $select.spinnerEnabled" class="ui-select-refreshing {{$select.spinnerClass}}"></span>
4+
<input type="search" autocomplete="off" tabindex="-1"
45
aria-expanded="true"
56
aria-label="{{ $select.baseTitle }}"
67
aria-owns="ui-select-choices-{{ $select.generatedId }}"

Diff for: src/common.css

+45-1
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,48 @@ body > .ui-select-bootstrap.open {
314314
height: 10px;
315315
right: 10px;
316316
margin-top: -2px;
317-
}
317+
}
318+
319+
/* Spinner */
320+
.ui-select-refreshing {
321+
position: absolute;
322+
right: 0;
323+
padding: 8px 27px;
324+
top: 1px;
325+
display: inline-block;
326+
font-family: 'Glyphicons Halflings';
327+
font-style: normal;
328+
font-weight: normal;
329+
line-height: 1;
330+
-webkit-font-smoothing:antialiased;
331+
}
332+
333+
@-webkit-keyframes ui-select-spin {
334+
0% {
335+
-webkit-transform: rotate(0deg);
336+
transform: rotate(0deg);
337+
}
338+
100% {
339+
-webkit-transform: rotate(359deg);
340+
transform: rotate(359deg);
341+
}
342+
}
343+
@keyframes ui-select-spin {
344+
0% {
345+
-webkit-transform: rotate(0deg);
346+
transform: rotate(0deg);
347+
}
348+
100% {
349+
-webkit-transform: rotate(359deg);
350+
transform: rotate(359deg);
351+
}
352+
}
353+
354+
.ui-select-spin {
355+
-webkit-animation: ui-select-spin 2s infinite linear;
356+
animation: ui-select-spin 2s infinite linear;
357+
}
358+
359+
.ui-select-refreshing.ng-animate {
360+
-webkit-animation: none 0s;
361+
}

Diff for: src/common.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ var uis = angular.module('ui.select', [])
106106
generateId: function() {
107107
return latestId++;
108108
},
109-
appendToBody: false
109+
appendToBody: false,
110+
spinnerEnabled: false,
111+
spinnerClass: 'glyphicon-refresh ui-select-spin'
110112
})
111113

112114
// See Rename minErr and make it accessible from outside https://github.com/angular/angular.js/issues/6913

Diff for: src/select2/select.tpl.html

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
ng-class="{'select2-display-none': !$select.open}">
99
<div class="search-container" ng-class="{'ui-select-search-hidden':!$select.searchEnabled, 'select2-search':$select.searchEnabled}">
1010
<input type="search" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
11+
ng-class="{'select2-active': $select.refreshing}"
1112
role="combobox"
1213
aria-expanded="true"
1314
aria-owns="ui-select-choices-{{ $select.generatedId }}"

Diff for: src/uiSelectController.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ uis.controller('uiSelectCtrl',
1818
ctrl.refreshDelay = uiSelectConfig.refreshDelay;
1919
ctrl.paste = uiSelectConfig.paste;
2020
ctrl.resetSearchInput = uiSelectConfig.resetSearchInput;
21+
ctrl.refreshing = false;
22+
ctrl.spinnerEnabled = uiSelectConfig.spinnerEnabled;
23+
ctrl.spinnerClass = uiSelectConfig.spinnerClass;
2124

2225
ctrl.removeSelected = uiSelectConfig.removeSelected; //If selected item(s) should be removed from dropdown list
2326
ctrl.closeOnSelect = true; //Initialized inside uiSelect directive link function
@@ -292,16 +295,20 @@ uis.controller('uiSelectCtrl',
292295
*/
293296
ctrl.refresh = function(refreshAttr) {
294297
if (refreshAttr !== undefined) {
295-
296298
// Debounce
297299
// See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L155
298300
// FYI AngularStrap typeahead does not have debouncing: https://github.com/mgcrea/angular-strap/blob/v2.0.0-rc.4/src/typeahead/typeahead.js#L177
299301
if (_refreshDelayPromise) {
300302
$timeout.cancel(_refreshDelayPromise);
301303
}
302304
_refreshDelayPromise = $timeout(function() {
303-
$scope.$eval(refreshAttr);
304-
}, ctrl.refreshDelay);
305+
var refreshPromise = $scope.$eval(refreshAttr);
306+
if (refreshPromise && angular.isFunction(refreshPromise.then) && !ctrl.refreshing) {
307+
ctrl.refreshing = true;
308+
refreshPromise.then(function() {
309+
ctrl.refreshing = false;
310+
});
311+
}}, ctrl.refreshDelay);
305312
}
306313
};
307314

Diff for: src/uiSelectDirective.js

+11
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,17 @@ uis.directive('uiSelect',
143143
}
144144
});
145145

146+
attrs.$observe('spinnerEnabled', function() {
147+
// $eval() is needed otherwise we get a string instead of a boolean
148+
var spinnerEnabled = scope.$eval(attrs.spinnerEnabled);
149+
$select.spinnerEnabled = spinnerEnabled !== undefined ? spinnerEnabled : uiSelectConfig.spinnerEnabled;
150+
});
151+
152+
attrs.$observe('spinnerClass', function() {
153+
var spinnerClass = attrs.spinnerClass;
154+
$select.spinnerClass = spinnerClass !== undefined ? attrs.spinnerClass : uiSelectConfig.spinnerClass;
155+
});
156+
146157
//Automatically gets focus when loaded
147158
if (angular.isDefined(attrs.autofocus)){
148159
$timeout(function(){

Diff for: test/select.spec.js

+72-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict';
22

33
describe('ui-select tests', function() {
4-
var scope, $rootScope, $compile, $timeout, $injector, uisRepeatParser;
4+
var scope, $rootScope, $compile, $timeout, $injector, $q,uisRepeatParser ;
55

66
var Key = {
77
Enter: 13,
@@ -78,12 +78,13 @@ describe('ui-select tests', function() {
7878
});
7979
});
8080

81-
beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$injector_, _uisRepeatParser_) {
81+
beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$injector_,_$q_ , _uisRepeatParser_) {
8282
$rootScope = _$rootScope_;
8383
scope = $rootScope.$new();
8484
$compile = _$compile_;
8585
$timeout = _$timeout_;
8686
$injector = _$injector_;
87+
$q = _$q_;
8788
uisRepeatParser = _uisRepeatParser_;
8889
scope.selection = {};
8990

@@ -146,7 +147,8 @@ describe('ui-select tests', function() {
146147

147148
function createUiSelect(attrs) {
148149
var attrsHtml = '',
149-
matchAttrsHtml = '';
150+
matchAttrsHtml = '',
151+
choicesAttrsHtml = ''
150152
if (attrs !== undefined) {
151153
if (attrs.disabled !== undefined) { attrsHtml += ' ng-disabled="' + attrs.disabled + '"'; }
152154
if (attrs.required !== undefined) { attrsHtml += ' ng-required="' + attrs.required + '"'; }
@@ -161,12 +163,16 @@ describe('ui-select tests', function() {
161163
if (attrs.ngClass !== undefined) { attrsHtml += ' ng-class="' + attrs.ngClass + '"'; }
162164
if (attrs.resetSearchInput !== undefined) { attrsHtml += ' reset-search-input="' + attrs.resetSearchInput + '"'; }
163165
if (attrs.closeOnSelect !== undefined) { attrsHtml += ' close-on-select="' + attrs.closeOnSelect + '"'; }
166+
if (attrs.spinnerEnabled !== undefined) { attrsHtml += ' spinner-enabled="' + attrs.spinnerEnabled + '"'; }
167+
if (attrs.spinnerClass !== undefined) { attrsHtml += ' spinner-class="' + attrs.spinnerClass + '"'; }
168+
if (attrs.refresh !== undefined) { choicesAttrsHtml += ' refresh="' + attrs.refresh + '"'; }
169+
if (attrs.refreshDelay !== undefined) { choicesAttrsHtml += ' refresh-delay="' + attrs.refreshDelay + '"'; }
164170
}
165171

166172
return compileTemplate(
167173
'<ui-select ng-model="selection.selected"' + attrsHtml + '> \
168174
<ui-select-match placeholder="Pick one..."' + matchAttrsHtml + '>{{$select.selected.name}}</ui-select-match> \
169-
<ui-select-choices repeat="person in people | filter: $select.search"> \
175+
<ui-select-choices repeat="person in people | filter: $select.search"'+ choicesAttrsHtml + '"> \
170176
<div ng-bind-html="person.name | highlight: $select.search"></div> \
171177
<div ng-bind-html="person.email | highlight: $select.search"></div> \
172178
</ui-select-choices> \
@@ -3031,4 +3037,66 @@ describe('ui-select tests', function() {
30313037
});
30323038
});
30333039

3040+
describe('Test Spinner for promises',function(){
3041+
var deferred;
3042+
3043+
function getFromServer(){
3044+
deferred = $q.defer();
3045+
return deferred.promise;
3046+
}
3047+
it('should have a default value of false', function () {
3048+
var control = createUiSelect();
3049+
expect(control.scope().$select.spinnerEnabled).toEqual(false);
3050+
});
3051+
3052+
it('should have a set a value of true', function () {
3053+
var control = createUiSelect({spinnerEnabled: true});
3054+
expect(control.scope().$select.spinnerEnabled).toEqual(true);
3055+
});
3056+
3057+
it('should have a default value of glyphicon-refresh ui-select-spin', function () {
3058+
var control = createUiSelect();
3059+
expect(control.scope().$select.spinnerClass).toEqual('glyphicon-refresh ui-select-spin');
3060+
});
3061+
3062+
it('should have set a custom class value of randomclass', function () {
3063+
var control = createUiSelect({spinnerClass: 'randomclass'});
3064+
expect(control.scope().$select.spinnerClass).toEqual('randomclass');
3065+
});
3066+
3067+
it('should not display spinner when disabled', function() {
3068+
scope.getFromServer = getFromServer;
3069+
var el = createUiSelect({theme: 'bootstrap', refresh:"getFromServer($select.search)", refreshDelay:0});
3070+
openDropdown(el);
3071+
var spinner = el.find('.ui-select-refreshing');
3072+
expect(spinner.hasClass('ng-hide')).toBe(true);
3073+
setSearchText(el, 'a');
3074+
expect(spinner.hasClass('ng-hide')).toBe(true);
3075+
deferred.resolve();
3076+
scope.$digest();
3077+
expect(spinner.hasClass('ng-hide')).toBe(true);
3078+
});
3079+
3080+
it('should display spinner when enabled', function() {
3081+
scope.getFromServer = getFromServer;
3082+
var el = createUiSelect({spinnerEnabled: true,theme: 'bootstrap', refresh:"getFromServer($select.search)", refreshDelay:0});
3083+
openDropdown(el);
3084+
var spinner = el.find('.ui-select-refreshing');
3085+
expect(spinner.hasClass('ng-hide')).toBe(true);
3086+
setSearchText(el, 'a');
3087+
expect(spinner.hasClass('ng-hide')).toBe(false);
3088+
deferred.resolve();
3089+
scope.$digest();
3090+
expect(spinner.hasClass('ng-hide')).toBe(true);
3091+
});
3092+
3093+
it('should not display spinner when enabled', function() {
3094+
var el = createUiSelect({spinnerEnabled: true,theme: 'bootstrap', spinnerClass: 'randomclass'});
3095+
openDropdown(el);
3096+
var spinner = el.find('.ui-select-refreshing');
3097+
setSearchText(el, 'a');
3098+
expect(el.scope().$select.spinnerClass).toBe('randomclass');
3099+
});
3100+
});
3101+
30343102
});

0 commit comments

Comments
 (0)