I have a problem with updating DOM. Probably it's not hard to do for experienced developers.
I made a carousel with 4 items and all of them are shown by default.
For example, when clicking on the < LEFT ARROW I need to replace the first item with the next one, and the first one must be pushed as the last one. The same logic for the right arrow >
I get my carousel like this:
const carouselItems = Array.from(document.querySelectorAll('.carousel-item'));
Also the function for replacing the items:
function carouselToLeft() {
carouselItems.push(carouselItems.shift());
}
So my caroueslItems array changes as expected but I can't make the DOM to update. Somebody help with this please. I'm not experienced as you guess, but I need the answer for now.
Here is a solution with using only a single constant and no variables or arrays:
const cont=document.querySelector(".container");
document.querySelectorAll("button").forEach(b=>b.onclick=ev=> // assign click events
cont[(b.textContent=="<"?"pre":"ap")+"pend"]( // prepend() or append()
cont.children[b.textContent=="<"?cont.children.length-1:0]) // last or first child-element
)
<h2>A simplified carousel</h2>
<div class="container">
<div class="carousel-item">one</div>
<div class="carousel-item">two</div>
<div class="carousel-item">three</div>
<div class="carousel-item">four</div>
<div class="carousel-item">five</div>
</div>
<button title="move last element to the top"><</button>
<button title="move first element to the bottom">></button>
The reason why I get away with so little code is that when we append() or prepend() a DOM element it will be taken away from its original position. So, no need to "clean up". And by looking freshly at the children-collection of .container I am always up-to-date with the current state of the carousel.
Array.from create a new Array Object that whatever you do on it can make DOM update itself.
You can get an Array-like object by using document.querySelectorAll, then push each item of it to a true Array.
Then, while you want to replace the first item with the next one, you can remove it, then append to the parent element of it.
Related
There is a website with the following div:
<div class="x" style="user-select: none; filter: blur(4px);">
under this div there are a lot of other divs. I would like to know if there is any possibility to delete only this DIV with javascript. So only that one gets deleted and all the divs underneath remain.
I want to get rid of this DIV becouse this div blurs an part of the website text. Just changing the blur(4px) wont work the website has some kind of protection what refreshes this part back to original.
the reason i am searching for an possibility in javascript is because i want to automate this in the browser.(Just deleting DIV manually under developer mode works)
so far i got the following:
var div = document.getElementsByClassName('page-content');
div.remove(); //(but this does not work)
getElementsByClassName() returns a collection. Only single objects have remove() method. You have to apply one of the following:
Use brackets to specify an index of the object you want to get:
div[0].remove()
Use item() method passing the index as well:
div.item(0).remove()
Both ways are equivalent.
As an alternative, you may call querySelector() method:
const div = document.querySelector('.page-content')
It returns a single object (according to the passed CSS selector) so you can use:
div.remove()
Edit:
To remove only the covering div, you may use replaceWith() method and pass the child nodes of that div as an argument:
div.replaceWith(...div.childNodes)
If you want to keep only element nodes, use children property:
div.replaceWith(...div.children)
I am using a knockout foreach (more specifically, template: { foreach: items }) binding to display a list of elements.
I then proceed to take the following actions:
Swap the first and second elements of the observable array. I see the changes reflected on screen, as expected.
Repeat the previous action to revert to the initial state. Again, this works as expected.
Now, swap the first and second DOM elements. I see the changes reflected on screen, as expected.
Repeat the previous action to revert to the initial state. Again, this works as expected.
Even though we have manually tampered with the DOM, we have reverted to exactly the initial state, without invoking knockout during the DOM tampering. This means the state is restored to the last time knockout was aware of it, so it should look to knockout as if nothing ever changed to begin with.
However, if I perform the first action again, that is, swap the first two elements in the array, the changes are not reflected on screen.
Here is a jsfiddle to illustrate the problem: https://jsfiddle.net/k7u5wep9/.
I know that manually tampering with the DOM managed by knockout is a bad idea and that it can lead to undefined behaviour. This is unfortunately unavoidable in my situation due to third party code. What stumps me is that, even after reverting the manual edits to the exact initial state, knockout still does not work as expected.
My question is: what causes this behaviour?
And then, how does one work around it?
Turns out there is nothing magical happening here. The mistake I made was to only consider elements instead of all nodes. The knockout template binding keeps a record of all nodes when reordering, not just elements.
Before manually editing the DOM, the child nodes of the template binding are:
NodeList(6) [text, div, text, text, div, text].
After manually swapping the first two elements using parent.insertBefore(parent.children[1], parent.children[0]), this turns into:
NodeList(6) [text, div, div, text, text, text].
Repeating the action yields:
NodeList(6) [text, div, div, text, text, text].
Although this is identical to the initial state when only referring to elements, it is quite different when referring to all nodes.
The solution now becomes clear. One way to perform a proper manual swap is to replace
parent.insertBefore(parent.children[1], parent.children[0]);
with
let nexts = [parent.children[0].nextSibling, parent.children[1].nextSibling];
parent.insertBefore(parent.children[1], nexts[0]);
parent.insertBefore(parent.children[0], nexts[1]);
as seen in https://jsfiddle.net/k7u5wep9/2/.
Obviously more care has to be taken when there are no text nodes before/after, but the idea remains the same.
By manipulating the DOM, you have broken the binding made.
Do not manipulate directly the DOM. Knockout will not detect the changes made.
If you put a with: items around your foreach, it at least keeps working but requires double click if dom order != array order .. might get you on track atleast, maybe you can re-order the ko-array inside the dom function to keep their 'orders' in sync?
let vm = {
items: ko.observableArray(['item1', 'item2']),
reorder_array() {
vm.items([vm.items()[1], vm.items()[0]]);
},
reorder_dom() {
let parent = document.querySelector('#items');
parent.insertBefore(parent.children[1], parent.children[0]);
vm.reorder_array();
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="with: items">
<div id="items" data-bind="template: { foreach: $data }">
<div data-bind="text: $data"></div>
</div>
</div>
<button data-bind="click: reorder_array">Reorder array</button>
<button data-bind="click: reorder_dom">Reorder DOM</button>
<div>
Reorder the array twice, then reorder DOM twice. This should work as expected, and end up with the initial state. Then, try to reorder the array again. It should not work. Why?
</div>
<div id="divWrapper">
<input id="firstInput"/>
<div id="insideDiv">
<input id="secondInput"/>
</div>
</div>
This is the basic structure of my HTML. So, given an access to "divWrapper" element how do I check if it contains "secondInput".
NOTE: This is to be done without jQuery. Only using Angularjs utility functions.
UPDATE: The solution should be dynamic. Meaning it should find any child, how much ever down the level it is in the DOM tree.
UPDATE 2: Please don't get misguided by "input" element. I don't want to track the text inside it. I want to a logic which will check if me or any of my child is clicked. If NOT then hide myself and my children.
var secondInput = angular.element(document.querySelector("#divWrapper #insideDiv #secondInput"));
This will return the element you're looking for or empty if it doesn't find anything.
angular.element("#someId").find("someOtherSelector") already does exactly what you want.
So for your question you could just do...
angular.element("#divWrapper").find("#secondInput"); // Empty array OR a match
I am really wondering if jQuery remove function really remove elements from DOM.
First, I looked here but the answers are not convincing.
I encountered this problem when I noticed I am still able to manipulate elements on which I have called remove function.
My code:
<div id="container">
<div id="div">
This is a div
</div>
</div>
var div = $('#div');
$('#div').remove();
$('#container').append(div);
Note: My question is not how to solve this? but I want to understand what's going on here!
Actually, this code doesn't remove the #div from the dom, but if I have any data set to the #div, it will be lost. I am pretty confused now about the behaviour of remove function. Can anyone explain this please?
DEMO
I am convinced that div variable is not just a clone of the dom element, is a reference to it, because when I manipulate the div variable, (like div.html('something')) the div within the DOM get updated.
Or am I wrong?
remove() does indeed remove the element from the DOM.
However in your example, the element has been cached in memory because you assigned it to the div variable. Therefore you can still use it, and append it again, and it will still contain all the events the original did.
If what you say is right, why I loose the data bound to the div so?
If you check the source for remove() you'll see that it calls the internal cleanData function which removes the events and clears the data cache for the element. This is why you lose the information.
If you want to remove the element from the DOM, but keep the elements, use detach() instead.
Here's a fiddle to show the difference: http://jsfiddle.net/2VbmX/
had to delete the assigned variable:
delete div;
What is the behavior of jquery when using each() loop? Is it:
look for the first object, execute function, look for the next object...
gather all objects in a container, then execute the function on each of them
other (what exactly?)
An example, where it is relevant:
<div id="a">
A
<div id="b">
B
</div>
</div>
<div id="c">
C
</div>
If I execute this javascript:
$('div').each(function(index){
alert($(this).html());
$(this).remove();
}
Will I see three alerts or only two?
Regardless of what action you will perform on a selection, jQuery will make that selection before applying that action. By which I mean $('div') is a selector, the selection process happens before and regardless of the other chained methods (such as each()). This is a product of the language, since $() must be evaluated before a method can be called upon it.
If that selection grabbed three divs from your page, then there are now 3 jQuery objects in a list ready to be iterated over. You can prove this by doing:
$('div').length
Thus you are iterating over an array with three indexes (0, 1, 2), if you remove the div from the DOM for index 1, the next iteration of the each() callback will still be for the object at index 2. Checkout this live demo for proof:
DEMO: http://jsfiddle.net/marcuswhybrow/HYJa4/
jQuery will behave as you've described in #2. You'll see three alerts:
"A <div> B </div>"
"B"
"C"
However, since div B is inside div A, it will be removed with div A. jQuery still knows about it, and will alert its contents, but it will have already been removed by that time. So, although you get three alerts, you only have two removes (since it removes child elements).
2 is the answer.
jQuery gathers a list of matching elements, then loops over the list.
One important think though, do not remove any element that can be mached that has not already been processed or you will risk getting a reference not found error.