I think I came up with something cooler and more relevant to what I actually needed to do - it matches at the beginning of the value
string or anywhere in the product_desc
string.
The forked jsFiddle is highly commented so those wanting to just do a lookup on two values (where the match is at the beginning of either string), can comment out some lines and achieve that effect.
I also implemented a number of other features:
- Match highlighting
- Display number of matches
- Scrollable results area
- Populate page elements with multiple values from an object
- Templated results
- Multiple instances on one page
These are also heavily commented so they can be removed or modified if required.
I would still appreciate confirmation that this is a relatively clean solution that does not clutter up the dom with two many divs (autocomplete seems to add lots of unused divs to the dom?)
JS
// BEGIN show message prompt requiring 2 characters
$(".my_lookup").on("keyup", function() {
var product_container = $(this).parent();
var $matches_count = product_container.find(".matches_count");
var $matches_text = product_container.find(".matches_text");
var input_length = $(this).val().length;
if (input_length < 2 && input_length !== 0) {
$(this).siblings(".matches_prompt").text("search requires min 2 characters").show();
$matches_count.text("").hide();
$matches_text.text("").hide();
} else if (input_length === 0 || input_length >= 2) {
$(this).siblings(".matches_prompt").hide();
}
});
// END show message prompt requiring 2 characters
// BEGIN look up on two values solution
// see: https://stackoverflow.com/a/15849692
function lightwell(request, response) {
// request.term is what you typed
// response is the data to suggest to the user
// added the 'match_type' argument to hasMatch() to:
// - check for match at beginning of object's 'value' string
// - check for match anywhere in object's 'product_desc' string
// BEGIN hasMatch() function
function hasMatch(s, match_type) {
// to match beginning of string only,
// use: === 0
// see: https://stackoverflow.com/a/12482182
// originally was !==-1
// to match anywhere in the string,
// use !==-1
// when match_type === "start",
// below returns true if request.term is at the 0 index
// of the object.value or object.product_desc string
// when match_type === "anywhere",
// returns true if at any index other than no index
// original usage (anywhere) below
// (remove 'match_type' argument and references to it):
// return s.toLowerCase().indexOf(request.term.toLowerCase()) !== -1;
// added conditional handling
// BEGIN if match_type === "start"
if (match_type === "start") {
// check if typed in term is at the 0 index of object's 'value' string
var is_at_start_of_string = s.toLowerCase().indexOf(request.term.toLowerCase()) === 0;
if (is_at_start_of_string === true) {
console.log("typed term is at start of value string");
}
return is_at_start_of_string;
}
// END if match_type === "start"
// BEGIN if match_type === "anywhere"
else if (match_type === "anywhere") {
// check if typed in term is at any index of object's 'product_desc' string
var exists_in_string = s.toLowerCase().indexOf(request.term.toLowerCase()) !== -1;
if (exists_in_string === true) {
console.log("typed term exists in product_desc string");
}
return exists_in_string;
}
// END if match_type === "anywhere"
}
// END hasMatch() function
// declare variables, i and obj are undefined, matches is []
var i, obj, matches = [];
// BEGIN get the index of the input being used
// see: https://mcmap.net/q/86623/-how-to-get-the-focused-element-with-jquery
var current_input_index = $(document.activeElement).parent().index() - 1;
// END get the index of the input being used
// BEGIN get refererences to dproduct relative dom elements
var $product_container = $(".product_container").eq(current_input_index);
var $matches_count = $product_container.find(".matches_count");
var $matches_text = $product_container.find(".matches_text");
// END get refererences to dproduct relative dom elements
// BEGIN if the typed in term is nothing
// pass an empty array to response()
if (request.term === "") {
console.log("this thing is happening");
// hide the matches count display
$matches_count.text("").hide();
$matches_text.text("").hide();
response([]);
return;
}
// END if the typed in term is nothing
// get length of array_of_objects
var array_of_objects_length = array_of_objects.length;
// for each object in the array, call the hasMatch() function
// and pass it the object's 'value' and 'product_desc' strings
for (i = 0; i < array_of_objects_length; i++) {
obj = array_of_objects[i];
// if either of the below conditions return true,
// push the object to the matches array
if (hasMatch(obj.value, "start") || hasMatch(obj.product_desc, "anywhere")) {
matches.push(obj);
}
}
// pass the matches array to response()
// get the count of matches for display
var matches_count = matches.length;
console.log("matches length is: " + matches_count)
if (matches_count === 0 || matches_count > 1) {
var matches_text = " matches"
} else if (matches_count === 1) {
var matches_text = " match"
}
// display the count of matches
$matches_count.text(matches_count).show();
$matches_text.text(matches_text).show();
response(matches);
}
// END look up on two values solution
// BEGIN autocomplete
$(".my_lookup").autocomplete({
// only show 5 results with scrollbar
// from: http://anseki.github.io/jquery-ui-autocomplete-scroll
maxShowItems: 5,
source: lightwell,
minLength: 2,
// called on input blur if value has changed
change: function(event, ui) {
var product_container = $(this).closest(".product_container");
var $matches_count = product_container.find(".matches_count");
var $matches_text = product_container.find(".matches_text");
$matches_count.text("").hide();
$matches_text.text("").hide();
},
// called when selecting an option
select: function(event, ui) {
// get references to product relative selectors
var product_container = $(this).closest(".product_container");
var product_desc = product_container.find(".product_desc");
var product_type = product_container.find(".product_type");
var product_attr_01 = product_container.find(".product_attr_01");
var product_attr_02 = product_container.find(".product_attr_02");
var $matches_count = product_container.find(".matches_count");
var $matches_text = product_container.find(".matches_text");
$matches_count.text("").hide();
$matches_text.text("").hide();
// add object's values to relative product container inputs
product_desc.val(ui.item.product_desc);
product_type.val(ui.item.product_type);
product_attr_01.val(ui.item.product_attr_01);
product_attr_02.val(ui.item.product_attr_02);
// BEGIN animate realtive inputs when selecting an option
product_desc.animate({
"backgroundColor": "#d0e4ff"
}, {
"queue": false,
"duration": 800
});
product_desc.animate({
"borderColor": "#7190dd"
}, {
"queue": false,
"duration": 800
});
product_type.animate({
"backgroundColor": "#d0e4ff"
}, {
"queue": false,
"duration": 800
});
product_type.animate({
"borderColor": "#7190dd"
}, {
"queue": false,
"duration": 800
});
product_attr_01.animate({
"backgroundColor": "#d0e4ff"
}, {
"queue": false,
"duration": 800
});
product_attr_01.animate({
"borderColor": "#7190dd"
}, {
"queue": false,
"duration": 800
});
product_attr_02.animate({
"backgroundColor": "#d0e4ff"
}, {
"queue": false,
"duration": 800
});
product_attr_02.animate({
"borderColor": "#7190dd"
}, {
"queue": false,
"duration": 800
});
setTimeout(function() {
product_desc.animate({
"backgroundColor": "#ffff"
}, {
"queue": false,
"duration": 800
});
product_desc.animate({
"borderColor": "#cacaca"
}, {
"queue": false,
"duration": 800
});
product_type.animate({
"backgroundColor": "#ffff"
}, {
"queue": false,
"duration": 800
});
product_type.animate({
"borderColor": "#cacaca"
}, {
"queue": false,
"duration": 800
});
product_attr_01.animate({
"backgroundColor": "#ffff"
}, {
"queue": false,
"duration": 800
});
product_attr_01.animate({
"borderColor": "#cacaca"
}, {
"queue": false,
"duration": 800
});
product_attr_02.animate({
"backgroundColor": "#ffff"
}, {
"queue": false,
"duration": 800
});
product_attr_02.animate({
"borderColor": "#cacaca"
}, {
"queue": false,
"duration": 800
});
}, 2000);
// END animate realtive inputs when selecting an option
},
// show fontawesome loading animation when search starts
search: function(event, ui) {
console.log("search started");
$(this).closest(".product_container").find(".fa-circle-o-notch").css("visibility", "visible");
},
// hide fontawesome loading animation when search finishes
response: function(event, ui) {
console.log("search finished");
$(this).closest(".product_container").find(".fa-circle-o-notch").css("visibility", "hidden");
},
// BEGIN add styles to results
// from: https://gist.github.com/DBasic/9545690
create: function() {
$(this).data('ui-autocomplete')._renderItem = function(ul, item) {
// create a new 'value' string for match highlighting
// see: https://mcmap.net/q/515384/-how-to-highlight-input-words-in-autocomplete-jquery-ui
var newValueText = String(item.value).replace(
// changed "gi" to i so that the match
// on instance of search term is limited
// to the first match, see:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
new RegExp(this.term, "i"),
"<span class='ui-state-highlight'>$&</span>");
// create a new 'description' string for match highlighting
var newDescText = String(item.product_desc.toUpperCase()).replace(
new RegExp(this.term, "gi"),
"<span class='ui-state-highlight'>$&</span>");
return $("<li></li>")
.data("ui-autocomplete-item", item)
// newValueText below used to be: item.value.toUpperCase()
// newDescText below used to be: item.product_desc.toUpperCase()
.append("<div class=\"my_result\"><span class=\"helper\"></span><img class=\"result_img\" src=\"https://placeimg.com/30/30/nature\">" + newValueText + " - " + newDescText + "</div>")
.appendTo(ul);
}
}
// END add styles to results
});
// END autocomplete
// new data set generated from https://github.com/Marak/faker.js
var array_of_objects = [{
"value": "020101",
"product_desc": "Handmade Frozen Pants",
"product_type": "Automotive",
"product_attr_01": "106.00",
"product_attr_02": "171.00"
}, {
"value": "010101",
"product_desc": "Practical Fresh Hat",
"product_type": "Movies",
"product_attr_01": "589.00",
"product_attr_02": "777.00"
}, {
"value": "7099",
"product_desc": "Awesome Concrete Sausages",
"product_type": "Grocery",
"product_attr_01": "249.00",
"product_attr_02": "167.00"
}, {
"value": "5740",
"product_desc": "Sleek Plastic Pizza",
"product_type": "Home",
"product_attr_01": "924.00",
"product_attr_02": "379.00"
}, {
"value": "7627",
"product_desc": "Intelligent Plastic Mouse",
"product_type": "Games",
"product_attr_01": "71.00",
"product_attr_02": "412.00"
}, {
"value": "6606",
"product_desc": "Handmade Steel Pizza",
"product_type": "Music",
"product_attr_01": "71.00",
"product_attr_02": "791.00"
}, {
"value": "0973",
"product_desc": "Intelligent Granite Tuna",
"product_type": "Industrial",
"product_attr_01": "25.00",
"product_attr_02": "441.00"
}, {
"value": "5453",
"product_desc": "Generic Steel Sausages",
"product_type": "Sports",
"product_attr_01": "887.00",
"product_attr_02": "786.00"
}, {
"value": "3871",
"product_desc": "Refined Wooden Keyboard",
"product_type": "Sports",
"product_attr_01": "897.00",
"product_attr_02": "402.00"
}, {
"value": "9646",
"product_desc": "Incredible Soft Chicken",
"product_type": "Kids",
"product_attr_01": "849.00",
"product_attr_02": "438.00"
}, {
"value": "2948",
"product_desc": "Licensed Plastic Gloves",
"product_type": "Baby",
"product_attr_01": "608.00",
"product_attr_02": "748.00"
}, {
"value": "1561",
"product_desc": "Sleek Steel Towels",
"product_type": "Music",
"product_attr_01": "315.00",
"product_attr_02": "332.00"
}, {
"value": "5012",
"product_desc": "Licensed Rubber Computer",
"product_type": "Electronics",
"product_attr_01": "886.00",
"product_attr_02": "738.00"
}, {
"value": "4827",
"product_desc": "Unbranded Wooden Shoes",
"product_type": "Shoes",
"product_attr_01": "390.00",
"product_attr_02": "753.00"
}, {
"value": "0056",
"product_desc": "Handcrafted Fresh Sausages",
"product_type": "Shoes",
"product_attr_01": "26.00",
"product_attr_02": "257.00"
}, {
"value": "0628",
"product_desc": "Fantastic Steel Tuna",
"product_type": "Tools",
"product_attr_01": "881.00",
"product_attr_02": "127.00"
}, {
"value": "8498",
"product_desc": "Gorgeous Soft Fish",
"product_type": "Toys",
"product_attr_01": "105.00",
"product_attr_02": "604.00"
}, {
"value": "9265",
"product_desc": "Gorgeous Wooden Cheese",
"product_type": "Clothing",
"product_attr_01": "257.00",
"product_attr_02": "438.00"
}, {
"value": "0666",
"product_desc": "Small Soft Keyboard",
"product_type": "Baby",
"product_attr_01": "960.00",
"product_attr_02": "852.00"
}, {
"value": "4628",
"product_desc": "Intelligent Plastic Car",
"product_type": "Music",
"product_attr_01": "598.00",
"product_attr_02": "339.00"
}, {
"value": "3341",
"product_desc": "Intelligent Metal Mouse",
"product_type": "Garden",
"product_attr_01": "92.00",
"product_attr_02": "371.00"
}, {
"value": "6547",
"product_desc": "Awesome Concrete Shirt",
"product_type": "Health",
"product_attr_01": "344.00",
"product_attr_02": "145.00"
}, {
"value": "0803",
"product_desc": "Unbranded Metal Chair",
"product_type": "Kids",
"product_attr_01": "343.00",
"product_attr_02": "700.00"
}, {
"value": "9769",
"product_desc": "Awesome Granite Bike",
"product_type": "Home",
"product_attr_01": "545.00",
"product_attr_02": "391.00"
}, {
"value": "3087",
"product_desc": "Refined Wooden Tuna",
"product_type": "Industrial",
"product_attr_01": "58.00",
"product_attr_02": "68.00"
}, {
"value": "3202",
"product_desc": "Small Concrete Gloves",
"product_type": "Kids",
"product_attr_01": "846.00",
"product_attr_02": "60.00"
}, {
"value": "9638",
"product_desc": "Generic Rubber Ball",
"product_type": "Garden",
"product_attr_01": "160.00",
"product_attr_02": "123.00"
}, {
"value": "4762",
"product_desc": "Tasty Frozen Computer",
"product_type": "Health",
"product_attr_01": "698.00",
"product_attr_02": "832.00"
}, {
"value": "6606",
"product_desc": "Rustic Frozen Shirt",
"product_type": "Automotive",
"product_attr_01": "867.00",
"product_attr_02": "92.00"
}, {
"value": "6853",
"product_desc": "Ergonomic Steel Pants",
"product_type": "Sports",
"product_attr_01": "712.00",
"product_attr_02": "378.00"
}, {
"value": "5418",
"product_desc": "Awesome Cotton Cheese",
"product_type": "Toys",
"product_attr_01": "483.00",
"product_attr_02": "918.00"
}, {
"value": "0230",
"product_desc": "Licensed Cotton Towels",
"product_type": "Clothing",
"product_attr_01": "540.00",
"product_attr_02": "415.00"
}, {
"value": "3975",
"product_desc": "Sleek Granite Pants",
"product_type": "Outdoors",
"product_attr_01": "823.00",
"product_attr_02": "331.00"
}, {
"value": "7581",
"product_desc": "Ergonomic Concrete Bacon",
"product_type": "Automotive",
"product_attr_01": "640.00",
"product_attr_02": "718.00"
}, {
"value": "8550",
"product_desc": "Practical Granite Table",
"product_type": "Shoes",
"product_attr_01": "94.00",
"product_attr_02": "487.00"
}, {
"value": "3358",
"product_desc": "Fantastic Plastic Computer",
"product_type": "Clothing",
"product_attr_01": "448.00",
"product_attr_02": "440.00"
}, {
"value": "4586",
"product_desc": "Ergonomic Steel Table",
"product_type": "Games",
"product_attr_01": "218.00",
"product_attr_02": "806.00"
}, {
"value": "6331",
"product_desc": "Intelligent Wooden Gloves",
"product_type": "Shoes",
"product_attr_01": "236.00",
"product_attr_02": "546.00"
}, {
"value": "2871",
"product_desc": "Handcrafted Wooden Salad",
"product_type": "Beauty",
"product_attr_01": "546.00",
"product_attr_02": "259.00"
}, {
"value": "1648",
"product_desc": "Tasty Soft Pants",
"product_type": "Kids",
"product_attr_01": "641.00",
"product_attr_02": "251.00"
}, {
"value": "8096",
"product_desc": "Practical Steel Chair",
"product_type": "Toys",
"product_attr_01": "609.00",
"product_attr_02": "374.00"
}, {
"value": "5810",
"product_desc": "Refined Steel Chicken",
"product_type": "Kids",
"product_attr_01": "529.00",
"product_attr_02": "705.00"
}, {
"value": "7057",
"product_desc": "Tasty Metal Mouse",
"product_type": "Garden",
"product_attr_01": "911.00",
"product_attr_02": "935.00"
}, {
"value": "2344",
"product_desc": "Intelligent Cotton Pizza",
"product_type": "Sports",
"product_attr_01": "705.00",
"product_attr_02": "220.00"
}, {
"value": "9188",
"product_desc": "Awesome Wooden Ball",
"product_type": "Movies",
"product_attr_01": "896.00",
"product_attr_02": "850.00"
}, {
"value": "1474",
"product_desc": "Sleek Plastic Salad",
"product_type": "Tools",
"product_attr_01": "15.00",
"product_attr_02": "668.00"
}, {
"value": "6513",
"product_desc": "Small Soft Chips",
"product_type": "Health",
"product_attr_01": "433.00",
"product_attr_02": "74.00"
}, {
"value": "4036",
"product_desc": "Unbranded Wooden Soap",
"product_type": "Grocery",
"product_attr_01": "826.00",
"product_attr_02": "920.00"
}, {
"value": "9226",
"product_desc": "Licensed Fresh Cheese",
"product_type": "Industrial",
"product_attr_01": "144.00",
"product_attr_02": "102.00"
}, {
"value": "1944",
"product_desc": "Fantastic Rubber Shoes",
"product_type": "Electronics",
"product_attr_01": "820.00",
"product_attr_02": "808.00"
}, {
"value": "9379",
"product_desc": "Small Wooden Tuna",
"product_type": "Baby",
"product_attr_01": "199.00",
"product_attr_02": "160.00"
}, {
"value": "7888",
"product_desc": "Handcrafted Frozen Sausages",
"product_type": "Electronics",
"product_attr_01": "70.00",
"product_attr_02": "419.00"
}, {
"value": "1941",
"product_desc": "Fantastic Granite Car",
"product_type": "Grocery",
"product_attr_01": "821.00",
"product_attr_02": "853.00"
}, {
"value": "8322",
"product_desc": "Licensed Wooden Fish",
"product_type": "Games",
"product_attr_01": "998.00",
"product_attr_02": "703.00"
}, {
"value": "5586",
"product_desc": "Fantastic Cotton Salad",
"product_type": "Games",
"product_attr_01": "887.00",
"product_attr_02": "841.00"
}];
/* code to generate the above data set,
from: https://github.com/Marak/faker.js
var array_of_objects = [];
for (i = 0; i < 5; i++) {
var obj = {};
obj['value'] = faker.fake("{{finance.mask}}");
obj['product_desc'] = faker.fake("{{commerce.productName}}");
obj['product_type'] = faker.fake("{{commerce.department}}");
obj['product_attr_01'] = faker.fake("{{commerce.price}}");
obj['product_attr_02'] = faker.fake("{{commerce.price}}");
array_of_objects.push(obj);
}
*/