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