Re-arrange list items based on alphabet? - javascript

I'm wondering if this is possible. I have some code below:
<p class="page_classList">
<strong>Crops:</strong>
<br>
<span class="cropElem">Berries - Blueberries</span>
<span class="cropElem">Peas</span>
<span class="cropElem">Squash</span>
<span class="cropElem">Okra</span>
</p>
Is there any way to have these spans display themselves in alphabetical order (by crop). This list is displayed when a user clicks on a farm page that is built in wordpress. Can a script rearrange these as the page loads?
Any help would be great!

You loop through and find all the crop elements, delete the element and save the crop name into a list. Sort the list then loop through the list and re-add each element to the <p> this time they will be alphabetical since the list is sorted alphabetically using .sort()
Fiddle: https://jsfiddle.net/d7wug6d8/
var spans = $(".page_classList").find(".cropElem");
var cropNames = [];
spans.each(function(index) {
$(spans[index]).remove();
cropNames.push($(spans[index]).html());
});
cropNames.sort();
$(cropNames).each(function(index) {
$(".page_classList").append('<span class="cropElem">' + cropNames[index] + '</span>\n');
});
This gives the final HTML:
<p class="page_classList">
<strong>Crops:</strong>
<br>
<span class="cropElem">Berries - Blueberries</span>
<span class="cropElem">Okra</span>
<span class="cropElem">Peas</span>
<span class="cropElem">Squash</span>
</p>
For the new HTML you asked for: Fiddle: https://jsfiddle.net/d7wug6d8/1/
var foodItems = $(".page_classList").html().split(",");
foodItems.sort();
$(".page_classList").html(foodItems.join(", "));

