2 Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * @description <p>Creates a Image Cropper control.</p>
9 * @namespace YAHOO.widget
10 * @requires yahoo, dom, dragdrop, element, event, resize
11 * @module imagecropper
15 var Dom
= YAHOO
.util
.Dom
,
16 Event
= YAHOO
.util
.Event
,
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.
27 var Crop = function(el
, config
) {
28 YAHOO
.log('Initializing', 'log', 'ImageCropper');
31 attributes
: config
|| {}
34 Crop
.superclass
.constructor.call(this, oConfig
.element
, oConfig
.attributes
);
40 * @property _instances
41 * @description Internal hash table for all ImageCropper instances
47 * @method getCropperById
48 * @description Get's an ImageCropper object by the HTML id of the image associated with the ImageCropper object.
49 * @return {Object} The ImageCropper Object
51 Crop
.getCropperById = function(id
) {
52 if (Crop
._instances
[id
]) {
53 return Crop
._instances
[id
];
58 YAHOO
.extend(Crop
, YAHOO
.util
.Element
, {
62 * @description The CSS class used to wrap the element
69 * @description The CSS class for the mask element
72 CSS_MASK
: 'yui-crop-mask',
75 * @property CSS_RESIZE_MASK
76 * @description The CSS class for the mask inside the resize element
79 CSS_RESIZE_MASK
: 'yui-crop-resize-mask',
84 * @description The url of the image we are cropping
91 * @description Flag to determine if the crop region is active
98 * @description A reference to the Resize Utility used in this Cropper Instance
104 * @property _resizeEl
105 * @description The HTML Element used to create the Resize Oject
111 * @property _resizeMaskEl
112 * @description The HTML Element used to create the Resize mask
119 * @description The HTML Element created to wrap the image
126 * @description The HTML Element created to "mask" the image being cropped
132 * @method _createWrap
133 * @description Creates the wrapper element used to wrap the image
135 _createWrap: function() {
136 YAHOO
.log('Creating the wrap element', 'log', 'ImageCropper');
137 this._wrap
= document
.createElement('div');
138 this._wrap
.id
= this.get('element').id
+ '_wrap';
139 this._wrap
.className
= this.CSS_MAIN
;
140 var el
= this.get('element');
141 this._wrap
.style
.width
= el
.width
? el
.width
+ 'px' : Dom
.getStyle(el
, 'width');
142 this._wrap
.style
.height
= el
.height
? el
.height
+ 'px' : Dom
.getStyle(el
, 'height');
144 var par
= this.get('element').parentNode
;
145 par
.replaceChild(this._wrap
, this.get('element'));
146 this._wrap
.appendChild(this.get('element'));
148 Event
.on(this._wrap
, 'mouseover', this._handleMouseOver
, this, true);
149 Event
.on(this._wrap
, 'mouseout', this._handleMouseOut
, this, true);
151 Event
.on(this._wrap
, 'click', function(ev
) { Event
.stopEvent(ev
); }, this, true);
156 * @method _createMask
157 * @description Creates the mask element used to mask the image
159 _createMask: function() {
160 YAHOO
.log('Creating the Mask', 'log', 'ImageCropper');
161 this._mask
= document
.createElement('div');
162 this._mask
.className
= this.CSS_MASK
;
163 this._wrap
.appendChild(this._mask
);
168 * @method _createResize
169 * @description Creates the resize element and the instance of the Resize Utility
171 _createResize: function() {
172 YAHOO
.log('Creating the Resize element', 'log', 'ImageCropper');
173 this._resizeEl
= document
.createElement('div');
174 this._resizeEl
.className
= YAHOO
.util
.Resize
.prototype.CSS_RESIZE
;
175 this._resizeEl
.style
.position
= 'absolute';
177 this._resizeEl
.innerHTML
= '<div class="' + this.CSS_RESIZE_MASK
+ '"></div>';
178 this._resizeMaskEl
= this._resizeEl
.firstChild
;
179 this._wrap
.appendChild(this._resizeEl
);
180 this._resizeEl
.style
.top
= this.get('initialXY')[1] + 'px';
181 this._resizeEl
.style
.left
= this.get('initialXY')[0] + 'px';
182 this._resizeMaskEl
.style
.height
= Math
.floor(this.get('initHeight')) + 'px';
183 this._resizeMaskEl
.style
.width
= Math
.floor(this.get('initWidth')) + 'px';
185 this._resize
= new YAHOO
.util
.Resize(this._resizeEl
, {
189 status
: this.get('status'),
190 minWidth
: this.get('minWidth'),
191 minHeight
: this.get('minHeight'),
192 ratio
: this.get('ratio'),
193 autoRatio
: this.get('autoRatio'),
194 height
: this.get('initHeight'),
195 width
: this.get('initWidth')
198 this._setBackgroundImage(this.get('element').getAttribute('src', 2));
199 this._setBackgroundPosition(-(this.get('initialXY')[0]), -(this.get('initialXY')[1]));
201 this._resize
.on('startResize', this._handleStartResizeEvent
, this, true);
202 this._resize
.on('endResize', this._handleEndResizeEvent
, this, true);
203 this._resize
.on('dragEvent', this._handleDragEvent
, this, true);
204 this._resize
.on('beforeResize', this._handleBeforeResizeEvent
, this, true);
205 this._resize
.on('resize', this._handleResizeEvent
, this, true);
206 this._resize
.dd
.on('b4StartDragEvent', this._handleB4DragEvent
, this, true);
211 * @method _handleMouseOver
212 * @description Handles the mouseover event
214 _handleMouseOver: function(ev
) {
215 var evType
= 'keydown';
216 if (YAHOO
.env
.ua
.gecko
|| YAHOO
.env
.ua
.opera
) {
221 if (this.get('useKeys')) {
222 Event
.on(document
, evType
, this._handleKeyPress
, this, true);
228 * @method _handleMouseOut
229 * @description Handles the mouseout event
231 _handleMouseOut: function(ev
) {
232 var evType
= 'keydown';
233 if (YAHOO
.env
.ua
.gecko
|| YAHOO
.env
.ua
.opera
) {
236 this._active
= false;
237 if (this.get('useKeys')) {
238 Event
.removeListener(document
, evType
, this._handleKeyPress
);
245 * @description Moves the resize element based on the arrow keys
247 _moveEl: function(dir
, inc
) {
248 YAHOO
.log('Moving the element', 'log', 'ImageCropper');
250 region
= this._setConstraints(),
256 if ((region
.bottom
- inc
) < 0) {
258 this._resizeEl
.style
.top
= (region
.top
+ region
.bottom
) + 'px';
263 if ((region
.top
- inc
) < 0) {
265 this._resizeEl
.style
.top
= '0px';
270 if ((region
.right
- inc
) < 0) {
272 this._resizeEl
.style
.left
= (region
.left
+ region
.right
) + 'px';
277 if ((region
.left
- inc
) < 0) {
279 this._resizeEl
.style
.left
= '0px';
285 YAHOO
.log('Moving via Key Listener: ' + dir
, 'log', 'ImageCropper');
286 this._resizeEl
.style
.left
= (parseInt(this._resizeEl
.style
.left
, 10) - l
) + 'px';
287 this._resizeEl
.style
.top
= (parseInt(this._resizeEl
.style
.top
, 10) - t
) + 'px';
288 this.fireEvent('moveEvent', { target
: 'keypress' });
290 this._setConstraints();
292 this._syncBackgroundPosition();
297 * @method _handleKeyPress
298 * @description Handles the keypress event
300 _handleKeyPress: function(ev
) {
301 var kc
= Event
.getCharCode(ev
),
303 inc
= ((ev
.shiftKey
) ? this.get('shiftKeyTick') : this.get('keyTick'));
307 this._moveEl('left', inc
);
311 this._moveEl('up', inc
);
315 this._moveEl('right', inc
);
319 this._moveEl('down', inc
);
325 Event
.preventDefault(ev
);
331 * @method _handleB4DragEvent
332 * @description Handles the DragDrop b4DragEvent event
334 _handleB4DragEvent: function() {
335 this._setConstraints();
340 * @method _handleDragEvent
341 * @description Handles the DragDrop DragEvent event
343 _handleDragEvent: function() {
344 this._syncBackgroundPosition();
345 this.fireEvent('dragEvent', arguments
);
346 this.fireEvent('moveEvent', { target
: 'dragevent' });
351 * @method _handleBeforeResizeEvent
352 * @description Handles the Resize Utilitys beforeResize event
354 _handleBeforeResizeEvent: function(args
) {
355 var region
= Dom
.getRegion(this.get('element')),
356 c
= this._resize
._cache
,
357 ch
= this._resize
._currentHandle
, h
= 0, w
= 0;
359 if (args
.top
&& (args
.top
< region
.top
)) {
360 h
= (c
.height
+ c
.top
) - region
.top
;
361 Dom
.setY(this._resize
.getWrapEl(), region
.top
);
362 this._resize
.getWrapEl().style
.height
= h
+ 'px';
363 this._resize
._cache
.height
= h
;
364 this._resize
._cache
.top
= region
.top
;
365 this._syncBackgroundPosition();
368 if (args
.left
&& (args
.left
< region
.left
)) {
369 w
= (c
.width
+ c
.left
) - region
.left
;
370 Dom
.setX(this._resize
.getWrapEl(), region
.left
);
371 this._resize
._cache
.left
= region
.left
;
372 this._resize
.getWrapEl().style
.width
= w
+ 'px';
373 this._resize
._cache
.width
= w
;
374 this._syncBackgroundPosition();
377 if (ch
!= 'tl' && ch
!= 'l' && ch
!= 'bl') {
378 if (c
.left
&& args
.width
&& ((c
.left
+ args
.width
) > region
.right
)) {
379 w
= (region
.right
- c
.left
);
380 Dom
.setX(this._resize
.getWrapEl(), (region
.right
- w
));
381 this._resize
.getWrapEl().style
.width
= w
+ 'px';
382 this._resize
._cache
.left
= (region
.right
- w
);
383 this._resize
._cache
.width
= w
;
384 this._syncBackgroundPosition();
388 if (ch
!= 't' && ch
!= 'tr' && ch
!= 'tl') {
389 if (c
.top
&& args
.height
&& ((c
.top
+ args
.height
) > region
.bottom
)) {
390 h
= (region
.bottom
- c
.top
);
391 Dom
.setY(this._resize
.getWrapEl(), (region
.bottom
- h
));
392 this._resize
.getWrapEl().style
.height
= h
+ 'px';
393 this._resize
._cache
.height
= h
;
394 this._resize
._cache
.top
= (region
.bottom
- h
);
395 this._syncBackgroundPosition();
402 * @method _handleResizeMaskEl
403 * @description Resizes the inner mask element
405 _handleResizeMaskEl: function() {
406 var a
= this._resize
._cache
;
407 this._resizeMaskEl
.style
.height
= Math
.floor(a
.height
) + 'px';
408 this._resizeMaskEl
.style
.width
= Math
.floor(a
.width
) + 'px';
412 * @method _handleResizeEvent
413 * @param Event ev The Resize Utilitys resize event.
414 * @description Handles the Resize Utilitys Resize event
416 _handleResizeEvent: function(ev
) {
417 this._setConstraints(true);
418 this._syncBackgroundPosition();
419 this.fireEvent('resizeEvent', arguments
);
420 this.fireEvent('moveEvent', { target
: 'resizeevent' });
425 * @method _syncBackgroundPosition
426 * @description Syncs the packground position of the resize element with the resize elements top and left style position
428 _syncBackgroundPosition: function() {
429 this._handleResizeMaskEl();
430 this._setBackgroundPosition(-(parseInt(this._resizeEl
.style
.left
, 10)), -(parseInt(this._resizeEl
.style
.top
, 10)));
435 * @method _setBackgroundPosition
436 * @param Number l The left position
437 * @param Number t The top position
438 * @description Sets the background image position to the top and left position
440 _setBackgroundPosition: function(l
, t
) {
441 //YAHOO.log('Setting the image background position of the mask to: (' + l + ', ' + t + ')', 'log', 'ImageCropper');
442 var bl
= parseInt(Dom
.getStyle(this._resize
.get('element'), 'borderLeftWidth'), 10);
443 var bt
= parseInt(Dom
.getStyle(this._resize
.get('element'), 'borderTopWidth'), 10);
444 var mask
= this._resize
.getWrapEl().firstChild
;
445 var pos
= (l
- bl
) + 'px ' + (t
- bt
) + 'px';
446 this._resizeMaskEl
.style
.backgroundPosition
= pos
;
451 * @method _setBackgroundImage
452 * @param String url The url of the image
453 * @description Sets the background image of the resize element
455 _setBackgroundImage: function(url
) {
456 YAHOO
.log('Setting the background image', 'log', 'ImageCropper');
457 var mask
= this._resize
.getWrapEl().firstChild
;
459 mask
.style
.backgroundImage
= 'url(' + url
+ '#)';
464 * @method _handleEndResizeEvent
465 * @description Handles the Resize Utilitys endResize event
467 _handleEndResizeEvent: function() {
468 this._setConstraints(true);
472 * @method _handleStartResizeEvent
473 * @description Handles the Resize Utilitys startResize event
475 _handleStartResizeEvent: function() {
476 this._setConstraints(true);
478 var h
= this._resize
._cache
.height
,
479 w
= this._resize
._cache
.width
,
480 t
= parseInt(this._resize
.getWrapEl().style
.top
, 10),
481 l
= parseInt(this._resize
.getWrapEl().style
.left
, 10),
484 switch (this._resize
._currentHandle
) {
486 maxH
= (h
+ this._resize
.dd
.bottomConstraint
);
489 maxW
= (w
+ this._resize
.dd
.leftConstraint
);
493 maxW
= (w
+ this._resize
.dd
.rightConstraint
);
496 maxH
= (h
+ this._resize
.dd
.bottomConstraint
);
497 maxW
= (w
+ this._resize
.dd
.rightConstraint
);
501 maxW
= (w
+ this._resize
.dd
.rightConstraint
);
507 YAHOO
.log('Setting the maxHeight on the resize object to: ' + maxH
, 'log', 'ImageCropper');
508 //this._resize.set('maxHeight', maxH);
511 YAHOO
.log('Setting the maxWidth on the resize object to: ' + maxW
, 'log', 'ImageCropper');
512 //this._resize.set('maxWidth', maxW);
515 this.fireEvent('startResizeEvent', arguments
);
520 * @method _setConstraints
521 * @param Boolean inside Used when called from inside a resize event, false by default (dragging)
522 * @description Set the DragDrop constraints to keep the element inside the crop area.
523 * @return {Object} Object containing Top, Right, Bottom and Left constraints
525 _setConstraints: function(inside
) {
526 YAHOO
.log('Setting Contraints', 'log', 'ImageCropper');
527 var resize
= this._resize
;
528 resize
.dd
.resetConstraints();
529 var height
= parseInt(resize
.get('height'), 10),
530 width
= parseInt(resize
.get('width'), 10);
532 //Called from inside the resize callback
533 height
= resize
._cache
.height
;
534 width
= resize
._cache
.width
;
537 //Get the top, right, bottom and left positions
538 var region
= Dom
.getRegion(this.get('element'));
539 //Get the element we are working on
540 var el
= resize
.getWrapEl();
542 //Get the xy position of it
543 var xy
= Dom
.getXY(el
);
545 //Set left to x minus left
546 var left
= xy
[0] - region
.left
;
548 //Set right to right minus x minus width
549 var right
= region
.right
- xy
[0] - width
;
551 //Set top to y minus top
552 var top
= xy
[1] - region
.top
;
554 //Set bottom to bottom minus y minus height
555 var bottom
= region
.bottom
- xy
[1] - height
;
561 resize
.dd
.setXConstraint(left
, right
);
562 resize
.dd
.setYConstraint(top
, bottom
);
563 YAHOO
.log('Constraints: ' + top
+ ',' + right
+ ',' + bottom
+ ',' + left
, 'log', 'ImageCropper');
576 * @method getCropCoords
577 * @description Returns the coordinates needed to crop the image
578 * @return {Object} The top, left, height, width and image url of the image being cropped
580 getCropCoords: function() {
582 top
: parseInt(this._resize
.getWrapEl().style
.top
, 10),
583 left
: parseInt(this._resize
.getWrapEl().style
.left
, 10),
584 height
: this._resize
._cache
.height
,
585 width
: this._resize
._cache
.width
,
588 YAHOO
.log('Getting the crop coordinates: ' + Lang
.dump(coords
), 'log', 'ImageCropper');
593 * @description Resets the crop element back to it's original position
594 * @return {<a href="YAHOO.widget.ImageCropper.html">YAHOO.widget.ImageCropper</a>} The ImageCropper instance
597 YAHOO
.log('Resetting the control', 'log', 'ImageCropper');
598 this._resize
.resize(null, this.get('initHeight'), this.get('initWidth'), 0, 0, true);
599 this._resizeEl
.style
.top
= this.get('initialXY')[1] + 'px';
600 this._resizeEl
.style
.left
= this.get('initialXY')[0] + 'px';
601 this._syncBackgroundPosition();
607 * @description Get the HTML reference for the image element.
608 * @return {HTMLElement} The image element
611 return this.get('element');
614 * @method getResizeEl
615 * @description Get the HTML reference for the resize element.
616 * @return {HTMLElement} The resize element
618 getResizeEl: function() {
619 return this._resizeEl
;
623 * @description Get the HTML reference for the wrap element.
624 * @return {HTMLElement} The wrap element
626 getWrapEl: function() {
632 * @description Get the HTML reference for the mask element.
633 * @return {HTMLElement} The mask element
635 getMaskEl: function() {
640 * @method getResizeMaskEl
641 * @description Get the HTML reference for the resizable object's mask element.
642 * @return {HTMLElement} The resize objects mask element.
644 getResizeMaskEl: function() {
645 return this._resizeMaskEl
;
649 * @method getResizeObject
650 * @description Get the Resize Utility object.
651 * @return {<a href="YAHOO.util.Resize.html">YAHOO.util.Resize</a>} The Resize instance
653 getResizeObject: function() {
660 * @description The ImageCropper class's initialization method
662 init: function(p_oElement
, p_oAttributes
) {
663 YAHOO
.log('init', 'info', 'ImageCropper');
664 Crop
.superclass
.init
.call(this, p_oElement
, p_oAttributes
);
668 if (!Lang
.isString(id
)) {
669 if (id
.tagName
&& (id
.tagName
.toLowerCase() == 'img')) {
670 id
= Dom
.generateId(id
);
672 YAHOO
.log('Element is not an image.', 'error', 'ImageCropper');
676 var el
= Dom
.get(id
);
677 if (el
.tagName
&& el
.tagName
.toLowerCase() == 'img') {
680 YAHOO
.log('Element is not an image.', 'error', 'ImageCropper');
687 Crop
._instances
[id
] = this;
690 this._createResize();
691 this._setConstraints();
696 * @method initAttributes
697 * @description Initializes all of the configuration attributes used to create a croppable element.
698 * @param {Object} attr Object literal specifying a set of
699 * configuration attributes used to create the widget.
702 initAttributes: function(attr
) {
703 Crop
.superclass
.initAttributes
.call(this, attr
);
706 * @attribute initialXY
707 * @description Array of the XY position that we need to set the crop element to when we build it. Defaults to [10, 10]
710 this.setAttributeConfig('initialXY', {
712 validator
: YAHOO
.lang
.isArray
,
713 value
: attr
.initialXY
|| [10, 10]
717 * @description The pixel tick for the arrow keys, defaults to 1
720 this.setAttributeConfig('keyTick', {
721 validator
: YAHOO
.lang
.isNumber
,
722 value
: attr
.keyTick
|| 1
726 * @attribute shiftKeyTick
727 * @description The pixel tick for shift + the arrow keys, defaults to 10
730 this.setAttributeConfig('shiftKeyTick', {
731 validator
: YAHOO
.lang
.isNumber
,
732 value
: attr
.shiftKeyTick
|| 10
737 * @description Should we use the Arrow keys to position the crop element, defaults to true
740 this.setAttributeConfig('useKeys', {
741 validator
: YAHOO
.lang
.isBoolean
,
742 value
: ((attr
.useKeys
=== false) ? false : true)
747 * @description Show the Resize Utility status, defaults to true
750 this.setAttributeConfig('status', {
751 validator
: YAHOO
.lang
.isBoolean
,
752 value
: ((attr
.status
=== false) ? false : true),
753 method: function(status
) {
755 this._resize
.set('status', status
);
761 * @attribute minHeight
762 * @description MinHeight of the crop area, default 50
765 this.setAttributeConfig('minHeight', {
766 validator
: YAHOO
.lang
.isNumber
,
767 value
: attr
.minHeight
|| 50,
768 method: function(h
) {
770 this._resize
.set('minHeight', h
);
776 * @attribute minWidth
777 * @description MinWidth of the crop area, default 50.
780 this.setAttributeConfig('minWidth', {
781 validator
: YAHOO
.lang
.isNumber
,
782 value
: attr
.minWidth
|| 50,
783 method: function(w
) {
785 this._resize
.set('minWidth', w
);
792 * @description Set the ratio config option of the Resize Utlility, default false
795 this.setAttributeConfig('ratio', {
796 validator
: YAHOO
.lang
.isBoolean
,
797 value
: attr
.ratio
|| false,
798 method: function(r
) {
800 this._resize
.set('ratio', r
);
807 * @description Set the autoRatio config option of the Resize Utlility, default true
810 this.setAttributeConfig('autoRatio', {
811 validator
: YAHOO
.lang
.isBoolean
,
812 value
: ((attr
.autoRatio
=== false) ? false : true),
813 method: function(a
) {
815 this._resize
.set('autoRatio', a
);
821 * @attribute initHeight
822 * @description Set the initlal height of the crop area, defaults to 1/4 of the image height
825 this.setAttributeConfig('initHeight', {
827 validator
: YAHOO
.lang
.isNumber
,
828 value
: attr
.initHeight
|| (this.get('element').height
/ 4)
832 * @attribute initWidth
833 * @description Set the initlal width of the crop area, defaults to 1/4 of the image width
836 this.setAttributeConfig('initWidth', {
837 validator
: YAHOO
.lang
.isNumber
,
839 value
: attr
.initWidth
|| (this.get('element').width
/ 4)
845 * @description Destroys the ImageCropper object and all of it's elements & listeners.
847 destroy: function() {
848 YAHOO
.log('Destroying the ImageCropper', 'info', 'ImageCropper');
849 this._resize
.destroy();
850 this._resizeEl
.parentNode
.removeChild(this._resizeEl
);
851 this._mask
.parentNode
.removeChild(this._mask
);
852 Event
.purgeElement(this._wrap
);
853 this._wrap
.parentNode
.replaceChild(this.get('element'), this._wrap
);
855 //Brutal Object Destroy
856 for (var i
in this) {
857 if (Lang
.hasOwnProperty(this, i
)) {
865 * @description Returns a string representing the ImageCropper Object.
868 toString: function() {
870 return 'ImageCropper (#' + this.get('id') + ')';
872 return 'Image Cropper';
876 YAHOO
.widget
.ImageCropper
= Crop
;
880 * @description Fires when the DragDrop dragEvent
881 * @type YAHOO.util.CustomEvent
884 * @event startResizeEvent
885 * @description Fires when when a resize action is started.
886 * @type YAHOO.util.CustomEvent
890 * @description Fires on every element resize.
891 * @type YAHOO.util.CustomEvent
895 * @description Fires on every element move. Inside these methods: _handleKeyPress, _handleDragEvent, _handleResizeEvent
896 * @type YAHOO.util.CustomEvent
901 YAHOO
.register("imagecropper", YAHOO
.widget
.ImageCropper
, {version
: "2.5.2", build
: "1076"});