From 3729c9fca8418779e0413d79f2d128d31b4df741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Gauthier?= Date: Thu, 20 Nov 2014 20:17:03 -0500 Subject: [PATCH 1/5] Added support for json object in addition to String as source for typeahead. --- README.md | 34 ++++++++++++--- bootstrap3-typeahead.js | 94 +++++++++++++++++++++++++---------------- 2 files changed, 86 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 75e1693..d5e45b7 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,6 @@ Destroys previously initialized typeaheads. This entails reverting DOM modificat $('.typeahead').typeahead('destroy') -Also read: [How to Use JSON Objects With Twitter Bootstrap Typeahead](http://tatiyants.com/how-to-use-json-objects-with-twitter-bootstrap-typeahead/) - Javascript Example ============= @@ -62,7 +60,30 @@ Loading a collection },'json'); //example_collection.json // ["item1","item2","item3"] + +Using JSON objects instead of simple strings +-------------------------------------------- + You can add all the properties you wish on your object, only name matters for typeahead. The other values are for you, to be able to match the selected item with something in your model. + + var $input = $('.typeahead'); + $input.typeahead({source:[{id: "someId1", name: "Display name 1"}, {id: "someId2", name: "Display name 2"}], + autoSelect: true}); + $input.change(function() { + var current = $input.typeahead("getActive"); + if (current) { + // Some item from your model is active! + if (current.name == $input.val()) { + // This means the exact match is found + } else { + // This means it is only a partial match, you can either add a new item or take the active if you don't want new items + } + } else { + // Nothing is active so it is a new value (or maybe empty value) + } + }); + + Options ======= @@ -82,7 +103,7 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap source array, function [ ] - The data source to query against. May be an array of strings or a function. The function accepts two arguments, the query value in the input field and the process callback. The function may be used synchronously by returning the data source directly or asynchronously via the process callback's single argument. + The data source to query against. May be an array of strings, an array of JSON object with a name property or a function. The function accepts two arguments, the query value in the input field and the process callback. The function may be used synchronously by returning the data source directly or asynchronously via the process callback's single argument. items @@ -142,7 +163,7 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap afterSelect function $.noop() - Call back function to execute after selected an item. + Call back function to execute after selected an item. It gets the current active item in parameter if any. delay @@ -157,10 +178,11 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap Methods ======= - .typeahead(options) + .typeahead(options): Initializes an input with a typeahead. .lookup: To trigger the lookup function externally + .getActive: To get the currently active item, you will get a String or a JSOn object depending on how you initialized typeahead. Works only for the first match. + -Initializes an input with a typeahead. Bower ===== diff --git a/bootstrap3-typeahead.js b/bootstrap3-typeahead.js index c4cc32f..5702eaf 100644 --- a/bootstrap3-typeahead.js +++ b/bootstrap3-typeahead.js @@ -71,11 +71,13 @@ , select: function () { var val = this.$menu.find('.active').data('value'); + this.$element.data("active", val); if(this.autoSelect || val) { + var newVal = this.updater(val); this.$element - .val(this.updater(val)) + .val(newVal.name || newVal) .change(); - this.afterSelect(); + this.afterSelect(newVal); } return this.hide(); } @@ -123,7 +125,7 @@ this.query = this.$element.val() || ''; } - if ((this.query.length < this.options.minLength) && !this.showHintOnFocus) { + if (this.query.length < this.options.minLength) { return this.shown ? this.hide() : this; } @@ -146,10 +148,16 @@ }); items = this.sorter(items); - + if (!items.length) { return this.shown ? this.hide() : this; } + + if (items.length > 0) { + this.$element.data("active", items[0]); + } else { + this.$element.data("active", null); + } if (this.options.items == 'all') { return this.render(items).show(); @@ -159,7 +167,8 @@ } , matcher: function (item) { - return ~item.toLowerCase().indexOf(this.query.toLowerCase()); + var it = item.name ? item.name : item; + return ~it.toLowerCase().indexOf(this.query.toLowerCase()); } , sorter: function (items) { @@ -169,48 +178,57 @@ , item; while ((item = items.shift())) { - if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); - else if (~item.indexOf(this.query)) caseSensitive.push(item); + var it = item.name ? item.name : item; + if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); + else if (~it.indexOf(this.query)) caseSensitive.push(item); else caseInsensitive.push(item); } return beginswith.concat(caseSensitive, caseInsensitive); } - , highlighter: function (item) { - var html = $('
'); - var query = this.query; + , highlighter: function (item) { + var html = $('
'); + var query = this.query; var i = item.toLowerCase().indexOf(query.toLowerCase()); - var len, leftPart, middlePart, rightPart, strong; - len = query.length; - if(len == 0){ - return html.text(item).html(); - } - while (i > -1) { - leftPart = item.substr(0, i); - middlePart = item.substr(i, len); - rightPart = item.substr(i + len); - strong = $('').text(middlePart); - html - .append(document.createTextNode(leftPart)) - .append(strong); - item = rightPart; - i = item.toLowerCase().indexOf(query.toLowerCase()); - } - return html.append(document.createTextNode(item)).html(); + var len, leftPart, middlePart, rightPart, strong; + len = query.length; + if(len == 0){ + return html.text(item).html(); + } + while (i > -1) { + leftPart = item.substr(0, i); + middlePart = item.substr(i, len); + rightPart = item.substr(i + len); + strong = $('').text(middlePart); + html + .append(document.createTextNode(leftPart)) + .append(strong); + item = rightPart; + i = item.toLowerCase().indexOf(query.toLowerCase()); + } + return html.append(document.createTextNode(item)).html(); } , render: function (items) { var that = this; - + var self = this; + var activeFound = false; items = $(items).map(function (i, item) { + var text = item.name ? item.name : item; i = $(that.options.item).data('value', item); - i.find('a').html(that.highlighter(item)); + i.find('a').html(that.highlighter(text)); + if (text == self.$element.val()) { + i.addClass("active"); + self.$element.data("active", item); + activeFound = true; + } return i[0]; }); - if (this.autoSelect) { + if (this.autoSelect && !activeFound) { items.first().addClass('active'); + this.$element.data("active", items.first().data('value')); } this.$menu.html(items); return this; @@ -237,7 +255,7 @@ prev.addClass('active'); } - + , listen: function () { this.$element .on('focus', $.proxy(this.focus, this)) @@ -256,6 +274,7 @@ } , destroy : function () { this.$element.data('typeahead',null); + this.$element.data('active',null); this.$element .off('focus') .off('blur') @@ -308,7 +327,7 @@ , keydown: function (e) { this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27]); if (!this.shown && e.keyCode == 40) { - this.lookup(""); + this.lookup(); } else { this.move(e); } @@ -349,8 +368,8 @@ , focus: function (e) { if (!this.focused) { this.focused = true; - if (this.options.minLength === 0 && !this.$element.val() || this.options.showHintOnFocus) { - this.lookup(); + if (this.options.showHintOnFocus) { + this.lookup(""); } } } @@ -388,6 +407,9 @@ $.fn.typeahead = function (option) { var arg = arguments; + if (typeof option == 'string' && option == "getActive") { + return this.data("active"); + } return this.each(function () { var $this = $(this) , data = $this.data('typeahead') @@ -395,9 +417,9 @@ if (!data) $this.data('typeahead', (data = new Typeahead(this, options))); if (typeof option == 'string') { if (arg.length > 1) { - data[option].apply(data, Array.prototype.slice.call(arg ,1)); + data[option].apply(data, Array.prototype.slice.call(arg ,1)); } else { - data[option](); + data[option](); } } }); From e34d3bfafa0dffed3840e01cad9f3e1380c44b8e Mon Sep 17 00:00:00 2001 From: MissChocoe Date: Thu, 20 Nov 2014 20:22:44 -0500 Subject: [PATCH 2/5] Update README.md Improved styling of the code example --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d5e45b7..f31cc59 100644 --- a/README.md +++ b/README.md @@ -64,19 +64,21 @@ Loading a collection Using JSON objects instead of simple strings -------------------------------------------- - You can add all the properties you wish on your object, only name matters for typeahead. The other values are for you, to be able to match the selected item with something in your model. +You can add all the properties you wish on your objects, as long as you provide a "name" attribute. The other values are for you, to be able to match the selected item with something in your model. var $input = $('.typeahead'); - $input.typeahead({source:[{id: "someId1", name: "Display name 1"}, {id: "someId2", name: "Display name 2"}], - autoSelect: true}); + $input.typeahead({source:[{id: "someId1", name: "Display name 1"}, + {id: "someId2", name: "Display name 2"}], + autoSelect: true}); $input.change(function() { var current = $input.typeahead("getActive"); if (current) { // Some item from your model is active! if (current.name == $input.val()) { - // This means the exact match is found + // This means the exact match is found. Use toLowerCase() if you want case insensitive match. } else { - // This means it is only a partial match, you can either add a new item or take the active if you don't want new items + // This means it is only a partial match, you can either add a new item + // or take the active if you don't want new items } } else { // Nothing is active so it is a new value (or maybe empty value) From 9c368551bf68ad92acd4b3c254efb0d91a36a80f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Gauthier?= Date: Thu, 20 Nov 2014 20:45:43 -0500 Subject: [PATCH 3/5] Restored formatting --- bootstrap3-typeahead.js | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/bootstrap3-typeahead.js b/bootstrap3-typeahead.js index 5702eaf..1a48b77 100644 --- a/bootstrap3-typeahead.js +++ b/bootstrap3-typeahead.js @@ -148,7 +148,7 @@ }); items = this.sorter(items); - + if (!items.length) { return this.shown ? this.hide() : this; } @@ -187,27 +187,27 @@ return beginswith.concat(caseSensitive, caseInsensitive); } - , highlighter: function (item) { - var html = $('
'); - var query = this.query; + , highlighter: function (item) { + var html = $('
'); + var query = this.query; var i = item.toLowerCase().indexOf(query.toLowerCase()); - var len, leftPart, middlePart, rightPart, strong; - len = query.length; - if(len == 0){ - return html.text(item).html(); - } - while (i > -1) { - leftPart = item.substr(0, i); - middlePart = item.substr(i, len); - rightPart = item.substr(i + len); - strong = $('').text(middlePart); - html - .append(document.createTextNode(leftPart)) - .append(strong); - item = rightPart; - i = item.toLowerCase().indexOf(query.toLowerCase()); - } - return html.append(document.createTextNode(item)).html(); + var len, leftPart, middlePart, rightPart, strong; + len = query.length; + if(len == 0){ + return html.text(item).html(); + } + while (i > -1) { + leftPart = item.substr(0, i); + middlePart = item.substr(i, len); + rightPart = item.substr(i + len); + strong = $('').text(middlePart); + html + .append(document.createTextNode(leftPart)) + .append(strong); + item = rightPart; + i = item.toLowerCase().indexOf(query.toLowerCase()); + } + return html.append(document.createTextNode(item)).html(); } , render: function (items) { @@ -255,7 +255,7 @@ prev.addClass('active'); } - + , listen: function () { this.$element .on('focus', $.proxy(this.focus, this)) @@ -417,9 +417,9 @@ if (!data) $this.data('typeahead', (data = new Typeahead(this, options))); if (typeof option == 'string') { if (arg.length > 1) { - data[option].apply(data, Array.prototype.slice.call(arg ,1)); + data[option].apply(data, Array.prototype.slice.call(arg ,1)); } else { - data[option](); + data[option](); } } }); From f44c0f436d187f192b4a265715fb874bc8334ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Gauthier?= Date: Mon, 24 Nov 2014 13:58:58 -0500 Subject: [PATCH 4/5] Added a method getDisplayText to support more generic object. --- README.md | 8 +++++++- bootstrap3-typeahead.js | 11 ++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f31cc59..50c8475 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Loading a collection Using JSON objects instead of simple strings -------------------------------------------- -You can add all the properties you wish on your objects, as long as you provide a "name" attribute. The other values are for you, to be able to match the selected item with something in your model. +You can add all the properties you wish on your objects, as long as you provide a "name" attribute OR you provide your own getDisplayText method. The other values are for you, to be able to match the selected item with something in your model. var $input = $('.typeahead'); $input.typeahead({source:[{id: "someId1", name: "Display name 1"}, @@ -154,6 +154,12 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap function highlights all default matches Method used to highlight autocomplete results. Accepts a single argument item and has the scope of the typeahead instance. Should return html. + + + getDisplayText + function + gets the display text of an item + Method used to get textual representation of an item of the sources. Accepts a single argument item and has the scope of the typeahead instance. Should return a String, returns item or item.name by default. autoSelect diff --git a/bootstrap3-typeahead.js b/bootstrap3-typeahead.js index 5702eaf..58e5be2 100644 --- a/bootstrap3-typeahead.js +++ b/bootstrap3-typeahead.js @@ -56,6 +56,7 @@ this.highlighter = this.options.highlighter || this.highlighter; this.render = this.options.render || this.render; this.updater = this.options.updater || this.updater; + this.getDisplayText = this.options.getDisplayText || this.getDisplayText; this.source = this.options.source; this.delay = this.options.delay; this.$menu = $(this.options.menu); @@ -167,7 +168,7 @@ } , matcher: function (item) { - var it = item.name ? item.name : item; + var it = this.getDisplayText(item); return ~it.toLowerCase().indexOf(this.query.toLowerCase()); } @@ -178,7 +179,7 @@ , item; while ((item = items.shift())) { - var it = item.name ? item.name : item; + var it = this.getDisplayText(item); if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); else if (~it.indexOf(this.query)) caseSensitive.push(item); else caseInsensitive.push(item); @@ -215,7 +216,7 @@ var self = this; var activeFound = false; items = $(items).map(function (i, item) { - var text = item.name ? item.name : item; + var text = self.getDisplayText(item); i = $(that.options.item).data('value', item); i.find('a').html(that.highlighter(text)); if (text == self.$element.val()) { @@ -234,6 +235,10 @@ return this; } + , getDisplayText: function(item) { + return item.name || item; + } + , next: function (event) { var active = this.$menu.find('.active').removeClass('active') , next = active.next(); From 7125b22ade4736753a71d4ca855e8aee8a8c83b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Gauthier?= Date: Mon, 24 Nov 2014 14:06:30 -0500 Subject: [PATCH 5/5] Used displayText instead of getDisplayText, which is more aligned with the other methods names. --- README.md | 8 ++++---- bootstrap3-typeahead.js | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 50c8475..1454043 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Loading a collection Using JSON objects instead of simple strings -------------------------------------------- -You can add all the properties you wish on your objects, as long as you provide a "name" attribute OR you provide your own getDisplayText method. The other values are for you, to be able to match the selected item with something in your model. +You can add all the properties you wish on your objects, as long as you provide a "name" attribute OR you provide your own displayText method. The other values are for you, to be able to match the selected item with something in your model. var $input = $('.typeahead'); $input.typeahead({source:[{id: "someId1", name: "Display name 1"}, @@ -156,10 +156,10 @@ Options can be passed via data attributes or JavaScript. For data attributes, ap Method used to highlight autocomplete results. Accepts a single argument item and has the scope of the typeahead instance. Should return html. - getDisplayText + displayText function - gets the display text of an item - Method used to get textual representation of an item of the sources. Accepts a single argument item and has the scope of the typeahead instance. Should return a String, returns item or item.name by default. + item.name || item + Method used to get textual representation of an item of the sources. Accepts a single argument item and has the scope of the typeahead instance. Should return a String. autoSelect diff --git a/bootstrap3-typeahead.js b/bootstrap3-typeahead.js index facf58b..ed8946a 100644 --- a/bootstrap3-typeahead.js +++ b/bootstrap3-typeahead.js @@ -56,7 +56,7 @@ this.highlighter = this.options.highlighter || this.highlighter; this.render = this.options.render || this.render; this.updater = this.options.updater || this.updater; - this.getDisplayText = this.options.getDisplayText || this.getDisplayText; + this.displayText = this.options.displayText || this.displayText; this.source = this.options.source; this.delay = this.options.delay; this.$menu = $(this.options.menu); @@ -168,7 +168,7 @@ } , matcher: function (item) { - var it = this.getDisplayText(item); + var it = this.displayText(item); return ~it.toLowerCase().indexOf(this.query.toLowerCase()); } @@ -179,7 +179,7 @@ , item; while ((item = items.shift())) { - var it = this.getDisplayText(item); + var it = this.displayText(item); if (!it.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item); else if (~it.indexOf(this.query)) caseSensitive.push(item); else caseInsensitive.push(item); @@ -216,7 +216,7 @@ var self = this; var activeFound = false; items = $(items).map(function (i, item) { - var text = self.getDisplayText(item); + var text = self.displayText(item); i = $(that.options.item).data('value', item); i.find('a').html(that.highlighter(text)); if (text == self.$element.val()) { @@ -235,7 +235,7 @@ return this; } - , getDisplayText: function(item) { + , displayText: function(item) { return item.name || item; }