Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / draw / Surface.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * A Surface is an interface to render methods inside a draw {@link Ext.draw.Component}.
17  * A Surface contains methods to render sprites, get bounding boxes of sprites, add
18  * sprites to the canvas, initialize other graphic components, etc. One of the most used
19  * methods for this class is the `add` method, to add Sprites to the surface.
20  *
21  * Most of the Surface methods are abstract and they have a concrete implementation
22  * in VML or SVG engines.
23  *
24  * A Surface instance can be accessed as a property of a draw component. For example:
25  *
26  *     drawComponent.surface.add({
27  *         type: 'circle',
28  *         fill: '#ffc',
29  *         radius: 100,
30  *         x: 100,
31  *         y: 100
32  *     });
33  *
34  * The configuration object passed in the `add` method is the same as described in the {@link Ext.draw.Sprite}
35  * class documentation.
36  *
37  * # Listeners
38  *
39  * You can also add event listeners to the surface using the `Observable` listener syntax. Supported events are:
40  *
41  * - mousedown
42  * - mouseup
43  * - mouseover
44  * - mouseout
45  * - mousemove
46  * - mouseenter
47  * - mouseleave
48  * - click
49  *
50  * For example:
51  *
52  *     drawComponent.surface.on({
53  *        'mousemove': function() {
54  *             console.log('moving the mouse over the surface');   
55  *         }
56  *     });
57  *
58  * # Example
59  *
60  *     var drawComponent = Ext.create('Ext.draw.Component', {
61  *         width: 800,
62  *         height: 600,
63  *         renderTo: document.body
64  *     }), surface = drawComponent.surface;
65  *   
66  *     surface.add([{
67  *         type: 'circle',
68  *         radius: 10,
69  *         fill: '#f00',
70  *         x: 10,
71  *         y: 10,
72  *         group: 'circles'
73  *     }, {
74  *         type: 'circle',
75  *         radius: 10,
76  *         fill: '#0f0',
77  *         x: 50,
78  *         y: 50,
79  *         group: 'circles'
80  *     }, {
81  *         type: 'circle',
82  *         radius: 10,
83  *         fill: '#00f',
84  *         x: 100,
85  *         y: 100,
86  *         group: 'circles'
87  *     }, {
88  *         type: 'rect',
89  *         width: 20,
90  *         height: 20,
91  *         fill: '#f00',
92  *         x: 10,
93  *         y: 10,
94  *         group: 'rectangles'
95  *     }, {
96  *         type: 'rect',
97  *         width: 20,
98  *         height: 20,
99  *         fill: '#0f0',
100  *         x: 50,
101  *         y: 50,
102  *         group: 'rectangles'
103  *     }, {
104  *         type: 'rect',
105  *         width: 20,
106  *         height: 20,
107  *         fill: '#00f',
108  *         x: 100,
109  *         y: 100,
110  *         group: 'rectangles'
111  *     }]);
112  *   
113  *     // Get references to my groups
114  *     circles = surface.getGroup('circles');
115  *     rectangles = surface.getGroup('rectangles');
116  *
117  *     // Animate the circles down
118  *     circles.animate({
119  *         duration: 1000,
120  *         to: {
121  *             translate: {
122  *                 y: 200
123  *             }
124  *         }
125  *     });
126  *   
127  *     // Animate the rectangles across
128  *     rectangles.animate({
129  *         duration: 1000,
130  *         to: {
131  *             translate: {
132  *                 x: 200
133  *             }
134  *         }
135  *     });
136  */
137 Ext.define('Ext.draw.Surface', {
138
139     /* Begin Definitions */
140
141     mixins: {
142         observable: 'Ext.util.Observable'
143     },
144
145     requires: ['Ext.draw.CompositeSprite'],
146     uses: ['Ext.draw.engine.Svg', 'Ext.draw.engine.Vml'],
147
148     separatorRe: /[, ]+/,
149
150     statics: {
151         /**
152          * Creates and returns a new concrete Surface instance appropriate for the current environment.
153          * @param {Object} config Initial configuration for the Surface instance
154          * @param {[String]} enginePriority Optional order of implementations to use; the first one that is
155          * available in the current environment will be used. Defaults to `['Svg', 'Vml']`.
156          * @return {Object} The created Surface or false.
157          * @static
158          */
159         create: function(config, enginePriority) {
160             enginePriority = enginePriority || ['Svg', 'Vml'];
161
162             var i = 0,
163                 len = enginePriority.length,
164                 surfaceClass;
165
166             for (; i < len; i++) {
167                 if (Ext.supports[enginePriority[i]]) {
168                     return Ext.create('Ext.draw.engine.' + enginePriority[i], config);
169                 }
170             }
171             return false;
172         }
173     },
174
175     /* End Definitions */
176
177     // @private
178     availableAttrs: {
179         blur: 0,
180         "clip-rect": "0 0 1e9 1e9",
181         cursor: "default",
182         cx: 0,
183         cy: 0,
184         'dominant-baseline': 'auto',
185         fill: "none",
186         "fill-opacity": 1,
187         font: '10px "Arial"',
188         "font-family": '"Arial"',
189         "font-size": "10",
190         "font-style": "normal",
191         "font-weight": 400,
192         gradient: "",
193         height: 0,
194         hidden: false,
195         href: "http://sencha.com/",
196         opacity: 1,
197         path: "M0,0",
198         radius: 0,
199         rx: 0,
200         ry: 0,
201         scale: "1 1",
202         src: "",
203         stroke: "#000",
204         "stroke-dasharray": "",
205         "stroke-linecap": "butt",
206         "stroke-linejoin": "butt",
207         "stroke-miterlimit": 0,
208         "stroke-opacity": 1,
209         "stroke-width": 1,
210         target: "_blank",
211         text: "",
212         "text-anchor": "middle",
213         title: "Ext Draw",
214         width: 0,
215         x: 0,
216         y: 0,
217         zIndex: 0
218     },
219
220     /**
221      * @cfg {Number} height
222      * The height of this component in pixels (defaults to auto).
223      * **Note** to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
224      */
225     /**
226      * @cfg {Number} width
227      * The width of this component in pixels (defaults to auto).
228      * **Note** to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
229      */
230
231     container: undefined,
232     height: 352,
233     width: 512,
234     x: 0,
235     y: 0,
236
237     /**
238      * Creates new Surface.
239      * @param {Object} config (optional) Config object.
240      */
241     constructor: function(config) {
242         var me = this;
243         config = config || {};
244         Ext.apply(me, config);
245
246         me.domRef = Ext.getDoc().dom;
247         
248         me.customAttributes = {};
249
250         me.addEvents(
251             'mousedown',
252             'mouseup',
253             'mouseover',
254             'mouseout',
255             'mousemove',
256             'mouseenter',
257             'mouseleave',
258             'click'
259         );
260
261         me.mixins.observable.constructor.call(me);
262
263         me.getId();
264         me.initGradients();
265         me.initItems();
266         if (me.renderTo) {
267             me.render(me.renderTo);
268             delete me.renderTo;
269         }
270         me.initBackground(config.background);
271     },
272
273     // @private called to initialize components in the surface
274     // this is dependent on the underlying implementation.
275     initSurface: Ext.emptyFn,
276
277     // @private called to setup the surface to render an item
278     //this is dependent on the underlying implementation.
279     renderItem: Ext.emptyFn,
280
281     // @private
282     renderItems: Ext.emptyFn,
283
284     // @private
285     setViewBox: Ext.emptyFn,
286
287     /**
288      * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
289      *
290      * For example:
291      *
292      *     drawComponent.surface.addCls(sprite, 'x-visible');
293      *
294      * @param {Object} sprite The sprite to add the class to.
295      * @param {String/[String]} className The CSS class to add, or an array of classes
296      * @method
297      */
298     addCls: Ext.emptyFn,
299
300     /**
301      * Removes one or more CSS classes from the element.
302      *
303      * For example:
304      *
305      *     drawComponent.surface.removeCls(sprite, 'x-visible');
306      *
307      * @param {Object} sprite The sprite to remove the class from.
308      * @param {String/[String]} className The CSS class to remove, or an array of classes
309      * @method
310      */
311     removeCls: Ext.emptyFn,
312
313     /**
314      * Sets CSS style attributes to an element.
315      *
316      * For example:
317      *
318      *     drawComponent.surface.setStyle(sprite, {
319      *         'cursor': 'pointer'
320      *     });
321      *
322      * @param {Object} sprite The sprite to add, or an array of classes to
323      * @param {Object} styles An Object with CSS styles.
324      * @method
325      */
326     setStyle: Ext.emptyFn,
327
328     // @private
329     initGradients: function() {
330         var gradients = this.gradients;
331         if (gradients) {
332             Ext.each(gradients, this.addGradient, this);
333         }
334     },
335
336     // @private
337     initItems: function() {
338         var items = this.items;
339         this.items = Ext.create('Ext.draw.CompositeSprite');
340         this.groups = Ext.create('Ext.draw.CompositeSprite');
341         if (items) {
342             this.add(items);
343         }
344     },
345     
346     // @private
347     initBackground: function(config) {
348         var me = this,
349             width = me.width,
350             height = me.height,
351             gradientId, gradient, backgroundSprite;
352         if (config) {
353             if (config.gradient) {
354                 gradient = config.gradient;
355                 gradientId = gradient.id;
356                 me.addGradient(gradient);
357                 me.background = me.add({
358                     type: 'rect',
359                     x: 0,
360                     y: 0,
361                     width: width,
362                     height: height,
363                     fill: 'url(#' + gradientId + ')'
364                 });
365             } else if (config.fill) {
366                 me.background = me.add({
367                     type: 'rect',
368                     x: 0,
369                     y: 0,
370                     width: width,
371                     height: height,
372                     fill: config.fill
373                 });
374             } else if (config.image) {
375                 me.background = me.add({
376                     type: 'image',
377                     x: 0,
378                     y: 0,
379                     width: width,
380                     height: height,
381                     src: config.image
382                 });
383             }
384         }
385     },
386     
387     /**
388      * Sets the size of the surface. Accomodates the background (if any) to fit the new size too.
389      *
390      * For example:
391      *
392      *     drawComponent.surface.setSize(500, 500);
393      *
394      * This method is generally called when also setting the size of the draw Component.
395      * 
396      * @param {Number} w The new width of the canvas.
397      * @param {Number} h The new height of the canvas.
398      */
399     setSize: function(w, h) {
400         if (this.background) {
401             this.background.setAttributes({
402                 width: w,
403                 height: h,
404                 hidden: false
405             }, true);
406         }
407     },
408
409     // @private
410     scrubAttrs: function(sprite) {
411         var i,
412             attrs = {},
413             exclude = {},
414             sattr = sprite.attr;
415         for (i in sattr) {    
416             // Narrow down attributes to the main set
417             if (this.translateAttrs.hasOwnProperty(i)) {
418                 // Translated attr
419                 attrs[this.translateAttrs[i]] = sattr[i];
420                 exclude[this.translateAttrs[i]] = true;
421             }
422             else if (this.availableAttrs.hasOwnProperty(i) && !exclude[i]) {
423                 // Passtrhough attr
424                 attrs[i] = sattr[i];
425             }
426         }
427         return attrs;
428     },
429
430     // @private
431     onClick: function(e) {
432         this.processEvent('click', e);
433     },
434
435     // @private
436     onMouseUp: function(e) {
437         this.processEvent('mouseup', e);
438     },
439
440     // @private
441     onMouseDown: function(e) {
442         this.processEvent('mousedown', e);
443     },
444
445     // @private
446     onMouseOver: function(e) {
447         this.processEvent('mouseover', e);
448     },
449
450     // @private
451     onMouseOut: function(e) {
452         this.processEvent('mouseout', e);
453     },
454
455     // @private
456     onMouseMove: function(e) {
457         this.fireEvent('mousemove', e);
458     },
459
460     // @private
461     onMouseEnter: Ext.emptyFn,
462
463     // @private
464     onMouseLeave: Ext.emptyFn,
465
466     /**
467      * Adds a gradient definition to the Surface. Note that in some surface engines, adding
468      * a gradient via this method will not take effect if the surface has already been rendered.
469      * Therefore, it is preferred to pass the gradients as an item to the surface config, rather
470      * than calling this method, especially if the surface is rendered immediately (e.g. due to
471      * 'renderTo' in its config). For more information on how to create gradients in the Chart
472      * configuration object please refer to {@link Ext.chart.Chart}.
473      *
474      * The gradient object to be passed into this method is composed by:
475      * 
476      * - **id** - string - The unique name of the gradient.
477      * - **angle** - number, optional - The angle of the gradient in degrees.
478      * - **stops** - object - An object with numbers as keys (from 0 to 100) and style objects as values.
479      *
480      * For example:
481      *
482      *    drawComponent.surface.addGradient({
483      *        id: 'gradientId',
484      *        angle: 45,
485      *        stops: {
486      *            0: {
487      *                color: '#555'
488      *            },
489      *            100: {
490      *                color: '#ddd'
491      *            }
492      *        }
493      *    });
494      *
495      * @method
496      */
497     addGradient: Ext.emptyFn,
498
499     /**
500      * Adds a Sprite to the surface. See {@link Ext.draw.Sprite} for the configuration object to be
501      * passed into this method.
502      *
503      * For example:
504      *
505      *     drawComponent.surface.add({
506      *         type: 'circle',
507      *         fill: '#ffc',
508      *         radius: 100,
509      *         x: 100,
510      *         y: 100
511      *     });
512      *
513      */
514     add: function() {
515         var args = Array.prototype.slice.call(arguments),
516             sprite,
517             index;
518
519         var hasMultipleArgs = args.length > 1;
520         if (hasMultipleArgs || Ext.isArray(args[0])) {
521             var items = hasMultipleArgs ? args : args[0],
522                 results = [],
523                 i, ln, item;
524
525             for (i = 0, ln = items.length; i < ln; i++) {
526                 item = items[i];
527                 item = this.add(item);
528                 results.push(item);
529             }
530
531             return results;
532         }
533         sprite = this.prepareItems(args[0], true)[0];
534         this.normalizeSpriteCollection(sprite);
535         this.onAdd(sprite);
536         return sprite;
537     },
538
539     /**
540      * @private
541      * Inserts or moves a given sprite into the correct position in the items
542      * MixedCollection, according to its zIndex. Will be inserted at the end of
543      * an existing series of sprites with the same or lower zIndex. If the sprite
544      * is already positioned within an appropriate zIndex group, it will not be moved.
545      * This ordering can be used by subclasses to assist in rendering the sprites in
546      * the correct order for proper z-index stacking.
547      * @param {Ext.draw.Sprite} sprite
548      * @return {Number} the sprite's new index in the list
549      */
550     normalizeSpriteCollection: function(sprite) {
551         var items = this.items,
552             zIndex = sprite.attr.zIndex,
553             idx = items.indexOf(sprite);
554
555         if (idx < 0 || (idx > 0 && items.getAt(idx - 1).attr.zIndex > zIndex) ||
556                 (idx < items.length - 1 && items.getAt(idx + 1).attr.zIndex < zIndex)) {
557             items.removeAt(idx);
558             idx = items.findIndexBy(function(otherSprite) {
559                 return otherSprite.attr.zIndex > zIndex;
560             });
561             if (idx < 0) {
562                 idx = items.length;
563             }
564             items.insert(idx, sprite);
565         }
566         return idx;
567     },
568
569     onAdd: function(sprite) {
570         var group = sprite.group,
571             draggable = sprite.draggable,
572             groups, ln, i;
573         if (group) {
574             groups = [].concat(group);
575             ln = groups.length;
576             for (i = 0; i < ln; i++) {
577                 group = groups[i];
578                 this.getGroup(group).add(sprite);
579             }
580             delete sprite.group;
581         }
582         if (draggable) {
583             sprite.initDraggable();
584         }
585     },
586
587     /**
588      * Removes a given sprite from the surface, optionally destroying the sprite in the process.
589      * You can also call the sprite own `remove` method.
590      *
591      * For example:
592      *
593      *     drawComponent.surface.remove(sprite);
594      *     //or...
595      *     sprite.remove();
596      *
597      * @param {Ext.draw.Sprite} sprite
598      * @param {Boolean} destroySprite
599      * @return {Number} the sprite's new index in the list
600      */
601     remove: function(sprite, destroySprite) {
602         if (sprite) {
603             this.items.remove(sprite);
604             this.groups.each(function(item) {
605                 item.remove(sprite);
606             });
607             sprite.onRemove();
608             if (destroySprite === true) {
609                 sprite.destroy();
610             }
611         }
612     },
613
614     /**
615      * Removes all sprites from the surface, optionally destroying the sprites in the process.
616      *
617      * For example:
618      *
619      *     drawComponent.surface.removeAll();
620      *
621      * @param {Boolean} destroySprites Whether to destroy all sprites when removing them.
622      * @return {Number} The sprite's new index in the list.
623      */
624     removeAll: function(destroySprites) {
625         var items = this.items.items,
626             ln = items.length,
627             i;
628         for (i = ln - 1; i > -1; i--) {
629             this.remove(items[i], destroySprites);
630         }
631     },
632
633     onRemove: Ext.emptyFn,
634
635     onDestroy: Ext.emptyFn,
636
637     // @private
638     applyTransformations: function(sprite) {
639             sprite.bbox.transform = 0;
640             this.transform(sprite);
641
642         var me = this,
643             dirty = false,
644             attr = sprite.attr;
645
646         if (attr.translation.x != null || attr.translation.y != null) {
647             me.translate(sprite);
648             dirty = true;
649         }
650         if (attr.scaling.x != null || attr.scaling.y != null) {
651             me.scale(sprite);
652             dirty = true;
653         }
654         if (attr.rotation.degrees != null) {
655             me.rotate(sprite);
656             dirty = true;
657         }
658         if (dirty) {
659             sprite.bbox.transform = 0;
660             this.transform(sprite);
661             sprite.transformations = [];
662         }
663     },
664
665     // @private
666     rotate: function (sprite) {
667         var bbox,
668             deg = sprite.attr.rotation.degrees,
669             centerX = sprite.attr.rotation.x,
670             centerY = sprite.attr.rotation.y;
671         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
672             bbox = this.getBBox(sprite);
673             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
674             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
675         }
676         sprite.transformations.push({
677             type: "rotate",
678             degrees: deg,
679             x: centerX,
680             y: centerY
681         });
682     },
683
684     // @private
685     translate: function(sprite) {
686         var x = sprite.attr.translation.x || 0,
687             y = sprite.attr.translation.y || 0;
688         sprite.transformations.push({
689             type: "translate",
690             x: x,
691             y: y
692         });
693     },
694
695     // @private
696     scale: function(sprite) {
697         var bbox,
698             x = sprite.attr.scaling.x || 1,
699             y = sprite.attr.scaling.y || 1,
700             centerX = sprite.attr.scaling.centerX,
701             centerY = sprite.attr.scaling.centerY;
702
703         if (!Ext.isNumber(centerX) || !Ext.isNumber(centerY)) {
704             bbox = this.getBBox(sprite);
705             centerX = !Ext.isNumber(centerX) ? bbox.x + bbox.width / 2 : centerX;
706             centerY = !Ext.isNumber(centerY) ? bbox.y + bbox.height / 2 : centerY;
707         }
708         sprite.transformations.push({
709             type: "scale",
710             x: x,
711             y: y,
712             centerX: centerX,
713             centerY: centerY
714         });
715     },
716
717     // @private
718     rectPath: function (x, y, w, h, r) {
719         if (r) {
720             return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
721         }
722         return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
723     },
724
725     // @private
726     ellipsePath: function (x, y, rx, ry) {
727         if (ry == null) {
728             ry = rx;
729         }
730         return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
731     },
732
733     // @private
734     getPathpath: function (el) {
735         return el.attr.path;
736     },
737
738     // @private
739     getPathcircle: function (el) {
740         var a = el.attr;
741         return this.ellipsePath(a.x, a.y, a.radius, a.radius);
742     },
743
744     // @private
745     getPathellipse: function (el) {
746         var a = el.attr;
747         return this.ellipsePath(a.x, a.y,
748                                 a.radiusX || (a.width / 2) || 0,
749                                 a.radiusY || (a.height / 2) || 0);
750     },
751
752     // @private
753     getPathrect: function (el) {
754         var a = el.attr;
755         return this.rectPath(a.x, a.y, a.width, a.height, a.r);
756     },
757
758     // @private
759     getPathimage: function (el) {
760         var a = el.attr;
761         return this.rectPath(a.x || 0, a.y || 0, a.width, a.height);
762     },
763
764     // @private
765     getPathtext: function (el) {
766         var bbox = this.getBBoxText(el);
767         return this.rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
768     },
769
770     createGroup: function(id) {
771         var group = this.groups.get(id);
772         if (!group) {
773             group = Ext.create('Ext.draw.CompositeSprite', {
774                 surface: this
775             });
776             group.id = id || Ext.id(null, 'ext-surface-group-');
777             this.groups.add(group);
778         }
779         return group;
780     },
781
782     /**
783      * Returns a new group or an existent group associated with the current surface.
784      * The group returned is a {@link Ext.draw.CompositeSprite} group.
785      *
786      * For example:
787      *
788      *     var spriteGroup = drawComponent.surface.getGroup('someGroupId');
789      *
790      * @param {String} id The unique identifier of the group.
791      * @return {Object} The {@link Ext.draw.CompositeSprite}.
792      */
793     getGroup: function(id) {
794         if (typeof id == "string") {
795             var group = this.groups.get(id);
796             if (!group) {
797                 group = this.createGroup(id);
798             }
799         } else {
800             group = id;
801         }
802         return group;
803     },
804
805     // @private
806     prepareItems: function(items, applyDefaults) {
807         items = [].concat(items);
808         // Make sure defaults are applied and item is initialized
809         var item, i, ln;
810         for (i = 0, ln = items.length; i < ln; i++) {
811             item = items[i];
812             if (!(item instanceof Ext.draw.Sprite)) {
813                 // Temporary, just take in configs...
814                 item.surface = this;
815                 items[i] = this.createItem(item);
816             } else {
817                 item.surface = this;
818             }
819         }
820         return items;
821     },
822     
823     /**
824      * Changes the text in the sprite element. The sprite must be a `text` sprite.
825      * This method can also be called from {@link Ext.draw.Sprite}.
826      *
827      * For example:
828      *
829      *     var spriteGroup = drawComponent.surface.setText(sprite, 'my new text');
830      *
831      * @param {Object} sprite The Sprite to change the text.
832      * @param {String} text The new text to be set.
833      * @method
834      */
835     setText: Ext.emptyFn,
836     
837     //@private Creates an item and appends it to the surface. Called
838     //as an internal method when calling `add`.
839     createItem: Ext.emptyFn,
840
841     /**
842      * Retrieves the id of this component.
843      * Will autogenerate an id if one has not already been set.
844      */
845     getId: function() {
846         return this.id || (this.id = Ext.id(null, 'ext-surface-'));
847     },
848
849     /**
850      * Destroys the surface. This is done by removing all components from it and
851      * also removing its reference to a DOM element.
852      *
853      * For example:
854      *
855      *      drawComponent.surface.destroy();
856      */
857     destroy: function() {
858         delete this.domRef;
859         this.removeAll();
860     }
861 });