Working with SVG in jQuery

I recently notice that if you create element like a circle in SVG (inline SVG embedded into HTML), using inspector/firebug or jQuery, your circle is not visible on SVG until you refresh the SVG, I found a hack to force refresh of the SVG, I just get text of the SVG and insert it again into the DOM, and all elements that were not visible like circle mention before will be rendered.

Here is referesh function, as jQuery plugin


$.fn.xml = function() {
    return (new XMLSerializer()).serializeToString(this[0]);
};

$.fn.DOMRefresh = function() {
    return $($(this.xml()).replaceAll(this));
};

Those plugins should work with every XML embeded into HTML not only SVG. Don’t look very nice but it work, but I found a better way fix jQuery to work with SVG. I found that when I use function document.createElementNS instead of document.createElement elements added to inline SVG using for instance appendChild function everything work fine. So only thing that need to be done for jQuery to work with inline SVG is to replace this function if element is SVG. First I wrote method in jQuery object that test if element is SVG element, there are only three elements that have the same name in HTML and SVG is a, script, style and title tags so I didn’t put them in.

isSVGElement: function( o ) {
        if (o instanceof SVGElement) {
            return true;
        } else {
            if (typeof o === 'string') {
                return $.inArray(o, ['altGlyph', 'altGlyphDef',
                                     'altGlyphItem', 'animate',
                                     'animateColor', 'animateMotion',
                                     'animateTransform', 'circle',
                                     'clipPath', 'color-profile',
                                     'cursor', 'defs', 'desc', 'ellipse',
                                     'feBlend', 'feColorMatrix',
                                     'feComponentTransfer',
                                     'feComposite', 'feConvolveMatrix',
                                     'feDiffuseLighting',
                                     'feDisplacementMap',
                                     'feDistantLight', 'feFlood',
                                     'feFuncA', 'feFuncB', 'feFuncG',
                                     'feFuncR', 'feGaussianBlur',
                                     'feImage', 'feMerge', 'feMergeNode',
                                     'feMorphology', 'feOffset',
                                     'fePointLight',
                                     'feSpecularLighting', 'feSpotLight',
                                     'feTile', 'feTurbulence', 'filter',
                                     'font', 'font-face',
                                     'font-face-format',
                                     'font-face-name', 'font-face-src',
                                     'font-face-uri', 'foreignObject',
                                     'g', 'glyph', 'glyphRef', 'hkern',
                                     'image', 'line', 'linearGradient',
                                     'marker', 'mask', 'metadata',
                                     'missing-glyph', 'mpath', 'path',
                                     'pattern', 'polygon', 'polyline',
                                     'radialGradient', 'rect',
                                     'set', 'stop', 'svg',
                                     'switch', 'symbol', 'text',
                                     'textPath', 'tref',
                                     'tspan', 'use', 'view',
                                     'vkern']) !== -1;
            }
        }
    }

I get the list of elements from Mozilla MDN.

I added this method to main jQuery.extend({ that add methods to jQuery object. Next thing is to replace createElement with createElementNS, there are only 2 places with this in parseHTML (in the same jQuery.extend) and createSafeFragment function, only one is responsible for inserting elements – parseHTML. Below is the code for that function.


	parseHTML: function( data, context, keepScripts ) {
		if ( !data || typeof data !== "string" ) {
			return null;
		}
		if ( typeof context === "boolean" ) {
			keepScripts = context;
			context = false;
		}
		context = context || document;

		var parsed = rsingleTag.exec( data ),
			scripts = !keepScripts && [];

		// Single tag
		if ( parsed ) {
			if ( jQuery.isSVGElement( parsed[1] ) ) {
				return [ context.createElementNS(
					"http://www.w3.org/2000/svg",
					parsed[1]) ];
			} else {
				return [ context.createElement( parsed[1] ) ];
			}
		}

		parsed = jQuery.buildFragment( [ data ], context, scripts );
		if ( scripts ) {
			jQuery( scripts ).remove();
		}
		return jQuery.merge( [], parsed.childNodes );
	},

Unfortunetnly not all manupulation methods that create new elements will work but it’s better then refresh hack. The stuff that don’t work is when you create more then one element from a string or if you put some attributes. In this case jQuery use document fragments and innerHTML to create the DOM. If we will want to fix that too we will need to write a parser that will call createElementNS.

You can also include those function after you load jQuery so original code will not be changed.

The other way is to use document.createElementNS and element.setAttributeNS that need to be used if you want to add xlink:href attribute (it use http://www.w3.org/1999/xlink namespace).

About these ads

,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: