How to detect if browser support specified css pseudo-class? - javascript

What's concept of detecting support of any css pseudo-class in browser through JavaScript? Exactly, I want to check if user's browser supports :checked pseudo-class or not, because I've made some CSS-popups with checkboxes and needs to do fallbacks for old browsers.
ANSWER: I'm found already implemented method of testing css selectors in a Modernizr "Additional Tests".

You can simply check if your style with pseudo-class was applied.
Something like this: http://jsfiddle.net/qPmT2/1/

For anyone still looking for a quick solution to this problem, I cribbed together something based on a few of the other answers in this thread. My goal was to make it succinct.
function supportsSelector (selector) {
const style = document.createElement('style')
document.head.appendChild(style)
try {
style.sheet.insertRule(selector + '{}', 0)
} catch (e) {
return false
} finally {
document.head.removeChild(style)
}
return true
}
supportsSelector(':hover') // true
supportsSelector(':fake') // false

stylesheet.insertRule(rule, index) method will throw error if the rule is invalid. so we can use it.
var support_pseudo = function (){
var ss = document.styleSheets[0];
if(!ss){
var el = document.createElement('style');
document.head.appendChild(el);
ss = document.styleSheets[0];
document.head.removeChild(el);
}
return function (pseudo_class){
try{
if(!(/^:/).test(pseudo_class)){
pseudo_class = ':'+pseudo_class;
}
ss.insertRule('html'+pseudo_class+'{}',0);
ss.deleteRule(0);
return true;
}catch(e){
return false;
}
};
}();
//test
support_pseudo(':hover'); //true
support_pseudo(':before'); //true
support_pseudo(':hello'); //false
support_pseudo(':world'); //false

If you're OK with using Javascript, you might skip the detection and go right for the shim: Selectivizr

Related

jQuery .hasClass() method fails for SVG elements

I have a set of SVG elements with the classes node and link. My program should detect whether an element has the node class or the link class upon hovering over any of the SVG elements. However, for some reason, the .hasClass() doesn't seem to work:
$(".node").hover(function(evt){
console.log($(this).attr("class")); //returns "node"
console.log($(this).hasClass('node')); //returns false
}, function(){console.log("Done");});
So the element I hovered on has the class node, and jQuery detects that too, as shown by console.log($(this).attr("class"));, but for some reason the actual .hasClass() fails. Why is this? Is it failing because of the SVG?
The class attribute for HTML element doesn't have the same meaning in SVG.
$("<b></b>").addClass($(this).attr("class")).hasClass("node")
Or
/(^|\s)node(\s|$)/.test($(this).attr("class"))
for SVG elements.
EDIT .hasClass seems to work just fine (at least in IE9 and FF) http://jsfiddle.net/X6BPX/1/
So the problem could be any combination of the following: a syntax error, using an outdated browser, using an outdated version of jQuery.
As Bergi pointed out in comments, jQuery silently fails on SVG elements on account of className returning an SVGAnimatedString object instead of a normal DOMString.
See this JSFiddle for a comparison.
I was tempted to submit a pull request on this, but did a quick project search, and apparently the jQuery project stance on SVG issues is wontfix: https://github.com/jquery/jquery/pull/1511
If you're using D3, you could use d3.select(this).classed('node'). Note that D3 correctly returns for both HTML elements and SVG elements.
This is not the fastest option ever, but it is a possible solution. Instead of using jQuery's hasClass you could instead obtain the class attribute as a string and use indexOf to search through it. There are probably use cases where this will fail, so I wouldn't recommend this except for super simple projects.
Working example:
var s = $(this).attr('class');
if( s.indexOf('node')!==-1 ){
// do something
}
Remember: indexOf returns -1 when it can't find anything, not 0. 0 is returned when the substring starts at index 0.
This is a hack for addClass, removeClass, hasClass jquery methods for before jquery 3.x.x versions.
$.fn.extend({
addSVGClass: function (cls) {
return this.each(function () {
var classList = $(this).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) === -1) {
classListArr.push(cls);
classList = classListArr.join(" ").trim();
$(this).attr('class', classList);
}
} else {
$(this).attr('class', cls);
}
});
},
removeSVGClass: function (cls) {
return this.each(function () {
var classList = $(this).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) !== -1) {
delete classListArr[classListArr.indexOf(cls)];
classList = classListArr.join(" ").trim();
$(this).attr('class', classList);
}
}
});
},
hasSVGClass: function (cls) {
var el = this[0];
var classList = $(el).attr('class');
if (classList) {
var classListArr = classList.split(" ");
if (classListArr.indexOf(cls) !== -1) {
return true;
} else {
return false;
}
}
return false;
}
});
usage :
$('.svg-element').addSVGClass('selected');
Works. But be sure to close the function
$(".node").hover(function(evt){
console.log($(this).attr("class")); //returns "node"
console.log($(this).hasClass('node')); //returns false
}, function(){console.log("Done");});
http://jsfiddle.net/X6BPX/

