Select2 Custom Matcher for Non-Adjacent Keywords - javascript

I use Select2 in my app to allow for searching a dropdown with about 1200 options.
I am currently making use of the default implementation of Select2's matcher, which works well as long as keywords are adjacent in the search results:
function(term, text) { return text.toUpperCase().indexOf(term.toUpperCase())>=0; }
For example, a search for 'stackoverflow question' returns option 'Stackoverflow question about Select2'
I would however else like the matcher to return results based on non-adjacent keywords. For instance, I would also like it to return the above option when searching for 'stackoverflow select2'.
Would anyone have an idea how to create a custom matcher to allow for this behavior?

this is what i did in Select2 4.
i wanted matcher to return only options that contains all of keywords entered (assuming keywords are search term splitted by " "). Matching is case insesitive.
matcher: function (params, data) {
// If there are no search terms, return all of the data
if ($.trim(params.term) === '') {
return data;
}
// `params.term` should be the term that is used for searching
// split by " " to get keywords
keywords=(params.term).split(" ");
// `data.text` is the text that is displayed for the data object
// check if data.text contains all of keywords, if some is missing, return null
for (var i = 0; i < keywords.length; i++) {
if (((data.text).toUpperCase()).indexOf((keywords[i]).toUpperCase()) == -1)
// Return `null` if the term should not be displayed
return null;
}
// If here, data.text contains all keywords, so return it.
return data;
}
i know this is old topic, but maybe someone find this usefull.

If you have large amount of data or nested data then the permutation will take lot of time.
Try instead this for searching using Non-Adjacent Keywords.
Just put this function in your document.ready before initializing select2.
$(function () {
var keywords;
$.fn.select2.defaults = $.extend($.fn.select2.defaults, {
placeholder: 'Select...',
matcher: function(term, text, option) {
if ($.trim(term) === '') {
return true;
}
keywords = (term).split(" ");
for (var i = 0; i < keywords.length; i++) {
if ((text.toUpperCase()).indexOf((keywords[i]).toUpperCase()) == -1 )
{
return false;
}
}
return true;
}
});
$("#DropdownID").select2();
});
The Working example is here : http://makmilan.blogspot.in/2015/11/select2-custom-matcher-for-non-adjacent.html

Try this:
search Stackoverflow question, stackoverflow select2, select2 stackoverflow, about stackoverflow select2 question, question select2 about
<select id="e17_2" style="width:300px">
<option alt="Stackoverflow question about Select2">Stackoverflow question about Select2</option>
<option alt="Stackoverflow Other line ...">Stackoverflow Other line ...</option>
</select>
Copied from: https://stackoverflow.com/a/21745151/3710490
function permute(input, permArr, usedChars) {
var i, ch;
for (i = 0; i < input.length; i++) {
ch = input.splice(i, 1)[0];
usedChars.push(ch);
if (input.length == 0) {
permArr.push(usedChars.slice());
}
permute(input, permArr, usedChars);
input.splice(i, 0, ch);
usedChars.pop();
}
return permArr
};
$("#e17_2").select2({
matcher: function(term, text) {
if (term.length == 0) return true;
texts = text.split(" ");
allCombinations = permute(texts, [], []);
for(i in allCombinations){
if( allCombinations[i].join(" ").toUpperCase().indexOf(term.toUpperCase())==0 ){
return true;
}
}
return false;
}
});

Look for all or part of imperativements words :
element.select2({
matcher: function(term, text){
if (term.length < 3) { return true }
var terms = term.split(" ");
var count = 0;
var nbterm = terms.length;
for(i in terms) {
if (text.toUpperCase().match(new RegExp(terms[i], "i"))) { count++ }
if(nbterm == count){
return true;
}
}
return false;
}
});

Related

Not able to replace text during search operation

