Skip to content
This repository has been archived by the owner on Nov 22, 2021. It is now read-only.

Commit

Permalink
refactor(tagsInput): Refactored autocomplete registration
Browse files Browse the repository at this point in the history
Refactored autocomplete registration in order to expose only what is
needed by the autocomplete directive. In addition, the event system was
normalized and now there's only one way to send events from one directive
to another.
  • Loading branch information
mbenford committed Dec 1, 2013
1 parent 326af7f commit 6306d5c
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 121 deletions.
110 changes: 54 additions & 56 deletions src/auto-complete.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
'</div>',
link: function(scope, element, attrs, tagsInputCtrl) {
var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down],
suggestionList, tagsInput, input, highlight;
suggestionList, tagsInput, highlight;

configuration.load(scope, attrs, {
debounceDelay: { type: Number, defaultValue: 100 },
Expand All @@ -103,7 +103,6 @@ angular.module('tags-input').directive('autoComplete', function($document, $time

suggestionList = new SuggestionList(scope.source, scope.options);
tagsInput = tagsInputCtrl.registerAutocomplete();
input = tagsInput.input;

if (scope.options.highlightMatchedText) {
highlight = function(item, text) {
Expand All @@ -123,9 +122,9 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
var added = false;

if (suggestionList.selected) {
input.changeValue(suggestionList.selected);
tagsInput.changeInputValue(suggestionList.selected);
suggestionList.reset();
input[0].focus();
tagsInput.focusInput();

added = true;
}
Expand All @@ -136,71 +135,70 @@ angular.module('tags-input').directive('autoComplete', function($document, $time
return $sce.trustAsHtml(highlight(item, suggestionList.query));
};

input.change(function(value) {
if (value) {
suggestionList.load(value);
} else {
tagsInput
.on('tag-added', function() {
suggestionList.reset();
}
});

input.on('keydown', function(e) {
var key, handled;

if (hotkeys.indexOf(e.keyCode) === -1) {
return;
}

// This hack is needed because jqLite doesn't implement stopImmediatePropagation properly.
// I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon.
// https://github.com/angular/angular.js/pull/4833
var immediatePropagationStopped = false;
e.stopImmediatePropagation = function() {
immediatePropagationStopped = true;
e.stopPropagation();
};
e.isImmediatePropagationStopped = function() {
return immediatePropagationStopped;
};

if (suggestionList.visible) {
key = e.keyCode;
handled = false;

if (key === KEYS.down) {
suggestionList.selectNext();
handled = true;
}
else if (key === KEYS.up) {
suggestionList.selectPrior();
handled = true;
}
else if (key === KEYS.escape) {
})
.on('input-changed', function(value) {
if (value) {
suggestionList.load(value);
} else {
suggestionList.reset();
handled = true;
}
else if (key === KEYS.enter || key === KEYS.tab) {
handled = scope.addSuggestion();
})
.on('input-keydown', function(e) {
var key, handled;

if (hotkeys.indexOf(e.keyCode) === -1) {
return;
}

if (handled) {
e.preventDefault();
e.stopImmediatePropagation();
scope.$apply();
// This hack is needed because jqLite doesn't implement stopImmediatePropagation properly.
// I've sent a PR to Angular addressing this issue and hopefully it'll be fixed soon.
// https://github.com/angular/angular.js/pull/4833
var immediatePropagationStopped = false;
e.stopImmediatePropagation = function() {
immediatePropagationStopped = true;
e.stopPropagation();
};
e.isImmediatePropagationStopped = function() {
return immediatePropagationStopped;
};

if (suggestionList.visible) {
key = e.keyCode;
handled = false;

if (key === KEYS.down) {
suggestionList.selectNext();
handled = true;
}
else if (key === KEYS.up) {
suggestionList.selectPrior();
handled = true;
}
else if (key === KEYS.escape) {
suggestionList.reset();
handled = true;
}
else if (key === KEYS.enter || key === KEYS.tab) {
handled = scope.addSuggestion();
}

if (handled) {
e.preventDefault();
e.stopImmediatePropagation();
scope.$apply();
}
}
}
});
});

$document.on('click', function() {
if (suggestionList.visible) {
suggestionList.reset();
scope.$apply();
}
});

tagsInput.events.on('tag-added', function() {
suggestionList.reset();
});
}
};
});
Expand Down
28 changes: 16 additions & 12 deletions src/tags-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ angular.module('tags-input').directive('tagsInput', function(configuration) {
},
trigger: function(name, args) {
angular.forEach(events[name], function(handler) {
handler(args);
handler.call(null, args);
});
}
};
Expand Down Expand Up @@ -157,23 +157,27 @@ angular.module('tags-input').directive('tagsInput', function(configuration) {
shouldRemoveLastTag = false;
});

$scope.newTagChange = angular.noop;

this.registerAutocomplete = function() {
var input = $element.find('input');
input.changeValue = function(value) {
$scope.newTag = value;
};
input.on('keydown', function(e) {
events.trigger('input-keydown', e);
});

input.change = function(handler) {
$scope.newTagChange = function() {
handler($scope.newTag);
};
$scope.newTagChange = function() {
events.trigger('input-changed', $scope.newTag);
};

return {
input: input,
events: events
changeInputValue: function(value) {
$scope.newTag = value;
},
focusInput: function() {
input[0].focus();
},
on: function(name, handler) {
events.on(name, handler);
return this;
}
};
};
},
Expand Down
36 changes: 17 additions & 19 deletions test/auto-complete.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

describe('autocomplete-directive', function() {
var $compile, $scope, $q, $timeout,
parentCtrl, element, isolateScope, input, suggestionList, deferred, inputChangeHandler, onTagAddedHandler;
parentCtrl, element, isolateScope, suggestionList, deferred, tagsInput, eventHandlers;

beforeEach(function() {
module('tags-input');
Expand All @@ -16,23 +16,22 @@ describe('autocomplete-directive', function() {
});

deferred = $q.defer();
eventHandlers = {};
$scope.loadItems = jasmine.createSpy().andReturn(deferred.promise);

compile();
});

function compile() {
var parent, tagsInput, options;

input = angular.element('<input type="text">');
input.changeValue = jasmine.createSpy();
input.change = jasmine.createSpy().andCallFake(function(handler) { inputChangeHandler = handler; });
var parent, options;

tagsInput = {
input: input,
events: {
on: jasmine.createSpy().andCallFake(function(name, handler) { onTagAddedHandler = handler; })
}
changeInputValue: jasmine.createSpy(),
focusInput: jasmine.createSpy(),
on: jasmine.createSpy().andCallFake(function(name, handler) {
eventHandlers[name] = handler;
return this;
})
};

parent = $compile('<tags-input ng-model="whatever"></tags-input>')($scope);
Expand All @@ -58,13 +57,13 @@ describe('autocomplete-directive', function() {

function sendKeyDown(keyCode) {
var event = jQuery.Event('keydown', { keyCode: keyCode });
input.trigger(event);
eventHandlers['input-keydown'](event);

return event;
}

function changeInputValue(value) {
inputChangeHandler(value);
eventHandlers['input-changed'](value);
$scope.$digest();
}

Expand Down Expand Up @@ -180,7 +179,7 @@ describe('autocomplete-directive', function() {
suggestionList.show();

// Act
onTagAddedHandler();
eventHandlers['tag-added']();

// Assert
expect(isSuggestionsBoxVisible()).toBe(false);
Expand All @@ -195,7 +194,7 @@ describe('autocomplete-directive', function() {
sendKeyDown(KEYS.enter);

// Assert
expect(input.changeValue).toHaveBeenCalledWith('Item1');
expect(tagsInput.changeInputValue).toHaveBeenCalledWith('Item1');
});

it('adds the selected suggestion to the input field when the tab key is pressed and there is a suggestion selected', function() {
Expand All @@ -207,7 +206,7 @@ describe('autocomplete-directive', function() {
sendKeyDown(KEYS.tab);

// Assert
expect(input.changeValue).toHaveBeenCalledWith('Item1');
expect(tagsInput.changeInputValue).toHaveBeenCalledWith('Item1');
});

it('does not change the input value when the enter key is pressed and there is nothing selected', function() {
Expand All @@ -218,7 +217,7 @@ describe('autocomplete-directive', function() {
sendKeyDown(KEYS.enter);

// Assert
expect(input.changeValue).not.toHaveBeenCalled();
expect(tagsInput.changeInputValue).not.toHaveBeenCalled();
});

it('sets the selected suggestion to null after adding it to the input field', function() {
Expand Down Expand Up @@ -342,20 +341,19 @@ describe('autocomplete-directive', function() {
getSuggestion(1).click();

// Assert
expect(input.changeValue).toHaveBeenCalledWith('Item2');
expect(tagsInput.changeInputValue).toHaveBeenCalledWith('Item2');
});

it('focuses the input field when a suggestion is added via a mouse click', function() {
// Arrange
loadSuggestions(['Item1', 'Item2', 'Item3']);
suggestionList.select(0);
spyOn(input[0], 'focus');

// Act
getSuggestion(1).click();

// Assert
expect(input[0].focus).toHaveBeenCalled();
expect(tagsInput.focusInput).toHaveBeenCalled();
});
});
});
Expand Down
Loading

0 comments on commit 6306d5c

Please sign in to comment.