jets
Version:
Native CSS search engine
182 lines (171 loc) • 7.65 kB
JavaScript
/*
Jets.js - v0.15.0 - 2022-12-11
http://NeXTs.github.com/Jets.js/
Copyright (c) 2015 Denis Lukov; Licensed MIT */
;(function(root, definition) {
if (typeof module != 'undefined') module.exports = definition();
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
else root['Jets'] = definition();
}(this, function() {
"use strict"
function Jets(opts) {
if( ! (this instanceof Jets)) {
return new Jets(opts);
}
var self = this;
var defaults = {
searchSelector: '*AND',
hideBy: 'display:none',
diacriticsMap: {}
}
self.options = {};
['columns', 'addImportant', 'searchSelector', 'hideBy', 'manualContentHandling', 'callSearchManually', 'searchInSpecificColumn', 'diacriticsMap', 'didSearch', 'invert', 'nonceId'].forEach(function(name) {
self.options[name] = opts[name] || defaults[name];
});
if(this.options.searchSelector.length > 1) {
var searchSelector = self.options['searchSelector'].trim();
self.options.searchSelector = searchSelector.substr(0, 1);
self.options.searchSelectorMode = searchSelector.substr(1).toUpperCase();
}
self.content_tag = document.querySelectorAll(opts.contentTag);
if( ! self.content_tag) throw new Error('Error! Could not find contentTag element');
self.content_param = opts.contentTag;
self.search_tag = document.querySelector(opts.searchTag);
if( ! self.search_tag && ! self.options.callSearchManually) throw new Error('Error! Provide one of search methods: searchTag or callSearchManually and call .search("phrase") manually');
var last_search_query = self.search_tag && self.search_tag.value || '';
self.search = function(search_query, optional_column) {
var new_search_query = self.options.callSearchManually && typeof search_query != 'undefined'
? search_query
: self.search_tag
? self.search_tag.value
: ''
if(last_search_query == (last_search_query = new_search_query)) return;
(0,self._applyCSS(last_search_query, optional_column));
self.options.didSearch && self.options.didSearch(last_search_query);
};
self._onSearch = function(event) {
if(event.type == 'keydown')
return setTimeout(self.search, 0);
self.search();
};
self.destroy = function() {
if( ! self.options.callSearchManually) self._processEventListeners('remove');
self._destroy();
};
if( ! self.options.callSearchManually) self._processEventListeners('add');
self._addStyleTag();
self._setJets();
self._applyCSS(last_search_query);
}
Jets.prototype = {
constructor: Jets,
_processEventListeners: function(action) {
['input', 'keydown', 'change'].forEach(function(event_type) {
this.search_tag[action + 'EventListener'](event_type, this._onSearch);
}.bind(this));
},
_applyCSS: function(search_query, optional_column) {
var options = this.options,
search_phrase = this.replaceDiacritics(search_query.trim().toLowerCase().replace(/\s\s+/g, ' ')).replace(/\\/g, '\\\\'),
words = options.searchSelectorMode
? search_phrase.split(' ').filter(function(item, pos, arr) { return arr.indexOf(item) == pos; })
: [search_phrase],
is_strict_selector = options.searchSelectorMode == 'AND',
selectors = new Array(words.length);
for(var i = 0, ii = words.length; i < ii; i++) {
selectors[i] = (is_strict_selector ? this.content_param + '>' : '') +
(options.invert ? '' : ':not(') +
'[data-jets' + (typeof optional_column != 'undefined' ? '-col-' + optional_column : '') + options.searchSelector + '="' + words[i] + '"]' +
(options.invert ? '' : ')');
}
var hide_rules = options.hideBy.split(';').filter(Boolean).map(function(rule) { return rule + (options.addImportant ? '!important' : '') });
var css_rule = (is_strict_selector ? '' : this.content_param + '>') + selectors.join(is_strict_selector ? ',' : '') + '{' + hide_rules.join(';') + '}';
this.styleTag.innerHTML = search_phrase.length ? css_rule : '';
},
_addStyleTag: function() {
var options = this.options;
this.styleTag = document.createElement('style');
if(typeof options.nonceId != 'undefined') this.styleTag.setAttribute('nonce', options.nonceId);
document.head.appendChild(this.styleTag);
},
_getText: function(tag) {
return tag && (tag.textContent || tag.innerText) || '';
},
_sanitize: function(text) {
return this.replaceDiacritics(text).trim().replace(/\s+/g, ' ').toLowerCase()
},
_getContentTags: function(query) {
return Array.prototype.slice.call(this.content_tag).reduce(function(all, elem) {
return all.concat(Array.prototype.slice.call(elem.querySelectorAll(query || ':scope > *')));
}, []);
},
_handleSpecificColumns: function(tag, set) {
var self = this;
if( ! self.options.searchInSpecificColumn) return;
Array.prototype.slice.call(tag.children).map(function(children, i) {
if(self.options.columns && self.options.columns.length && self.options.columns.indexOf(i) == -1) return
tag[(set || 'remove') + 'Attribute']('data-jets-col-' + i, set && self._sanitize(self._getText(children)));
})
},
_setJets: function(query, force) {
var self = this,
tags = self._getContentTags(force ? '' : query), text;
for(var i = 0, tag; tag = tags[i]; i++) {
if(tag.hasAttribute('data-jets') && ! force) continue;
text = this.options.manualContentHandling
? this.options.manualContentHandling(tag)
: self.options.columns && self.options.columns.length
? self.options.columns.map(function(column) {
return self._getText(tag.children[column]);
}).join(' ')
: self._getText(tag);
tag.setAttribute('data-jets', self._sanitize(text));
self._handleSpecificColumns(tag, 'set');
};
},
replaceDiacritics: function(text) {
var diacritics = this.options.diacriticsMap;
for(var letter in diacritics) if(diacritics.hasOwnProperty(letter)) {
for(var i = 0, ii = diacritics[letter].length; i < ii; i++) {
text = text.replace(new RegExp(diacritics[letter][i], 'g'), letter);
}
}
return text;
},
update: function(force) {
this._setJets(':scope > :not([data-jets])', force);
},
_destroy: function() {
this.styleTag.parentNode && document.head.removeChild(this.styleTag);
var tags = this._getContentTags();
for(var i = 0, tag; tag = tags[i]; i++) {
tag.removeAttribute('data-jets');
this._handleSpecificColumns(tag);
}
}
}
// :scope polyfill
// http://stackoverflow.com/a/17989803/1221082
;(function(doc, proto) {
try {
doc.querySelector(':scope body');
} catch (err) {
['querySelector', 'querySelectorAll'].forEach(function(method) {
var nativ = proto[method];
proto[method] = function(selectors) {
if (/(^|,)\s*:scope/.test(selectors)) {
var id = this.getAttribute('id');
this.id = 'ID_' + Date.now();
selectors = selectors.replace(/((^|,)\s*):scope/g, '$1#' + this.getAttribute('id'));
var result = doc[method](selectors);
this.id = id;
return result;
} else {
return nativ.call(this, selectors);
}
}
});
}
})(window.document, Element.prototype);
return Jets;
}));