I am currently search a word document using the office-js api and find all instances of a token which is being placed in the word doc. However when I am trying to replace the matching token I am getting debug.errorlocation = "Range.insertParagraph". Seems like the operation should work the way it is written, however it does not replace the desired word when search result is found.
Sample string
Our strategy is to consider ~~client~~​'s​ business needs, and our audit will spec​​ifically focus on these related key factors:
Code
Word.run(function (context) {
var content = contentObject.Content.replace(/<img[^>"']*((("[^"]*")|('[^']*'))[^"'>]*)*>/g, "");
var range = context.document.getSelection().insertHtml(content, Word.InsertLocation.replace);
var paragraphs = context.document.body.paragraphs;
var clientName;
paragraphs.load(paragraphs, range, 'text');
return context.sync().then(function () {
for (var x = 0; x < paragraphs.items.length; x++) {
var paragraph = paragraphs.items[x];
var styleType = paragraphs.items[x].text.toString().match(/~~([^]*?)~~/g);
if (paragraphs.items[x].text.search("~~") >= 0 && styleType[0] != "~~/picture~~") {
var styleValue = styleType[0].toString().replace(/[\]~~)}[{(]/g, '').trim();
paragraph.style = styleValue;
}
if(paragraphs.items[x].style === "/Title Page Client Name")
{
var name = paragraphs.items[x].text;
clientName = name;
}
}
return context.sync().then(function () {
var searchResults = context.document.body.search('~~client~~', { ignoreSpace: true });
context.load(searchResults);
return context.sync().then(function () {
for (var i = 0; i < searchResults.items.length; i++) {
error location>> searchResults.items[i].insertParagraph(clientName, Word.InsertLocation.replace);
}
clientName = "";
})
})
})
.then(context.sync)
.then(cleanTags())
.catch(function (error) {
feedBackMessage(error.description);
})
});
};
Ok the issue which was causing me the error was that I was using .insertParagraph where the word api was expecting a paragraph insertion and not just a word. I think it is awesome that this api is designed well enough to actually detect a paragraph vs simple text insertion. For the record, if someone is going to make an insertion of text (just one word), they will need to use .insertText .
Wording Code
return context.sync().then(function () {
var searchResults = context.document.body.search('~~client~~', { ignoreSpace: true });
context.load(searchResults);
return context.sync().then(function () {
for (var i = 0; i < searchResults.items.length; i++) {
searchResults.items[i].insertText(clientName, Word.InsertLocation.replace);
}
clientName = "";
return context.sync().then(function () {
})
})
})

Search string inside array javascript

I am looking for a simple function to check if a specific string is inside my array.
function checkRegion(departement){
var region = '';
if(alsace.indexOf(departement) != -1)
{
region = "alsace";
}
if(aquitaine.indexOf(departement) != -1){
region = "aquitaine";
}
if(auvergne.indexOf(departement) != -1){
region = "auvergne";
}
if(basseNormandie.indexOf(departement) != -1){
region = "basse-normandie";
}
if(iledefrance.indexOf(departement) != -1){
region = "ile-de-france";
}else{
region = 'undennnnnfined';
}
return region;
};
Any solution ?
Thanks
Your problem lies in the use of consecutive if statements without chaining them together to make a complete check.
Doing it your way, the code actually completely disregards all the if statements, but the last one.
So, if iledefrance.indexOf(departement) != -1 gives false, it'will always execute the code inside else, meaning it'll set region = 'undennnnnfined'.
Note:
In the code, I replaced != -1 with ~ as it makes for a somewhat more succinct code. In essence, it will convert -1 to 0, namely false.
Be sure to check out MDN's documentation for more if you are not familiar.
• 1st Option :
Try chaining your if statements together in an if/else if/else format as follows:
function checkRegion(departement){
var region = '';
if(~alsace.indexOf(departement)) {
region = "alsace";
}
else if(~aquitaine.indexOf(departement)) {
region = "aquitaine";
}
else if(~auvergne.indexOf(departement)) {
region = "auvergne";
}
else if(~basseNormandie.indexOf(departement)) {
region = "basse-normandie";
}
else if(~iledefrance.indexOf(departement)) {
region = "ile-de-france";
}
else{
region = 'undennnnnfined';
}
return region;
};
• 2nd Option :
Create two arrays:
One should contain your region arrays (alsace, aquitaine etc) &
One more containing the names of your arrays as strings, so that you can return the appropriate string based on the array that was evaluated as true.
Code:
function checkRegion(departement) {
var
regions = [alsace, aquitaine, auvergne, basseNormandie, iledefrance],
regionsNames = ["alsace", "aquitaine", "auvergne", "basseNormandie", "iledefrance"];
for (var i = 0; i < regions.length; i++) {
if (~regions[i].indexOf(departement)) {
return regionsNames[i];
}
}
return "undennnnnfined"; // If the 'for' loop doesn't return anything, it's false.
};
Demos:
Working demo with the 1st solution → here.
Working demo with the 2nd solution → here.
Snippets:
Snippet with the 1st solution:
function checkRegion(departement){
var region = '';
if(~alsace.indexOf(departement)) {
region = "alsace";
}
else if(~aquitaine.indexOf(departement)) {
region = "aquitaine";
}
else if(~auvergne.indexOf(departement)) {
region = "auvergne";
}
else if(~basseNormandie.indexOf(departement)) {
region = "basse-normandie";
}
else if(~iledefrance.indexOf(departement)) {
region = "ile-de-france";
}
else{
region = 'undennnnnfined';
}
return region;
};
var
alsace = ["Strasbourg", "Colmar"],
aquitaine = ["Gironde", "Landes", "Dordogne", "Pyrenees-Atlantiques", "Lot-et-Garonne"],
auvergne = [],
basseNormandie = [],
iledefrance = [];
alert(checkRegion("Strasbourg"));
Snippet with the 2nd solution:
function checkRegion(departement) {
var
regions = [alsace, aquitaine, auvergne, basseNormandie, iledefrance],
regionsNames = ["alsace", "aquitaine", "auvergne", "basseNormandie", "iledefrance"];
for (var i = 0; i < regions.length; i++) {
if (~regions[i].indexOf(departement)) {
return regionsNames[i];
}
}
return "undennnnnfined"; // If the 'for' loop doesn't return anything, it's false.
};
var
alsace = ["Strasbourg", "Colmar"],
aquitaine = ["Gironde", "Landes", "Dordogne", "Pyrenees-Atlantiques", "Lot-et-Garonne"],
auvergne = [],
basseNormandie = [],
iledefrance = [];
alert(checkRegion("Strasbourg"));

