Given:
<body>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<a xlink:href="url"></a>
</svg>
</body>
Is it possible to use the HTML DOM's .querySelector() or .querySelectorAll() to select the link inside the SVG by the contents of its xlink:href attribute?
This works:
document.querySelector('a') // <a xlink:href="url"/>
These don't:
document.querySelector('[href="url"]') // null
document.querySelector('[xlink:href="url"]') // Error: not a valid selector
document.querySelector('[xlink\:href="url"]') // Error: not a valid selector
document.querySelector('[xlink\\:href="url"]') // null
Is there a way of writing that attribute selector to make it 'see' the xlink:href?
Query selector can handle namespaces, but it gets tricky because
The syntax for specifying namespaces in CSS selectors is different from html;
The querySelector API doesn't have any method for assigning a namespace prefix (like xlink) to an actual namespace (like "http://www.w3.org/1999/xlink").
On the first point, the relevant part of the CSS specs allows you to specify no namespace (the default), a specific namespace, or any namespace:
#namespace foo "http://www.example.com";
[foo|att=val] { color: blue }
[*|att] { color: yellow }
[|att] { color: green }
[att] { color: green }
The first rule will match only elements with the attribute att in the "http://www.example.com" namespace with the value "val".
The second rule will match only elements with the attribute att regardless of the namespace of the attribute (including no namespace).
The last two rules are equivalent and will match only elements with the attribute att where the attribute is not in a namespace.
See this fiddle, paying attention to the fill styles (default, hover, and active):
https://jsfiddle.net/eg43L/
The Selectors API adopts the CSS selector syntax, but has no equivalent to the #namespace rule for defining a namespace. As a result, selectors with namespaces are not valid but the wildcard namespace token is valid:
If the group of selectors include namespace prefixes that need to be resolved, the implementation must raise a SYNTAX_ERR exception ([DOM-LEVEL-3-CORE], section 1.4).
This specification does not provide support for resolving arbitrary namespace prefixes. However, support for a namespace prefix resolution mechanism may be considered for inclusion in a future version of this specification.
A namespace prefix needs to be resolved if the namespace component is neither empty (e.g. |div), representing the null namespace, or an asterisk (e.g. *|div), representing any namespace. Since the asterisk or empty namespace prefix do not need to be resolved, implementations that support the namespace syntax in Selectors must support these.
(bold added)
Check out the fiddle again, this time paying attention to the console output. The command document.querySelector('[*|href="#url"]') returns the element you want.
One final warning: MDN tells me that IE8- do not support CSS namespaces, so this might not work for them.
Update 2015-01-31:
As #Netsi1964 pointed out in the comments, this doesn't work for custom namespaced attributes in HTML 5 documents, since HTML doesn't support XML namespaces. (It would work in a stand-alone SVG or other XML document including XHTML.)
When the HTML5 parser encounters an attribute like data:myAttribute="value" it treats that as a single string for the attribute name, including the :. To make things more confusing, it auto-lowercases the string.
To get querySelector to select these attributes, you have to include the data: as part of the attribute string. However, since the : has special meaning in CSS selectors, you need to escape it with a \ character. And since you need the \ to get passed through as part of the selector, you need to escape it in your JavaScript.
The successful call therefore looks like:
document.querySelector('[data\\:myattribute="value"]');
To make things a little more logical, I would recommend using all lower-case for your attribute names, since the HTML 5 parser will convert them anyway. Blink/Webkit browser will auto-lowercase selectors you pass querySelector, but that's actually a very problematic bug (in means you can never select SVG elements with mixed-case tag names).
But does the same solution work for xlink:href? No! The HTML 5 parser recognizes xlink:href in SVG markup, and correctly parses it as a namespaced attribute.
Here's the updated fiddle with additional tests. Again, look at the console output to see the results. Tested in Chrome 40, Firefox 35, and IE 11; the only difference in behavior is that Chrome matches the mixed-case selector.
[*|href] will match both html href and svg xlink:href, then use :not([href]) to exclude html href.
document.querySelectorAll('[*|href]:not([href])')
tested in chrome
Unfortunately not.
querySelector doesn't handle XML namespaces, so there is no easy way to do this that way. You can however use an XPath query.
var result = document.evaluate(
// Search for all nodes with an href attribute in the xlink namespace.
'//*[#xlink:href="url"]',
document,
function(prefix){
return {
xlink: "http://www.w3.org/1999/xlink"
}[prefix] || null;
},
XPathResult.ORDERED_NODE_ITERATOR_TYPE
);
var element = result.iterateNext();
If you need full cross-browser support, such as for IE, which does not have a document.evaluate, you can polyfill it with wicked-good-xpath.
Of course, depending on your usage, it may be easier to do this (which I think will work on IE):
var element = Array.prototype.filter.call(document.querySelectorAll('a'),
function(el){
return el.getAttributeNS('http://www.w3.org/1999/xlink', 'href') === 'url';
})[0] || null;
Related
I'm working on a small querying module (in js) for html and I want to provide a generic query(selector) function supporting both, css selectors and XPath selectors as string argument.
Regardless of how each kind of selection is done, my problem here is how to identify whether a given string is an xpath or a css selector. We can assume that the function would be something like this:
function query(selector){
selectorKind = identifySelectorKind(selector); // I want to know how to code this particular function
if(selectorKind==="css") return queryCss(selector);
if(selectorKind==="xPath") return queryXPath(selector); //Assume both functions exists and work
}
My first approach (given my limited knowledge of xPath queries) was to identify the query kind by checking if the first character is / (here I am assuming all relevant xPath queries begin with /)
So, identifySelectorKind would go a bit like this:
function identifySelectorKind(selector){
if (selector[0] === "/") return "xPath";
else return "css";
}
Note that I don't need to validate neither css nor xpath selectors, I only need an unambiguous way to differentiate them. Would this logic be enough? (in other words, all xPath selectors begin with / and no css selector begins the same way?), if not, is there a better way or some considerations I may want to know?
You can't necessarily. For example, * is a valid xpath and a valid css selector, but it matches a different set of elements in each.
If you're absolutely sure your XPath selector will always begin with /, then yes, it's fine. Note that an XPath selector doesn't have to begin with a /, but if yours always selects from the root, then it's fine.
Is there any way to get access to the original (from source) casing of an attribute name or tag name?
attribute.name is lowercased
element.tagName is uppercased
element.localName is lowercased
I'm hoping for a non-XHTML solution, because XML makes me sad ☹ . And I can't require that level of validity from the users...
Yes, there is a way: use XHTML(*).
According to element.tagName article MDN,
In XML (and XML-based languages such as XHTML), tagName preserves
case.
Currently there is no Attr.name article, but in my tests it also preserves case in XHTML.
(*) I mean you must serve the page as XHTML, just using a XHTML doctype isn't enough.
Caveat: In XHTML, <span> and <sPaN> are different. That means that:
span {} CSS selector won't affect <sPaN>
document.getElementsByTagName('span') won't include <sPaN>
document.querySelectorAll('span') won't include <sPaN>
document.createElement('sPaN') gives an instance of HTMLUnknownElement instead of HTMLSpanElement.
And of course, if you attempt to validate the page, it will be invalid XHTML. But browsers will parse it well (no draconian error).
This seems like a simple thing, but I keep getting "undefined"
I am trying out the "data-" HTML5 attribute and I am looping through a bunch of div tags that look like this:
<div id="myEvent"
data-scheduledOn="1399985100000"
data-eventStatus="3">
And I am looping through a bunch of these like this:
$('[id="myEvent"]').each(function(index, divItem) {
alert($(divItem).data("scheduledOn"));
}
But I keep getting "undefined" If I do this (get the attribute) it works fine:
alert($(divItem).attr("data-scheduledOn"));
So What am I missing?
http://api.jquery.com/data/
"The .data() method allows us to attach data of any type to DOM elements in a way that is safe from circular references and therefore from memory leaks."
At least at this point in time, to use the .data function you have to attach the data using the function before you can read it back using the .data function.
If you need to read pre-existing data use the .attr or .prop functions.
It seems as though It is a naming problem as Hamza Kubba suggested, but just a bit different...
if I changed the name of the data attribute to "data-scheduled-on" I can retrieve it by .data("scheduledOn") OR using data-scheduledon and .data("scheduledon") also works.
So don't use CAPS for data- names is the moral of this story!
Please note that per HTML 5 specs, the attribute name should not contain any uppercase letters and some browsers such as FF & Chrome will change any uppercase letter to lowercase. That's why the following demo works if you access the data attributes with lowercase names:
http://jsfiddle.net/fiddleyetu/5LdQd/
$('div.myEvent').each(function(index, divItem) {
console.log($(divItem).data("scheduledon"));
console.log( $(divItem).data("eventstatus") );
});
Ans since you cannot have more than one element on a page with the same ID, I have used a class selector for the demo.
MORAL: Do not use UPPERcase; your browsers may not always be that 'understanding'.
I'm working with some legacy code where the original developers made heavy use of generating HTML DOM nodes with a non-standard attribute named translate
<span translate="[{"shown":"My Account","translated":"My Account","original":"My Account","location":"Text","scope":"Mage_Customer"}]">My Account</span>
and then traversing/searching for those nodes with javascript code like the following.
if (!$(target).match('*[translate]')) {
target = target.up('*[translate]');
}
The problem I'm trying to solve is, it appears that Google Chrome automatically adds a translate attribute to every DOM node in the document, and that this DOM node's value is a boolean true. You can see this by running the following Javascript from Chrome's javascript console
> document.getElementsByTagName('p')[0].translate
true
>
Is there anyway to tell Chrome not to populate these attributes? Their presence is wrying havoc with the legacy code. PrototypeJS's match and up nodes treat these boolean object attributes as matches, while the code I'm dealing with is specifically looking for DOM nodes with an attribute named translate. I'd like to find a solution for my problem that doesn't involved rewriting the old Javascript to use methods like hasAttribute.
I tried (as a wild guess) adding the meta attributes mentioned in this article,
<meta name=”google” value=”notranslate”>
<meta name=”google” content=”notranslate”>
but the nodes in the page still has a boolean true translate attribute.
(if it matters, this is Magento's inline translation system I'm talking about here)
The best I've been able to come up with so far is going through every DOM element in the page defining a getter that checks for the existence of an attribute. (the Object.__defineGetter__ guard clause ensures no errors in browsers that don't support modern Javascript)
if(Object.__defineGetter__)
{
var hasTranslateAttribute = function(){
return $(this).hasAttribute("translate");
};
document.observe("dom:loaded", function() {
$$('*').each(function(theElement){
theElement.__defineGetter__("translate", hasTranslateAttribute);
});
});
}
I tried defining a getting on Object.prototype and Element.prototype, but it seems like the browser's native translate is defined higher up the chain, so you need to redefine things on a per element basis.
Replace the nonstandard attribute translate by an attribute like data-translate, which is virtually guaranteed to be and to remain undefined in HTML specifications and in browsers. The data-* attributes were invented to prevent issues like this, and they can also be used to fix them.
I'm trying to get the contents of a XML document element, but the element has a colon in it's name.
This line works for every element but the ones with a colon in the name:
$(this).find("geo:lat").text();
I assume that the colon needs escaping. How do I fix this?
Use a backslash, which itself should be escaped so JavaScript doesn't eat it:
$(this).find("geo\\:lat").text();
That isn't just an ordinary element name. That's a qualified name, meaning that it is a name that specifically refers to an element type within a namespace. The element type name is 'lat', and the namespace prefix is 'geo'.
Right now, jQuery can't deal with namespaces very well, see bug 155 for details.
Right now, as a workaround, you should be able to select these elements with just the local name:
$(this).find("lat").text();
If you have to distinguish between element types with the same local name, then you can use filter():
var NS = "http://example.com/whatever-the-namespace-is-for-geo";
$(this).find("lat").filter(function() { return this.namespaceURI == NS; }).text();
Edit: my mistake, I was under the impression that patch had already landed. Use Adam's suggestion for the selector, and filter() if you need the namespacing too:
var NS = "http://example.com/whatever-the-namespace-is-for-geo";
$(this).find("geo\\:lat").filter(function() { return this.namespaceURI == NS; }).text();
if you have a jquery selector problem with chrome or webkit not selecting it try
$(this).find('[nodeName=geo:lat]').text();
this way it works in all browsers