how do I override appendChild()?

appendChild = function(message) {
console.log("intercepted!");
}
using the code above does not seem to work.
Anyone knows?
What you might want to replace is Element.prototype.appendChild but it's probably a bad idea.
This example adds the text intercepted in the inserted element :
var f = Element.prototype.appendChild;
Element.prototype.appendChild = function(){f.apply(this, arguments);arguments[0].innerHTML="!Intercepted!"; };
document.body.appendChild(document.createElement("div"));
Demonstration
It is ill advised to overwrite native functions, but if you do it than make sure you return the appended element as well to prevent problems with code that uses the returned value of the native "appendChild" function:
window.callbackFunc = function(elem, args) {
// write some logic here
}
window.f = Element.prototype.appendChild;
Element.prototype.appendChild = function() {
window.callbackFunc.call(this, arguments);
return window.f.apply(this, arguments);
};

Can I subclass a DOM-class?

I was wondering if I can create a subclass of HTMLDivElement. Like this.
MyDivElement.prototype.pickColor = function()
{
return this.picked;
}
function MyDivElement()
{
this = new HTMLDivElement();
this.picked = 'unknowd';
}
alert(this.picked); // print: 'unkowd'
Is (something like) this possible?
If not, what is the best way to achieve this?
In browsers where __proto__ is exposed and mutable you can sub class DOM elements. It looks like this:
function CustomEl () {
var el = document.createElement('div')
el.__proto__ = CustomEl.prototype
return el
}
CustomEl.prototype.__proto__ = HTMLDivElement.prototype
I also played with it in more detail on jsFiddle. Unfortunately though IE and Opera don't allow __proto__ access and have no plans to in the future as far as I know.
new HTMLDivElement(); throws a TypError "Illegal constructor" in Chrome - so it's not possible.
Update: I've tested in other current browsers, and they throw various types of errors - but they all throw.
Actually, this would work:
function MyDivElement() {
this.picked = 'unknowd';
}
MyDivElement.prototype = document.createElement('div');
var mydiv = new MyDivElement();
But I'm not sure how you could use this pattern...
In some browsers, you can extend the prototype, in others, no. I'll let you guess the ones where you can't. :-) That's not really the same as extending a DOM element, but it does let you do a certain subset of the things for which you might want that facility. The thing is, DOM elements aren't really JavaScript entities; they're only simulacrums provided by the runtime system. (Maybe someday all the jsdom work will actually come to fruition.)
Well ok I'll tell you about the problematic browsers: IE doesn't like that at all. However others do. If you've ever looked at the Prototype library, you'll come across a manifestation of that fact all the time via nasty irritating IE-only bugs when you forget to Prototype-ize a DOM element reference.
(IE9 may be different, but I sort-of doubt it.)
This is the kind of thing that's dirt simple to test over at jsfiddle.
I'm experimenting with this a little bit. A big difficulty is that you need the context of a document to create an element. You can go with window.document as a default, but that's boring.
Here's the POC I'm working on:
function CustomNode(type, parent) {
if (type instanceof Node) {
// Decorate a preexisting node appropriately if called that way.
if (arguments.length === 2 && type.ownerDocument !== parent) {
// Import the node if it's not owned by the requested document.
type = parent.importNode(type, true);
}
return Object.assign(type, this.__proto__);
}
//Normal flow, e.g., new CustomNode("div");
var d = document;
if (parent) {
// Alt document flow, e.g., new CustomNode("div", xmlDoc);
if (parent.nodeType === 9) {
d = parent;
} else {
// Support for new CustomNode("div", parentElement);
// This doesn't append the element, just makes sure
// the documents match
d = parent.ownerDocument;
}
}
var inst;
// Creation flags
if (type[0] === '#') { //text
inst = d.createTextNode(type.substr(1));
} else if (type[0] === '?') { //Processing instruction
type = type.substr(1).split(' ');
inst = d.createProcessingInstruction(type.shift(), type.join(' '));
} else if (type[0] === '[') { // CDATA
inst = d.createCDATASection(type.substr(1));
} else if (type[0] === '/') { // Comment
inst = d.createComment(type.substr(1));
} else { //Everything else gets an element.
inst = d.createElement(type);
}
// DE-COR-ATE
Object.assign(inst, this.__proto__);
return inst;
}
// Decorator for customized NodeLists; probably inefficient. Decorates
// contents with CustomNode
function CustomNodeList(list) {
var Self = this.constructor,
CNode = this.Node;
return Object.assign([].map.call(list, function (node) {
return new CNode(node);
}), this.__proto__);
}
CustomNodeList.prototype = {
// so we know how to wrap...
Node: CustomNode,
text: function () {
return this[0].textContent;
}
};
CustomNode.prototype = {
// So we know how to decorate NodeLists
NodeList: CustomNodeList,
// So we know how to decorate Nodes
Node: CustomNode,
// Easy make-and-attach
new: function (type, attach) {
var CNode = this.Node;
var ret = new CNode(type, this.ownerDocument);
if (attach) {
this.appendChild(ret);
}
return ret;
},
// NodeLists with ES5 iterators!
find: function () {
var CNodeList = this.NodeList;
return new CNodeList(this.querySelectorAll.apply(this, arguments));
},
kids: function () {
var CNodeList = this.NodeList;
return new CNodeList(this.childNodes);
}
};
Mind, this is likely a bad idea: everything in the same scope of these functions (including the elements themselves) will never get garbage collected, as the GC in most browsers is dead-stupid when it comes to elements referencing objects. I'll never use it for production for that reason alone: it's a straight-up memory leak.