select first visible row after filter ui-grid

I can't force to select first row after applied filter. So when I'm loading my page to select first row I use:
gridApi.selection.selectRow($scope.gridOptions.data[0]);
this is from API documentation and it is clear.
Now, I'm trying to select first row after filter.
I have singleFilter function which comes from official documentation
$scope.singleFilter = function( renderableRows ){
var matcher = new RegExp($scope.filterValue);
renderableRows.forEach( function( row ) {
var match = false;
[
'name', 'company', 'email'
].forEach(function( field ){
if (field.indexOf('.') !== '-1' ) {
field = field.split('.');
}
if ( row.entity.hasOwnProperty(field) && row.entity[field].match(matcher) || field.length === 2 && row.entity[field[0]][field[1]].match(matcher)){
match = true;
}
});
if ( !match ){
row.visible = false;
}
});
var rows = $scope.gridApi.core.getVisibleRows();
var first = function(array, n) {
if (array == null){
return void 0;
}
if (n == null) {
return array[0];
}
if (n < 0) {
return [];
}
return array.slice(0, n);
};
console.log(first(rows))
$scope.gridApi.selection.selectRow(first(rows));
return renderableRows;
};
where I get the length of visible rows
var rows = $scope.gridApi.core.getVisibleRows();
thru simple script I get first row
var first = function(array, n) {
if (array == null){
return void 0;
}
if (n == null) {
return array[0];
}
if (n < 0) {
return [];
}
return array.slice(0, n);
};
console.log(first(rows))
then I'm trying to apply selection
$scope.gridApi.selection.selectRow(first(rows));
But unfortunately no success. Where is my mistake? I appreciate any help.
My plunker
I've created a working plunker below.
The reason this is not working is because the visible rows that you are getting is all of the rows, and not just the filtered rows. The reason that is all of the rows is because you are calling for them before returning the filter. I've created logic using what we are knowledgeable about at this point, which is what will be returned once the function completes.
http://plnkr.co/edit/LIcpOs7dXda5Qa6DTxFU
var filtered = [];
for (var i = 0; i < renderableRows.length; i++) {
if (renderableRows[i].visible) {
filtered.push(renderableRows[i].entity)
}
}
if (filtered.length) {
$scope.gridApi.selection.selectRow(filtered[0]);
}

Get only one match with regexp

