KinteticJS - destroying nodes, infinite while loop because child doesn't get removed - javascript

KinerticJS version: 4.4.3
I have this problem when i want to remove a layer from stage. When I call Layer.destroy() kinetic runs upon an node which can't be removed. I don't get any error, but the while loop gets infinite since the loop is based on the length of the children.
destroy: function (_nameSpace) {
var parent = this.getParent(),
stage = this.getStage(),
dd = Kinetic.DD,
go = Kinetic.Global;
var tempLength
// destroy children
while (this.children && this.children.length > 0) {
this.children[0].destroy();
}
}
The object which, in my case, can not be removed is a Kinetic.Image. When I trace the node type it returns a Shape (which is correct). Also, i can trace all the stuff which i want to know from the object...
Rectangles do get removed.
I created a low level test in fiddle, and there everything is working fine and dandy, so it has te be something with my code. Than again, the node whicht is not being removed IS a valid object, so why does is not being removed?
I created a error message, so i wouldn't crash my browser everytime:
tempLength = this.children.length
this.children[0].destroy();
if (tempLength == this.children.length) {
throw 'item not removed ' + this.children[0].getNodeType();
}
As you can see i check right after i destroyed an item if the length of the children did change, so i can assume thah no other code is interfering. For example adding an node when destroying one.
I'm at a dead end here. I hope somebody can help me out or point me in any direction. Anyway, thanks for reading :)

Ok, i figured out what the issue was. Somehow i aadded a same Kinetic.Image twice to a layer. When destroying the layer it could not remove it's child.
I assume this is an Kinetic bug. I should not be able to break the framework so (relatively) easy.
https://github.com/ericdrowell/KineticJS/issues/434

Related

Safely / completely remove element from DOM [duplicate]

