diff --git a/src/uiSelectController.js b/src/uiSelectController.js
index c0a1a94ac..c14e4bea3 100644
--- a/src/uiSelectController.js
+++ b/src/uiSelectController.js
@@ -5,8 +5,8 @@
* put as much logic in the controller (instead of the link functions) as possible so it can be easily tested.
*/
uis.controller('uiSelectCtrl',
- ['$scope', '$element', '$timeout', '$filter', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
- function($scope, $element, $timeout, $filter, RepeatParser, uiSelectMinErr, uiSelectConfig) {
+ ['$scope', '$element', '$timeout', '$filter', '$q', 'uisRepeatParser', 'uiSelectMinErr', 'uiSelectConfig',
+ function($scope, $element, $timeout, $filter, $q, RepeatParser, uiSelectMinErr, uiSelectConfig) {
var ctrl = this;
@@ -284,24 +284,43 @@ uis.controller('uiSelectCtrl',
}
}
- $scope.$broadcast('uis:select', item);
+ var completeSelection = function() {
+ $scope.$broadcast('uis:select', item);
+
+ $timeout(function(){
+ ctrl.onSelectCallback($scope, callbackContext);
+ });
+
+ if (ctrl.closeOnSelect) {
+ ctrl.close(skipFocusser);
+ }
+ if ($event && $event.type === 'click') {
+ ctrl.clickTriggeredSelect = true;
+ }
+ };
var locals = {};
locals[ctrl.parserResult.itemName] = item;
- $timeout(function(){
- ctrl.onSelectCallback($scope, {
- $item: item,
- $model: ctrl.parserResult.modelMapper($scope, locals)
- });
- });
+ var callbackContext = {
+ $item: item,
+ $model: ctrl.parserResult.modelMapper($scope, locals)
+ };
- if (ctrl.closeOnSelect) {
- ctrl.close(skipFocusser);
- }
- if ($event && $event.type === 'click') {
- ctrl.clickTriggeredSelect = true;
+ var onBeforeSelectResult = ctrl.onBeforeSelectCallback($scope, callbackContext);
+
+ if (angular.isDefined(onBeforeSelectResult)) {
+ if (!onBeforeSelectResult) {
+ return; // abort the selection in case of deliberate falsey result
+ } else if (angular.isFunction(onBeforeSelectResult.then)) {
+ onBeforeSelectResult.then(completeSelection);
+ } else {
+ completeSelection();
+ }
+ } else {
+ completeSelection();
}
+
}
}
};
diff --git a/src/uiSelectDirective.js b/src/uiSelectDirective.js
index fc6cc4138..4af6acb7f 100644
--- a/src/uiSelectDirective.js
+++ b/src/uiSelectDirective.js
@@ -41,6 +41,7 @@ uis.directive('uiSelect',
}
}();
+ $select.onBeforeSelectCallback = $parse(attrs.onBeforeSelect);
$select.onSelectCallback = $parse(attrs.onSelect);
$select.onRemoveCallback = $parse(attrs.onRemove);
diff --git a/test/select.spec.js b/test/select.spec.js
index 2ba86fb2a..0ef4d4394 100644
--- a/test/select.spec.js
+++ b/test/select.spec.js
@@ -959,6 +959,196 @@ describe('ui-select tests', function() {
});
+ it('should invoke before-select callback before select callback synchronously', function () {
+
+ var order = [];
+ scope.onBeforeSelectFn = function ($item, $model, $label) {
+ order.push('onBeforeSelectFn');
+ };
+ scope.onSelectFn = function ($item, $model, $label) {
+ order.push('onSelectFn');
+ };
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ clickItem(el, 'Samantha');
+ $timeout.flush();
+
+ expect(order[0]).toEqual('onBeforeSelectFn');
+ expect(order[1]).toEqual('onSelectFn');
+
+ });
+
+ it('should invoke before-select callback before select callback when promised', inject(function ($q) {
+
+ var order = [];
+ scope.onBeforeSelectFn = function ($item, $model, $label) {
+ var deferred = $q.defer();
+ $timeout(function () {
+ order.push('onBeforeSelectFn');
+ deferred.resolve(order);
+ });
+ return deferred.promise;
+ };
+ scope.onSelectFn = function ($item, $model, $label) {
+ order.push('onSelectFn');
+ };
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ clickItem(el, 'Samantha');
+ $timeout.flush();
+
+ expect(order[0]).toEqual('onBeforeSelectFn');
+ expect(order[1]).toEqual('onSelectFn');
+
+ }));
+
+ it('should complete on-select if before-select callback promise is resolved', inject(function ($q) {
+
+ scope.onBeforeSelectFn = function ($item, $model, $label) {
+ var deferred = $q.defer();
+ $timeout(function () {
+ deferred.resolve();
+ });
+ return deferred.promise;
+ };
+ scope.onSelectFn = function ($item, $model, $label) {
+ scope.$item = $item;
+ scope.$model = $model;
+ };
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ expect(scope.$item).toBeFalsy();
+ expect(scope.$model).toBeFalsy();
+
+ clickItem(el, 'Samantha');
+ $timeout.flush();
+
+ expect(scope.selection.selected).toBe('Samantha');
+
+ expect(scope.$item).toEqual(scope.people[5]);
+ expect(scope.$model).toEqual('Samantha');
+
+ }));
+
+ it('should abort selection if before-select callback returns falsy', function () {
+
+ scope.onBeforeSelectFn = function ($item, $model, $label) {
+ return false;
+ };
+ scope.onSelectFn = function ($item, $model, $label) {
+ scope.$item = $item;
+ scope.$model = $model;
+ };
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ expect(scope.$item).toBeFalsy();
+ expect(scope.$model).toBeFalsy();
+
+ clickItem(el, 'Samantha');
+ $timeout.flush();
+
+ expect(scope.$item).toBeFalsy();
+ expect(scope.$model).toBeFalsy();
+
+ });
+
+ it('should abort selection if before-select callback rejects promise', inject(function ($q) {
+
+ scope.onBeforeSelectFn = function ($item, $model, $label) {
+ var deferred = $q.defer();
+ $timeout(function () {
+ deferred.reject();
+ });
+ return deferred.promise;
+ };
+ scope.onSelectFn = function ($item, $model, $label) {
+ scope.$item = $item;
+ scope.$model = $model;
+ };
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ expect(scope.$item).toBeFalsy();
+ expect(scope.$model).toBeFalsy();
+
+ clickItem(el, 'Samantha');
+ $timeout.flush();
+
+ expect(scope.$item).toBeFalsy();
+ expect(scope.$model).toBeFalsy();
+
+ }));
+
+ it('should keep reference to current selection and incoming selection within before-select callback', function () {
+
+ var currentSelection, incomingSelection;
+ scope.onBeforeSelectFn = function ($item, $model, $label) {
+ incomingSelection = $item;
+ currentSelection = scope.selection.selected;
+ };
+ var el = compileTemplate(
+ ' \
+ {{$select.selected.name}} \
+ \
+ \
+ \
+ \
+ '
+ );
+
+ clickItem(el, 'Samantha');
+ $timeout.flush();
+
+ expect(currentSelection).toBeFalsy();
+ expect(incomingSelection.name).toBe('Samantha');
+
+ clickItem(el, 'Adam');
+ $timeout.flush();
+
+ expect(currentSelection).toBe('Samantha');
+ expect(incomingSelection.name).toBe('Adam');
+
+ });
+
it('should invoke hover callback', function(){
var highlighted;