Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't prevent default browser behavior for modifier+up/down #6

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/js/dropdown_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ var DropdownView = (function() {
this.isMouseOverDropdown = false;
},

_handleMouseover: function(e) {
_handleMouseover: function($e) {
this._getSuggestions().removeClass('tt-is-under-cursor');
$(e.currentTarget).addClass('tt-is-under-cursor');
$($e.currentTarget).addClass('tt-is-under-cursor');
},

_handleSelection: function(e) {
this.trigger('select', formatDataForSuggestion($(e.currentTarget)));
_handleSelection: function($e) {
this.trigger('select', formatDataForSuggestion($($e.currentTarget)));
},

_moveCursor: function(increment) {
Expand Down
29 changes: 11 additions & 18 deletions src/js/input_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ var InputView = (function() {
utils.bindAll(this);

this.specialKeyCodeMap = {
9: { event: 'tab' },
27: { event: 'esc' },
37: { event: 'left' },
39: { event: 'right' },
13: { event: 'enter' },
38: { event: 'up', preventDefault: true },
40: { event: 'down', preventDefault: true }
9: 'tab',
27: 'esc',
37: 'left',
39: 'right',
13: 'enter',
38: 'up',
40: 'down'
};

this.query = '';
Expand Down Expand Up @@ -64,14 +64,11 @@ var InputView = (function() {
this.trigger('blur');
},

_handleSpecialKeyEvent: function(e) {
_handleSpecialKeyEvent: function($e) {
// which is normalized and consistent (but not for IE)
var keyCode = this.specialKeyCodeMap[e.which || e.keyCode];
var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode];

if (keyCode) {
this.trigger(keyCode.event, e);
keyCode.preventDefault && e.preventDefault();
}
keyName && this.trigger(keyName, $e);
},

_compareQueryToInputValue: function() {
Expand Down Expand Up @@ -100,10 +97,6 @@ var InputView = (function() {
this.$input.blur();
},

setPreventDefaultValueForKey: function(key, value) {
this.specialKeyCodeMap[key].preventDefault = !!value;
},

getQuery: function() {
return this.query;
},
Expand Down Expand Up @@ -138,7 +131,7 @@ var InputView = (function() {
selectionStart = this.$input[0].selectionStart,
range;

if (selectionStart) {
if (utils.isNumber(selectionStart)) {
return selectionStart === valueLength;
}

Expand Down
37 changes: 28 additions & 9 deletions src/js/typeahead_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,24 +74,36 @@ var TypeaheadView = (function() {
.on('queryChange whitespaceChange', this._setLanguageDirection)
.on('esc', this._hideDropdown)
.on('esc', this._setInputValueToQuery)
.on('tab up down', this._managePreventDefault)
.on('up down', this._moveDropdownCursor)
.on('up down', this._showDropdown)
.on('tab', this._setPreventDefaultValueForTab)
.on('tab left right', this._autocomplete);
}

utils.mixin(TypeaheadView.prototype, EventTarget, {
// private methods
// ---------------

_setPreventDefaultValueForTab: function(e) {
var hint = this.inputView.getHintValue(),
inputValue = this.inputView.getInputValue(),
_managePreventDefault: function(e) {
var $e = e.data,
hint,
inputValue,
preventDefault = false;

switch (e.type) {
case 'tab':
hint = this.inputView.getHintValue();
inputValue = this.inputView.getInputValue();
preventDefault = hint && hint !== inputValue;
break;

case 'up':
case 'down':
preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey;
break;
}

// if the user tabs to autocomplete while the menu is open
// this will prevent the focus from being lost from the query input
this.inputView.setPreventDefaultValueForKey('9', preventDefault);
preventDefault && $e.preventDefault();
},

_setLanguageDirection: function() {
Expand Down Expand Up @@ -136,7 +148,9 @@ var TypeaheadView = (function() {
},

_setInputValueToSuggestionUnderCursor: function(e) {
this.inputView.setInputValue(e.data.value, true);
var suggestion = e.data;

this.inputView.setInputValue(suggestion.value, true);
},

_showDropdown: function() {
Expand All @@ -149,7 +163,12 @@ var TypeaheadView = (function() {
},

_moveDropdownCursor: function(e) {
this.dropdownView[e.type === 'up' ? 'moveCursorUp' : 'moveCursorDown']();
var $e = e.data;

if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) {
this.dropdownView[e.type === 'up' ?
'moveCursorUp' : 'moveCursorDown']();
}
},

_handleSelection: function(e) {
Expand Down
12 changes: 0 additions & 12 deletions test/input_view_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,18 +162,6 @@ describe('InputView', function() {
});
});

describe('#setPreventDefaultValueForKey', function() {
it('should act as a setter for keyCodeMap', function() {
var key = '9';

this.inputView.setPreventDefaultValueForKey(key, 'truthy value');
expect(this.inputView.specialKeyCodeMap[key].preventDefault).toBe(true);

this.inputView.setPreventDefaultValueForKey(key, false);
expect(this.inputView.specialKeyCodeMap[key].preventDefault).toBe(false);
});
});

describe('#getQuery', function() {
it('should act as a getter for query', function() {
this.inputView.query = 'i am the query value';
Expand Down
87 changes: 87 additions & 0 deletions test/playground.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="../dist/typeahead.css">
<script src="vendor/jquery-1.9.1.js"></script>
<script src="../dist/typeahead.js"></script>

<style>
.container {
width: 800px;
margin: 50px auto;
}

.tt-dropdown-menu {
background-color: #fff;
border: 1px solid #000;
}

.tt-suggestion.tt-is-under-cursor {
background-color: #ccc;
}
</style>
</head>

<body>
<div class="container">
<input class="typeahead" type="text">
</div>

<script>
$('.typeahead').typeahead({
local: [
"Alabama",
"Alaska",
"Arizona",
"Arkansas",
"California",
"Colorado",
"Connecticut",
"Delaware",
"Florida",
"Georgia",
"Hawaii",
"Idaho",
"Illinois",
"Indiana",
"Iowa",
"Kansas",
"Kentucky",
"Louisiana",
"Maine",
"Maryland",
"Massachusetts",
"Michigan",
"Minnesota",
"Mississippi",
"Missouri",
"Montana",
"Nebraska",
"Nevada",
"New Hampshire",
"New Jersey",
"New Mexico",
"New York",
"North Carolina",
"North Dakota",
"Ohio",
"Oklahoma",
"Oregon",
"Pennsylvania",
"Rhode Island",
"South Carolina",
"South Dakota",
"Tennessee",
"Texas",
"Utah",
"Vermont",
"Virginia",
"Washington",
"West Virginia",
"Wisconsin",
"Wyoming"
]
});
</script>
</body>
</html>
101 changes: 93 additions & 8 deletions test/typeahead_view_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,48 +229,133 @@ describe('TypeaheadView', function() {
});
});

['up', 'down'].forEach(function(eventType) {
var fnModifier = eventType.charAt(0).toUpperCase() + eventType.slice(1);
describe('when inputView triggers up', function() {

describe('when inputView triggers ' + eventType, function() {
describe('if modifier key was pressed', function() {
beforeEach(function() {
this.inputView.trigger(eventType);
this.$e = $.extend($.Event('keydown'), { keyCode: 38, shiftKey: true });
spyOn(this.$e, 'preventDefault');

this.inputView.trigger('up', this.$e);
});

it('should show the dropdown menu', function() {
expect(this.dropdownView.show).toHaveBeenCalled();
});

it('should not prevent default browser behavior', function() {
expect(this.$e.preventDefault).not.toHaveBeenCalled();
});

it('should not move cursor up', function() {
expect(this.dropdownView.moveCursorUp).not.toHaveBeenCalled();
});
});

describe('if modifier key was not pressed', function() {
beforeEach(function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 38 });
spyOn(this.$e, 'preventDefault');

this.inputView.trigger('up', this.$e);
});

it('should show the dropdown menu', function() {
expect(this.dropdownView.show).toHaveBeenCalled();
});

it('should move cursor ' + eventType, function() {
expect(this.dropdownView['moveCursor' + fnModifier]).toHaveBeenCalled();
it('should prevent default browser behavior', function() {
expect(this.$e.preventDefault).toHaveBeenCalled();
});

it('should move cursor up', function() {
expect(this.dropdownView.moveCursorUp).toHaveBeenCalled();
});
});
});

describe('when inputView triggers down', function() {

describe('if modifier key was pressed', function() {
beforeEach(function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 40, shiftKey: true });
spyOn(this.$e, 'preventDefault');

this.inputView.trigger('down', this.$e);
});

it('should show the dropdown menu', function() {
expect(this.dropdownView.show).toHaveBeenCalled();
});

it('should not prevent default browser behavior', function() {
expect(this.$e.preventDefault).not.toHaveBeenCalled();
});

it('should not move cursor down', function() {
expect(this.dropdownView.moveCursorDown).not.toHaveBeenCalled();
});
});

describe('if modifier key was not pressed', function() {
beforeEach(function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 40 });
spyOn(this.$e, 'preventDefault');

this.inputView.trigger('down', this.$e);
});

it('should show the dropdown menu', function() {
expect(this.dropdownView.show).toHaveBeenCalled();
});

it('should prevent default browser behavior', function() {
expect(this.$e.preventDefault).toHaveBeenCalled();
});

it('should move cursor down', function() {
expect(this.dropdownView.moveCursorDown).toHaveBeenCalled();
});
});
});

describe('when inputView triggers tab', function() {
beforeEach(function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 9 });
spyOn(this.$e, 'preventDefault');
});

describe('if hint is empty string', function() {
beforeEach(function() {
this.inputView.getHintValue.andReturn('');

this.inputView.trigger('tab');
this.inputView.trigger('tab', this.$e);
});

it('should not update input value', function() {
expect(this.inputView.setInputValue).not.toHaveBeenCalled();
});

it('should not prevent default browser behavior', function() {
expect(this.$e.preventDefault).not.toHaveBeenCalled();
});
});

describe('if hint differs from query', function() {
beforeEach(function() {
this.inputView.getQuery.andReturn('app');
this.inputView.getHintValue.andReturn('apple');

this.inputView.trigger('tab');
this.inputView.trigger('tab', this.$e);
});

it('should update input value', function() {
expect(this.inputView.setInputValue).toHaveBeenCalled();
});

it('should prevent default browser behavior', function() {
expect(this.$e.preventDefault).toHaveBeenCalled();
});
});
});

Expand Down