JS, DOM. Delete several elements from DOM using user intput value - javascript

Example on Codepen below. Can anybody explain as simple as posible how to delete a number of elements from DOM based on a user input value (just an integer). I have a path(SVG curve). User specifies a number of steps(dots) to reach the end point of the path. Then ship will travel to the end point, after some event ( for test I'm using click event ). User will specify a number of steps per move for each event ( event done ship start moving ). And by moving i want to delete dots on the pass that are left behind.
var trigger = window.addEventListener('click', function(){
var steps = parseInt(prompt("Select Number Of Steps Per Move "),10);
var positionVal = parseFloat(window.getComputedStyle(el,null).getPropertyValue("motion-offset"));
el.animate([
{ motionOffset: positionVal + "%" },
{ motionOffset: positionVal + 100/(userValue - 1)*steps + "%" }
],
{duration: 5000,
direction: 'alternate',
fill: 'forwards',
iterations: 1,
easing: 'ease-in-out'
});
function deleteThis () {
dotsArray.splice(positionVal, steps)
var dots = document.getElementsByClassName("dots");
for (i=0;i<steps;i++) {
dots[i].remove();
}
}
window.setTimeout(deleteThis,3000);
});
Codepen Example

If you want to remove DOM elements:
for (i=0;i<steps;i++) {
dotsArray[0].remove();
dotsArray.splice(0,1);
}

Sounds like you're asking how to remove elements from the DOM, which happen to be stored in an Array.
The Array that happens to be holding some DOM elements and the DOM/elements themselves are completely unrelated concepts. There will be no native Array method for dealing with the DOM, because Arrays are not a DOM concept, they are a general programming construct of JS. They could be holding DOM elements, Strings, Numbers, other Arrays, Objects, null Objects, or a combination of all the above...or more. There is nothing about Arrays that is specific to the DOM...that just happens to be what you have shoved into that Array.
For that you're simply gonna need to loop through the Array and remove the elements. I'd recommend making a function to do so if you're gonna be doing it a lot:
// removes the elements in the Array from the DOM, then removes them from the Array
function removeArrElemsFromDOM(arr, start, howMany)
{
for (var i=start; i<start+howMany; i++)
arr[i].parentNode.removeChild(arr[i]);
arr.splice(start, howMany);
}
You'd use like removeArrElemsFromDOM(myArray, 0, 2); would remove the first 2 elements of the array from the DOM (and also then cut them from the Array as well).
JSFiddle showing it working

Related

How can I separate items/ make new array of items that have positional values that are close together

Working Code Example
My Question:
How can I separate the events in my array that are close together? Specifically, .005deg in either lat or longitude. I need to use separate logic for these events because there is only one of these events a user can see as they are on top of each other, so once again I plan on having different logic for handling these events.
Code:
// filter through all events and only return lat and long values that have at least one duplicate
let duplicateEvents = events.filter((event, index, self) => {
// console.log({ lat: event.event_links.data[0].attributes.linkLabel, long: event.event_links.data[0].attributes.linkUrl }),
let eventLat = Number(event.event_links.data[0].attributes.linkLabel)
let eventLong = Number(event.event_links.data[0].attributes.linkUrl)
// Remove the current event item from the self array
let selfWithoutCurrentEvent = self.filter((item, i) => i !== index)
console.log({
selfWithoutCurrentEvent
});
// We must remove the current item form the self array because we don't want to compare the current item with itself
return selfWithoutCurrentEvent.map((otherEvent) => {
let otherEventLat = Number(otherEvent.event_links.data[0].attributes.linkLabel)
let otherEventLong = Number(otherEvent.event_links.data[0].attributes.linkUrl)
let latDiff = Math.abs(otherEventLat - eventLat);
let longDiff = Math.abs(otherEventLong - eventLong);
if ((latDiff <= 0.005) && (longDiff <= 0.005)) {
return true
} else {
console.log({
eventLat,
eventLong,
otherEventLat,
otherEventLong,
latDiff,
longDiff,
math: (latDiff <= 0.001) && (longDiff <= 0.001),
event
})
return false
}
// console.log({ latdif: Math.abs(eventLat - otherEventLat), longdif: Math.abs(eventLong - otherEventLong) })
})
})
console.log({
duplicateEvents
})
What I tried to do
I have tried first making a new array that removes the current item from the Array. So that we can test this array for similar coordinates. If I leave the current event it will always be returned because it's coordinates will match with itself. After removing the current item we compare its coordinates with all the other items in the array to see if any other items are within the .005 range. If so return true to the filter... But this isn't working
My Problem:
No matter how I try to filter the array of events I keep getting back every event which is weird because one of them should be removed from the selfWithoutCurrentEvent ARRAY no matter what every time. I think my problem has something to do with how returns are working with my multiple filters
Why I am doing it
I plan on having different logic for displaying the map pin to the map via react. What I will do is instead of having the pop-up display the event information I will first see if their coordinates are within the range of any of the events in that array of events that have other events within the .005 range of them (this is what I am unable to create right now). Then instead of displaying its information in the map pin pop-up, I will list the other events at that location, this will stop events from being hidden behind other map markers.
As you can see this pop-up displays information, but if there are 5 other events with the same location they can't see those other events. This is why I need to separate events that are on top of each other (within .005 lat || long of each other). Then when someone hovers on the pin we can display all the covered events links instead of just one event information.
Path to the Lat coordinate: event.event_links.data[0].attributes.linkLabel
Path to the Long coordinate: event.event_links.data[0].attributes.linkUrl
Working Code Example Again
You could have a look at how it's done for #googlemaps/markerclusterer https://github.com/googlemaps/js-markerclusterer/blob/main/src/algorithms/grid.ts#L113
The structure might differ a bit but the algorithm is rather simple (and does not need multiple layers of filters that could introduce bugs):
Loop on the events
for each event, check if they are "close" to the center of an existing cluster, if so add it to the cluster, else create a new cluster that only contains your event
This way you get a list of clusters of close events (which might contain only one event) and do whatever you want with them. Then you would need to recompute the clusters based on zoom, if the events are in the viewport...

Swapping elements in Angular JS

I was curious what the appropriate way is to animate and swap elements using angular. I have a two-dimensional array that has a nested ng-repeat to create the table. My end goal is to have two elements in the table swap places using an animation, so basically just hover over to eachother's positions.
I have this function that swaps the position of two jquery elements, but it doesn't update the array and ultimately the angular array will no longer be synced with what is displayed. I also find that the animation hangs a little when moving elements between rows whereas columns are smooth. ie. [0][0] -> [0][1] is smooth and [0][0] -> [1][0] hangs
jQuery.fn.swapWith = function (to, callback) {
animating = true;
thisPos = this.position();
toPos = to.position();
$.when(this.animate({
top: toPos.top,
left: toPos.left
}, 300),
to.animate({
top: thisPos.top,
left: thisPos.left
}, 300)).done(function () {
animating = false;
if (callback) {
callback();
}
});
};
I've also found this codepen which is pretty nice, but also not quite what I'm looking for. http://codepen.io/daleyjem/pen/xbZYpY Is there a standard way of handling something like this in angular?
To put in more simple terms: If I want array[0][0] to swap positions with array[0][1] and have the $scope object update appropriately, how should I do it? Same with array[0][0] to array[1][0]?
EDIT: I should mention, I have noticed that simply setting the elements equal to eachother will update the table appropriately. The problem being the lack of animation. example:
a = [0][1]
b = [0][0]
[0][1] = b
[0][0] = a

jquery/js too slow while operating over 2000 rows

I have 2000 rows of data as follows
<div class="rr cf">
<span>VLKN DR EXP</span>
<span>01046</span>
<span>VELANKANNI</span>
<span>20:30</span>
<span>DADAR</span>
<span>10:00</span>
</div>
On a button click I am checking for text within them and updating the display of each row to block or none. The code that does this is
$('.rr').each(function(){
this.style.display="block";
});
var nodes = $(".rr");
for(var i=0;i < nodes.length; i++) {
// if data found
nodes.get(i).style.display="block";
// else
nodes.get(i).style.display="none";
}
This seems to be possibly very slow. I get chrome alert box as to kill the page.
Any ideas? what optimization can I do here?
Local Variables and Loops
Another simple way to improve the performance of a loop is to
decrement the iterator toward 0 rather than incrementing toward the
total length. Making this simple change can result in savings of up to
50% off the original execution time, depending on the complexity of
each iteration.
Taken from: http://oreilly.com/server-administration/excerpts/even-faster-websites/writing-efficient-javascript.html
Try saving the nodes.length as a local variable so that the loop doesn't have to compute it each time.
Also, you can store nodes.get(i) into a local variable to save some time if you are accessing that data a lot.
If the order isn't important, consider decrementing your for loop towards 0.
jQuery's each() loop is a bit slower than looping through the set yourself as well. You can see here that there is a clear difference.
Very simple example
You'll see that in my example, I've condensed the loop into a while loop:
var nodes = $(".rr span");
var i = nodes.length;
while(i--){
if(i%2 === 0){
nodes.get(i).style.color = "blue";}
}​
Notice that the while loop decrements i through each iteration. This way when i = 0, the loop will exit, because while(0) evaluates to false.
"Chunking" the Array
The chunk() function is designed to process an array in small chunks
(hence the name), and accepts three arguments: a “to do” list of
items, the function to process each item, and an optional context
variable for setting the value of this within the process() function.
A timer is used to delay the processing of each item (100ms in this
case, but feel free to alter for your specific use). Each time
through, the first item in the array is removed and passed to the
process() function. If there’s still items left to process, another
timer is used to repeat the process.
Have a look at Nick Zakas's chunk method defined here, if you need to run the loop in sections to reduce the chance of crashing the browser:
function chunk(array, process, context){
setTimeout(function(){
var item = array.shift();
process.call(context, item);
if (array.length > 0){
setTimeout(arguments.callee, 100);
}
}, 100);
}
Using createDocumentFragment()
Since the document fragment is in memory and not part of the main DOM
tree, appending children to it does not cause page reflow (computation
of element's position and geometry). Consequently, using document
fragments often results in better performance.
DocumentFragment are supported in all browsers, even Internet Explorer
6, so there is no reason to not use them.
Reflow is the process by which the geometry of the layout engine's
formatting objects are computed.
Since you are changing the display property of these elements iteratively, the page mush 'repaint' the window for each change. If you use createDocumentFragment and make all the changes there, then push those to the DOM, you drastically reduce the amount of repainting necessary.
Firstly, where are the delays occurring - in the jquery code, or the data check? If it is the jquery, you could try detaching the data container element (ie the html element that contains all the .rr divs) from the DOM, make your changes, and then re-attach it. This will stop the browser re-processing the DOM after each change.
I would try
1) set display of the common parent element of all those divs, to "none"
2) loop through divs, setting display as appropriate
3) set parent element display back to block
I believe this will help because it gives the browser the opportunity to aggregate the rendering updates, instead of forcing it to fully complete each single time you change the display property. The visibility of all the child node are irrelevant if the parent isnt displayed, so the browser no longer has a need to render a change in each child until the parent becomes visible again.
ALso, I fail to see the purpose of you first looping through them all and setting them all to block before you loop again and set them to their intended value.
Don't use jQuery here, jQuery will just slow things down here.
var elements = document.getElementsByClassName('rr'),
len = elements.length;
for(var i = 0; i < len; i++)
{
var ele = elements[i];
if(ele.innerHTML.search(/01046/) != -1)
ele.style.display = "none";
}
This should be much faster.
I'm also having performance problems while looping through roughly 1500 items.
As you might have guessed, the loop itself isn't the bottleneck. It's the operation you do within it that's the problem.
So, what I migrated the load using setTimeout. Not the prettiest of solutions but it makes the browser responsive between the updates.
var _timeout_ = 0;
for(var i=0;i < nodes.length; i++)
{
setTimeout(
(function(i)
{
return function()
{
if(stuff)
{
nodes.get(i).style.display="block";
}
else
{
nodes.get(i).style.display="none";
}
}
})(i),
_timeout_
);
_timeout_ += 4;
}
This will delay every update with 4 milliseconds, if the operation takes longer, the browser will become unresponsive. If the operation takes only 2 milisecond on your slowest browser, you can set it to 3, etc. just play around with it.

