Show:
                            /**
                             * The Image Cropper Component
                             *
                             * @module aui-image-cropper
                             */
                            
                            var Lang = A.Lang,
                                isBoolean = Lang.isBoolean,
                                isNumber = Lang.isNumber,
                            
                                toFloat = Lang.toFloat,
                            
                                CSS_CROP = A.getClassName('image-cropper', 'crop'),
                                CSS_CROP_OUTLINE = A.getClassName('image-cropper', 'crop', 'outline'),
                                CSS_OVERLAY = A.getClassName('image-cropper', 'overlay'),
                                CSS_OVERLAY_HOVER = A.getClassName('image-cropper', 'crop', 'hover');
                            
                            /**
                             * A base class for Image Cropper.
                             *
                             * Check the [live demo](http://alloyui.com/examples/image-cropper/).
                             *
                             * @class A.ImageCropper
                             * @extends A.Component
                             * @param {Object} config Object literal specifying widget configuration
                             *     properties.
                             * @constructor
                             * @example
                            ```
                            <img id="myImage" src="https://alloyui.com/image-cropper/img/crop-image.jpg" />
                            ```
                             * @example
                            ```
                            YUI().use(
                              'aui-image-cropper',
                              function(Y) {
                                new Y.ImageCropper(
                                  {
                                    cropHeight: 200,
                                    cropWidth: 200,
                                    srcNode: '#myImage',
                                    x: 50,
                                    y: 50
                                  }
                                ).render();
                              }
                            );
                            ```
                             */
                            var ImageCropper = A.Component.create({
                                /**
                                 * Static property provides a string to identify the class.
                                 *
                                 * @property NAME
                                 * @type String
                                 * @static
                                 */
                                NAME: 'image-cropper',
                            
                                /**
                                 * Static property used to define the default attribute
                                 * configuration for the Image Cropper.
                                 *
                                 * @property ATTRS
                                 * @type Object
                                 * @static
                                 */
                                ATTRS: {
                            
                                    /**
                                     * The height of a selected area to crop.
                                     *
                                     * @attribute cropHeight
                                     * @default 100
                                     * @type Number
                                     */
                                    cropHeight: {
                                        validator: isNumber,
                                        value: 100
                                    },
                            
                                    /**
                                     * The width of a selected area to crop.
                                     *
                                     * @attribute cropWidth
                                     * @default 100
                                     * @type Number
                                     */
                                    cropWidth: {
                                        validator: isNumber,
                                        value: 100
                                    },
                            
                                    /**
                                     * The minimum width of a selected area to crop.
                                     *
                                     * @attribute minWidth
                                     * @default undefined
                                     * @type Number
                                     */
                                    minWidth: {
                                        value: undefined
                                    },
                            
                                    /**
                                     * The minimum height of a selected area to crop.
                                     *
                                     * @attribute minHeight
                                     * @default undefined
                                     * @type Number
                                     */
                                    minHeight: {
                                        value: undefined
                                    },
                            
                                    /**
                                     * Determine if the crop area should move or not.
                                     *
                                     * @attribute movable
                                     * @default true
                                     * @type Boolean
                                     */
                                    movable: {
                                        validator: isBoolean,
                                        value: true
                                    },
                            
                                    /**
                                     * Determine if the crop area should preserve the
                                     * aspect ratio or not.
                                     *
                                     * @attribute preserveRatio
                                     * @default false
                                     * @type Boolean
                                     */
                                    preserveRatio: {
                                        validator: isBoolean,
                                        value: false
                                    },
                            
                                    /**
                                     * Determine the region of a selected area to crop.
                                     *
                                     * @attribute region
                                     * @default {}
                                     * @type Object
                                     */
                                    region: {
                                        getter: '_getCropRegion',
                                        value: {}
                                    },
                            
                                    /**
                                     * Determine if the crop area should resize or not.
                                     *
                                     * @attribute resizable
                                     * @default true
                                     * @type Boolean
                                     */
                                    resizable: {
                                        validator: isBoolean,
                                        value: true
                                    },
                            
                                    /**
                                     * The X position of a selected area to crop.
                                     *
                                     * @attribute x
                                     * @default 0
                                     * @type Number
                                     */
                                    x: {
                                        setter: Math.round,
                                        validator: isNumber,
                                        value: 0
                                    },
                            
                                    /**
                                     * The Y position of a selected area to crop.
                                     *
                                     * @attribute y
                                     * @default 0
                                     * @type Number
                                     */
                                    y: {
                                        value: 0,
                                        setter: Math.round,
                                        validator: isNumber
                                    }
                                },
                            
                                /**
                                 * Static property used to define the UI attributes.
                                 *
                                 * @property UI_ATTRS
                                 * @type Array
                                 * @static
                                 */
                                UI_ATTRS: [
                                        'cropHeight',
                                        'cropWidth',
                                        'minWidth',
                                        'minHeight',
                                        'movable',
                                        'resizable',
                                        'x',
                                        'y'
                                    ],
                            
                                prototype: {
                            
                                    /**
                                     * Render the Image Cropper component instance. Lifecycle.
                                     *
                                     * @method renderUI
                                     * @protected
                                     */
                                    renderUI: function() {
                                        var instance = this;
                            
                                        var boundingBox = instance.get('boundingBox');
                            
                                        instance.cropNode = A.Node.create('<div class="' + CSS_CROP + '"></div>');
                                        instance.cropNode.append(A.Node.create('<div class="' + CSS_CROP_OUTLINE + '"></div>'));
                            
                                        instance.overlay = A.Node.create('<div class="' + CSS_OVERLAY + '"></div>');
                            
                                        A.all([instance.cropNode, instance.overlay]).appendTo(boundingBox);
                            
                                        instance._boundingBox = boundingBox;
                            
                                        instance._renderDrag();
                                        instance._renderResize();
                                    },
                            
                                    /**
                                     * Bind the events on the Image Cropper UI. Lifecycle.
                                     *
                                     * @method bindUI
                                     * @protected
                                     */
                                    bindUI: function() {
                                        var instance = this;
                            
                                        instance._fireCropEventTask = A.debounce(instance._fireCropEvent, 10, instance);
                            
                                        instance.publish(
                                            'crop', {
                                                defaultFn: instance._defCropFn
                                            }
                                        );
                            
                                        instance.on(['drag:start', 'resize:start'], A.debounce(instance._syncRegion, 25));
                            
                                        instance.on(['drag:drag', 'drag:end', 'resize:end', 'resize:resize'], A.debounce(instance._constrainValues, 10));
                            
                                        instance.after(['drag:drag', 'resize:resize'], instance._fireCropEvent, instance);
                            
                                        instance.after(
                                                ['xChange', 'yChange', 'cropWidthChange', 'cropHeightChange'],
                                            function(event) {
                                                instance._fireCropEventTask(event);
                            
                                                instance._syncCropNodeUI();
                                            }
                                        );
                            
                                        instance._createHover();
                                    },
                            
                                    /**
                                     * Sync the Image Cropper UI. Lifecycle.
                                     *
                                     * @method syncUI
                                     * @protected
                                     */
                                    syncUI: function() {
                                        var instance = this;
                            
                                        instance._uiSetPreserveRatio(instance.get('preserveRatio'));
                            
                                        instance.syncImageUI();
                                        instance._syncCropNodeUI();
                                    },
                            
                                    /**
                                     * Destructor lifecycle implementation for the `ImageCropper` class.
                                     *
                                     * @method syncUI
                                     * @protected
                                     */
                                    destructor: function() {
                                        var instance = this;
                            
                                        instance._destroyDrag();
                                        instance._destroyResize();
                                    },
                            
                                    /**
                                     * Sync the image on the UI.
                                     *
                                     * @method syncImageUI
                                     */
                                    syncImageUI: function() {
                                        var instance = this;
                            
                                        var imageNode = instance.get('srcNode');
                            
                                        instance.cropNode.setStyle('backgroundImage', 'url(' + imageNode.attr('src') + ')');
                            
                                        instance.cropNode.setStyle('backgroundSize', imageNode.width() + 'px ' + imageNode.height() + 'px');
                            
                                        instance._constrainValues();
                                        instance._syncXY();
                            
                                        var origRegion = instance._getConstraintRegion();
                            
                                        var drag = instance.drag;
                                        var resize = instance.resize;
                            
                                        if (drag) {
                                            drag.con.set('constrain', origRegion);
                                        }
                            
                                        if (resize) {
                                            resize.con.set('constrain', origRegion);
                                        }
                                    },
                            
                                    /**
                                     * Constrain to valid values.
                                     *
                                     * @method _constrainValues
                                     * @protected
                                     */
                                    _constrainValues: function() {
                                        var instance = this;
                            
                                        var imageNode = instance.get('srcNode');
                            
                                        var cropHeight = instance.get('cropHeight');
                                        var cropWidth = instance.get('cropWidth');
                            
                                        var xBorder = instance.cropNode.getBorderWidth('lr');
                                        var yBorder = instance.cropNode.getBorderWidth('tb');
                            
                                        var x = instance.get('x');
                                        var y = instance.get('y');
                            
                                        var imageWidth = imageNode.width();
                                        var imageHeight = imageNode.height();
                            
                                        if (imageHeight > 0) {
                                            // Find valid y
                            
                                            y = Math.max(y, 0);
                            
                                            if (y + (cropHeight - yBorder) > imageHeight) {
                                                y = Math.max(imageHeight - cropHeight, 0);
                                            }
                            
                                            // Find valid cropHeight
                            
                                            if (y + (cropHeight - yBorder) > imageHeight) {
                                                cropHeight = Math.max(imageHeight - y, 0);
                                            }
                                        }
                            
                                        instance.set('y', y);
                            
                                        instance.set('cropHeight', cropHeight);
                            
                                        if (imageWidth > 0) {
                                            // Find valid x
                            
                                            x = Math.max(x, 0);
                            
                                            if (x + (cropWidth - xBorder) > imageWidth) {
                                                if (imageWidth) {
                                                    x = Math.max(imageWidth - cropWidth, 0);
                                                }
                                            }
                            
                                            // Find valid cropWidth
                            
                                            if (x + (cropWidth - xBorder) > imageWidth) {
                                                if (imageWidth) {
                                                    cropWidth = Math.max(imageWidth - x, 0);
                                                }
                                            }
                                        }
                            
                                        instance.set('x', x);
                            
                                        instance.set('cropWidth', cropWidth);
                                    },
                            
                                    /**
                                     * Create mouse over effect.
                                     *
                                     * @method _createHover
                                     * @protected
                                     */
                                    _createHover: function() {
                                        var instance = this;
                            
                                        instance._destroyHover();
                            
                                        instance._hoverHandles = instance.cropNode.on(
                                            'hover',
                                            instance._hoverOverlay,
                                            instance._unHoverOverlay,
                                            instance
                                        );
                                    },
                            
                                    /**
                                     * Define which function Image Cropper should execute.
                                     *
                                     * @method _defCropFn
                                     * @protected
                                     */
                                    _defCropFn: function(event) {
                                        var instance = this;
                            
                                        var cropType = event.cropType;
                            
                                        if (cropType === 'drag:drag') {
                                            instance._syncXY();
                                        }
                                        else if (cropType === 'resize:resize') {
                                            instance._syncCropSize();
                                        }
                                    },
                            
                                    /**
                                     * Destroy the ability to drag.
                                     *
                                     * @method _destroyDrag
                                     * @param object
                                     * @protected
                                     */
                                    _destroyDrag: function() {
                                        var instance = this;
                            
                                        if (instance.drag) {
                                            instance.drag.destroy();
                            
                                            delete instance.drag;
                                        }
                                    },
                            
                                    /**
                                     * Destroy the mouse over effect.
                                     *
                                     * @method _destroyHover
                                     * @protected
                                     */
                                    _destroyHover: function() {
                                        var instance = this;
                            
                                        if (instance._hoverHandles) {
                                            instance._hoverHandles.detach();
                            
                                            instance._hoverHandles = null;
                                        }
                                    },
                            
                                    /**
                                     * Destroy the ability to resize.
                                     *
                                     * @method _destroyResize
                                     * @param object
                                     * @protected
                                     */
                                    _destroyResize: function() {
                                        var instance = this;
                            
                                        if (instance.resize) {
                                            instance.resize.destroy();
                            
                                            delete instance.resize;
                                        }
                                    },
                            
                                    /**
                                     * Fire event of cropping a selected area.
                                     *
                                     * @method _fireCropEvent
                                     * @param event
                                     * @protected
                                     */
                                    _fireCropEvent: function(event) {
                                        var instance = this;
                            
                                        instance.fire('crop', {
                                            cropType: event.type
                                        });
                                    },
                            
                                    /**
                                     * Get contraint region.
                                     *
                                     * @method _getConstraintRegion
                                     * @param force
                                     * @protected
                                     */
                                    _getConstraintRegion: function(force) {
                                        var instance = this;
                            
                                        var region = !force ? instance._origRegion : null;
                            
                                        if (!region) {
                                            var imageNode = instance.get('srcNode');
                            
                                            var cropNode = instance.cropNode;
                            
                                            var imageXY = imageNode.getXY();
                            
                                            var imageX = imageXY[0];
                                            var imageY = imageXY[1];
                            
                                            region = {
                                                bottom: imageY + imageNode.height() + cropNode.getBorderWidth('b'),
                                                left: imageX - cropNode.getBorderWidth('l'),
                                                right: imageX + imageNode.width() + cropNode.getBorderWidth('r'),
                                                top: imageY - cropNode.getBorderWidth('t')
                                            };
                            
                                            if (!instance._origRegion) {
                                                instance._origRegion = region;
                                            }
                                        }
                            
                                        return region;
                                    },
                            
                                    /**
                                     * Get crop region (width/height/x/y).
                                     *
                                     * @method _getCropRegion
                                     * @return {Object}
                                     * @protected
                                     */
                                    _getCropRegion: function() {
                                        var instance = this;
                            
                                        return {
                                            height: instance.get('cropHeight'),
                                            width: instance.get('cropWidth'),
                                            x: instance.get('x'),
                                            y: instance.get('y')
                                        };
                                    },
                            
                                    /**
                                     * Add overlay class on mouse over event.
                                     *
                                     * @method _hoverOverlay
                                     * @protected
                                     */
                                    _hoverOverlay: function() {
                                        var instance = this;
                            
                                        if (!instance._isDragging() && !instance._isResizing()) {
                                            instance._boundingBox.addClass(CSS_OVERLAY_HOVER);
                                        }
                                    },
                            
                                    /**
                                     * Check if it's dragging.
                                     *
                                     * @method _isDragging
                                     * @protected
                                     */
                                    _isDragging: function() {
                                        var instance = this;
                            
                                        var drag = instance.drag;
                            
                                        return drag && drag.get('dragging');
                                    },
                            
                                    /**
                                     * Check if it's resizing.
                                     *
                                     * @method _isResizing
                                     * @protected
                                     */
                                    _isResizing: function() {
                                        var instance = this;
                            
                                        var resize = instance.resize;
                            
                                        return resize && resize.get('resizing');
                                    },
                            
                                    /**
                                     * Plug Drag into Image Cropper.
                                     *
                                     * @method _renderDrag
                                     * @protected
                                     */
                                    _renderDrag: function() {
                                        var instance = this;
                            
                                        var drag = new A.DD.Drag({
                                            node: instance.cropNode
                                        }).plug(
                                            A.Plugin.DDConstrained, {
                                                constrain: instance._getConstraintRegion()
                                            }
                                        );
                            
                                        drag.addTarget(instance);
                            
                                        drag.addHandle('.' + CSS_CROP_OUTLINE);
                            
                                        instance.drag = drag;
                                    },
                            
                                    /**
                                     * Plug Resize into Image Cropper.
                                     *
                                     * @method _renderResize
                                     * @protected
                                     */
                                    _renderResize: function() {
                                        var instance = this;
                            
                                        var resize = new A.Resize({
                                            node: instance.cropNode
                                        }).plug(
                                            A.Plugin.ResizeConstrained, {
                                                constrain: instance._getConstraintRegion(),
                                                preserveRatio: instance.get('preserveRatio'),
                                                minHeight: instance.get('minHeight'),
                                                minWidth: instance.get('minWidth')
                                            }
                                        );
                            
                                        resize.addTarget(instance);
                            
                                        instance.resize = resize;
                                    },
                            
                                    /**
                                     * Sync crop node on the UI.
                                     *
                                     * @method _syncCropNodeUI
                                     * @protected
                                     */
                                    _syncCropNodeUI: function() {
                                        var instance = this;
                            
                                        instance.cropNode.setStyle('backgroundPosition', (-instance.get('x')) + 'px ' + (-instance.get('y')) +
                                            'px');
                                    },
                            
                                    /**
                                     * Sync crop size (width/height).
                                     *
                                     * @method _syncCropSize
                                     * @param event
                                     * @protected
                                     */
                                    _syncCropSize: function() {
                                        var instance = this;
                            
                                        var cropNode = instance.cropNode;
                            
                                        instance.set('cropHeight', cropNode.outerHeight());
                                        instance.set('cropWidth', cropNode.outerWidth());
                                    },
                            
                                    /**
                                     * Sync region (top/bottom/left/right).
                                     *
                                     * @method _syncRegion
                                     * @param event
                                     * @protected
                                     */
                                    _syncRegion: function() {
                                        var instance = this;
                            
                                        var region = instance._getConstraintRegion(true);
                            
                                        var origRegion = instance._origRegion;
                            
                                        if (
                                            region.bottom !== origRegion.bottom ||
                                            region.left !== origRegion.left ||
                                            region.right !== origRegion.right ||
                                            region.top !== origRegion.top
                                        ) {
                            
                                            var drag = instance.drag;
                                            var resize = instance.resize;
                            
                                            if (drag) {
                                                drag.con.set('constrain', region);
                                            }
                            
                                            if (resize) {
                                                resize.con.set('constrain', region);
                                            }
                            
                                            instance._origRegion = region;
                                        }
                                    },
                            
                                    /**
                                     * Sync positions (x/y).
                                     *
                                     * @method _syncXY
                                     * @param event
                                     * @protected
                                     */
                                    _syncXY: function() {
                                        var instance = this;
                            
                                        var cropNode = instance.cropNode;
                            
                                        instance.set('x', toFloat(cropNode.getStyle('left')) + cropNode.getBorderWidth('l'));
                                        instance.set('y', toFloat(cropNode.getStyle('top')) + cropNode.getBorderWidth('t'));
                                    },
                            
                                    /**
                                     * Set `cropHeight` attribute on the UI.
                                     *
                                     * @method _uiSetCropHeight
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetCropHeight: function(value) {
                                        var instance = this;
                            
                                        instance.cropNode.height(value);
                                    },
                            
                                    /**
                                     * Set `cropWidth` attribute on the UI.
                                     *
                                     * @method _uiSetCropWidth
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetCropWidth: function(value) {
                                        var instance = this;
                            
                                        instance.cropNode.width(value);
                                    },
                            
                                    /**
                                     * Enable or disable mouse over effect on the UI.
                                     *
                                     * @method _uiSetDisabled
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetDisabled: function(value) {
                                        var instance = this;
                            
                                        ImageCropper.superclass._uiSetDisabled.apply(instance, arguments);
                            
                                        var enabled = !value;
                            
                                        instance.cropNode.toggle(enabled);
                            
                                        if (enabled) {
                                            instance._createHover();
                                        }
                                        else {
                                            instance._destroyHover();
                                        }
                                    },
                            
                                    /**
                                     * Set `minHeight` attribute on the UI.
                                     *
                                     * @method _uiSetMinHeight
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetMinHeight: function(value) {
                                        var instance = this;
                            
                                        var resize = instance.resize;
                            
                                        if (resize) {
                                            resize.con.set('minHeight', value);
                                        }
                                    },
                            
                                    /**
                                     * Set `minWidth` attribute on the UI.
                                     *
                                     * @method _uiSetMinWidth
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetMinWidth: function(value) {
                                        var instance = this;
                            
                                        var resize = instance.resize;
                            
                                        if (resize) {
                                            resize.con.set('minWidth', value);
                                        }
                                    },
                            
                                    /**
                                     * Set `movable` attribute on the UI.
                                     *
                                     * @method _uiSetMovable
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetMovable: function(value) {
                                        var instance = this;
                            
                                        instance.drag.set('lock', !value);
                                    },
                            
                                    /**
                                     * Set `preserveRatio` attribute on the UI.
                                     *
                                     * @method _uiSetPreserveRatio
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetPreserveRatio: function(value) {
                                        var instance = this;
                            
                                        var resize = instance.resize;
                            
                                        if (resize) {
                                            resize.con.set('preserveRatio', value);
                                        }
                                    },
                            
                                    /**
                                     * Set `resizable` attribute on the UI.
                                     *
                                     * @method _uiSetResizable
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetResizable: function(value) {
                                        var instance = this;
                            
                                        if (value) {
                                            if (instance._stopResizeHandle) {
                                                instance._stopResizeHandle.detach();
                                            }
                                        }
                                        else if (!instance._stopResizeHandle) {
                                            instance._stopResizeHandle = instance.resize.on(
                                                'resize:resize',
                                                function(event) {
                                                    event.halt();
                                                }
                                            );
                                        }
                                    },
                            
                                    /**
                                     * Set `x` attribute on the UI.
                                     *
                                     * @method _uiSetX
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetX: function(value) {
                                        var instance = this;
                            
                                        var cropNode = instance.cropNode;
                            
                                        cropNode.setStyle('left', value - cropNode.getBorderWidth('l'));
                                    },
                            
                                    /**
                                     * Set `y` attribute on the UI.
                                     *
                                     * @method _uiSetY
                                     * @param value
                                     * @protected
                                     */
                                    _uiSetY: function(value) {
                                        var instance = this;
                            
                                        var cropNode = instance.cropNode;
                            
                                        cropNode.setStyle('top', value - cropNode.getBorderWidth('t'));
                                    },
                            
                                    /**
                                     * Remove overlay class on mouse over event.
                                     *
                                     * @method _unHoverOverlay
                                     * @protected
                                     */
                                    _unHoverOverlay: function() {
                                        var instance = this;
                            
                                        if (!instance._isDragging() && !instance._isResizing()) {
                                            instance._boundingBox.removeClass(CSS_OVERLAY_HOVER);
                                        }
                                    }
                                }
                            });
                            
                            A.ImageCropper = ImageCropper;