I am using Adobe Flex/Air here, but as far as I know this applies to all of JavaScript. I have come across this problem a few times, and there must be an easy solution out there!
Suppose I have the following XML (using e4x):
var xml:XML = <root><example>foo</example></root>
I can change the contents of the example node using the following code:
xml.example = "bar";
However, if I have this:
var xml:XML = <root>foo</root>
How do i change the contents of the root node?
xml = "bar";
Obviously doesn't work as I'm attempting to assign a string to an XML object.
It seems you confuse variables for the values they contain. The assignment
node = textInput.text;
changes the value the variable node points to, it doesn't change anything with the object that node currently points to. To do what you want to do you can use the setChildren method of the XML class:
node.setChildren(textInput.text)
Ah thank you Theo - indeed seems I was confused there. I think the root of the confustion came from the fact I was able to assign
textInput.text = node;
Which I now guess is just implicity calling XML.toString() to convert XML->String. setChildren() is what I was looking for.
If you're trying to change the root element of a document, you don't really need to-- just throw out the existing document and replace it. Alternatively, just wrap your element in a more proper root element (you shouldn't be editing the root node anyway) and you'd be set.
Of course, that doesn't answer your question. There's an ugly JS hack that can do what you want, but bear in mind that it's likely far slower than doing the above. Anyway, here it is:
var xml = <root>foo</root>; // </fix_syntax_highlighter>
var parser = new DOMParser();
var serializer = new XMLSerializer();
// Parse xml as DOM document
// Must inject "<root></root>" wrapper because
// E4X's toString() method doesn't give it to us
// Not sure if this is expected behaviour.. doesn't seem so to me.
var xmlDoc = parser.parseFromString("<root>" +
xml.toString() + "</root>", "text/xml");
// Make the change
xmlDoc.documentElement.firstChild.nodeValue = "CHANGED";
// Serialize back to string and then to E4X XML()
xml = new XML(serializer.serializeToString(xmlDoc));
You can ignore the fix_syntax_highlighter comment.
Related
So let's say I have a WYSIWYG (Tiny MCE in my case) and I take some input.
I want to look at that input and extract any base64 images and then send those somewhere to be converted to URL's in an AWS bucket.
I can do the last bit but I'm stuck with the middle part which is to extract the elements from a variable.
This is what I am trying that does not work ["TypeError: Cannot read property 'getElementsByTagName' of undefined"]
let dombody = document.createElement("div").append(mcebody);
let imglist = dombody.getElementsByTagName("img");
Thanks for any ideas or suggestions in advance. Perhaps this is not possible.
.append has no return value, so dombody is undefined, so an error is thrown when you try to call a method on it.
Assign the dombody in an earlier statement instead (and remember to use const instead of let when possible):
const dombody = document.createElement("div");
dombody.append(mcebody);
const imglist = dombody.getElementsByTagName("img");
I have a little testcase over at:
http://jsfiddle.net/9xwUx/1/
The code boils down to the following (given a node with id "target"):
var string = '<div class="makeitpink">this should be pink, but is not</div>';
var parser = new DOMParser();
var domNode = parser.parseFromString(string,"text/xml");
document.getElementById("target").appendChild(domNode.firstChild);
If you run the testcase, and then inspect the target node via firebug/chrome web inspector and select any node within the body tag of jsfiddle's iframe, and do "edit as HTML", add a random charachter anywhere as a string [not an attribute to a domnode, to be clear], and "save", the style is applied. but not before that.
To say that i'm confused is an understatement.
Can anybody please clarify what is going on here?
Thanks.
You can change the mime type to text/html and do the following:
var parser = new DOMParser()
var doc = parser.parseFromString(markup, 'text/html')
return doc.body.firstChild
I didn't test on every browser but it works on Chrome and Firefox. I don't see any reason it wouldn't work elsewhere.
a bit late, but the reason is that you have parsed these using the text/xml option, which means that the results are XML nodes, which don't have CSS applied to them. When you right-click and go "edit as HTML" the browser reinterprets them as HTML and the change in the element will cause a redraw, reapplying the CSS.
I've been parsing my using the relatively hack-ish, yet definitely working method of creating a temporary element and manipulating the innerHTML property, making the browser do the parsing instead:
var temp = document.createElement("div")
//assuming you have some HTML partial called 'fragment'
temp.innerHTML = fragment
return temp.firstChild
Which you've noted in your jsfiddle. Basically it boils down to the output of the DOMParser being an instance of XMLDocument when you use the text/xml option.
We are using jQuery to generate an XML fragment and then convert it to a string using the html() function. But as we just found out, and if anyone doesn't know, the html() JavaScript function as implemented in IE is broken, broken, broken. Basically, it capitalizes some tags, adds attributes to others "helpfully" (in our case, ), and generally doesn't do the Right Thing.
I would like to use something like this to generate the XML string instead:
http://www.stainlessvision.com/jquery-html-vs-innerxhtml
However, this library won't play nicely with jQuery out of the box, e.g.:
var $dummyRoot = $('<dummyroot/>'); // since html() doesn't generate the outer element
var $foo = $('<foo></foo>');
var $font = $('<font ></font >');
$foo.append($font);
$dummyRoot.append($foo);
var $s = innerXHTML($dummyRoot); // <-- Doesn't work
I think it wants a more W3C DOM-ish object.
How can I get jQuery to talk to this innerXHTML() function; or, alternatively, is there another function I can use (maybe something built into jQuery or a jQuery plugin))?
Edit: Follow up for DDaviesBrackett's question. I also have a "body" element in my XML; look how it picks up CSS styling (and not just a element).
Is there an unwritten rule to not generate XML inside the DOM whose elements have names like body, font, head, etc.?
var $dummyRoot = $('<dummyroot/>');
var $foo = $('<foo></foo>');
var $body = $('<body></body>');
var $font = $('<font></font>');
$body.append($font);
$foo.append($body);
$dummyRoot.append($foo);
var $s = innerXHTML($dummyRoot[0]);
// $s equals "<foo><body bottommargin="15" leftmargin="10" rightmargin="10" topmargin="15"><font size="+0"></font></body></foo>"
the jQuery object wraps its contents, but exposes them via an array indexer. What do you get when you use
var $s = innerXHTML($dummyRoot[0]);
instead of your example?
Is there an unwritten rule to not generate XML inside the DOM whose elements have names like body, font, head, etc.?
jQuery relies on the innerHTML property to parse a given piece of text and construct the DOM from that. It was never meant to parse or generate XML as colliding names can give totally unpredictable results depending on how the browser sees it.
See
jQuery won’t parse xml with nodes called option
How do I parse xml with jQuery?
Parse content like XML, with jQuery
I have given a similar answer for generating proper XML in fewer steps using a recursive approach. To create the following XML:
<foo>
<body>
<font></font>
</body>
</foo>
you would write:
Σ('foo',
Σ('body',
Σ('font', '')
)
);
Σ just looks cooler, but you can change the function name to whatever you want :)
I am actually making a Sidebar Gadget, (which is AJAX-based) and I am looking for a way to extract a single element from an AJAX Request.
The only way I found yet was to do something like that:
var temp = document.createElement("div");
temp.innerHTML = HttpRequest.innerText;
document.body.appendChild(temp);
temp.innerHTML = document.getElementByID("WantedElement").innerText;
But it is pretty ugly, I would like to extract WantedElement directly from the request without adding it to the actual document...
Thank you!
If you're in control of the data, the way you're doing it is probably the best method. Other answers here have their benefits but also they're all rather flawed. For instance, the querySelector() method is only available to Windows Desktop Gadgets running in IE8 mode on the host machine. Regular expressions are particularly unreliable for parsing HTML and should not be used.
If you're not in control of the data or if the data is not transferred over a secure protocol, you should be more concerned about security than code aesthetics -- you may be introducing potential security risks to the gadget and the host machine by inserting unsanitized HTML into the document. Since gadgets run with user or admin level privileges, the obvious security risk is untrusted source/MITM script injection, leaving a hole for malicious scripts to wreak havoc on the machine it's running on.
One potential solution is to use the htmlfile ActiveXObject:
function getElementFromResponse(divId)
{
var h = new ActiveXObject("htmlfile");
h.open();
// disable activex controls
h.parentWindow.ActiveXObject = function () {};
// write the html to the document
h.write(html);
h.close();
return h.getElementById("divID").innerText;
}
You could also make use of IE8's toStaticHTML() method, but your gadget would need to be running in IE8 mode.
One option would be to use regular expressions:
var str = response.match(/<div id="WantedElement">(.+)<\/div>/);
str[0]; // contents of div
However, if your server response is more complex, I'd suggest you to use a data format like JSON for the response. Then it would be much cleaner to parse at the client side.
You could append the response from XMLHttpRequest inside a hidden div, and then call getElementById to get the desired element. Later remove the div when done with it. Or maybe create a function that handles this for you.
function addNinjaNodeToDOM(html) {
var ninjaDiv = document.createElement("div");
ninjaDiv.innerHTML = html;
ninjaDiv.style.display = 'none';
return ninjaDiv;
}
var wrapper = addNinjaNodeToDOM(HttpRequest.innerText);
var requiredNode = wrapper.getElementById("WantedElement");
// do something with requiredNode
document.body.removeChild(wrapper); // remove when done
The only reason for appending it to the DOM was because getElementById will not work unless its part of the DOM tree. See MDC.
However, you can still run selector and XPath queries on detached DOM nodes. That would save you from having you to append elements to the DOM.
var superNinjaDiv = document.createElement('div');
superNinjaDiv.innerHTML = html;
var requiedNode = superNinjaDiv.querySelector("[id=someId]");
I think using getElementById to lookup the element in this case is not a good approach. This is because of extra steps you have to take to use it. You wrap the element in a DIV, inject in DOM, lookup your element using getElementById and then remove the injected DIV from DOM.
DOM manipulation is expensive and injection might cause unnecessary reflow as well. The problem is that you have a document.getElementById and not a element.getElementById which would allow you to query without injection in the document.
To solve this, using querySelector is an obvious solution which is far more easier. Else, I would suggest using getElementsByClassName if you can and if your element has a class defined.
getElementsByClassName is defined on ELEMENT and hence can be used without injecting the element in DOM.
Hope this helps.
It's somewhat unusual to pass HTML through an AJAX request; normally you pass a JSON string that the client can evaluate directly, and work with that
That being said, I don't think there's a way to parse HTML in javascript the way you want that's cross-browser, but here's a way to do it in Mozilla derivatives:
var r = document.createRange();
r.selectNode(document.body);
var domNode = r.createContextualFragment(HTTPRequest.innerText);
I'm getting an xml file and want to get data from it.
The source of the xml doesn't really matter but what I;ve got to get a certain field is:
tracks = xmlDoc.getElementsByTagName("track");
variable = tracks.item(i).childNodes.item(4).childNodes.item(0).nodeValue;
Now this works like a charm, EXCEPT when there is no value in the node. So if the structure is like this:
<xml>
<one>
<two>nodeValue</two>
</one>
<one>
<two></two>
</one>
</xml>
the widget will crash on the second 'one' node, because there is no value in the 'two' node. The console says:
TypeError: tracks.item(i).childNodes.item(4).childNodes.item(0) has no properties
Any ideas on how to get the widget to just see empty as an empty string (null, empty, or ""), instead of crashing? I'm guessing something along the lines of data, getValue(), text, or something else.
using
var track= xmlDoc.getElementsByTagName('track')[0];
var info= track.getElementsByTagName('artist')[0];
var value= info.firstChild? info.firstChild.data : '';
doesn't work and returns "TypeError: track has no properties". That's from the second line where artist is called.
Test that the ‘two’ node has a child node before accessing its data.
childNodes.item(i) (or the JavaScript simple form childNodes[i]) should generally be avoided, it's a bit fragile relying on whitespace text nodes being in the exact expected place.
I'd do something like:
var tracks= xmlDoc.getElementsByTagName('track')[0];
var track= tracks.getElementsByTagName('one')[0];
var info= track.getElementsByTagName('two')[0];
var value= info.firstChild? info.firstChild.data : '';
(If you don't know the tagnames of ‘one’ and ‘two’ in advance, you could always use ‘getElementsByTagName('*')’ to get all elements, as long as you don't need to support IE5, where this doesn't work.)
An alternative to the last line is to use a method to read all the text inside the node, including any of its child nodes. This doesn't matter if the node only ever contains at most one Text node, but can be useful if the tree can get denormalised or contain EntityReferences or nested elements. Historically one had to write a recurse method to get this information, but these days most browsers support the DOM Level 3 textContent property and/or IE's innerText extension:
var value= info.textContent!==undefined? info.textContent : info.innerText;
without a dtd that allows a one element to contain an empty two element, you will have to parse and fiddle the text of your xml to get a document out of it.
Empty elements are like null values in databases- put in something, a "Nothing" or "0" value, a non breaking space, anything at all- or don't include the two element.
Maybe it could be an attribute of one, instead of an element in its own right.
Attributes can have empty strings for values. Better than phantom elements .
Yahoo! Widgets does not implement all basic javascript functions needed to be able to use browser-code in a widget.
instead of using:
tracks = xmlDoc.getElementsByTagName("track");
variable = tracks.item(i).childNodes.item(4).childNodes.item(0).nodeValue;
to get values it's better to use Xpath with a direct conversion to string. When a string is empty in Yahoo! Widgets it doesn't give any faults, but returns the 'empty'. innerText and textContent (the basic javascript way in browsers, used alongside things like getElementsByTagName) are not fully (or not at all) implemented in the Yahoo! Widgets Engine and make it run slower and quite awfully react to xmlNodes and childNodes. an easy way however to traverse an xml Document structure is using 'evaluate' to get everything you need (including lists of nodes) from the xml.
After finding this out, my solution was to make everything a lot easier and less sensitive to faults and errors. Also I chose to put the objects in an array to make working with them easier.
var entries = xmlDoc.evaluate("lfm/recenttracks/track");
var length = entries.length;
for(var i = 0; i < length; i++) {
var entry = entries.item(i);
var obj = {
artist: entry.evaluate("string(artist)"),
name: entry.evaluate("string(name)"),
url: entry.evaluate("string(url)"),
image: entry.evaluate("string(image[#size='medium'])")
};
posts[i] = obj;
}