Recursively Scan DOM Elements - Javascript - javascript

I have the following html -
<a>
<b>
....
.....
<input type="button" name="add" onclick="..." value="add another"/>
</d>
</b>
....
</a>
And I use the following js snippets-
/**
* Dynamically add a remove button on next to the add button.
*
*/
addRemoveButton = function(node) {
if(node.nodeType == 3) {
if(node.nodeName == "input") {
if(node.getAttribute("type") == "button") {
if(node.getAttribute("name") == "add") {
var removeButton = node.cloneNode(true);
removeButton.removeAttribute("name");
removeButton.setAttribute("value", "remove");
removeButton.setAttribute("onclick", "");
removeButton.setAttribute("id", "");
(node.parentNode).appendChild(removeButton);
return;
}
}
}
}
if(node.nodeType == 1) {
var list = node.childNodes;
var i = 0;
while(i<list.length) {
return addRemoveButton(list[i]);
i++;
}
}
return;
}
Now I want to add a input of type button, (remove button), next to the current button shown in the above listing. I tried to do this recursively. But this is not working. Can you find the problem in the above code?

As far as I can tell, your code was pretty far off. You were using the wrong nodeType and had the wrong case on nodeName and there was no reason for the vastly nested if statements. But, you can make it work recursively like this:
addRemoveButton = function(node) {
if (node.nodeType == 1) {
if (node.nodeName.toLowerCase() == "input" &&
node.getAttribute("type") == "button" &&
node.getAttribute("name") == "add") {
var removeButton = node.cloneNode(true);
removeButton.removeAttribute("name");
removeButton.setAttribute("value", "remove");
removeButton.setAttribute("onclick", "");
removeButton.setAttribute("id", "");
(node.parentNode).appendChild(removeButton);
return;
} else {
var list = node.childNodes;
for (var i=0; i < list.length; i++) {
// be aware of childNodes changing on us live here
// when we modify the DOM
addRemoveButton(list[i]);
}
}
}
}
addRemoveButton(document.body);
You can see it work here: http://jsfiddle.net/jfriend00/WCj4b/
Using jQuery (which you also tagged your question with) and continuing to use the clone operation, you can do this:
$("input[type='button'][name='add']").each(function(index, el) {
$(this).clone(false)
.val("remove")
.removeAttr("name")
.attr("onclick", "")
.attr("id", "")
.insertAfter(this);
});
Demo here: http://jsfiddle.net/jfriend00/JKsZC/
Or a much, much simpler version that just inserts new HTML rather than clone the existing button:
$("input[type='button'][name='add']").after('<input type="button" value="Remove" />');
Demo here: http://jsfiddle.net/jfriend00/vSZwp/

Why recursive? Just to find the existing button? Let jQuery worry about finding it
$('input[type=button]').after("<input type='button' value='Remove' />");
Tweak this to get your remove button to do what you need.

Related

converting javascript to jquery function not correctly working

I am trying to convert a small script from javascript to jquery, but I don't know where I should be putting the [i] in jquery?. I am nearly there, I just need someone to point out where I have gone wrong.
This script expands a search input when focused, if the input contains any values, it retains it's expanded state, or else if the entry is removed and clicks elsewhere, it will snap back.
Here is the javascript:
const searchInput = document.querySelectorAll('.search');
for (i = 0; i < searchInput.length; ++i) {
searchInput[i].addEventListener("change", function() {
if(this.value == '') {
this.classList.remove('not-empty')
} else {
this.classList.add('not-empty')
}
});
}
and converting to jquery:
var $searchInput = $(".search");
for (i = 0; i < $searchInput.length; ++i) {
$searchInput.on("change", function () {
if ($(this).value == "") {
$(this).removeClass("not-empty");
} else {
$(this).addClass("not-empty");
}
});
}
Note the key benefit of jQuery that it works on collections of elements: methods such as .on automatically loop over the collection, so you don't need any more than this:
$('.search').on("change", function() {
this.classList.toggle('not-empty', this.value != "");
});
This adds a change event listener for each of the .search elements. I've used classList.toggle as it accepts a second argument telling it whether to add or remove the class, so the if statement isn't needed either.

How to refactor long if statment?

