Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate label/value for each suggestion on the list. #16852

Closed
48 changes: 41 additions & 7 deletions awesomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var _ = function (input, o) {
minChars: 2,
maxItems: 10,
autoFirst: false,
data: _.DATA,
filter: _.FILTER_CONTAINS,
sort: _.SORT_BYLENGTH,
item: _.ITEM,
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -184,17 +194,21 @@ _.prototype = {
},

select: function (selected, originalTarget) {
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],
originalTarget: originalTarget || selected
});

if (allowed) {
this.replace(selected.textContent);
this.replace(this.suggestions[this.index]);
this.close();
$.fire(this.input, "awesomplete-selectcomplete");
}
Expand All @@ -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);
})
Expand Down Expand Up @@ -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],
Expand Down
57 changes: 47 additions & 10 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,29 @@ <h1>Basic usage</h1>

awesomplete.list = ["Ada", "Java", "JavaScript", "Brainfuck", "LOLCODE", "Node.js", "Ruby on Rails"];</code></pre>

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

<pre class="language-markup"><code>&lt;input id="myinput" /></code></pre>
<pre class="language-javascript"><code>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" ]
]
});</code></pre>

</section>

<section id="customization">
Expand Down Expand Up @@ -233,6 +256,27 @@ <h1>Extend</h1>
this.input.value = text;
}</code></pre></td>
</tr>
<tr>
<td><code>data</code></td>
<td>Controls suggestions' <code>label</code> and <code>value</code>. This is useful if you have list items in custom format, or want to change list items based on user's input.</td>
<td>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:
<ul>
<li><code>"JavaScript"</code></li>
<li><code>{ label: "JavaScript", value: "JS" }</code></li>
<li><code>[ "JavaScript", "JS" ]</code></li>
</ul>
To <strong>use objects without <code>label</code> or <code>value</code> properties</strong>, e.g. <code>name</code> and <code>id</code> instead, you can do this:
<pre class="language-javascript"><code>data: function (item, input) {
return { label: item.name, value: item.id };
}</code></pre>
You can <strong>use any object for <code>label</code> and <code>value</code></strong> and it will be converted to String where necessary:
<pre class="language-javascript"><code>list: [ new Date("2015-01-01"), ... ] </code></pre>
Original list items as Date objects will be accessible in <code>filter</code>, <code>sort</code>, <code>item</code> and <code>replace</code> 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.
<br />
We can also <strong>generate list items based on user's input</strong>. See E-mail autocomplete example in <a href="#advanced-examples">Advanced Examples</a> section.
</td>
<td><code class="language-javascript">Awesomplete.DATA</code>: Identity function which just returns the original list item.</td>
</tr>
</tbody>
</table>
</section>
Expand Down Expand Up @@ -335,17 +379,10 @@ <h2>E-mail autocomplete</h2>
<pre class="language-markup"><code>&lt;input type="email" /></code></pre>
<pre class="language-javascript"><code><script>new Awesomplete($('input[type="email"]'), {
list: ["@aol.com", "@att.net", "@comcast.net", "@facebook.com", "@gmail.com", "@gmx.com", "@googlemail.com", "@google.com", "@hotmail.com", "@hotmail.co.uk", "@mac.com", "@me.com", "@mail.com", "@msn.com", "@live.com", "@sbcglobal.net", "@verizon.net", "@yahoo.com", "@yahoo.co.uk"],
item: function(text, input) {
var newText = input.slice(0, input.indexOf("@")) + text;

return Awesomplete.$.create("li", {
innerHTML: newText.replace(RegExp(input.trim(), "gi"), "<mark>$&</mark>"),
"aria-selected": "false"
});
data: function (text, input) {
return input.slice(0, input.indexOf("@")) + text;
},
filter: function(text, input) {
return RegExp("^" + Awesomplete.$.regExpEscape(input.replace(/^.+?(?=@)/, ''), "i")).test(text);
}
filter: Awesomplete.FILTER_STARTSWITH
});</script></code></pre>
</section>

Expand Down
4 changes: 2 additions & 2 deletions test/api/selectSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
originalTarget: this.selectArgument || this.subject.ul.children[0]
})
);
Expand Down
35 changes: 29 additions & 6 deletions test/init/listSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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" }
]);
});
});

Expand All @@ -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" }
]);
});
});
});
4 changes: 4 additions & 0 deletions test/init/optionsSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down
19 changes: 19 additions & 0 deletions test/static/dataSpec.js
Original file line number Diff line number Diff line change
@@ -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" ]);
});
});
2 changes: 1 addition & 1 deletion test/static/replaceSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
});