Adding SVG class (className) support to jQuery

Showing 1-6 of 6 messages
Adding SVG class (className) support to jQuery David Baird 5/26/09 10:01 PM
Overview of the problem: The HTML and SVG DOM extensions have
differing interface definitions for "className".  For the HTML DOM,
className is a DOMString [1].  For SVG, className is defined as type
SVGAnimatedString [2] (instead of good old DOMString) in SVGStylable
[3].  (Furthermore, not all SVG elements inherit from SVGStylable - a
fact which some Mozilla devs think is ridiculous, and I'd tend to
agree, and they plan to add support for class everywhere in spite of
the W3C recommendation [4]).  To access the class value of an SVG
element, you must call elem.className.baseVal (or elem.getAttribute
("class")).

So, I'd like to tackle this problem as either a modification of jQuery
or a plugin to jQuery.  The following jQuery interfaces would need to
be changed:

- addClass/hasClass/removeClass/toggleClass

- Selection: The Sizzle backend for allowing selection based on class
would need to be changed a little

Keith Wood has provided a jQuery plugin which solves part of this
problem by creating a wrapper around addClass, et al. [5].  But his
plugin doesn't address selection of SVG elements by class name (e.g. $
(".my-svg-class")).

My question for the devs: is a modification to jQuery itself
necessitated or can/should I factor this out into a separate plugin?
It seems that I have to drill to the bottom layers of Sizzle, which
makes me think a plugin is not ideal, and I would appreciate any
suggestions.

Here's a patch I came up with (alternatively, it should be available
here for a month: http://pastebin.com/m24bdbbd9):