In case you already have events set up on your elements, you might not want to recreate them, but rather reorder them. Here is how I would do it:
var container = $('.page_classList'),
data = $('.cropElem');
data.sort(function(a,b){
if($(a).html() > $(b).html()) return 1;
if($(a).html() < $(b).html()) return -1;
return 0;
});
for(var i=0; i<data.length; i++) {
container.append(data[i]);
}
.cropElem{ display: block; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="page_classList">
<strong>Crops:</strong>
<br>
<span class="cropElem">Berries - Blueberries</span>
<span class="cropElem">Peas</span>
<span class="cropElem">Squash</span>
<span class="cropElem">Okra</span>
</p>
Why use append?
When you do append of an element, it does not duplicate it, it moves it. This allows you to reorder your elements.

That is basically what you would do I dont feel like finishing the code though. Then you could loop through the cropArray and output it in between the html span elements.
var cropArray = ["Berries - Blueberries", "Peas", "Squash", "Okra"];
cropArray.sort();

Or simply:
Grab a collection of spans, sort them, and re-append with newly sorted collection.
var els = $('.cropElem');
els.sort(function (a, b) {
a = $(a).text();
b = $(b).text();
// compare
if(a > b) {
return 1;
} else if (a < b) {
return -1;
}
return 0;
});
$('.page_classList').append(els);
span { display: block; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p class="page_classList">
<strong>Crops:</strong>
<br>
<span class="cropElem">Berries - Blueberries</span>
<span class="cropElem">Peas</span>
<span class="cropElem">Squash</span>
<span class="cropElem">Okra</span>
</p>

Related

Creating step numbers dynamically in jquery

I hate manually typing steps numbers. So I was trying to write a small function to find some text and replace it with generated step numbers.
And I can't use the ol/li tags because I have multiple groups on the page. So I need to add an "a", "b", etc after the number.
My HTML:
<span class="grouping" v="a">
----My first step
----This is another
----And another
</span>
<br/>
<span class="grouping" v="b">
----second group
----second group 2
</span>
This is my jquery (but it doesn't replace the ---- to a step number).
$(function(){
$(".grouping").each(function(){
var val=$(this).attr("v");
var counter=1;
$(this).find(":contains('----')").each(function(){
$(this).text("("+counter+val+") ");
counter++;
});
});
});
So eventually, I want the webpage to finish like this:
(1a) My first step
(2a) This is another
(3a) And another
(1b) second group
(2b) second group 2
For each of the groupings, get the inner html and split it by newline
If it starts with '----', replace it with an incrementing line number, and append the v value.
Put the html back into the grouping.
$('.grouping').each(function(index, grouping){
var lines = grouping.innerHTML.trim().split("\n");
var lineNumber = 0;
var v = grouping.getAttribute('v');
lines.forEach(function(line, index){
if (line.startsWith('----')) {
lines[index] = '('+ (++lineNumber) + v +') '+ line.slice(4);
}
});
grouping.innerHTML = lines.join('\n');
});
.grouping { white-space: pre; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="grouping" v="a">
----My first step
----This is another
I should not have a line number.
----And another
</span>
<br/>
<span class="grouping" v="b">
I also should not have a line number.
----second group
----second group 2
</span>
You can use split to split the text at '----' and concat with the values (added brs for lisibility so I used html instead of text):
$(function(){
$(".grouping").each(function(){
var val=$(this).attr("v");
var arr = $(this).html().split('----');
if(arr.length > 1){
var str = arr[0], i, l = arr.length;
for(i = 1; i < l; i++){
str += '(' + i + val + ') ' + arr[i];
}
$(this).html(str);
}
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="grouping" v="a">
----My first step<br>
----This is another<br>
----And another<br>
</span>
<br/>
<span class="grouping" v="b">
----second group<br>
----second group 2<br>
</span>
.find() will not work. You should get text of the element and split() it and then change it using map() and replace() and reset text()
$(function(){
$(".grouping").each(function(){
var val=$(this).attr("v");
var counter=1;
let lines = $(this).text().split('\n');
lines = lines.map(ln => {
if(ln.includes('----')){
ln = ln.replace('----',`(${counter}${val})`)
counter++;
}
return ln;
})
lines = lines.filter(ln => ln !== '');
$(this).text(lines.join('\n'));
});
});
.grouping { white-space: pre; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="grouping" v="a">
----My first step
----This is another
----And another
</span>
<br/>
<span class="grouping" v="b">
----second group
----second group 2
</span>
First, I suggest wraping those groups into some kind of tag. for example, span:
<span class="grouping" v="a">
<span class="grouping-item">My first step</span>
</span>
And so on, it will be easier and faster to target those elements.
Then create one function to search through those new tags
$(function(){
// This will create those numbers
function createNumbers(el) {
const mainGroup = el.attr("v");
const children = el.children(".grouping-item");
let i = 1;
children.each(function(){
const currentText = $(this).text();
$(this).text( '('+i+mainGroup+')' + currentText );
i++;
});
}
$(".grouping").each(function(){
createNumbers($(this));
});
});

Delete Specific <li> elements using JavaScript

The function does not seem to delete the Node containing the specified value unless it is first value (in this case 'apples'). The for loop also has to execute twice before deletion of any kind. Why is that so?
function removeSpec()
{
var query = document.getElementById('spec').value; /* Value inputted by user */
elements = document.getElementsByClassName('fruit'); /* Get the li elements in the list */
var myList = document.getElementById("myList3"); /* Var to reference the list */
var length = (document.getElementsByClassName('fruit').length); /* # of li elements */
var checker = 'false'; /* boolean-ish value to determine if value was found */
for(var counter = 0; counter < length; counter ++)
{
if (elements[counter].textContent == query )
{
alert("Counter : " + counter);
myList.removeChild(myList.childNodes[ (counter) ]);
checker="true";
}
}
if ( checker == "false")
{
alert("Not Found");
}
}
The corresponding HTML:
<ul id="myList3">
<li class="fruit" >Apples</li>
<li class="fruit" >Oranges</li>
<li class="fruit" >Banannas</li>
<li class="fruit">Strawberry</li>
</ul>
<form>
Value: <input type="text" name="" value="" id="spec">
<br><br>
</form>
<button type="button" style="height:20px;width:200px" href="javascript:void(0)" onclick="removeSpec()" >
Remove Specified
</button>
childNodes returns a list of all child nodes. That includes text nodes. Between every <li> element you have a text node that contains spaces and a line break. So, childNodes returns a list of 9 nodes, but you are assuming list of 4 nodes (document.getElementsByClassName('fruit').length).
You could use .children instead of .childNodes. .children returns a list of only element nodes. Or better yet, use elements, since that's what you are iterating over.
You also need to stop iterating after you found an removed a node, otherwise you will be trying to access a position that doesn't exist anymore.
function removeSpec()
{
var query = document.getElementById('spec').value; /* Value inputted by user */
elements = document.getElementsByClassName('fruit'); /* Get the li elements in the list */
var myList = document.getElementById("myList3"); /* Var to reference the list */
var length = (document.getElementsByClassName('fruit').length); /* # of li elements */
var checker = 'false'; /* boolean-ish value to determine if value was found */
for(var counter = 0; counter < length; counter ++)
{
if (elements[counter].textContent == query )
{
myList.removeChild(myList.children[ (counter) ]);
// better: myList.removeChild(elements[counter]);
checker="true";
break;
}
}
if ( checker == "false")
{
alert("Not Found");
}
}
<ul id="myList3">
<li class="fruit" >Apples</li>
<li class="fruit" >Oranges</li>
<li class="fruit" >Banannas</li>
<li class="fruit">Strawberry</li>
</ul>
<form>
Value: <input type="text" name="" value="" id="spec">
<br><br>
</form>
<button type="button" style="height:20px;width:200px" href="javascript:void(0)" onclick="removeSpec()" >
Remove Specified
</button>
There are other things that could be improved (e.g. why not assign an actual boolean value to checker?), but they are not related to your question.
I run this code. you should add this line
elements[counter].remove();
instead of this line
myList.removeChild(myList.childNodes[ (counter) ]);
Instead of for loop you can consider of doing it the below way.
check this snippet
function removeSpec() {
var query = document.getElementById('spec').value; /* Value inputted by user */
var elements = document.getElementsByClassName('fruit'); /* Get the li elements in the list */
var myList = document.getElementById("myList3"); /* Var to reference the list */
var length = (document.getElementsByClassName('fruit').length); /* # of li elements */
var checker = 'false'; /* boolean-ish value to determine if value was found */
myList.querySelectorAll('li').forEach(function(item) {
if (item.innerHTML == query)
item.remove();
});
}
<ul id="myList3">
<li class="fruit">Apples</li>
<li class="fruit">Oranges</li>
<li class="fruit">Banannas</li>
<li class="fruit">Strawberry</li>
</ul>
<form>
Value:
<input type="text" name="" value="" id="spec">
<br>
<br>
</form>
<button type="button" style="height:20px;width:200px" href="javascript:void(0)" onclick="removeSpec()">
Remove Specified
</button>
Hope it helps
This might sound crazy, but Chrome seems to parse your HTML unordered list into the following:
NodeList[9]
0: text
1: li.fruit
2: text
3: li.fruit
4: text
5: li.fruit
6: text
7: li.fruit
8: text
length: 9
__proto__: NodeList
Essentially, it appears to be creating a text node in your unordered list for each newline inside the tag. This also explains why deletion only occurs after you call the function a second time - it deletes the text node first, then it deletes the actual element on its second try.
Simple converting your HTML to the following form solves the problem (but is not very pretty):
<ul id="myList3"><li class="fruit">Apples</li><li class="fruit">Oranges</li><li class="fruit">Banannas</li><li class="fruit">Strawberry</li></ul>
There are some workarounds that you can try using. For example, you could try using the childNode.remove() method instead, though not all browsers support this.
Alternatively, something like this might also work:
selectedChildNode.parentNode.removeChild(selectedChildNode);
here the problem is in myList.removeChild(myList.childNodes[ (counter) ]); because myList.childNodes node return 8 values instead of 4. We have elements array with 4 nodes, hence the removing from elements array yields a proper result
Try the code snippet below,
function removeSpec() {
var query = document.getElementById('spec').value;
elements = document.getElementsByClassName('fruit');
var myList = document.getElementById("myList3");
var length = elements.length;
var checker = 'false';
for(var counter = 0; counter < length; counter ++)
{
if (elements[counter].textContent == query )
{
alert("Counter : " + counter);
myList.removeChild(elements[counter]);
checker="true";
}
}
if ( checker == "false")
{
alert("Not Found");
}
}
myList is an array of li element so removeChild on myList is logically not correct.
Also, myList.childNodes doesn't make sense here.
Try
myList[counter].parentNode.removeChild(myList[counter]);

Fast filter in a list of records with JavaScript

I have a list with about 10 000 customers on a web page and need to be able to search within this list for matching input. It works with some delay and I'm looking for the ways how to improve performance. Here is simplified example of HTML and JavaScript I use:
<input id="filter" type="text" />
<input id="search" type="button" value="Search" />
<div id="customers">
<div class='customer-wrapper'>
<div class='customer-info'>
...
</div>
</div>
...
</div>
<script type="text/javascript">
$(document).ready(function() {
$("#search").on("click", function() {
var filter = $("#filter").val().trim().toLowerCase();
FilterCustomers(filter);
});
});
function FilterCustomers(filter) {
if (filter == "") {
$(".customer-wrapper").show();
return;
}
$(".customer-info").each(function() {
if ($(this).html().toLowerCase().indexOf(filter) >= 0) {
$(this).parent().show();
} else {
$(this).parent().hide();
}
});
}
</script>
The problem is that when I click on Search button, there is a quite long delay until I get list with matched results. Are there some better ways to filter list?
1) DOM manipulation is usually slow, especially when you're appending new elements. Put all your html into a variable and append it, that results in one DOM operation and is much faster than do it for each element
function LoadCustomers() {
var count = 10000;
var customerHtml = "";
for (var i = 0; i < count; i++) {
var name = GetRandomName() + " " + GetRandomName();
customerHtml += "<div class='customer-info'>" + name + "</div>";
}
$("#customers").append(customerHtml);
}
2) jQuery.each() is slow, use for loop instead
function FilterCustomers(filter) {
var customers = $('.customer-info').get();
var length = customers.length;
var customer = null;
var i = 0;
var applyFilter = false;
if (filter.length > 0) {
applyFilter = true;
}
for (i; i < length; i++) {
customer = customers[i];
if (applyFilter && customer.innerHTML.toLowerCase().indexOf(filter) < 0) {
$(customer).addClass('hidden');
} else {
$(customer).removeClass('hidden');
}
}
}
Example: http://jsfiddle.net/29ubpjgk/
Thanks to all your answers and comments, I've come at least to solution with satisfied results of performance. I've cleaned up redundant wrappers and made grouped showing/hiding of elements in a list instead of doing separately for each element. Here is how filtering looks now:
function FilterCustomers(filter) {
if (filter == "") {
$(".customer-info").show();
} else {
$(".customer-info").hide();
$(".customer-info").removeClass("visible");
$(".customer-info").each(function() {
if ($(this).html().toLowerCase().indexOf(filter) >= 0) {
$(this).addClass("visible");
}
});
$(".customer-info.visible").show();
}
}
And an test example http://jsfiddle.net/vtds899r/
The problem is that you are iterating the records, and having 10000 it can be very slow, so my suggestion is to change slightly the structure, so you won't have to iterate:
Define all the css features of the list on customer-wrapper
class and make it the parent div of all the list elements.
When your ajax request add an element, create a variable containing the name replacing spaces for underscores, let's call it underscore_name.
Add the name to the list as:
var customerHtml = "<div id='"+underscore_name+'>" + name + "</div>";
Each element of the list will have an unique id that will be "almost" the same as the name, and all the elements of the list will be on the same level under customer-wrapper class.
For the search you can take the user input replace spaces for underscores and put in in a variable, for example searchable_id, and using Jquery:
$('#'+searchable_id).siblings().hide();
siblings will hide the other elements on the same level as searchable_id.
The only problem that it could have is if there is a case of two or more repeated names, because it will try to create two or more divs with the same id.
You can check a simple implementation on http://jsfiddle.net/mqpsppxm/
​

jQuery script for finding elements by typing and organize them

I would like to search by any term (name, user, from, price), and display the div into top and hide the ones who doesn't have the typed value.
Here's the jsfiddle: http://jsfiddle.net/Sc9ys/10/
I would like to have the same result as the jquery mobile table filter http://demos.jquerymobile.com/1.4.0/filterable/
Where you can search for any term.
I know that for search for any term I should use $(list).find("li *:)... but I can't figure out how to display the items properly. If you test my jsfiddle it doesn't work very well.
Edit: As asked by the user below, here's some more info.
<ul id='list'>
<li>
<div class='row'>
<div class='middle'>
<ul>
<li><h3>Stackoverflow</h3></li>
<li><span>User</span></li>
<li><span>London</span></li>
</ul>
</div>
<div style='clear: both'></div>
</div>
</li>
</ul>
$("#search").change( function () {
$(list).find("li *:not(:Contains(" + filter + "))").parent().hide();
});
DEMO
The idea is in
$("#ul_container").find("li").filter(function () {//your comparing logic here });
Here, try this out. Honesty I couldn't read thru your code, so I made this example. I added the sub items (spans that contain data to be searched) in an array datalist by their class name.
Generic Search Function.
HTML
<input type="text" id="search" />
<ul id="ul_container">
<li class="listItem">
<span class="car">Honda</span>
<span class="country">Japan</span>
</li>
<li class="listItem">
<span class="car">BMW</span>
<span class="country">Germany</span>
</li>
</ul>
Script:
//Capture user input
$("#search").on("keyup change", function () {
var str = $.trim($(this).val());
if (str) {
search(str);
} else {
// if no input, then show all
$(".listItem").show();
}
});
//the search part.
var datalist = ["car", "country"];
function search(toFind) {
//select all li and loop thru them one by one
$("#ul_container").find("li").filter(function () {
var $li = $(this);//hold current li in a variable
//loop thru all sub spans by their class and check if the toFind keyword is there
// you modify this step, i use it to specify which sub span to be searched. Sometimes I don't want all field to be searched, only the ones I select.
for (var i = 0; i < datalist.length; i++) {
//hold the span in a var called $item
var $item = $li.children("." + datalist[i]);
var content_str = $item.html();//get the actual string
//the comparing code
if (content_str.toLowerCase().indexOf(toFind.toLowerCase()) >= 0) {
$li.show();
break;
} else {
$li.hide();
}
}
});
}
Solved guys. Thank you all.
You can see the following example working at: http://jsfiddle.net/Sc9ys/29/
$('#search').on('keyup change', function(){
var str = $.trim($(this).val());
if (str) {
search(str, $("#list"));
} else {
$("#list").find('li').show();
/* The <li> are display: none, to show them again if the input type is clear,
we must find those <li> and show them. Showing only the #list isn't enough. */
}
});
function search(toFind, list){
$(list).find('li').filter(function() {
$li = $(this);
$li.find(".middle :contains(" + toFind +")").parent().parent().slideDown();
$li.find(".middle").not(":contains(" + toFind + ")").parent().parent().slideUp();
});
}
/* Function to search with the input lowercase */
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
return function( elem ) {
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
};
});
Edit: Made some adjustments according to the help of user #Joraid.

How to add array items to <li> Jquery

I'm working on something really simple, a short quiz, and I am trying to make the items I have listed in a 2-d array each display as a <li>. I tried using the JS array.join() method but it didn't really do what I wanted. I'd like to place them into a list, and then add a radio button for each one.
I have taken the tiny little leap to Jquery, so alot of this is my unfamiliarity with the "syntax". I skimmed over something on their API, $.each...? I'm sure this works like the for statement, I just can't get it to work without crashing everything I've got.
Here's the HTML pretty interesting stuff.
<div id="main_">
<div class="facts_div">
<ul>
</ul>
</div>
<form>
<input id="x" type="button" class="myBtn" value="Press Me">
</form>
</div>
And, here is some extremely complex code. Hold on to your hats...
$(document).ready (function () {
var array = [["Fee","Fi","Fo"],
["La","Dee","Da"]];
var q = ["<li>Fee-ing?","La-ing?</li>"];
var counter = 0;
$('.myBtn').on('click', function () {
$('#main_ .facts_div').text(q[counter]);
$('.facts_div ul').append('<input type= "radio">'
+ array[counter]);
counter++;
if (counter > q.length) {
$('#main_ .facts_div').text('You are done with the quiz.');
$('.myBtn').hide();
}
});
});
Try
<div id="main_">
<div class="facts_div"> <span class="question"></span>
<ul></ul>
</div>
<form>
<input id="x" type="button" class="myBtn" value="Press Me" />
</form>
</div>
and
jQuery(function ($) {
//
var array = [
["Fee", "Fi", "Fo"],
["La", "Dee", "Da"]
];
var q = ["Fee-ing?", "La-ing?"];
var counter = 0;
//cache all the possible values since they are requested multiple times
var $facts = $('#main_ .facts_div'),
$question = $facts.find('.question'),
$ul = $facts.find('ul'),
$btn = $('.myBtn');
$btn.on('click', function () {
//display the question details only of it is available
if (counter < q.length) {
$question.text(q[counter]);
//create a single string containing all the anwers for the given question - look at the documentation for jQuery.map for details
var ansstring = $.map(array[counter], function (value) {
return '<li><input type="radio" name="ans"/>' + value + '</li>'
}).join('');
$ul.html(ansstring);
counter++;
} else {
$facts.text('You are done with the quiz.');
$(this).hide();
}
});
//
});
Demo: Fiddle
You can use $.each to iterate over array[counter] and create li elements for your options:
var list = $('.facts_div ul');
$.each(array[counter], function() {
$('<li></li>').html('<input type="radio" /> ' + this).appendTo(list);
}
The first parameter is your array and the second one is an anonymous function to do your action, in which this will hold the current element value.
Also, if you do this:
$('#main_ .facts_div').text(q[counter]);
You will be replacing the contents of your element with q[counter], losing your ul tag inside it. In this case, you could use the prepend method instead of text to add this text to the start of your tag, or create a new element just for holding this piece of text.

Categories

Resources