Use non-english characters in List.js - javascript

I have a list of features created with List.js library. Is there any way I could match non-english characters with regular ones?
For example, the list contains this elements: 'șuncă', 'brânză', 'mărar'.
I'm curious if there's a way to find these elements even if I'm searching without the non-english characters, like this: 'sunca', 'branza', 'marar'

Use the filter() API function and a helper function to replace accented characters in a string with their base versions.
Helper functions
// generic accent removal from input string, add any missing characters
var removeAccents = (function () {
var letters1 = "äáàâăëéèêĕüúùûŭöóòôŏÄÁÀÂĂËÉÈÊĔÜÚÙÛŬÖÓÒÔŎßșȘ",
letters2 = "aaaaaeeeeeuuuuuoooooAAAAAEEEEEUUUUUOOOOOssS",
patternLetters = new RegExp("[" + letters1 + "]", "g"),
lookupLetters = {}, letterTranslator;
letters1.split("").forEach(function (letter, i) {
lookupLetters[letter] = letters2[i];
});
letterTranslator = function(match) {
return lookupLetters[match] || match;
};
return function removeAccents(input) {
return input.replace(patternLetters, letterTranslator);
};
})();
// creates a specific filter function for use in List.js
function getAccentInsensitiveFilter(property, search) {
search = removeAccents(search).toLowerCase();
return function (item) {
var value = removeAccents(item.values()[property] || "").toLowerCase();
return value.indexOf(search) > -1;
};
}
and then
// filter for "sunca" in the "featureName" property
yourList.filter(getAccentInsensitiveFilter("featureName", "șuncă"));
// remove filter
yourList.filter();

Related

How do I search for a substring within a string and return that string

I'm trying to add the following logic to the below Custom Javascript variable within GTM:
"Find if a string contains this word, and return that word"
function() {
var el = {{Click Element}};
el = el.parentElement;
var classes = el.className;
return classes ;
}
In the above code the classes variable returns the following string:
'wp-block-button jungle-cta tiger-animal'
I'm trying to do the following: if the string contains "tiger" then return "tiger-animal" and only that.
I am applying this function on different CTAs on the same landing page. Each CTA has a tiger constant as part of the class name. So one CTA is "tiger-animal", the other "tiger-something", "tiger-word" etc. I'm looking for logic that is agnostic of what follows after "tiger-". If the tiger is found then parse that word which contains the tiger and return it.
Update:
The below works now! Thank you.
function() {
var el = {{Click Element}};
el = el.parentElement;
var classes = el.className;
var regex = /tiger-[a-z]*/g;
var found = classes.match(regex);
return found[0]
}
Updating the code here to match what you mentioned in the comment. It now returns the string if matches, otherwise returns undefined:
function checkIfTiger(input) {
const regex = /tiger-[a-z]*/g;
const found = input.match(regex)
return found !== null && found[0]
}
checkIfTiger("fsd adfj adkfj tiger-adsf dfadf")
// returns "tiger-asdf"
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match
How about using the .includes property. For example:
let a = 'wp-block-button jungle-cta tiger-animal'
function checkIfTiger() {
if(a.includes("tiger")) {
return "tiger-animal"
}
}
checkIfTiger()
Actually, here is a version with lesser code. It return "tiger-animal" if "tiger" is present in whatever you're checking, otherwise returns false:
let a = 'wp-block-button jungle-cta tiger-animal'
function checkIfTiger() {
return a.includes("tiger") && "tiger-animal"
}
checkIfTiger()
If the only important change you're looking to make to this function is that it will return the string "tiger-animal" if your variable classes contains the substring "tiger", then I recommend a simple if/else statement that makes use of the .includes() method, which returns true if your string contains a specified substring, or false if it doesn't. For example:
function() {
var el = {{Click Element}};
el = el.parentElement;
var classes = el.className;
if (classes.includes("tiger")) {
return "tiger-animal";
} else {
return classes;
}
}
And if you don't feel like it'll hurt readability, the ternary operator uses fewer lines:
function() {
var el = {{Click Element}};
el = el.parentElement;
var classes = el.className;
return (classes.includes("tiger") ? "tiger-animal" : classes)
}
Both of these functions will either return classes, or, if classes contains the substring "tiger" it will return the string "tiger-animal".
For any casual passersby wanting to know how to check for substrings, here's a more barebones function demonstrating this principle using less of the question's original context:
function tigerChecker(string) {
let classes = string
if (classes.includes("tiger")) {
return "tiger-animal";
} else {
return classes;
}
}

