How to select special attributes (prefix:name="")? - javascript

how do I select special attributes like 'user:name' or 'city:id' using jQuery?
<div user:name="Ropstah"></div>
<span city:id="4"></div>
Javascript
//this works:
alert($('div').attr("user:name")); // alerts 'Ropstah'
//this doesn't work:
alert($('div[user:name]').attr("user:name")); //error
alert($('div[user\\:name]').attr("user:name")); //error even with special character escaping...

This is a bug in jQuery.
You have two options:
Get rid of the : and using "non standard" attributes (honestly, it's not a big deal)
Get more verbose, or use a plugin to get the functionality anyways:
Initially, you might have to do this:
$('div').filter(function() {
return $(this).attr('user:name') !== undefined;
}).whateverElse();
Speed wise this should be fairly close to jQuery's [] selector as that's what it's doing inside the library anyways. It is, of course, more to type every time you want to find an element that has an attribute, so you could write a plugin for it, since jQuery is awesome and lets you do this sort of thing:
$.fn.hasattr = function(attr) {
return this.filter(function() {
return $(this).attr(attr) !== undefined;
});
};
Which would then let you do a much simpler:
$('div').hasattr('user:name').whateverElse();
Or if you wanted to check if the attribute was equal to something, the plugin might be:
$.fn.cmpattr = function(attr, value) {
return this.filter(function() {
return $(this).attr(attr) == value;
});
};
And you could then do:
$('div').cmpattr('user:name', 'Ropstah').whateverElse();

Fix it:
jQuery.expr.match.ATTR = /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+|\w+:\w+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/;
// ^^^^^^^^
EDIT: This is not an official fix, it appears to work quite well though.
I've had quite a few headaches caused by jQuery and its inner-workings... sometimes, I think, it's okay to get stuck in there and fix it yourself. :)

If you're OK with using non-standard properties (which doesn't effect the rendering of your markup in any way.. it's pretty harmless, really) you can do this:
<div nonstandard="harmless" />
$("div[nonstandard='harmless']")
I've used this approach a few times. It doesn't hurt to be pragmatic.

Related

Simplify jQuery code by naming and recalling a repeated snippet

Sorry this question is so "newbie." I'm not entirely sure if what I'm attempting to do is possible, but I thought I would check.
I have a script that finds and operates on many different elements inside of an iframe, like so:
$(window).load(function(){
$('#iframe').contents().find('target1').doSomething1();
$('#iframe').contents().find('target2').doSomething2();
$('#iframe').contents().find('target3').doSomething3();
$('#iframe').contents().find('target4').doSomething4();
$('#iframe').contents().find('target5').doSomething5();
$('#iframe').contents().find('target6').doSomething6();
$('#iframe').contents().find('target7').doSomething7();
$('#iframe').contents().find('target8').doSomething8();
$('#iframe').contents().find('target9').doSomething9();
...
});
What I would like to do, if possible, is make the code shorter and more readable by defining and recalling $('#iframe').contents().find() as a shorter expression of some sort. Something like this:
$(window).load(function(){
function iframe(){
$('#iframe').contents().find()
}
iframe('target1').doSomething1();
iframe('target2').doSomething2();
iframe('target3').doSomething3();
iframe('target4').doSomething4();
iframe('target5').doSomething5();
iframe('target6').doSomething6();
iframe('target7').doSomething7();
iframe('target8').doSomething8();
iframe('target9').doSomething9();
});
Obviously, that example doesn't work. Is what I'm trying to do even possible?
Sure - you just need a function. You were part of the way there, but you need to specify and use a parameter.
Example:
function iframe(target) {
return $('#iframe').contents().find(target);
}
$(window).load(function(){
iframe('target1').doSomething1();
iframe('target2').doSomething2();
iframe('target3').doSomething3();
iframe('target4').doSomething4();
iframe('target5').doSomething5();
iframe('target6').doSomething6();
iframe('target7').doSomething7();
iframe('target8').doSomething8();
iframe('target9').doSomething9();
});
Note that the function can (and probably should) be defined outside of the .load handler. Also, it's very likely you can simplify this even more, but it's hard to tell without knowing what doSomething1, doSomething2, etc., actually do.
This is what a function is for. You're actually much closer than you realize, you just need your function to accept a parameter and return a result:
function iframe(selector){
return $('#iframe').contents().find(selector);
}
keep it simple and silly.
As the one who asked the question said that he is newbie in functions, I suggest this easy-to-understand solution. Then he will know what to do to make shorter and faster after becoming advanced in JQuery.
Otherwise, he will use the advanced solutions without understanding them. Add on that if he get used to use code that he does not exactly know what it actually does.
$(window).load(function(){
iFrame('#id1');
iFreme('#id2');
...
});
function iFrame(x)
{
if(x=='#id1')
{
$('#iframe').contents().find($(x)).doSomething();
}
else if (x=='#id2')
{
$('#iframe').contents().find($(x)).doSomethingElse();
}
...
}
I'd actually argue in favor of the following:
$(window).load(function(){
var $contents = $('#iframe').contents();
$contents.find('target1').doSomething1();
$contents.find('target2').doSomething2();
$contents.find('target3').doSomething3();
$contents.find('target4').doSomething4();
$contents.find('target5').doSomething5();
});
The reason being the find on the contents() is a smaller scope search than the $('#iframe') search. Alternatively you could also do
$(window).load(function(){
var $targets = $('#iframe').contents().find('target1, target2, target3, target4, target5');
$targets.filter('target1').doSomething1();
$targets.filter('target2').doSomething2();
$targets.filter('target3').doSomething3();
$targets.filter('target4').doSomething4();
$targets.filter('target5').doSomething5();
});
This has the added benifit of doing the finds once and then filtering on the result array to find the object to operate on, rather than doing a find on the contents dom for each target to execute on.
$(window).load(function(){
var i = 1,
$contents = $('#iframe').contents();
for (; i < 10; i++) {
$contents.find('target' + i)['doSomething' + i]();
}
});