When removing an element with standard JavaScript, you must go to its parent first:
var element = document.getElementById("element-id");
element.parentNode.removeChild(element);
Having to go to the parent node first seems a bit odd to me, is there a reason JavaScript works like this?
I know that augmenting native DOM functions isn't always the best or most popular solution, but this works fine for modern browsers.
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = this.length - 1; i >= 0; i--) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
And then you can remove elements like this
document.getElementById("my-element").remove();
or
document.getElementsByClassName("my-elements").remove();
Note: this solution doesn't work for IE 7 and below. For more info about extending the DOM read this article.
EDIT: Reviewing my answer in 2019, node.remove() has come to the rescue and can be used as follows (without the polyfill above):
document.getElementById("my-element").remove();
or
[...document.getElementsByClassName("my-elements")].map(n => n && n.remove());
These functions are available in all modern browsers (not IE). Read more on MDN.
Crossbrowser and IE >= 11:
document.getElementById("element-id").outerHTML = "";
You could make a remove function so that you wouldn't have to think about it every time:
function removeElement(id) {
var elem = document.getElementById(id);
return elem.parentNode.removeChild(elem);
}
Update 2011
This was added to the DOM spec back in 2011, so you can just use:
element.remove()
The DOM is organized in a tree of nodes, where each node has a value, along with a list of references to its child nodes. So element.parentNode.removeChild(element) mimics exactly what is happening internally: First you go the parent node, then remove the reference to the child node.
As of DOM4, a helper function is provided to do the same thing: element.remove(). This works in 96% of browsers (as of 2020), but not IE 11.
If you need to support older browsers, you can:
Remove elements via the parent node
Modify the native DOM functions, as in Johan Dettmar's answer, or
Use a DOM4 polyfill.
It's what the DOM supports. Search that page for "remove" or "delete" and removeChild is the only one that removes a node.
For removing one element:
var elem = document.getElementById("yourid");
elem.parentElement.removeChild(elem);
For removing all the elements with for example a certain class name:
var list = document.getElementsByClassName("yourclassname");
for(var i = list.length - 1; 0 <= i; i--)
if(list[i] && list[i].parentElement)
list[i].parentElement.removeChild(list[i]);
you can just use element.remove()
You can directly remove that element by using remove() method of DOM.
here's an example:
let subsWrapper = document.getElementById("element_id");
subsWrapper.remove();
//OR directly.
document.getElementById("element_id").remove();
The ChildNode.remove() method removes the object from the tree it belongs to.
https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
Here is a fiddle that shows how you can call document.getElementById('my-id').remove()
https://jsfiddle.net/52kp584L/
**
There is no need to extend NodeList. It has been implemented already.
**
According to DOM level 4 specs, which is the current version in development, there are some new handy mutation methods available: append(), prepend(), before(), after(), replace(), and remove().
https://catalin.red/removing-an-element-with-plain-javascript-remove-method/
You can simply use
document.getElementById("elementID").outerHTML="";
It works in all browsers, even on Internet Explorer.
Having to go to the parent node first seems a bit odd to me, is there a reason JavaScript works like this?
The function name is removeChild(), and how is it possible to remove the child when there's no parent? :)
On the other hand, you do not always have to call it as you have shown. element.parentNode is only a helper to get the parent node of the given node. If you already know the parent node, you can just use it like this:
Ex:
// Removing a specified element when knowing its parent node
var d = document.getElementById("top");
var d_nested = document.getElementById("nested");
var throwawayNode = d.removeChild(d_nested);
https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
=========================================================
To add something more:
Some answers have pointed out that instead of using parentNode.removeChild(child);, you can use elem.remove();. But as I have noticed, there is a difference between the two functions, and it's not mentioned in those answers.
If you use removeChild(), it will return a reference to the removed node.
var removedChild = element.parentNode.removeChild(element);
console.log(removedChild); //will print the removed child.
But if you use elem.remove();, it won't return you the reference.
var el = document.getElementById('Example');
var removedChild = el.remove(); //undefined
https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
This behavior can be observed in Chrome and FF. I believe It's worth noticing :)
Hope my answer adds some value to the question and will be helpful!!
Functions that use ele.parentNode.removeChild(ele) won't work for elements you've created but not yet inserted into the HTML. Libraries like jQuery and Prototype wisely use a method like the following to evade that limitation.
_limbo = document.createElement('div');
function deleteElement(ele){
_limbo.appendChild(ele);
_limbo.removeChild(ele);
}
I think JavaScript works like that because the DOM's original designers held parent/child and previous/next navigation as a higher priority than the DHTML modifications that are so popular today. Being able to read from one <input type='text'> and write to another by relative location in the DOM was useful in the mid-90s, a time when the dynamic generation of entire HTML forms or interactive GUI elements was barely a twinkle in some developer's eye.
Shortest
I improve Sai Sunder answer because OP uses ID which allows to avoid getElementById:
elementId.remove();
box2.remove(); // remove BOX 2
this["box-3"].remove(); // remove BOX 3 (for Id with 'minus' character)
<div id="box1">My BOX 1</div>
<div id="box2">My BOX 2</div>
<div id="box-3">My BOX 3</div>
<div id="box4">My BOX 4</div>
Having to go to the parent node first seems a bit odd to me, is there
a reason JavaScript works like this?
IMHO: The reason for this is the same as I've seen in other environments: You are performing an action based on your "link" to something. You can't delete it while you're linked to it.
Like cutting a tree limb. Sit on the side closest to the tree while cutting or the result will be ... unfortunate (although funny).
From what I understand, removing a node directly does not work in Firefox, only Internet Explorer. So, to support Firefox, you have to go up to the parent to remove it's child.
Ref: http://chiragrdarji.wordpress.com/2007/03/16/removedelete-element-from-page-using-javascript-working-in-firefoxieopera/
This one actually comes from Firefox... for once, IE was ahead of the pack and allowed the removal of an element directly.
This is just my assumption, but I believe the reason that you must remove a child through the parent is due to an issue with the way Firefox handled the reference.
If you call an object to commit hari-kari directly, then immediately after it dies, you are still holding that reference to it. This has the potential to create several nasty bugs... such as failing to remove it, removing it but keeping references to it that appear valid, or simply a memory leak.
I believe that when they realized the issue, the workaround was to remove an element through its parent because when the element is gone, you are now simply holding a reference to the parent. This would stop all that unpleasantness, and (if closing down a tree node by node, for example) would 'zip-up' rather nicely.
It should be an easily fixable bug, but as with many other things in web programming, the release was probably rushed, leading to this... and by the time the next version came around, enough people were using it that changing this would lead to breaking a bunch of code.
Again, all of this is simply my guesswork.
I do, however, look forward to the day when web programming finally gets a full spring cleaning, all these strange little idiosyncracies get cleaned up, and everyone starts playing by the same rules.
Probably the day after my robot servant sues me for back wages.
// http://javascript.crockford.com/memory/leak.html
// cleans dom element to prevent memory leaks
function domPurge(d) {
var a = d.attributes, i, l, n;
if (a) {
for (i = a.length - 1; i >= 0; i -= 1) {
n = a[i].name;
if (typeof d[n] === 'function') {
d[n] = null;
}
}
}
a = d.childNodes;
if (a) {
l = a.length;
for (i = 0; i < l; i += 1) {
domPurge(d.childNodes[i]);
}
}
}
function domRemove(id) {
var elem = document.getElementById(id);
domPurge(elem);
return elem.parentNode.removeChild(elem);
}
This is the best function to remove an element without script error:
function Remove(EId)
{
return(EObj=document.getElementById(EId))?EObj.parentNode.removeChild(EObj):false;
}
Note to EObj=document.getElementById(EId).
This is ONE equal sign not ==.
if element EId exists then the function removes it, otherwise it returns false, not error.

