Parent node is null after replaceWith jQuery method - javascript

I'm using jQuery to manipulate DOM in my project. I've got class method, that works like this:
<!-- language: lang-js -->
var template = this._Template.split('{0}');
var content = template[0] + this._Content + template[1];
if (!this._BlockNode) {
this._BlockNode = $(content);
this._ParentNode.append(this._BlockNode);
}
else {
this._BlockNode.replaceWith(content);
}
Everything is ok on the first call of this method, because it creates node and appends it to parent node. The second call (using replaceWith() method) works ok too. But after it property this._BlockNode[0].parentNode is null. So when I call it third time and replaceWith() works with new _.BlockNode without .parentNode property it does not replace content of node because of this check: if ( !isDisconnected( this[0] ) ) { //line 5910 in jQuery 1.8.3.
How to deal with it?

You need to ensure that _BlockNode always points to the current version of the content.
When you call replaceWith you correctly update the DOM structure, but fail to update the contents of your object. The original _BlockNode ends up orphaned, and all subsequent replaceWith calls work on that node and not on the newer content.
Try this:
var template = this._Template.split('{0}');
var $content = $(template[0] + this._Content + template[1]);
if (!this._BlockNode) {
this._ParentNode.append($content);
} else {
this._BlockNode.replaceWith($content);
}
this._BlockNode = $content;
It may be preferable to hold a native DOM element in _BlockNode rather than a jQuery object.

Related

How to alter DOM with xmldom by XPath in node.js?

I am trying to alter a DOM structure in node.js. I can load the XML string and alter it with the native methods in xmldom (https://github.com/jindw/xmldom), but when I load XPath (https://github.com/goto100/xpath) and try to alter the DOM via that selector, it does not work.
Is there another way to do this out there? The requirements are:
Must work both in the browser and server side (pure js?)
Cannot use eval or other code execution stuff (for security)
Example code to show how I am trying today below, maybe I simply miss something basic?
var xpath = require('xpath'),
dom = require('xmldom').DOMParser;
var xml = '<!DOCTYPE html><html><head><title>blah</title></head><body id="test">blubb</body></html>';
var doc = new dom().parseFromString(xml);
var bodyByXpath = xpath.select('//*[#id = "test"]', doc);
var bodyById = doc.getElementById('test');
var h1 = doc.createElement('h1').appendChild(doc.createTextNode('title'));
// Works fine :)
bodyById.appendChild(h1);
// Does not work :(
bodyByXpath.appendChild(h1);
console.log(doc.toString());
bodyByXpath is not a single node. The fourth parameter to select, if true, will tell it to only return the first node; otherwise, it's a list.
As aredridel states, .select() will return an array by default when you are selecting nodes. So you would need to obtain your node from that array.
You can also use .select1() if you only want to select a single node:
var bodyByXpath = xpath.select1('//*[#id = "test"]', doc);

TypeError: undefined is not a function jQuery

I'm new to jQuery and I can get it to sometimes work, however, for some reason, when I try to call a function, it gives me the title error, but if I do it in developer tools, it works fine.
http://jsfiddle.net/otanan/pmzzLo3e/#&togetherjs=AezijhfBrj
It seems to work fine when retrieving the classes from the DOM, but not when I call a function such as
.click(function() {});
Here's the code:
var downloads = $(".info"),
className = "info_clicked";
for(var i in downloads)
{
downloads[i].click(function()
{
if(!this.hasClass(className))
this.addClass(className);
else
this.removeClass(className);
});
}
When you access a jQuery collection as an array, it returns the DOM elements, not jQuery objects. You should use .each() rather than for (i in downloads):
downloads.each(function() {
$(this).click(function() {
if (!$(this).hasClass(className)) {
$(this).addClass(className);
} else {
$(this).removeClass(className);
}
});
});
You could also simplify the whole thing to:
downloads.click(function() {
$(this).toggleClass(className);
});
Most jQuery methods automatically iterate over all the elements in a collection if it makes sense to do so (the notable exceptions are methods that return information from the element, like .text() or .val() -- they just use the first element). So you generally only have to iterate explicitly if you need to do different things for each element. This is one of the great conveniences of using jQuery rather than plain JS: you rarely have to write explicit iterations.
I think the issue is that you're attempting to call a jQuery function on an object that is no longer a jQuery object.
For example you're saying $(".info"). Which retrieves a single jQuery object. As soon as you index that object downloads[i] it is no longer a jQuery object, it is a plain HTML element and does not have a click function available.
What you really need to do is get the jQuery object for the indexed item:
var downloads = $(".info"),
className = "info_clicked";
for(var i = 0; i < downloads.length; i++)
{
$(downloads[i]).click(function()
{
if(!this.hasClass(className))
this.addClass(className);
else
this.removeClass(className);
});
}
try it:
$(downloads[i]).click(function(){ //...

How to update cached jquery object after adding elements via AJAX

I'm trying to write a plugin-like function in jQuery to add elements to a container with AJAX.
It looks like this:
$.fn.cacheload = function(index) {
var $this = $(this);
$.get("cache.php", {{ id: index }).done(function(data) {
// cache.php returns <div class='entry'>Content</div> ...
$(data).insertAfter($this.last());
});
}
and I would like to use it like this:
var entries = $("div.entry"),
id = 28;
entries.cacheload(id);
Think that this would load another "entry"-container and add it to the DOM.
This is works so far. But of course the variable that holds the cached jQuery object (entries) isn't updated. So if there were two divs in the beginning and you would add another with this function it would show in the DOM, but entries would still reference the original two divs only.
I know you can't use the return value of get because the AJAX-call is asynchronous. But is there any way to update the cached object so it contains the elements loaded via AJAX as well?
I know I could do it like this and re-query after inserting:
$.get("cache.php", {{ id: num }).done(function(data) {
$(data).insertAfter($this.last());
entries = $("div.entry");
});
but for this I would have to reference the variable holding the cached objects directly.
Is there any way around this so the function is self-contained?
I tried re-assigning $(this), but got an error. .add() doesn't update the cached object, it creates a new (temporary) object.
Thanks a lot!
// UPDATE:
John S gave a really good answer below. However, I ended up realizing that for me something else would actually work better.
Now the plugin function inserts a blank element (synchronously) and when the AJAX call is complete the attributes of that element are updated. That also ensures that elements are loaded in the correct order. For anyone stumbling over this, here is a JSFiddle: http://jsfiddle.net/JZsLt/2/
As you said yourself, the ajax call is asynchronous. Therefore, your plugin is asynchronous as as well. There's no way for your plugin to add the new elements to the jQuery object until the ajax call returns. Plus, as you discovered, you can't really add to the original jQuery object, you can only create a new jQuery object.
What you can do is have the plugin take a callback function as a second parameter. The callback could be passed a jQuery object that contains the original elements plus the newly inserted ones.
$.fn.cacheload = function(index, callback) {
var $this = this;
$.get('cache.php', { id: index }).done(function(html) {
var $elements = $(html);
$this.last().after($elements);
if (callback) {
callback.call($this, $this.add($elements));
}
});
return $this;
};
Then you could call:
entries.cacheload(id, function($newEntries) { doSomething($newEntries); } );
Of course, you could do this:
entries.cacheload(id, function($newEntries) { entries = $newEntries; } );
But entries will not be changed until the ajax call returns, so I don't see much value in it.
BTW: this inside a plugin refers to a jQuery object, so there's no need to call $(this).

Intercept append method on dom

i need a way to parsing the dynamic content of webpage.
For example if I need to append a class for every element with a class "test" i can do like that :
$(".test").each(function(index, element)
{
if(!$(this).data("parsed"))
{
$(this).data("parsed", true);
//all my operation - Example :
$(this).addClass("new-class");
}
});
That work, but only for the html at this time.
If on second time I append new html to my page, that will not parsed by my script.
I would a way for do that.
There is a method called when the content change? For example on every append, html, ajax request etc?
That could be one own solution :
var original_append = $.fn.append;
$.fn.append = function()
{
console.log(arguments);
//my code
return original_append.apply(this, arguments);
}

adding a new div after another one

I am trying to add a new div right after a div with the class of dtext01 but its not working.
Here is what the div looks like with the class <div class="dtext01"></div> and I would like the final code to look like this <div class="dtext01"></div><div id="rewards">Testing!!!</div>
Here is the code I have tried with no success.
<script type="text/javascript">
var stickyNode = document.createElement("div");
stickyNode.innerHTML = 'Testing!!!';
stickyNode.id = "rewards";
var referenceNode = document.getElementByClass("dtext01");
referenceNode.parentNode.insertBefore(stickyNode, referenceNode);
</script>
There is no document.getElementByClass(). There are document.getElementsByClassName() and querySelectorAll() which can be used for fetching elements by className, but they are not standard across browsers.
You can always import a cross browser getElementsByClassName from a reliable source, such as this one, or write your own - or just use IDs instead of classes if you have a choice.
The function you are looking for is document.getElementsByClassName (note the plural on Elements). It is a fairly recent addition to the HTMLElement interface, so you should test for it and provide a fallback if it isn't supported.
To append your div just after the first node returned, use:
var referenceNode;
if (document.getElementsByClassName) {
referenceNode = document.getElementsByClassName("dtext01")[0];
} else {
// fallback to some other function
}
// Make sure you got a node before trying to call methods on it
if (referenceNode) {
referenceNode.parentNode.insertBefore(stickyNode, referenceNode);
}

Categories