How to check if css box-shadow is supported (jQuery)?

I'm creating a layout in full css. However, some browser (such as IE6) do not support box-shadow (.. and -webkit-box-shadow or -moz-box-shadow). I would like to check if it's not supported and then add other styles.
How's this possible in jQuery?
Martti Laine
var check = document.createElement('div');
var shadow = !!(0 + check.style['MozBoxShadow']);
if(shadow)
alert('moz-box-shadow available');
That is the doing-it-yourself way. Other reliable way is the modernizr library, which does feature detection for you.
http://www.modernizr.com/
No jQuery needed at all here.
A neat function (pure JavaScript, no jQuery) to check which CSS features are supported by the browser is described in Quick Tip: Detect CSS3 Support in Browsers with JavaScript.
Function is as follows:
var supports = (function() {
var div = document.createElement('div'),
vendors = 'Khtml Ms O Moz Webkit'.split(' '),
len = vendors.length;
return function(prop) {
if (prop in div.style) {
return true;
}
prop = prop.replace(/^[a-z]/, function(val) {
return val.toUpperCase();
});
while (len--) {
if (vendors[len] + prop in div.style) {
// browser supports box-shadow. Do what you need.
// Or use a bang (!) to test if the browser doesn't.
return true;
}
}
return false;
};
})();
Usage is like this:
if (supports('boxShadow')) {
// Do whatever
}
It worked like a charm for me! :-)

Is it possible to listen to a "style change" event?