--- jquery-1.3.2.js        2009-05-25 12:00:43.667854804 -0600
+++ jquery-1.3.2-modified-selectors.js        2009-05-26 22:52:33.748668152
-0600
@@ -707,27 +707,54 @@
         },

         className: {
+
+                get: function( elem ) {
+                        //var classNames = elem.className.baseVal || elem.className ||
elem.getAttribute("class");
+                        var classNames = elem.getAttribute("class");
+                        classNames = classNames ? classNames : "";
+                        return classNames
+                },
+
+                set: function( elem, value ) {
+                        elem.setAttribute("class", value);
+                },
+
+
                 // internal only, use addClass("class")
                 add: function( elem, classNames ) {
                         jQuery.each((classNames || "").split(/\s+/), function(i, className)
{
-                                if ( elem.nodeType == 1 && !jQuery.className.has( elem.className,
className ) )
-                                        elem.className += (elem.className ? " " : "") + className;
+                                var curClassName = jQuery.className.get(elem);
+                                if ( elem.nodeType == 1 && !jQuery.className.has( curClassName,
className ) )
+                                jQuery.className.set(elem, curClassName + (curClassName ? " " :
"") + className);
                         });
                 },

                 // internal only, use removeClass("class")
                 remove: function( elem, classNames ) {
                         if (elem.nodeType == 1)
-                                elem.className = classNames !== undefined ?
-                                        jQuery.grep(elem.className.split(/\s+/), function(className){
+                                jQuery.className.set(elem, classNames !== undefined ?
+                                        jQuery.grep(jQuery.className.get(elem).split(/\s+/), function
(className){
                                                 return !jQuery.className.has( classNames, className );
                                         }).join(" ") :
-                                        "";
+                                        ""
+                                );
                 },

                 // internal only, use hasClass("class")
                 has: function( elem, className ) {
-                        return elem && jQuery.inArray( className, (elem.className ||
elem).toString().split(/\s+/) ) > -1;
+                        var classNames = "";
+                        if (elem)
+                                {
+                                        if (elem.getAttribute)
+                                                {
+                                                        classNames = elem.jQuery.className.get(elem);
+                                                }
+                                        else
+                                                {
+                                                        classNames = elem; // <-- ???
+                                                }
+                                }
+                        return elem && jQuery.inArray( className, classNames.toString
().split(/\s+/) ) > -1;
                 }
         },

@@ -1779,7 +1806,8 @@

                         for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
                                 if ( elem ) {
-                                        if ( not ^ (elem.className && (" " + elem.className + "
").indexOf(match) >= 0) ) {
+                                        var className = jQuery.className.get(elem);
+                                        if ( not ^ (className && (" " + className + " ").indexOf(match)
>= 0) ) {
                                                 if ( !inplace )
                                                         result.push( elem );
                                         } else if ( inplace ) {
@@ -2005,7 +2033,8 @@
                         return (match === "*" && elem.nodeType === 1) || elem.nodeName ===
match;
                 },
                 CLASS: function(elem, match){
-                        return (" " + (elem.className || elem.getAttribute("class")) + "
")
+                        var className = jQuery.className.get(elem);
+                        return (" " + (className) + " ")
                                 .indexOf( match ) > -1;
                 },
                 ATTR: function(elem, match){
@@ -2207,7 +2236,7 @@
         if ( div.querySelectorAll && div.querySelectorAll(".TEST").length
=== 0 ) {
                 return;
         }
-
+
         Sizzle = function(query, context, extra, seed){
                 context = context || document;








[1] http://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-58190037
"Interface HTMLElement"
[2] http://www.w3.org/TR/SVG/types.html#InterfaceSVGAnimatedString
[3] http://www.w3.org/TR/SVG/types.html#InterfaceSVGStylable
[4] https://developer.mozilla.org/en/SVG/Specification_Deviations
[5] http://keith-wood.name/svg.html
Re: Adding SVG class (className) support to jQuery David Baird 5/30/09 7:27 AM
Anyone have advice on how to proceed?

The fundamental goal is to add support for selectors like this:

    $("#my-svg-element .my-class") // this currently won't work

The reason this doesn't work right now is because jQuery's class name
selection relies on the .className member of the HTML DOM.  The SVG
DOM also has .className, but it is of an incompatible type
("SVGAnimatedString").  (This is explained in more detail in the
original post).

I'm a newbie to jQuery development, so any pointers would be much
appreciated.

Thanks in advance,
David
Re: [jquery-dev] Re: Adding SVG class (className) support to jQuery John Resig 5/30/09 9:59 AM
David -

It'd be good to file a ticket on the issue and add in your patch and a
link back to this discussion:
http://dev.jquery.com/newticket

How well have you tested the change across browsers? Does changing
.className to .getAttribute/setAttribute("class") have any other
ramifications? It's definitely something that we can look in to for
another release.

--John
Re: [jquery-dev] Re: Adding SVG class (className) support to jQuery David Baird 5/30/09 12:32 PM
On Sat, May 30, 2009 at 10:59 AM, John Resig <jer...@gmail.com> wrote:
> It'd be good to file a ticket on the issue and add in your patch and a
> link back to this discussion:
> http://dev.jquery.com/newticket

Alrighty, I made a new ticket here:

    http://dev.jquery.com/ticket/4705

> How well have you tested the change across browsers? Does changing
> .className to .getAttribute/setAttribute("class") have any other
> ramifications? It's definitely something that we can look in to for
> another release.

I haven't tested across browsers and I'm not sure about ramifications.
 One thing I am curious about is to see if this hurts jQuery's
selector performance.  Do people test that by simply running a query
several times inside of a loop, and measure the total time?

-David

Re: [jquery-dev] Re: Adding SVG class (className) support to jQuery Karl Swedberg 5/30/09 2:32 PM

On May 30, 2009, at 12:59 PM, John Resig wrote:


David -

It'd be good to file a ticket on the issue and add in your patch and a
link back to this discussion:
http://dev.jquery.com/newticket

How well have you tested the change across browsers? Does changing
.className to .getAttribute/setAttribute("class") have any other
ramifications? It's definitely something that we can look in to for
another release.

If I recall correctly, getAttribute/setAttribute("class") doesn't work in (at least) IE 6 and 7. 



--Karl

____________
Karl Swedberg

Re: [jquery-dev] Re: Adding SVG class (className) support to jQuery David Baird 5/30/09 2:59 PM
On Sat, May 30, 2009 at 3:32 PM, Karl Swedberg <ka...@englishrules.com> wrote:
> If I recall correctly, getAttribute/setAttribute("class") doesn't work in
> (at least) IE 6 and 7.

I think you're right.  I don't have any browsers setup for testing
right now, so I can't try things out.  But, here are two alternatives
that may or may not be better:

This first technique relies on "namespaceURI" being part of the DOM interface:

    svgNS = 'http://www.w3.org/2000/svg';

    jQuery.className.get = function( elem ) {
          if (elem.namespaceURI == svgNS) {
              return elem.className.baseVal; // SVG DOM
          } else {
              return elem.className; // HTML DOM
          }
    }

    jQuery.className.set = function( elem, value ) {
          if (elem.namespaceURI == svgNS) {
              return elem.className.baseVal = value; // SVG DOM
          } else {
              return elem.className = value; // HTML DOM
          }
    }

This second technique relies upon elem.className.baseVal being
"undefined" (and not generating an error or something):

    jQuery.className.get = function( elem ) {
          if (elem.className.baseVal == undefined) {
              return elem.className; // HTML DOM
          } else {
              return elem.className.baseVal; // SVG DOM
          }
    }

    jQuery.className.set = function( elem, value ) {
          if (elem.className.baseVal == undefined) {
              return elem.className = value; // HTML DOM
          } else {
              return elem.className.baseVal = value; // SVG DOM
          }
    }

-David