Skip to content

Commit

Permalink
feat(spinner) Added spinner on refresh and select2 (angular-ui#1779)
Browse files Browse the repository at this point in the history
* 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 angular-ui#295, closes angular-ui#1787

* Fix: Quotation mark error

* Update CHANGELOG.md (angular-ui#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
  • Loading branch information
Jefiozie authored and Bogaerts Kristof committed Nov 29, 2016
1 parent b163cfd commit b46fd49
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/bootstrap/select-multiple.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
role="combobox"
aria-expanded="{{$select.open}}"
aria-label="{{$select.baseTitle}}"
ng-class="{'spinner': $select.refreshing}"
ondrop="return false;">
</div>
<div class="ui-select-choices"></div>
Expand Down
3 changes: 2 additions & 1 deletion src/bootstrap/select.tpl.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="ui-select-container ui-select-bootstrap dropdown" ng-class="{open: $select.open}">
<div class="ui-select-match"></div>
<input type="search" role="search" autocomplete="off" tabindex="-1"
<span ng-show="$select.open && $select.refreshing && $select.spinnerEnabled" class="ui-select-refreshing {{$select.spinnerClass}}"></span>
<input type="search" autocomplete="off" tabindex="-1"
aria-expanded="true"
aria-label="{{ $select.baseTitle }}"
aria-owns="ui-select-choices-{{ $select.generatedId }}"
Expand Down
46 changes: 45 additions & 1 deletion src/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -314,4 +314,48 @@ body > .ui-select-bootstrap.open {
height: 10px;
right: 10px;
margin-top: -2px;
}
}

/* Spinner */
.ui-select-refreshing {
position: absolute;
right: 0;
padding: 8px 27px;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing:antialiased;
}

@-webkit-keyframes ui-select-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes ui-select-spin {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}

.ui-select-spin {
-webkit-animation: ui-select-spin 2s infinite linear;
animation: ui-select-spin 2s infinite linear;
}

.ui-select-refreshing.ng-animate {
-webkit-animation: none 0s;
}
4 changes: 3 additions & 1 deletion src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ var uis = angular.module('ui.select', [])
generateId: function() {
return latestId++;
},
appendToBody: false
appendToBody: false,
spinnerEnabled: false,
spinnerClass: 'glyphicon-refresh ui-select-spin'
})

// See Rename minErr and make it accessible from outside https://github.com/angular/angular.js/issues/6913
Expand Down
1 change: 1 addition & 0 deletions src/select2/select.tpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ng-class="{'select2-display-none': !$select.open}">
<div class="search-container" ng-class="{'ui-select-search-hidden':!$select.searchEnabled, 'select2-search':$select.searchEnabled}">
<input type="search" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
ng-class="{'select2-active': $select.refreshing}"
role="combobox"
aria-expanded="true"
aria-owns="ui-select-choices-{{ $select.generatedId }}"
Expand Down
13 changes: 10 additions & 3 deletions src/uiSelectController.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ uis.controller('uiSelectCtrl',
ctrl.refreshDelay = uiSelectConfig.refreshDelay;
ctrl.paste = uiSelectConfig.paste;
ctrl.resetSearchInput = uiSelectConfig.resetSearchInput;
ctrl.refreshing = false;
ctrl.spinnerEnabled = uiSelectConfig.spinnerEnabled;
ctrl.spinnerClass = uiSelectConfig.spinnerClass;

ctrl.removeSelected = uiSelectConfig.removeSelected; //If selected item(s) should be removed from dropdown list
ctrl.closeOnSelect = true; //Initialized inside uiSelect directive link function
Expand Down Expand Up @@ -292,16 +295,20 @@ uis.controller('uiSelectCtrl',
*/
ctrl.refresh = function(refreshAttr) {
if (refreshAttr !== undefined) {

// Debounce
// See https://github.com/angular-ui/bootstrap/blob/0.10.0/src/typeahead/typeahead.js#L155
// FYI AngularStrap typeahead does not have debouncing: https://github.com/mgcrea/angular-strap/blob/v2.0.0-rc.4/src/typeahead/typeahead.js#L177
if (_refreshDelayPromise) {
$timeout.cancel(_refreshDelayPromise);
}
_refreshDelayPromise = $timeout(function() {
$scope.$eval(refreshAttr);
}, ctrl.refreshDelay);
var refreshPromise = $scope.$eval(refreshAttr);
if (refreshPromise && angular.isFunction(refreshPromise.then) && !ctrl.refreshing) {
ctrl.refreshing = true;
refreshPromise.then(function() {
ctrl.refreshing = false;
});
}}, ctrl.refreshDelay);
}
};

Expand Down
11 changes: 11 additions & 0 deletions src/uiSelectDirective.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,17 @@ uis.directive('uiSelect',
}
});

attrs.$observe('spinnerEnabled', function() {
// $eval() is needed otherwise we get a string instead of a boolean
var spinnerEnabled = scope.$eval(attrs.spinnerEnabled);
$select.spinnerEnabled = spinnerEnabled !== undefined ? spinnerEnabled : uiSelectConfig.spinnerEnabled;
});

attrs.$observe('spinnerClass', function() {
var spinnerClass = attrs.spinnerClass;
$select.spinnerClass = spinnerClass !== undefined ? attrs.spinnerClass : uiSelectConfig.spinnerClass;
});