Infinite loop when using generic solution to split up carousel contents

Basically, I'm getting an infinite loop and maybe I'm working too hard but I can't see why.
Context:
I'm using a carousel (Bootstrap's). The contents of the carousel is generated and pushed into one carousel slide, then the goal is to take the contents and split it up into multiple slides if the number of items inside surpass a certain pre-defined max-length property (5). I got this working fine for a specific use case of the carousel (a table being spread across the multiple slides if there are more than 5 table rows), but it's not generic enough. What happened is that the JS would take the overflown table rows (i.e. of index 5 and up), create a new slide from a harcoded HTML string in the function (a slide div containing all the markup for the table yet empty) and push those extra rows into it.
To make it more generic, I've decided to use classes like carousel_common_list and carousel_common_item which would be applied to the tbody and trs in the case I've explained. Then, I've to handle the template in a decoupled way. What I've tried to do is, take a clone of the original sole slide, empty the carousel_common_list and push any overflown carousel_common_items into it, and so on. But I get an infinite loop.
Code
What I've called a slide so far is called an item in the code (to match Bootstrap's carousel's item class for slides).
var carousels = $('div.carousel'),
carouselCommonListClass = 'carousel_common_list',
carouselCommonItemClass = 'carousel_common_item',
items_per_slide = 5;
$.each(carousels, function (index, element) {//for each carousel
var $carousel = carousels.eq(index),
$items = $carousel.find('.item');
var getItemTemplate = function ($item) {
$copy = $item.clone();//take the html, create a new element from it (not added to DOM)
$copy.find('.' + carouselCommonListClass).empty();
$copy.removeClass('active');
return $copy;
}
var splitUpItem = function ($item, $itemTemplate) {
var $bigList = $item.find('.' + carouselCommonListClass), group;
while ((group = $bigList.find('.' + carouselCommonItemClass + ':gt(' + (items_per_slide - 1 ) + ')').remove()).length) {
var $newItem = $itemTemplate;
$newItem.find('.' + carouselCommonListClass).prepend(group);
$newItem.insertAfter($item);
splitUpItem($newItem, $itemTemplate);//infintely called
}
}
//foreach item
$.each($items, function (item_index, item_element) {//for each slide, in each carousel
var $item = $items.eq(item_index);
splitUpItem($item, getItemTemplate($item));
});
});
FYI, this works like expected when the line marked with //infintely called is commented out; i.e. splits one oversized slide into one slide of items_per_slide length and another slide (which could be over items_per_slide in length if the original sole slide was over items_per_slide * 2 in length.
Also, I took this answer and modified it for the contents of splitUpItem().
Note:
I know it's not the most usable or accessible solution to split tables, lists, etc. over multiple slides like I am, but if you've a better idea answer my open question on that.
You're not getting an infinite loop per se, in that you're not infinitely stuck in the same while loop. As you mention, when you remove the //infinitely called line you're fine. The first pass through that while loop, the length you compute will equal the number of items (with gt:(4)) in all the lists in $item. You then remove all those items, so the next pass through will have that number equal to 0. This will always be the behaviour of that loop, so it really doesn't need to be a loop, but that's not the main problem.
The problem is that it's a recursive call. And the only guard you have against making the recursive call infinite is the condition in your while loop, but that condition will always be met the first pass through. In fact, if $item has 5 lists, each with 3 items with gt:(4), then $newItem will have 5 lists, each with 5 x 3 = 15 items. So when splitUpItem gets called on $newItem, the condition in your while loop will again first be non-zero. And then it'll get called again, and that number will be 5 x 15 = 75. And so on. In other words, you're recursively calling this function, and your guard against this call being made infinitely many times is to check that some number is 0, but the number there will actually grow exponentially with each recursive call of splitUpItem.
Hope that answers your question about why it's "infinitely looping." Gotta get to work, but I'll try to suggest a better way to split up the slides tomorrow if no one else has by then.

