How to select the values of the value attribute? - javascript

I am trying to hide a comment with its children by a toggling function, but I want to hide them based on the value attribute.
this is my js function:
function toggle(id, lft, rgt) {
var kids = (rgt - lft - 1) / 2;
if (kids >= 1) {
var element = document.querySelectorAll("div.md#com" + id)[0].getAttribute('value');
var low = Number(element.split('-')[0]);
var high = Number(element.split('-')[1]);
for(var i = low + 1; i <= high - 1; i += 1){
var x = document.querySelectorAll("div.md#com" + i)[0]
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
}
}
and this is the result:
<div>
<div id="com4" class="md" value="7-10">yellow
<a onclick="toggle(4, 7, 10)" href="javascript:void(0)">[-]</a>
</div>
<div id="com5" class="md" value="8-9">not collapsing</div> //because of the id but I want to toggle them based on the value attribute
<div id="com8" class="md" value="10-11">collapses</div>
</div>
but I want:
<div>
<div id="com4" class="md" value="7-10">yellow
<a onclick="toggle(4, 7, 10)" href="javascript:void(0)">[-]</a>
</div>
<div id="com5" class="md" value="8-9">Should collapse</div>
<div id="com8" class="md" value="10-11">Should not collapse</div>
</div>

Assuming that the behavior you want is that when the <a> element is clicked on, you want to hide the elements based on the numeric range provided in its parent's value attribute. E.g. in your example, you will want to hide #com8 because it lies within the range of 7-10, but not #com5.
To achieve that, there are some changes that I would strongly recommend doing:
Give all your comments a more unique class, e.g. .com. I suspect that .md is a very generic class that you might use in other elements on the same page, so let's just get that over with.
Do not use a reserved value attribute, since that is reserved for input-like elements. Use data-* attributes instead: for example, we can use data-range="7-10". Even better, you can use data-min and data-max, so that we can avoid parsing the range. I'll leave the latter up to you.
Do not use inline JS, but rely on addEventListener to bind click events to your <a> element
Now, to the solution: if we slightly tweak your logic, it is actually very doable:
When the <a> element is clicked on, get the data-range value of its closest parent. This can be done using .closest('div.com'), and then use the dataset API to retrieve the range.
Split the data-range attribute, and remember to convert it into a number by using the unary +
Use ES6 array destructuring to assign min and max variables to the parsed data-range
Go through all .com elements:
If the element matches our trigger element's parent element, we ignore it
Otherwise, we check if its id—parsed using String.prototype.substring() to strip out the first 3 characters—lies inside or outside the numeric range
Then, use this boolean value to determine the style.display property of the element
See proof-of-concept below:
const collapseButtons = document.querySelectorAll('.collapse');
Array.from(collapseButtons).forEach(collapseButton => {
collapseButton.addEventListener('click', e => {
e.preventDefault();
// Use ES6 destructuring to assign min/max values
const [min, max] = e.target.closest('div.com').dataset.range.split('-').map(v => +v);
const comments = document.querySelectorAll('.com');
Array.from(comments).forEach(comment => {
// If the element matches the trigger element's parent, do nothing
if (comment === e.target.closest('div.com')) {
return;
}
// Otherwise, decide if we should hide or show it
const id = +comment.id.substring(3);
const shouldHide = id < min || id > max;
comment.style.display = shouldHide ? 'none' : 'block';
});
});
});
<div>
<div id="com4" class="com md" data-range="7-10">yellow
<a class="collapse" href="#">[-]</a>
</div>
<div id="com5" class="com md" data-range="8-9">Should collapse</div>
<div id="com8" class="com md" data-range="10-11">Should not collapse</div>
</div>

Try this instead
<div class="box" name="box1"></div>
<div class="box" name="box2"></div>
<div class="box" name="box3"></div>
$('.box').on('click', function(){
var value = $(this).attr('name');
alert(value);
});
Or if you want to use value attribute
$('.box').on('click', function(){
var value = $(this).attr('value');
alert(value);
});

Related

JavaScript innerHTML manipulation with "onclick" handler

I wanna know how I can change the innerHtml to switch back and forth between two states.
I have this html
<div class="test" id="test">
<p>this is a test</p>
</div>
<p id="js" class="test" >Change</p>
and this is the JavaScript I have
let button = document.getElementById("js");
button.onclick = function() {
document.getElementById("test").innerHTML = "test";
};
how can I change the innerHTML from "test" to "another test" and vice versa ?
It's better not to store state in the HTML - don't test against what's currently in the HTML, keep a variable in your Javascript instead.
You should also only use let when you want to warn other programmers that you're going to reassign the variable in question - otherwise, use const.
Also, if you're changing the text of an element, assign to textContent instead - it's safer, faster, and more predictable.
const button = document.getElementById("js");
const test = document.getElementById("test");
let clicked = false;
button.onclick = function() {
clicked = !clicked;
if (clicked) test.textContent = 'another test';
else test.textContent = 'test';
};
<div class="test" id="test">
<p>this is a test</p>
</div>
<p id="js" class="test" >Change</p>
First, .innerHTML is only for when you are setting/getting a string that contains HTML. When you are not, use .textContent, which is more efficient and reduces security risks.
Then, you only need a toggling if statement, which can be done with a JavaScript ternary operator.
Also, rather than using event properties, like onclick. Use .addEventListener(), which is more robust and follows the modern standard.
// Get a reference to the <p> that is inside of the element who's id is "test"
let output = document.querySelector("#test p");
// Set up an event handler for the "Change" element
document.getElementById("js").addEventListener("click", function() {
// Check the current textContent and set it to the other value
output.textContent === "test" ? output.textContent = "another test" : output.textContent = "test";
});
<div class="test" id="test">
<p>this is a test</p>
</div>
<p id="js" class="test" >Change</p>
Just toggle between the two with an if statement like this:
let button = document.getElementById("js");
let toggleText = document.getElementById("test");
button.addEventListener('click', function() {
if (toggleText.innerHTML !== "another test") {
toggleText.innerHTML = "another test";
} else {
toggleText.innerHTML = "test";
}
});
ALso, as #CertainPerformance mentioned in the other answer, I would recommend that you use textContent rather than innerHTML since you're checking and toggling the element's string content and not the element itself.
I'm going to expand on a couple of excellent answers here.
What if you had more options and wanted to iterate through a list?
In that case, I'd use a data attribute to hold the button state. This is still a valid approach with just two options.
I am going to use Scott's answer as the basis of mine, so everything he says is pertinent.
// Get a reference to the <p> that is inside of the element who's id is "test"
let output = document.querySelector("#test p");
// Set up an event handler for the "Change" element
document.getElementById("js").addEventListener("click", function() {
//Here is out list of options
var options = ["test", "another test", "yet another test", "Really? Another Test?"];
//get the current state value and increment it
var index = parseInt(this.dataset.state, 10) + 1;
//if index is out of bounds set it to 0
if(index > options.length -1 || index < 0)
{
index = 0;
}
//Set the text
output.textContent = options[index];
//Set the new state
this.dataset.state = index;
});
<div class="test" id="test">
<p>this is a test</p>
</div>
<p id="js" class="test" data-state="-1" >Change</p>

How to remove last element using jquery?

Here I'm trying to create a calling pad that reads a maximum of 10 numbers at a time, and displays the numbers as a maximum of 6 numbers in a row. It's working functionally. I want to remove the last number when the user presses the clear button.
I used $("#calling-pad").last().remove(); to try to remove the last number, but it removes the whole contents and doesn't allow to enter a new number. How can I fix it?
var key = 1;
$("#nine").click(function(){
if (p === 1) {
$("#mini-screen").css("display","none");
$("#number-screen").css("display","block");
if (key < 11) {
if ((key % 7) !== 0) {
$("#calling-pad").append("9");
key = key + 1;
}
else {
$("#calling-pad").append("<br>");
$("#calling-pad").append("9");
key = key + 1;
}
}
}
});
$("#inner-icon-one").click(function(){
if (p === 1) {
$("#mini-screen").css("display","none");
$("#number-screen").css("display","block");
if (key > 1) {
if ((key%6) !== 0) {
$("#calling-pad").last().remove();
key = key - 1;
if ( key === 1) {
$("#number-screen").css("display","none");
$("#mini-screen").css("display","block");
}
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span id="calling-pad"> </span>
You are just appending numbers to a span tag and are not really keeping track of user input.
$("#calling-pad").last().remove();
Is telling jQuery to remove the full contents because you are not inserting any child elements to the calling-pad span.
Therefore you could use an array to keep track of the users numbers or use a counter as I have shown below.
var totalInputs = 0;
$("#insert").on("click", function() {
totalInputs++;
var inputText = $("#input").val();
var id = "calling_" + totalInputs;
$("#calling-pad").append("<span id='" + id + "'>" + inputText + "</span>");
});
$("#remove").on("click", function() {
$("#calling_" + totalInputs).remove();
totalInputs--;
});
span {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input type="text" id="input" />
<button id="insert">Insert</button>
<div id="calling-pad">
</div>
<button id="remove">Remove last element</button>
Problem - Using 'last' instead of ':last-child'
The jQuery last method does not find child elements. Instead, given a collection of elements matching a selector, it filters that collection to include only the last element. Combining this with an id-selector (i.e. $("#element-id").last()) is always redundant, since $("#element-id") only matches a single element, and the resulting jQuery object is always of size 1. If there's only one element, it's always the last one.
Therefore $("#calling-pad").last().remove(); is effectively the same as saying $("#calling-pad").remove();.
Solution
Instead, when you're appending data to the #calling-pad element, ensure they're included as new elements (e.g. wrapped in <span></span> tags):
$('#calling-pad').append("<span>9</span>");
Then, when you want to remove the last element in the #calling-pad, you simply have to do this:
$('#calling-pad > span:last-child').remove();
This finds all span elements that are direct children of the #calling-pad, filters that to only include the last element (using :last-child), and then removes that element.
$("#calling-pad").contents().last().remove();
if ($("#calling-pad").contents().last().is("br")) {
$("#calling-pad").contents().last().remove();
}
As you're dealing with textNodes, you need to use .contents() - the <br> split them up so no need to parse things, and if you're deleting the last node, you need to delete the last break at the same time...
You need one line to remove last comment... no need to count ids ...
here is snippet ... Cheers Man
$("#insert").on("click", function() {
var inputText = $("#input").val();
$("#calling-pad").append("<span>" + inputText + "</br></span>");
});
$("#remove").click(function(){
$("#calling-pad").children("span:last").remove()
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<input type="text" id="input" />
<button id="insert">Insert</button>
<div id="calling-pad">
</div>
<button id="remove">Remove last one</button>

CSS selector to match elements by attribute's name start

Is there any CSS selector to match these elements? (I need it for adblocker config, looked at W3C selectors document - no hints found there. Generic solution needed because part after data-d- gets randomized by the site).
<div data-d-9y3x>
<div data-d-m01>
<div data-d-whatever>
No, there is currently no way to select elements based on the presence of an attribute whose name is starting with a certain value. The starts with selection is only possible for attribute values.
Such a selector is not mentioned in the CSS Selectors Level 4 spec also and so it doesn't look like it would be available anytime soon.
You have the following choices:
Use group of selectors with all possible attribute name values in the format element[attribute-name]. But this option is not viable when the exact attribute names are not fixed/unknown.
Use JavaScript (or some other scripting library of your preference). Below is a very quick rough sample for the benefit of future visitors.
var el = document.getElementsByTagName('div');
for (var i = 0; i < el.length; i++) {
var attr = el[i].attributes; /* get all attributes on the element */
for (var j = 0; j < attr.length; j++) {
if (attr[j].name.indexOf('data-') == 0) { /* if element has an attribute whose name has data- */
el[i].style.color = 'red';
break;
}
}
}
<div data-d-9y3x>Some</div>
<div data-d-m01>text</div>
<div data-d-whatever>content</div>
<div test-data-d-whatever>and</div>
<div d-whatever>more</div>
<div testdata-d-whatever>...</div>
With ES6+ you can use spread operator (...) and then filter those element that their attribute names' start with data-d-:
var res = [...document.querySelectorAll("*")]
.filter(t => [...t.attributes]
.filter(({ name }) => name.startsWith("data-d-")).length > 0)
console.log(res);
<div data-d-9y3x>Some</div>
<div data-d-m01>text</div>
<div data-d-whatever>content</div>
<div test-data-d-whatever>and</div>
<div d-whatever>more</div>
<div testdata-d-whatever>...</div>

How to make querySelectorAll select only from child elements of the current element

What I'm asking is how to implement the equivalent functionality of jQuery's children() with HTML5's querySelector/querySelectorAll, i.e. how do I designate the current element in the selector pattern.
For example:
<div id="foo">
<div class="bar" id="div1">
<div class="bar" id="div1.1">
</div>
</div>
<div class="bar" id="div2"></div>
</div>
With document.getElementById('foo').querySelectAll('div.bar') all three divs will be selected. What if I only wanna get div1 and div2, not div1's child div1.1? How do I write [[current node]] > div.bar like css selector?
Could anybody shed some light on this?
In your example you have did id="foo", so above example works.
But in a situation when parent element has no ID, but you still want to use querySelectorAll to get immediate children - it is also possible to use :scope to reference element like this:
var div1 = document.getElementById('div1'); //get child
var pdiv = div1.parentNode; //get parent wrapper
var list = pdiv.querySelectorAll(':scope > div.bar');
Here query will be "rooted" to pdiv element..
Actually there is a pseudo-class :scope to select the current element, however, it is not designated as being supported by any version of MS Internet Explorer.
document.getElementById('foo').querySelectAll(':scope>div.bar')
There's no selector for designating the element from which the .querySelectorAll was called (though I think something may have been proposed).
So you can't do anything like this:
var result = document.getElementById('foo').querySelectorAll('[[context]] > p.bar');
What you'd need would be to select from the document, and include the #foo ID in the selector.
var result = document.querySelectorAll("#foo > p.bar");
But if you must start with an element, one possibility would be to take its ID (assuming it has one) and concatenate it into the selector.
var result = document.querySelectorAll("#" + elem.id + " > p.bar");
If it's possible that the element doesn't have an ID, then you could temporarily give it one.
var origId = elem.id
if (!origId) {
do {
var id = "_" + Math.random().toString(16)
} while (document.getElementById(id));
elem.id = id;
}
var result = document.querySelectorAll("#" + elem.id + " > p.bar");
elem.id = origId;
You can just use like this:
document.querySelectorAll('#foo > p.bar')

jQuery clone duplicate IDs

I have an HTML element with a large collection of unordered lists contained within it. I need to clone this element to place elsewhere on the page with different styles added (this is simple enough using jQuery).
$("#MainConfig").clone(false).appendTo($("#smallConfig"));
The problem, however, is that all the lists and their associated list items have IDs and clone duplicates them. Is there an easy way to replace all these duplicate IDs using jQuery before appending?
If you need a way to reference the list items after you've cloned them, you must use classes, not IDs. Change all id="..." to class="..."
If you are dealing with legacy code or something and can't change the IDs to classes, you must remove the id attributes before appending.
$("#MainConfig").clone(false).find("*").removeAttr("id").appendTo($("#smallConfig"));
Just be aware that you don't have a way to reference individual items anymore.
Since the OP asked for a way to replace all the duplicate id's before appending them, maybe something like this would work. Assuming you wanted to clone MainConfig_1 in an HTML block such as this:
<div id="smallConfig">
<div id="MainConfig_1">
<ul>
<li id="red_1">red</li>
<li id="blue_1">blue</li>
</ul>
</div>
</div>
The code could be something like the following, to find all child elements (and descendants) of the cloned block, and modify their id's using a counter:
var cur_num = 1; // Counter used previously.
//...
var cloned = $("#MainConfig_" + cur_num).clone(true, true).get(0);
++cur_num;
cloned.id = "MainConfig_" + cur_num; // Change the div itself.
$(cloned).find("*").each(function(index, element) { // And all inner elements.
if(element.id)
{
var matches = element.id.match(/(.+)_\d+/);
if(matches && matches.length >= 2) // Captures start at [1].
element.id = matches[1] + "_" + cur_num;
}
});
$(cloned).appendTo($("#smallConfig"));
To create new HTML like this:
<div id="smallConfig">
<div id="MainConfig_1">
<ul>
<li id="red_1">red</li>
<li id="blue_1">blue</li>
</ul>
</div>
<div id="MainConfig_2">
<ul>
<li id="red_2">red</li>
<li id="blue_2">blue</li>
</ul>
</div>
</div>
$("#MainConfig")
.clone(false)
.find("ul,li")
.removeAttr("id")
.appendTo($("#smallConfig"));
Try that on for size. :)
[Edit] Fixed for redsquare's comment.
I use something like this:
$("#details").clone().attr('id','details_clone').after("h1").show();
This is based on Russell's answer but a bit more aesthetic and functional for forms.
jQuery:
$(document).ready(function(){
var cur_num = 1; // Counter
$('#btnClone').click(function(){
var whatToClone = $("#MainConfig");
var whereToPutIt = $("#smallConfig");
var cloned = whatToClone.clone(true, true).get(0);
++cur_num;
cloned.id = whatToClone.attr('id') + "_" + cur_num; // Change the div itself.
$(cloned).find("*").each(function(index, element) { // And all inner elements.
if(element.id)
{
var matches = element.id.match(/(.+)_\d+/);
if(matches && matches.length >= 2) // Captures start at [1].
element.id = matches[1] + "_" + cur_num;
}
if(element.name)
{
var matches = element.name.match(/(.+)_\d+/);
if(matches && matches.length >= 2) // Captures start at [1].
element.name = matches[1] + "_" + cur_num;
}
});
$(cloned).appendTo( whereToPutIt );
});
});
The Markup:
<div id="smallConfig">
<div id="MainConfig">
<ul>
<li id="red_1">red</li>
<li id="blue_1">blue</li>
</ul>
<input id="purple" type="text" value="I'm a text box" name="textboxIsaid_1" />
</div>
</div>
FWIW, I used Dario's function, but needed to catch form labels as well.
Add another if statement like this to do so:
if(element.htmlFor){
var matches = element.htmlFor.match(/(.+)_\d+/);
if(matches && matches.length >= 2) // Captures start at [1].
element.htmlFor = matches[1] + "_" + cur_num;
}
If you will have several similar items on a page, it is best to use classes, not ids. That way you can apply styles to uls inside different container ids.
I believe this is the best way
var $clone = $("#MainConfig").clone(false);
$clone.removeAttr('id'); // remove id="MainConfig"
$clone.find('[id]').removeAttr('id'); // remove all other id attributes
$clone.appendTo($("#smallConfig")); // add to DOM.
Here is a solution with id.
var clone = $("#MainConfig").clone();
clone.find('[id]').each(function () { this.id = 'new_'+this.id });
$('#smallConfig').append(clone);
if you want dynamically set id, you can use counter instead of 'new_'.

Categories

Resources