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]);
Related
I'm trying to make a small script that allows for a little notes section. This section would have an input box that allows for adding elements to the list; which will be saved in localStorage so they are not lost when I refresh or close the browser. The code I have is as follows (it's all done through JS even the html, but ignore that.)
var notes = [];
var listthings = "<h2 id=\"titlething\">Notes</h2>" +
"<ul id=\"listing\">" +
"</ul>"
"<input type=\"text\" name=\"item\" id=\"textfield\">" +
"<input type=\"submit\" id=\"submitthing\" value=\"Submit\">";
JSON.parse(localStorage.getItem('notes')) || [].forEach( function (note) {
"<li id=\"listitem\">" + notes + "</li>";
})
$('#submitthing').click(function() {
notes.push($('#textfield').val());
});
localStorage.setItem('notes', JSON.stringify(notes));
Also, how would I go about appending the latest added li between the opening and closing tag? Obviously I'd usually do it using jQuery, but this is puzzling me a little. However, only the 'Notes' loads at the top, any ideas?
Your approach is way off the mark. You don't need JSON at all (this just confuses things) and you don't need to manually create HTML.
Also, you can use an array to store the notes, but since localStorage is the storage area, so an array is redundant. Additionally, without using an array, you don't need JSON. The entire problem becomes much easier to solve.
Unfortunately, the following won't run here in this snippet editor, due to security issues, but it would do what you are asking. This fiddle shows it working: https://jsfiddle.net/Lqjwbn1r/14/
// Upon the page being ready:
window.addEventListener("DOMContentLoaded", function(){
// Get a reference to the empty <ul> element on the page
var list = document.getElementById("notes");
// Loop through localStorage
for (var i = 0; i < localStorage.length; i++){
// Make sure that we only read the notes from local storage
if(localStorage.key(i).indexOf("note") !== -1){
// For each item, create a new <li> element
var item = document.createElement("li");
// Populate the <li> with the contents of the current
// localStorage item's value
item.textContent = localStorage.getItem(localStorage.key(i));
// Append the <li> to the page's <ul>
list.appendChild(item);
}
}
// Get references to the button and input
var btn = document.getElementById("btnSave");
var note = document.getElementById("txtNote");
// Store a note count:
var noteCount = 1;
// When the button is clicked...
btn.addEventListener("click", function(){
// Get the value of the input
var noteVal = note.value;
// As long as the value isn't an empty string...
if(noteVal.trim() !== ""){
// Create the note in localStorage using the
// note counter so that each stored item gets
// a unique key
localStorage.setItem("note" + noteCount, noteVal);
// Create a new <li>
var lstItem = document.createElement("li");
// Set the content of the <li>
lstItem.textContent = noteVal;
// Append the <li> to the <ul>
list.appendChild(lstItem);
// Bump up the note counter
noteCount++;
}
});
});
<input type=text id=txtNote><input type=button value=Save id=btnSave>
<ul id=notes></ul>
This is how I would approach it using jquery. but depens how complex this should be. this is just simple demo.
<input type="text" id="note" />
<button id="add">add note</button>
<ul id="notes"></ul>
javascript and jquery
function addNote(){
var data = localStorage.getItem("notes")
var notes = null;
if(data != null)
{
notes = JSON.parse(data);
}
if(notes == null){
notes = [];
}
notes.push($("#note").val());
localStorage.setItem("notes", JSON.stringify(notes));
refreshNotes();
}
function refreshNotes(){
var notesElement =$("#notes");
notesElement.empty();
var notes = JSON.parse(localStorage.getItem("notes"));
for(var i = 0; i< notes.length; i++){
var note = notes[i];
notesElement.append("<li>"+note+"</li>");
}
}
$(function(){
refreshNotes();
$("#add").click(function(){
addNote();
});
})
example:
http://codepen.io/xszaboj/pen/dOXEey?editors=1010
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/
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.
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.
My problem is that the DELETE ITEM (Item which has been added dynamically) is not deleteing by click in IE6.
javascript:
var TDCount = 3;
var i=0;
function insertTD(){
var possition=document.getElementById('elmnt_pos').value;
if(possition=="")
{
possition='a';
alert('Enter a number!!!');
}
if(isNaN(possition))
{
alert('Enter a number!!!');
document.getElementById('elmnt_pos').value='';
}else{
var newTD = document.createElement("li");
var newid='li'+TDCount++;
newTD.setAttribute("id", newid);
newTD.setAttribute("onclick","javascript:removenode(this);" );
var newText = document.createTextNode(i+"New fruit " + (possition++));
newTD.appendChild(newText);
var trElm = document.getElementById("menu");
var refTD = trElm.getElementsByTagName("li").item(possition-2);
trElm.insertBefore(newTD,refTD);
i++;
}
}
function removenode(th)
{
answer = confirm("Do you really want to Remove Element "+th.id + " ? ")
if (answer !=0)
{
document.getElementById('menu').removeChild(document.getElementById(th.id));
}
}
html
<ul id="menu">
<li id="li0" onclick="javascript:removenode(this);">apple</li>
<li id="li1" onclick="javascript:removenode(this);">Banana</li>
<li id="li2" onclick="javascript:removenode(this);">Jackfruit</li>
</ul>
<form name="justfrm">
<input type="text" value="Enter the position" name="pos1" id="elmnt_pos" />
<input type="button" value="click" onclick="javascript:insertTD()"/>
</form>
"Enter the position" means add element on a specific position like 2,3,5 etc.
We can Delete Item by click on item .
I don't have an instance on Internet Explorer 6 to test with, but more than likely it's this line, which is causing the problem:
newTD.setAttribute("onclick","javascript:removenode(this);" );
It does not work in Internet Explorer 6. You will need to do something like:
newTD.onclick = function() { removeNode(this); };
or
newTD.onclick = new Function("removenode(this)");
See this article for more information. Also, as a side note you may want to look into using a library like jQuery, which already handles these types of cross-browser issues.