In the function below I iterate through an array (incidents) which contains of strings. The strings is describing an incident (crime or accidents) that is scrapted from another web app, and what I'm doing is dividing and counting the different crimes / accidents and placing them in an object (INCIDENT_MATCHES).
However, some of the text strings may contain of several of the keywords that I search for (e.g. both "gunfire" and "battery"), but that I don't want. Instead I just want the first found word to be counted, and if more keywords are found they should be ignored.
How could this be done?
var INCIDENT_MATCHES = {
battery: /\w*(bråk)\w*|överfall|slagsmål|slogs|misshandel|misshandlad|\w*(tjuv)\w*/ig,
burglaries: /snattade|snattare|snatta|inbrott|bestulen|stöld|\w*(tjuv)\w*/ig,
robberies: /\w*(rån)\w*|personrån|\w*(ryckning)\w*|väskryckt*/ig,
gunfire: /skottlossning|skjuten|sköt/ig,
drugs: /narkotikabrott/ig,
vandalism: /skadegörelse|klotter|\w*(klottra)\w*/ig,
trafficAccidents: /(trafik|bil)olycka|(trafik|bil)olyckor|\w*(personbil)\w*|singelolycka|kollision|\w*(kollidera)\w*|påkörd|trafik|smitningsolycka/ig,
};
var j = 0,
incidentCounts = {},
incidentTypes = Object.keys(INCIDENT_MATCHES);
incidents.forEach(function(incident) {
matchFound = false;
incidentTypes.forEach(function(type) {
if(typeof incidentCounts[type] === 'undefined') {
incidentCounts[type] = 0;
}
var matchFound = incident.match(INCIDENT_MATCHES[type]);
if(matchFound){
matchFound = true;
incidentCounts[type] += 1;
}
});
j++;
});
You can return false from the "each" handler to stop iteration.
if(matchFound){
matchFound = true;
incidentCounts[type] += 1;
return false;
}
edit — and you'll want (I think) another test outside that, at the end of the outer loop:
j++; // I don't understand what that does ...
if (matchFound) return false;
I found this solution below to work. What I did was the following:
I replaced the second forEach statement with "every"
Put "return false" inside "if(matchFound)"
Added "else { return true; }" so that the loop continues if no match is found.
The code:
incidents[2].forEach(function(incident) {
matchFound = false;
incidentTypes.every(function(type) {
if(typeof crimesPerType[type] === 'undefined') {
crimesPerType[type] = 0;
}
var matchFound = incident.match(INCIDENT_MATCHES[type]);
if(matchFound){
crimesPerType[type] += 1;
if (type == 'trafficAccidents') {
incidents[3][j].push('traffic');
}
else {
incidents[3][j].push('crime');
}
return false;
}
else {
return true;
}
});

Add space between numbers/digits and letters/characters

I have a code like this
(function($, window, document, undefined) {
$.fn.quicksearch = function (target, opt) {
var timeout, cache, rowcache, jq_results, val = '', e = this, options = $.extend({
delay: 100,
selector: null,
stripeRows: null,
loader: null,
noResults: '',
bind: 'keyup',
onBefore: function () {
return;
},
onAfter: function () {
return;
},
show: function () {
this.style.display = "";
},
hide: function () {
this.style.display = "none";
},
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
testQuery: function (query, txt, _row) {
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
}, opt);
this.go = function () {
var i = 0,
noresults = true,
query = options.prepareQuery(val),
val_empty = (val.replace(' ', '').length === 0);
for (var i = 0, len = rowcache.length; i < len; i++) {
if (val_empty || options.testQuery(query, cache[i], rowcache[i])) {
options.show.apply(rowcache[i]);
noresults = false;
} else {
options.hide.apply(rowcache[i]);
}
}
if (noresults) {
this.results(false);
} else {
this.results(true);
this.stripe();
}
this.loader(false);
options.onAfter();
return this;
};
this.stripe = function () {
if (typeof options.stripeRows === "object" && options.stripeRows !== null)
{
var joined = options.stripeRows.join(' ');
var stripeRows_length = options.stripeRows.length;
jq_results.not(':hidden').each(function (i) {
$(this).removeClass(joined).addClass(options.stripeRows[i % stripeRows_length]);
});
}
return this;
};
this.strip_html = function (input) {
var output = input.replace(new RegExp('<[^<]+\>', 'g'), "");
output = $.trim(output.toLowerCase());
return output;
};
this.results = function (bool) {
if (typeof options.noResults === "string" && options.noResults !== "") {
if (bool) {
$(options.noResults).hide();
} else {
$(options.noResults).show();
}
}
return this;
};
this.loader = function (bool) {
if (typeof options.loader === "string" && options.loader !== "") {
(bool) ? $(options.loader).show() : $(options.loader).hide();
}
return this;
};
this.cache = function () {
jq_results = $(target);
if (typeof options.noResults === "string" && options.noResults !== "") {
jq_results = jq_results.not(options.noResults);
}
var t = (typeof options.selector === "string") ? jq_results.find(options.selector) : $(target).not(options.noResults);
cache = t.map(function () {
return e.strip_html(this.innerHTML);
});
rowcache = jq_results.map(function () {
return this;
});
return this.go();
};
this.trigger = function () {
this.loader(true);
options.onBefore();
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
e.go();
}, options.delay);
return this;
};
this.cache();
this.results(true);
this.stripe();
this.loader(false);
return this.each(function () {
$(this).bind(options.bind, function () {
val = $(this).val();
e.trigger();
});
});
};
}(jQuery, this, document));
I try to figure out where and how I can make a split/add space between numbers and letters. Cause some people type for example "ip1500" and the script cant match the input with an element that is like "ip 1500". My problem ist that Im a js beginner.
I was trying and trying but i cant get it work. I also tried this
I found this spot and I think it can be done here where the everything get splitted by an " " (space):
prepareQuery: function (val) {
return val.toLowerCase().split(' ');
},
Would be very nice if somebody can help me.
If you want "123abc345def" to "123 abc 345 def". The replace function may help. The code is like this.
var str = "123abc345def";
str = str.replace(/(\d+)/g, function (_, num){
console.log(num);
return ' ' + num + ' ';
});
str = str.trim();
The code you linked didn't work mainly because it's using a different programming language to javascript. In theory, it should work, but javascript does not support regular expression lookbehinds (at this present time)..
Instead, I have re-wrote that fragment of code:
prepareQuery: function (val) {
function isNotLetter(a){
return (/[0-9-_ ]/.test(a));
}
var val=val.toLowerCase().split("");
var tempArray=val.join("").split("");
var currentIndex=1;
for (var i=0;i<val.length-1;i++){
if (isNotLetter(val[i]) !== isNotLetter(val[i+1])){
tempArray.splice(i+currentIndex, 0, " ");
currentIndex++;
}
}
return tempArray.join("");
}
Since you're new to javascript, I'm going to explain what it does.
It declares a function in prepareQuery to check whether or not a string contains a letter [this can be moved somewhere else]
It then splits val into an array and copies the content of val into tempArray
An index is declared (explained later)
A loop is made, which goes through every single character in val
The if statement detects whether or not the current character (val[i] as set by the loop) is the same as the character next to it (val[i+1]).
IF either one are different to the other (ie the current character is a letter while the next isn't) then a space is added to the tempArray at that "index"
The index is incremented and used as an offset in #6
The loop finishes, joins the "array" into a string and outputs the result.
DEMO:
http://jsbin.com/ebitus/1/edit
(JSFiddle was down....)
EDIT:
Sorry, but I completely misinterpreted your question... You failed to mention that you were using "quicksearch" and jQuery. In that case I'm assuming that you have a list of elements that have names and you want to search through them with the plugin...
A much easier way to match the user's query (if there is no space) is to strip the space from the search table along with the query itself - though original reverse method will work (just not as efficiently) [aka: expanding the user's query]
In this case, stripping the space from both the search table and user input would be a better method
prepareQuery: function (val) {
return val.toLowerCase().replace(/ /ig,'').split(" ");
},
testQuery: function (query, txt, _row) {
txt=txt.toLowerCase().replace(/ /ig,'');
for (var i = 0; i < query.length; i += 1) {
if (txt.indexOf(query[i]) === -1) {
return false;
}
}
return true;
}
DEMO:
http://jsfiddle.net/q9k9Y/3/
Edit 2:
It seems like your real intent is to create a fully functioning search feature on your website, not to just add spaces between letters and numbers. With this, I suggest using Quicksilver. I would love to work out an algorithm to extend quickSearcher but at the current time I cannot (timezones). Instead, I suggest using Quicksilver
http://jsbin.com/oruhet/12/

Categories

Resources