Is it possible to create an event listener in jQuery that can be bound to any style changes? For example, if I want to "do" something when an element changes dimensions, or any other changes in the style attribute I could do:
$('div').bind('style', function() {
console.log($(this).css('height'));
});
$('div').height(100); // yields '100'
It would be really useful.
Any ideas?
UPDATE
Sorry for answering this myself, but I wrote a neat solution that might fit someone else:
(function() {
var ev = new $.Event('style'),
orig = $.fn.css;
$.fn.css = function() {
$(this).trigger(ev);
return orig.apply(this, arguments);
}
})();
This will temporary override the internal prototype.css method and the redefine it with a trigger at the end. So it works like this:
$('p').bind('style', function(e) {
console.log( $(this).attr('style') );
});
$('p').width(100);
$('p').css('color','red');
Things have moved on a bit since the question was asked - it is now possible to use a MutationObserver to detect changes in the 'style' attribute of an element, no jQuery required:
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutationRecord) {
console.log('style changed!');
});
});
var target = document.getElementById('myId');
observer.observe(target, { attributes : true, attributeFilter : ['style'] });
The argument that gets passed to the callback function is a MutationRecord object that lets you get hold of the old and new style values.
Support is good in modern browsers including IE 11+.
Since jQuery is open-source, I would guess that you could tweak the css function to call a function of your choice every time it is invoked (passing the jQuery object). Of course, you'll want to scour the jQuery code to make sure there is nothing else it uses internally to set CSS properties. Ideally, you'd want to write a separate plugin for jQuery so that it does not interfere with the jQuery library itself, but you'll have to decide whether or not that is feasible for your project.
The declaration of your event object has to be inside your new css function. Otherwise the event can only be fired once.
(function() {
orig = $.fn.css;
$.fn.css = function() {
var ev = new $.Event('style');
orig.apply(this, arguments);
$(this).trigger(ev);
}
})();
I think the best answer if from Mike in the case you can't launch your event because is not from your code. But I get some errors when I used it. So I write a new answer for show you the code that I use.
Extension
// Extends functionality of ".css()"
// This could be renamed if you'd like (i.e. "$.fn.cssWithListener = func ...")
(function() {
orig = $.fn.css;
$.fn.css = function() {
var result = orig.apply(this, arguments);
$(this).trigger('stylechanged');
return result;
}
})();
Usage
// Add listener
$('element').on('stylechanged', function () {
console.log('css changed');
});
// Perform change
$('element').css('background', 'red');
I got error because var ev = new $.Event('style'); Something like style was not defined in HtmlDiv.. I removed it, and I launch now $(this).trigger("stylechanged"). Another problem was that Mike didn't return the resulto of $(css, ..) then It can make problems in some cases. So I get the result and return it. Now works ^^ In every css change include from some libs that I can't modify and trigger an event.
As others have suggested, if you have control over whatever code is changing the style of the element you could fire a custom event when you change the element's height:
$('#blah').bind('height-changed',function(){...});
...
$('#blah').css({height:'100px'});
$('#blah').trigger('height-changed');
Otherwise, although pretty resource-intensive, you could set a timer to periodically check for changes to the element's height...
There is no inbuilt support for the style change event in jQuery or in java script. But jQuery supports to create custom event and listen to it but every time there is a change, you should have a way to trigger it on yourself. So it will not be a complete solution.
Interesting question. The problem is that height() does not accept a callback, so you wouldn't be able to fire up a callback. Use either animate() or css() to set the height and then trigger the custom event in the callback. Here is an example using animate() , tested and works (demo), as a proof of concept :
$('#test').bind('style', function() {
alert($(this).css('height'));
});
$('#test').animate({height: 100},function(){
$(this).trigger('style');
});
you can try Jquery plugin , it trigger events when css is change and its easy to use
http://meetselva.github.io/#gist-section-attrchangeExtension
$([selector]).attrchange({
trackValues: true,
callback: function (e) {
//console.log( '<p>Attribute <b>' + e.attributeName +'</b> changed from <b>' + e.oldValue +'</b> to <b>' + e.newValue +'</b></p>');
//event.attributeName - Attribute Name
//event.oldValue - Prev Value
//event.newValue - New Value
}
});
Just adding and formalizing #David 's solution from above:
Note that jQuery functions are chainable and return 'this' so that multiple invocations can be called one after the other (e.g $container.css("overflow", "hidden").css("outline", 0);).
So the improved code should be:
(function() {
var ev = new $.Event('style'),
orig = $.fn.css;
$.fn.css = function() {
var ret = orig.apply(this, arguments);
$(this).trigger(ev);
return ret; // must include this
}
})();
How about jQuery cssHooks?
Maybe I do not understand the question, but what you are searching for is easily done with cssHooks, without changing css() function.
copy from documentation:
(function( $ ) {
// First, check to see if cssHooks are supported
if ( !$.cssHooks ) {
// If not, output an error message
throw( new Error( "jQuery 1.4.3 or above is required for this plugin to work" ) );
}
// Wrap in a document ready call, because jQuery writes
// cssHooks at this time and will blow away your functions
// if they exist.
$(function () {
$.cssHooks[ "someCSSProp" ] = {
get: function( elem, computed, extra ) {
// Handle getting the CSS property
},
set: function( elem, value ) {
// Handle setting the CSS value
}
};
});
})( jQuery );
https://api.jquery.com/jQuery.cssHooks/
I had the same problem, so I wrote this. It works rather well. Looks great if you mix it with some CSS transitions.
function toggle_visibility(id) {
var e = document.getElementById("mjwelcome");
if(e.style.height == '')
e.style.height = '0px';
else
e.style.height = '';
}

Categories

Resources