I have a simple set of statements that actually work (I have run that code and it does what I expect of it) that looks as follow:
result_xml = result.jqxhr.responseXML;
a = jQuery("data[name='calendar']", result_xml).text();
new_calendar = jQuery.parseHTML(a);
jQuery("div.calendar").html(jQuery("div.calendar table.calendar-table", new_calendar));
result comes from an AJAX reply, you should recognize the jqxhr field.
I expect a to be a string since I get a block of data with text(). The data is an HTML string that I saved in the XML document. So I can cast it this way:
a = /** #type {string} */ jQuery("data[name='calendar']", result_xml).text();
Now I convert that string to an HTML DOM thinking it would be an Element:
new_calendar = /** #type {Element} */ jQuery.parseHTML(a);
And that's where I get this weird error:
WARNING - invalid cast - must be a subtype or supertype
from: (Array.<(Element|null)>|null)
to : (Element|null)
So parseHTML() would be returning an array of elements (or null)?
Then I was trying to use the output of the parseHTML() in the html() function and there too, I have a hard time to understand what is going on.
WARNING - invalid cast - must be a subtype or supertype
found : (Array.<(Element|null)>|null)
required: (Document|Element|Object.|jQuery|null|undefined)
Frankly, I do not see why the Google compiler makes it such that the output of one function cannot be the input of another, even if it works just fine in the real world.
Would an array of Element be considered a Document?
Then finally (yeah! all of that for 3 lines of JavaScript!) I get another error in regard to the input of the html() function:
WARNING - actual parameter 1 of jQuery.prototype.html does not match formal parameter
found : jQuery
required: (function (number, string): ?|string|undefined)
jQuery("div.calendar").html(jQuery("div.calendar table.calendar-table", new_calendar));
Here too, it works in the real world... will the jQuery object automatically be converted to a string and then re-transformed to a set of tags to be added in my calendar?
Are all of these normal limitations of the closure compiler?
Based on Chad answer, I changed my code this way:
result_xml = result.jqxhr.responseXML;
a = /** #type {string} */ (jQuery("data[name='calendar']", result_xml).text());
new_calendar = jQuery.parseHTML(a);
jQuery("div.calendar").empty().append(jQuery("div.calendar table.calendar-table", new_calendar[0]));
I cast the text() output to string in closure;
I use element 0 of new_calendar instead of directly new_calendar.
I changed the html() with empty().append() since the append() function accepts a jQuery object as input
This makes the closure compiler happy.
Yes Absolutely this is expected behavior. There are many things that "work" in JavaScript. Closure-compiler attempts to force good behavior when asked.
As for jQuery - these are the normal limitations of the published jQuery API. With jQuery the code often supports undocumented behaviors (like you are listing). However the jQuery team is free to change behaviors that are not documented at any time.
Closure-compiler warns you if you are not matching what is listed as the API spec (and that definition is community maintained - so not always 100% right).
Here's the relevant specifications:
jQuery().html(string) - A string of HTML to set as the content of each matched element
jQuery.parseHTML('string') - Returns: Array
I use DOM API instead of jQuery.
There is a full HTML text in data.
// create DOM parser object
var dom_parser = new DOMParser();
// parse HTML into DOM document object
var doc = dom_parser.parseFromString(data , "text/html");
// doc.body.innerHTML = body part in text, not object
// <body> tag is stripped.
// append the body part into div with the id "contentsarea".
$("#contentsarea").empty().append(doc.body.innerHTML);
Related
I've built a simple practice game with 5 values stored separately. I want to put these 5 values/divs into a single object, but I'm confused about the output. the console.log returns: "the value for cardValue is [object HTMLDivElement]". The first part is working, but I'm confused about the latter. When I type into my console player.cardValue I get 'player is not defined'. Any help is appreciated. Thanks.
var cardValue = document.getElementById('cardValue');
var cardValue2 = document.getElementById('cardValue2');
var playerHit1Div = document.getElementById('playerHit1Div');
var playerHit2Div = document.getElementById('playerHit2Div');
var playerHit3Div = document.getElementById('playerHit3Div');
var player = {
cardValue: document.getElementById('cardValue'),
cardValue2: document.getElementById('cardValue2'),
playerHit1Div: document.getElementById('playerHit1Div'),
playerHit2Div: document.getElementById('playerHit2Div'),
playerHit3Div: document.getElementById('playerHit3Div')
};
for (var x in player){
console.log('the value for ' + x + ' is ' + player[x]);
}
You seem to be confused about the behaviour of the console. It's implementation dependent and has quite different behaviour in each browser that has one, so you need to learn the peculiarities of the one(s) you are using.
Running your code and entering player.cardValue into the console in IE returns:
player.cardValue
null
In Chrome it returns:
null
Which is expected as I don't have any element with an ID of cardValue. Note that if you do have suitable elements in the page, they must be before the code, otherwise when the code runs, the elements don't exist yet. Or you can run the code after the onload event (e.g. use window.onload = function(){...})
If you wish to get the text content of an element, then use its textContent property. Older IE supports innerText instead, so you can do:
var theText = element.textContent || element.innerText;
If you want to get the markup inside the element, use its innerHTML property, which has been supported by all browsers for a long time:
var theMarkup = element.innerHTML;
To see reliable output, write your own output routines and don't rely on the console's interpretation of what you might want, at least until you are familiar with how they work.
I think that console log has many cool features like error / debug / warn / info / assert, you can also profile and also trace. console.log has very cool features and is browser dependent as it was spoken in the comments.
If there is an element in DOM with id cardValue you will get in player.cardValue the Html Object that represents it, you will actually get the value or text of the DOM element depending on the kind of element it is.
For instance getting the text from an element could be some times innerText of the element and in other cases .value attribute of an input text and so on.
Accessing to those values depends on each case of the element you are trying to get info from. And not only that but actually also depends on the DOCTYPE you define, so it is good to have some kind of library to get access in the correct way to the values we need from DOM in the way we expect.
I will also add a reference a book I read, that explains that accessing with attr of jquery actually handles browser incompatibilities.
I have a jsfiddle that demonstrates the question:
http://jsfiddle.net/H6gML/8/
$(document).ready(function() {
// this seems fine in IE9 and 10
var $div = $("<div>");
console.log("In IE, this <div> is just fine: " + $div[0].outerHTML);
// this is weird in IE
var $test = $("<test>");
console.log("However, this <test> has an xml tag prepended: \n"
+ $test[0].outerHTML);
$test.find("test");
console.log("Now, it does not: \n" + $test[0].outerHTML);
console.log("Why does this behave this way?");
});
Why does this happen? It doesn't happen in Chrome or Firefox. Is there a better way to fix this than to call .find("test") on the object?
Edit
To clarify, I'm not asking why the xml tag is added, rather, I'm wondering why the .find() call get's rid of it. It doesn't make sense to me.
Why does this happen? It doesn't happen in Chrome or Firefox. Is there a better way to fix this than to call .find("test") on the object
It is the IE causing the issue while doing document.createElement on an unknown html element type. It thinks it is an XML node and adds the xml namespace prefixed <?XML:NAMESPACE PREFIX = PUBLIC NS = "URN:COMPONENT" />. Instead if you try to make it explicit to mention that it is an html element, this issue doesn't happen.
Try:
var $test = $("<html><test/></html>");
The issue no longer occurs.
To clarify, I'm not asking why the xml tag is added, rather, I'm wondering why the .find() call get's rid of it. It doesn't make sense to me.
Now, when you do a find, jquery internally uses context.getElementsByTagName or (similar based on the type whether it is a class or a tag or id etc..) which means it does this operation on the element test. So in IE when you do that it probably internally resolves the fact that you are trying to perform the operation on an html element and not an xml element and it changes the document type for the underlying context(But i don't know why it changes the parent context though rather than just returning a match). You can check this out by this simple example as well.
var $test = document.createElement("test");
console.log("However, this <test> has an xml tag prepended: \n"
+ $test.outerHTML);
$test.getElementsByTagName("test");
console.log("Now, it does not: \n" + $test.outerHTML);
Demo
Update
Here is a documented way of defining the custom elements
The custom element type identifies a custom element interface and is a sequence of characters that must match the NCName production and contain a U+002D HYPHEN-MINUS character. The custom element type must not be one of the following values:
annotation-xml,
color-profile,
font-face,
font-face-src,
font-face-uri,
font-face-format,
font-face-name,
missing-glyph
So according to this had your tag name been somename-test ex:- custom-test IE recognizes it and it works as expected.
Demo
My Web application is currently using Tapestry 5.2.6. I want to write a new feature with the following requirements:
Users can click on items in a gallery to see a lightbox showing a detailed view, including description, comments, seller controls if they have the right credentials, and the ability to buy without leaving the page.
The url should be updated to reflect whether they're in gallery view or details view.
URL changes should be dynamic in browsers that support HTML5 pushState. Full page refreshes are acceptable in older browsers.
Both the gallery page and the details page must be crawlable - users without Javascript should see a fully marked up page.
Speed - needs to be much faster than I know I can expect from Tapestry.
My plan is to choose and implement a template language that can evaluate equally well on the server or on the client. For the initial page load, I can render the template on the server. For subsequent updates, I can pass the item's viewmodel object as JSON to the client and evaluate the template there.
So far so good. The problem is that none of the template languages I've looked at are powerful enough to feel good about moving toward for the future. As a case study, consider that out of the following:
Mustache
dust
Hogan
Handlebars
None seem to have the power to do a "wrapping" transformation like this:
# base template
{>widget}
<span class="content">Hello world</span>
{/widget}
# widget template
<div class="widget">
{>widget_body/}
</div>
# rendered output
<div class="widget">
<span class="content">Hello world</span>
</div>
Notice that the wrapped content is taken from the base template, and the output of the widget template surrounds it on both sides. The only way I know to achieve this in the above languages would be a template something like:
{>open_widget/}
{>widget_body/}
{>close_widget/}
Which means two templates for every component, an opener and a closer, both containing unclosed tags. (In fairness, dust can do this somewhat elegantly using blocks and inline partials, but because inline partials are global to the template, you're limited to one use of the widget per template.)
My questions about templates are these:
I know that industry leaders like LinkedIn and Twitter are using these technologies and doing great. Am I asking too much? If you've used one of them, how did you deal with the "wrapping problem"?
A few solutions I've investigated do appear to support it: jquery-tmpl, which is no longer officially maintained; underscore and ejs, which make me nervous as a long term solution with their embedded code; and Closure templates. Currently Closure looks the best to me, to my surprise! If you've used any of these, what were your findings?
I'm not sure whether anything out there does what you're talking about. I needed something similar and figured writing a simple text replacement script would be quicker than comparing existing solutions and learning to use one.
This script is not production ready (should be tested more, and the API is weird), but it should give you an idea of one way it can be done.
Here's how it's set up:
Storing templates in the document
The template text is stored in script tags with a type attribute other than "text/javascript". Each template has a unique id attribute.
<script id="some_template" type="text/plain">
a valid template
</script>
Browsers should not render these. Any characters are allowed (except </script>), and nothing needs to be escaped.
Placeholders
Placeholders look like this: {#some_identifier}.
<script id="image_template" type="text/plain">
<img src="{#img_url}">
</script>
Each placeholder will be replaced with either:
a value passed in from another template,
an argument passed to a JavaScript function when getting a copy of the template, or
an empty string if no replacement value was found.
Including one template in another
The ## "pseudotag" includes the contents of another template in the current template.
<script id="photo_template" type="text/plain">
<## image_template></##>
<div class="photo-caption">{#caption}</div>
</script>
photo_template includes image_template. All inclusion replacement happens before any placeholder replacement, so photo_template has {#img_url} and {#caption} placeholders.
Inclusion with placeholder replacement
This is where the "wrapping" comes from. Ideally, placeholders will almost always be replaced by content from other templates, rather than values passed in when getting a copy of the template.
<script id="missing_photo_template" type="text/plain">
<## photo_template>
<# img_url>notfound.png</#>
</##>
</script>
missing_photo_template includes photo_template, providing it with a replacement for {#img_url}, so missing_photo_template has only the {#caption} placeholder.
JavaScript
The API sucks right now, but essentially the main namespace at has two functions, txt and node. The first one gets a copy of a template as text, the second one gets a copy as an Element (which means it should have one root node, unlike some of my examples above).
Here it is:
/**
Atrocious Templates
*/
var at = (function(){
var rTemplate = /<##\s*(.*?)>((?:[\w\W](?!<##))*?)<\/##>/gm,
rOption = /<#\s*(.*?)>([\w\W]*?)<\/#>/gm,
rValue = /\{#(.*?)\}/g,
rTag = /<(\w+)/i,
rSpace = /\s+/,
templates = {},
doc = document.implementation.createHTMLDocument('');
/**
Inlcude inner templates.
#private
#param {string} m0
The full inclusion text.
#param {string} m1
The ID of the included template.
#param {string} m2
Values passed to included template.
#return {string}
*/
function includeTemplates(m0, m1, m2) {
var opts = {};
m2.replace(rOption, function(m0, m1, m2) { opts[m1] = m2; });
return txt(m1, opts, true);
}
/**
Get text contents of a template.
#private
#param {string} id
The ID of the template.
#return {string}
*/
function get(id) {
if (templates[id]) return templates[id];
var last, t = document.getElementById(id).innerHTML;
while (last != t && (last = t)) t = t.replace(rTemplate, includeTemplates);
return (templates[id] = t);
}
/**
Get a text copy of a template.
#param {string} id
The ID of the template.
#param {Object.<string|function ():string>} options
Properties of this object will replace placeholder tokens.
Each property can be either a string, or a function which
returns a string.
#param {boolean=} noStrip
By default, placeholders for which no replacement text is
found are removed. Setting this to `true` overrides that
behavior, leaving non-replaced placeholders intact.
#return {string}
*/
function txt(id, options, noStrip) {
if (!options) options = {};
return get(id).replace(rValue, function(m0, m1) {
var v = options[m1];
return noStrip && !v && m0 || v && (v.call ? v() : v) || '';
});
}
/**
Get a node copy of a template.
#param {string} id
The ID of the template.
#param {Object.<string|function ():string>} options
Properties of this object will replace placeholder tokens.
#return {string}
*/
function node(id, options) {
var text = txt(id, options),
root = text.match(rTag)[1];
doc.open; doc.write(text); doc.close();
return doc.getElementsByTagName(root)[0];
}
// exports
return { txt: txt, node: node };
}());
Again, I'm not recommending you use this in production as it hasn't been tested much (although it seems to work fine), but hopefully this will give you some ideas about how what you want can be accomplished.
Try looking at handlebarjs.com - I'm using it on both the server and client.
Maybe the question is old, but doesn't Mustache support partials?
http://mustache.github.io/mustache.5.html
...In this way you may want to think of partials as includes, or template
expansion, even though it's not literally true.
For example, this template and partial:
base.mustache:
<h2>Names</h2> {{#names}} {{> user}} {{/names}}
user.mustache:
<strong>{{name}}</strong>
Can be thought of as a single, expanded template:
<h2>Names</h2> {{#names}} <strong>{{name}}</strong> {{/names}}
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'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;
}