Is it possible to programmatically define a function from a string or some other technique without using eval()?

I am working on coding for a situation where I need to construct a function of nested callbacks of an unknown length. It is to create a sequenced animation queue to move an element across an unknown # of positions.
For example, output would look something like this with X 'complete' callbacks nested inside:
$('#element').animate(css, { complete: function () {
$('#element').animate(css, { complete: function () {
// more nested calls inside
}
});
Right now I am generating these functions as a string, and then once completed, feeding it to new Function():
myFunc = new Function(generatedFuncString);
The content is trusted but this still uses eval() which has negative performance implications. I was just wondering if there is another/better way?
edit: The reason I am doing it this way is because I have a very complicated set of animations to perform and am working outside of the jQuery animation queue. If anyone has a better suggestion for how to accomplish a situation like this that would be helpful...
Imagine a baseball diamond with a runner(A) on 1st and a runner(B) on 3rd. In one animation bundle, I want to animate runner A to 3rd (stopping at 2nd in the middle, 2 advances), and runner B to HOME (1 advance).
I have to fire-off the initial advance with 'queue: false' so that runner A and B move to their first base at the same time (runner A to 2nd, runner B to home).
When Runner A is done moving to 2nd, I want to then move him to 3rd (hence constructing a animate() call with nested callbacks pro grammatically to ensure this sequencing is preserved).
The reason I am constructing the function via string is because I know what the inner-most callback is going to be first, and then recursively constructed 1 or more outer-callbacks from there. I couldn't figure out a way to do this by working with functions as objects and keeping all of the references in tact.
Keep in mind this is a simple example. Imagine a situation where the bases are loaded, and I need to animate a grand slam (all 4 runners circle all bases, runner originating at home needs to make 3 stops before running back to home). Etc etc.
Answering the question you ask in your title: You can create functions from strings via eval, new Function, and by inserting a script element with the text you want. But it all comes to the same thing: Firing up the JavaScript parser and creating the function.
But rather than nesting, I think you want chaining. Build a list of the animations in an array, and use the animate callback to call the next animation in the array. Something like:
var animations = [
/* css for first animation */,
/* css for second animation */,
/* etc. */
];
var index = 0;
function runAnimation() {
if (index < animations.length) {
$("#element").animate(animations[index++], runAnimation);
}
}
You'd build up the array dynamically, of course.
gdoron points out in the comments that if all of your animations are on the same element, it can be even simpler:
var animations = [
/* css for first animation */,
/* css for second animation */,
/* etc. */
];
var index = 0;
for (index = 0; index < animations.length; ++index) {
$("#element").animate(animations[index]);
}
...because when you call animate multiple times on the same element, by default the animations queue up (the queue option defaults to true; sadly the docs don't seem to say that). My code example above doesn't rely on the queue, and so in theory each entry in the array could be an object with a property for the selector for the elements to animate and the css to apply. But if it's all one element, you can just use a straight loop.

Categories