From e0483036f7b42c47c2cfcd488ba9a23b0c383524 Mon Sep 17 00:00:00 2001 From: Jared Barboza <jared.m.barboza@gmail.com> Date: Thu, 16 Feb 2012 00:17:16 -0500 Subject: [PATCH 1/6] Typeahead now support multiple selections --- js/bootstrap-typeahead.js | 14 +++++- js/tests/unit/bootstrap-typeahead.js | 65 +++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index 1426185afc..35e11526ec 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -30,7 +30,10 @@ this.$menu = $(this.options.menu).appendTo('body') this.source = this.options.source this.shown = false + this.delimiter = this.options.delimiter || this.delimiter this.listen() + this.mode = this.options.mode || this.mode + this.selections = [] } Typeahead.prototype = { @@ -39,7 +42,11 @@ , select: function () { var val = this.$menu.find('.active').attr('data-value') - this.$element.val(val) + if( this.mode === 'multiple' ) { + this.selections.push(val) + val = this.selections.join(this.delimiter) + } + this.$element.val( val ) return this.hide() } @@ -68,8 +75,9 @@ var that = this , items , q + , input = this.mode === 'multiple' ? this.$element.val().split(this.delimiter) : [this.$element.val()] - this.query = this.$element.val() + this.query = $.trim(input[input.length - 1]) if (!this.query) { return this.shown ? this.hide() : this @@ -251,6 +259,8 @@ , items: 8 , menu: '<ul class="typeahead dropdown-menu"></ul>' , item: '<li><a href="#"></a></li>' + , delimiter: ',' + , mode: 'single' } $.fn.typeahead.Constructor = Typeahead diff --git a/js/tests/unit/bootstrap-typeahead.js b/js/tests/unit/bootstrap-typeahead.js index 455ed415b7..f0d188dde9 100644 --- a/js/tests/unit/bootstrap-typeahead.js +++ b/js/tests/unit/bootstrap-typeahead.js @@ -125,4 +125,67 @@ $(function () { typeahead.$menu.remove() }) -}) \ No newline at end of file + + test("should allow multiple selections", function () { + var $input = $('<input />').typeahead({ + source: ['aa', 'ab', 'ac'] + , mode: 'multiple' + }) + , typeahead = $input.data('typeahead') + + $input.val('a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + $input.val( $input.val() + ',a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + equals($input.val(), 'ac,ac', 'input value was correctly set') + ok(!typeahead.$menu.is(':visible'), 'the menu was hidden') + + typeahead.$menu.remove() + }) + + test("should allow user to specify delimiter in options", function () { + var $input = $('<input />').typeahead({ + source: ['aa', 'ab', 'ac'] + , delimiter: ';' + , mode: 'multiple' + }) + , typeahead = $input.data('typeahead') + + $input.val('a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + $input.val( $input.val() + ';a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + equals($input.val(), 'ac;ac', 'input value was correctly set') + }) + + test("should not allow multiple selection if multiple selection mode is not enabled", function () { + var $input = $('<input />').typeahead({ + source: ['aa', 'ab', 'ac'] + }) + , typeahead = $input.data('typeahead') + + $input.val('a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + $input.val( $input.val() + ', a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + equals($input.val(), 'ac', 'input value was correctly set') + }) +}) -- GitLab From 62c7eac26c4db082c24f7302c854774787a45068 Mon Sep 17 00:00:00 2001 From: Sam Critchley <sam.critchley@adslot.com> Date: Sat, 25 Feb 2012 18:10:32 +1100 Subject: [PATCH 2/6] updated the doc typeahead asset and updated the typeahead document to include the two new settings. when mode is multiple selection of an item will append the delimiter and a single space character to the field restored formatting --- docs/assets/js/bootstrap-typeahead.js | 18 ++++++++++++++++-- docs/javascript.html | 12 ++++++++++++ js/bootstrap-typeahead.js | 6 +++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/docs/assets/js/bootstrap-typeahead.js b/docs/assets/js/bootstrap-typeahead.js index 1426185afc..94be8468e7 100644 --- a/docs/assets/js/bootstrap-typeahead.js +++ b/docs/assets/js/bootstrap-typeahead.js @@ -30,7 +30,10 @@ this.$menu = $(this.options.menu).appendTo('body') this.source = this.options.source this.shown = false + this.delimiter = this.options.delimiter || this.delimiter this.listen() + this.mode = this.options.mode || this.mode + this.selections = [] } Typeahead.prototype = { @@ -39,7 +42,11 @@ , select: function () { var val = this.$menu.find('.active').attr('data-value') - this.$element.val(val) + if( this.mode === 'multiple' ) { + this.selections.push(val) + val = this.selections.join(this.formatteddelimiter()) + this.formatteddelimiter() + } + this.$element.val( val ) return this.hide() } @@ -68,8 +75,9 @@ var that = this , items , q + , input = this.mode === 'multiple' ? this.$element.val().split(this.delimiter) : [this.$element.val()] - this.query = this.$element.val() + this.query = $.trim(input[input.length - 1]) if (!this.query) { return this.shown ? this.hide() : this @@ -164,6 +172,10 @@ .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) } + , formatteddelimiter: function(){ + return this.delimiter + ' '; + } + , keyup: function (e) { e.stopPropagation() e.preventDefault() @@ -251,6 +263,8 @@ , items: 8 , menu: '<ul class="typeahead dropdown-menu"></ul>' , item: '<li><a href="#"></a></li>' + , delimiter: ',' + , mode: 'single' } $.fn.typeahead.Constructor = Typeahead diff --git a/docs/javascript.html b/docs/javascript.html index 7960a29e39..5bab801d14 100644 --- a/docs/javascript.html +++ b/docs/javascript.html @@ -1418,6 +1418,18 @@ $('.myCarousel').carousel({ <td>highlights all default matches</td> <td>Method used to highlight autocomplete results. Accepts a single argument <code>item</code> and has the scope of the typeahead instance. Should return html.</td> </tr> + <tr> + <td>mode</td> + <td>string</td> + <td>single</td> + <td>Setting used to allow single or multiple items to be selected from the autocomplete results. Accepts a single value of either 'single' or 'multiple'.</td> + </tr> + <tr> + <td>delimiter</td> + <td>string</td> + <td>,</td> + <td>Delimiter used to seperate selected autocomplete results from one another. Only used when setting 'mode' is 'multiple'.</td> + </tr> </tbody> </table> diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index 35e11526ec..94be8468e7 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -44,7 +44,7 @@ var val = this.$menu.find('.active').attr('data-value') if( this.mode === 'multiple' ) { this.selections.push(val) - val = this.selections.join(this.delimiter) + val = this.selections.join(this.formatteddelimiter()) + this.formatteddelimiter() } this.$element.val( val ) return this.hide() @@ -172,6 +172,10 @@ .on('mouseenter', 'li', $.proxy(this.mouseenter, this)) } + , formatteddelimiter: function(){ + return this.delimiter + ' '; + } + , keyup: function (e) { e.stopPropagation() e.preventDefault() -- GitLab From 02f5dd1cf6766b93100172d35f9222f879ee79af Mon Sep 17 00:00:00 2001 From: Sam Critchley <sam.critchley@adslot.com> Date: Sat, 25 Feb 2012 19:21:11 +1100 Subject: [PATCH 3/6] added support for backspace selection clearing functionality when mode is set to multiple. if a user pushes backspace after making a selection then the previous selection is removed. backspace is unchanged when the autocomplete results are showing --- docs/assets/js/bootstrap-typeahead.js | 26 +++++++++++++++++++++++--- js/bootstrap-typeahead.js | 26 +++++++++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/docs/assets/js/bootstrap-typeahead.js b/docs/assets/js/bootstrap-typeahead.js index 94be8468e7..58082d5dce 100644 --- a/docs/assets/js/bootstrap-typeahead.js +++ b/docs/assets/js/bootstrap-typeahead.js @@ -44,12 +44,24 @@ var val = this.$menu.find('.active').attr('data-value') if( this.mode === 'multiple' ) { this.selections.push(val) - val = this.selections.join(this.formatteddelimiter()) + this.formatteddelimiter() + val = this.selections.join(this.formatteddelimiter()) + if (val.length) val += this.formatteddelimiter() } this.$element.val( val ) return this.hide() } + , pop: function () { + var val = null + if( this.mode === 'multiple' ) { + this.selections.pop() + val = this.selections.join(this.formatteddelimiter()) + if (val.length) val += this.formatteddelimiter() + } + this.$element.val( val ) + return this + } + , show: function () { var pos = $.extend({}, this.$element.offset(), { height: this.$element[0].offsetHeight @@ -173,18 +185,22 @@ } , formatteddelimiter: function(){ - return this.delimiter + ' '; + return this.delimiter + ' ' } , keyup: function (e) { e.stopPropagation() e.preventDefault() - + switch(e.keyCode) { case 40: // down arrow case 38: // up arrow break + case 8: // backspace + if (this.mode === 'multiple' && !this.shown) this.pop() + break + case 9: // tab case 13: // enter if (!this.shown) return @@ -203,6 +219,10 @@ , keypress: function (e) { e.stopPropagation() + + if (e.keyCode === 8 && this.mode === 'multiple' && !this.shown) + e.preventDefault() + if (!this.shown) return switch(e.keyCode) { diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index 94be8468e7..58082d5dce 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -44,12 +44,24 @@ var val = this.$menu.find('.active').attr('data-value') if( this.mode === 'multiple' ) { this.selections.push(val) - val = this.selections.join(this.formatteddelimiter()) + this.formatteddelimiter() + val = this.selections.join(this.formatteddelimiter()) + if (val.length) val += this.formatteddelimiter() } this.$element.val( val ) return this.hide() } + , pop: function () { + var val = null + if( this.mode === 'multiple' ) { + this.selections.pop() + val = this.selections.join(this.formatteddelimiter()) + if (val.length) val += this.formatteddelimiter() + } + this.$element.val( val ) + return this + } + , show: function () { var pos = $.extend({}, this.$element.offset(), { height: this.$element[0].offsetHeight @@ -173,18 +185,22 @@ } , formatteddelimiter: function(){ - return this.delimiter + ' '; + return this.delimiter + ' ' } , keyup: function (e) { e.stopPropagation() e.preventDefault() - + switch(e.keyCode) { case 40: // down arrow case 38: // up arrow break + case 8: // backspace + if (this.mode === 'multiple' && !this.shown) this.pop() + break + case 9: // tab case 13: // enter if (!this.shown) return @@ -203,6 +219,10 @@ , keypress: function (e) { e.stopPropagation() + + if (e.keyCode === 8 && this.mode === 'multiple' && !this.shown) + e.preventDefault() + if (!this.shown) return switch(e.keyCode) { -- GitLab From 7eb446ffefb7b82ba978763a85e868d6fb7582ed Mon Sep 17 00:00:00 2001 From: Jared Barboza <jared.m.barboza@gmail.com> Date: Sat, 25 Feb 2012 20:53:34 -0500 Subject: [PATCH 4/6] Added tests, fixed bugs in merge The bug fixes in the merge introduced another bug, when the user would press backspace, their entire last selection would be popped from the selections array. I changed the code so that the lookup replaces the selections array with what the user had selected previously. --- docs/assets/js/bootstrap-typeahead.js | 22 +++--------- js/bootstrap-typeahead.js | 22 +++--------- js/tests/unit/bootstrap-typeahead.js | 50 ++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 40 deletions(-) diff --git a/docs/assets/js/bootstrap-typeahead.js b/docs/assets/js/bootstrap-typeahead.js index 58082d5dce..dadad8358d 100644 --- a/docs/assets/js/bootstrap-typeahead.js +++ b/docs/assets/js/bootstrap-typeahead.js @@ -44,24 +44,12 @@ var val = this.$menu.find('.active').attr('data-value') if( this.mode === 'multiple' ) { this.selections.push(val) - val = this.selections.join(this.formatteddelimiter()) - if (val.length) val += this.formatteddelimiter() + val = this.selections.join(this.formatteddelimiter()) + this.formatteddelimiter() } this.$element.val( val ) return this.hide() } - , pop: function () { - var val = null - if( this.mode === 'multiple' ) { - this.selections.pop() - val = this.selections.join(this.formatteddelimiter()) - if (val.length) val += this.formatteddelimiter() - } - this.$element.val( val ) - return this - } - , show: function () { var pos = $.extend({}, this.$element.offset(), { height: this.$element[0].offsetHeight @@ -89,6 +77,8 @@ , q , input = this.mode === 'multiple' ? this.$element.val().split(this.delimiter) : [this.$element.val()] + this.selections = input.slice(0, input.length - 1) + this.query = $.trim(input[input.length - 1]) if (!this.query) { @@ -191,16 +181,12 @@ , keyup: function (e) { e.stopPropagation() e.preventDefault() - + switch(e.keyCode) { case 40: // down arrow case 38: // up arrow break - case 8: // backspace - if (this.mode === 'multiple' && !this.shown) this.pop() - break - case 9: // tab case 13: // enter if (!this.shown) return diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index 58082d5dce..dadad8358d 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -44,24 +44,12 @@ var val = this.$menu.find('.active').attr('data-value') if( this.mode === 'multiple' ) { this.selections.push(val) - val = this.selections.join(this.formatteddelimiter()) - if (val.length) val += this.formatteddelimiter() + val = this.selections.join(this.formatteddelimiter()) + this.formatteddelimiter() } this.$element.val( val ) return this.hide() } - , pop: function () { - var val = null - if( this.mode === 'multiple' ) { - this.selections.pop() - val = this.selections.join(this.formatteddelimiter()) - if (val.length) val += this.formatteddelimiter() - } - this.$element.val( val ) - return this - } - , show: function () { var pos = $.extend({}, this.$element.offset(), { height: this.$element[0].offsetHeight @@ -89,6 +77,8 @@ , q , input = this.mode === 'multiple' ? this.$element.val().split(this.delimiter) : [this.$element.val()] + this.selections = input.slice(0, input.length - 1) + this.query = $.trim(input[input.length - 1]) if (!this.query) { @@ -191,16 +181,12 @@ , keyup: function (e) { e.stopPropagation() e.preventDefault() - + switch(e.keyCode) { case 40: // down arrow case 38: // up arrow break - case 8: // backspace - if (this.mode === 'multiple' && !this.shown) this.pop() - break - case 9: // tab case 13: // enter if (!this.shown) return diff --git a/js/tests/unit/bootstrap-typeahead.js b/js/tests/unit/bootstrap-typeahead.js index f0d188dde9..923244d579 100644 --- a/js/tests/unit/bootstrap-typeahead.js +++ b/js/tests/unit/bootstrap-typeahead.js @@ -138,12 +138,12 @@ $(function () { $(typeahead.$menu.find('li')[2]).mouseover().click() - $input.val( $input.val() + ',a') + $input.val( $input.val() + 'a') typeahead.lookup() $(typeahead.$menu.find('li')[2]).mouseover().click() - equals($input.val(), 'ac,ac', 'input value was correctly set') + equals($input.val(), 'ac, ac, ', 'input value was correctly set') ok(!typeahead.$menu.is(':visible'), 'the menu was hidden') typeahead.$menu.remove() @@ -162,12 +162,12 @@ $(function () { $(typeahead.$menu.find('li')[2]).mouseover().click() - $input.val( $input.val() + ';a') + $input.val( $input.val() + 'a') typeahead.lookup() $(typeahead.$menu.find('li')[2]).mouseover().click() - equals($input.val(), 'ac;ac', 'input value was correctly set') + equals($input.val(), 'ac; ac; ', 'input value was correctly set') }) test("should not allow multiple selection if multiple selection mode is not enabled", function () { @@ -188,4 +188,46 @@ $(function () { equals($input.val(), 'ac', 'input value was correctly set') }) + + test("should insert a space after the delimiter when multiple selection is enabled", function () { + var $input = $('<input />').typeahead({ + source: ['aa', 'ab', 'ac'] + , delimiter: ',' + , mode: 'multiple' + }) + , typeahead = $input.data('typeahead') + + $input.val('a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + equals($input.val(), 'ac, ', 'delimiter was followed by a space') + }) + + test("should not append old matches when user clears input", function () { + var $input = $('<input />').typeahead({ + source: ['aa', 'ab', 'ac'] + , delimiter: ',' + , mode: 'multiple' + }) + , typeahead = $input.data('typeahead') + + $input.val('a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + $input.val( $input.val() + 'a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[1]).mouseover().click() + + $input.val( 'ac, a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[0]).mouseover().click() + + equals($input.val(), 'ac, aa, ', 'input value was correctly set') + }) }) -- GitLab From 97283f2c212217a83b2d9c27a7b2e0d0e01f100c Mon Sep 17 00:00:00 2001 From: Jared Barboza <jared.m.barboza@gmail.com> Date: Sat, 25 Feb 2012 21:28:55 -0500 Subject: [PATCH 5/6] Removed code that prevents backspace key from deleting when in multiple selection mode. Forgot to push this commit when I changed the merge. --- docs/assets/js/bootstrap-typeahead.js | 3 --- js/bootstrap-typeahead.js | 3 --- 2 files changed, 6 deletions(-) diff --git a/docs/assets/js/bootstrap-typeahead.js b/docs/assets/js/bootstrap-typeahead.js index dadad8358d..c8e91a7339 100644 --- a/docs/assets/js/bootstrap-typeahead.js +++ b/docs/assets/js/bootstrap-typeahead.js @@ -206,9 +206,6 @@ , keypress: function (e) { e.stopPropagation() - if (e.keyCode === 8 && this.mode === 'multiple' && !this.shown) - e.preventDefault() - if (!this.shown) return switch(e.keyCode) { diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index dadad8358d..c8e91a7339 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -206,9 +206,6 @@ , keypress: function (e) { e.stopPropagation() - if (e.keyCode === 8 && this.mode === 'multiple' && !this.shown) - e.preventDefault() - if (!this.shown) return switch(e.keyCode) { -- GitLab From 9bf32cd8050615ef630b8f84f4b356a3123e0403 Mon Sep 17 00:00:00 2001 From: Jared Barboza <jared.m.barboza@gmail.com> Date: Wed, 29 Feb 2012 23:36:36 -0500 Subject: [PATCH 6/6] Added fix for a bug that @rversaw pointed out. after the user selected multiple items there would be extra spaces hanging around in the input field --- docs/assets/js/bootstrap-typeahead.js | 2 +- js/bootstrap-typeahead.js | 2 +- js/tests/unit/bootstrap-typeahead.js | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/assets/js/bootstrap-typeahead.js b/docs/assets/js/bootstrap-typeahead.js index c8e91a7339..ee25610c57 100644 --- a/docs/assets/js/bootstrap-typeahead.js +++ b/docs/assets/js/bootstrap-typeahead.js @@ -75,7 +75,7 @@ var that = this , items , q - , input = this.mode === 'multiple' ? this.$element.val().split(this.delimiter) : [this.$element.val()] + , input = this.mode === 'multiple' ? this.$element.val().split(this.formatteddelimiter()) : [this.$element.val()] this.selections = input.slice(0, input.length - 1) diff --git a/js/bootstrap-typeahead.js b/js/bootstrap-typeahead.js index c8e91a7339..ee25610c57 100644 --- a/js/bootstrap-typeahead.js +++ b/js/bootstrap-typeahead.js @@ -75,7 +75,7 @@ var that = this , items , q - , input = this.mode === 'multiple' ? this.$element.val().split(this.delimiter) : [this.$element.val()] + , input = this.mode === 'multiple' ? this.$element.val().split(this.formatteddelimiter()) : [this.$element.val()] this.selections = input.slice(0, input.length - 1) diff --git a/js/tests/unit/bootstrap-typeahead.js b/js/tests/unit/bootstrap-typeahead.js index 923244d579..93a3a41527 100644 --- a/js/tests/unit/bootstrap-typeahead.js +++ b/js/tests/unit/bootstrap-typeahead.js @@ -230,4 +230,30 @@ $(function () { equals($input.val(), 'ac, aa, ', 'input value was correctly set') }) + + test("should not have extra spaces when multiple items are selected", function () { + var $input = $('<input />').typeahead({ + source: ['aa', 'ab', 'ac'] + , delimiter: ';' + , mode: 'multiple' + }) + , typeahead = $input.data('typeahead') + + $input.val('a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + $input.val( $input.val() + 'a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + $input.val( $input.val() + 'a') + typeahead.lookup() + + $(typeahead.$menu.find('li')[2]).mouseover().click() + + equals($input.val(), 'ac; ac; ac; ', 'input value was correctly set') + }) }) -- GitLab