I have a long if statement that I'm wanting to refactor. The statement listens for a click and then updates one of five text boxes depending on if those text boxes have anything in them or not. How could I change my code to be more efficient.
$('#img1').click(function() {
if ($('#card1').val().length === 0) {
$('#card1').val('A feeling of warmth');
} else if ($('#card2').val().length === 0) {
$('#card2').val('A feeling of warmth');
} else if ($('#card3').val().length === 0){
$('#card3').val('A feeling of warmth');
} else if ($('#card4').val().length === 0){
$('#card4').val('A feeling of warmth');
} else if ($('#card5').val().length === 0){
$('#card5').val('A feeling of warmth');
}
});
you could use a loop
$('#img1').click(function() {
var items = ["#card1", "#card2", "etc"];
for(var i=0;i<items.length;i++){
if ($(items[i]).val().length === 0) {
$(items[i]).val('A feeling of warmth');
}
}
});
it's at least easier to read. Also if your buttons are always card + a number you could make it even simplier (not easier to read, just less lines & maintenance)
$('#img1').click(function() {
for(var i=0;i<=5;i++){
if ($("#card" + i).val().length === 0) {
$("#card" + i).val('A feeling of warmth');
}
}
});
It seems like you're using JQuery. You can use a selector and a filter to isolate the first empty item:
$('#img1').click(function() {
$('input:text[id^=card]')
.filter(function() { return $(this).val() == ""; })
.first()
.val('A feeling of warmth');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="img1">CLICK ME</button><br>
<input id="card1"><br>
<input id="card2"><br>
<input id="card3"><br>
<input id="card4"><br>
<input id="card5">
$('input:text[id^=card]') selects all text inputs whose IDs begin with 'card'. But the same principle would apply to other element types.
$('#img1').click(function() {
// num can be total count of the element like $(.card).children.count
var num = 5, // preferably dont make it hardcoded.
str = 'A feeling of warmth',
preIdStr = '#card',
id;
for (var i = 1; i <= num; i += 1) {
id = preIdStr + i;
if ($(id).val().length === 0) {
$(id).val(str);
}
}
});
Give all cards the same class.
Then use the selector $('.yourclass')
Now use the jQuery for-each (.each) function to iterate all elements. Within the loop you check the value, set it to whatever you want and return false when the value was set, since this exit's the loop.
$('.yourclass').each(
function () {
if (this.val().length === 0) {
this.val('your value');
return false; // exit loop
}
});

Best method to handle nodelist?

Since .foreach and .map won't work on a nodelist, is the only way to work with the elements in a nodelist through a for loop?
What I'm trying to accomplish is adding different event listeners to the different elements within a nodelist. If the element has the class name of "bold", then the iBold() function should be run, and likewise for "italics" and "underline". Having multiple for loops running to handle each individually feels excessive, so that's why I'm trying to work with one loop to handle all rich text. However, if there's a better way to go about this, I'd really like to know since it seems as though I'm just over-thinking all of this.
var QSA = document.querySelectorAll('div > form > div > a.richText');
for (var rtIndex = 0; rtIndex < QSA.length;rtIndex++) { //Rich text event listeners
var rtid = QSA[rtIndex].id;
var targetiFrame = document.getElementById(rtid).getAttribute('data-pstid');
if (document.getElementById(rtid).className == "richText bold") { //Bold text event listener
QSA[rtIndex].addEventListener('click', function() {
if (targetiFrame != 0) {iBold(targetiFrame);}
else {
document.getElementById('richTextField').contentDocument.execCommand('bold', false, null);
document.getElementById('richTextField').contentWindow.focus();
}
}, false);
} else if (document.getElementsByClassName('richText')[rtIndex].className == 'richText underline') { //Underline text event listener
document.getElementsByClassName('richText')[rtIndex].addEventListener('click', function() {
if (targetiFrame == 0) {
document.getElementById('richTextField').contentDocument.execCommand('underline', false, null);
document.getElementById('richTextField').contentWindow.focus();
} else {iUnderline(targetiFrame);}
}, false);
} else if (document.getElementsByClassName('richText')[rtIndex].className == 'richText italic') { //Italic text event listener
document.getElementsByClassName('richText')[rtIndex].addEventListener('click', function() {
if (targetiFrame == 0) {
document.getElementById('richTextField').contentDocument.execCommand('italic', false, null);
document.getElementById('richTextField').contentWindow.focus();
} else {iItalic(targetiFrame);}
}, false);
}
}
for (var sbmtIndex = 0;sbmtIndex < document.getElementsByClassName('sbmtPost').length;sbmtIndex++) { //Event listener for submitting posts or comments
var iSubmt = document.querySelectorAll('form > div')[sbmtIndex];
document.querySelectorAll('form > div > .sbmtPost')[sbmtIndex].addEventListener('click', function() {
var pstData = iSubmt.querySelector('form > div > .sbmtPost').getAttribute('data-cmtid');
var cPrntID = iSubmt.querySelector('form > div > .sbmtPost').getAttribute('data-pstid');
sendData(pstData, cPrntID); //Post Data (data being the id) and Comment Parent Id. Comments are posts. Variables only used for comments
}, false);
}
If you don't mind converting your NodeList to an Array, you can use:
var nodeArray = Array.prototype.slice.call(nodeList);
Then you can use all the functional methods on the resulting Array.
Note: this is not cross-browser compatible, but neither is .forEach, so I don't think it's an issue.
<div class="btn" data='btn1'>btn1</div>
<div class="btn" data='btn2'>btn2</div>
<div class="btn" data='btn3'>btn3</div>
const btns = document.getElementsByClassName('btn');
function getBtns(el, callback) {
Array.prototype.forEach.call(el, function(node) {
// Wrap below in if() condition here.
node.addEventListener('click', callback);
});
}
getBtns(btns, function() {
console.log(this.getAttribute('data'));
});
this points to the button that's clicked.

Finding the specific Tag a

My HTML is:
<a id="showSlotsByLocation_" href="#" style="color:blue;" onclick="confirmAppt('28/05/2013','364301');">14.00 - 14.15</a>
<a id="showSlotsByLocation_" href="#" style="color:blue;" onclick="confirmAppt('28/05/2013','364303');">14.15 - 14.30</a>
Id name are same on all links. This is the main difficulty.
I want to click second link my javascript code are configure web browser is
if (location.pathname == "/abc")
{
//alert('location found') this is ok found;
var el = document.getElementsByTagName("a");
for (var i=0;i<el.length;i++)
{
if (el.id == 'showSlotsByLocation_' && el.innerText.isEqual('14.15 - 14.30') && el.outerHTML.contains("confirmAppt('28/05/2013'"))
{
alert('link found') \\this condition not match;
el.onclick();
}
}
}
What do i do to match the condition?
You can't have two element with the same ID, IDs are unique.
When you will have changed the IDs, you'll can access them simply using document.getElementById('idOfYourElement')
EDIT:
First of all, you need to declare a "current" variable that takes the current element in the loop, you can't use el.id because el is a collection of HTMLElements! I'm sorry I didn't noticed it before.
So you need this(define the variable inside the for loop, just before the if statement):
var current = el[i];
Now that you have defined it, change this whole line with the code below.
if (el.id == 'showSlotsByLocation_' && el.innerText.isEqual('14.15 - 14.30') && el.outerHTML.contains("confirmAppt('28/05/2013'"))
I think this is the code that stops you. There are no functions called isEqual and contains in JS.
if (current.id == 'showSlotsByLocation_' && current.textContent === '14.15 - 14.30' && current.outerHTML.indexOf("confirmAppt('28/05/2013'") !== -1)
One last thing: innerText isn't a valid cross browser property, use textContent instead.
MDN Reference
Updated JS code
if (location.pathname == "/abc")
{
var el = document.getElementsByTagName("a");
for (var i=0;i<el.length;i++)
{
var current = el[i];
if (current.id == 'showSlotsByLocation_' && current.textContent === '14.15 - 14.30')//I'm not sure about this one, in case you want it just remove the comment and the last parenthesis && current.outerHTML.indexOf("confirmAppt('28/05/2013'") !== -1)
{
alert('link found');
current.click();
}
}
}

JavaScript Button Style change on click

I have put together this piece of JavaScript, but I am struggling with the code as I'm a newbie. What I want to do is when a button is clicked it will change the background color opacity. The code below does this, but now I want the button to be reverted to the normal state when I click it again.
How can I do this? Thanks..
Normal state: background="rgba(255,0,0,0.8)"; Pressed state:
background="rgba(255,0,0,0.6)";
function highlight(id) {
document.getElementById(id).style.background="rgba(255,0,0,0.6)";
}
I would use a CSS class:
.opacityClicked{
background:rgba(255,0,0,0.8);
}
.opacityDefault{
background:rgba(255,0,0,0.6);
}
And change your function to:
function highlight(id) {
var element = document.getElementById(id);
element.class = (element.class == "opacityClicked") ? "opacityDefault" : "opacityClicked";
}
Or if you want to use only JavaScript
var isClicked = false;
function highlight(id) {
isClicked = !isClicked;
var element = document.getElementById(id);
element.style.background = (isClicked == true) ? "rgba(255,0,0,0.6)" : "rgba(255,0,0,0.8)";
}
Update(See comments: if you use 2 buttons):
var buttonClicked = null;
function highlight(id) {
if(buttonClicked != null)
{
buttonClicked.style.background = "rgba(255,0,0,0.8)";
}
buttonClicked = document.getElementById(id);
buttonClicked.style.background = "rgba(255,0,0,0.6)";
}
You could do something really quick like this:
First, add a hidden input element to your page like so:
<input type="button" id="foobar" value="FooBar!" onclick="highlight('foobar')" style="background-color:rgba(255,0,0,0.8);" />
<input type="hidden" id="one_neg_one" value="1" /> <= hidden element
Next, put this in your highlight function:
function highlight(id) {
var a = 7;
var o = document.getElementById("one_neg_one");
var newa = (a + (parseInt(o.value) * -1)) * 0.1;
document.getElementById(id).style.background="rgba(255,0,0," + newa + ")";
o.value = o.value * -1;
}
That should work, although I agree with a previous answer that you should use a css class for this.
#Ruben-J answer works fine. There is a syntax error though - you should instead use element.className rather than element.class.
https://developer.mozilla.org/en-US/docs/Web/API/Element/className
Below is an updated answer using the correct syntax:
function highlight(id) {
var element = document.getElementById(id);
element.className = (element.className == "opacityClicked") ? "opacityDefault" : "opacityClicked";
}
Also noticed that this answer doesn't show the HTML. Make sure to pass through the id element, not the name of the id.

Categories