Openlayers 3 - How to garbage collect Canvas ReplayGroup objects?

I've got an issue with ol.render.canvas.ReplayGroup objects not being let go to garbage collection.
The layer that this is for is an ol.layer.Image, created from an ol.source.ImageVector, in turn created from an ol.source.Vector source.
The sequence of events that I'd like to have result in some garbage collection is,
the Image's style is set to null with setStyle(null);
then the Image's source is set to null with setSource(null);
then the ol.layer.Image object is removed from the map with setMap(null);
This does result in the layer being removed from the map ( I think, it disappears ), but when I profile the web page with Chrome's heap allocation profile, the canvas.ReplayGroup object is still there, never to be used again.
Is this something anyone else has run into? I've tried to use the map.addLayer() instead of layer.setMap( ), same results.
== edit ==
I forgot to write, the ol.layer.Image has been added to an ol.layer.Group. More specifically, that last part above was map.addLayer( group ) and also group.getLayers().forEach(function(l){ l.setMap(map); }), no difference it seems.
https://github.com/openlayers/ol3/blob/master/src/ol/source/imagevectorsource.js
In the ol.source.ImageVector that's the source for the ol.layer.Image, I found a reference to the replay group classes called replayGroup_. Setting that 'private' property to null after setting the layer source to null results in garbage collection.. yay!
var imageVector = layer.getSource();
layer.setSource(null);
imageVector.setStyle(null);
imageVector.replayGroup_ = null;
imageVector = null;
This works for me for now

Counting occurrences in JavaScript

