Matcher

from a set of items with string values, given a query string, mark the matching items.

by Laurens Maneschijn

HTML

<hr>

From a set of items with string values, given a query string, mark the matching items.

<hr>

<input id="query" type="search" placeholder="type query string here..."><br>

<details open><summary>Options...</summary>
<div>
<label>
	<input type="checkbox" data-optionkey="literal_match">
	basic literal match
	<small>(ignores other options, but still allows case insensitive)</small>
</label><br>
<label>
	<input type="checkbox" data-optionkey="case_sensitive">
	case sensitive
</label><br>
<label>
	<input type="checkbox" data-optionkey="or_enabled" checked>
	OR enabled <small>("a,b" matches "a" or "b")</small>
</label>
--
<label>
	separator:
	<input type="text" data-optionkey="or_separator" value=","
		style="width: 1em;">
</label><br>
<label>
	<input type="checkbox" data-optionkey="and_enabled" checked>
	AND enabled <small>("a b" matches "a ... b")</small>
</label>
--
<label>
	separator:
	<input type="text" data-optionkey="and_separator" value=" "
		style="width: 1em;">
</label><br>
<label>
	<input type="checkbox" data-optionkey="order_matters" checked>
	AND order matters <small>("a b" matches "a b" but not "b a")</small>
</label><br>
</div>
</details>

<hr>

example data, match by row:<br>
<table>
<tbody>
<tr><td>aa</td><td>bb</td><td>cc</td></tr>
<tr><td>dd</td><td>ee</td><td>ff</td></tr>
<tr><td>ab</td><td>bc</td><td>cd</td></tr>
<tr><td>aa</td><td>ee</td><td>zz</td></tr>
<tr><td>a b</td><td>c d</td><td>e f</td></tr>
<tr><td>xx</td><td>yy</td><td>zz</td></tr>
</tbody>
</table>

<hr>

features:

<ul>
	<li>Optionally case sensitive.</li>
	<li>OR support: Can match multiple queries at once by separating with comma.<br>
		e.g. "A , B" will match any item with either "A" or "B" in it.</li>
	<li>AND support: Any whitespace is a wildcard.<br>
		e.g. "A*B*C" will match "A*B*C" ("ABC","AxxBC","xxAxxBxxCxx", ...)</li>
	<li>query AND order optionally matters.<br>
		e.g. "A B C" could match "Aaa Bbb Ccc" but not "Bbb Aaa Ccc"</li>
	<li>Can work on just a list of...

CSS

table {
	border-collapse: collapsed;
}
table td {
	padding: 2px;
	border: 1px solid #888;
}
table tr.match {
	padding: 2px;
	border: 1px solid #888;
}
table tr.match td {
	background: #00f2;
}
details > summary {
	cursor: pointer;
}

details > div {
	display: inline-block;
	margin: 2px;
	padding: 2px;
	border: 1px solid #8884;
}

JavaScript

// https://jsfiddle.net/ElMoonLite/6zgnu27e/

class Matcher {
	items = [];
	options = {};
	options_default = {
		// literal_match : disable features like whitespace wildcards,
		// but still supports or_enabled and case_sensitive flags.
		// Could also be quicker for very large data sets.
		literal_match: false,

		case_sensitive: false,

		or_enabled: true,
		or_separator: ',', // either a string or regexp

		and_enabled: true,
		and_separator: ' ', // either a string or regexp

		parseItemValueFunction: null, // null, or a function returning null for default handling.

		order_matters: true,
	};

	constructor() {
//		super();
		this.items = [];
		this.resetOptions();
	}

	setItems(arr) {
		this.items = [];
		if (!arr) {
			// this allows rebuilding items (e.g. updating values with new options)
			arr = this.getItems();
		}
		arr.forEach((item) => {
			let o = {
				_ : item, // original value, could also be an object
				value: '',
			};

			if (typeof item === 'string') {
				o.value = item;
			} else if (typeof item === 'object') {
				if (typeof this.options.parseItemValueFunction === 'function') {
					o.value = '' + this.options.parseItemValueFunction(item);
				}
				if (item.tagName === 'TR') {
					// assume item is a HTMLTableRowElement (i.e. <tr>)
					// add some "salt" between tablecells,
					// prevent matching from one cell into another,
					// e.g. "ab" matching on "<td>a</td><td>b</td>"
					o.value = Array.from(item.childNodes).map((td) => {
						return td.textContent;
					}).join('|');
				}
				if (!o.value && item.value && typeof item.value === 'string') {
					o.value = item.value;
				}
				if (!o.value && typeof item.textContent === 'string') {
					o.value = item.textContent;
				}
				if (!o.value && typeof item.toString === 'function') {
					o.value = item.toString();
				}
				if (!o.value && item.valueOf) {
					o.value = ('' + item.valueOf());
				}
			}
			o.value_lowercase =...