Shortening a javascript checking for CSS property support

EDIT:
I had a script that checked for CSS property support, in this case hyphenation support. It was quite long, but with the help of the respondents it was shortened to this:
var styleEngine = document.documentElement.style;
if ((!('hyphens' in styleEngine)) && (!('MozHyphens' in styleEngine)) && (!('WebkitHyphens' in styleEngine)) && (!('msHyphens' in styleEngine)))
alert('CSS hyphenation is not supported.');
else
alert('CSS hyphenation is supported.');
The reason for this edit, including the title, is to make this post more useful to people to people googling for a Javascript that checks for CSS property and/or value support. I later learned a few things about CSS support and its check which might be of interest. The first thing I learned is that Chrome at this moment (2014) says it supports -webkit-hyphens, but it does not support the most important value auto. That means that we have to check for property value support.
That can be done in two ways. The first is with the new CSS at-rule #supports. That is explained on https://developer.mozilla.org/en-US/docs/Web/CSS/#supports. The other way, with Javascript, is explained on http://ryanmorr.com/detecting-css-style-support/. And in the case of hyphenation, here is a Javascript polyfill for when proper hyphenation is not supported: https://code.google.com/p/hyphenator/.
Extract it to a function:
function hasStyle(prop) {
return prop in document.documentElement.style;
}
Then you can use:
if (!hasStyle('MozHyphens' && !hasStyle('msHyphens')) { ... }
Not too much shorter, but shorter enough (and encapsulated).
To be more concise, you can create another function:
function hasHyphens() {
return hasStyle('MozHypens') || hasStyle('msHyphens');
}
And then:
if (!hasHyphens()) { ... }
What about:
ValuesIntersect(valArray1, valArray2) {
var len = valArray2.length,
i = 0,
found = false;
while (i<len && !found) {
found = valArray2[i] in valArray1;
++i;
}
return found;
}
Use it like this:
if (!ValuesIntersect(['hyphens','MozHyphens', 'WebkitHyphens', 'msHyphens'], document.documentElement.style)) {...}
I find this is best for readability. When you're at that if statement, this clearly shows what you're checking for. The external function need not necessarily be small since it's rarely ever going to be checked. And it's generic enough to work in multiple places.

Select tags that starts with "x-" in jQuery

How can I select nodes that begin with a "x-" tag name, here is an hierarchy DOM tree example:
<div>
<x-tab>
<div></div>
<div>
<x-map></x-map>
</div>
</x-tab>
</div>
<x-footer></x-footer>
jQuery does not allow me to query $('x-*'), is there any way that I could achieve this?
The below is just working fine. Though I am not sure about performance as I am using regex.
$('body *').filter(function(){
return /^x-/i.test(this.nodeName);
}).each(function(){
console.log(this.nodeName);
});
Working fiddle
PS: In above sample, I am considering body tag as parent element.
UPDATE :
After checking Mohamed Meligy's post, It seems regex is faster than string manipulation in this condition. and It could become more faster (or same) if we use find. Something like this:
$('body').find('*').filter(function(){
return /^x-/i.test(this.nodeName);
}).each(function(){
console.log(this.nodeName);
});
jsperf test
UPDATE 2:
If you want to search in document then you can do the below which is fastest:
$(Array.prototype.slice.call(document.all)).filter(function () {
return /^x-/i.test(this.nodeName);
}).each(function(){
console.log(this.nodeName);
});
jsperf test
There is no native way to do this, it has worst performance, so, just do it yourself.
Example:
var results = $("div").find("*").filter(function(){
return /^x\-/i.test(this.nodeName);
});
Full example:
http://jsfiddle.net/6b8YY/3/
Notes: (Updated, see comments)
If you are wondering why I use this way for checking tag name, see:
JavaScript: case-insensitive search
and see comments as well.
Also, if you are wondering about the find method instead of adding to selector, since selectors are matched from right not from left, it may be better to separate the selector. I could also do this:
$("*", $("div")). Preferably though instead of just div add an ID or something to it so that parent match is quick.
In the comments you'll find a proof that it's not faster. This applies to very simple documents though I believe, where the cost of creating a jQuery object is higher than the cost of searching all DOM elements. In realistic page sizes though this will not be the case.
Update:
I also really like Teifi's answer. You can do it in one place and then reuse it everywhere. For example, let me mix my way with his:
// In some shared libraries location:
$.extend($.expr[':'], {
x : function(e) {
return /^x\-/i.test(this.nodeName);
}
});
// Then you can use it like:
$(function(){
// One way
var results = $("div").find(":x");
// But even nicer, you can mix with other selectors
// Say you want to get <a> tags directly inside x-* tags inside <section>
var anchors = $("section :x > a");
// Another example to show the power, say using a class name with it:
var highlightedResults = $(":x.highlight");
// Note I made the CSS class right most to be matched first for speed
});
It's the same performance hit, but more convenient API.
It might not be efficient, but consider it as a last option if you do not get any answer.
Try adding a custom attribute to these tags. What i mean is when you add a tag for eg. <x-tag>, add a custom attribute with it and assign it the same value as the tag, so the html looks like <x-tag CustAttr="x-tag">.
Now to get tags starting with x-, you can use the following jQuery code:
$("[CustAttr^=x-]")
and you will get all the tags that start with x-
custom jquery selector
jQuery(function($) {
$.extend($.expr[':'], {
X : function(e) {
return /^x-/i.test(e.tagName);
}
});
});
than, use $(":X") or $("*:X") to select your nodes.
Although this does not answer the question directly it could provide a solution, by "defining" the tags in the selector you can get all of that type?
$('x-tab, x-map, x-footer')
Workaround: if you want this thing more than once, it might be a lot more efficient to add a class based on the tag - which you only do once at the beginning, and then you filter for the tag the trivial way.
What I mean is,
function addTagMarks() {
// call when the document is ready, or when you have new tags
var prefix = "tag--"; // choose a prefix that avoids collision
var newbies = $("*").not("[class^='"+prefix+"']"); // skip what's done already
newbies.each(function() {
var tagName = $(this).prop("tagName").toLowerCase();
$(this).addClass(prefix + tagName);
});
}
After this, you can do a $("[class^='tag--x-']") or the same thing with querySelectorAll and it will be reasonably fast.
See if this works!
function getXNodes() {
var regex = /x-/, i = 0, totalnodes = [];
while (i !== document.all.length) {
if (regex.test(document.all[i].nodeName)) {
totalnodes.push(document.all[i]);
}
i++;
}
return totalnodes;
}
Demo Fiddle
var i=0;
for(i=0; i< document.all.length; i++){
if(document.all[i].nodeName.toLowerCase().indexOf('x-') !== -1){
$(document.all[i].nodeName.toLowerCase()).addClass('test');
}
}
Try this
var test = $('[x-]');
if(test)
alert('eureka!');
Basically jQuery selector works like CSS selector.
Read jQuery selector API here.

JavaScript/JQuery: use $(this) in a variable-name

I'm writing a jquery-plugin, that changes a css-value of certain elements on certain user-actions.
On other actions the css-value should be reseted to their initial value.
As I found no way to get the initial css-values back, I just created an array that stores all initial values in the beginning.
I did this with:
var initialCSSValue = new Array()
quite in the beginning of my plugin and later, in some kind of setup-loop where all my elements get accessed I used
initialCSSValue[$(this)] = parseInt($(this).css('<CSS-attribute>'));
This works very fine in Firefox.
However, I just found out, that IE (even v8) has problems with accessing the certain value again using
initialCSSValue[$(this)]
somewhere else in the code. I think this is due to the fact, that I use an object ($(this)) as a variable-name.
Is there a way arround this problem?
Thank you
Use $(this).data()
At first I was going to suggest using a combination of the ID and the attribute name, but every object might not have an ID. Instead, use the jQuery Data functions to attach the information directly to the element for easy, unique, access.
Do something like this (Where <CSS-attribute> is replaced with the css attribute name):
$(this).data('initial-<CSS-attribute>', parseInt( $(this).css('<CSS-attribute>') ) );
Then you can access it again like this:
$(this).data('initial-<CSS-attribute>');
Alternate way using data:
In your plugin, you could make a little helper function like this, if you wanted to avoid too much data usage:
var saveCSS = function (el, css_attribute ) {
var data = $(el).data('initial-css');
if(!data) data = {};
data[css_attribute] = $(el).css(css_attribute);
$(el).data('initial-css', data);
}
var readCSS = function (el, css_attribute) {
var data = $(el).data('initial-css');
if(data && data[css_attribute])
return data[css_attribute];
else
return "";
}
Indexing an array with a jQuery object seems fishy. I'd use the ID of the object to key the array.
initialCSSValue[$(this).attr("id")] = parseInt...
Oh please, don't do that... :)
Write some CSS and use the addClass and removeClass - it leaves the styles untouched afterwards.
if anybody wants to see the plugin in action, see it here:
http://www.sj-wien.at/leopoldstadt/zeug/marcel/slidlabel/jsproblem.html

Attaching an event to multiple elements at one go

Say I have the following :
var a = $("#a");
var b = $("#b");
//I want to do something as such as the following :
$(a,b).click(function () {/* */}); // <= does not work
//instead of attaching the handler to each one separately
Obviously the above does not work because in the $ function, the second argument is the context, not another element.
So how can I attach the event to both the elements at one go ?
[Update]
peirix posted an interesting snippet in which he combines elements with the & sign; But something I noticed this :
$(a & b).click(function () { /* */ }); // <= works (event is attached to both)
$(a & b).attr("disabled", true); // <= doesn't work (nothing happens)
From what you can see above, apparently, the combination with the & sign works only when attaching events...?
The jQuery add method is what you want:
Adds more elements, matched by the given expression, to the set of matched elements
var a = $("#a");
var b = $("#b");
var combined = a.add(b)
Don't forget either that jQuery selectors support the CSS comma syntax. If you need to combine two arbitrary collections, everyone else's suggestions are on the mark, but if it's as simple as doing something to elements with IDs a and b, use $('#a,#b').
This question has already been answered, but I think a simpler more streamlined way to accomplish the same end would be to rely on the similarities between jQuery's and CSS's selector model and just do:
$("#a, #b").click(function () {/* */});
I use this frequently, and have never seen it not work (I can't speak for jQuery versions before 1.3.2 though as I have not tested this there). Hopefully this helps someone someday.
UPDATE: I just reread the thread, and missed the comment you made about having the nodes in question already saved off to variables, but this approach will still work, with one minor tweek. you will want to do:
var a = $("#a");
var b = $("#b");
$(a.selector+", "+b.selector).click(function () {/* */});
One of the cool things that jquery does is that it adds a few jquery specific properties to the node that it returns (selector, which is the original selector used to grab that node is one of them). You may run into some issues with this if the selector you used already contained commas. Its also probably arguable if this is any easier then just using add, but its a fun example of how cool jquery can be :).
You could just put them in an array:
$.each([a, b], function()
{
this.click(function () { });
});
Why don't you use an array? This works on my side:
$([item1, item2]).on('click', function() {
// your logic
});
I just tried messing around with this, and found something very cool:
$(a & b).click(function() { /* WORKS! */ });
supersweet!
Edit: Now I feel really embarrassed for not testing this properly. What this did, was actually to put the click event on everything... Not sure why it does that, though...
You can also make up a class name and give each element that you want to work with that class. Then you can bind the event to all elements sharing that class.
<p><a class="fakeClass" href="#">Click Me!</a></p>
<p><a class="fakeClass" href="#">No, Click Me!</a></p>
<div class="fakeClass">Please, Click Me!</div>
<script type="text/javascript">
$(function () {
$(".fakeClass").on("click", function () {
alert("Clicked!");
});
})
</script>
try this: sweet and simple.
var handler = function() {
alert('hi!');
}
$.each([a,b], function() {
this.click(handler);
}
BTW, this method is not worth the trouble.
If you already know there are just two of these methods, then I guess the best bet would be
a.click(handler);
b.click(handler);
Cheers!
Example:
$("[name=ONE_FIELD_NAME], [name=ANOTHER_FIELD_NAME]").keypress(function(e){alert(e.which);});

Categories