2
* Image Cropper (v. 1.2.0 - 2006-10-30 )
3
* Copyright (c) 2006 David Spurr (http://www.defusion.org.uk/)
5
* The image cropper provides a way to draw a crop area on an image and capture
6
* the coordinates of the drawn crop area.
9
* - Based on Prototype and Scriptaculous
10
* - Image editing package styling, the crop area functions and looks
11
* like those found in popular image editing software
12
* - Dynamic inclusion of required styles
13
* - Drag to draw areas
14
* - Shift drag to draw/resize areas as squares
15
* - Selection area can be moved
16
* - Seleciton area can be resized using resize handles
17
* - Allows dimension ratio limited crop areas
18
* - Allows minimum dimension crop areas
19
* - Allows maximum dimesion crop areas
20
* - If both min & max dimension options set to the same value for a single axis,then the cropper will not
21
* display the resize handles as appropriate (when min & max dimensions are passed for both axes this
22
* results in a 'fixed size' crop area)
23
* - Allows dynamic preview of resultant crop ( if minimum width & height are provided ), this is
24
* implemented as a subclass so can be excluded when not required
25
* - Movement of selection area by arrow keys ( shift + arrow key will move selection area by
27
* - All operations stay within bounds of image
28
* - All functionality & display compatible with most popular browsers supported by Prototype:
29
* PC: IE 7, 6 & 5.5, Firefox 1.5, Opera 8.5 (see known issues) & 9.0b
30
* MAC: Camino 1.0, Firefox 1.5, Safari 2.0
33
* - Prototype v. 1.5.0_rc0 > (as packaged with Scriptaculous 1.6.1)
34
* - Scriptaculous v. 1.6.1 > modules: builder, dragdrop
37
* - Safari animated gifs, only one of each will animate, this seems to be a known Safari issue
39
* - After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height
40
* appears as the last height until the user drags, this appears to be the related to the error
41
* that the forceReRender() method fixes for IE 6, i.e. IE 5.5 is not redrawing the box properly.
43
* - Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, these
44
* could be fixed by using PNGs with transparency if Opera 8.5 support is high priority for you
46
* - Marching ants keep reloading in IE <6 (not tested in IE7), it is a known issue in IE and I have
47
* found no viable workarounds that can be included in the release. If this really is an issue for you
48
* either try this post: http://mir.aculo.us/articles/2005/08/28/internet-explorer-and-ajax-image-caching-woes
49
* or uncomment the 'FIX MARCHING ANTS IN IE' rules in the CSS file
51
* - Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will
52
* cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.
54
* - overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera (maybe Mac browsers too)
55
* I'm not sure why yet.
58
* See Cropper.Img & Cropper.ImgWithPreview for usage details
62
* + Added id to the preview image element using 'imgCrop_[originalImageID]'
63
* * #00001 - Fixed bug: Doesn't account for scroll offsets
64
* * #00009 - Fixed bug: Placing the cropper inside differently positioned elements causes incorrect co-ordinates and display
65
* * #00013 - Fixed bug: I-bar cursor appears on drag plane
66
* * #00014 - Fixed bug: If ID for image tag is not found in document script throws error
67
* * Fixed bug with drag start co-ordinates if wrapper element has moved in browser (e.g. dragged to a new position)
68
* * Fixed bug with drag start co-ordinates if image contained in a wrapper with scrolling - this may be buggy if image
69
* has other ancestors with scrolling applied (except the body)
70
* * #00015 - Fixed bug: When cropper removed and then reapplied onEndCrop callback gets called multiple times, solution suggestion from Bill Smith
71
* * Various speed increases & code cleanup which meant improved performance in Mac - which allowed removal of different overlay methods for
72
* IE and all other browsers, which led to a fix for:
73
* * #00010 - Fixed bug: Select area doesn't adhere to image size when image resized using img attributes
74
* - #00006 - Removed default behaviour of automatically setting a ratio when both min width & height passed, the ratioDimensions must be passed in
75
* + #00005 - Added ability to set maximum crop dimensions, if both min & max set as the same value then we'll get a fixed cropper size on the axes as appropriate
76
* and the resize handles will not be displayed as appropriate
77
* * Switched keydown for keypress for moving select area with cursor keys (makes for nicer action) - doesn't appear to work in Safari
80
* * Fixed wrong cursor on western handle in CSS
81
* + #00008 & #00003 - Added feature: Allow to set dimensions & position for cropper on load
82
* * #00002 - Fixed bug: Pressing 'remove cropper' twice removes image in IE
85
* * Fixed bugs with ratios when GCD is low (patch submitted by Andy Skelton)
88
* * Fixed bug with rendering issues fix in IE 5.5
89
* * Fixed bug with endCrop callback issues once cropper had been removed & reset in IE
92
* * Fixed bug with IE constantly trying to reload select area background image
93
* * Applied more robust fix to Safari & IE rendering issues
94
* + Added method to reset parameters - useful for when dynamically changing img cropper attached to
95
* + Added method to remove cropper from image
101
* Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)
102
* All rights reserved.
105
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
107
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
108
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
109
* * Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
111
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
113
* http://www.opensource.org/licenses/bsd-license.php
115
* See scriptaculous.js for full scriptaculous licence
119
* Extend the Draggable class to allow us to pass the rendering
120
* down to the Cropper object.
122
var CropDraggable = Class.create();
124
Object.extend( Object.extend( CropDraggable.prototype, Draggable.prototype), {
126
initialize: function(element) {
127
this.options = Object.extend(
130
* The draw method to defer drawing to
132
drawMethod: function() {}
137
this.element = $(element);
139
this.handle = this.element;
141
this.delta = this.currentDelta();
142
this.dragging = false;
144
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
145
Event.observe(this.handle, "mousedown", this.eventMouseDown);
147
Draggables.register(this);
151
* Defers the drawing of the draggable to the supplied method
153
draw: function(point) {
154
var pos = Position.cumulativeOffset(this.element);
155
var d = this.currentDelta();
159
var p = [0,1].map(function(i) {
160
return (point[i]-pos[i]-this.offset[i])
163
this.options.drawMethod( p );
170
* The Cropper object, this will attach itself to the provided image by wrapping it with
171
* the generated xHTML structure required by the cropper.
174
* @param obj Image element to attach to
175
* @param obj Optional options:
177
* The pixel dimensions to apply as a restrictive ratio, with properties x & y
180
* The minimum width for the select area in pixels
183
* The mimimum height for the select area in pixels
186
* The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
189
* The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
191
* - displayOnInit int
192
* Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
195
* The callback function to provide the crop details to on end of a crop (see below)
197
* - captureKeys boolean
198
* Whether to capture the keys for moving the select area, as these can cause some problems at the moment
201
* A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
203
*----------------------------------------------
205
* The callback function provided via the onEndCrop option should accept the following parameters:
207
* The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area
210
* The dimensions object with properites width & height; for the dimensions of the select area
214
* function onEndCrop( coords, dimensions ) {
215
* $( 'x1' ).value = coords.x1;
216
* $( 'y1' ).value = coords.y1;
217
* $( 'x2' ).value = coords.x2;
218
* $( 'y2' ).value = coords.y2;
219
* $( 'width' ).value = dimensions.width;
220
* $( 'height' ).value = dimensions.height;
225
Cropper.Img = Class.create();
226
Cropper.Img.prototype = {
229
* Initialises the class
232
* @param obj Image element to attach to
236
initialize: function(element, options) {
237
this.options = Object.extend(
241
* The pixel dimensions to apply as a restrictive ratio
243
ratioDim: { x: 0, y: 0 },
246
* The minimum pixel width, also used as restrictive ratio if min height passed too
251
* The minimum pixel height, also used as restrictive ratio if min width passed too
256
* Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
258
displayOnInit: false,
261
* The call back function to pass the final values to
263
onEndCrop: Prototype.emptyFunction,
266
* The call back function to be called if crop is canceled
268
onCancelCrop: Prototype.emptyFunction,
271
* The call back function to pass the final values to
273
onDblClick: Prototype.emptyFunction,
276
* The call back function on mouse wheel scrolling
281
* The call back function to pass the final values to
286
* The call back to be called on click of save button
291
* The call back function to handle click event
296
* Whether to capture key presses or not
300
* @var obj Coordinate object x1, y1, x2, y2
301
* The coordinates to optionally display the select area at onload
305
* @var obj tooltips for misc. buttons
310
* The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
315
* The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
319
* @var obj Coordinate object left, top, right, bottom
320
* The non-selectable margins
322
margins: { left: 0, top: 0, right: 0, bottom: 0 },
325
* If width is below this value, we are selecting whole horizontal range
330
* If height is below this value, we are selecting whole vertical range
335
* Whether to monitor reloading of picture
340
* Whether the image is already loaded
345
* Maximal duration of the click event in ms (after dragging is started)
354
* The img node to attach to
356
this.img = $( element );
359
* The x & y coordinates of the click point
361
this.clickCoords = { x: 0, y: 0 };
364
* Whether the user is dragging
366
this.dragging = false;
369
* Whether the user is resizing
371
this.resizing = false;
374
* Whether the selected area is present on the screen
376
this.selected = false;
379
* Whether the selected area is present on the screen
381
this.altered = false;
384
* Whether the user is on a webKit browser
386
this.isWebKit = /Konqueror|Safari|KHTML/.test( navigator.userAgent );
389
* Whether the user is on IE
391
this.isIE = /MSIE/.test( navigator.userAgent );
394
* Whether the user is on Opera below version 9
396
this.isOpera8 = /Opera\s[1-8]/.test( navigator.userAgent );
409
* Whether we've attached sucessfully
411
this.attached = false;
414
* Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width
415
* in the case of minWidth > maxWidth maxWidth wins as the fixed width)
417
this.fixedWidth = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );
420
* Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height
421
* in the case of minHeight > maxHeight maxHeight wins as the fixed height)
423
this.fixedHeight = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );
425
// quit if the image element doesn't exist
426
if( typeof this.img == 'undefined' ) return;
428
// include the stylesheet
429
$A( document.getElementsByTagName( 'script' ) ).each(
431
if( s.src.match( /cropper\.js/ ) ) {
432
var path = s.src.replace( /cropper\.js(.*)?/, '' );
433
// '<link rel="stylesheet" type="text/css" href="' + path + 'cropper.css" media="screen" />';
434
var style = document.createElement( 'link' );
435
style.rel = 'stylesheet';
436
style.type = 'text/css';
437
style.href = path + 'cropper.css';
438
style.media = 'screen';
439
document.getElementsByTagName( 'head' )[0].appendChild( style );
444
// calculate the ratio when neccessary
445
if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
446
var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );
447
this.ratioX = this.options.ratioDim.x / gcd;
448
this.ratioY = this.options.ratioDim.y / gcd;
449
// dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );
452
// initialise sub classes
453
this.subInitialize();
455
// only load the event observers etc. once the image is loaded
456
// this is done after the subInitialize() call just in case the sub class does anything
457
// that will affect the result of the call to onLoad()
458
if( this.options.imageReady || this.img.complete || this.isWebKit ) {
459
this.onLoad(); // for some reason Safari seems to support img.complete but returns 'undefined' on the this.img object
460
if (this.options.monitorImage) Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
461
} else Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );
468
* @param object Associative array of tooltips
471
setTooltip : function (opts) {
472
if (!this.options.tooltips) this.options.tooltips = new Object();
474
for (var i in opts) {
475
this.options.tooltips[i] = opts[i];
478
if ((this.applyButton)&&(opts.apply)) {
479
this.applyButton.setAttribute('title', opts.apply);
481
if ((this.saveButton)&&(opts.save)) {
482
this.saveButton.setAttribute('title', opts.save);
487
* The Euclidean algorithm used to find the greatest common divisor
494
getGCD : function( a , b ) {
495
if( b == 0 ) return a;
496
return this.getGCD(b, a % b );
500
* Attaches the cropper to the image once it has loaded
505
onLoad: function( ) {
507
* Build the container and all related elements, will result in the following
509
* <div class="imgCrop_wrap">
510
* <img ... this.img ... />
511
* <div class="imgCrop_dragArea">
512
* <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
513
* <div class="imgCrop_overlay imageCrop_north"><span></span></div>
514
* <div class="imgCrop_overlay imageCrop_east"><span></span></div>
515
* <div class="imgCrop_overlay imageCrop_south"><span></span></div>
516
* <div class="imgCrop_overlay imageCrop_west"><span></span></div>
517
* <div class="imgCrop_selArea">
519
* <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
520
* <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
521
* <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
522
* <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
523
* <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>
525
* <div class="imgCrop_handle imgCrop_handleN"></div>
526
* <div class="imgCrop_handle imgCrop_handleNE"></div>
527
* <div class="imgCrop_handle imgCrop_handleE"></div>
528
* <div class="imgCrop_handle imgCrop_handleSE"></div>
529
* <div class="imgCrop_handle imgCrop_handleS"></div>
530
* <div class="imgCrop_handle imgCrop_handleSW"></div>
531
* <div class="imgCrop_handle imgCrop_handleW"></div>
532
* <div class="imgCrop_handle imgCrop_handleNW"></div>
533
* <div class="imgCrop_clickArea"></div>
535
* <div class="imgCrop_clickArea"></div>
541
if (this.monitorImage) {
547
var cNamePrefix = 'imgCrop_';
549
// get the point to insert the container
550
var insertPoint = this.img.parentNode;
552
// apply an extra class to the wrapper to fix Opera below version 9
553
var fixOperaClass = '';
554
if( this.isOpera8 ) fixOperaClass = ' opera8';
555
this.imgWrap = Builder.node( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );
557
this.applyLinked = 0;
560
this.applyButton = Builder.node( 'div', { 'class': cNamePrefix + 'apply'} );
561
if ((this.applyButton.parentNode)&&(this.options.tooltips)&&(this.options.tooltips.apply)) {
562
this.applyButton.setAttribute('title', this.options.tooltips.apply);
564
this.saveButton = Builder.node( 'div', { 'class': cNamePrefix + 'save'} );
565
if ((this.saveButton.parentNode)&&(this.options.tooltips)&&(this.options.tooltips.save)) {
566
this.saveButton.setAttribute('title', this.options.tooltips.save);
570
this.north = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
571
this.north_east = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
572
this.north_west = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
573
this.east = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' } , [Builder.node( 'span' )] );
574
this.south = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
575
this.south_east = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
576
this.south_west = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
577
this.west = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' } , [Builder.node( 'span' )] );
579
var overlays = [ this.north_west, this.north, this.north_east, this.east, this.south_east, this.south, this.south_west, this.west ];
581
this.dragArea = Builder.node( 'div', { 'class': cNamePrefix + 'dragArea' }, overlays );
583
this.handleN = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );
584
this.handleNE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );
585
this.handleE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );
586
this.handleSE = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );
587
this.handleS = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );
588
this.handleSW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );
589
this.handleW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );
590
this.handleNW = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );
592
if (Prototype.Browser.IE) {
593
this.selArea = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
595
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
596
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' } , [Builder.node( 'span' )] ),
597
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
598
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' } , [Builder.node( 'span' )] ),
599
Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
603
this.handleArea = Builder.node( 'div', { 'class': cNamePrefix + 'handleArea' },
616
this.dragArea.appendChild( this.handleArea );
618
this.eventArea = this.handleArea;
620
this.selArea = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
622
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
623
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' } , [Builder.node( 'span' )] ),
624
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
625
Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' } , [Builder.node( 'span' )] ),
634
Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
638
this.eventArea = this.selArea;
641
this.imgWrap.appendChild( this.img );
642
this.imgWrap.appendChild( this.dragArea );
643
this.dragArea.appendChild( this.selArea );
644
this.dragArea.appendChild( Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } ) );
646
insertPoint.appendChild( this.imgWrap );
648
// add event observers
649
this.startDragBind = this.startDrag.bindAsEventListener( this );
650
Event.observe( this.dragArea, 'mousedown', this.startDragBind );
652
this.onDragBind = this.onDrag.bindAsEventListener( this );
653
Event.observe( document, 'mousemove', this.onDragBind );
655
this.endCropBind = this.endCrop.bindAsEventListener( this );
656
Event.observe( document, 'mouseup', this.endCropBind );
657
// Event.observe( this.imgWrap, 'mouseup', this.endCropBind );
659
this.dblClickBind = this.dblClick.bindAsEventListener( this );
660
Event.observe( this.eventArea, 'dblclick', this.dblClickBind );
663
Ext.EventManager.addListener(this.dragArea, "mousewheel", this.mouseScrollExt, this, {
668
this.mouseScrollBind = this.mouseScroll.bindAsEventListener( this );
669
Event.observe( this.dragArea, 'DOMMouseScroll', this.mouseScrollBind );
670
Event.observe( this.dragArea, 'mousewheel', this.mouseScrollBind );
673
this.applyClickBind = this.applyClick.bindAsEventListener( this );
674
Event.observe( this.applyButton, 'mousedown', this.applyClickBind );
675
// Event.observe( this.applyButton, 'click', this.applyClickBind );
677
this.saveClickBind = this.saveClick.bindAsEventListener( this );
678
Event.observe( this.saveButton, 'mousedown', this.saveClickBind );
680
this.resizeBind = this.startResize.bindAsEventListener( this );
681
this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];
682
this.registerHandles( true );
684
if( this.options.captureKeys ) {
685
this.keysBind = this.handleKeys.bindAsEventListener( this );
686
Event.observe( document, 'keypress', this.keysBind );
689
// attach the dragable to the select area
690
new CropDraggable( this.eventArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );
696
* Manages adding or removing the handle event handler and hiding or displaying them as appropriate
699
* @param boolean registration true = add, false = remove
702
registerHandles: function( registration ) {
703
for( var i = 0; i < this.handles.length; i++ ) {
704
var handle = $( this.handles[i] );
707
var hideHandle = false; // whether to hide the handle
709
// disable handles asappropriate if we've got fixed dimensions
710
// if both dimensions are fixed we don't need to do much
711
if( this.fixedWidth && this.fixedHeight ) hideHandle = true;
712
else if( this.fixedWidth || this.fixedHeight ) {
713
// if one of the dimensions is fixed then just hide those handles
714
var isCornerHandle = handle.className.match( /([S|N][E|W])$/ )
715
var isWidthHandle = handle.className.match( /(E|W)$/ );
716
var isHeightHandle = handle.className.match( /(N|S)$/ );
717
if( isCornerHandle ) hideHandle = true;
718
else if( this.fixedWidth && isWidthHandle ) hideHandle = true;
719
else if( this.fixedHeight && isHeightHandle ) hideHandle = true;
721
if( hideHandle ) handle.hide();
722
else Event.observe( handle, 'mousedown', this.resizeBind );
725
Event.stopObserving( handle, 'mousedown', this.resizeBind );
731
* Sets up all the cropper parameters, this can be used to reset the cropper when dynamically
732
* changing the images
737
setParams: function() {
742
this.imgW = this.img.width;
747
this.imgH = this.img.height;
749
$( this.north ).setStyle( { height: 0 } );
750
$( this.north_east ).setStyle( { height: 0 } );
751
$( this.north_west ).setStyle( { height: 0 } );
752
$( this.east ).setStyle( { width: 0, height: 0 } );
753
$( this.south ).setStyle( { height: 0 } );
754
$( this.south_east ).setStyle( { height: 0 } );
755
$( this.south_west ).setStyle( { height: 0 } );
756
$( this.west ).setStyle( { width: 0, height: 0 } );
758
// resize the container to fit the image
759
$( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );
761
// hide the select area
762
$( this.selArea ).hide();
763
if (this.handleArea) $( this.handleArea).hide();
766
if (this.applyLinked) {
767
this.dragArea.removeChild(this.applyButton);
768
this.applyLinked = false;
771
if (this.saveLinked) {
772
this.dragArea.removeChild(this.saveButton);
773
this.saveLinked = false;
776
this.selected = false;
777
this.altered = false;
779
// setup the starting position of the select area
780
var startCoords = { x1: this.options.margins.left, y1: this.options.margins.top, x2: this.options.margins.left, y2: this.options.margins.top };
781
var validCoordsSet = false;
783
// display the select area
784
if( this.options.onloadCoords != null ) {
785
// if we've being given some coordinates to
786
startCoords = this.cloneCoords( this.options.onloadCoords );
787
validCoordsSet = true;
788
} else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
789
// if there is a ratio limit applied and the then set it to initial ratio
790
startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );
791
startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );
792
startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;
793
startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;
794
validCoordsSet = true;
797
this.setAreaCoords( startCoords, false, false, 1 );
799
if( this.options.displayOnInit && validCoordsSet ) {
801
if (this.handleArea) this.handleArea.show();
806
this.attached = true;
810
* Removes the cropper
816
if( this.attached ) {
817
this.attached = false;
819
// remove the elements we inserted
820
this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );
821
this.imgWrap.parentNode.removeChild( this.imgWrap );
823
// remove the event observers
824
Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );
825
Event.stopObserving( document, 'mousemove', this.onDragBind );
826
Event.stopObserving( document, 'mouseup', this.endCropBind );
827
// Event.stopObserving( this.imgWrap, 'mouseup', this.endCropBind );
828
Event.stopObserving( this.eventArea, 'dblclick', this.dblClickBind );
830
Ext.EventManager.removeListener(this.dragArea, "mousewheel", this.mouseScrollExt);
832
Event.stopObserving( this.dragArea, 'DOMMouseScroll', this.mouseScrollBind );
833
Event.stopObserving( this.dragArea, 'mousewheel', this.mouseScrollBind );
835
Event.stopObserving( this.applyButton, 'mousedown', this.applyClickBind );
836
Event.stopObserving( this.saveButton, 'mousedown', this.saveClickBind );
838
this.registerHandles( false );
839
if( this.options.captureKeys ) Event.stopObserving( document, 'keypress', this.keysBind );
844
* Resets the cropper, can be used either after being removed or any time you wish
850
if( !this.attached ) this.onLoad();
851
else this.setParams();
856
* Resets the cropper selection
862
$( this.north ).setStyle( { height: 0 } );
863
$( this.north_east ).setStyle( { height: 0 } );
864
$( this.north_west ).setStyle( { height: 0 } );
865
$( this.east ).setStyle( { width: 0, height: 0 } );
866
$( this.south ).setStyle( { height: 0 } );
867
$( this.south_east ).setStyle( { height: 0 } );
868
$( this.south_west ).setStyle( { height: 0 } );
869
$( this.west ).setStyle( { width: 0, height: 0 } );
871
// hide the select area
872
$( this.selArea ).hide();
873
if (this.handleArea) $( this.handleArea).hide();
876
if (this.applyLinked) {
877
this.dragArea.removeChild(this.applyButton);
878
this.applyLinked = false;
880
if (this.saveLinked) {
881
this.dragArea.removeChild(this.saveButton);
882
this.saveLinked = false;
886
this.selected = false;
887
this.altered = false;
889
this.setAreaCoords( { x1: this.options.margins.left, y1: this.options.margins.top, x2: this.options.margins.left, y2: this.options.margins.top }, false, false, null );
894
* Handles the key functionality, currently just using arrow keys to move, if the user
895
* presses shift then the area will move by 10 pixels
897
handleKeys: function( e ) {
898
var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels
899
if( !this.dragging ) {
901
// catch the arrow keys
902
switch( e.keyCode ) {
909
case( 39 ) : // right
917
if( dir.x != 0 || dir.y != 0 ) {
918
// if shift is pressed then move by 10 pixels
924
this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );
931
* Calculates the width from the areaCoords
937
return (this.areaCoords.x2 - this.areaCoords.x1)
941
* Calculates the height from the areaCoords
947
return (this.areaCoords.y2 - this.areaCoords.y1)
951
* Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)
954
* @param array Point for x1 & y1 to move select area to
957
moveArea: function( point ) {
958
// dump( 'moveArea : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );
964
x2: point[0] + this.calcW(),
965
y2: point[1] + this.calcH()
974
* Clones a co-ordinates object, stops problems with handling them by reference
977
* @param obj Coordinate object x1, y1, x2, y2
978
* @return obj Coordinate object x1, y1, x2, y2
980
cloneCoords: function( coords ) {
981
return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };
985
* Sets the select coords to those provided but ensures they don't go
986
* outside the bounding box
989
* @param obj Coordinates x1, y1, x2, y2
990
* @param boolean Whether this is a move
991
* @param boolean Whether to apply squaring
992
* @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.
993
* @param string The current resize handle || null
996
setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {
997
// dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );
1000
var targW = coords.x2 - coords.x1;
1001
var targH = coords.y2 - coords.y1;
1003
// ensure we're within the bounds
1004
if( coords.x1 < this.options.margins.left ) {
1005
coords.x1 = this.options.margins.left;
1006
coords.x2 = this.options.margins.left + targW;
1008
if( coords.y1 < this.options.margins.top ) {
1009
coords.y1 = this.options.margins.top;
1010
coords.y2 = this.options.margins.top + targH;
1012
if( coords.x2 > (this.imgW - this.options.margins.right) ) {
1013
coords.x2 = this.imgW - this.options.margins.right;
1014
coords.x1 = coords.x2 - targW;
1016
if( coords.y2 > (this.imgH - this.options.margins.bottom) ) {
1017
coords.y2 = this.imgH - this.options.margins.bottom;
1018
coords.y1 = coords.y2 - targH;
1021
// ensure we're within the bounds
1022
if( coords.x1 < this.options.margins.left ) coords.x1 = this.options.margins.left;
1023
if( coords.y1 < this.options.margins.top ) coords.y1 = this.options.margins.top;
1024
if( coords.x2 > (this.imgW - this.options.margins.right) ) coords.x2 = this.imgW - this.options.margins.right;
1025
if( coords.y2 > (this.imgH - this.options.margins.bottom) ) coords.y2 = this.imgH - this.options.margins.bottom;
1027
// This is passed as null in onload
1028
if( direction != null ) {
1030
// apply the ratio or squaring where appropriate
1031
if( this.ratioX > 0 ) this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );
1032
else if( square ) this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );
1034
var mins = [ this.options.minWidth, this.options.minHeight ]; // minimum dimensions [x,y]
1035
var maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]
1037
// apply dimensions where appropriate
1038
if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {
1040
var coordsTransX = { a1: coords.x1, a2: coords.x2 };
1041
var coordsTransY = { a1: coords.y1, a2: coords.y2 };
1042
var boundsX = { min: 0, max: this.imgW };
1043
var boundsY = { min: 0, max: this.imgH };
1045
// handle squaring properly on single axis minimum dimensions
1046
if( (mins[0] != 0 || mins[1] != 0) && square ) {
1047
if( mins[0] > 0 ) mins[1] = mins[0];
1048
else if( mins[1] > 0 ) mins[0] = mins[1];
1051
if( (maxs[0] != 0 || maxs[0] != 0) && square ) {
1052
// if we have a max x value & it is less than the max y value then we set the y max to the max x (so we don't go over the minimum maximum of one of the axes - if that makes sense)
1053
if( maxs[0] > 0 && maxs[0] <= maxs[1] ) maxs[1] = maxs[0];
1054
else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) maxs[0] = maxs[1];
1057
if( mins[0] > 0 ) this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' );
1058
if( mins[1] > 1 ) this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' );
1060
if( maxs[0] > 0 ) this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' );
1061
if( maxs[1] > 1 ) this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' );
1063
coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };
1069
// dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );
1070
this.areaCoords = coords;
1074
* Applies the supplied dimension restriction to the supplied coordinates along a single axis
1077
* @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)
1078
* @param int The restriction value
1079
* @param int The direction ( -1 = negative, 1 = positive )
1080
* @param obj The bounds of the image ( for this axis )
1081
* @param string The dimension restriction type ( 'min' | 'max' )
1084
applyDimRestriction: function( coords, val, direction, bounds, type ) {
1086
if( type == 'min' ) check = ( ( coords.a2 - coords.a1 ) < val );
1087
else check = ( ( coords.a2 - coords.a1 ) > val );
1089
if( direction == 1 ) coords.a2 = coords.a1 + val;
1090
else coords.a1 = coords.a2 - val;
1092
// make sure we're still in the bounds (not too pretty for the user, but needed)
1093
if( coords.a1 < bounds.min ) {
1094
coords.a1 = bounds.min;
1096
} else if( coords.a2 > bounds.max ) {
1097
coords.a1 = bounds.max - val;
1098
coords.a2 = bounds.max;
1104
* Applies the supplied ratio to the supplied coordinates
1107
* @param obj Coordinates, x1, y1, x2, y2
1108
* @param obj Ratio, x, y
1109
* @param obj Direction of mouse, x & y : -1 == negative 1 == positive
1110
* @param string The current resize handle || null
1113
applyRatio : function( coords, ratio, direction, resizeHandle ) {
1114
// dump( 'direction.y : ' + direction.y + '\n');
1116
if( resizeHandle == 'N' || resizeHandle == 'S' ) {
1117
// dump( 'north south \n');
1118
// if moving on either the lone north & south handles apply the ratio on the y axis
1119
newCoords = this.applyRatioToAxis(
1120
{ a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },
1121
{ a: ratio.y, b: ratio.x },
1122
{ a: direction.y, b: direction.x },
1123
{ min: 0, max: this.imgW }
1125
coords.x1 = newCoords.b1;
1126
coords.y1 = newCoords.a1;
1127
coords.x2 = newCoords.b2;
1128
coords.y2 = newCoords.a2;
1130
// otherwise deal with it as if we're applying the ratio on the x axis
1131
newCoords = this.applyRatioToAxis(
1132
{ a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },
1133
{ a: ratio.x, b: ratio.y },
1134
{ a: direction.x, b: direction.y },
1135
{ min: 0, max: this.imgH }
1137
coords.x1 = newCoords.a1;
1138
coords.y1 = newCoords.b1;
1139
coords.x2 = newCoords.a2;
1140
coords.y2 = newCoords.b2;
1146
* Applies the provided ratio to the provided coordinates based on provided direction & bounds,
1147
* use to encapsulate functionality to make it easy to apply to either axis. This is probably
1148
* quite hard to visualise so see the x axis example within applyRatio()
1150
* Example in parameter details & comments is for requesting applying ratio to x axis.
1153
* @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
1154
* @param obj Ratio object (a, b) where a = x & b = y in example
1155
* @param obj Direction object (a, b) where a = x & b = y in example
1156
* @param obj Bounds (min, max)
1157
* @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
1159
applyRatioToAxis: function( coords, ratio, direction, bounds ) {
1160
var newCoords = Object.extend( coords, {} );
1161
var calcDimA = newCoords.a2 - newCoords.a1; // calculate dimension a (e.g. width)
1162
var targDimB = Math.floor( calcDimA * ratio.b / ratio.a ); // the target dimension b (e.g. height)
1163
var targB; // to hold target b (e.g. y value)
1164
var targDimA; // to hold target dimension a (e.g. width)
1165
var calcDimB = null; // to hold calculated dimension b (e.g. height)
1167
// dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
1169
if( direction.b == 1 ) { // if travelling in a positive direction
1170
// make sure we're not going out of bounds
1171
targB = newCoords.b1 + targDimB;
1172
if( targB > bounds.max ) {
1174
calcDimB = targB - newCoords.b1; // calcuate dimension b (e.g. height)
1177
newCoords.b2 = targB;
1178
} else { // if travelling in a negative direction
1179
// make sure we're not going out of bounds
1180
targB = newCoords.b2 - targDimB;
1181
if( targB < bounds.min ) {
1183
calcDimB = targB + newCoords.b2; // calcuate dimension b (e.g. height)
1185
newCoords.b1 = targB;
1188
// dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
1190
// apply the calculated dimensions
1191
if( calcDimB != null ) {
1192
targDimA = Math.floor( calcDimB * ratio.a / ratio.b );
1194
if( direction.a == 1 ) newCoords.a2 = newCoords.a1 + targDimA;
1195
else newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA;
1198
// dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
1204
* Draws the select area
1209
drawArea: function( ) {
1211
* NOTE: I'm not using the Element.setStyle() shortcut as they make it
1212
* quite sluggish on Mac based browsers
1214
// dump( 'drawArea : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );
1215
var areaWidth = this.calcW();
1216
var areaHeight = this.calcH();
1219
* Calculate all the style strings before we use them, allows reuse & produces quicker
1220
* rendering (especially noticable in Mac based browsers)
1224
this.areaCoords.x1 + px, // the left of the selArea
1225
this.areaCoords.y1 + px, // the top of the selArea
1226
areaWidth + px, // width of the selArea
1227
areaHeight + px, // height of the selArea
1228
this.areaCoords.x2 + px, // bottom of the selArea
1229
this.areaCoords.y2 + px, // right of the selArea
1230
(this.img.width - this.areaCoords.x2) + px, // right edge of selArea
1231
(this.img.height - this.areaCoords.y2) + px // bottom edge of selArea
1234
// do the select area
1235
var areaStyle = this.selArea.style;
1236
areaStyle.left = params[0];
1237
areaStyle.top = params[1];
1238
areaStyle.width = params[2];
1239
areaStyle.height = params[3];
1241
if (this.handleArea) {
1242
var handleStyle = this.handleArea.style;
1243
handleStyle.left = params[0];
1244
handleStyle.top = params[1];
1245
handleStyle.width = params[2];
1246
handleStyle.height = params[3];
1249
// position the north, east, south & west handles
1250
var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px;
1251
var vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;
1253
this.handleN.style.left = horizHandlePos;
1254
this.handleE.style.top = vertHandlePos;
1255
this.handleS.style.left = horizHandlePos;
1256
this.handleW.style.top = vertHandlePos;
1258
if ((areaWidth < this.options.allWidth)&&(areaHeight<this.options.allHeight)) {
1259
if (this.applyLinked) {
1260
this.dragArea.removeChild(this.applyButton);
1261
this.applyLinked = false;
1263
if (this.saveLinked) {
1264
this.dragArea.removeChild(this.saveButton);
1265
this.saveLinked = false;
1268
this.east.className = "imgCrop_overlay";
1269
this.west.className = "imgCrop_overlay";
1270
this.north.className = "imgCrop_overlay";
1271
this.south.className = "imgCrop_overlay";
1272
} else if (areaWidth < this.options.allWidth) {
1273
if ((this.options.onApplyClick)&&(!this.applyLinked)) {
1274
this.dragArea.appendChild(this.applyButton);
1275
this.applyLinked = true;
1277
if ((this.options.onSaveClick)&&(!this.saveLinked)) {
1278
this.dragArea.appendChild(this.saveButton);
1279
this.saveLinked = true;
1282
this.east.className = "imgCrop_selArea";
1283
this.west.className = "imgCrop_selArea";
1284
this.north.className = "imgCrop_overlay";
1285
this.south.className = "imgCrop_overlay";
1286
} else if (areaHeight < this.options.allHeight) {
1287
if ((this.options.onApplyClick)&&(!this.applyLinked)) {
1288
this.dragArea.appendChild(this.applyButton);
1289
this.applyLinked = true;
1291
if ((this.options.onSaveClick)&&(!this.saveLinked)) {
1292
this.dragArea.appendChild(this.saveButton);
1293
this.saveLinked = true;
1296
this.east.className = "imgCrop_overlay";
1297
this.west.className = "imgCrop_overlay";
1298
this.north.className = "imgCrop_selArea";
1299
this.south.className = "imgCrop_selArea";
1301
if ((this.options.onApplyClick)&&(!this.applyLinked)) {
1302
this.dragArea.appendChild(this.applyButton);
1303
this.applyLinked = true;
1305
if ((this.options.onSaveClick)&&(!this.saveLinked)) {
1306
this.dragArea.appendChild(this.saveButton);
1307
this.saveLinked = true;
1310
this.east.className = "imgCrop_overlay";
1311
this.west.className = "imgCrop_overlay";
1312
this.north.className = "imgCrop_overlay";
1313
this.south.className = "imgCrop_overlay";
1316
// draw the four overlays
1317
var northStyle = this.north.style;
1318
northStyle.height = params[1];
1319
northStyle.left = params[0];
1320
northStyle.width = params[2];
1322
var neStyle = this.north_east.style;
1323
neStyle.height = params[1];
1324
neStyle.width = params[0];
1326
var nwStyle = this.north_west.style;
1327
nwStyle.height = params[1];
1328
nwStyle.left = params[4];
1329
nwStyle.width = params[6];
1331
var eastStyle = this.east.style;
1332
eastStyle.top = params[1];
1333
eastStyle.height = params[3];
1334
eastStyle.left = params[4];
1335
eastStyle.width = params[6];
1337
var southStyle = this.south.style;
1338
southStyle.top = params[5];
1339
southStyle.height = params[7];
1340
southStyle.left = params[0];
1341
southStyle.width = params[2];
1343
var seStyle = this.south_east.style;
1344
seStyle.top = params[5];
1345
seStyle.height = params[7];
1346
seStyle.width = params[0];
1348
var swStyle = this.south_west.style;
1349
swStyle.top = params[5];
1350
swStyle.height = params[7];
1351
swStyle.left = params[4];
1352
swStyle.width = params[6];
1354
var westStyle = this.west.style;
1355
westStyle.top = params[1];
1356
westStyle.height = params[3];
1357
westStyle.width = params[0];
1359
var buttonWidth = 0;
1361
if (this.applyButton.parentNode) {
1362
var applyStyle = this.applyButton.style;
1363
applyStyle.left = params[4];
1364
applyStyle.top = params[5];
1366
buttonWidth += 1 + this.applyButton.offsetWidth;
1369
if (this.saveButton.parentNode) {
1370
var saveStyle = this.saveButton.style;
1371
saveStyle.left = (this.areaCoords.x2 - buttonWidth) + px;
1372
saveStyle.top = params[5];
1375
// call the draw method on sub classes
1378
this.forceReRender();
1382
* Force the re-rendering of the selArea element which fixes rendering issues in Safari
1383
* & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles
1388
forceReRender: function() {
1389
if( this.isIE || this.isWebKit) {
1390
var n = document.createTextNode(' ');
1393
if( this.isIE ) fixEl = this.selArea;
1394
else if( this.isWebKit ) {
1395
fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];
1396
/* we have to be a bit more forceful for Safari, otherwise the the marquee &
1397
* the south handles still don't move
1399
d = Builder.node( 'div', '' );
1400
d.style.visibility = 'hidden';
1402
var classList = ['SE','S','SW'];
1403
for( i = 0; i < classList.length; i++ ) {
1404
el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];
1405
if( el.childNodes.length ) el.removeChild( el.childNodes[0] );
1409
fixEl.appendChild(n);
1410
fixEl.removeChild(n);
1421
startResize: function( e ) {
1422
this.startCoords = this.cloneCoords( this.areaCoords );
1424
this.altered = true;
1425
this.resizing = true;
1427
this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');
1428
// dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );
1439
startDrag: function( e ) {
1440
if (!this.dragging) {
1441
var curtime = new Date();
1442
this.start_time = curtime.getTime();
1445
this.selArea.show();
1446
if (this.handleArea) this.handleArea.show();
1447
this.clickCoords = this.getCurPos( e );
1449
this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );
1451
this.altered = true;
1452
this.dragging = true;
1454
if (this.options.onClick)
1455
this.dragged = false;
1457
this.onDrag( e ); // incase the user just clicks once after already making a selection
1462
* Gets the current cursor position relative to the image
1466
* @return obj x,y pixels of the cursor
1468
getCurPos: function( e ) {
1469
// get the offsets for the wrapper within the document
1470
var el = this.imgWrap, wrapOffsets = Position.cumulativeOffset( el );
1472
// remove any scrolling that is applied to the wrapper (this may be buggy) - don't count the scroll on the body as that won't affect us
1473
/* DS: Causes problems in Opera9 (IE, Seamonkey are OK)
1474
while( el.nodeName != 'BODY' ) {
1475
wrapOffsets[1] -= el.scrollTop || 0;
1476
wrapOffsets[0] -= el.scrollLeft || 0;
1481
x: Event.pointerX(e) - wrapOffsets[0],
1482
y: Event.pointerY(e) - wrapOffsets[1]
1487
* Performs the drag for both resize & inital draw dragging
1493
onDrag: function( e ) {
1494
if( this.dragging || this.resizing ) {
1495
this.dragged = true;
1497
var resizeHandle = null;
1498
var curPos = this.getCurPos( e );
1499
var newCoords = this.cloneCoords( this.areaCoords );
1500
var direction = { x: 1, y: 1 };
1502
if( this.dragging ) {
1503
if( curPos.x < this.clickCoords.x ) direction.x = -1;
1504
if( curPos.y < this.clickCoords.y ) direction.y = -1;
1506
this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );
1507
this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );
1508
} else if( this.resizing ) {
1509
resizeHandle = this.resizeHandle;
1510
// do x movements first
1511
if( resizeHandle.match(/E/) ) {
1512
// if we're moving an east handle
1513
this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' );
1514
if( curPos.x < this.startCoords.x1 ) direction.x = -1;
1515
} else if( resizeHandle.match(/W/) ) {
1516
// if we're moving an west handle
1517
this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );
1518
if( curPos.x < this.startCoords.x2 ) direction.x = -1;
1521
// do y movements second
1522
if( resizeHandle.match(/N/) ) {
1523
// if we're moving an north handle
1524
this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );
1525
if( curPos.y < this.startCoords.y2 ) direction.y = -1;
1526
} else if( resizeHandle.match(/S/) ) {
1527
// if we're moving an south handle
1528
this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' );
1529
if( curPos.y < this.startCoords.y1 ) direction.y = -1;
1534
this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );
1536
Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC
1541
* Applies the appropriate transform to supplied co-ordinates, on the
1542
* defined axis, depending on the relationship of the supplied values
1545
* @param int Current value of pointer
1546
* @param int Base value to compare current pointer val to
1547
* @param obj Coordinates to apply transformation on x1, x2, y1, y2
1548
* @param string Axis to apply transformation on 'x' || 'y'
1551
transformCoords : function( curVal, baseVal, coords, axis ) {
1552
var newVals = [ curVal, baseVal ];
1553
if( curVal > baseVal ) newVals.reverse();
1554
coords[ axis + '1' ] = newVals[0];
1555
coords[ axis + '2' ] = newVals[1];
1559
* Ends the crop & passes the values of the select area on to the appropriate
1560
* callback function on completion of a crop
1565
endCrop : function(e) {
1569
if ((this.dragging)&&(this.start_time)) {
1570
var curtime = new Date();
1571
var duration = curtime.getTime() - this.start_time;
1573
if (duration < this.options.clickDuration) click = true;
1577
this.dragging = false;
1578
this.resizing = false;
1584
if ((w>this.options.allWidth)||(h>this.options.allHeight)) click = false;
1588
var x = this.areaCoords.x1;
1589
var y = this.areaCoords.y1;
1591
if (this.selected) {
1592
this.options.onCancelCrop();
1594
} else if (this.options.onClick) {
1595
this.options.onClick(e,
1603
} else if ((w>this.options.allWidth)||(h>this.options.allHeight)) {
1604
if (!this.dragged) this.onDrag( e );
1607
this.options.onEndCrop(
1614
this.altered = false;
1616
this.selected = true;
1623
* Passes the values of the select area on to the appropriate
1624
* callback function on double click in cropping area
1629
dblClick : function() {
1630
this.dragging = false;
1631
this.resizing = false;
1633
var w = this.calcW();
1634
var h = this.calcH();
1636
if ((w > this.options.allWidth)&&(h > this.options.allHeight)) {
1637
this.options.onDblClick(
1647
mouseScroll : function(ev) {
1648
if (this.options.onMouseScroll) {
1649
this.dragging = false;
1650
this.resizing = false;
1652
var delta = domGetScrollEventDelta(ev);
1654
this.options.onMouseScroll(delta,
1661
mouseScrollExt: function(e) {
1662
this.options.onMouseScroll(-e.getWheelDelta(),
1669
* Passes the values of the select area on to the appropriate
1670
* callback function on a click on apply button
1675
applyClick : function(ev) {
1676
this.dragging = false;
1677
this.resizing = false;
1679
this.options.onApplyClick(
1682
width: this.calcW(),
1683
height: this.calcH()
1686
//ev.cancelBubble = true;
1691
* Calls save callback
1696
saveClick : function(ev) {
1697
this.dragging = false;
1698
this.resizing = false;
1700
this.options.onSaveClick(
1703
width: this.calcW(),
1704
height: this.calcH()
1707
//ev.cancelBubble = true;
1712
* Abstract method called on the end of initialization
1718
subInitialize: function() {},
1721
* Abstract method called on the end of drawArea()
1727
subDrawArea: function() {}
1734
* Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,
1735
* the option for displayOnInit is always overridden to true when displaying a preview image
1738
* @param obj Image element to attach to
1739
* @param obj Optional options:
1740
* - see Cropper.Img for base options
1743
* HTML element that will be used as a container for the preview image
1745
Cropper.ImgWithPreview = Class.create();
1747
Object.extend( Object.extend( Cropper.ImgWithPreview.prototype, Cropper.Img.prototype ), {
1750
* Implements the abstract method from Cropper.Img to initialize preview image settings.
1751
* Will only attach a preview image is the previewWrap element is defined and the minWidth
1752
* & minHeight options are set.
1754
* @see Croper.Img.subInitialize
1756
subInitialize: function() {
1758
* Whether or not we've attached a preview image
1761
this.hasPreviewImg = false;
1762
if( typeof(this.options.previewWrap) != 'undefined'
1763
&& this.options.minWidth > 0
1764
&& this.options.minHeight > 0
1767
* The preview image wrapper element
1768
* @var obj HTML element
1770
this.previewWrap = $( this.options.previewWrap );
1772
* The preview image element
1773
* @var obj HTML IMG element
1775
this.previewImg = this.img.cloneNode( false );
1776
// set the ID of the preview image to be unique
1777
this.previewImg.id = 'imgCrop_' + this.previewImg.id;
1780
// set the displayOnInit option to true so we display the select area at the same time as the thumbnail
1781
this.options.displayOnInit = true;
1783
this.hasPreviewImg = true;
1785
this.previewWrap.addClassName( 'imgCrop_previewWrap' );
1787
this.previewWrap.setStyle(
1789
width: this.options.minWidth + 'px',
1790
height: this.options.minHeight + 'px'
1794
this.previewWrap.appendChild( this.previewImg );
1799
* Implements the abstract method from Cropper.Img to draw the preview image
1801
* @see Croper.Img.subDrawArea
1803
subDrawArea: function() {
1804
if( this.hasPreviewImg ) {
1805
// get the ratio of the select area to the src image
1806
var calcWidth = this.calcW();
1807
var calcHeight = this.calcH();
1808
// ratios for the dimensions of the preview image
1810
x: this.imgW / calcWidth,
1811
y: this.imgH / calcHeight
1813
//ratios for the positions within the preview
1815
x: calcWidth / this.options.minWidth,
1816
y: calcHeight / this.options.minHeight
1819
// setting the positions in an obj before apply styles for rendering speed increase
1821
w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',
1822
h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',
1823
x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x ) + 'px',
1824
y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'
1827
var previewStyle = this.previewImg.style;
1828
previewStyle.width = calcPos.w;
1829
previewStyle.height = calcPos.h;
1830
previewStyle.left = calcPos.x;
1831
previewStyle.top = calcPos.y;