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/
Related
We just upgraded our jQuery version from v1.7 to v3.4.1. We have code which passes a jQuery object (which doesn't yet exist in the DOM, and I'm guessing is the root cause of this problem) to a function. However, when attempting to handle this newly created variable, it only returns the root jQuery function itself: jQuery.fn.init {}
This SO answer helped me realize the issue: Help understanding jQuery's jQuery.fn.init Why is init in fn
Actual Snippet:
create: function (params) {
console.log('params.target:', params.target);
var $target, id;
if (_.isString(params.target)) {
if (params.target.charAt(0) === '#') {
$target = $('#send-to-friend-dialog');
console.log('$target1: ', $target);
} else {
$target = $('#' + params.target);
}
} else if (params.target instanceof jQuery) {
$target = params.target;
} else {
$target = $('#dialog-container');
}
console.log('$target.length: ', $target.length)
// if no element found, create one
if ($target.length === 0) {
console.log('$target2 : ', $target);
if ($target.selector && $target.selector.charAt(0) === '#') {
id = $target.selector.substr(1);
$target = $('<div>').attr('id', id).addClass('dialog-content').appendTo('body');
}
}
// create the dialog
console.log('$target3: ', $target);
this.$container = $target;
this.$container.dialog($.extend(true, {}, this.settings, params.options || {}));
return this.$container;
},
Output in v1.12.4: https://i.imgur.com/4xg1u1s.png
Output in v3.4.1: https://i.imgur.com/QPITza9.png
So while it is defined, the object itself is a very different output in both versions and I'm wondering why?
Thank you!
The jQuery .selector property was deprecated in 1.7, and removed in 3.0. See https://api.jquery.com/selector/. This was an internal interface, not intended to be used by applications.
You'll need to redesign your code so it doesn't need this. The documentation suggests an alternative:
For example, a "foo" plugin could be written as $.fn.foo = function( selector, options ) { /* plugin code goes here */ };, and the person using the plugin would write $( "div.bar" ).foo( "div.bar", {dog: "bark"} ); with the "div.bar" selector repeated as the first argument of .foo().
What's wrong with this piece of code?
(function (){
'use strict';
// add hasClass function
String.prototype.hasClass = function (className) {
return this.className.search('(\\s|^)' + className + '(\\s|$)') != -1 ? true : false;
};
console.log(document.getElementById('link').hasClass('test'));
})();
I'd expect it to return true or false, but all I get is
TypeError: document.getElementById("link").hasClass is not a function**
UPD: Thanks guys. Now i get it. I should set method to Object or Element (What is more right?) not String!
document.getElementById('link') doesn't return a String, it returns a DOM element. You could try this instead:-
Element.prototype.hasClass = function (className) {
return this.className.search('(\\s|^)' + className + '(\\s|$)') != -1 ? true : false;
};
console.log(document.getElementById('link').hasClass('test'));
As far as I know, hasClass is not a method of Element, you're likely thinking of the jQuery method, as such you would have to use jQuery and select the element using a jQuery selector. Other frameworks may also have such methods, I believe YUI does as well.
The way to do this is to write a function that receives a DOM element, as the String object has nothing to do with it ;)
A simple example:
function hasClass(element, classcheck){
return element.className.indexOf(classcheck) !== -1;
}
So your code would look like:
(function (){
'use strict';
// add hasClass function
function hasClass(element, classcheck){
return element && element.className && element.className.indexOf(classcheck) !== -1;
}
console.log(hasClass(document.body,'test'));
})();
Obviously, you should be checking that the first argument is actually a DOM element too (quite a lot of different ways to achieve that), but this is the right way to go about it.
I'm having a problem, here is the javascript/jquery
getTextForDisplay: function() {
var displayText = "Select...";
var options = this.dataSource._data;
var selectedOptions = $.filter(options, function(index){
return this.selected;
});
if (selectedOptions.length == 1) {
displayText = "length1";
}
else if (selectedOptions.length > 1) {
displayText = "Multiple...";
}
return displayText;
}
});
so this is in regards to a multi-select dropdown box that has checkboxes, the options variable is an observable array pulling its data from a viewmodel, so what I am trying to do is to display "length1" if only one of the checkboxes is selected and to display "Multiple..." if more than one checkbox is selected, this seems pretty straightforward but I keep getting a error in when I run it. the error is c.replace is not a function and the error is in the jquery.min.js file. If I remove index from the .filter then it still doesn't work but it doesn't error out either.
jQuery doesn't define a jQuery.filter() function (at least, not in the public API). The .filter() it does define is a method for jQuery collections.
Perhaps jQuery.grep() is what you're looking for?
var selectedOptions = $.grep(options, function (option, index) {
return option.selected;
});
There is no such thing as $.filter(), unless you wrote it yourself or are using a plugin.
The correct syntax is
options.filter(function (index) {
...
});
Here's the documentation: http://api.jquery.com/filter/
You aren't properly using the jQuery.filter method, however, there is a documented method that does what you need. You should use documented methods rather than undocumented methods.
$.grep(options, function(){
return this.selected;
});
For this to work, options must be an array-like structure.
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
I think I have mistaken some fundamentals here, because I think this should work. I am trying to to through the child p and div elements of the matched set, and remove those which fail to meet the required wordcount from the matched set.
I have tested the wordCount plugin, and the if statement it is being used it, and all seems to be working fine, but my element is not being removed from the matched set.
(function($){
$.fn.extend({
textBlocks: function(count){
var JQ_Object = $(this);
if(!count) count = 100;
return this.each(function(){
$(this).find("p, div").each(function(){
if($(this).wordCount()<count){
var x = $(this);
JQ_Object.not(x);
};
});
return JQ_Object;
});
}
});
})(jQuery);
Here is the wordCount plugin, just in case you wondered:
(function($){
$.fn.extend({
wordCount: function(){
return $(this).html().split(" ").length;
}
});
})(jQuery);
I made a few changes... see fiddle for working example and code for comments.
http://jsfiddle.net/8PXpt/
(function ($){
$.fn.extend({
wordCount: function (){
//Don't need $(this), this already refers to the jQuery object
//Always trim .html() and .text() when using .split()
//May want to use .text() instead of .html() - I leave that to you
return $.trim(this.html()).split(' ').length;
}
});
})(jQuery);
(function ($){
$.fn.extend({
textBlocks: function (count){
var collection = this;
//Check that a number was passed
//"50" would break your extension
if(typeof count !== 'number') {
count = 100;
}
//Removed $('div, p') - this should be part of your call
//See ready function below
this.each(function (){
if ($(this).wordCount() < count){
//This could be double assignment
//but better safe than sorry
collection = collection.not(this);
};
});
//Return what is left (what passed)
return collection ;
}
});
})(jQuery);
$(function() {
//Here is where you define your selector... in your case 'div, p'
$('div, p').textBlocks(2);
});
Have you tried $(this).remove() rather than JQ_Object.not(x);
I think .not() removes them from the selection rather than from the HTML... unless that's what you're trying to do
You're creating a new JQ_Object in the internal each, so I'm not sure if it would modify the original JQ_Object. I'm not 100% on that though. Try JQ_Object.not(this).
This assumes, however, that .each is synchronous, which I'd hope it isn't. If that's the case, you'd need to make use of jQuery's while function.
This should give you the desired result, but I'd be wary each being asynchronous.
return $(this).find("p, div").each(function(){
if($(this).wordCount()<count){
JQ_Object.not(this);
};
});
EDIT:
I'm not to sure about the above code. What I'd do is use a callback. This assumes a callback is passed in to your plugin.
$(this).find("p, div").each(function(){
if($(this).wordCount()<count){
JQ_Object.not(this);
};
}).when(function () {
callback(JQ_Object);
});