diff --git a/Gruntfile.js b/Gruntfile.js
index 7407bea7..65377317 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,17 +1,17 @@
var semver = require('semver'),
f = require('util').format,
jsFiles = [
- 'src/js/version.js',
- 'src/js/utils.js',
- 'src/js/event_target.js',
- 'src/js/persistent_storage.js',
- 'src/js/request_cache.js',
- 'src/js/transport.js',
- 'src/js/dataset.js',
- 'src/js/input_view.js',
- 'src/js/dropdown_view.js',
- 'src/js/typeahead_view.js',
- 'src/js/typeahead.js'
+ 'src/version.js',
+ 'src/utils.js',
+ 'src/event_target.js',
+ 'src/persistent_storage.js',
+ 'src/request_cache.js',
+ 'src/transport.js',
+ 'src/dataset.js',
+ 'src/input_view.js',
+ 'src/dropdown_view.js',
+ 'src/typeahead_view.js',
+ 'src/typeahead.js'
];
module.exports = function(grunt) {
@@ -28,41 +28,13 @@ module.exports = function(grunt) {
' */\n\n'
].join('\n'),
- less: {
- css: {
- src: 'src/css/typeahead.css',
- dest: '<%= buildDir %>/typeahead.css'
- },
- cssmin: {
- options: { yuicompress: true },
- src: 'src/css/typeahead.css',
- dest: '<%= buildDir %>/typeahead.min.css'
- }
- },
-
concat: {
- css: {
- options: {
- banner: '<%= banner %>',
- stripBanners: true
- },
- src: '<%= less.css.dest %>',
- dest: '<%= less.css.dest %>'
- },
- cssmin: {
- options: {
- banner: '<%= banner %>',
- stripBanners: true
- },
- src: '<%= less.cssmin.dest %>',
- dest: '<%= less.cssmin.dest %>'
- },
js: {
- src: ['src/js/intro.js', jsFiles, 'src/js/outro.js'],
+ src: ['src/intro.js', jsFiles, 'src/outro.js'],
dest: '<%= buildDir %>/typeahead.js'
},
jsmin: {
- src: ['src/js/intro.js', jsFiles, 'src/js/outro.js'],
+ src: ['src/intro.js', jsFiles, 'src/outro.js'],
dest: '<%= buildDir %>/typeahead.min.js'
}
},
@@ -111,10 +83,6 @@ module.exports = function(grunt) {
js: {
files: jsFiles,
tasks: 'build:js'
- },
- css: {
- files: '<%= less.css.src %>',
- tasks: 'build:css'
}
},
@@ -245,9 +213,7 @@ module.exports = function(grunt) {
// -------
grunt.registerTask('default', 'build');
- grunt.registerTask('build', ['build:js', 'build:css']);
- grunt.registerTask('build:js', ['concat:js', 'concat:jsmin', 'sed:version', 'uglify']);
- grunt.registerTask('build:css', ['less', 'concat:css', 'concat:cssmin']);
+ grunt.registerTask('build', ['concat:js', 'concat:jsmin', 'sed:version', 'uglify']);
grunt.registerTask('server', 'connect:server');
grunt.registerTask('lint', 'jshint');
grunt.registerTask('test', 'jasmine:js');
@@ -258,7 +224,6 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-sed');
grunt.loadNpmTasks('grunt-exec');
- grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-uglify');
diff --git a/component.json b/component.json
index b4f42cac..3a4d8cc3 100644
--- a/component.json
+++ b/component.json
@@ -2,10 +2,9 @@
"name": "typeahead.js",
"version": "0.8.1",
"main": [
- "dist/typeahead.css",
"dist/typeahead.js"
],
"dependencies": {
"jquery": "~1.9"
}
-}
\ No newline at end of file
+}
diff --git a/dist/typeahead.css b/dist/typeahead.css
deleted file mode 100644
index 1fa961b0..00000000
--- a/dist/typeahead.css
+++ /dev/null
@@ -1,72 +0,0 @@
-/*!
- * typeahead.js 0.8.1
- * https://github.com/twitter/typeahead
- * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
- */
-
-.twitter-typeahead {
- position: relative !important;
- display: inline-block;
- *display: inline;
- *zoom: 1;
-}
-.tt-query {
- position: relative !important;
- /* for unknown reasons, this fixes alignment issues in ie7 */
-
- *margin-top: -1px !important;
- vertical-align: top !important;
- background-color: transparent !important;
- /* ie6-8 doesn't fire hover and click events for transparent elements
- for a workaround, use a 1x1 transparent gif */
-
- background-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) !important;
-}
-.tt-hint {
- position: absolute !important;
- top: 0 !important;
- left: 0 !important;
- border-color: transparent !important;
- -webkit-box-shadow: none !important;
- -moz-box-shadow: none !important;
- box-shadow: none !important;
-}
-.tt-dropdown-menu,
-.tt-suggestions,
-.tt-suggestion {
- padding: 0;
- margin: 0;
- list-style: none;
-}
-.tt-dropdown-menu {
- position: absolute;
- top: 100%;
- left: 0;
- z-index: 100;
- /* TODO: need default z-index, should be configurable */
-
- display: none;
-}
-.tt-dropdown-menu.tt-is-open {
- display: block;
-}
-.tt-dropdown-menu.tt-is-empty {
- display: none;
-}
-.tt-suggestion {
- display: block;
- white-space: nowrap;
- cursor: pointer;
-}
-.tt-suggestion * {
- white-space: normal;
-}
-/* rtl support */
-/* ----------- */
-.twitter-typeahead.tt-rtl {
- direction: rtl;
-}
-.twitter-typeahead.tt-rtl .tt-dropdown-menu {
- left: auto;
- right: 0;
-}
diff --git a/dist/typeahead.min.css b/dist/typeahead.min.css
deleted file mode 100644
index 87b44b65..00000000
--- a/dist/typeahead.min.css
+++ /dev/null
@@ -1,7 +0,0 @@
-/*!
- * typeahead.js 0.8.1
- * https://github.com/twitter/typeahead
- * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
- */
-
-.twitter-typeahead{position:relative!important;display:inline-block;*display:inline;*zoom:1}.tt-query{position:relative!important;*margin-top:-1px!important;vertical-align:top!important;background-color:transparent!important;background-image:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)!important}.tt-hint{position:absolute!important;top:0!important;left:0!important;border-color:transparent!important;-webkit-box-shadow:none!important;-moz-box-shadow:none!important;box-shadow:none!important}.tt-dropdown-menu,.tt-suggestions,.tt-suggestion{padding:0;margin:0;list-style:none}.tt-dropdown-menu{position:absolute;top:100%;left:0;z-index:100;display:none}.tt-dropdown-menu.tt-is-open{display:block}.tt-dropdown-menu.tt-is-empty{display:none}.tt-suggestion{display:block;white-space:nowrap;cursor:pointer}.tt-suggestion *{white-space:normal}.twitter-typeahead.tt-rtl{direction:rtl}.twitter-typeahead.tt-rtl .tt-dropdown-menu{left:auto;right:0}
\ No newline at end of file
diff --git a/package.json b/package.json
index 3c07b129..52e692bd 100644
--- a/package.json
+++ b/package.json
@@ -29,7 +29,6 @@
"grunt": "~0.4",
"grunt-sed": "~0.1",
"grunt-exec": "~0.4",
- "grunt-contrib-less": "~0.5",
"grunt-contrib-watch": "~0.2",
"grunt-contrib-jshint": "~0.1",
"grunt-contrib-uglify": "~0.1",
@@ -44,4 +43,4 @@
},
"private": true,
"version": "0.8.1"
-}
\ No newline at end of file
+}
diff --git a/src/css/typeahead.css b/src/css/typeahead.css
deleted file mode 100644
index c6a0ee9c..00000000
--- a/src/css/typeahead.css
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * typeahead.js
- * https://github.com/twitter/typeahead
- * Copyright 2013 Twitter, Inc. and other contributors; Licensed MIT
- */
-
-.twitter-typeahead {
- position: relative !important;
- display: inline-block;
- *display: inline;
- *zoom: 1;
-}
-
-.tt-query {
- position: relative !important;
- /* for unknown reasons, this fixes alignment issues in ie7 */
- *margin-top: -1px !important;
- vertical-align: top !important;
- background-color: transparent !important;
- /* ie6-8 doesn't fire hover and click events for transparent elements
- for a workaround, use a 1x1 transparent gif */
- background-image: url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7) !important;
-}
-
-.tt-hint {
- position: absolute !important;
- top: 0 !important;
- left: 0 !important;
- border-color: transparent !important;
- -webkit-box-shadow: none !important;
- -moz-box-shadow: none !important;
- box-shadow: none !important;
-}
-
-.tt-dropdown-menu,
-.tt-suggestions,
-.tt-suggestion {
- padding: 0;
- margin: 0;
- list-style: none;
-}
-
-.tt-dropdown-menu {
- position: absolute;
- top: 100%;
- left: 0;
- z-index: 100; /* TODO: need default z-index, should be configurable */
- display: none;
-}
-
-.tt-dropdown-menu.tt-is-open {
- display: block;
-}
-
-.tt-dropdown-menu.tt-is-empty {
- display: none;
-}
-
-.tt-suggestion {
- display: block;
- white-space: nowrap;
- cursor: pointer;
-}
-
-.tt-suggestion * {
- white-space: normal;
-}
-
-/* rtl support */
-/* ----------- */
-
-.twitter-typeahead.tt-rtl {
- direction: rtl;
-}
-
-.twitter-typeahead.tt-rtl .tt-dropdown-menu {
- left: auto;
- right: 0;
-}
diff --git a/src/js/dataset.js b/src/dataset.js
similarity index 100%
rename from src/js/dataset.js
rename to src/dataset.js
diff --git a/src/js/dropdown_view.js b/src/dropdown_view.js
similarity index 63%
rename from src/js/dropdown_view.js
rename to src/dropdown_view.js
index 5feffa13..bd866b26 100644
--- a/src/js/dropdown_view.js
+++ b/src/dropdown_view.js
@@ -5,6 +5,14 @@
*/
var DropdownView = (function() {
+ var html = {
+ suggestionsList: ''
+ },
+ css = {
+ suggestionsList: { display: 'block' },
+ suggestion: { whiteSpace: 'nowrap', cursor: 'pointer' },
+ suggestionChild: { whiteSpace: 'normal' }
+ };
// constructor
// -----------
@@ -12,7 +20,9 @@ var DropdownView = (function() {
function DropdownView(o) {
utils.bindAll(this);
- this.isMouseOverDropdown;
+ this.isOpen = false;
+ this.isEmpty = true;
+ this.isMouseOverDropdown = false;
this.$menu = $(o.menu)
.on('mouseenter', this._handleMouseenter)
@@ -34,19 +44,32 @@ var DropdownView = (function() {
},
_handleMouseover: function($e) {
+ var $suggestion = $($e.currentTarget);
+
this._getSuggestions().removeClass('tt-is-under-cursor');
- $($e.currentTarget).addClass('tt-is-under-cursor');
+ $suggestion.addClass('tt-is-under-cursor');
},
_handleSelection: function($e) {
- this.trigger('select', formatDataForSuggestion($($e.currentTarget)));
+ var $suggestion = $($e.currentTarget);
+ this.trigger('suggestionSelected', formatDataForSuggestion($suggestion));
+ },
+
+ _show: function() {
+ // can't use jQuery#show because $menu is a span element we want
+ // display: block; not dislay: inline;
+ this.$menu.css('display', 'block');
+ },
+
+ _hide: function() {
+ this.$menu.hide();
},
_moveCursor: function(increment) {
var $suggestions, $cur, nextIndex, $underCursor;
- // don't bother moving the cursor if the menu is hidden
- if (!this.$menu.hasClass('tt-is-open')) {
+ // don't bother moving the cursor if the menu is closed or empty
+ if (!this.isVisible()) {
return;
}
@@ -60,7 +83,7 @@ var DropdownView = (function() {
nextIndex = (nextIndex + 1) % ($suggestions.length + 1) - 1;
if (nextIndex === -1) {
- this.trigger('cursorOff');
+ this.trigger('cursorRemoved');
return;
}
@@ -71,7 +94,7 @@ var DropdownView = (function() {
}
$underCursor = $suggestions.eq(nextIndex).addClass('tt-is-under-cursor');
- this.trigger('cursorOn', { value: $underCursor.data('value') });
+ this.trigger('cursorMoved', { value: $underCursor.data('value') });
},
_getSuggestions: function() {
@@ -81,37 +104,47 @@ var DropdownView = (function() {
// public methods
// --------------
- hideUnlessMouseIsOverDropdown: function() {
+ isVisible: function() {
+ return this.isOpen && !this.isEmpty;
+ },
+
+ closeUnlessMouseIsOverDropdown: function() {
// this helps detect the scenario a blur event has triggered
- // this function. we don't want to hide the menu in that case
+ // this function. we don't want to close the menu in that case
// because it'll prevent the probable associated click event
// from being fired
if (!this.isMouseOverDropdown) {
- this.hide();
+ this.close();
}
},
- hide: function() {
- if (this.$menu.hasClass('tt-is-open')) {
+ close: function() {
+ if (this.isOpen) {
+ this.isOpen = false;
+ this._hide();
+
this.$menu
- .removeClass('tt-is-open')
.find('.tt-suggestions > .tt-suggestion')
.removeClass('tt-is-under-cursor');
- this.trigger('hide');
+ this.trigger('closed');
}
},
- show: function() {
- if (!this.$menu.hasClass('tt-is-open')) {
- this.$menu.addClass('tt-is-open');
+ open: function() {
+ if (!this.isOpen) {
+ this.isOpen = true;
+ !this.isEmpty && this._show();
- this.trigger('show');
+ this.trigger('opened');
}
},
- isOpen: function() {
- return this.$menu.hasClass('tt-is-open');
+ setLanguageDirection: function(dir) {
+ var ltrCss = { left: '0', right: 'auto' },
+ rtlCss = { left: 'auto', right:' 0' };
+
+ dir === 'ltr' ? this.$menu.css(ltrCss) : this.$menu.css(rtlCss);
},
moveCursorUp: function() {
@@ -140,15 +173,19 @@ var DropdownView = (function() {
renderSuggestions: function(query, dataset, suggestions) {
var datasetClassName = 'tt-dataset-' + dataset.name,
+ $suggestionsList,
$dataset = this.$menu.find('.' + datasetClassName),
elBuilder,
fragment,
- el;
+ $el;
// first time rendering suggestions for this dataset
if ($dataset.length === 0) {
- $dataset = $('
')
+ $suggestionsList = $(html.suggestionsList).css(css.suggestionsList);
+
+ $dataset = $('')
.addClass(datasetClassName)
+ .append($suggestionsList)
.appendTo(this.$menu);
}
@@ -158,15 +195,21 @@ var DropdownView = (function() {
this.clearSuggestions(dataset.name);
if (suggestions.length > 0) {
- this.$menu.removeClass('tt-is-empty');
+ this.isEmpty = false;
+ this.isOpen && this._show();
utils.each(suggestions, function(i, suggestion) {
elBuilder.innerHTML = dataset.template.render(suggestion);
- el = elBuilder.firstChild;
- el.setAttribute('data-value', suggestion.value);
+ $el = $(elBuilder.firstChild)
+ .css(css.suggestion)
+ .data('value', suggestion.value);
+
+ $el.children().each(function() {
+ $(this).css(css.suggestionChild);
+ });
- fragment.appendChild(el);
+ fragment.appendChild($el[0]);
});
}
@@ -174,7 +217,7 @@ var DropdownView = (function() {
.data({ query: query, dataset: dataset.name })
.append(fragment);
- this.trigger('suggestionsRender');
+ this.trigger('suggestionsRendered');
},
clearSuggestions: function(datasetName) {
@@ -184,8 +227,10 @@ var DropdownView = (function() {
$suggestions.empty();
- // add empty class if the dropdown menu is empty
- this._getSuggestions().length === 0 && this.$menu.addClass('tt-is-empty');
+ if (this._getSuggestions().length === 0) {
+ this.isEmpty = true;
+ this._hide();
+ }
}
});
diff --git a/src/js/event_target.js b/src/event_target.js
similarity index 100%
rename from src/js/event_target.js
rename to src/event_target.js
diff --git a/src/js/input_view.js b/src/input_view.js
similarity index 94%
rename from src/js/input_view.js
rename to src/input_view.js
index 46144c1d..2ce9e64f 100644
--- a/src/js/input_view.js
+++ b/src/input_view.js
@@ -60,18 +60,18 @@ var InputView = (function() {
// ---------------
_handleFocus: function() {
- this.trigger('focus');
+ this.trigger('focused');
},
_handleBlur: function() {
- this.trigger('blur');
+ this.trigger('blured');
},
_handleSpecialKeyEvent: function($e) {
// which is normalized and consistent (but not for IE)
var keyName = this.specialKeyCodeMap[$e.which || $e.keyCode];
- keyName && this.trigger(keyName, $e);
+ keyName && this.trigger(keyName + 'Keyed', $e);
},
_compareQueryToInputValue: function() {
@@ -81,11 +81,11 @@ var InputView = (function() {
this.query.length !== inputValue.length : false;
if (isSameQueryExceptWhitespace) {
- this.trigger('whitespaceChange', { value: this.query });
+ this.trigger('whitespaceChanged', { value: this.query });
}
else if (!isSameQuery) {
- this.trigger('queryChange', { value: this.query = inputValue });
+ this.trigger('queryChanged', { value: this.query = inputValue });
}
},
diff --git a/src/js/intro.js b/src/intro.js
similarity index 100%
rename from src/js/intro.js
rename to src/intro.js
diff --git a/src/js/outro.js b/src/outro.js
similarity index 100%
rename from src/js/outro.js
rename to src/outro.js
diff --git a/src/js/persistent_storage.js b/src/persistent_storage.js
similarity index 100%
rename from src/js/persistent_storage.js
rename to src/persistent_storage.js
diff --git a/src/js/request_cache.js b/src/request_cache.js
similarity index 100%
rename from src/js/request_cache.js
rename to src/request_cache.js
diff --git a/src/js/transport.js b/src/transport.js
similarity index 100%
rename from src/js/transport.js
rename to src/transport.js
diff --git a/src/js/typeahead.js b/src/typeahead.js
similarity index 97%
rename from src/js/typeahead.js
rename to src/typeahead.js
index 95b180e9..a1d53432 100644
--- a/src/js/typeahead.js
+++ b/src/typeahead.js
@@ -86,7 +86,7 @@
}
function compileTemplate(template, engine) {
- var wrapper = '%body',
+ var wrapper = '%body
',
compiledTemplate;
if (template) {
diff --git a/src/js/typeahead_view.js b/src/typeahead_view.js
similarity index 52%
rename from src/js/typeahead_view.js
rename to src/typeahead_view.js
index 0be0a6f0..dc3a71f1 100644
--- a/src/js/typeahead_view.js
+++ b/src/typeahead_view.js
@@ -5,12 +5,48 @@
*/
var TypeaheadView = (function() {
-
var html = {
- wrapper: '',
- hint: '',
- dropdown: ''
- };
+ wrapper: '',
+ hint: '',
+ dropdown: ''
+ },
+ css = {
+ wrapper: {
+ position: 'relative',
+ display: 'inline-block'
+ },
+ hint: {
+ position: 'absolute',
+ top: '0',
+ left: '0',
+ borderColor: 'transparent',
+ boxShadow: 'none'
+ },
+ query: {
+ position: 'relative',
+ verticalAlign: 'top',
+ backgroundColor: 'transparent',
+ // ie6-8 doesn't fire hover and click events for elements with
+ // transparent backgrounds, for a workaround, use 1x1 transparent gif
+ backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)'
+ },
+ dropdown: {
+ position: 'absolute',
+ top: '100%',
+ left: '0',
+ // TODO: should this be configurable?
+ zIndex: '100',
+ display: 'none'
+ }
+ };
+
+ // ie7 specific styling
+ if (utils.isMsie() && utils.isMsie() <= 7) {
+ utils.mixin(css.wrapper, { display: 'inline', zoom: '1' });
+ // if someone can tell me why this is necessary to align
+ // the hint with the query in ie7, i'll send you $5 - @JakeHarding
+ utils.mixin(css.query, { marginTop: '-1px' });
+ }
// constructor
// -----------
@@ -20,40 +56,41 @@ var TypeaheadView = (function() {
utils.bindAll(this);
- this.$node = wrapInput(o.input);
+ this.$node = buildDomStructure(o.input);
this.datasets = o.datasets;
+ this.dir = null;
$menu = this.$node.find('.tt-dropdown-menu');
$input = this.$node.find('.tt-query');
$hint = this.$node.find('.tt-hint');
this.dropdownView = new DropdownView({ menu: $menu })
- .on('select', this._handleSelection)
- .on('cursorOn', this._clearHint)
- .on('cursorOn', this._setInputValueToSuggestionUnderCursor)
- .on('cursorOff', this._setInputValueToQuery)
- .on('cursorOff', this._updateHint)
- .on('suggestionsRender', this._updateHint)
- .on('show', this._updateHint)
- .on('hide', this._clearHint);
+ .on('suggestionSelected', this._handleSelection)
+ .on('cursorMoved', this._clearHint)
+ .on('cursorMoved', this._setInputValueToSuggestionUnderCursor)
+ .on('cursorRemoved', this._setInputValueToQuery)
+ .on('cursorRemoved', this._updateHint)
+ .on('suggestionsRendered', this._updateHint)
+ .on('opened', this._updateHint)
+ .on('closed', this._clearHint);
this.inputView = new InputView({ input: $input, hint: $hint })
- .on('focus', this._showDropdown)
- .on('blur', this._hideDropdown)
- .on('blur', this._setInputValueToQuery)
- .on('enter', this._handleSelection)
- .on('queryChange', this._clearHint)
- .on('queryChange', this._clearSuggestions)
- .on('queryChange', this._getSuggestions)
- .on('whitespaceChange', this._updateHint)
- .on('queryChange whitespaceChange', this._showDropdown)
- .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 left right', this._autocomplete);
+ .on('focused', this._openDropdown)
+ .on('blured', this._closeDropdown)
+ .on('blured', this._setInputValueToQuery)
+ .on('enterKeyed', this._handleSelection)
+ .on('queryChanged', this._clearHint)
+ .on('queryChanged', this._clearSuggestions)
+ .on('queryChanged', this._getSuggestions)
+ .on('whitespaceChanged', this._updateHint)
+ .on('queryChanged whitespaceChanged', this._openDropdown)
+ .on('queryChanged whitespaceChanged', this._setLanguageDirection)
+ .on('escKeyed', this._closeDropdown)
+ .on('escKeyed', this._setInputValueToQuery)
+ .on('tabKeyed upKeyed downKeyed', this._managePreventDefault)
+ .on('upKeyed downKeyed', this._moveDropdownCursor)
+ .on('upKeyed downKeyed', this._openDropdown)
+ .on('tabKeyed leftKeyed rightKeyed', this._autocomplete);
}
utils.mixin(TypeaheadView.prototype, EventTarget, {
@@ -67,14 +104,14 @@ var TypeaheadView = (function() {
preventDefault = false;
switch (e.type) {
- case 'tab':
+ case 'tabKeyed':
hint = this.inputView.getHintValue();
inputValue = this.inputView.getInputValue();
preventDefault = hint && hint !== inputValue;
break;
- case 'up':
- case 'down':
+ case 'upKeyed':
+ case 'downKeyed':
preventDefault = !$e.shiftKey && !$e.ctrlKey && !$e.metaKey;
break;
}
@@ -83,22 +120,26 @@ var TypeaheadView = (function() {
},
_setLanguageDirection: function() {
- var dirClassName = 'tt-' + this.inputView.getLanguageDirection();
+ var dir = this.inputView.getLanguageDirection();
- if (!this.$node.hasClass(dirClassName)) {
- this.$node.removeClass('tt-ltr tt-rtl').addClass(dirClassName);
+ if (dir !== this.dir) {
+ this.dir = dir;
+ this.$node.css('direction', dir);
+ this.dropdownView.setLanguageDirection(dir);
}
},
_updateHint: function() {
var dataForFirstSuggestion = this.dropdownView.getFirstSuggestion(),
hint = dataForFirstSuggestion ? dataForFirstSuggestion.value : null,
+ dropdownIsVisible = this.dropdownView.isVisible(),
+ inputHasOverflow = this.inputView.isOverflow(),
inputValue,
query,
beginsWithQuery,
match;
- if (hint && this.dropdownView.isOpen() && !this.inputView.isOverflow()) {
+ if (hint && dropdownIsVisible && !inputHasOverflow) {
inputValue = this.inputView.getInputValue();
query = inputValue
.replace(/\s{2,}/g, ' ') // condense whitespace
@@ -129,26 +170,26 @@ var TypeaheadView = (function() {
this.inputView.setInputValue(suggestion.value, true);
},
- _showDropdown: function() {
- this.dropdownView.show();
+ _openDropdown: function() {
+ this.dropdownView.open();
},
- _hideDropdown: function(e) {
- this.dropdownView[e.type === 'blur' ?
- 'hideUnlessMouseIsOverDropdown' : 'hide']();
+ _closeDropdown: function(e) {
+ this.dropdownView[e.type === 'blured' ?
+ 'closeUnlessMouseIsOverDropdown' : 'close']();
},
_moveDropdownCursor: function(e) {
var $e = e.data;
if (!$e.shiftKey && !$e.ctrlKey && !$e.metaKey) {
- this.dropdownView[e.type === 'up' ?
+ this.dropdownView[e.type === 'upKeyed' ?
'moveCursorUp' : 'moveCursorDown']();
}
},
_handleSelection: function(e) {
- var byClick = e.type === 'select',
+ var byClick = e.type === 'suggestionSelected',
suggestionData = byClick ?
e.data : this.dropdownView.getSuggestionUnderCursor();
@@ -163,7 +204,7 @@ var TypeaheadView = (function() {
// focus is not a synchronous event in ie, so we deal with it
byClick && utils.isMsie() ?
- setTimeout(this.dropdownView.hide, 0) : this.dropdownView.hide();
+ setTimeout(this.dropdownView.close, 0) : this.dropdownView.close();
}
},
@@ -188,10 +229,10 @@ var TypeaheadView = (function() {
_autocomplete: function(e) {
var isCursorAtEnd, ignoreEvent, query, hint;
- if (e.type === 'right' || e.type === 'left') {
+ if (e.type === 'rightKeyed' || e.type === 'leftKeyed') {
isCursorAtEnd = this.inputView.isCursorAtEnd();
ignoreEvent = this.inputView.getLanguageDirection() === 'ltr' ?
- e.type === 'left' : e.type === 'right';
+ e.type === 'leftKeyed' : e.type === 'rightKeyed';
if (!isCursorAtEnd || ignoreEvent) { return; }
}
@@ -207,26 +248,38 @@ var TypeaheadView = (function() {
return TypeaheadView;
- function wrapInput(input) {
- var $input = $(input),
- $hint = $(html.hint).css({
- 'background-color': $input.css('background-color')
- });
-
- if ($input.length === 0) {
- return null;
- }
+ function buildDomStructure(input) {
+ var $wrapper = $(html.wrapper),
+ $dropdown = $(html.dropdown),
+ $input = $(input),
+ $hint = $(html.hint);
+
+ $wrapper = $wrapper.css(css.wrapper);
+ $dropdown = $dropdown.css(css.dropdown);
+
+ $hint
+ .attr({
+ type: 'text',
+ autocomplete: 'off',
+ spellcheck: false,
+ disabled: true
+ })
+ .css(css.hint)
+ .css('background', $input.css('background'));
+
+ $input
+ .addClass('tt-query')
+ .attr({ autocomplete: 'off', spellcheck: false })
+ .css(css.query);
// ie7 does not like it when dir is set to auto,
// it does not like it one bit
try { !$input.attr('dir') && $input.attr('dir', 'auto'); } catch (e) {}
return $input
- .attr({ autocomplete: 'off', spellcheck: false })
- .addClass('tt-query')
- .wrap(html.wrapper)
+ .wrap($wrapper)
.parent()
.prepend($hint)
- .append(html.dropdown);
+ .append($dropdown);
}
})();
diff --git a/src/js/utils.js b/src/utils.js
similarity index 96%
rename from src/js/utils.js
rename to src/utils.js
index 15d937f3..96bee3ca 100644
--- a/src/js/utils.js
+++ b/src/utils.js
@@ -6,7 +6,9 @@
var utils = {
isMsie: function() {
- return (/msie [\w.]+/i).test(navigator.userAgent);
+ var match = /(msie) ([\w.]+)/i.exec(navigator.userAgent);
+
+ return match ? parseInt(match[2], 10) : false;
},
isString: function(obj) { return typeof obj === 'string'; },
diff --git a/src/js/version.js b/src/version.js
similarity index 100%
rename from src/js/version.js
rename to src/version.js
diff --git a/test/dropdown_view_spec.js b/test/dropdown_view_spec.js
index e1ade1ce..39c9d2c3 100644
--- a/test/dropdown_view_spec.js
+++ b/test/dropdown_view_spec.js
@@ -1,15 +1,5 @@
describe('DropdownView', function() {
- var fixture = [
- ''
- ].join('\n');
+ var fixture = '';
beforeEach(function() {
var $fixtures;
@@ -18,13 +8,14 @@ describe('DropdownView', function() {
$fixtures = $('#jasmine-fixtures');
this.$menu = $fixtures.find('.tt-dropdown-menu');
- this.$testSet = $fixtures.find('.tt-dataset-test > .tt-suggestions');
this.dropdownView = new DropdownView({ menu: this.$menu });
});
- describe('when mouseenter', function() {
+ describe('on mouseenter', function() {
beforeEach(function() {
+ renderTestDataset(this.dropdownView, true);
+
this.$menu.mouseleave().mouseenter();
});
@@ -33,7 +24,7 @@ describe('DropdownView', function() {
});
});
- describe('when mouseleave', function() {
+ describe('on mouseleave', function() {
beforeEach(function() {
this.$menu.mouseenter().mouseleave();
});
@@ -43,48 +34,51 @@ describe('DropdownView', function() {
});
});
- describe('when mouseover suggestion', function() {
- var spy, $suggestion1, $suggestion2, $suggestion3;
-
+ describe('on suggestion mouseover', function() {
beforeEach(function() {
- this.dropdownView.on('cursorOn', spy = jasmine.createSpy());
+ this.dropdownView.on('cursorMoved', this.spy = jasmine.createSpy());
+
+ this.$testDataset = renderTestDataset(this.dropdownView, true);
- $suggestion1 = this.$testSet.find('.tt-suggestion:nth-child(1)');
- $suggestion2 = this.$testSet.find('.tt-suggestion:nth-child(2)');
- $suggestion3 = this.$testSet.find('.tt-suggestion:nth-child(3)');
+ this.$suggestion1 = this.$testDataset.find('.tt-suggestion:nth-child(1)');
+ this.$suggestion2 = this.$testDataset.find('.tt-suggestion:nth-child(2)');
+ this.$suggestion3 = this.$testDataset.find('.tt-suggestion:nth-child(3)');
// start with suggestion1 highlighted
- $suggestion1.addClass('tt-is-under-cursor');
+ this.$suggestion1.addClass('tt-is-under-cursor');
- $suggestion2.mouseover();
+ this.$suggestion2.mouseover();
});
it('should add tt-is-under-cursor class to suggestion', function() {
- expect($suggestion2).toHaveClass('tt-is-under-cursor');
+ expect(this.$suggestion2).toHaveClass('tt-is-under-cursor');
});
it('should remove tt-is-under-cursor class from other suggestions',
function() {
- expect($suggestion1).not.toHaveClass('tt-is-under-cursor');
- expect($suggestion3).not.toHaveClass('tt-is-under-cursor');
+ expect(this.$suggestion1).not.toHaveClass('tt-is-under-cursor');
+ expect(this.$suggestion3).not.toHaveClass('tt-is-under-cursor');
});
});
- describe('when click suggestion', function() {
- var spy, $suggestion;
+ describe('on suggestion click', function() {
beforeEach(function() {
- this.dropdownView.on('select', spy = jasmine.createSpy());
+ this.dropdownView
+ .on('suggestionSelected', this.spy = jasmine.createSpy());
- $suggestion = this.$testSet
+ this.$testDataset = renderTestDataset(this.dropdownView, true);
+
+ this.$suggestion = this.$testDataset
.data('query', 'test query')
.data('dataset', 'test dataset')
- .find('.tt-suggestion:nth-child(1)').click();
+ .find('.tt-suggestion:nth-child(1)')
+ .click();
});
- it('should trigger select', function() {
- expect(spy).toHaveBeenCalledWith({
- type: 'select',
+ it('should trigger suggestionSelected', function() {
+ expect(this.spy).toHaveBeenCalledWith({
+ type: 'suggestionSelected',
data: {
query: 'test query',
dataset: 'test dataset',
@@ -94,151 +88,146 @@ describe('DropdownView', function() {
});
});
- describe('#hideUnlessMouseIsOverDropdown', function() {
+ describe('#closeUnlessMouseIsOverDropdown', function() {
beforeEach(function() {
- spyOn(this.dropdownView, 'hide');
+ spyOn(this.dropdownView, 'close');
});
describe('if isMouseOverDropdown is true', function() {
beforeEach(function() {
this.dropdownView.isMouseOverDropdown = true;
- this.dropdownView.hideUnlessMouseIsOverDropdown();
+ this.dropdownView.closeUnlessMouseIsOverDropdown();
});
- it('should not call hide', function() {
- expect(this.dropdownView.hide).not.toHaveBeenCalled();
+ it('should not call close', function() {
+ expect(this.dropdownView.close).not.toHaveBeenCalled();
});
});
describe('if isMouseOverDropdown is false', function() {
beforeEach(function() {
this.dropdownView.isMouseOverDropdown = false;
- this.dropdownView.hideUnlessMouseIsOverDropdown();
+ this.dropdownView.closeUnlessMouseIsOverDropdown();
});
- it('should call hide', function() {
- expect(this.dropdownView.hide).toHaveBeenCalled();
+ it('should call close', function() {
+ expect(this.dropdownView.close).toHaveBeenCalled();
});
});
});
- describe('#hide', function() {
- var spy;
-
- beforeEach(function() {
- this.dropdownView.on('hide', spy = jasmine.createSpy());
- });
-
- describe('if menu has tt-is-open class', function() {
+ describe('#close', function() {
+ describe('if open', function() {
beforeEach(function() {
- this.$menu.addClass('tt-is-open')
+ renderTestDataset(this.dropdownView, true);
+ this.dropdownView.on('closed', this.spy = jasmine.createSpy());
+
+ this.$menu
.find('.tt-suggestions > .tt-suggestion')
.addClass('.tt-is-under-cursor');
- this.dropdownView.hide();
+ this.dropdownView.close();
});
- it('should remove tt-is-open class', function() {
- expect(this.$menu).not.toHaveClass('tt-is-open');
+ it('should close menu', function() {
+ expect(this.$menu).toBeHidden();
});
it('should remove tt-is-under-cursor class from suggestions', function() {
- var $suggestions = this.$menu.find('.tt-suggestions > .tt-suggestion');
+ var $suggestions = this.$menu.find('.tt-suggestion');
+
expect($suggestions).not.toHaveClass('tt-is-under-cursor');
});
- it('should trigger hide', function() {
- expect(spy).toHaveBeenCalled();
+ it('should trigger closed', function() {
+ expect(this.spy).toHaveBeenCalled();
});
});
- describe('if menu does not have tt-is-open class', function() {
+ describe('if not open', function() {
beforeEach(function() {
- this.$menu.removeClass('tt-is-open');
+ renderTestDataset(this.dropdownView, false);
+ this.dropdownView.on('closed', this.spy = jasmine.createSpy());
- this.dropdownView.hide();
+ this.dropdownView.close();
});
- it('should not add the tt-is-open class', function() {
- expect(this.$menu).not.toHaveClass('tt-is-open');
+ it('should keep menu hidden', function() {
+ expect(this.$menu).toBeHidden();
});
- it('should not trigger hide', function() {
- expect(spy).not.toHaveBeenCalled();
+ it('should not trigger closed', function() {
+ expect(this.spy).not.toHaveBeenCalled();
});
});
});
- describe('#show', function() {
- var spy;
-
- beforeEach(function() {
- this.dropdownView.on('show', spy = jasmine.createSpy());
- });
-
- describe('if menu has tt-is-open class', function() {
+ describe('#open', function() {
+ describe('if open', function() {
beforeEach(function() {
- this.$menu.addClass('tt-is-open');
+ renderTestDataset(this.dropdownView, true);
+ this.dropdownView.on('opened', this.spy = jasmine.createSpy());
- this.dropdownView.show();
+ this.dropdownView.open();
});
- it('should not remove tt-is-open class', function() {
- expect(this.$menu).toHaveClass('tt-is-open');
+ it('should keep menu visible', function() {
+ expect(this.$menu).toBeVisible();
});
- it('should not trigger show', function() {
- expect(spy).not.toHaveBeenCalled();
+ it('should not trigger opened', function() {
+ expect(this.spy).not.toHaveBeenCalled();
});
});
- describe('if menu does not have tt-is-open class', function() {
+ describe('if not open', function() {
beforeEach(function() {
- this.$menu.removeClass('tt-is-open');
+ renderTestDataset(this.dropdownView, false);
+ this.dropdownView.on('opened', this.spy = jasmine.createSpy());
- this.dropdownView.show();
+ this.dropdownView.open();
});
- it('should add the tt-is-open class', function() {
- expect(this.$menu).toHaveClass('tt-is-open');
+ it('should make menu visible', function() {
+ expect(this.$menu).toBeVisible();
});
- it('should trigger show', function() {
- expect(spy).toHaveBeenCalled();
+ it('should trigger opened', function() {
+ expect(this.spy).toHaveBeenCalled();
});
});
});
describe('#moveCursorUp', function() {
- var cursorOnSpy, cursorOffSpy;
-
beforeEach(function() {
- this.dropdownView.on('cursorOn', cursorOnSpy = jasmine.createSpy());
- this.dropdownView.on('cursorOff', cursorOffSpy = jasmine.createSpy());
+ this.dropdownView
+ .on('cursorMoved', this.cursorMovedSpy = jasmine.createSpy())
+ .on('cursorRemoved', this.cursorRemovedSpy = jasmine.createSpy());
});
- describe('if menu is closed', function() {
+ describe('if not visible', function() {
beforeEach(function() {
- this.dropdownView.hide();
+ renderTestDataset(this.dropdownView, false);
+
this.dropdownView.moveCursorUp();
});
it('should not move the cursor to any suggestion', function() {
- expect(this.$testSet.find('.under-cursor')).not.toExist();
+ expect(this.$menu.find('.tt-is-under-cursor')).not.toExist();
});
- it('should not trigger cursorOn', function() {
- expect(cursorOnSpy).not.toHaveBeenCalled();
+ it('should not trigger cursorMoved', function() {
+ expect(this.cursorMovedSpy).not.toHaveBeenCalled();
});
- it('should not trigger cursorOff', function() {
- expect(cursorOffSpy).not.toHaveBeenCalled();
+ it('should not trigger cursorRemoved', function() {
+ expect(this.cursorRemovedSpy).not.toHaveBeenCalled();
});
});
- describe('if menu is open', function() {
+ describe('if visible', function() {
beforeEach(function() {
- this.dropdownView.show();
+ renderTestDataset(this.dropdownView, true);
});
describe('if no suggestion is under the cursor', function() {
@@ -247,21 +236,22 @@ describe('DropdownView', function() {
});
it('should move cursor to last suggestion', function() {
- var $lastSuggestion = this.$testSet.find('.tt-suggestion').last(),
- $suggestionUnderCursor = this.$testSet
- .find('.tt-is-under-cursor');
+ var $lastSuggestion = this.$menu
+ .find('.tt-suggestion')
+ .last(),
+ $suggestionUnderCursor = this.$menu.find('.tt-is-under-cursor');
expect($lastSuggestion).toBe($suggestionUnderCursor);
});
- it('should trigger cursorOn', function() {
- expect(cursorOnSpy).toHaveBeenCalled();
+ it('should trigger cursorMoved', function() {
+ expect(this.cursorMovedSpy).toHaveBeenCalled();
});
});
describe('if the last suggestion is under the cursor', function() {
beforeEach(function() {
- this.$testSet
+ this.$menu
.find('.tt-suggestion')
.last()
.addClass('tt-is-under-cursor');
@@ -270,9 +260,8 @@ describe('DropdownView', function() {
});
it('should move cursor to previous suggestion', function() {
- var $suggestionUnderCursor = this.$testSet
- .find('.tt-is-under-cursor'),
- $prevSuggestion = this.$testSet
+ var $suggestionUnderCursor = this.$menu.find('.tt-is-under-cursor'),
+ $prevSuggestion = this.$menu
.find('.tt-suggestion')
.last()
.prev();
@@ -280,14 +269,14 @@ describe('DropdownView', function() {
expect($prevSuggestion).toBe($suggestionUnderCursor);
});
- it('should trigger cursorOn', function() {
- expect(cursorOnSpy).toHaveBeenCalled();
+ it('should trigger cursorMoved', function() {
+ expect(this.cursorMovedSpy).toHaveBeenCalled();
});
});
describe('if the first suggestion is under the cursor', function() {
beforeEach(function() {
- this.$testSet
+ this.$menu
.find('.tt-suggestion')
.first()
.addClass('tt-is-under-cursor');
@@ -296,50 +285,48 @@ describe('DropdownView', function() {
});
it('should remove the cursor', function() {
- var $suggestionUnderCursor = this.$testSet
- .find('.tt-is-under-cursor');
+ var $suggestionUnderCursor = this.$menu.find('.tt-is-under-cursor');
expect($suggestionUnderCursor).not.toExist();
});
- it('should trigger cursorOff', function() {
- expect(cursorOffSpy).toHaveBeenCalled();
+ it('should trigger cursorRemoved', function() {
+ expect(this.cursorRemovedSpy).toHaveBeenCalled();
});
});
});
});
describe('#moveCursorDown', function() {
- var cursorOnSpy, cursorOffSpy;
-
beforeEach(function() {
- this.dropdownView.on('cursorOn', cursorOnSpy = jasmine.createSpy());
- this.dropdownView.on('cursorOff', cursorOffSpy = jasmine.createSpy());
+ this.dropdownView
+ .on('cursorMoved', this.cursorMovedSpy = jasmine.createSpy())
+ .on('cursorRemoved', this.cursorRemovedSpy = jasmine.createSpy());
});
- describe('if menu is closed', function() {
+ describe('if not visible', function() {
beforeEach(function() {
- this.dropdownView.hide();
+ renderTestDataset(this.dropdownView, false);
this.dropdownView.moveCursorDown();
});
it('should not move the cursor to any suggestion', function() {
- expect(this.$testSet.find('.under-cursor')).not.toExist();
+ expect(this.$menu.find('.tt-is-under-cursor')).not.toExist();
});
- it('should not trigger cursorOn', function() {
- expect(cursorOnSpy).not.toHaveBeenCalled();
+ it('should not trigger cursorMoved', function() {
+ expect(this.cursorMovedSpy).not.toHaveBeenCalled();
});
- it('should not trigger cursorOff', function() {
- expect(cursorOffSpy).not.toHaveBeenCalled();
+ it('should not trigger cursorRemoved', function() {
+ expect(this.cursorRemovedSpy).not.toHaveBeenCalled();
});
});
- describe('if menu is open', function() {
+ describe('if visible', function() {
beforeEach(function() {
- this.dropdownView.show();
+ renderTestDataset(this.dropdownView, true);
});
describe('if no suggestion is under the cursor', function() {
@@ -348,21 +335,22 @@ describe('DropdownView', function() {
});
it('should move cursor to first suggestion', function() {
- var $firstSuggestion = this.$testSet.find('.tt-suggestion').first(),
- $suggestionUnderCursor = this.$testSet
+ var $firstSuggestion = this.$menu
+ .find('.tt-suggestion').first(),
+ $suggestionUnderCursor = this.$menu
.find('.tt-is-under-cursor');
expect($firstSuggestion).toBe($suggestionUnderCursor);
});
- it('should trigger cursorOn', function() {
- expect(cursorOnSpy).toHaveBeenCalled();
+ it('should trigger cursorMoved', function() {
+ expect(this.cursorMovedSpy).toHaveBeenCalled();
});
});
describe('if the first suggestion is under the cursor', function() {
beforeEach(function() {
- this.$testSet
+ this.$menu
.find('.tt-suggestion')
.first()
.addClass('tt-is-under-cursor');
@@ -371,9 +359,8 @@ describe('DropdownView', function() {
});
it('should move cursor to next suggestion', function() {
- var $suggestionUnderCursor = this.$testSet
- .find('.tt-is-under-cursor'),
- $nextSuggestion = this.$testSet
+ var $suggestionUnderCursor = this.$menu.find('.tt-is-under-cursor'),
+ $nextSuggestion = this.$menu
.find('.tt-suggestion')
.first()
.next();
@@ -381,14 +368,14 @@ describe('DropdownView', function() {
expect($nextSuggestion).toBe($suggestionUnderCursor);
});
- it('should trigger cursorOn', function() {
- expect(cursorOnSpy).toHaveBeenCalled();
+ it('should trigger cursorMoved', function() {
+ expect(this.cursorMovedSpy).toHaveBeenCalled();
});
});
describe('if the last suggestion is under the cursor', function() {
beforeEach(function() {
- this.$testSet
+ this.$menu
.find('.tt-suggestion')
.last()
.addClass('tt-is-under-cursor');
@@ -397,20 +384,23 @@ describe('DropdownView', function() {
});
it('should remove the cursor', function() {
- var $suggestionUnderCursor = this.$testSet
- .find('.tt-is-under-cursor');
+ var $suggestionUnderCursor = this.$menu.find('.tt-is-under-cursor');
expect($suggestionUnderCursor).not.toExist();
});
- it('should trigger cursorOff', function() {
- expect(cursorOffSpy).toHaveBeenCalled();
+ it('should trigger cursorRemoved', function() {
+ expect(this.cursorRemovedSpy).toHaveBeenCalled();
});
});
});
});
describe('#getSuggestionUnderCursor', function() {
+ beforeEach(function() {
+ this.$testDataset = renderTestDataset(this.dropdownView, true);
+ });
+
describe('if no suggestion is under the cursor', function() {
it('should return null', function() {
expect(this.dropdownView.getSuggestionUnderCursor()).toBeNull();
@@ -418,45 +408,50 @@ describe('DropdownView', function() {
});
describe('if suggestion is under the cursor', function() {
- var $suggestion;
-
- beforeEach(function() {
- $suggestion = this.$testSet
- .find('.tt-suggestion')
- .first()
- .addClass('tt-is-under-cursor');
- });
-
it('should return obj with data about suggestion under the cursor',
function() {
- var suggestionData = this.dropdownView.getSuggestionUnderCursor();
+ var $suggestion = this.$menu
+ .find('.tt-suggestion')
+ .first()
+ .addClass('tt-is-under-cursor'),
+ suggestionData = this.dropdownView.getSuggestionUnderCursor();
expect(suggestionData.value).toBe($suggestion.data('value'));
- expect(suggestionData.query).toBe(this.$testSet.data('query'));
- expect(suggestionData.dataset).toBe(this.$testSet.data('dataset'));
+ expect(suggestionData.query).toBe(this.$testDataset.data('query'));
+ expect(suggestionData.dataset).toBe(this.$testDataset.data('dataset'));
});
});
});
describe('#getFirstSuggestion', function() {
+ beforeEach(function() {
+ this.$testDataset = renderTestDataset(this.dropdownView, true);
+ });
+
it('should return obj with data about suggestion under the cursor',
function() {
var $firstSuggestion = this.dropdownView._getSuggestions().first(),
suggestionData = this.dropdownView.getFirstSuggestion();
expect(suggestionData.value).toBe($firstSuggestion.data('value'));
- expect(suggestionData.query).toBe(this.$testSet.data('query'));
- expect(suggestionData.dataset).toBe(this.$testSet.data('dataset'));
+ expect(suggestionData.query).toBe(this.$testDataset.data('query'));
+ expect(suggestionData.dataset).toBe(this.$testDataset.data('dataset'));
});
});
describe('#renderSuggestions', function() {
var template = {
- render: function(c) { return '' + c.value + '
'; }
+ render: function(c) {
+ return '' + c.value + '
';
+ }
},
mockNewDataset = { name: 'new', template: template },
mockOldDataset = { name: 'test', template: template };
+ beforeEach(function() {
+ this.$testDataset = renderTestDataset(this.dropdownView, true);
+ });
+
describe('if new dataset', function() {
beforeEach(function() {
this.dropdownView.renderSuggestions('query', mockNewDataset, []);
@@ -469,7 +464,7 @@ describe('DropdownView', function() {
describe('if there are no suggestions', function() {
beforeEach(function() {
- this.dropdownView.on('suggestionsRender', spy = jasmine.createSpy());
+ this.dropdownView.on('suggestionsRendered', spy = jasmine.createSpy());
spyOn(this.dropdownView, 'clearSuggestions');
@@ -480,16 +475,15 @@ describe('DropdownView', function() {
expect(this.dropdownView.clearSuggestions).toHaveBeenCalledWith('test');
});
- it('should trigger suggestionsRender', function() {
+ it('should trigger suggestionsRendered', function() {
expect(spy).toHaveBeenCalled();
});
});
describe('if there are suggestions', function() {
- var spy;
-
beforeEach(function() {
- this.dropdownView.on('suggestionsRender', spy = jasmine.createSpy());
+ this.dropdownView
+ .on('suggestionsRendered', this.spy = jasmine.createSpy());
spyOn(this.dropdownView, 'clearSuggestions').andCallThrough();
@@ -504,31 +498,28 @@ describe('DropdownView', function() {
expect(this.dropdownView.clearSuggestions).toHaveBeenCalledWith('test');
});
- it('should remove tt-is-empty class from menu', function() {
- expect(this.dropdownView.$menu).not.toHaveClass('tt-is-empty');
- });
-
it('should update data values on list', function() {
- expect(this.$testSet).toHaveData('query', 'query');
- expect(this.$testSet).toHaveData('dataset', 'test');
+ expect(this.$testDataset).toHaveData('query', 'query');
+ expect(this.$testDataset).toHaveData('dataset', 'test');
});
it('should overwrite previous suggestions', function() {
- var $suggestions = this.$testSet.children();
+ var $suggestions = this.$testDataset.children();
expect($suggestions.length).toBe(1);
expect($suggestions.first()).toHaveText('i am a value');
expect($suggestions.first()).toHaveData('value', 'i am a value');
});
- it('should trigger suggestionsRender', function() {
- expect(spy).toHaveBeenCalled();
+ it('should trigger suggestionsRendered', function() {
+ expect(this.spy).toHaveBeenCalled();
});
});
});
describe('#clearSuggestions', function() {
beforeEach(function() {
+ renderTestDataset(this.dropdownView, true);
this.dropdownView.clearSuggestions();
});
@@ -536,8 +527,33 @@ describe('DropdownView', function() {
expect(this.$menu.find('.tt-suggestion')).not.toExist();
});
- it('should add tt-is-empty class to menu', function() {
- expect(this.$menu).toHaveClass('tt-is-empty');
+ it('should close menu', function() {
+ expect(this.$menu).toBeHidden();
});
});
+
+ // helper functions
+ // ----------------
+
+ function renderTestDataset(view, open) {
+ var mockQuery = 'test q',
+ mockDataset = {
+ name: 'test' ,
+ template: {
+ render: function(c) {
+ return '' + c.value + '
';
+ }
+ }
+ },
+ mockSuggestions = [
+ { value: 'one' },
+ { value: 'two' },
+ { value: 'three' }
+ ];
+
+ view.renderSuggestions(mockQuery, mockDataset, mockSuggestions);
+ open && view.open();
+
+ return $('#jasmine-fixtures .tt-dataset-test > .tt-suggestions');
+ }
});
diff --git a/test/input_view_spec.js b/test/input_view_spec.js
index c3c095b5..3678e76f 100644
--- a/test/input_view_spec.js
+++ b/test/input_view_spec.js
@@ -28,23 +28,25 @@ describe('InputView', function() {
describe('when input gains focus', function() {
beforeEach(function() {
- spyOnEvent(this.$input, 'focus');
+ this.inputView.on('focused', this.spy = jasmine.createSpy());
+
this.$input.blur().focus();
});
- it('should trigger focus', function() {
- expect('focus').toHaveBeenTriggeredOn(this.$input);
+ it('should trigger focused', function() {
+ expect(this.spy).toHaveBeenCalled();
});
});
describe('when query loses focus', function() {
beforeEach(function() {
- spyOnEvent(this.$input, 'blur');
+ this.inputView.on('blured', this.spy = jasmine.createSpy());
+
this.$input.focus().blur();
});
- it('should trigger blur', function() {
- expect('blur').toHaveBeenTriggeredOn(this.$input);
+ it('should trigger blured', function() {
+ expect(this.spy).toHaveBeenCalled();
});
});
@@ -57,7 +59,7 @@ describe('InputView', function() {
this.spies = {};
keys.forEach(function(key) {
- that.inputView.on(key, that.spies[key] = jasmine.createSpy());
+ that.inputView.on(key + 'Keyed', that.spies[key] = jasmine.createSpy());
});
});
@@ -68,7 +70,7 @@ describe('InputView', function() {
simulateKeyEvent(this.$input, 'keydown', KEY_MAP[key]);
});
- it('should trigger ' + key, function() {
+ it('should trigger ' + key + 'Keyed', function() {
expect(this.spies[key]).toHaveBeenCalled();
});
});
@@ -77,8 +79,8 @@ describe('InputView', function() {
describe('when input', function() {
beforeEach(function() {
- this.inputView.on('queryChange', this.qcSpy = jasmine.createSpy());
- this.inputView.on('whitespaceChange', this.wcSpy = jasmine.createSpy());
+ this.inputView.on('queryChanged', this.qcSpy = jasmine.createSpy());
+ this.inputView.on('whitespaceChanged', this.wcSpy = jasmine.createSpy());
});
describe('if query changed', function() {
@@ -89,11 +91,11 @@ describe('InputView', function() {
simulateKeyEvent(this.$input, 'input', KEY_MAP.NORMAL);
});
- it('should trigger queryChange', function() {
+ it('should trigger queryChanged', function() {
expect(this.qcSpy).toHaveBeenCalled();
});
- it('should not trigger whitespaceChange', function() {
+ it('should not trigger whitespaceChanged', function() {
expect(this.wcSpy).not.toHaveBeenCalled();
});
@@ -110,11 +112,11 @@ describe('InputView', function() {
simulateKeyEvent(this.$input, 'input', KEY_MAP.NORMAL);
});
- it('should trigger whitespaceChange', function() {
+ it('should trigger whitespaceChanged', function() {
expect(this.wcSpy).toHaveBeenCalled();
});
- it('should not trigger queryChange', function() {
+ it('should not trigger queryChanged', function() {
expect(this.qcSpy).not.toHaveBeenCalled();
});
@@ -131,11 +133,11 @@ describe('InputView', function() {
simulateKeyEvent(this.$input, 'input', KEY_MAP.NORMAL);
});
- it('should not trigger queryChange', function() {
+ it('should not trigger queryChanged', function() {
expect(this.qcSpy).not.toHaveBeenCalled();
});
- it('should not trigger whitespaceChange', function() {
+ it('should not trigger whitespaceChanged', function() {
expect(this.wcSpy).not.toHaveBeenCalled();
});
});
diff --git a/test/typeahead_view_spec.js b/test/typeahead_view_spec.js
index 84d3da6c..74651366 100644
--- a/test/typeahead_view_spec.js
+++ b/test/typeahead_view_spec.js
@@ -36,9 +36,10 @@ describe('TypeaheadView', function() {
// handlers triggered by dropdownView events
// -----------------------------------------
- describe('when dropdownView triggers select', function() {
+ describe('when dropdownView triggers suggestionSelected', function() {
beforeEach(function() {
- this.dropdownView.trigger('select', { value: 'i am selected' });
+ this.dropdownView
+ .trigger('suggestionSelected', { value: 'i am selected' });
});
it('should update input value', function() {
@@ -50,14 +51,14 @@ describe('TypeaheadView', function() {
expect(this.inputView.focus).toHaveBeenCalled();
});
- it('should hide dropdown', function() {
- expect(this.dropdownView.hide).toHaveBeenCalled();
+ it('should close dropdown', function() {
+ expect(this.dropdownView.close).toHaveBeenCalled();
});
});
- describe('when dropdownView triggers cursorOn', function() {
+ describe('when dropdownView triggers cursorMoved', function() {
beforeEach(function() {
- this.dropdownView.trigger('cursorOn', { value: 'i am hint' });
+ this.dropdownView.trigger('cursorMoved', { value: 'i am hint' });
});
it('should clear hint', function() {
@@ -70,24 +71,24 @@ describe('TypeaheadView', function() {
});
});
- describe('when dropdownView triggers cursorOff', function() {
+ describe('when dropdownView triggers cursorRemoved', function() {
it('should reset input value to user query', function() {
this.inputView.getQuery.andReturn('san ');
- this.dropdownView.trigger('cursorOff');
+ this.dropdownView.trigger('cursorRemoved');
expect(this.inputView.setInputValue).toHaveBeenCalledWith('san ');
});
- _updateHintSpecHelper('dropdownView', 'cursorOff');
+ _updateHintSpecHelper('dropdownView', 'cursorRemoved');
});
- describe('when dropdownView triggers suggestionsRender', function() {
- _updateHintSpecHelper('dropdownView', 'suggestionsRender');
+ describe('when dropdownView triggers suggestionsRendered', function() {
+ _updateHintSpecHelper('dropdownView', 'suggestionsRendered');
});
- describe('when dropdownView triggers hide', function() {
+ describe('when dropdownView triggers closed', function() {
beforeEach(function() {
- this.dropdownView.trigger('hide');
+ this.dropdownView.trigger('closed');
});
it('should clear hint', function() {
@@ -98,15 +99,15 @@ describe('TypeaheadView', function() {
// handlers triggered by inputView events
// --------------------------------------
- describe('when inputView triggers blur', function() {
+ describe('when inputView triggers blured', function() {
beforeEach(function() {
this.inputView.getQuery.andReturn('reset');
- this.inputView.trigger('blur');
+ this.inputView.trigger('blured');
});
- it('should hide dropdown unless mouse is over it', function() {
- expect(this.dropdownView.hideUnlessMouseIsOverDropdown)
+ it('should close dropdown unless mouse is over it', function() {
+ expect(this.dropdownView.closeUnlessMouseIsOverDropdown)
.toHaveBeenCalled();
});
@@ -115,14 +116,14 @@ describe('TypeaheadView', function() {
});
});
- describe('when inputView triggers enter', function() {
+ describe('when inputView triggers enterKeyed', function() {
beforeEach(function() {
this.spy = jasmine.createSpy();
this.dropdownView.getSuggestionUnderCursor
.andReturn({ value: 'i am selected' });
- this.inputView.trigger('enter', { preventDefault: this.spy });
+ this.inputView.trigger('enterKeyed', { preventDefault: this.spy });
});
it('should update input value', function() {
@@ -134,94 +135,92 @@ describe('TypeaheadView', function() {
expect(this.spy).toHaveBeenCalled();
});
- it('should hide dropdown', function() {
- expect(this.dropdownView.hide).toHaveBeenCalled();
+ it('should close dropdown', function() {
+ expect(this.dropdownView.close).toHaveBeenCalled();
});
});
- describe('when inputView triggers whitespaceChange', function() {
- _updateHintSpecHelper('inputView', 'whitespaceChange');
+ describe('when inputView triggers whitespaceChanged', function() {
+ _updateHintSpecHelper('inputView', 'whitespaceChanged');
- it('should show the dropdown menu', function() {
- this.inputView.trigger('whitespaceChange');
- expect(this.dropdownView.show).toHaveBeenCalled();
+ it('should open the dropdown menu', function() {
+ this.inputView.trigger('whitespaceChanged');
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
describe('if language direction has changed', function() {
beforeEach(function() {
- this.typeaheadView.$node
- .removeClass('tt-ltr tt-rtl')
- .addClass('tt-ltr');
-
+ this.typeaheadView.dir = 'ltr';
this.inputView.getLanguageDirection.andReturn('rtl');
- this.inputView.trigger('whitespaceChange');
+
+ this.inputView.trigger('whitespaceChanged');
});
- it('should update language class name', function() {
- expect(this.typeaheadView.$node).toHaveClass('tt-rtl');
- expect(this.typeaheadView.$node).not.toHaveClass('tt-ltr');
+ it('should update styling', function() {
+ expect(this.typeaheadView.$node).toHaveCss({ direction: 'rtl' });
+ expect(this.dropdownView.setLanguageDirection)
+ .toHaveBeenCalledWith('rtl');
});
});
});
- describe('when inputView triggers queryChange', function() {
- it('should show the dropdown menu', function() {
- this.inputView.trigger('queryChange');
- expect(this.dropdownView.show).toHaveBeenCalled();
+ describe('when inputView triggers queryChanged', function() {
+ it('should open the dropdown menu', function() {
+ this.inputView.trigger('queryChanged');
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
it('should clear hint', function() {
- this.inputView.trigger('queryChange');
+ this.inputView.trigger('queryChanged');
expect(this.inputView.setHintValue).toHaveBeenCalledWith('');
});
it('should clear suggestions', function() {
- this.inputView.trigger('queryChange');
+ this.inputView.trigger('queryChanged');
expect(this.dropdownView.clearSuggestions).toHaveBeenCalled();
});
it('should call dropdownView.renderSuggestions for each dataset',
function() {
- this.inputView.trigger('queryChange');
+ this.inputView.trigger('queryChanged');
expect(this.dropdownView.renderSuggestions.callCount).toBe(3);
});
describe('if language direction has changed', function() {
beforeEach(function() {
- this.typeaheadView.$node
- .removeClass('tt-ltr tt-rtl')
- .addClass('tt-ltr');
-
+ this.typeaheadView.dir = 'ltr';
this.inputView.getLanguageDirection.andReturn('rtl');
- this.inputView.trigger('queryChange');
+
+ this.inputView.trigger('queryChanged');
});
- it('should update language class name', function() {
- expect(this.typeaheadView.$node).toHaveClass('tt-rtl');
- expect(this.typeaheadView.$node).not.toHaveClass('tt-ltr');
+ it('should update styling', function() {
+ expect(this.typeaheadView.$node).toHaveCss({ direction: 'rtl' });
+ expect(this.dropdownView.setLanguageDirection)
+ .toHaveBeenCalledWith('rtl');
});
});
});
- describe('when inputView triggers focus', function() {
+ describe('when inputView triggers focused', function() {
beforeEach(function() {
- this.inputView.trigger('focus');
+ this.inputView.trigger('focused');
});
- it('should show the dropdown menu', function() {
- expect(this.dropdownView.show).toHaveBeenCalled();
+ it('should open the dropdown menu', function() {
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
});
- describe('when inputView triggers esc', function() {
+ describe('when inputView triggers escKeyed', function() {
beforeEach(function() {
this.inputView.getQuery.andReturn('reset');
- this.inputView.trigger('esc');
+ this.inputView.trigger('escKeyed');
});
- it('should hide dropdown', function() {
- expect(this.dropdownView.hide).toHaveBeenCalled();
+ it('should close dropdown', function() {
+ expect(this.dropdownView.close).toHaveBeenCalled();
});
it('should reset input value to user query', function() {
@@ -229,18 +228,17 @@ describe('TypeaheadView', function() {
});
});
- describe('when inputView triggers up', function() {
-
+ describe('when inputView triggers upKeyed', function() {
describe('if modifier key was pressed', function() {
beforeEach(function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 38, shiftKey: true });
spyOn(this.$e, 'preventDefault');
- this.inputView.trigger('up', this.$e);
+ this.inputView.trigger('upKeyed', this.$e);
});
- it('should show the dropdown menu', function() {
- expect(this.dropdownView.show).toHaveBeenCalled();
+ it('should open the dropdown menu', function() {
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
it('should not prevent default browser behavior', function() {
@@ -257,11 +255,11 @@ describe('TypeaheadView', function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 38 });
spyOn(this.$e, 'preventDefault');
- this.inputView.trigger('up', this.$e);
+ this.inputView.trigger('upKeyed', this.$e);
});
it('should show the dropdown menu', function() {
- expect(this.dropdownView.show).toHaveBeenCalled();
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
it('should prevent default browser behavior', function() {
@@ -274,18 +272,18 @@ describe('TypeaheadView', function() {
});
});
- describe('when inputView triggers down', function() {
+ describe('when inputView triggers downKeyed', 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);
+ this.inputView.trigger('downKeyed', this.$e);
});
- it('should show the dropdown menu', function() {
- expect(this.dropdownView.show).toHaveBeenCalled();
+ it('should open the dropdown menu', function() {
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
it('should not prevent default browser behavior', function() {
@@ -302,11 +300,11 @@ describe('TypeaheadView', function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 40 });
spyOn(this.$e, 'preventDefault');
- this.inputView.trigger('down', this.$e);
+ this.inputView.trigger('downKeyed', this.$e);
});
- it('should show the dropdown menu', function() {
- expect(this.dropdownView.show).toHaveBeenCalled();
+ it('should open the dropdown menu', function() {
+ expect(this.dropdownView.open).toHaveBeenCalled();
});
it('should prevent default browser behavior', function() {
@@ -319,7 +317,7 @@ describe('TypeaheadView', function() {
});
});
- describe('when inputView triggers tab', function() {
+ describe('when inputView triggers tabKeyed', function() {
beforeEach(function() {
this.$e = $.extend($.Event('keydown'), { keyCode: 9 });
spyOn(this.$e, 'preventDefault');
@@ -329,7 +327,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.getHintValue.andReturn('');
- this.inputView.trigger('tab', this.$e);
+ this.inputView.trigger('tabKeyed', this.$e);
});
it('should not update input value', function() {
@@ -346,7 +344,7 @@ describe('TypeaheadView', function() {
this.inputView.getQuery.andReturn('app');
this.inputView.getHintValue.andReturn('apple');
- this.inputView.trigger('tab', this.$e);
+ this.inputView.trigger('tabKeyed', this.$e);
});
it('should update input value', function() {
@@ -359,7 +357,7 @@ describe('TypeaheadView', function() {
});
});
- describe('when inputView triggers left', function() {
+ describe('when inputView triggers leftKeyed', function() {
beforeEach(function() {
this.inputView.getQuery.andReturn('app');
this.inputView.getHintValue.andReturn('apple');
@@ -371,7 +369,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.getLanguageDirection.andReturn('ltr');
- this.inputView.trigger('left');
+ this.inputView.trigger('leftKeyed');
});
it('should not update input value', function() {
@@ -383,7 +381,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.getLanguageDirection.andReturn('rtl');
- this.inputView.trigger('left');
+ this.inputView.trigger('leftKeyed');
});
it('should update value of input', function() {
@@ -395,7 +393,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.isCursorAtEnd.andReturn(false);
- this.inputView.trigger('left');
+ this.inputView.trigger('leftKeyed');
});
it('should not update input value', function() {
@@ -404,7 +402,7 @@ describe('TypeaheadView', function() {
});
});
- describe('when inputView triggers right', function() {
+ describe('when inputView triggers rightKeyed', function() {
beforeEach(function() {
this.inputView.getQuery.andReturn('app');
this.inputView.getHintValue.andReturn('apple');
@@ -416,7 +414,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.getLanguageDirection.andReturn('ltr');
- this.inputView.trigger('right');
+ this.inputView.trigger('rightKeyed');
});
it('should update input value', function() {
@@ -428,7 +426,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.getLanguageDirection.andReturn('rtl');
- this.inputView.trigger('right');
+ this.inputView.trigger('rightKeyed');
});
it('should not update input value', function() {
@@ -440,7 +438,7 @@ describe('TypeaheadView', function() {
beforeEach(function() {
this.inputView.isCursorAtEnd.andReturn(false);
- this.inputView.trigger('right');
+ this.inputView.trigger('rightKeyed');
});
it('should not update input value', function() {
@@ -465,9 +463,9 @@ describe('TypeaheadView', function() {
});
});
- describe('if dropdown menu is closed', function() {
+ describe('if dropdown menu is not visible', function() {
it('should not show hint', function() {
- this.dropdownView.isOpen.andReturn(false);
+ this.dropdownView.isVisible.andReturn(false);
this.inputView.getInputValue.andReturn('san ');
this.dropdownView.getFirstSuggestion
.andReturn({ value: 'desert sand' });
@@ -480,7 +478,7 @@ describe('TypeaheadView', function() {
describe('if top suggestion\'s value begins with query', function() {
it('should show hint', function() {
- this.dropdownView.isOpen.andReturn(true);
+ this.dropdownView.isVisible.andReturn(true);
this.inputView.getInputValue.andReturn('san ');
this.dropdownView.getFirstSuggestion
.andReturn({ value: 'san francisco' });
@@ -495,7 +493,7 @@ describe('TypeaheadView', function() {
describe('if top suggestion\'s value does not begin with query',
function() {
it('should not show hint', function() {
- this.dropdownView.isOpen.andReturn(true);
+ this.dropdownView.isVisible.andReturn(true);
this.inputView.getInputValue.andReturn('san ');
this.dropdownView.getFirstSuggestion
.andReturn({ value: 'desert sand' });