/dev/trunk

To get this branch, use:
bzr branch http://darksoft.org/webbzr/dev/trunk

« back to all changes in this revision

Viewing changes to includes/cropper.1.2/cropper.js

  • Committer: Suren A. Chilingaryan
  • Date: 2008-04-02 10:23:22 UTC
  • Revision ID: csa@dside.dyndns.org-20080402102322-okib92sicg2dx3o3
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 */
 
122
var CropDraggable = Class.create();
 
123
 
 
124
Object.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
 */
 
224
var Cropper = {};
 
225
Cropper.Img = Class.create();
 
226
Cropper.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
 */
 
1241
Cropper.ImgWithPreview = Class.create();
 
1242
 
 
1243
Object.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
});