Using str.replace for multiple words

I am creating a work tool for notepad abbreviations. As the company I work for is strict about downloading any external tools I've resorted to using Javascript and HTML built on notepad.
I've been able to replace single words such as when I type "Vacancy" it returns "VAC". Or when typing "Payment" it returns "PYMT". My issue is trying to replace multiple words into 1 small abbreviation. For instance "Follow Up" I want to return "F/U". With the spaces I'm finding it is not working.
Tried multiple ways but unable to figure this out.
Here is the code snippet that I've used
function myFunction() {
var str = document.getElementById("demo").value;
var mapObj = {
Payment:"PYMT",
Vacancy:"VAC",
str = str.replace(/Payment|Vacancy, fucntion(matched){
return mapObj[matched];
});
alert(str);
document.getElementById("demo").value = res;
}
What I would like to do is add my mabObj
so it would read
function myFunction() {
var str = document.getElementById("demo").value;
var mapObj = {
Follow Up:"F/U"
str = str.replace(/Follow Up|, fucntion(matched){
return mapObj[matched];
});
alert(str);
document.getElementById("demo").value = res;
}
JavaScript objects can have properties with spaces in them, but in order to do so, the property name needs to have quotes around it.
That said, I would suggest using a Map in this case, as it will allow you to match any string without worrying about naming collisions with properties from the object's prototype.
const abbreviation = new Map([
['Follow Up', 'F/U'],
['Payment', 'PYMT'],
['Vacancy', 'VAC']
]);
const input = 'Payment noise Vacancy noise Follow Up noise Vacancy';
const pattern = new RegExp(Array.from(abbreviation.keys()).join('|'),'g');
const result = input.replace(pattern, (matched) => {
return abbreviation.get(matched) || matched;
});
console.log(result); // 'PYMT noise VAC noise F/U noise VAC'
To include a key with a space in an object you can put it in brackets like {["Follow Up"]: "F/U"}
function replaceKeyWords(str) {
var mapObj = {
Payment:"PYMT",
Vacancy:"VAC",
["Follow Up"]:"F/U",
};
str = str.replace(/(Payment|Vacancy|Follow Up)/, function(matched){
return mapObj[matched];
});
return str;
}
console.log(replaceKeyWords("Payment"));
console.log(replaceKeyWords("Vacancy"));
console.log(replaceKeyWords("Follow Up"));

Search for multiple elements in an array

I want to retrieve inside an array all the elements who match multiple strings (all of them & not necessary words): like a search engine returning all results matching term_searched#1 && term_searched#2.
It's not a question about duplicates in the array (there's none), but about searching for a conjunction of elements: traditionally, the search is for one element, by himself or in disjunction with others (a|b|c). Just want to search (a && b && c).
I tried:
indexOf() : I can work only with one element to locate in the array.
match() : there is no AND operator in a regex expression (only | - sadly, it would be so simple). So I tried to inject these regex expressions
/(?=element1).*(?=element2)/gim
/(?=element1)(?=element2)/gim see here
The first regex expression works, but not at every time: seems very fragile...
So I don't know if I'm in the good direction (match) or if I can't figure what is the right regex expression... Need your advices.
// filter grid by searching on 'input' event
'input #search': (e)=> {
var keypressed = e.currentTarget.value;
// create array on 'space' input
var keyarr = keypressed.toLowerCase().split(" ");
// format each array's element into regex expression
var keyarrReg = [];
for(i = 0; i < keyarr.length; i++) {
var reg = '(?=' + keyarr[i] + ')';
keyarrReg.push(reg);
}
// array to regex string into '/(?=element1).*(?=element2)/gim' format
var searching = new RegExp(keyarrReg.join(".*"), 'mgi');
// set grid
var grid = new Muuri('#gridre', {
layout: {
fillGaps: true,
}
});
if (keypressed) {
// filter all grid's items (grid of items is an array)
grid.filter(function (item) {
var searchoperator = item.getElement().textContent.toLowerCase().match(searching);
// get items + only their text + lower case their text + return true (not false) in the value ('keypressed') is found in them
//var searchoperator = item.getElement().textContent.toLowerCase().indexOf(keypressed.toLowerCase()) != -1;
return searchoperator;
}
[....]
}
}
Edit with Gawil's answer adapted to my initial code (to help if needed)
// filter grid by searching on 'input' event
'input #search': (e)=> {
var keypressed = e.currentTarget.value;
// create array on 'space' input
var keyarr = keypressed.toLowerCase().split(" ");
// convert the array to a regex string, in a '^(?=.*word1)(?=.*word2).*$' format
// here is Gawil's answer, formatted by Teemu
var searching = new RegExp('^(?=.*' + keyarr.join(')(?=.*') + ').*$', 'm');
// set grid
var grid = new Muuri('#gridre', {
layout: {
fillGaps: true,
}
});
if (keypressed) {
// filter all grid's items (grid of items is an array)
grid.filter(function (item) {
// get items + only their text + lower case their text + delete space between paragraphs
var searchraw = item.getElement().textContent.toLowerCase().replace(/\r\n|\n|\r/gm,' ');
var searchoperator = searchraw.match(searching);
return searchoperator;
}
[....]
}
}
The code bellow will log each element of the array containing words cats and dogs.
It uses the regex ^(?=.*word1)(?=.*word2).*$To handle new lines, use this one instead :
^(?=(?:.|\n)*word1)(?=(?:.|\n)*word2).*$
You can add as many words as you want following the same logic, and it does not take order of the words in count.
It is very similar to what you tried, except that you have to do all (?=) checks before matching the string. Indeed, your first regex works only when the words are in the right order (element1 and then element2). Your second regex almost works, but you wrote only lookaheads, so it checks the presence of each word, but won't match anything.
var words = ["cats", "dog"]
var array = [
"this is a string",
"a string with the word cats",
"a string with the word dogs",
"a string with both words cats and dogs",
"cats rule everything",
"dogs rule cats",
"this line is for dog\nbut cats prefer this one"
]
var regexString = "^";
words.forEach(function(word) { regexString += ("(?=(?:.|\n)*"+word+")"); });
var regex = new RegExp(regexString);
array.forEach(function(str) { // Loop through the array
if(str.match(regex)) {
console.log(str); // Display if words have been found
}
});
If I've correctly understood your question, you've an array of strings, and some keywords, which have to be found from every index in the array to be accepted in the search results.
You can use a "whitelist", i.e. a regExp where the keywords are separated with |. Then iterate through the array, and on every member create an array of matches against the whitelist. Remove the duplicates from the matches array, and check, that all the keywords are in the list simply by comparing the length of the matches array to the count of the keywords. Like so:
function searchAll (arr, keywords) {
var txt = keywords.split(' '),
len = txt.length,
regex = new RegExp(txt.join('|'), 'gi'), // A pipe separated whitelist
hits; // The final results to return, an array containing the contents of the matched members
// Create an array of the rows matching all the keywords
hits = arr.filter(function (row) {
var res = row.match(regex), // An array of matched keywords
final, temp;
if (!res) {return false;}
// Remove the dups from the matches array
temp = {}; // Temporary store for the found keywords
final = res.filter(function (match) {
if (!temp[match]) {
// Add the found keyword to store, and accept the keyword to the final array
return temp[match] = true;
}
return false;
});
// Return matches count compared to keywords count to make sure all the keywords were found
return final.length === len;
});
return hits;
}
var txt = "Some text including a couple of numbers like 8 and 9. More text to retrieve, also containing some numbers 7, 8, 8, 8 and 9",
arr = txt.split('.'),
searchBut = document.getElementById('search');
searchBut.addEventListener('change', function (e) {
var hits = searchAll(arr, e.target.value);
console.log(hits);
});
<input id="search">
The advantage of the whitelist is, that you don't have to know the exact order of the keywords in the text, and the text can contain any characters.

Issue with JavaScript hasOwnProperty in Selectize createFilter

I'm trying to figure out the workings of a neat jQuery based library called selectize.js.
On the demos-page they have an example of the "createFilter" function, which I want to use to check whether the user input already exist, and if not, create the item.
The example from selectize.js (meant for e-mail):
createFilter: function(input) {
var match, regex;
// email#address.com
regex = new RegExp('^' + REGEX_EMAIL + '$', 'i');
match = input.match(regex);
if (match) return !this.options.hasOwnProperty(match[0]);
// name <email#address.com>
regex = new RegExp('^([^<]*)\<' + REGEX_EMAIL + '\>$', 'i');
match = input.match(regex);
if (match) return !this.options.hasOwnProperty(match[2]);
return false;
},
My own take on making a filter to only allow letters, and check if it's already in the list:
createFilter: function (input) {
var match, regex;
regex = new RegExp('^[a-zA-ZæøåÆØÅ][a-zA-ZæøåÆØÅ ]*[a-zA-ZæøåÆØÅ]$', 'i');
match = input.match(regex);
if (match) return !this.options.hasOwnProperty(match[0]);
return false;
},
For some reason it always returns true thus allowing the user to add a new item, even though it already exists, after hours of testing, I'm convinced it has something to do with this.options.hasOwnProperty - but I'm at a dead end figuring out what and why, any help or guidance would be greatly appreciated.
I have been trying to get the createFilter to work as well, and looked into the source, but things seems setup properly but there is a clearly a bug in there somewhere. For now my solution was the use of the actual create method. If you dig a bit in the source (alluded to by the documentation on the create option) you will notice that prior to item creation it will check if your value of create is a function:
var setup = (typeof self.settings.create === 'function') ? this.settings.create : function(input) {
var data = {};
data[self.settings.labelField] = input;
data[self.settings.valueField] = input;
return data;
};
By passing in a creation function that only returns a value for data when a certain condition is passed, else it returns {}, you can effectively do exactly what you wanted to do in the createFilter function.
$('#foo').selectize({
create: function(input){
var self = this;
var data = {};
// IF SOME CONDITION MET
if (bar) {
data[self.settings.labelField] = input;
data[self.settings.valueField] = input;
}
return data;
},
});
If data is returned as {}, the item is not created and the user can continue to modify it as needed (or be informed that there selection was invalid).

jQuery selector that simulates :starts-with or :ends-with for searching text?

If you look at the selectors list on the jQuery website, there are selectors for starts-with and ends-with on attributes. There's also a :contains selector for searching text:
alert( $("div").find("span:contains(text)").html() );
Does jQuery have an implementation for searching strings using starts-with or ends-with?
FYI: I need to search through an XML object.
Not by default as far as I know, but you can add your own pseudo-selectors through $.expr[":"]: http://jsfiddle.net/h6KYk/.
$.extend($.expr[":"], {
"starts-with": function(elem, i, data, set) {
var text = $.trim($(elem).text()),
term = data[3];
// first index is 0
return text.indexOf(term) === 0;
},
"ends-with": function(elem, i, data, set) {
var text = $.trim($(elem).text()),
term = data[3];
// last index is last possible
return text.lastIndexOf(term) === text.length - term.length;
}
});
When you don't want to extend jQuery, you can use the filter() function to create the contains functionality:
$("div").find("span").filter(function () {
return $(this).text().indexOf(text) >= 0;
});
Or create a startsWith function with a regular expression:
var expression = new RegExp('^' + text);
$("div").find("span").filter(function () {
return expression.test($.trim($(this).text()));
});
The endsWith function is quite similar:
var expression = new RegExp(text + '$');
$("div").find("span").filter(function () {
return expression.test($.trim($(this).text()));
});
Note the use of $.trim() because HTML can contain a lot of whitespace.

Categories