root/afridex/plugins/Flutter/js/cropper.uncompressed.js @ 23

Revision 21, 48.1 kB (checked in by admin, 18 years ago)
Line 
1/**
2 * Image Cropper (v. 1.2.0 - 2006-10-30 )
3 * Copyright (c) 2006 David Spurr (http://www.defusion.org.uk/)
4 *
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.
7 *
8 * Features include:
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
26 *                10 pixels )
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
31 *
32 * Requires:
33 *              - Prototype v. 1.5.0_rc0 > (as packaged with Scriptaculous 1.6.1)
34 *              - Scriptaculous v. 1.6.1 > modules: builder, dragdrop
35 *             
36 * Known issues:
37 *              - Safari animated gifs, only one of each will animate, this seems to be a known Safari issue
38 *
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.
42 *
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
45 *
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
50 *             
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.
53 *
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.
56 *
57 * Usage:
58 *              See Cropper.Img & Cropper.ImgWithPreview for usage details
59 *
60 * Changelog:
61 * v1.2.0 - 2006-10-30
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
78 *
79 * v1.1.3 - 2006-08-21
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
83 *
84 * v1.1.2 - 2006-06-09
85 *              * Fixed bugs with ratios when GCD is low (patch submitted by Andy Skelton)
86 *
87 * v1.1.1 - 2006-06-03
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
90 *
91 * v1.1.0 - 2006-06-02
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
96 *
97 * v1.0.0 - 2006-05-18
98 *              + Initial verison
99 *
100 *
101 * Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)
102 * All rights reserved.
103 *
104 *
105 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
106 *
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.
110 *
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.
112 *
113 * http://www.opensource.org/licenses/bsd-license.php
114 *
115 * See scriptaculous.js for full scriptaculous licence
116 */
117 
118/**
119 * Extend the Draggable class to allow us to pass the rendering
120 * down to the Cropper object.
121 */
122var CropDraggable = Class.create();
123
124Object.extend( Object.extend( CropDraggable.prototype, Draggable.prototype), {
125       
126        initialize: function(element) {
127                this.options = Object.extend(
128                        {
129                                /**
130                                 * The draw method to defer drawing to
131                                 */
132                                drawMethod: function() {}
133                        }, 
134                        arguments[1] || {}
135                );
136
137                this.element = $(element);
138
139                this.handle = this.element;
140
141                this.delta    = this.currentDelta();
142                this.dragging = false;   
143
144                this.eventMouseDown = this.initDrag.bindAsEventListener(this);
145                Event.observe(this.handle, "mousedown", this.eventMouseDown);
146
147                Draggables.register(this);
148        },
149       
150        /**
151         * Defers the drawing of the draggable to the supplied method
152         */
153        draw: function(point) {
154                var pos = Position.cumulativeOffset(this.element);
155                var d = this.currentDelta();
156                pos[0] -= d[0]; 
157                pos[1] -= d[1];
158                               
159                var p = [0,1].map(function(i) { 
160                        return (point[i]-pos[i]-this.offset[i]) 
161                }.bind(this));
162                               
163                this.options.drawMethod( p );
164        }
165       
166});
167
168
169/**
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.
172 *
173 * Usage:
174 *      @param obj Image element to attach to
175 *      @param obj Optional options:
176 *              - ratioDim obj
177 *                      The pixel dimensions to apply as a restrictive ratio, with properties x & y
178 *
179 *              - minWidth int
180 *                      The minimum width for the select area in pixels
181 *
182 *              - minHeight     int
183 *                      The mimimum height for the select area in pixels
184 *
185 *              - maxWidth int
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)
187 *
188 *              - maxHeight int
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)
190 *
191 *              - displayOnInit int
192 *                      Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
193 *
194 *              - onEndCrop func
195 *                      The callback function to provide the crop details to on end of a crop (see below)
196 *
197 *              - captureKeys boolean
198 *                      Whether to capture the keys for moving the select area, as these can cause some problems at the moment
199 *
200 *              - onloadCoords obj
201 *                      A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
202 *     
203 *----------------------------------------------
204 *
205 * The callback function provided via the onEndCrop option should accept the following parameters:
206 *              - coords obj
207 *                      The coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area
208 *
209 *              - dimensions obj
210 *                      The dimensions object with properites width & height; for the dimensions of the select area
211 *             
212 *
213 *              Example:
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;
221 *                      }
222 *
223 */
224var Cropper = {};
225Cropper.Img = Class.create();
226Cropper.Img.prototype = {
227       
228        /**
229         * Initialises the class
230         *
231         * @access public
232         * @param obj Image element to attach to
233         * @param obj Options
234         * @return void
235         */
236        initialize: function(element, options) {
237                this.options = Object.extend(
238                        {
239                                /**
240                                 * @var obj
241                                 * The pixel dimensions to apply as a restrictive ratio
242                                 */
243                                ratioDim: { x: 0, y: 0 },
244                                /**
245                                 * @var int
246                                 * The minimum pixel width, also used as restrictive ratio if min height passed too
247                                 */
248                                minWidth:               0,
249                                /**
250                                 * @var int
251                                 * The minimum pixel height, also used as restrictive ratio if min width passed too
252                                 */
253                                minHeight:              0,
254                                /**
255                                 * @var boolean
256                                 * Whether to display the select area on initialisation, only used when providing minimum width & height or ratio
257                                 */
258                                displayOnInit:  false,
259                                /**
260                                 * @var function
261                                 * The call back function to pass the final values to
262                                 */
263                                onEndCrop: Prototype.emptyFunction,
264                                /**
265                                 * @var boolean
266                                 * Whether to capture key presses or not
267                                 */
268                                captureKeys: true,
269                                /**
270                                 * @var obj Coordinate object x1, y1, x2, y2
271                                 * The coordinates to optionally display the select area at onload
272                                 */
273                                onloadCoords: null,
274                                /**
275                                 * @var int
276                                 * The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
277                                 */
278                                maxWidth: 0,
279                                /**
280                                 * @var int
281                                 * The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
282                                 */
283                                maxHeight: 0
284                        }, 
285                        options || {}
286                );                             
287                /**
288                 * @var obj
289                 * The img node to attach to
290                 */
291                this.img                        = $( element );
292                /**
293                 * @var obj
294                 * The x & y coordinates of the click point
295                 */
296                this.clickCoords        = { x: 0, y: 0 };
297                /**
298                 * @var boolean
299                 * Whether the user is dragging
300                 */
301                this.dragging           = false;
302                /**
303                 * @var boolean
304                 * Whether the user is resizing
305                 */
306                this.resizing           = false;
307                /**
308                 * @var boolean
309                 * Whether the user is on a webKit browser
310                 */
311                this.isWebKit           = /Konqueror|Safari|KHTML/.test( navigator.userAgent );
312                /**
313                 * @var boolean
314                 * Whether the user is on IE
315                 */
316                this.isIE                       = /MSIE/.test( navigator.userAgent );
317                /**
318                 * @var boolean
319                 * Whether the user is on Opera below version 9
320                 */
321                this.isOpera8           = /Opera\s[1-8]/.test( navigator.userAgent );
322                /**
323                 * @var int
324                 * The x ratio
325                 */
326                this.ratioX                     = 0;
327                /**
328                 * @var int
329                 * The y ratio
330                 */
331                this.ratioY                     = 0;
332                /**
333                 * @var boolean
334                 * Whether we've attached sucessfully
335                 */
336                this.attached           = false;
337                /**
338                 * @var boolean
339                 * Whether we've got a fixed width (if minWidth EQ or GT maxWidth then we have a fixed width
340                 * in the case of minWidth > maxWidth maxWidth wins as the fixed width)
341                 */
342                this.fixedWidth         = ( this.options.maxWidth > 0 && ( this.options.minWidth >= this.options.maxWidth ) );
343                /**
344                 * @var boolean
345                 * Whether we've got a fixed height (if minHeight EQ or GT maxHeight then we have a fixed height
346                 * in the case of minHeight > maxHeight maxHeight wins as the fixed height)
347                 */
348                this.fixedHeight        = ( this.options.maxHeight > 0 && ( this.options.minHeight >= this.options.maxHeight ) );
349               
350                // quit if the image element doesn't exist
351                if( typeof this.img == 'undefined' ) return;
352                               
353                // include the stylesheet               
354                $A( document.getElementsByTagName( 'script' ) ).each( 
355                        function(s) {
356                                if( s.src.match( /cropper\.js/ ) ) {
357                                        var path        = s.src.replace( /cropper\.js(.*)?/, '' );
358                                        // '<link rel="stylesheet" type="text/css" href="' + path + 'cropper.css" media="screen" />';
359                                        var style               = document.createElement( 'link' );
360                                        style.rel               = 'stylesheet';
361                                        style.type              = 'text/css';
362                                        style.href              = path + 'cropper.css';
363                                        style.media     = 'screen';
364                                        document.getElementsByTagName( 'head' )[0].appendChild( style );
365                                }
366                }
367            );   
368       
369                // calculate the ratio when neccessary
370                if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
371                        var gcd = this.getGCD( this.options.ratioDim.x, this.options.ratioDim.y );
372                        this.ratioX = this.options.ratioDim.x / gcd;
373                        this.ratioY = this.options.ratioDim.y / gcd;
374                        // dump( 'RATIO : ' + this.ratioX + ':' + this.ratioY + '\n' );
375                }
376                                                       
377                // initialise sub classes
378                this.subInitialize();
379
380                // only load the event observers etc. once the image is loaded
381                // this is done after the subInitialize() call just in case the sub class does anything
382                // that will affect the result of the call to onLoad()
383                if( this.img.complete || this.isWebKit ) this.onLoad(); // for some reason Safari seems to support img.complete but returns 'undefined' on the this.img object
384                else Event.observe( this.img, 'load', this.onLoad.bindAsEventListener( this) );         
385        },
386       
387        /**
388         * The Euclidean algorithm used to find the greatest common divisor
389         *
390         * @acces private
391         * @param int Value 1
392         * @param int Value 2
393         * @return int
394         */
395        getGCD : function( a , b ) {
396                if( b == 0 ) return a;
397                return this.getGCD(b, a % b );
398        },
399       
400        /**
401         * Attaches the cropper to the image once it has loaded
402         *
403         * @access private
404         * @return void
405         */
406        onLoad: function( ) {
407                /*
408                 * Build the container and all related elements, will result in the following
409                 *
410                 * <div class="imgCrop_wrap">
411                 *              <img ... this.img ... />
412                 *              <div class="imgCrop_dragArea">
413                 *                      <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
414                 *                      <div class="imgCrop_overlay imageCrop_north"><span></span></div>
415                 *                      <div class="imgCrop_overlay imageCrop_east"><span></span></div>
416                 *                      <div class="imgCrop_overlay imageCrop_south"><span></span></div>
417                 *                      <div class="imgCrop_overlay imageCrop_west"><span></span></div>
418                 *                      <div class="imgCrop_selArea">
419                 *                              <!-- marquees -->
420                 *                              <!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
421                 *                              <div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
422                 *                              <div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
423                 *                              <div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
424                 *                              <div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>                       
425                 *                              <!-- handles -->
426                 *                              <div class="imgCrop_handle imgCrop_handleN"></div>
427                 *                              <div class="imgCrop_handle imgCrop_handleNE"></div>
428                 *                              <div class="imgCrop_handle imgCrop_handleE"></div>
429                 *                              <div class="imgCrop_handle imgCrop_handleSE"></div>
430                 *                              <div class="imgCrop_handle imgCrop_handleS"></div>
431                 *                              <div class="imgCrop_handle imgCrop_handleSW"></div>
432                 *                              <div class="imgCrop_handle imgCrop_handleW"></div>
433                 *                              <div class="imgCrop_handle imgCrop_handleNW"></div>
434                 *                              <div class="imgCrop_clickArea"></div>
435                 *                      </div> 
436                 *                      <div class="imgCrop_clickArea"></div>
437                 *              </div> 
438                 * </div>
439                 */
440                var cNamePrefix = 'imgCrop_';
441               
442                // get the point to insert the container
443                var insertPoint = this.img.parentNode;
444               
445                // apply an extra class to the wrapper to fix Opera below version 9
446                var fixOperaClass = '';
447                if( this.isOpera8 ) fixOperaClass = ' opera8';
448                this.imgWrap = Builder.node( 'div', { 'class': cNamePrefix + 'wrap' + fixOperaClass } );
449               
450                this.north              = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'north' }, [Builder.node( 'span' )] );
451                this.east               = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'east' } , [Builder.node( 'span' )] );
452                this.south              = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'south' }, [Builder.node( 'span' )] );
453                this.west               = Builder.node( 'div', { 'class': cNamePrefix + 'overlay ' + cNamePrefix + 'west' } , [Builder.node( 'span' )] );
454               
455                var overlays    = [ this.north, this.east, this.south, this.west ];
456
457                this.dragArea   = Builder.node( 'div', { 'class': cNamePrefix + 'dragArea' }, overlays );
458                                               
459                this.handleN    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleN' } );
460                this.handleNE   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNE' } );
461                this.handleE    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleE' } );
462                this.handleSE   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSE' } );
463                this.handleS    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleS' } );
464                this.handleSW   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleSW' } );
465                this.handleW    = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleW' } );
466                this.handleNW   = Builder.node( 'div', { 'class': cNamePrefix + 'handle ' + cNamePrefix + 'handleNW' } );
467                               
468                this.selArea    = Builder.node( 'div', { 'class': cNamePrefix + 'selArea' },
469                        [
470                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeNorth' }, [Builder.node( 'span' )] ),
471                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeEast' }  , [Builder.node( 'span' )] ),
472                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeHoriz ' + cNamePrefix + 'marqueeSouth' }, [Builder.node( 'span' )] ),
473                                Builder.node( 'div', { 'class': cNamePrefix + 'marqueeVert ' + cNamePrefix + 'marqueeWest' }  , [Builder.node( 'span' )] ),
474                                this.handleN,
475                                this.handleNE,
476                                this.handleE,
477                                this.handleSE,
478                                this.handleS,
479                                this.handleSW,
480                                this.handleW,
481                                this.handleNW,
482                                Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } )
483                        ]
484                );
485                               
486                this.imgWrap.appendChild( this.img );
487                this.imgWrap.appendChild( this.dragArea );
488                this.dragArea.appendChild( this.selArea );
489                this.dragArea.appendChild( Builder.node( 'div', { 'class': cNamePrefix + 'clickArea' } ) );
490
491                insertPoint.appendChild( this.imgWrap );
492
493                // add event observers
494                this.startDragBind      = this.startDrag.bindAsEventListener( this );
495                Event.observe( this.dragArea, 'mousedown', this.startDragBind );
496               
497                this.onDragBind         = this.onDrag.bindAsEventListener( this );
498                Event.observe( document, 'mousemove', this.onDragBind );
499               
500                this.endCropBind        = this.endCrop.bindAsEventListener( this );
501                Event.observe( document, 'mouseup', this.endCropBind );
502               
503                this.resizeBind         = this.startResize.bindAsEventListener( this );
504                this.handles = [ this.handleN, this.handleNE, this.handleE, this.handleSE, this.handleS, this.handleSW, this.handleW, this.handleNW ];
505                this.registerHandles( true );
506               
507                if( this.options.captureKeys ) {
508                        this.keysBind = this.handleKeys.bindAsEventListener( this );
509                        Event.observe( document, 'keypress', this.keysBind );
510                }
511
512                // attach the dragable to the select area
513                new CropDraggable( this.selArea, { drawMethod: this.moveArea.bindAsEventListener( this ) } );
514               
515                this.setParams();
516        },
517       
518        /**
519         * Manages adding or removing the handle event handler and hiding or displaying them as appropriate
520         *
521         * @access private
522         * @param boolean registration true = add, false = remove
523         * @return void
524         */
525        registerHandles: function( registration ) {     
526                for( var i = 0; i < this.handles.length; i++ ) {
527                        var handle = $( this.handles[i] );
528                       
529                        if( registration ) {
530                                var hideHandle  = false;        // whether to hide the handle
531                               
532                                // disable handles asappropriate if we've got fixed dimensions
533                                // if both dimensions are fixed we don't need to do much
534                                if( this.fixedWidth && this.fixedHeight ) hideHandle = true;
535                                else if( this.fixedWidth || this.fixedHeight ) {
536                                        // if one of the dimensions is fixed then just hide those handles
537                                        var isCornerHandle      = handle.className.match( /([S|N][E|W])$/ )
538                                        var isWidthHandle       = handle.className.match( /(E|W)$/ );
539                                        var isHeightHandle      = handle.className.match( /(N|S)$/ );
540                                        if( isCornerHandle ) hideHandle = true;
541                                        else if( this.fixedWidth && isWidthHandle ) hideHandle = true;
542                                        else if( this.fixedHeight && isHeightHandle ) hideHandle = true;
543                                }
544                                if( hideHandle ) handle.hide();
545                                else Event.observe( handle, 'mousedown', this.resizeBind );
546                        } else {
547                                handle.show();
548                                Event.stopObserving( handle, 'mousedown', this.resizeBind );
549                        }
550                }
551        },
552               
553        /**
554         * Sets up all the cropper parameters, this can be used to reset the cropper when dynamically
555         * changing the images
556         *
557         * @access private
558         * @return void
559         */
560        setParams: function() {
561                /**
562                 * @var int
563                 * The image width
564                 */
565                this.imgW = this.img.width;
566                /**
567                 * @var int
568                 * The image height
569                 */
570                this.imgH = this.img.height;                   
571
572                $( this.north ).setStyle( { height: 0 } );
573                $( this.east ).setStyle( { width: 0, height: 0 } );
574                $( this.south ).setStyle( { height: 0 } );
575                $( this.west ).setStyle( { width: 0, height: 0 } );
576               
577                // resize the container to fit the image
578                $( this.imgWrap ).setStyle( { 'width': this.imgW + 'px', 'height': this.imgH + 'px' } );
579               
580                // hide the select area
581                $( this.selArea ).hide();
582                                               
583                // setup the starting position of the select area
584                var startCoords = { x1: 0, y1: 0, x2: 0, y2: 0 };
585                var validCoordsSet = false;
586               
587                // display the select area
588                if( this.options.onloadCoords != null ) {
589                        // if we've being given some coordinates to
590                        startCoords = this.cloneCoords( this.options.onloadCoords );
591                        validCoordsSet = true;
592                } else if( this.options.ratioDim.x > 0 && this.options.ratioDim.y > 0 ) {
593                        // if there is a ratio limit applied and the then set it to initial ratio
594                        startCoords.x1 = Math.ceil( ( this.imgW - this.options.ratioDim.x ) / 2 );
595                        startCoords.y1 = Math.ceil( ( this.imgH - this.options.ratioDim.y ) / 2 );
596                        startCoords.x2 = startCoords.x1 + this.options.ratioDim.x;
597                        startCoords.y2 = startCoords.y1 + this.options.ratioDim.y;
598                        validCoordsSet = true;
599                }
600               
601                this.setAreaCoords( startCoords, false, false, 1 );
602               
603                if( this.options.displayOnInit && validCoordsSet ) {
604                        this.selArea.show();
605                        this.drawArea();
606                        this.endCrop();
607                }
608               
609                this.attached = true;
610        },
611       
612        /**
613         * Removes the cropper
614         *
615         * @access public
616         * @return void
617         */
618        remove: function() {
619                if( this.attached ) {
620                        this.attached = false;
621                       
622                        // remove the elements we inserted
623                        this.imgWrap.parentNode.insertBefore( this.img, this.imgWrap );
624                        this.imgWrap.parentNode.removeChild( this.imgWrap );
625                       
626                        // remove the event observers
627                        Event.stopObserving( this.dragArea, 'mousedown', this.startDragBind );
628                        Event.stopObserving( document, 'mousemove', this.onDragBind );         
629                        Event.stopObserving( document, 'mouseup', this.endCropBind );
630                        this.registerHandles( false );
631                        if( this.options.captureKeys ) Event.stopObserving( document, 'keypress', this.keysBind );
632                }
633        },
634       
635        /**
636         * Resets the cropper, can be used either after being removed or any time you wish
637         *
638         * @access public
639         * @return void
640         */
641        reset: function() {
642                if( !this.attached ) this.onLoad();
643                else this.setParams();
644                this.endCrop();
645        },
646       
647        /**
648         * Handles the key functionality, currently just using arrow keys to move, if the user
649         * presses shift then the area will move by 10 pixels
650         */
651        handleKeys: function( e ) {
652                var dir = { x: 0, y: 0 }; // direction to move it in & the amount in pixels
653                if( !this.dragging ) {
654                       
655                        // catch the arrow keys
656                        switch( e.keyCode ) {
657                                case( 37 ) : // left
658                                        dir.x = -1;
659                                        break;
660                                case( 38 ) : // up
661                                        dir.y = -1;
662                                        break;
663                                case( 39 ) : // right
664                                        dir.x = 1;
665                                        break
666                                case( 40 ) : // down
667                                        dir.y = 1;
668                                        break;
669                        }
670                       
671                        if( dir.x != 0 || dir.y != 0 ) {
672                                // if shift is pressed then move by 10 pixels
673                                if( e.shiftKey ) {
674                                        dir.x *= 10;
675                                        dir.y *= 10;
676                                }
677                               
678                                this.moveArea( [ this.areaCoords.x1 + dir.x, this.areaCoords.y1 + dir.y ] );
679                                Event.stop( e ); 
680                        }
681                }
682        },
683       
684        /**
685         * Calculates the width from the areaCoords
686         *
687         * @access private
688         * @return int
689         */
690        calcW: function() {
691                return (this.areaCoords.x2 - this.areaCoords.x1)
692        },
693       
694        /**
695         * Calculates the height from the areaCoords
696         *
697         * @access private
698         * @return int
699         */
700        calcH: function() {
701                return (this.areaCoords.y2 - this.areaCoords.y1)
702        },
703       
704        /**
705         * Moves the select area to the supplied point (assumes the point is x1 & y1 of the select area)
706         *
707         * @access public
708         * @param array Point for x1 & y1 to move select area to
709         * @return void
710         */
711        moveArea: function( point ) {
712                // dump( 'moveArea        : ' + point[0] + ',' + point[1] + ',' + ( point[0] + ( this.areaCoords.x2 - this.areaCoords.x1 ) ) + ',' + ( point[1] + ( this.areaCoords.y2 - this.areaCoords.y1 ) ) + '\n' );
713                this.setAreaCoords( 
714                        {
715                                x1: point[0], 
716                                y1: point[1],
717                                x2: point[0] + this.calcW(),
718                                y2: point[1] + this.calcH()
719                        },
720                        true,
721                        false
722                );
723                this.drawArea();
724        },
725
726        /**
727         * Clones a co-ordinates object, stops problems with handling them by reference
728         *
729         * @access private
730         * @param obj Coordinate object x1, y1, x2, y2
731         * @return obj Coordinate object x1, y1, x2, y2
732         */
733        cloneCoords: function( coords ) {
734                return { x1: coords.x1, y1: coords.y1, x2: coords.x2, y2: coords.y2 };
735        },
736
737        /**
738         * Sets the select coords to those provided but ensures they don't go
739         * outside the bounding box
740         *
741         * @access private
742         * @param obj Coordinates x1, y1, x2, y2
743         * @param boolean Whether this is a move
744         * @param boolean Whether to apply squaring
745         * @param obj Direction of mouse along both axis x, y ( -1 = negative, 1 = positive ) only required when moving etc.
746         * @param string The current resize handle || null
747         * @return void
748         */
749        setAreaCoords: function( coords, moving, square, direction, resizeHandle ) {
750                // dump( 'setAreaCoords (in) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 );
751                if( moving ) {
752                        // if moving
753                        var targW = coords.x2 - coords.x1;
754                        var targH = coords.y2 - coords.y1;
755                       
756                        // ensure we're within the bounds
757                        if( coords.x1 < 0 ) {
758                                coords.x1 = 0;
759                                coords.x2 = targW;
760                        }
761                        if( coords.y1 < 0 ) {
762                                coords.y1 = 0;
763                                coords.y2 = targH;
764                        }
765                        if( coords.x2 > this.imgW ) {
766                                coords.x2 = this.imgW;
767                                coords.x1 = this.imgW - targW;
768                        }
769                        if( coords.y2 > this.imgH ) {
770                                coords.y2 = this.imgH;
771                                coords.y1 = this.imgH - targH;
772                        }                       
773                } else {
774                        // ensure we're within the bounds
775                        if( coords.x1 < 0 ) coords.x1 = 0;
776                        if( coords.y1 < 0 ) coords.y1 = 0;
777                        if( coords.x2 > this.imgW ) coords.x2 = this.imgW;
778                        if( coords.y2 > this.imgH ) coords.y2 = this.imgH;
779                       
780                        // This is passed as null in onload
781                        if( direction != null ) {
782                                                               
783                                // apply the ratio or squaring where appropriate
784                                if( this.ratioX > 0 ) this.applyRatio( coords, { x: this.ratioX, y: this.ratioY }, direction, resizeHandle );
785                                else if( square ) this.applyRatio( coords, { x: 1, y: 1 }, direction, resizeHandle );
786                                                                               
787                                var mins = [ this.options.minWidth, this.options.minHeight ]; // minimum dimensions [x,y]                       
788                                var maxs = [ this.options.maxWidth, this.options.maxHeight ]; // maximum dimensions [x,y]
789               
790                                // apply dimensions where appropriate
791                                if( mins[0] > 0 || mins[1] > 0 || maxs[0] > 0 || maxs[1] > 0) {
792                               
793                                        var coordsTransX        = { a1: coords.x1, a2: coords.x2 };
794                                        var coordsTransY        = { a1: coords.y1, a2: coords.y2 };
795                                        var boundsX                     = { min: 0, max: this.imgW };
796                                        var boundsY                     = { min: 0, max: this.imgH };
797                                       
798                                        // handle squaring properly on single axis minimum dimensions
799                                        if( (mins[0] != 0 || mins[1] != 0) && square ) {
800                                                if( mins[0] > 0 ) mins[1] = mins[0];
801                                                else if( mins[1] > 0 ) mins[0] = mins[1];
802                                        }
803                                       
804                                        if( (maxs[0] != 0 || maxs[0] != 0) && square ) {
805                                                // 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)
806                                                if( maxs[0] > 0 && maxs[0] <= maxs[1] ) maxs[1] = maxs[0];
807                                                else if( maxs[1] > 0 && maxs[1] <= maxs[0] ) maxs[0] = maxs[1];
808                                        }
809                                       
810                                        if( mins[0] > 0 ) this.applyDimRestriction( coordsTransX, mins[0], direction.x, boundsX, 'min' );
811                                        if( mins[1] > 1 ) this.applyDimRestriction( coordsTransY, mins[1], direction.y, boundsY, 'min' );
812                                       
813                                        if( maxs[0] > 0 ) this.applyDimRestriction( coordsTransX, maxs[0], direction.x, boundsX, 'max' );
814                                        if( maxs[1] > 1 ) this.applyDimRestriction( coordsTransY, maxs[1], direction.y, boundsY, 'max' );
815                                       
816                                        coords = { x1: coordsTransX.a1, y1: coordsTransY.a1, x2: coordsTransX.a2, y2: coordsTransY.a2 };
817                                }
818                               
819                        }
820                }
821               
822                // dump( 'setAreaCoords (out) : ' + coords.x1 + ',' + coords.y1 + ',' + coords.x2 + ',' + coords.y2 + '\n' );
823                this.areaCoords = coords;
824        },
825       
826        /**
827         * Applies the supplied dimension restriction to the supplied coordinates along a single axis
828         *
829         * @access private
830         * @param obj Single axis coordinates, a1, a2 (e.g. for the x axis a1 = x1 & a2 = x2)
831         * @param int The restriction value
832         * @param int The direction ( -1 = negative, 1 = positive )
833         * @param obj The bounds of the image ( for this axis )
834         * @param string The dimension restriction type ( 'min' | 'max' )
835         * @return void
836         */
837        applyDimRestriction: function( coords, val, direction, bounds, type ) {
838                var check;
839                if( type == 'min' ) check = ( ( coords.a2 - coords.a1 ) < val );
840                else check = ( ( coords.a2 - coords.a1 ) > val );
841                if( check ) {
842                        if( direction == 1 ) coords.a2 = coords.a1 + val;
843                        else coords.a1 = coords.a2 - val;
844                       
845                        // make sure we're still in the bounds (not too pretty for the user, but needed)
846                        if( coords.a1 < bounds.min ) {
847                                coords.a1 = bounds.min;
848                                coords.a2 = val;
849                        } else if( coords.a2 > bounds.max ) {
850                                coords.a1 = bounds.max - val;
851                                coords.a2 = bounds.max;
852                        }
853                }
854        },
855               
856        /**
857         * Applies the supplied ratio to the supplied coordinates
858         *
859         * @access private
860         * @param obj Coordinates, x1, y1, x2, y2
861         * @param obj Ratio, x, y
862         * @param obj Direction of mouse, x & y : -1 == negative 1 == positive
863         * @param string The current resize handle || null
864         * @return void
865         */
866        applyRatio : function( coords, ratio, direction, resizeHandle ) {
867                // dump( 'direction.y : ' + direction.y + '\n');
868                var newCoords;
869                if( resizeHandle == 'N' || resizeHandle == 'S' ) {
870                        // dump( 'north south \n');
871                        // if moving on either the lone north & south handles apply the ratio on the y axis
872                        newCoords = this.applyRatioToAxis( 
873                                { a1: coords.y1, b1: coords.x1, a2: coords.y2, b2: coords.x2 },
874                                { a: ratio.y, b: ratio.x },
875                                { a: direction.y, b: direction.x },
876                                { min: 0, max: this.imgW }
877                        );
878                        coords.x1 = newCoords.b1;
879                        coords.y1 = newCoords.a1;
880                        coords.x2 = newCoords.b2;
881                        coords.y2 = newCoords.a2;
882                } else {
883                        // otherwise deal with it as if we're applying the ratio on the x axis
884                        newCoords = this.applyRatioToAxis( 
885                                { a1: coords.x1, b1: coords.y1, a2: coords.x2, b2: coords.y2 },
886                                { a: ratio.x, b: ratio.y },
887                                { a: direction.x, b: direction.y },
888                                { min: 0, max: this.imgH }
889                        );
890                        coords.x1 = newCoords.a1;
891                        coords.y1 = newCoords.b1;
892                        coords.x2 = newCoords.a2;
893                        coords.y2 = newCoords.b2;
894                }
895               
896        },
897       
898        /**
899         * Applies the provided ratio to the provided coordinates based on provided direction & bounds,
900         * use to encapsulate functionality to make it easy to apply to either axis. This is probably
901         * quite hard to visualise so see the x axis example within applyRatio()
902         *
903         * Example in parameter details & comments is for requesting applying ratio to x axis.
904         *
905         * @access private
906         * @param obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
907         * @param obj Ratio object (a, b) where a = x & b = y in example
908         * @param obj Direction object (a, b) where a = x & b = y in example
909         * @param obj Bounds (min, max)
910         * @return obj Coords object (a1, b1, a2, b2) where a = x & b = y in example
911         */
912        applyRatioToAxis: function( coords, ratio, direction, bounds ) {
913                var newCoords = Object.extend( coords, {} );
914                var calcDimA = newCoords.a2 - newCoords.a1;                     // calculate dimension a (e.g. width)
915                var targDimB = Math.floor( calcDimA * ratio.b / ratio.a );      // the target dimension b (e.g. height)
916                var targB;                                                                                      // to hold target b (e.g. y value)
917                var targDimA;                                           // to hold target dimension a (e.g. width)
918                var calcDimB = null;                                                            // to hold calculated dimension b (e.g. height)
919               
920                // dump( 'newCoords[0]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
921                               
922                if( direction.b == 1 ) {                                                        // if travelling in a positive direction
923                        // make sure we're not going out of bounds
924                        targB = newCoords.b1 + targDimB;
925                        if( targB > bounds.max ) {
926                                targB = bounds.max;
927                                calcDimB = targB - newCoords.b1;                        // calcuate dimension b (e.g. height)
928                        }
929                       
930                        newCoords.b2 = targB;
931                } else {                                                                                        // if travelling in a negative direction
932                        // make sure we're not going out of bounds
933                        targB = newCoords.b2 - targDimB;
934                        if( targB < bounds.min ) {
935                                targB = bounds.min;
936                                calcDimB = targB + newCoords.b2;                        // calcuate dimension b (e.g. height)
937                        }
938                        newCoords.b1 = targB;
939                }
940               
941                // dump( 'newCoords[1]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
942                       
943                // apply the calculated dimensions
944                if( calcDimB != null ) {
945                        targDimA = Math.floor( calcDimB * ratio.a / ratio.b );
946                       
947                        if( direction.a == 1 ) newCoords.a2 = newCoords.a1 + targDimA;
948                        else newCoords.a1 = newCoords.a1 = newCoords.a2 - targDimA;
949                }
950               
951                // dump( 'newCoords[2]: ' + newCoords.a1 + ',' + newCoords.b1 + ','+ newCoords.a2 + ',' + newCoords.b2 + '\n');
952                       
953                return newCoords;
954        },
955       
956        /**
957         * Draws the select area
958         *
959         * @access private
960         * @return void
961         */
962        drawArea: function( ) { 
963                /*
964                 * NOTE: I'm not using the Element.setStyle() shortcut as they make it
965                 * quite sluggish on Mac based browsers
966                 */
967                // dump( 'drawArea        : ' + this.areaCoords.x1 + ',' + this.areaCoords.y1 + ',' + this.areaCoords.x2 + ',' + this.areaCoords.y2 + '\n' );
968                var areaWidth     = this.calcW();
969                var areaHeight    = this.calcH();
970               
971                /*
972                 * Calculate all the style strings before we use them, allows reuse & produces quicker
973                 * rendering (especially noticable in Mac based browsers)
974                 */
975                var px = 'px';
976                var params = [
977                        this.areaCoords.x1 + px,        // the left of the selArea
978                        this.areaCoords.y1 + px,                // the top of the selArea
979                        areaWidth + px,                                 // width of the selArea
980                        areaHeight + px,                                        // height of the selArea
981                        this.areaCoords.x2 + px,                // bottom of the selArea
982                        this.areaCoords.y2 + px,                // right of the selArea
983                        (this.img.width - this.areaCoords.x2) + px,     // right edge of selArea
984                        (this.img.height - this.areaCoords.y2) + px     // bottom edge of selArea
985                ];
986                               
987                // do the select area
988                var areaStyle                           = this.selArea.style;
989                areaStyle.left                          = params[0];
990                areaStyle.top                           = params[1];
991                areaStyle.width                         = params[2];
992                areaStyle.height                        = params[3];
993                               
994                // position the north, east, south & west handles
995                var horizHandlePos = Math.ceil( (areaWidth - 6) / 2 ) + px;
996                var vertHandlePos = Math.ceil( (areaHeight - 6) / 2 ) + px;
997               
998                this.handleN.style.left         = horizHandlePos;
999                this.handleE.style.top          = vertHandlePos;
1000                this.handleS.style.left         = horizHandlePos;
1001                this.handleW.style.top          = vertHandlePos;
1002               
1003                // draw the four overlays
1004                this.north.style.height         = params[1];
1005               
1006                var eastStyle                           = this.east.style;
1007                eastStyle.top                           = params[1];
1008                eastStyle.height                        = params[3];
1009                eastStyle.left                          = params[4];
1010            eastStyle.width                             = params[6];
1011           
1012                var southStyle                          = this.south.style;
1013                southStyle.top                          = params[5];
1014                southStyle.height                       = params[7];
1015           
1016            var westStyle                       = this.west.style;
1017            westStyle.top                               = params[1];
1018            westStyle.height                    = params[3];
1019                westStyle.width                         = params[0];
1020               
1021                // call the draw method on sub classes
1022                this.subDrawArea();
1023               
1024                this.forceReRender();
1025        },
1026       
1027        /**
1028         * Force the re-rendering of the selArea element which fixes rendering issues in Safari
1029         * & IE PC, especially evident when re-sizing perfectly vertical using any of the south handles
1030         *
1031         * @access private
1032         * @return void
1033         */
1034        forceReRender: function() {
1035                if( this.isIE || this.isWebKit) {
1036                        var n = document.createTextNode(' ');
1037                        var d,el,fixEL,i;
1038               
1039                        if( this.isIE ) fixEl = this.selArea;
1040                        else if( this.isWebKit ) {
1041                                fixEl = document.getElementsByClassName( 'imgCrop_marqueeSouth', this.imgWrap )[0];
1042                                /* we have to be a bit more forceful for Safari, otherwise the the marquee &
1043                                 * the south handles still don't move
1044                                 */ 
1045                                d = Builder.node( 'div', '' );
1046                                d.style.visibility = 'hidden';
1047                               
1048                                var classList = ['SE','S','SW'];
1049                                for( i = 0; i < classList.length; i++ ) {
1050                                        el = document.getElementsByClassName( 'imgCrop_handle' + classList[i], this.selArea )[0];
1051                                        if( el.childNodes.length ) el.removeChild( el.childNodes[0] );
1052                                        el.appendChild(d);
1053                                }
1054                        }
1055                        fixEl.appendChild(n);
1056                        fixEl.removeChild(n);
1057                }
1058        },
1059       
1060        /**
1061         * Starts the resize
1062         *
1063         * @access private
1064         * @param obj Event
1065         * @return void
1066         */
1067        startResize: function( e ) {
1068                this.startCoords = this.cloneCoords( this.areaCoords );
1069               
1070                this.resizing = true;
1071                this.resizeHandle = Event.element( e ).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/, '');
1072                // dump( 'this.resizeHandle : ' + this.resizeHandle + '\n' );
1073                Event.stop( e );
1074        },
1075       
1076        /**
1077         * Starts the drag
1078         *
1079         * @access private
1080         * @param obj Event
1081         * @return void
1082         */
1083        startDrag: function( e ) {     
1084                this.selArea.show();
1085                this.clickCoords = this.getCurPos( e );
1086       
1087        this.setAreaCoords( { x1: this.clickCoords.x, y1: this.clickCoords.y, x2: this.clickCoords.x, y2: this.clickCoords.y }, false, false, null );
1088       
1089        this.dragging = true;
1090        this.onDrag( e ); // incase the user just clicks once after already making a selection
1091        Event.stop( e );
1092        },
1093       
1094        /**
1095         * Gets the current cursor position relative to the image
1096         *
1097         * @access private
1098         * @param obj Event
1099         * @return obj x,y pixels of the cursor
1100         */
1101        getCurPos: function( e ) {
1102                // get the offsets for the wrapper within the document
1103                var el = this.imgWrap, wrapOffsets = Position.cumulativeOffset( el );
1104                // 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
1105                while( el.nodeName != 'BODY' ) {
1106                        wrapOffsets[1] -= el.scrollTop  || 0;
1107                        wrapOffsets[0] -= el.scrollLeft || 0;
1108                        el = el.parentNode;
1109            }           
1110                return curPos = { 
1111                        x: Event.pointerX(e) - wrapOffsets[0],
1112                        y: Event.pointerY(e) - wrapOffsets[1]
1113                }
1114        },
1115       
1116        /**
1117         * Performs the drag for both resize & inital draw dragging
1118         *
1119         * @access private
1120         * @param obj Event
1121         * @return void
1122         */
1123        onDrag: function( e ) {
1124                if( this.dragging || this.resizing ) { 
1125               
1126                        var resizeHandle = null;
1127                        var curPos = this.getCurPos( e );                       
1128                        var newCoords = this.cloneCoords( this.areaCoords );
1129                        var direction = { x: 1, y: 1 };
1130                                               
1131                    if( this.dragging ) {
1132                        if( curPos.x < this.clickCoords.x ) direction.x = -1;
1133                        if( curPos.y < this.clickCoords.y ) direction.y = -1;
1134                       
1135                                this.transformCoords( curPos.x, this.clickCoords.x, newCoords, 'x' );
1136                                this.transformCoords( curPos.y, this.clickCoords.y, newCoords, 'y' );
1137                        } else if( this.resizing ) {
1138                                resizeHandle = this.resizeHandle;                       
1139                                // do x movements first
1140                                if( resizeHandle.match(/E/) ) {
1141                                        // if we're moving an east handle
1142                                        this.transformCoords( curPos.x, this.startCoords.x1, newCoords, 'x' ); 
1143                                        if( curPos.x < this.startCoords.x1 ) direction.x = -1;
1144                                } else if( resizeHandle.match(/W/) ) {
1145                                        // if we're moving an west handle
1146                                        this.transformCoords( curPos.x, this.startCoords.x2, newCoords, 'x' );
1147                                        if( curPos.x < this.startCoords.x2 ) direction.x = -1;
1148                                }
1149                                                                       
1150                                // do y movements second
1151                                if( resizeHandle.match(/N/) ) {
1152                                        // if we're moving an north handle     
1153                                        this.transformCoords( curPos.y, this.startCoords.y2, newCoords, 'y' );
1154                                        if( curPos.y < this.startCoords.y2 ) direction.y = -1;
1155                                } else if( resizeHandle.match(/S/) ) {
1156                                        // if we're moving an south handle
1157                                        this.transformCoords( curPos.y, this.startCoords.y1, newCoords, 'y' ); 
1158                                        if( curPos.y < this.startCoords.y1 ) direction.y = -1;
1159                                }       
1160                                                       
1161                        }
1162               
1163                        this.setAreaCoords( newCoords, false, e.shiftKey, direction, resizeHandle );
1164                        this.drawArea();
1165                        Event.stop( e ); // stop the default event (selecting images & text) in Safari & IE PC
1166                }
1167        },
1168       
1169        /**
1170         * Applies the appropriate transform to supplied co-ordinates, on the
1171         * defined axis, depending on the relationship of the supplied values
1172         *
1173         * @access private
1174         * @param int Current value of pointer
1175         * @param int Base value to compare current pointer val to
1176         * @param obj Coordinates to apply transformation on x1, x2, y1, y2
1177         * @param string Axis to apply transformation on 'x' || 'y'
1178         * @return void
1179         */
1180        transformCoords : function( curVal, baseVal, coords, axis ) {
1181                var newVals = [ curVal, baseVal ];
1182                if( curVal > baseVal ) newVals.reverse();
1183                coords[ axis + '1' ] = newVals[0];
1184                coords[ axis + '2' ] = newVals[1];             
1185        },
1186       
1187        /**
1188         * Ends the crop & passes the values of the select area on to the appropriate
1189         * callback function on completion of a crop
1190         *
1191         * @access private
1192         * @return void
1193         */
1194        endCrop : function() {
1195                this.dragging = false;
1196                this.resizing = false;
1197               
1198                this.options.onEndCrop(
1199                        this.areaCoords,
1200                        {
1201                                width: this.calcW(), 
1202                                height: this.calcH() 
1203                        }
1204                );
1205        },
1206       
1207        /**
1208         * Abstract method called on the end of initialization
1209         *
1210         * @access private
1211         * @abstract
1212         * @return void
1213         */
1214        subInitialize: function() {},
1215       
1216        /**
1217         * Abstract method called on the end of drawArea()
1218         *
1219         * @access private
1220         * @abstract
1221         * @return void
1222         */
1223        subDrawArea: function() {}
1224};
1225
1226
1227
1228
1229/**
1230 * Extend the Cropper.Img class to allow for presentation of a preview image of the resulting crop,
1231 * the option for displayOnInit is always overridden to true when displaying a preview image
1232 *
1233 * Usage:
1234 *      @param obj Image element to attach to
1235 *      @param obj Optional options:
1236 *              - see Cropper.Img for base options
1237 *             
1238 *              - previewWrap obj
1239 *                      HTML element that will be used as a container for the preview image             
1240 */
1241Cropper.ImgWithPreview = Class.create();
1242
1243Object.extend( Object.extend( Cropper.ImgWithPreview.prototype, Cropper.Img.prototype ), {
1244       
1245        /**
1246         * Implements the abstract method from Cropper.Img to initialize preview image settings.
1247         * Will only attach a preview image is the previewWrap element is defined and the minWidth
1248         * & minHeight options are set.
1249         *
1250         * @see Croper.Img.subInitialize
1251         */
1252        subInitialize: function() {
1253                /**
1254                 * Whether or not we've attached a preview image
1255                 * @var boolean
1256                 */
1257                this.hasPreviewImg = false;
1258                if( typeof(this.options.previewWrap) != 'undefined' 
1259                        && this.options.minWidth > 0 
1260                        && this.options.minHeight > 0
1261                ) {
1262                        /**
1263                         * The preview image wrapper element
1264                         * @var obj HTML element
1265                         */
1266                        this.previewWrap        = $( this.options.previewWrap );
1267                        /**
1268                         * The preview image element
1269                         * @var obj HTML IMG element
1270                         */
1271                        this.previewImg         = this.img.cloneNode( false );
1272                        // set the ID of the preview image to be unique
1273                        this.previewImg.id      = 'imgCrop_' + this.previewImg.id;
1274                       
1275                                               
1276                        // set the displayOnInit option to true so we display the select area at the same time as the thumbnail
1277                        this.options.displayOnInit = true;
1278
1279                        this.hasPreviewImg      = true;
1280                       
1281                        this.previewWrap.addClassName( 'imgCrop_previewWrap' );
1282                       
1283                        this.previewWrap.setStyle(
1284                         { 
1285                                width: this.options.minWidth + 'px',
1286                                height: this.options.minHeight + 'px'
1287                         }
1288                        );
1289                       
1290                        this.previewWrap.appendChild( this.previewImg );
1291                }
1292        },
1293       
1294        /**
1295         * Implements the abstract method from Cropper.Img to draw the preview image
1296         *
1297         * @see Croper.Img.subDrawArea
1298         */
1299        subDrawArea: function() {
1300                if( this.hasPreviewImg ) {
1301                        // get the ratio of the select area to the src image
1302                        var calcWidth = this.calcW();
1303                        var calcHeight = this.calcH();
1304                        // ratios for the dimensions of the preview image
1305                        var dimRatio = { 
1306                                x: this.imgW / calcWidth, 
1307                                y: this.imgH / calcHeight 
1308                        }; 
1309                        //ratios for the positions within the preview
1310                        var posRatio = { 
1311                                x: calcWidth / this.options.minWidth, 
1312                                y: calcHeight / this.options.minHeight 
1313                        };
1314                       
1315                        // setting the positions in an obj before apply styles for rendering speed increase
1316                        var calcPos     = {
1317                                w: Math.ceil( this.options.minWidth * dimRatio.x ) + 'px',
1318                                h: Math.ceil( this.options.minHeight * dimRatio.y ) + 'px',
1319                                x: '-' + Math.ceil( this.areaCoords.x1 / posRatio.x )  + 'px',
1320                                y: '-' + Math.ceil( this.areaCoords.y1 / posRatio.y ) + 'px'
1321                        }
1322                       
1323                        var previewStyle        = this.previewImg.style;
1324                        previewStyle.width      = calcPos.w;
1325                        previewStyle.height     = calcPos.h;
1326                        previewStyle.left       = calcPos.x;
1327                        previewStyle.top        = calcPos.y;
1328                }
1329        }
1330       
1331});
Note: See TracBrowser for help on using the browser.