Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / Svg.html
1 <!DOCTYPE html>
2 <html>
3 <head>
4   <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5   <title>The source code</title>
6   <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7   <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8   <style type="text/css">
9     .highlight { display: block; background-color: #ddd; }
10   </style>
11   <script type="text/javascript">
12     function highlight() {
13       document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14     }
15   </script>
16 </head>
17 <body onload="prettyPrint(); highlight();">
18   <pre class="prettyprint lang-js"><span id='Ext-draw-engine-Svg'>/**
19 </span> * @class Ext.draw.engine.Svg
20  * @extends Ext.draw.Surface
21  * Provides specific methods to draw with SVG.
22  */
23 Ext.define('Ext.draw.engine.Svg', {
24
25     /* Begin Definitions */
26
27     extend: 'Ext.draw.Surface',
28
29     requires: ['Ext.draw.Draw', 'Ext.draw.Sprite', 'Ext.draw.Matrix', 'Ext.Element'],
30
31     /* End Definitions */
32
33     engine: 'Svg',
34
35     trimRe: /^\s+|\s+$/g,
36     spacesRe: /\s+/,
37     xlink: &quot;http:/&quot; + &quot;/www.w3.org/1999/xlink&quot;,
38
39     translateAttrs: {
40         radius: &quot;r&quot;,
41         radiusX: &quot;rx&quot;,
42         radiusY: &quot;ry&quot;,
43         path: &quot;d&quot;,
44         lineWidth: &quot;stroke-width&quot;,
45         fillOpacity: &quot;fill-opacity&quot;,
46         strokeOpacity: &quot;stroke-opacity&quot;,
47         strokeLinejoin: &quot;stroke-linejoin&quot;
48     },
49     
50     parsers: {},
51
52     minDefaults: {
53         circle: {
54             cx: 0,
55             cy: 0,
56             r: 0,
57             fill: &quot;none&quot;,
58             stroke: null,
59             &quot;stroke-width&quot;: null,
60             opacity: null,
61             &quot;fill-opacity&quot;: null,
62             &quot;stroke-opacity&quot;: null
63         },
64         ellipse: {
65             cx: 0,
66             cy: 0,
67             rx: 0,
68             ry: 0,
69             fill: &quot;none&quot;,
70             stroke: null,
71             &quot;stroke-width&quot;: null,
72             opacity: null,
73             &quot;fill-opacity&quot;: null,
74             &quot;stroke-opacity&quot;: null
75         },
76         rect: {
77             x: 0,
78             y: 0,
79             width: 0,
80             height: 0,
81             rx: 0,
82             ry: 0,
83             fill: &quot;none&quot;,
84             stroke: null,
85             &quot;stroke-width&quot;: null,
86             opacity: null,
87             &quot;fill-opacity&quot;: null,
88             &quot;stroke-opacity&quot;: null
89         },
90         text: {
91             x: 0,
92             y: 0,
93             &quot;text-anchor&quot;: &quot;start&quot;,
94             &quot;font-family&quot;: null,
95             &quot;font-size&quot;: null,
96             &quot;font-weight&quot;: null,
97             &quot;font-style&quot;: null,
98             fill: &quot;#000&quot;,
99             stroke: null,
100             &quot;stroke-width&quot;: null,
101             opacity: null,
102             &quot;fill-opacity&quot;: null,
103             &quot;stroke-opacity&quot;: null
104         },
105         path: {
106             d: &quot;M0,0&quot;,
107             fill: &quot;none&quot;,
108             stroke: null,
109             &quot;stroke-width&quot;: null,
110             opacity: null,
111             &quot;fill-opacity&quot;: null,
112             &quot;stroke-opacity&quot;: null
113         },
114         image: {
115             x: 0,
116             y: 0,
117             width: 0,
118             height: 0,
119             preserveAspectRatio: &quot;none&quot;,
120             opacity: null
121         }
122     },
123
124     createSvgElement: function(type, attrs) {
125         var el = this.domRef.createElementNS(&quot;http:/&quot; + &quot;/www.w3.org/2000/svg&quot;, type),
126             key;
127         if (attrs) {
128             for (key in attrs) {
129                 el.setAttribute(key, String(attrs[key]));
130             }
131         }
132         return el;
133     },
134
135     createSpriteElement: function(sprite) {
136         // Create svg element and append to the DOM.
137         var el = this.createSvgElement(sprite.type);
138         el.id = sprite.id;
139         if (el.style) {
140             el.style.webkitTapHighlightColor = &quot;rgba(0,0,0,0)&quot;;
141         }
142         sprite.el = Ext.get(el);
143         this.applyZIndex(sprite); //performs the insertion
144         sprite.matrix = Ext.create('Ext.draw.Matrix');
145         sprite.bbox = {
146             plain: 0,
147             transform: 0
148         };
149         sprite.fireEvent(&quot;render&quot;, sprite);
150         return el;
151     },
152
153     getBBox: function (sprite, isWithoutTransform) {
154         var realPath = this[&quot;getPath&quot; + sprite.type](sprite);
155         if (isWithoutTransform) {
156             sprite.bbox.plain = sprite.bbox.plain || Ext.draw.Draw.pathDimensions(realPath);
157             return sprite.bbox.plain;
158         }
159         sprite.bbox.transform = sprite.bbox.transform || Ext.draw.Draw.pathDimensions(Ext.draw.Draw.mapPath(realPath, sprite.matrix));
160         return sprite.bbox.transform;
161     },
162     
163     getBBoxText: function (sprite) {
164         var bbox = {},
165             bb, height, width, i, ln, el;
166
167         if (sprite &amp;&amp; sprite.el) {
168             el = sprite.el.dom;
169             try {
170                 bbox = el.getBBox();
171                 return bbox;
172             } catch(e) {
173                 // Firefox 3.0.x plays badly here
174             }
175             bbox = {x: bbox.x, y: Infinity, width: 0, height: 0};
176             ln = el.getNumberOfChars();
177             for (i = 0; i &lt; ln; i++) {
178                 bb = el.getExtentOfChar(i);
179                 bbox.y = Math.min(bb.y, bbox.y);
180                 height = bb.y + bb.height - bbox.y;
181                 bbox.height = Math.max(bbox.height, height);
182                 width = bb.x + bb.width - bbox.x;
183                 bbox.width = Math.max(bbox.width, width);
184             }
185             return bbox;
186         }
187     },
188
189     hide: function() {
190         Ext.get(this.el).hide();
191     },
192
193     show: function() {
194         Ext.get(this.el).show();
195     },
196
197     hidePrim: function(sprite) {
198         this.addCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
199     },
200
201     showPrim: function(sprite) {
202         this.removeCls(sprite, Ext.baseCSSPrefix + 'hide-visibility');
203     },
204
205     getDefs: function() {
206         return this._defs || (this._defs = this.createSvgElement(&quot;defs&quot;));
207     },
208
209     transform: function(sprite) {
210         var me = this,
211             matrix = Ext.create('Ext.draw.Matrix'),
212             transforms = sprite.transformations,
213             transformsLength = transforms.length,
214             i = 0,
215             transform, type;
216             
217         for (; i &lt; transformsLength; i++) {
218             transform = transforms[i];
219             type = transform.type;
220             if (type == &quot;translate&quot;) {
221                 matrix.translate(transform.x, transform.y);
222             }
223             else if (type == &quot;rotate&quot;) {
224                 matrix.rotate(transform.degrees, transform.x, transform.y);
225             }
226             else if (type == &quot;scale&quot;) {
227                 matrix.scale(transform.x, transform.y, transform.centerX, transform.centerY);
228             }
229         }
230         sprite.matrix = matrix;
231         sprite.el.set({transform: matrix.toSvg()});
232     },
233
234     setSize: function(w, h) {
235         var me = this,
236             el = me.el;
237         
238         w = +w || me.width;
239         h = +h || me.height;
240         me.width = w;
241         me.height = h;
242
243         el.setSize(w, h);
244         el.set({
245             width: w,
246             height: h
247         });
248         me.callParent([w, h]);
249     },
250
251 <span id='Ext-draw-engine-Svg-method-getRegion'>    /**
252 </span>     * Get the region for the surface's canvas area
253      * @returns {Ext.util.Region}
254      */
255     getRegion: function() {
256         // Mozilla requires using the background rect because the svg element returns an
257         // incorrect region. Webkit gives no region for the rect and must use the svg element.
258         var svgXY = this.el.getXY(),
259             rectXY = this.bgRect.getXY(),
260             max = Math.max,
261             x = max(svgXY[0], rectXY[0]),
262             y = max(svgXY[1], rectXY[1]);
263         return {
264             left: x,
265             top: y,
266             right: x + this.width,
267             bottom: y + this.height
268         };
269     },
270
271     onRemove: function(sprite) {
272         if (sprite.el) {
273             sprite.el.remove();
274             delete sprite.el;
275         }
276         this.callParent(arguments);
277     },
278     
279     setViewBox: function(x, y, width, height) {
280         if (isFinite(x) &amp;&amp; isFinite(y) &amp;&amp; isFinite(width) &amp;&amp; isFinite(height)) {
281             this.callParent(arguments);
282             this.el.dom.setAttribute(&quot;viewBox&quot;, [x, y, width, height].join(&quot; &quot;));
283         }
284     },
285
286     render: function (container) {
287         var me = this;
288         if (!me.el) {
289             var width = me.width || 10,
290                 height = me.height || 10,
291                 el = me.createSvgElement('svg', {
292                     xmlns: &quot;http:/&quot; + &quot;/www.w3.org/2000/svg&quot;,
293                     version: 1.1,
294                     width: width,
295                     height: height
296                 }),
297                 defs = me.getDefs(),
298
299                 // Create a rect that is always the same size as the svg root; this serves 2 purposes:
300                 // (1) It allows mouse events to be fired over empty areas in Webkit, and (2) we can
301                 // use it rather than the svg element for retrieving the correct client rect of the
302                 // surface in Mozilla (see https://bugzilla.mozilla.org/show_bug.cgi?id=530985)
303                 bgRect = me.createSvgElement(&quot;rect&quot;, {
304                     width: &quot;100%&quot;,
305                     height: &quot;100%&quot;,
306                     fill: &quot;#000&quot;,
307                     stroke: &quot;none&quot;,
308                     opacity: 0
309                 }),
310                 webkitRect;
311             
312                 if (Ext.isSafari3) {
313                     // Rect that we will show/hide to fix old WebKit bug with rendering issues.
314                     webkitRect = me.createSvgElement(&quot;rect&quot;, {
315                         x: -10,
316                         y: -10,
317                         width: &quot;110%&quot;,
318                         height: &quot;110%&quot;,
319                         fill: &quot;none&quot;,
320                         stroke: &quot;#000&quot;
321                     });
322                 }
323             el.appendChild(defs);
324             if (Ext.isSafari3) {
325                 el.appendChild(webkitRect);
326             }
327             el.appendChild(bgRect);
328             container.appendChild(el);
329             me.el = Ext.get(el);
330             me.bgRect = Ext.get(bgRect);
331             if (Ext.isSafari3) {
332                 me.webkitRect = Ext.get(webkitRect);
333                 me.webkitRect.hide();
334             }
335             me.el.on({
336                 scope: me,
337                 mouseup: me.onMouseUp,
338                 mousedown: me.onMouseDown,
339                 mouseover: me.onMouseOver,
340                 mouseout: me.onMouseOut,
341                 mousemove: me.onMouseMove,
342                 mouseenter: me.onMouseEnter,
343                 mouseleave: me.onMouseLeave,
344                 click: me.onClick
345             });
346         }
347         me.renderAll();
348     },
349
350     // private
351     onMouseEnter: function(e) {
352         if (this.el.parent().getRegion().contains(e.getPoint())) {
353             this.fireEvent('mouseenter', e);
354         }
355     },
356
357     // private
358     onMouseLeave: function(e) {
359         if (!this.el.parent().getRegion().contains(e.getPoint())) {
360             this.fireEvent('mouseleave', e);
361         }
362     },
363     // @private - Normalize a delegated single event from the main container to each sprite and sprite group
364     processEvent: function(name, e) {
365         var target = e.getTarget(),
366             surface = this.surface,
367             sprite;
368
369         this.fireEvent(name, e);
370         // We wrap text types in a tspan, sprite is the parent.
371         if (target.nodeName == &quot;tspan&quot; &amp;&amp; target.parentNode) {
372             target = target.parentNode;
373         }
374         sprite = this.items.get(target.id);
375         if (sprite) {
376             sprite.fireEvent(name, sprite, e);
377         }
378     },
379
380     /* @private - Wrap SVG text inside a tspan to allow for line wrapping.  In addition this normallizes
381      * the baseline for text the vertical middle of the text to be the same as VML.
382      */
383     tuneText: function (sprite, attrs) {
384         var el = sprite.el.dom,
385             tspans = [],
386             height, tspan, text, i, ln, texts, factor;
387
388         if (attrs.hasOwnProperty(&quot;text&quot;)) {
389            tspans = this.setText(sprite, attrs.text);
390         }
391         // Normalize baseline via a DY shift of first tspan. Shift other rows by height * line height (1.2)
392         if (tspans.length) {
393             height = this.getBBoxText(sprite).height;
394             for (i = 0, ln = tspans.length; i &lt; ln; i++) {
395                 // The text baseline for FireFox 3.0 and 3.5 is different than other SVG implementations
396                 // so we are going to normalize that here
397                 factor = (Ext.isFF3_0 || Ext.isFF3_5) ? 2 : 4;
398                 tspans[i].setAttribute(&quot;dy&quot;, i ? height * 1.2 : height / factor);
399             }
400             sprite.dirty = true;
401         }
402     },
403
404     setText: function(sprite, textString) {
405          var me = this,
406              el = sprite.el.dom,
407              x = el.getAttribute(&quot;x&quot;),
408              tspans = [],
409              height, tspan, text, i, ln, texts;
410         
411         while (el.firstChild) {
412             el.removeChild(el.firstChild);
413         }
414         // Wrap each row into tspan to emulate rows
415         texts = String(textString).split(&quot;\n&quot;);
416         for (i = 0, ln = texts.length; i &lt; ln; i++) {
417             text = texts[i];
418             if (text) {
419                 tspan = me.createSvgElement(&quot;tspan&quot;);
420                 tspan.appendChild(document.createTextNode(Ext.htmlDecode(text)));
421                 tspan.setAttribute(&quot;x&quot;, x);
422                 el.appendChild(tspan);
423                 tspans[i] = tspan;
424             }
425         }
426         return tspans;
427     },
428
429     renderAll: function() {
430         this.items.each(this.renderItem, this);
431     },
432
433     renderItem: function (sprite) {
434         if (!this.el) {
435             return;
436         }
437         if (!sprite.el) {
438             this.createSpriteElement(sprite);
439         }
440         if (sprite.zIndexDirty) {
441             this.applyZIndex(sprite);
442         }
443         if (sprite.dirty) {
444             this.applyAttrs(sprite);
445             this.applyTransformations(sprite);
446         }
447     },
448
449     redraw: function(sprite) {
450         sprite.dirty = sprite.zIndexDirty = true;
451         this.renderItem(sprite);
452     },
453
454     applyAttrs: function (sprite) {
455         var me = this,
456             el = sprite.el,
457             group = sprite.group,
458             sattr = sprite.attr,
459             parsers = me.parsers,
460             //Safari does not handle linear gradients correctly in quirksmode
461             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
462             //ref: EXTJSIV-1472
463             gradientsMap = me.gradientsMap || {},
464             safariFix = Ext.isSafari &amp;&amp; !Ext.isStrict,
465             groups, i, ln, attrs, font, key, style, name, rect;
466
467         if (group) {
468             groups = [].concat(group);
469             ln = groups.length;
470             for (i = 0; i &lt; ln; i++) {
471                 group = groups[i];
472                 me.getGroup(group).add(sprite);
473             }
474             delete sprite.group;
475         }
476         attrs = me.scrubAttrs(sprite) || {};
477
478         // if (sprite.dirtyPath) {
479             sprite.bbox.plain = 0;
480             sprite.bbox.transform = 0;
481             if (sprite.type == &quot;circle&quot; || sprite.type == &quot;ellipse&quot;) {
482                 attrs.cx = attrs.cx || attrs.x;
483                 attrs.cy = attrs.cy || attrs.y;
484             }
485             else if (sprite.type == &quot;rect&quot;) {
486                 attrs.rx = attrs.ry = attrs.r;
487             }
488             else if (sprite.type == &quot;path&quot; &amp;&amp; attrs.d) {
489                 attrs.d = Ext.draw.Draw.pathToString(Ext.draw.Draw.pathToAbsolute(attrs.d));
490             }
491             sprite.dirtyPath = false;
492         // }
493         // else {
494         //     delete attrs.d;
495         // }
496
497         if (attrs['clip-rect']) {
498             me.setClip(sprite, attrs);
499             delete attrs['clip-rect'];
500         }
501         if (sprite.type == 'text' &amp;&amp; attrs.font &amp;&amp; sprite.dirtyFont) {
502             el.set({ style: &quot;font: &quot; + attrs.font});
503             sprite.dirtyFont = false;
504         }
505         if (sprite.type == &quot;image&quot;) {
506             el.dom.setAttributeNS(me.xlink, &quot;href&quot;, attrs.src);
507         }
508         Ext.applyIf(attrs, me.minDefaults[sprite.type]);
509
510         if (sprite.dirtyHidden) {
511             (sattr.hidden) ? me.hidePrim(sprite) : me.showPrim(sprite);
512             sprite.dirtyHidden = false;
513         }
514         for (key in attrs) {
515             if (attrs.hasOwnProperty(key) &amp;&amp; attrs[key] != null) {
516                 //Safari does not handle linear gradients correctly in quirksmode
517                 //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
518                 //ref: EXTJSIV-1472
519                 //if we're Safari in QuirksMode and we're applying some color attribute and the value of that
520                 //attribute is a reference to a gradient then assign a plain color to that value instead of the gradient.
521                 if (safariFix &amp;&amp; ('color|stroke|fill'.indexOf(key) &gt; -1) &amp;&amp; (attrs[key] in gradientsMap)) {
522                     attrs[key] = gradientsMap[attrs[key]];
523                 }
524                 if (key in parsers) {
525                     el.dom.setAttribute(key, parsers[key](attrs[key], sprite, me));
526                 } else {
527                     el.dom.setAttribute(key, attrs[key]);
528                 }
529             }
530         }
531         
532         if (sprite.type == 'text') {
533             me.tuneText(sprite, attrs);
534         }
535
536         //set styles
537         style = sattr.style;
538         if (style) {
539             el.setStyle(style);
540         }
541
542         sprite.dirty = false;
543
544         if (Ext.isSafari3) {
545             // Refreshing the view to fix bug EXTJSIV-1: rendering issue in old Safari 3
546             me.webkitRect.show();
547             setTimeout(function () {
548                 me.webkitRect.hide();
549             });
550         }
551     },
552
553     setClip: function(sprite, params) {
554         var me = this,
555             rect = params[&quot;clip-rect&quot;],
556             clipEl, clipPath;
557         if (rect) {
558             if (sprite.clip) {
559                 sprite.clip.parentNode.parentNode.removeChild(sprite.clip.parentNode);
560             }
561             clipEl = me.createSvgElement('clipPath');
562             clipPath = me.createSvgElement('rect');
563             clipEl.id = Ext.id(null, 'ext-clip-');
564             clipPath.setAttribute(&quot;x&quot;, rect.x);
565             clipPath.setAttribute(&quot;y&quot;, rect.y);
566             clipPath.setAttribute(&quot;width&quot;, rect.width);
567             clipPath.setAttribute(&quot;height&quot;, rect.height);
568             clipEl.appendChild(clipPath);
569             me.getDefs().appendChild(clipEl);
570             sprite.el.dom.setAttribute(&quot;clip-path&quot;, &quot;url(#&quot; + clipEl.id + &quot;)&quot;);
571             sprite.clip = clipPath;
572         }
573         // if (!attrs[key]) {
574         //     var clip = Ext.getDoc().dom.getElementById(sprite.el.getAttribute(&quot;clip-path&quot;).replace(/(^url\(#|\)$)/g, &quot;&quot;));
575         //     clip &amp;&amp; clip.parentNode.removeChild(clip);
576         //     sprite.el.setAttribute(&quot;clip-path&quot;, &quot;&quot;);
577         //     delete attrss.clip;
578         // }
579     },
580
581 <span id='Ext-draw-engine-Svg-method-applyZIndex'>    /**
582 </span>     * Insert or move a given sprite's element to the correct place in the DOM list for its zIndex
583      * @param {Ext.draw.Sprite} sprite
584      */
585     applyZIndex: function(sprite) {
586         var me = this,
587             items = me.items,
588             idx = items.indexOf(sprite),
589             el = sprite.el,
590             prevEl;
591         if (me.el.dom.childNodes[idx + 2] !== el.dom) { //shift by 2 to account for defs and bg rect
592             if (idx &gt; 0) {
593                 // Find the first previous sprite which has its DOM element created already
594                 do {
595                     prevEl = items.getAt(--idx).el;
596                 } while (!prevEl &amp;&amp; idx &gt; 0);
597             }
598             el.insertAfter(prevEl || me.bgRect);
599         }
600         sprite.zIndexDirty = false;
601     },
602
603     createItem: function (config) {
604         var sprite = Ext.create('Ext.draw.Sprite', config);
605         sprite.surface = this;
606         return sprite;
607     },
608
609     addGradient: function(gradient) {
610         gradient = Ext.draw.Draw.parseGradient(gradient);
611         var me = this,
612             ln = gradient.stops.length,
613             vector = gradient.vector,
614             //Safari does not handle linear gradients correctly in quirksmode
615             //ref: https://bugs.webkit.org/show_bug.cgi?id=41952
616             //ref: EXTJSIV-1472
617             usePlain = Ext.isSafari &amp;&amp; !Ext.isStrict,
618             gradientEl, stop, stopEl, i, gradientsMap;
619             
620         gradientsMap = me.gradientsMap || {};
621         
622         if (!usePlain) {
623             if (gradient.type == &quot;linear&quot;) {
624                 gradientEl = me.createSvgElement(&quot;linearGradient&quot;);
625                 gradientEl.setAttribute(&quot;x1&quot;, vector[0]);
626                 gradientEl.setAttribute(&quot;y1&quot;, vector[1]);
627                 gradientEl.setAttribute(&quot;x2&quot;, vector[2]);
628                 gradientEl.setAttribute(&quot;y2&quot;, vector[3]);
629             }
630             else {
631                 gradientEl = me.createSvgElement(&quot;radialGradient&quot;);
632                 gradientEl.setAttribute(&quot;cx&quot;, gradient.centerX);
633                 gradientEl.setAttribute(&quot;cy&quot;, gradient.centerY);
634                 gradientEl.setAttribute(&quot;r&quot;, gradient.radius);
635                 if (Ext.isNumber(gradient.focalX) &amp;&amp; Ext.isNumber(gradient.focalY)) {
636                     gradientEl.setAttribute(&quot;fx&quot;, gradient.focalX);
637                     gradientEl.setAttribute(&quot;fy&quot;, gradient.focalY);
638                 }
639             }
640             gradientEl.id = gradient.id;
641             me.getDefs().appendChild(gradientEl);
642             for (i = 0; i &lt; ln; i++) {
643                 stop = gradient.stops[i];
644                 stopEl = me.createSvgElement(&quot;stop&quot;);
645                 stopEl.setAttribute(&quot;offset&quot;, stop.offset + &quot;%&quot;);
646                 stopEl.setAttribute(&quot;stop-color&quot;, stop.color);
647                 stopEl.setAttribute(&quot;stop-opacity&quot;,stop.opacity);
648                 gradientEl.appendChild(stopEl);
649             }
650         } else {
651             gradientsMap['url(#' + gradient.id + ')'] = gradient.stops[0].color;
652         }
653         me.gradientsMap = gradientsMap;
654     },
655
656 <span id='Ext-draw-engine-Svg-method-hasCls'>    /**
657 </span>     * Checks if the specified CSS class exists on this element's DOM node.
658      * @param {String} className The CSS class to check for
659      * @return {Boolean} True if the class exists, else false
660      */
661     hasCls: function(sprite, className) {
662         return className &amp;&amp; (' ' + (sprite.el.dom.getAttribute('class') || '') + ' ').indexOf(' ' + className + ' ') != -1;
663     },
664
665     addCls: function(sprite, className) {
666         var el = sprite.el,
667             i,
668             len,
669             v,
670             cls = [],
671             curCls =  el.getAttribute('class') || '';
672         // Separate case is for speed
673         if (!Ext.isArray(className)) {
674             if (typeof className == 'string' &amp;&amp; !this.hasCls(sprite, className)) {
675                 el.set({ 'class': curCls + ' ' + className });
676             }
677         }
678         else {
679             for (i = 0, len = className.length; i &lt; len; i++) {
680                 v = className[i];
681                 if (typeof v == 'string' &amp;&amp; (' ' + curCls + ' ').indexOf(' ' + v + ' ') == -1) {
682                     cls.push(v);
683                 }
684             }
685             if (cls.length) {
686                 el.set({ 'class': ' ' + cls.join(' ') });
687             }
688         }
689     },
690
691     removeCls: function(sprite, className) {
692         var me = this,
693             el = sprite.el,
694             curCls =  el.getAttribute('class') || '',
695             i, idx, len, cls, elClasses;
696         if (!Ext.isArray(className)){
697             className = [className];
698         }
699         if (curCls) {
700             elClasses = curCls.replace(me.trimRe, ' ').split(me.spacesRe);
701             for (i = 0, len = className.length; i &lt; len; i++) {
702                 cls = className[i];
703                 if (typeof cls == 'string') {
704                     cls = cls.replace(me.trimRe, '');
705                     idx = Ext.Array.indexOf(elClasses, cls);
706                     if (idx != -1) {
707                         Ext.Array.erase(elClasses, idx, 1);
708                     }
709                 }
710             }
711             el.set({ 'class': elClasses.join(' ') });
712         }
713     },
714
715     destroy: function() {
716         var me = this;
717         
718         me.callParent();
719         if (me.el) {
720             me.el.remove();
721         }
722         delete me.el;
723     }
724 });</pre>
725 </body>
726 </html>