//Automatically gets focus when loaded
if (angular.isDefined(attrs.autofocus)){
$timeout(function(){
Expand Down
76 changes: 72 additions & 4 deletions test/select.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

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

var Key = {
Enter: 13,
Expand Down Expand Up @@ -78,12 +78,13 @@ describe('ui-select tests', function() {
});
});

beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$injector_, _uisRepeatParser_) {
beforeEach(inject(function(_$rootScope_, _$compile_, _$timeout_, _$injector_,_$q_ , _uisRepeatParser_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$compile = _$compile_;
$timeout = _$timeout_;
$injector = _$injector_;
$q = _$q_;
uisRepeatParser = _uisRepeatParser_;
scope.selection = {};

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

function createUiSelect(attrs) {
var attrsHtml = '',
matchAttrsHtml = '';
matchAttrsHtml = '',
choicesAttrsHtml = ''
if (attrs !== undefined) {
if (attrs.disabled !== undefined) { attrsHtml += ' ng-disabled="' + attrs.disabled + '"'; }
if (attrs.required !== undefined) { attrsHtml += ' ng-required="' + attrs.required + '"'; }
Expand All @@ -161,12 +163,16 @@ describe('ui-select tests', function() {
if (attrs.ngClass !== undefined) { attrsHtml += ' ng-class="' + attrs.ngClass + '"'; }
if (attrs.resetSearchInput !== undefined) { attrsHtml += ' reset-search-input="' + attrs.resetSearchInput + '"'; }
if (attrs.closeOnSelect !== undefined) { attrsHtml += ' close-on-select="' + attrs.closeOnSelect + '"'; }
if (attrs.spinnerEnabled !== undefined) { attrsHtml += ' spinner-enabled="' + attrs.spinnerEnabled + '"'; }
if (attrs.spinnerClass !== undefined) { attrsHtml += ' spinner-class="' + attrs.spinnerClass + '"'; }
if (attrs.refresh !== undefined) { choicesAttrsHtml += ' refresh="' + attrs.refresh + '"'; }
if (attrs.refreshDelay !== undefined) { choicesAttrsHtml += ' refresh-delay="' + attrs.refreshDelay + '"'; }
}

return compileTemplate(
'<ui-select ng-model="selection.selected"' + attrsHtml + '> \
<ui-select-match placeholder="Pick one..."' + matchAttrsHtml + '>{{$select.selected.name}}</ui-select-match> \
<ui-select-choices repeat="person in people | filter: $select.search"> \
<ui-select-choices repeat="person in people | filter: $select.search"'+ choicesAttrsHtml + '"> \
<div ng-bind-html="person.name | highlight: $select.search"></div> \
<div ng-bind-html="person.email | highlight: $select.search"></div> \
</ui-select-choices> \
Expand Down Expand Up @@ -3031,4 +3037,66 @@ describe('ui-select tests', function() {
});
});

describe('Test Spinner for promises',function(){
var deferred;

function getFromServer(){
deferred = $q.defer();
return deferred.promise;
}
it('should have a default value of false', function () {
var control = createUiSelect();
expect(control.scope().$select.spinnerEnabled).toEqual(false);
});

it('should have a set a value of true', function () {
var control = createUiSelect({spinnerEnabled: true});
expect(control.scope().$select.spinnerEnabled).toEqual(true);
});

it('should have a default value of glyphicon-refresh ui-select-spin', function () {
var control = createUiSelect();
expect(control.scope().$select.spinnerClass).toEqual('glyphicon-refresh ui-select-spin');
});

it('should have set a custom class value of randomclass', function () {
var control = createUiSelect({spinnerClass: 'randomclass'});
expect(control.scope().$select.spinnerClass).toEqual('randomclass');
});

it('should not display spinner when disabled', function() {
scope.getFromServer = getFromServer;
var el = createUiSelect({theme: 'bootstrap', refresh:"getFromServer($select.search)", refreshDelay:0});
openDropdown(el);
var spinner = el.find('.ui-select-refreshing');
expect(spinner.hasClass('ng-hide')).toBe(true);
setSearchText(el, 'a');
expect(spinner.hasClass('ng-hide')).toBe(true);
deferred.resolve();
scope.$digest();
expect(spinner.hasClass('ng-hide')).toBe(true);
});

it('should display spinner when enabled', function() {
scope.getFromServer = getFromServer;
var el = createUiSelect({spinnerEnabled: true,theme: 'bootstrap', refresh:"getFromServer($select.search)", refreshDelay:0});
openDropdown(el);
var spinner = el.find('.ui-select-refreshing');
expect(spinner.hasClass('ng-hide')).toBe(true);
setSearchText(el, 'a');
expect(spinner.hasClass('ng-hide')).toBe(false);
deferred.resolve();
scope.$digest();
expect(spinner.hasClass('ng-hide')).toBe(true);
});

it('should not display spinner when enabled', function() {
var el = createUiSelect({spinnerEnabled: true,theme: 'bootstrap', spinnerClass: 'randomclass'});
openDropdown(el);
var spinner = el.find('.ui-select-refreshing');
setSearchText(el, 'a');
expect(el.scope().$select.spinnerClass).toBe('randomclass');
});
});

});

0 comments on commit b46fd49

Please sign in to comment.