Skip to content

Commit

Permalink
Add ability to prevent or delay updating the ngModel on choose/unchoo…
Browse files Browse the repository at this point in the history
…se. (#62)

`onChosen` and `onUnchosen` now get access to an `$event` object in their locals which let them
call `preventDefault()` to prevent updating of the model, and later `performDefault()` to perform it.

Also, added unit tests for the `choose` and `unchoose` functions, including these new features.
  • Loading branch information
josephschmitt authored Jun 6, 2017
1 parent 7012a47 commit 66c3175
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 22 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,10 +226,18 @@ field receives focus, you can use the `target` property of the event object to f
the entire component, not just the field. This blur event also has logic to reduce the noise that
sometimes happens where it'll lose focus then immediately regain it, so the blur is called only
after a timeout to make sure it doesn't re-receive focus first.
- `onChosen({choice})`: An expression that's called when a suggestion is chosen. In its locals it
has access to `choice`, which is the item that was chosen.
- `onUnchosen({choice})`: An expression that's called when a suggestion is unchosen (removed as a
choice). In its locals it has access to `choice`, which is the item that was unchosen.
- `onChosen({choice, $event})`: An expression that's called when a suggestion is chosen. In its
locals it has access to `choice`, which is the item that was chosen, and an `$event` object. The
`$event` object has the following properties: `isDefaultPrevented`, `preventDefault()`, and
`performDefault()`. If `isDefaultPrevented` is set to true by calling `preventDefault()` from this
callback function, then the choice is not automatically added to the ngModel. If you then do want
the choice to be added, you can call `performDefault()` to do so.
- `onUnchosen({choice, $event})`: An expression that's called when a suggestion is unchosen (removed as a
choice). In its locals it has access to `choice`, which is the item that was unchosen, and an
`$event` object. The `$event` object has the following properties: `isDefaultPrevented`,
`preventDefault()`, and `performDefault()`. If `isDefaultPrevented` is set to true by calling
`preventDefault()` from this callback function, then the choice is not automatically removed from
the ngModel. If you then do want the choice to be removed, you can call `performDefault()` to do so.
- `onShowSuggestions({suggestions})`: An expression that's called when the suggestions UI is shown.
In its locals it has access to `suggestions`.
- `onHideSuggestions({suggestions})`: An expression that's called when the suggestions UI is
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngc-omnibox",
"version": "0.3.5",
"version": "0.4.0",
"description": "A modern, flexible, Angular 1.x autocomplete library with limited assumptions.",
"main": "dist/ngc-omnibox.js",
"scripts": {
Expand Down
87 changes: 87 additions & 0 deletions spec/tests/angularComponent/ngcOmniboxControllerSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ describe('ngcOmnibox.angularComponent.ngcOmniboxController', () => {
omniboxController = new NgcOmniboxController([document], [fakeEl], {$apply() {}});
omniboxController._suggestionElements = [fakeEl, fakeEl, fakeEl, fakeEl];
omniboxController.isSelectable = () => {};
omniboxController.onChosen = () => {};
omniboxController.onUnchosen = () => {};
});

it('should inject $document, $element, and $scope', () => {
Expand Down Expand Up @@ -414,6 +416,91 @@ describe('ngcOmnibox.angularComponent.ngcOmniboxController', () => {
});
});

describe('choosing and unchoosing', () => {
describe('when multiple is off', () => {
beforeEach(() => {
omniboxController.multiple = false;
omniboxController.ngModel = 'one';
});

it('should set the ngModel to the choice when multiple is off', () => {
omniboxController.choose('three');
expect(omniboxController.ngModel).toEqual('three');
});

it('should set the ngModel to null when unchoosing if multiple is off', () => {
omniboxController.unchoose('one');
expect(omniboxController.ngModel).toEqual(null);
});
});

describe('when multiple is on', () => {
beforeEach(() => {
omniboxController.multiple = true;
omniboxController.ngModel = ['one', 'two'];
});

it('should push the choice to an array', () => {
omniboxController.choose('three');
expect(omniboxController.ngModel).toEqual(['one', 'two', 'three']);
});

it('should not update the ngModel when onChosen event prevents default', () => {
omniboxController.onChosen = ({$event}) => $event.preventDefault();
omniboxController.choose('three');

expect(omniboxController.ngModel).toEqual(['one', 'two']);
});

it('should update the ngModel when onChosen event prevents then peforms default', (done) => {
omniboxController.onChosen = ({$event}) => {
$event.preventDefault();
expect(omniboxController.ngModel).toEqual(['one', 'two']);

$event.performDefault();
expect(omniboxController.ngModel).toEqual(['one', 'two', 'three']);

done();
};

omniboxController.choose('three');
});

it('should not update the ngModel when choosing if an item is not selectable', () => {
omniboxController.isSelectable = () => false;
omniboxController.choose('three');

expect(omniboxController.ngModel).toEqual(['one', 'two']);
});

it('should remove a choice from the ngModel array when unchoosing', () => {
omniboxController.unchoose('two');
expect(omniboxController.ngModel).toEqual(['one']);
});

it('should not update the ngModel when onUnchosen event prevents default', () => {
omniboxController.onUnchosen = ({$event}) => $event.preventDefault();
omniboxController.unchoose('two');

expect(omniboxController.ngModel).toEqual(['one', 'two']);
});

it('should update the ngModel when onUnchosen event prevents then peforms default', (done) => {
omniboxController.onUnchosen = ({$event}) => {
$event.preventDefault();
expect(omniboxController.ngModel).toEqual(['one', 'two']);

$event.performDefault();
expect(omniboxController.ngModel).toEqual(['one']);

done();
};

omniboxController.unchoose('two');
});
});
});

describe('choices visibility', () => {
beforeEach(() => {
omniboxController.multiple = true;
Expand Down
51 changes: 34 additions & 17 deletions src/angularComponent/ngcOmniboxController.js
Original file line number Diff line number Diff line change
Expand Up @@ -395,18 +395,28 @@ export default class NgcOmniboxController {
choose(item, shouldFocusField = true) {
if (item && !(Array.isArray(this.ngModel) && this.ngModel.indexOf(item) >= 0) &&
this.isSelectable({suggestion: item}) !== false) {
if (this.multiple) {
this.ngModel = this.ngModel || [];
this.ngModel.push(item);
} else {
this.ngModel = item;
}

this.onChosen({choice: item});
const $event = {
isDefaultPrevented: false,
preventDefault: () => $event.isDefaultPrevented = true,
performDefault: () => {
$event.isDefaultPrevented = false;

if (this.multiple) {
this.ngModel = this.ngModel || [];
this.ngModel.push(item);
} else {
this.ngModel = item;
}

this.query = '';
shouldFocusField && this.focus();
this.hideSuggestions = true;
this.query = '';
shouldFocusField && this.focus();
this.hideSuggestions = true;
}
};

this.onChosen({choice: item, $event});
!$event.isDefaultPrevented && $event.performDefault();
}
}

Expand All @@ -419,15 +429,22 @@ export default class NgcOmniboxController {
*/
unchoose(item, shouldFocusField = true) {
if (item) {
if (Array.isArray(this.ngModel)) {
this.ngModel.splice(this.ngModel.indexOf(item), 1);
} else if (!this.multiple) {
this.ngModel = null;
}
const $event = {
isDefaultPrevented: false,
preventDefault: () => $event.isDefaultPrevented = true,
performDefault: () => {
if (Array.isArray(this.ngModel)) {
this.ngModel.splice(this.ngModel.indexOf(item), 1);
} else if (!this.multiple) {
this.ngModel = null;
}

this.onUnchosen({choice: item});
shouldFocusField && this.focus();
}
};

shouldFocusField && this.focus();
this.onUnchosen({choice: item, $event});
!$event.isDefaultPrevented && $event.performDefault();
}
}

Expand Down

0 comments on commit 66c3175

Please sign in to comment.