I'm building a simple puzzle app for and iPad using a JavaScript library for dragging and dropping the pieces. I can't figure out how to check if all the pieces are in the right place however.
What I've got so far is
// create the draggable puzzle piece
var p1=document.getElementById('piece1');
new webkit_draggable(p1);
p1.className = 'ps1';
// create an array to count the pieces placed correctly
var countArray = new Array();
// create the dropspot, link it to the puzzle piece and add an element to the array to count. Finally kill the dropspot
var p1p1 = webkit_drop.add('droppiece1',{accept : ['ps1'], onDrop : function(){countArray.push("a");webkit_drop.remove('droppiece1')}});
// piece has been placed correctly.
if(countArray.length = 1){
alert('Piece placed correctly');
};
The problem I'm having is that the alert from the counting function fires immediately.
How can I fix this?
Change your last three lines to:
// piece has been placed correctly.
if(countArray.length == 1){
alert('Piece placed correctly');
};
You were trying to assign, instead of checking for equality.
Edit: So, it's still not working for you? It seems to me you are setting up a listener on onDrop but then directly after you have done that (presumably before the onDrop event ever gets triggered) you are checking if anything has been "dropped". See how that won't work?
If you just want to see that the event is actually getting triggered, and that the "a" is in fact pushed onto your array you could move the last three lines inside your callback. Like so:
// create the draggable puzzle piece
var p1 = document.getElementById('piece1');
new webkit_draggable(p1);
p1.className = 'ps1';
// create an array to count the pieces placed correctly
var countArray = new Array();
// create the dropspot, link it to the puzzle piece and add an element to the array to count. Finally kill the dropspot
var p1p1 = webkit_drop.add('droppiece1', {accept: ['ps1'], onDrop: function() {
countArray.push("a");
webkit_drop.remove('droppiece1');
// piece has been placed correctly.
if (countArray.length == 1) {
alert('Piece placed correctly');
}
}});
I haven't really tested that, but here is a version with all the webkit stuff taken out and the drop replaced with a simple click: http://jsfiddle.net/kajic/snJMr/1/
If you click the red box, is that what you expected to happen on your ipad app when you drop the piece?
You should change this
if(countArray.length = 1){
to
if(countArray.length == 1){
// ^^ use comparison, not assignment
The first line assigns 1 to countArray.length, which resolves to 1, which is truthy.

IE javascript problem with break or classname

i'm not really sure whats the problem here, it works with chrome and ff but not ie.
var newdiv = document.createElement("div");
newdiv.innerHTML = xhr.responseText;
el = newdiv.firstChild.nextSibling;
var next;
do{
next = addpoint.nextSibling;
if(next.className != "commentstyle golduser") break;
}while(addpoint = next);
document.getElementById("testimonialcommentlisting"+id).insertBefore(el,next);
error is at this line document.getElementById("testimonialcommentlisting"+id).insertBefore(el,next);
**UPDATE**
ok this is werid, i did some test and i found the problem. the problem is with var el
for chrome and ff el is div element while ie is null.
here comes the weirder problem. By right, newdiv.firstChild should be the div element but i dont know why ff and chrome register it as a text element and clearly my responseText is something like this
<div>blahblah</div>
i hope somebody understands what i'm talking about.
Your loop follows an odd construction. You're fetching addpoint.nextSibling then looking at its className attribute, and only then checking to see if the node is valid. This means on the last iteration through the loop, when next is null, you're trying to access the className property of null.
while (next) {
if (next.className != 'commentstyle golduser') break;
next = next.nextSibling;
}
Without more context from what surrounds this loop I don't know how addpoint fits in. It's not necessary just to make the loop work, though if you do need to change addpoint there's no reason you can't do so using this loop construction.
The point is that you are never checking next.className when next is null.

Removing items from data bound array

How do I remove an items from a data bound array? My code follows.
for(var i = 0; i < listBox.selectedIndices.length; i++) {
var toRemove = listFiles.selectedIndices[i];
dataArray.splice(toRemove, 1);
}
Thanks in advance!
Edit Here is my swf. The Add Photos works except when you remove items.
http://www.3rdshooter.com/Content/Flash/PhotoUploader.html
Add 3 photos different.
Remove 2nd photo.
Add a different photo.
SWF adds the 2nd photo to the end.
Any ideas on why it would be doing this?
Edit 2 Here is my code
private function OnSelectFileRefList(e:Event):void
{
Alert.show('addstart:' + arrayQueue.length);
for each (var f:FileReference in fileRefList.fileList)
{
var lid:ListItemData = new ListItemData();
lid.fileRef = f;
arrayQueue[arrayQueue.length]=lid;
}
Alert.show('addcomplete:' + arrayQueue.length);
listFiles.executeBindings();
Alert.show(ListItemData(arrayQueue[arrayQueue.length-1]).fileRef.name);
PushStatus('Added ' + fileRefList.fileList.length.toString() + ' photo(s) to queue!');
fileRefList.fileList.length = 0;
buttonUpload.enabled = (arrayQueue.length > 0);
}
private function OnButtonRemoveClicked(e:Event):void
{
for(var i:Number = 0; i < listFiles.selectedIndices.length; i++) {
var toRemove:Number = listFiles.selectedIndices[i];
//Alert.show(toRemove.toString());
arrayQueue.splice(toRemove, 1);
}
listFiles.executeBindings();
Alert.show('removecomplete:' + arrayQueue.length);
PushStatus('Removed photos from queue.');
buttonRemove.enabled = (listFiles.selectedItems.length > 0);
buttonUpload.enabled = (arrayQueue.length > 0);
}
It would definitely be helpful to know two things:
Which version of ActionScript are you targeting?
Judging from the behavior of your application, the error isn't occurring when the user removes an item from the list of files to upload. Looks more like an issue with your logic when a user adds a new item to the list. Any chance you could post that code as well?
UPDATE:
Instead of: arrayQueue[arrayQueue.length]=lid
Try: arrayQueue.push(lid)
That will add a new item to the end of the array and push the item in to that spot.
UPDATE 2:
Ok, did a little more digging. Turns out that the fileList doesn't get cleared every time the dialog is opened (if you're not creating a new instance of the FileReferenceList each time the user selects new files). You need to call splice() on the fileList after you add each file to your Array.
Try something like this in your AddFile() method...
for(var j:int=0; j < fileRefList.fileList.length; j++)
{
arrayQueue.push(fileRefList.fileList[j]);
fileRefList.fileList.splice(j, 1);
}
That will keep the fileList up to date rather than holding on to previous selections.
I see one issue. The selected indices are no longer valid once you have spliced out the first element from the array. But that should only be a problem when removing multiple items at once.
I think we need to see more code about how you are handling the upload before we can figure out what is going on. It looks to me like you are holding a reference to the removed FileReference or something. The described problem is occurring when you upload a new file, not when you remove the selected one.
Do you mean to use listBox and listFiles to refer to the same thing?
I'm stepping out on a limb here, because I don't have a ton of experience with JavaScript, but I'd do this the same way that I'd do it in C, C++, or Java: By copying the remaining array elements down into their new locations.
Assuming that listFiles.selectedIndices is sorted (and its contents are valid indices for dataArray), the code would be something like the following:
(WARNING: untested code follows.)
// Don't bother copying any elements below the first selected element.
var writeIndex = listFiles.selectedIndices[0];
var readIndex = listFiles.selectedIndices[0] + 1;
var selectionIndex = 1;
while(writeIndex < (dataArray.length - listFiles.selectedIndices.length)) {
if (selectionIndex < listFiles.selectedIndices.length) {
// If the read pointer is currently at a selected element,
// then bump it up until it's past selected range.
while(selectionIndex < listFiles.selectedIndices.length &&
readIndex == listFiles.selectedIndices[selectionIndex]) {
selectionIndex++;
readIndex++;
}
}
dataArray[writeIndex++] = dataArray[readIndex++];
}
// Remove the tail of the dataArray
if (writeIndex < dataArray.length) {
dataArray.splice(writeIndex, dataArray.length - writeIndex);
}
EDIT 2009/04/04: Your Remove algorithm still suffers from the flaw that as you remove items in listFiles.selectedIndices, you break the correspondence between the indices in arrayQueue and those in listFiles.selectedIndices.
To see this, try adding 3 files, then doing "Select All" and then hit Remove. It will start by removing the 1st file in the list (index 0). Now what had been the 2nd and 3rd files in the list are at indices 0 and 1. The next value taken from listFiles.selectedIndices is 1 -- but now, what had been the 3rd file is at index 1. So the former File #3 gets spliced out of the array, leaving the former 2nd file un-removed and at index 0. (Using more files, you'll see that this implementation only removes every other file in the array.)
This is why my JavaScript code (above) uses a readIndex and a writeIndex to copy the entries in the array, skipping the readIndex over the indices that are to be deleted. This algorithm avoids the problem of losing correspondence between the array indices. (It does need to be coded carefully to guard against various edge conditions.) I tried some JavaScript code similar to what I wrote above; it worked for me.
I suspect that the problem in your original test case (removing the 2nd file, then adding another) is analogous. Since you've only shown part of your code, I can't tell whether the array indices and the data in listFiles.selectedIndices, arrayQueue, and fileRefList.fileList are always going to match up appropriately. (But I suspect that the problem is that they don't.)
BTW, even if you fix the problem with using splice() by adjusting the array index values appropriately, it's still an O(N2) algorithm in the general case. The array copy algorithm is O(N).
I'd really need to see the whole class to provide a difinitive answer, but I would write a method to handle removing multiple objects from the dataProvider and perhaps assigning a new array as the dataProvider for the list instead of toying with binding and using the same list for the duration. Like I said, this is probably inefficient, and would require a look at the context of the question, but that is what I would do 9unless you have a big need for binding in this circumstance)
/**
* Returns a new Array with the selected objects removed
*/
private function removeSelected(selectedItems:Array):Array
{
var returnArray:Array = []
for each(var object:Object in this.arrayQueue)
{
if( selectedItems.indexOf(object)==-1 )
returnArray.push( object )
}
return returnArray;
}
You might be interested in this blog entry about the fact that robust iterators are missing in the Java language.
The programming language, you mentioned Javascript, is not the issue, it's the concept of robust iterators that I wanted to point out (the paper actually is about C++ as the programming language).
The [research document]() about providing robust iterators for the ET++ C++ framework may still e helpful in solving your problem. I am sure the document can provide you with the necessary ideas how to approach your problem.

Categories