Creating search bar - javascript

I am trying to make a pure JavaScript search box. I want it to search very loosely, i.e. it shouldn't be case sensitive, it shouldn't take spaces into account, etc.
I came upon this codepen, which has what I'm looking for, but it's written using JQuery. I'm trying to convert it to JavaScript only, but I'm having trouble doing it.
My question isn't on the animation, it's on the bare minimum search code. So basically here's the only part I need help with:
$.extend($.expr[':'], {
'containsi': function(elem, i, match, array) {
return (elem.textContent || elem.innerText || '').toLowerCase()
.indexOf((match[3] || "").toLowerCase()) >= 0;
}
});
var searchSplit = searchTerm.replace(/ /g, "'):containsi('")
$("#list li").not(":containsi('" + searchSplit + "')").each(function(e) {
$(this).addClass('hidden');
});
$("#list li:containsi('" + searchSplit + "')").each(function(e) {
$(this).removeClass('hidden');
});
I got the JavaScript equivalent of JQuery's extend which is in the JSFiddle bellow.
JSFiddle
$(document).ready(function() {
// My JavaScript code
var input = document.getElementById('search-text');
input.addEventListener("keyup", function() {
var searchTerm = input.value,
listItem = document.getElementsByClassName('searchArray');
// JavaScript equivalent of jQuery's extend method
function extend(a, b) {
for (var key in b)
if (b.hasOwnProperty(key))
a[key] = b[key];
return a;
}
});
// Bare minimum code from http://codepen.io/robooneus/pen/ivdFH
//we want this function to fire whenever the user types in the search-box
$("#search-text").keyup(function() {
//first we create a variable for the value from the search-box
var searchTerm = $("#search-text").val();
//then a variable for the list-items (to keep things clean)
var listItem = $('#list').children('li');
//extends the default :contains functionality to be case insensitive
//if you want case sensitive search, just remove this next chunk
$.extend($.expr[':'], {
'containsi': function(elem, i, match, array) {
return (elem.textContent || elem.innerText || '').toLowerCase()
.indexOf((match[3] || "").toLowerCase()) >= 0;
}
}); //end of case insensitive chunk
//this part is optional
//here we are replacing the spaces with another :contains
//what this does is to make the search less exact by searching all words and not full strings
var searchSplit = searchTerm.replace(/ /g, "'):containsi('")
//here is the meat. We are searching the list based on the search terms
$("#list li").not(":containsi('" + searchSplit + "')").each(function(e) {
//add a "hidden" class that will remove the item from the list
$(this).addClass('hidden');
});
//this does the opposite -- brings items back into view
$("#list li:containsi('" + searchSplit + "')").each(function(e) {
//remove the hidden class (reintroduce the item to the list)
$(this).removeClass('hidden');
});
});
});
.hidden {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="text" id="search-text" placeholder="search">
<ul id="list">
<li class="searchArray">Apple pie</li>
<li class="searchArray">Pumpkin pie</li>
<li class="searchArray">Banana-creme pie</li>
<li class="searchArray">Peach-blackberry cobbler</li>
<li class="searchArray">Chocolate-strawberry torte</li>
<li class="searchArray">Chocolate-zucchini cake</li>
<li class="searchArray">Anything involving chocolate and mint</li>
<li class="searchArray">Red-velvet cake</li>
<li class="searchArray">Anything involving fruits that aren't cherries</li>
</ul>

You can use a version of typeahead.js substringMatcher function
var substringMatcher = function(strs, q, cb) {
return (function(q, cb, name) {
var matches, substrRegex;
// an array that will be populated with substring matches
matches = [];
// regex used to determine if a string contains the substring `q`
substrRegex = new RegExp(q, 'i');
// iterate through the pool of strings and for any string that
// contains the substring `q`, add it to the `matches` array
strs.forEach(function(str, i) {
if (substrRegex.test(str)) {
// the typeahead jQuery plugin expects suggestions to a
// JavaScript object, refer to typeahead docs for more info
matches.push(name(str));
}
});
cb(matches);
}(q, cb, function(res) {
return res
}));
};
var elems = document.getElementsByClassName('searchArray');
// My JavaScript code
var listItem = Array.prototype.slice.call(elems);
var list = listItem.map(function(el) {
el.className = el.className + " " + "hidden";
return el.textContent;
})
var input = document.getElementById('search-text');
input.addEventListener("keyup", function() {
var searchTerm = this.value;
if (searchTerm.length === 0) {
listItem.forEach(function(el, i) {
el.className = el.className + " " + "hidden"
});
return false
}
substringMatcher(list, searchTerm, function(results) {
results.forEach(function(value, index) {
if (list.indexOf(value) > -1) {
elems[list.indexOf(value)].className = elems[list.indexOf(value)]
.className.replace("hidden", "");
} else {
listItem.forEach(function(el, i) {
if (i !== list.indexOf(value)) {
el.className = el.className + " " + "hidden";
}
})
}
})
})
});
.hidden {
display: none;
}
<input type="text" id="search-text" placeholder="search">
<ul id="list">
<li class="searchArray">Apple pie</li>
<li class="searchArray">Pumpkin pie</li>
<li class="searchArray">Banana-creme pie</li>
<li class="searchArray">Peach-blackberry cobbler</li>
<li class="searchArray">Chocolate-strawberry torte</li>
<li class="searchArray">Chocolate-zucchini cake</li>
<li class="searchArray">Anything involving chocolate and mint</li>
<li class="searchArray">Red-velvet cake</li>
<li class="searchArray">Anything involving fruits that aren't cherries</li>
</ul>

Related

sort list ul->li in function for different ids

I came from What is the easiest way to order a <UL>/<OL> in jQuery?
works fine.
But I need this on different ids, so I tried to put it in a function.
I tried:
var sortmenues = ['#firstid', '#secondid', '#anotherid'];
function sortuls(paths) {
jQuery.each(paths,function(index, value) {
var items = jQuery(value).get();
items.sort(function(a,b){
var keyA = jQuery(a).text();
var keyB = jQuery(b).text();
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
var ul = jQuery(value);
jQuery.each(items, function(i, li){
ul.append(li);
});
});
}
jQuery(function() {
sortuls(sortmenues);
});
But I doesn't work, just for the first element in array.
I tried to duplicate the snippet, but I just works on first element, too.
like:
var items = jQuery('#firstid li').get();
...
var ul = jQuery('#firstid');
...
});
var items = jQuery('#secondid li').get();
...
var ul = jQuery('#secondid');
...
});
also this doesn't work for all elements:
jQuery.each([ '#firstid', '#secondid' ], function( index, value ) {
var items = jQuery(value + ' li').get();
items.sort(function(a,b){
var keyA = jQuery(a).text();
var keyB = jQuery(b).text();
if (keyA < keyB) return -1;
if (keyA > keyB) return 1;
return 0;
});
var ul = jQuery(value);
jQuery.each(items, function(i, li){
ul.append(value + 'li');
});
});
Can somebody please tell me, what I am doing wrong?
the html is
...
<ul id="firstid">
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
<li>Delta</li>
</ul>
...
<ul id="anotheridNosortinghere">
<li>Alphaa</li>
<li>Betaa</li>
<li>Gammaa</li>
<li>Deltaa</li>
</ul>
...
<ul id="secondid">
<li>Alphaa</li>
<li>Betaa</li>
<li>Gammaa</li>
<li>Deltaa</li>
</ul>
...
Given your HTML you can just do a simple loop through the selectors for each ul your provide, sorting the child li elements by their text value as you go. Try this:
$('#firstid, #secondid').each(function() {
$(this).find('li').sort(function(a, b) {
return $(a).text() < $(b).text() ? -1 : 1;
}).appendTo(this);
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="firstid">
<li>Delta</li>
<li>Beta</li>
<li>Alpha</li>
<li>Gamma</li>
</ul>
<ul id="anotheridNosortinghere">
<li>Deltaa</li>
<li>Gammaa</li>
<li>Alphaa</li>
<li>Betaa</li>
</ul>
^ Not sorted
<ul id="secondid">
<li>Betaa</li>
<li>Alphaa</li>
<li>Deltaa</li>
<li>Gammaa</li>
</ul>

Sorting array by HTML data attribute?

I'm trying to learn local storage. I have five links each with a data-date attribute and I want them to be sorted using that attribute. I've tried numerous ways but nothing seems to work. From what I understand, I should parse before sorting but it didn't work for me. I must have done it wrong because I don't see how else to do it.
Here is my HTML:
<div id="div1">
<input id='clearHistory' type='button' value='Remove All History' />
<input id='showHistory' type='button' value='Show History' />
<ul id='history'>
<li>
<a class='date' href="#aa" data-date="November 12, 2001 03:24:00">Link 1</a>
</li>
<li>
<a class='date' href="#bb" data-date="August 4, 1993 03:24:00">Link 2</a>
</li>
<li>
<a class='date' href="#cc" data-date="October 17, 1995 03:24:00">Link 3</a>
</li>
<li>
<a class='date' href="#dd" data-date="December 1, 2010 03:24:00">Link 4</a>
</li>
<li>
<a class='date' href="#ee" data-date="August 17, 2004 03:24:00">Link 5</a>
</li>
</ul>
</div>
<p>Click on 'Show History' to see the 'user history'.</p>
<ul id='storedHistory'></ul>
And my JavaScript:
$(document).ready(function() {
var storedHistory = document.getElementById('storedHistory');
Storage.prototype.setObject = function(key, value) {
this.setItem(key, JSON.stringify(value));
};
Storage.prototype.getObject = function(key) {
var value = this.getItem(key);
return value && JSON.parse(value);
};
//function sortDatesAscending(a, b) { return a.valueOf() - b.valueOf(); } function sortDatesDescending(a, b) { return b.valueOf() - a.valueOf(); }
function sortLinkDatesAscending(obj1, obj2) {
return obj1.date.valueOf() - obj2.date.valueOf();
}
function sortLinkDatesDescending(obj1, obj2) {
return obj2.date.valueOf() - obj1.date.valueOf();
}
var history = {
items: []
};
// Get each anchor and retrieve its date and link. Add to an object and add that object to the history's array Sort by ascending. Add to local storage.
$('ul > li > a').click(function(e) {
var date = $(this).attr('data-date');
var listData = {
link: $(this).attr("href"),
date: date
};
history.items.push(listData);
window.localStorage.setObject("history", history);
});
/* Remove items from local storage */
$('#clearHistory').click(function() {
window.localStorage.clear();
});
/* Retrieve items from local storage and add to stored history unordered list */
$('#showHistory').click(function() {
console.log(window.localStorage);
var listHistory = localStorage.getObject('history');
var counter = 1;
for (var i = 0; i < listHistory.items.length; i++) {
$("#storedHistory").append('<li>' + counter + '. Link: ' + listHistory.items[i].link + '<br>' + 'Date: ' + listHistory.items[i].date + '</li>');
counter++;
}
});
});
And here is the jsfiddle: https://jsfiddle.net/fLLsfd5j/2/
Try this for sorting! (http://trentrichardson.com/2013/12/16/sort-dom-elements-jquery/)
var $history = $('#history li'),
$historyA = $history.children();
$historyA.sort(function (a, b) {
var an = Date.parse(a.getAttribute('data-date')).valueOf(),
bn = Date.parse(b.getAttribute('data-date')).valueOf();
if (an > bn) {
return 1;
}
if (an < bn) {
return -1;
}
return 0;
});
$('#history').empty();
$.each($historyA, function () {
$('#history').append($('<li>').html(this));
});
I guess this should do your job
function getHistory(){
var as = document.querySelectorAll(".date"); // get elements with date class
Array.prototype.map.call(as, e => e.cloneNode(true)) //clone them into an array and sort
.sort((p,c) => Date.parse(p.dataset.date)<=Date.parse(c.dataset.date) ? -1 : 1)
.forEach((e,i) => as[i].parentNode.replaceChild(e, as[i]));
}
showHistory.onclick = getHistory; //add "click" eL to the DOM element with showHistory id
http://jsbin.com/yoraqusora/2/edit?js,console,output

Jquery - Auto Increment ID's for Nested List

I have a nested list that resembles this:
<div id="treeview-left">
<ul>
<li>Country
<ul>
<li>Region
<ul>
<li>District
<ul>
<li>Group
<ul>
<li>People</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
This list is dynamically generated, and I need to auto increment ID's for each list item at each level.
Eg.
Country li's would have #Lv1-1 , #Lv1-2, #Lv1-3
Region li's would have #Lv2-1 , #Lv2-2, #Lv2-3
Each level needs to start with at 0 or 1, and increment the id based on it's index in that specific ul.
This is my current code, I am unable to even get the first level working.
<script>
$(function () {
$("#treeview-left > ul li").each(function () {
var TopPosition = $(this).index();
console.log(TopPosition);
$(this).id("Lvl1"+TopPosition);
});
});
</script>
Your help is appreciated.
Thanks
demo: https://so.lucafilosofi.com/jquery-auto-increment-ids-for-nested-list/
$(function () {
$("#treeview-left ul").each(function (i, item) {
var Tp = i + 1;
$(this).find('li').each(function (j, item) {
$(this).attr('id', "Lvl" + Tp + '-' + (j + 1));
});
});
});
here is a Recursive solution
function updateIds($node, index) {
if ($node.is('ul')) {
updateIds($node.children(), index);
} else {
$node.each(function (i, el) {
$(el).attr('id', 'Lv' + index + '-' + (i+1));
var $child = $node.children('ul');
if ($child.length > 0) {
updateIds($child, index+1);
}
});
}
}
demo: http://jsfiddle.net/3xpBw/1/
Another solution could be:Fiddle
<script type="text/javascript">
$(function () {
var count=0
$('ul').each(function(i, ul) {
count++
ul = $(ul);
var first=count
ul.children('li').each(function(i, li) {
li = $(li);
var second = first + '.' + (li.index() + 1);
li.prepend('<span>' + second + '</span>');
li.attr('id',second)
})
})
});
</script>

How to update counter and replace text with button click

I have a form where people can add more input with a button.
The javascript function clones the "origin-item", but I can't seem to get the function to correctly update the value in #id_nested-TOTAL_FORMS and __prefix__ does not get replaced with a counter-number, It just copies and adds __prefix__ instead of 1 or 2 and so on.
Anyone know what is wrong with the function?
The script was found here: https://github.com/rotaris/dynamicformdjango/blob/master/todo/templates/edit_items_in_list.html
<input id="id_nested-TOTAL_FORMS" name="nested-TOTAL_FORMS" type="hidden" value="1">
<input id="id_nested-INITIAL_FORMS" name="nested-INITIAL_FORMS" type="hidden" value="0">
<input id="id_nested-MAX_NUM_FORMS" name="nested-MAX_NUM_FORMS" type="hidden" value="1000">
<div id="origin-item" class="hide item">
<div id="div_id_nested-__prefix__-name">
<label for="id_nested-__prefix__-name">Name</label>
<div class="controls col-lg-10">
<input id="id_nested-__prefix__-name" name="nested-__prefix__-name" type="text" />
</div>
</div>
<p><a id="add-new-item" href="#">Add new person</a></p>
<script type="text/javascript">
var prefix = 'nested';
var MAX_FORMS = '1000';
var MIN_FORMS = 1;
/*
* Perform any enhancements you'd like to a given item here
* e.g. add datepicker widgets, popovers etc.
*/
function enhanceItem(item) {
$('.btn.delete', item).popover({
offset: 10
});
}
function enhanceItems() {
$('.item').each(function() {
enhanceItem(this);
});
}
/*
* The opposite of enhanceItem()
*/
function diminishItem(item) {
$('.btn.delete', item).unbind();
}
function diminishItems() {
$('.item').each(function() {
diminishItem(this);
});
}
$(document).ready(function() {
enhanceItems();
});
function getFormCount() {
return parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
}
function updateFormCount(newValue) {
$('#id_' + prefix + '-TOTAL_FORMS').val(newValue);
}
/*
* ====================================================
* General Notes: 'Form' and 'Item' are used somewhat interchangeably.
* ====================================================
*/
$.fn.clearForm = function() {
return this.each(function() {
var type = this.type, tag = this.tagName.toLowerCase();
if (tag == 'form')
return $(':input',this).clearForm();
if (type == 'text' || type == 'password' || tag == 'textarea')
this.value = '';
else if (type == 'checkbox' || type == 'radio')
this.checked = false;
else if (tag == 'select')
this.selectedIndex = -1;
});
};
/*
* Given an element, this function replaces a given string/regex
* with another specified replacement (new_value) within the element
*/
function updateElement(el, target_regex, replacement) {
var id_regex = target_regex;
if ($(el).attr("for")) {
$(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
}
if (el.id) {
el.id = el.id.replace(id_regex, replacement);
// Update the value of the hidden ID
// This hidden ID represents the ID of the model instance
var hidden_ID_patt = new RegExp('id_(' + prefix + '-\\d+)-id');
// Only update if an ID exists (i.e. if a corresponding model instance exists)
if (hidden_ID_patt.test(el.id)) {
$(el).val(new_value + 1);
}
}
if (el.name) {
el.name = el.name.replace(id_regex, replacement);
}
}
/*
* Given an element, this function replaces (the first?) occurence of a number
* that follows a specific prefix (e.g. 'exampleprefix-n')
* with another specified number (new_value) within the element
* where n is a number
*/
function updateElementIndex(el, prefix, new_value) {
var id_regex = new RegExp('(' + prefix + '-\\d+-)');
var replacement = prefix + '-' + new_value + '-';
updateElement(this, id_regex, replacement);
}
function reapplyEnhancements() {
// Apply some fresh enhancements
diminishItems();
enhanceItems();
}
/*
* btn = the button (or link or some source object) that instigated this action
* prefix = the prefix used in the formset (?)
*/
function addItem(btn, prefix) {
var formCount = getFormCount();
// You might like/want to do some validation before proceeding in this function
// i.e. before adding an item
// In this case, I'm limiting it to max number of forms
if (formCount < MAX_FORMS) {
// Clone a item (without event handlers) from the first item
//var item = $(".item:first").clone(false).get(0);
// Clone the origin item
var item = $("#origin-item").clone(false).get(0);
$(item).removeAttr("id");
$(item).removeClass("hide");
// Clear its values
$(':input', item).clearForm();
// Insert it after the last item
$(item).removeAttr('id').hide().insertAfter("form .item:last").slideDown(300);
$(':input, label', item).each(function() {
// Relabel/rename all the relevant bits
// '__prefix__' comes from #origin-item
// see 'empty_form': https://docs.djangoproject.com/en/1.4/topics/forms/formsets/#empty-form
var target_regex = new RegExp('(' + prefix + '-__prefix__-)');
var replacement = prefix + '-' + formCount + '-';
updateElement(this, target_regex, replacement);
// Remove error classes
$(this).removeClass("error");
});
reapplyEnhancements();
// Update the total form count (in the management form)
updateFormCount(formCount + 1);
}
else {
// Feel free to notify the user using some other technique instead of an JS alert
alert("Sorry, you can only have a maximum of " + MAX_FORMS + " goals.");
}
}
/*
* Relabel all items
*/
function relabelItems() {
var forms = $('.item'); // Get all the forms
// Update the total number of forms (likely 1 less than before)
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
var i = 0;
// Go through the forms and set their indices, names and IDs
for (formCount = forms.length; i < formCount; i++) {
$(":input, label", forms.get(i)).each(function() {
updateElementIndex(this, prefix, i);
});
}
}
/*
* Removes an item from a list of items
*/
function removeItem(btn, prefix) {
var formCount = getFormCount();
// Do some validation before proceeding
// In this case, just make sure there is at least one item
if (formCount > MIN_FORMS) {
var item = $(btn).parents('.item');
// Delete the item
$("*", item).fadeOut();
$(item).animate({'backgroundColor':'#fb6c6c'}, 300).slideUp('slow', function() {
$(item).remove();
relabelItems();
});
// Apply enhancements
enhanceItems();
}
else {
// Feel free to notify the user using some other technique instead of an JS alert
alert("Come on now, you need to have at least a minimum of " + MIN_FORMS + " item(s).");
}
}
// JavaScript to create a new items/entries
$(document).ready(function() {
$('#add-new-item').click(function(e) {
addItem(this, prefix);
return false;
});
$('a.delete').live("click", function(e) {
removeItem(this, prefix);
return false;
});
});
</script>
The bugs in this code seem to be caused by the variable named prefix being used for 2 different tasks.
Here it is used like this
$('#id_' + prefix + '-TOTAL_FORMS').val(newValue);
where I assume prefix is assigned '_prefix_', but later it is used like this
var target_regex = new RegExp('(' + prefix + '-__prefix__-)');
where I assume prefix is assigned 'id'
If you where to sort out the variable prefix to only contain one type of piece of information, by have 2 variables with different names.
I'm pretty sure everything would fall into place.

Enable exact multi word search for node titles with the jstree search plugin

I use the jstree search plugin (documentation) to search for multiple IDs in the title fields of my HTML tree.
I took the original jstree code (line 3398) and changed it as suggested here to enable multi word searching in the title fields of the tree.
It works fine for "title contains" queries (e.g. title ID:40857332 of node x and title ID:408 of node y contain the search word ID:408) but I'm at loss how to change the code in order to find only exact matches (e.g. title ID:408 of node y matches the search word ID:408 exactly).
The function gets called like this:
$("#my_html_tree").jstree("search", search_words);
with the following configurations:
"search" : {"case_insensitive" : true, "search_method": "jstree_title_contains_multi"}
The variable "search_words" is a string containing several IDs:
var search_words = "ID:4 ID:7 ID:3188";
The format of the HTML tree nodes:
<li id="3188"> <a title="ID:3188">Tree node 3188</a></li>
This is my changed code:
$.expr[':'].jstree_title_contains_multi = function(a,i,m){
var word, words = [];
var searchFor = m[3].toLowerCase().replace(/^\s+/g,'').replace(/\s+$/g,'');
if(searchFor.indexOf(' ') >= 0) {
words = searchFor.split(' ');
}
else {
words = [searchFor];
}
for (var i=0; i < words.length; i++) {
word = words[i];
if((a.getAttribute("title") || "").toLowerCase().indexOf(word) >= 0) {
return true;
}
}
return false;
};
How must I change the code in order to enable searching only for exact matches?
Any help would be greatly appreciated.
I found the following solution:
Instead of using
if((a.getAttribute("title") || "").toLowerCase().indexOf(word) >= 0)
I use
if((a.getAttribute("title") || "").toLowerCase() === word )
It's cleaner to use the id attribute and not the title. You can even use both.
Configuration:
"search" : {"case_insensitive" : true,
"search_method": "jstree_codigo_descripcion"}
Search call
var codigo = "0102";
var descripcion = "sdf";
jQuery("#arbol").jstree("search",cod +"-"+ desc);
Tree
<div id="arbol">
<ul>
<li><a>01</a>
<ul>
<li><a>0101</a>
<ul>
<li><a id="010101" title="foo">010101 foo</a></li>
<li><a id="010102" title="bar">010102 bar</a></li>
</ul>
</li>
<li><a>0102</a>
<ul>
<li><a id="010201" title="asdf">010201 asdf</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
The new search function
$.expr[':'].jstree_codigo_descripcion = function(a, i, m) {
var searchFor = m[3].toLowerCase();
var params = searchFor.split('-');
var codigo = params[0];
var descripcion = params[1];
// attribute id start with codigo
// attribute title contains descripcion
if (codigo.length > 0 && descripcion.length === 0) {
if ((a.getAttribute("id") || "").toLowerCase().indexOf(codigo) === 0) {
return true;
}
}
if (codigo.length === 0 && descripcion.length > 0) {
if ((a.getAttribute("title") || "").toLowerCase().indexOf(descripcion) >= 0) {
return true;
}
}
if (codigo.length > 0 && descripcion.length > 0) {
if ((a.getAttribute("id") || "").toLowerCase().indexOf(codigo) === 0 &&
(a.getAttribute("title") || "").toLowerCase().indexOf(descripcion) >= 0) {
return true;
}
}
return false;
};

Categories

Resources