Skip to content

Commit

Permalink
Merge pull request #6 from jharding/up-down-keys-with-modifiers
Browse files Browse the repository at this point in the history
Don't prevent default browser behavior for modifier+up/down
  • Loading branch information
timtrueman committed Feb 20, 2013
2 parents f47aed0 + 75a1c46 commit 3e56655
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 51 deletions.
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

0 comments on commit 3e56655

Please sign in to comment.