Merge remote-tracking branch 'origin/new/bug_8130'
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / lib / yui / imagecropper / imagecropper.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
6 */
7 /**
8  * @description <p>Creates a Image Cropper control.</p>
9  * @namespace YAHOO.widget
10  * @requires yahoo, dom, dragdrop, element, event, resize
11  * @module imagecropper
12  * @beta
13  */
14 (function() {
15 var Dom = YAHOO.util.Dom,
16     Event = YAHOO.util.Event,
17     Lang = YAHOO.lang;
18
19     /**
20      * @constructor
21      * @class ImageCropper
22      * @description <p>Creates a Image Cropper control.</p>
23      * @extends YAHOO.util.Element
24      * @param {String/HTMLElement} el The image element to make croppable.
25      * @param {Object} attrs Object liternal containing configuration parameters.
26     */
27     var Crop = function(el, config) {
28         var oConfig = {
29             element: el,
30             attributes: config || {}
31         };
32
33         Crop.superclass.constructor.call(this, oConfig.element, oConfig.attributes);    
34     };
35
36     /**
37     * @private
38     * @static
39     * @property _instances
40     * @description Internal hash table for all ImageCropper instances
41     * @type Object
42     */ 
43     Crop._instances = {};
44     /**
45     * @static
46     * @method getCropperById 
47     * @description Get's an ImageCropper object by the HTML id of the image associated with the ImageCropper object.
48     * @return {Object} The ImageCropper Object
49     */ 
50     Crop.getCropperById = function(id) {
51         if (Crop._instances[id]) {
52             return Crop._instances[id];
53         }
54         return false;
55     };
56
57     YAHOO.extend(Crop, YAHOO.util.Element, {
58         /**
59         * @private
60         * @property CSS_MAIN
61         * @description The CSS class used to wrap the element 
62         * @type String
63         */
64         CSS_MAIN: 'yui-crop',
65         /**
66         * @private
67         * @property CSS_MASK
68         * @description The CSS class for the mask element
69         * @type String
70         */
71         CSS_MASK: 'yui-crop-mask',
72         /**
73         * @private
74         * @property CSS_RESIZE_MASK
75         * @description The CSS class for the mask inside the resize element
76         * @type String
77         */
78         CSS_RESIZE_MASK: 'yui-crop-resize-mask',
79
80         /**
81         * @private
82         * @property _image
83         * @description The url of the image we are cropping
84         * @type String
85         */
86         _image: null,
87         /**
88         * @private
89         * @property _active
90         * @description Flag to determine if the crop region is active
91         * @type Boolean
92         */
93         _active: null,
94         /**
95         * @private
96         * @property _resize
97         * @description A reference to the Resize Utility used in this Cropper Instance
98         * @type Object
99         */
100         _resize: null,
101         /**
102         * @private
103         * @property _resizeEl
104         * @description The HTML Element used to create the Resize Oject
105         * @type HTMLElement
106         */
107         _resizeEl: null,
108         /**
109         * @private
110         * @property _resizeMaskEl
111         * @description The HTML Element used to create the Resize mask
112         * @type HTMLElement
113         */
114         _resizeMaskEl: null,
115         /**
116         * @private
117         * @property _wrap
118         * @description The HTML Element created to wrap the image
119         * @type HTMLElement
120         */
121         _wrap: null,
122         /**
123         * @private
124         * @property _mask
125         * @description The HTML Element created to "mask" the image being cropped
126         * @type HTMLElement
127         */
128         _mask: null,
129         /**
130         * @private
131         * @method _createWrap
132         * @description Creates the wrapper element used to wrap the image
133         */
134         _createWrap: function() {
135             this._wrap = document.createElement('div');
136             this._wrap.id = this.get('element').id + '_wrap';
137             this._wrap.className = this.CSS_MAIN;
138             var el = this.get('element');
139             this._wrap.style.width = el.width ? el.width + 'px' : Dom.getStyle(el, 'width');
140             this._wrap.style.height = el.height ? el.height + 'px' : Dom.getStyle(el, 'height');
141             
142             var par = this.get('element').parentNode;
143             par.replaceChild(this._wrap, this.get('element'));
144             this._wrap.appendChild(this.get('element'));
145
146             Event.on(this._wrap, 'mouseover', this._handleMouseOver, this, true);
147             Event.on(this._wrap, 'mouseout', this._handleMouseOut, this, true);
148
149             Event.on(this._wrap, 'click', function(ev) { Event.stopEvent(ev); }, this, true);
150         },
151
152         /**
153         * @private
154         * @method _createMask
155         * @description Creates the mask element used to mask the image
156         */
157         _createMask: function() {
158             this._mask = document.createElement('div');
159             this._mask.className = this.CSS_MASK;
160             this._wrap.appendChild(this._mask);
161         },
162
163         /**
164         * @private
165         * @method _createResize
166         * @description Creates the resize element and the instance of the Resize Utility
167         */
168         _createResize: function() {
169             this._resizeEl = document.createElement('div');
170             this._resizeEl.className = YAHOO.util.Resize.prototype.CSS_RESIZE;
171             this._resizeEl.style.position = 'absolute';
172             
173             this._resizeEl.innerHTML = '<div class="' + this.CSS_RESIZE_MASK + '"></div>';
174             this._resizeMaskEl = this._resizeEl.firstChild;
175             this._wrap.appendChild(this._resizeEl);
176             this._resizeEl.style.top = this.get('initialXY')[1] + 'px';
177             this._resizeEl.style.left = this.get('initialXY')[0] + 'px';
178             this._resizeMaskEl.style.height = Math.floor(this.get('initHeight')) + 'px';
179             this._resizeMaskEl.style.width = Math.floor(this.get('initWidth')) + 'px';
180
181             this._resize = new YAHOO.util.Resize(this._resizeEl, {
182                 knobHandles: true,
183                 handles: 'all',
184                 draggable: true,
185                 status: this.get('status'),
186                 minWidth: this.get('minWidth'),
187                 minHeight: this.get('minHeight'),
188                 ratio: this.get('ratio'),
189                 autoRatio: this.get('autoRatio'),
190                 height: this.get('initHeight'),
191                 width: this.get('initWidth')
192             });
193
194             this._setBackgroundImage(this.get('element').getAttribute('src', 2));
195             this._setBackgroundPosition(-(this.get('initialXY')[0]),  -(this.get('initialXY')[1]));
196
197             this._resize.on('startResize', this._handleStartResizeEvent, this, true);
198             this._resize.on('endResize', this._handleEndResizeEvent, this, true);
199             this._resize.on('dragEvent', this._handleDragEvent, this, true);
200             this._resize.on('beforeResize', this._handleBeforeResizeEvent, this, true);
201             this._resize.on('resize', this._handleResizeEvent, this, true);
202             this._resize.dd.on('b4StartDragEvent', this._handleB4DragEvent, this, true);
203         },
204
205         /**
206         * @private
207         * @method _handleMouseOver
208         * @description Handles the mouseover event
209         */
210         _handleMouseOver: function(ev) {
211             var evType = 'keydown';
212             if (YAHOO.env.ua.gecko || YAHOO.env.ua.opera) {
213                 evType = 'keypress';
214             }
215             if (!this._active) {
216                 this._active = true;
217                 if (this.get('useKeys')) {
218                     Event.on(document, evType, this._handleKeyPress, this, true);
219                 }
220             }
221         },
222         /**
223         * @private
224         * @method _handleMouseOut
225         * @description Handles the mouseout event
226         */
227         _handleMouseOut: function(ev) {
228             var evType = 'keydown';
229             if (YAHOO.env.ua.gecko || YAHOO.env.ua.opera) {
230                 evType = 'keypress';
231             }
232             this._active = false;
233             if (this.get('useKeys')) {
234                 Event.removeListener(document, evType, this._handleKeyPress);
235             }
236         },
237
238         /**
239         * @private
240         * @method _moveEl
241         * @description Moves the resize element based on the arrow keys
242         */
243         _moveEl: function(dir, inc) {
244             var t = 0, l = 0,
245                 region = this._setConstraints(),
246                 resize = true;
247
248             switch (dir) {
249                 case 'down':
250                     t = -(inc);
251                     if ((region.bottom - inc) < 0) {
252                         resize = false;
253                         this._resizeEl.style.top = (region.top + region.bottom) + 'px';
254                     }
255                     break;
256                 case 'up':
257                     t = (inc);
258                     if ((region.top - inc) < 0) {
259                         resize = false;
260                         this._resizeEl.style.top = '0px';
261                     }
262                     break;
263                 case 'right':
264                     l = -(inc);
265                     if ((region.right - inc) < 0) {
266                         resize = false;
267                         this._resizeEl.style.left = (region.left + region.right) + 'px';
268                     }
269                     break;
270                 case 'left':
271                     l = inc;
272                     if ((region.left - inc) < 0) {
273                         resize = false;
274                         this._resizeEl.style.left = '0px';
275                     }
276                     break;
277             }
278
279             if (resize) {
280                 this._resizeEl.style.left = (parseInt(this._resizeEl.style.left, 10) - l) + 'px';
281                 this._resizeEl.style.top = (parseInt(this._resizeEl.style.top, 10) - t) + 'px';
282                 this.fireEvent('moveEvent', { target: 'keypress' });
283             } else {
284                 this._setConstraints();
285             }
286             this._syncBackgroundPosition();
287         },
288
289         /**
290         * @private
291         * @method _handleKeyPress
292         * @description Handles the keypress event
293         */
294         _handleKeyPress: function(ev) {
295             var kc = Event.getCharCode(ev),
296                 stopEvent = false,
297                 inc = ((ev.shiftKey) ? this.get('shiftKeyTick') : this.get('keyTick'));
298
299             switch (kc) {
300                 case 0x25: // left
301                     this._moveEl('left', inc);
302                     stopEvent = true;
303                     break;
304                 case 0x26: // up
305                     this._moveEl('up', inc);
306                     stopEvent = true;
307                     break;
308                 case 0x27: // right
309                     this._moveEl('right', inc);
310                     stopEvent = true;
311                     break;
312                 case 0x28: // down
313                     this._moveEl('down', inc);
314                     stopEvent = true;
315                     break;
316                 default:
317             }
318             if (stopEvent) {
319                 Event.preventDefault(ev);
320             }
321         },
322
323         /**
324         * @private
325         * @method _handleB4DragEvent
326         * @description Handles the DragDrop b4DragEvent event
327         */
328         _handleB4DragEvent: function() {
329             this._setConstraints();
330         },
331
332         /**
333         * @private
334         * @method _handleDragEvent
335         * @description Handles the DragDrop DragEvent event
336         */
337         _handleDragEvent: function() {
338             this._syncBackgroundPosition();
339             this.fireEvent('dragEvent', arguments);
340             this.fireEvent('moveEvent', { target: 'dragevent' });
341         },
342
343         /**
344         * @private
345         * @method _handleBeforeResizeEvent
346         * @description Handles the Resize Utilitys beforeResize event
347         */
348         _handleBeforeResizeEvent: function(args) {
349             var region = Dom.getRegion(this.get('element')),
350                 c = this._resize._cache,
351                 ch = this._resize._currentHandle, h = 0, w = 0;
352
353             if (args.top && (args.top < region.top)) {
354                 h = (c.height + c.top) - region.top;
355                 Dom.setY(this._resize.getWrapEl(), region.top);
356                 this._resize.getWrapEl().style.height = h + 'px';
357                 this._resize._cache.height = h;
358                 this._resize._cache.top = region.top;
359                 this._syncBackgroundPosition();
360                 return false;
361             }
362             if (args.left && (args.left < region.left)) {
363                 w = (c.width + c.left) - region.left;
364                 Dom.setX(this._resize.getWrapEl(), region.left);
365                 this._resize._cache.left = region.left;
366                 this._resize.getWrapEl().style.width = w + 'px';
367                 this._resize._cache.width = w;
368                 this._syncBackgroundPosition();
369                 return false;
370             }
371             if (ch != 'tl' && ch != 'l' && ch != 'bl') {
372                 if (c.left && args.width && ((c.left + args.width) > region.right)) {
373                     w = (region.right - c.left);
374                     Dom.setX(this._resize.getWrapEl(), (region.right - w));
375                     this._resize.getWrapEl().style.width = w + 'px';
376                     this._resize._cache.left = (region.right - w);
377                     this._resize._cache.width = w;
378                     this._syncBackgroundPosition();
379                     return false;
380                 }
381             }
382             if (ch != 't' && ch != 'tr' && ch != 'tl') {
383                 if (c.top && args.height && ((c.top + args.height) > region.bottom)) {
384                     h = (region.bottom - c.top);
385                     Dom.setY(this._resize.getWrapEl(), (region.bottom - h));
386                     this._resize.getWrapEl().style.height = h + 'px';
387                     this._resize._cache.height = h;
388                     this._resize._cache.top = (region.bottom - h);
389                     this._syncBackgroundPosition();
390                     return false;
391                 }
392             }
393         },
394         /**
395         * @private
396         * @method _handleResizeMaskEl
397         * @description Resizes the inner mask element
398         */
399         _handleResizeMaskEl: function() {
400             var a = this._resize._cache;
401             this._resizeMaskEl.style.height = Math.floor(a.height) + 'px';
402             this._resizeMaskEl.style.width = Math.floor(a.width) + 'px';
403         },
404         /**
405         * @private
406         * @method _handleResizeEvent
407         * @param Event ev The Resize Utilitys resize event.
408         * @description Handles the Resize Utilitys Resize event
409         */
410         _handleResizeEvent: function(ev) {
411             this._setConstraints(true);
412             this._syncBackgroundPosition();
413             this.fireEvent('resizeEvent', arguments);
414             this.fireEvent('moveEvent', { target: 'resizeevent' });
415         },
416
417         /**
418         * @private
419         * @method _syncBackgroundPosition
420         * @description Syncs the packground position of the resize element with the resize elements top and left style position
421         */
422         _syncBackgroundPosition: function() {
423             this._handleResizeMaskEl();
424             this._setBackgroundPosition(-(parseInt(this._resizeEl.style.left, 10)), -(parseInt(this._resizeEl.style.top, 10)));
425         },
426
427         /**
428         * @private
429         * @method _setBackgroundPosition
430         * @param Number l The left position
431         * @param Number t The top position
432         * @description Sets the background image position to the top and left position
433         */
434         _setBackgroundPosition: function(l, t) {
435             var bl = parseInt(Dom.getStyle(this._resize.get('element'), 'borderLeftWidth'), 10);
436             var bt = parseInt(Dom.getStyle(this._resize.get('element'), 'borderTopWidth'), 10);
437             if (isNaN(bl)) {
438                 bl = 0;
439             }
440             if (isNaN(bt)) {
441                 bt = 0;
442             }
443             var mask = this._resize.getWrapEl().firstChild;
444             var pos = (l - bl) + 'px ' + (t - bt) + 'px';
445             this._resizeMaskEl.style.backgroundPosition = pos;
446         },
447
448         /**
449         * @private
450         * @method _setBackgroundImage
451         * @param String url The url of the image
452         * @description Sets the background image of the resize element
453         */
454         _setBackgroundImage: function(url) {
455             var mask = this._resize.getWrapEl().firstChild;
456             this._image = url;
457             mask.style.backgroundImage = 'url(' + url + '#)';
458         },
459         
460         /**
461         * @private
462         * @method _handleEndResizeEvent
463         * @description Handles the Resize Utilitys endResize event
464         */
465         _handleEndResizeEvent: function() {
466             this._setConstraints(true);
467         },
468         /**
469         * @private
470         * @method _handleStartResizeEvent
471         * @description Handles the Resize Utilitys startResize event
472         */
473         _handleStartResizeEvent: function() {
474             this._setConstraints(true);
475
476             var h = this._resize._cache.height,
477                  w = this._resize._cache.width,
478                  t = parseInt(this._resize.getWrapEl().style.top, 10),
479                  l = parseInt(this._resize.getWrapEl().style.left, 10),
480                  maxH = 0, maxW = 0;
481  
482             switch (this._resize._currentHandle) {
483                 case 'b':
484                     maxH = (h + this._resize.dd.bottomConstraint);
485                     break;
486                 case 'l':
487                     maxW = (w + this._resize.dd.leftConstraint);
488                     break;
489                 case 'r':
490                     maxH = (h + t);
491                     maxW = (w + this._resize.dd.rightConstraint);
492                     break;
493                  case 'br':
494                      maxH = (h + this._resize.dd.bottomConstraint);
495                      maxW = (w + this._resize.dd.rightConstraint);
496                      break;
497                  case 'tr':
498                      maxH = (h + t);
499                      maxW = (w + this._resize.dd.rightConstraint);
500                      break;
501
502              }
503             
504              if (maxH) {
505                  //this._resize.set('maxHeight', maxH);
506              }
507              if (maxW) {
508                  //this._resize.set('maxWidth', maxW);
509              }
510
511             this.fireEvent('startResizeEvent', arguments);
512         },
513         
514         /**
515         * @private
516         * @method _setConstraints
517         * @param Boolean inside Used when called from inside a resize event, false by default (dragging)
518         * @description Set the DragDrop constraints to keep the element inside the crop area.
519         * @return {Object} Object containing Top, Right, Bottom and Left constraints
520         */
521         _setConstraints: function(inside) {
522             var resize = this._resize;
523             resize.dd.resetConstraints();
524             var height = parseInt(resize.get('height'), 10),
525                 width = parseInt(resize.get('width'), 10);
526             if (inside) {
527                 //Called from inside the resize callback
528                 height = resize._cache.height;
529                 width = resize._cache.width;
530             }
531
532             //Get the top, right, bottom and left positions
533             var region = Dom.getRegion(this.get('element'));
534             //Get the element we are working on
535             var el = resize.getWrapEl();
536
537             //Get the xy position of it
538             var xy = Dom.getXY(el);
539
540             //Set left to x minus left
541             var left = xy[0] - region.left;
542
543             //Set right to right minus x minus width
544             var right = region.right - xy[0] - width;
545
546             //Set top to y minus top
547             var top = xy[1] - region.top;
548
549             //Set bottom to bottom minus y minus height
550             var bottom = region.bottom - xy[1] - height;
551
552             if (top < 0) {
553                 top = 0;
554             }
555             
556             resize.dd.setXConstraint(left, right); 
557             resize.dd.setYConstraint(top, bottom);
558
559             return {
560                 top: top,
561                 right: right,
562                 bottom: bottom,
563                 left: left
564             };
565
566             
567             
568         },
569         /**
570         * @method getCropCoords
571         * @description Returns the coordinates needed to crop the image
572         * @return {Object} The top, left, height, width and image url of the image being cropped
573         */
574         getCropCoords: function() {
575             var coords = {
576                 top: parseInt(this._resize.getWrapEl().style.top, 10),
577                 left: parseInt(this._resize.getWrapEl().style.left, 10),
578                 height: this._resize._cache.height,
579                 width: this._resize._cache.width,
580                 image: this._image
581             };
582             return coords;
583         },
584         /**
585         * @method reset
586         * @description Resets the crop element back to it's original position
587         * @return {<a href="YAHOO.widget.ImageCropper.html">YAHOO.widget.ImageCropper</a>} The ImageCropper instance
588         */
589         reset: function() {
590             this._resize.resize(null, this.get('initHeight'), this.get('initWidth'), 0, 0, true);
591             this._resizeEl.style.top = this.get('initialXY')[1] + 'px';
592             this._resizeEl.style.left = this.get('initialXY')[0] + 'px';
593             this._syncBackgroundPosition();
594             return this;
595         },
596
597         /**
598         * @method getEl
599         * @description Get the HTML reference for the image element.
600         * @return {HTMLElement} The image element
601         */      
602         getEl: function() {
603             return this.get('element');
604         },
605         /**
606         * @method getResizeEl
607         * @description Get the HTML reference for the resize element.
608         * @return {HTMLElement} The resize element
609         */      
610         getResizeEl: function() {
611             return this._resizeEl;
612         },
613         /**
614         * @method getWrapEl
615         * @description Get the HTML reference for the wrap element.
616         * @return {HTMLElement} The wrap element
617         */      
618         getWrapEl: function() {
619             return this._wrap;
620         },
621
622         /**
623         * @method getMaskEl
624         * @description Get the HTML reference for the mask element.
625         * @return {HTMLElement} The mask element
626         */      
627         getMaskEl: function() {
628             return this._mask;
629         },
630
631         /**
632         * @method getResizeMaskEl
633         * @description Get the HTML reference for the resizable object's mask element.
634         * @return {HTMLElement} The resize objects mask element.
635         */      
636         getResizeMaskEl: function() {
637             return this._resizeMaskEl;
638         },
639
640         /**
641         * @method getResizeObject
642         * @description Get the Resize Utility object.
643         * @return {<a href="YAHOO.util.Resize.html">YAHOO.util.Resize</a>} The Resize instance
644         */      
645         getResizeObject: function() {
646             return this._resize;
647         },
648
649         /** 
650         * @private
651         * @method init
652         * @description The ImageCropper class's initialization method
653         */        
654         init: function(p_oElement, p_oAttributes) {
655             Crop.superclass.init.call(this, p_oElement, p_oAttributes);
656
657             var id = p_oElement;
658
659             if (!Lang.isString(id)) {
660                 if (id.tagName && (id.tagName.toLowerCase() == 'img')) {
661                     id = Dom.generateId(id);                    
662                 } else {
663                     return false;
664                 }
665             } else {
666                 var el = Dom.get(id);
667                 if (el.tagName && el.tagName.toLowerCase() == 'img') {
668                     //All good
669                 } else {
670                     return false;
671                 }
672             }
673             
674
675
676             Crop._instances[id] = this;
677             this._createWrap();
678             this._createMask();
679             this._createResize();
680             this._setConstraints();
681
682         },
683         /**
684         * @private
685         * @method initAttributes
686         * @description Initializes all of the configuration attributes used to create a croppable element.
687         * @param {Object} attr Object literal specifying a set of 
688         * configuration attributes used to create the widget.
689         */      
690
691         initAttributes: function(attr) {
692             Crop.superclass.initAttributes.call(this, attr);
693
694             /**
695             * @attribute initialXY
696             * @description Array of the XY position that we need to set the crop element to when we build it. Defaults to [10, 10]
697             * @type Array
698             */
699             this.setAttributeConfig('initialXY', {
700                 writeOnce: true,
701                 validator: YAHOO.lang.isArray,
702                 value: attr.initialXY || [10, 10]
703             });
704             /**
705             * @attribute keyTick
706             * @description The pixel tick for the arrow keys, defaults to 1
707             * @type Number
708             */
709             this.setAttributeConfig('keyTick', {
710                 validator: YAHOO.lang.isNumber,
711                 value: attr.keyTick || 1
712             });
713
714             /**
715             * @attribute shiftKeyTick
716             * @description The pixel tick for shift + the arrow keys, defaults to 10
717             * @type Number
718             */
719             this.setAttributeConfig('shiftKeyTick', {
720                 validator: YAHOO.lang.isNumber,
721                 value: attr.shiftKeyTick || 10
722             });
723
724             /**
725             * @attribute useKeys
726             * @description Should we use the Arrow keys to position the crop element, defaults to true
727             * @type Boolean
728             */
729             this.setAttributeConfig('useKeys', {
730                 validator: YAHOO.lang.isBoolean,
731                 value: ((attr.useKeys === false) ? false : true)
732             });
733
734             /**
735             * @attribute status
736             * @description Show the Resize Utility status, defaults to true
737             * @type Boolean
738             */
739             this.setAttributeConfig('status', {
740                 validator: YAHOO.lang.isBoolean,
741                 value: ((attr.status === false) ? false : true),
742                 method: function(status) {
743                     if (this._resize) {
744                         this._resize.set('status', status);
745                     }
746                 }
747             });
748
749             /**
750             * @attribute minHeight
751             * @description MinHeight of the crop area, default 50
752             * @type Number
753             */
754             this.setAttributeConfig('minHeight', {
755                 validator: YAHOO.lang.isNumber,
756                 value: attr.minHeight || 50,
757                 method: function(h) {
758                     if (this._resize) {
759                         this._resize.set('minHeight', h);
760                     }
761                 }
762             });
763
764             /**
765             * @attribute minWidth
766             * @description MinWidth of the crop area, default 50.
767             * @type Number
768             */
769             this.setAttributeConfig('minWidth', {
770                 validator: YAHOO.lang.isNumber,
771                 value: attr.minWidth || 50,
772                 method: function(w) {
773                     if (this._resize) {
774                         this._resize.set('minWidth', w);
775                     }
776                 }
777             });
778
779             /**
780             * @attribute ratio
781             * @description Set the ratio config option of the Resize Utlility, default false
782             * @type Boolean
783             */
784             this.setAttributeConfig('ratio', {
785                 validator: YAHOO.lang.isBoolean,
786                 value: attr.ratio || false,
787                 method: function(r) {
788                     if (this._resize) {
789                         this._resize.set('ratio', r);
790                     }
791                 }
792             });
793
794             /**
795             * @attribute ratio
796             * @description Set the autoRatio config option of the Resize Utlility, default true
797             * @type Boolean
798             */
799             this.setAttributeConfig('autoRatio', {
800                 validator: YAHOO.lang.isBoolean,
801                 value: ((attr.autoRatio === false) ? false : true),
802                 method: function(a) {
803                     if (this._resize) {
804                         this._resize.set('autoRatio', a);
805                     }
806                 }
807             });
808
809             /**
810             * @attribute initHeight
811             * @description Set the initlal height of the crop area, defaults to 1/4 of the image height
812             * @type Number
813             */
814             this.setAttributeConfig('initHeight', {
815                 writeOnce: true,
816                 validator: YAHOO.lang.isNumber,
817                 value: attr.initHeight || (this.get('element').height / 4)
818             });
819
820             /**
821             * @attribute initWidth
822             * @description Set the initlal width of the crop area, defaults to 1/4 of the image width
823             * @type Number
824             */
825             this.setAttributeConfig('initWidth', {
826                 validator: YAHOO.lang.isNumber,
827                 writeOnce: true,
828                 value: attr.initWidth || (this.get('element').width / 4)
829             });
830
831         },
832         /**
833         * @method destroy
834         * @description Destroys the ImageCropper object and all of it's elements & listeners.
835         */        
836         destroy: function() {
837             this._resize.destroy();
838             this._resizeEl.parentNode.removeChild(this._resizeEl);
839             this._mask.parentNode.removeChild(this._mask);
840             Event.purgeElement(this._wrap);
841             this._wrap.parentNode.replaceChild(this.get('element'), this._wrap);
842             
843             //Brutal Object Destroy
844             for (var i in this) {
845                 if (Lang.hasOwnProperty(this, i)) {
846                     this[i] = null;
847                 }
848             }
849                        
850         },
851         /**
852         * @method toString
853         * @description Returns a string representing the ImageCropper Object.
854         * @return {String}
855         */        
856         toString: function() {
857             if (this.get) {
858                 return 'ImageCropper (#' + this.get('id') + ')';
859             }
860             return 'Image Cropper';
861         }
862     });
863
864     YAHOO.widget.ImageCropper = Crop;
865
866 /**
867 * @event dragEvent
868 * @description Fires when the DragDrop dragEvent
869 * @type YAHOO.util.CustomEvent
870 */
871 /**
872 * @event startResizeEvent
873 * @description Fires when when a resize action is started.
874 * @type YAHOO.util.CustomEvent
875 */
876 /**
877 * @event resizeEvent
878 * @description Fires on every element resize.
879 * @type YAHOO.util.CustomEvent
880 */
881 /**
882 * @event moveEvent
883 * @description Fires on every element move. Inside these methods: _handleKeyPress, _handleDragEvent, _handleResizeEvent
884 * @type YAHOO.util.CustomEvent
885 */
886
887 })();
888
889 YAHOO.register("imagecropper", YAHOO.widget.ImageCropper, {version: "2.8.0r4", build: "2449"});