diff --git a/awesomplete.js b/awesomplete.js index 950a5ace..545086ed 100644 --- a/awesomplete.js +++ b/awesomplete.js @@ -22,6 +22,7 @@ var _ = function (input, o) { minChars: 2, maxItems: 10, autoFirst: false, + data: _.DATA, filter: _.FILTER_CONTAINS, sort: _.SORT_BYLENGTH, item: _.ITEM, @@ -117,9 +118,18 @@ _.prototype = { list = $(list); if (list && list.children) { - this._list = slice.apply(list.children).map(function (el) { - return el.textContent.trim(); + var items = []; + slice.apply(list.children).forEach(function (el) { + if (!el.disabled) { + var text = el.textContent.trim(); + var value = el.value || text; + var label = el.label || text; + if (value !== "") { + items.push({ label: label, value: value }); + } + } }); + this._list = items; } } @@ -184,17 +194,21 @@ _.prototype = { }, select: function (selected, origin) { - selected = selected || this.ul.children[this.index]; + if (selected) { + this.index = $.siblingIndex(selected); + } else { + selected = this.ul.children[this.index]; + } if (selected) { var allowed = $.fire(this.input, "awesomplete-select", { - text: selected.textContent, - data: this.suggestions[$.siblingIndex(selected)], + text: this.suggestions[this.index], + data: this.suggestions[this.index], origin: origin || selected }); if (allowed) { - this.replace(selected.textContent); + this.replace(this.suggestions[this.index]); this.close(); $.fire(this.input, "awesomplete-selectcomplete"); } @@ -211,6 +225,9 @@ _.prototype = { this.ul.innerHTML = ""; this.suggestions = this._list + .map(function(item) { + return new Suggestion(me.data(item, value)); + }) .filter(function(item) { return me.filter(item, value); }) @@ -262,11 +279,28 @@ _.ITEM = function (text, input) { }; _.REPLACE = function (text) { - this.input.value = text; + this.input.value = text.value; }; +_.DATA = function (item/*, input*/) { return item; }; + // Private functions +function Suggestion(data) { + var o = Array.isArray(data) + ? { label: data[0], value: data[1] } + : typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data }; + + this.label = o.label || o.value; + this.value = o.value; +} +Object.defineProperty(Suggestion.prototype = Object.create(String.prototype), "length", { + get: function() { return this.label.length; } +}); +Suggestion.prototype.toString = Suggestion.prototype.valueOf = function () { + return "" + this.label; +}; + function configure(instance, properties, o) { for (var i in properties) { var initial = properties[i], diff --git a/index.html b/index.html index b9ff3412..0dc39172 100644 --- a/index.html +++ b/index.html @@ -125,6 +125,29 @@

Basic usage

awesomplete.list = ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE", "Node.js", "Ruby on Rails"]; +

Suggestions with different label and value are supported too. The label will be shown in autocompleter and the value will be inserted into the input.

+ +
<input id="myinput" />
+
var input = document.getElementById("myinput");
+
+// Show label but insert value into the input:
+new Awesomplete(input, {
+	list: [
+		{ label: "Belarus", value: "BY" },
+		{ label: "China", value: "CN" },
+		{ label: "United States", value: "US" }
+	]
+});
+
+// Same with arrays:
+new Awesomplete(input, {
+	list: [
+		[ "Belarus", "BY" ],
+		[ "China", "CN" ],
+		[ "United States", "US" ]
+	]
+});
+
@@ -233,6 +256,27 @@

Extend

this.input.value = text; } + + data + Controls suggestions' label and value. This is useful if you have list items in custom format, or want to change list items based on user's input. + Function that takes two parameters, the first one being the original list item and the second a string with the user’s input and returns a list item in one of supported by default formats: + +To use objects without label or value properties, e.g. name and id instead, you can do this: +
data: function (item, input) {
+	return { label: item.name, value: item.id };
+}
+You can use any object for label and value and it will be converted to String where necessary: +
list: [ new Date("2015-01-01"), ... ] 
+Original list items as Date objects will be accessible in filter, sort, item and replace functions, but by default we'll just see Date objects converted to strings in autocompleter and the same value will be inserted to the input. +
+We can also generate list items based on user's input. See E-mail autocomplete example in Advanced Examples section. + + Awesomplete.DATA: Identity function which just returns the original list item. +
@@ -335,17 +379,10 @@

E-mail autocomplete

<input type="email" />
diff --git a/test/api/selectSpec.js b/test/api/selectSpec.js index 84895b16..9a1214e0 100644 --- a/test/api/selectSpec.js +++ b/test/api/selectSpec.js @@ -46,8 +46,8 @@ describe("awesomplete.select", function () { expect(handler).toHaveBeenCalledWith( jasmine.objectContaining({ - text: expectedTxt, - data: expectedTxt, + text: jasmine.objectContaining({ label: expectedTxt, value: expectedTxt }), + data: jasmine.objectContaining({ label: expectedTxt, value: expectedTxt }), origin: this.selectArgument || this.subject.ul.children[0] }) ); diff --git a/test/init/listSpec.js b/test/init/listSpec.js index 6f95ff05..eff78d61 100644 --- a/test/init/listSpec.js +++ b/test/init/listSpec.js @@ -23,12 +23,20 @@ describe("Awesomplete list", function () { it("assigns from element specified by selector", function () { this.subject.list = "#data-list"; - expect(this.subject._list).toEqual([ "With", "Data", "List" ]); + expect(this.subject._list).toEqual([ + { label: "With", value: "With" }, + { label: "Data", value: "Data" }, + { label: "List", value: "List" } + ]); }); it("assigns from element", function () { this.subject.list = $("#data-list"); - expect(this.subject._list).toEqual([ "With", "Data", "List" ]); + expect(this.subject._list).toEqual([ + { label: "With", value: "With" }, + { label: "Data", value: "Data" }, + { label: "List", value: "List" } + ]); }); it("does not assigns from not found list", function () { @@ -68,12 +76,20 @@ describe("Awesomplete list", function () { it("assigns from element specified by selector", function () { this.options = { list: "#data-list" }; - expect(this.subject._list).toEqual([ "With", "Data", "List" ]); + expect(this.subject._list).toEqual([ + { label: "With", value: "With" }, + { label: "Data", value: "Data" }, + { label: "List", value: "List" } + ]); }); it("assigns from list specified by element", function () { this.options = { list: $("#data-list") }; - expect(this.subject._list).toEqual([ "With", "Data", "List" ]); + expect(this.subject._list).toEqual([ + { label: "With", value: "With" }, + { label: "Data", value: "Data" }, + { label: "List", value: "List" } + ]); }); }); @@ -85,14 +101,21 @@ describe("Awesomplete list", function () { it("assigns from element referenced by selector", function () { this.element = "#with-data-list"; - expect(this.subject._list).toEqual(["With", "Data", "List"]); + expect(this.subject._list).toEqual([ + { label: "With", value: "With" }, + { label: "Data", value: "Data" }, + { label: "List", value: "List" } + ]); }); }); describe("list html attribute", function () { it("assigns from element referenced by id", function () { this.element = "#with-list"; - expect(this.subject._list).toEqual(["With", "List"]); + expect(this.subject._list).toEqual([ + { label: "With", value: "With" }, + { label: "List", value: "List" } + ]); }); }); }); diff --git a/test/init/optionsSpec.js b/test/init/optionsSpec.js index 40153c1d..fc60997c 100644 --- a/test/init/optionsSpec.js +++ b/test/init/optionsSpec.js @@ -19,6 +19,10 @@ describe("Constructor options", function () { expect(this.subject.autoFirst).toBe(false); }); + it("modifies list item with DATA", function () { + expect(this.subject.data).toBe(Awesomplete.DATA); + }); + it("filters with FILTER_CONTAINS", function () { expect(this.subject.filter).toBe(Awesomplete.FILTER_CONTAINS); }); diff --git a/test/static/dataSpec.js b/test/static/dataSpec.js new file mode 100644 index 00000000..311c6eb0 --- /dev/null +++ b/test/static/dataSpec.js @@ -0,0 +1,19 @@ +describe("Awesomplete.DATA", function () { + + subject(function () { return Awesomplete.DATA(this.item) }); + + it("returns original String", function () { + this.item = "JavaScript"; + expect(this.subject).toEqual("JavaScript"); + }); + + it("returns original Object", function () { + this.item = { label: "JavaScript", value: "JS" }; + expect(this.subject).toEqual({ label: "JavaScript", value: "JS" }); + }); + + it("returns original Array", function () { + this.item = [ "JavaScript", "JS" ]; + expect(this.subject).toEqual([ "JavaScript", "JS" ]); + }); +}); diff --git a/test/static/replaceSpec.js b/test/static/replaceSpec.js index 4339ff21..e2eddbae 100644 --- a/test/static/replaceSpec.js +++ b/test/static/replaceSpec.js @@ -5,7 +5,7 @@ describe("Awesomplete.REPLACE", function () { def("instance", function() { return { input: { value: "initial" } } }); it("replaces input value", function () { - this.subject.call(this.instance, "JavaScript"); + this.subject.call(this.instance, { value: "JavaScript" }); expect(this.instance.input.value).toBe("JavaScript"); }); });