diff -r 2f6f6f7551ca -r 32102edaa81b web/wp-includes/js/imgareaselect/jquery.imgareaselect.dev.js --- a/web/wp-includes/js/imgareaselect/jquery.imgareaselect.dev.js Thu Sep 16 15:45:36 2010 +0000 +++ b/web/wp-includes/js/imgareaselect/jquery.imgareaselect.dev.js Mon Nov 19 18:26:13 2012 +0100 @@ -1,8 +1,8 @@ /* * imgAreaSelect jQuery plugin - * version 0.9.1 + * version 0.9.9 * - * Copyright (c) 2008-2009 Michal Wojciechowski (odyniec.net) + * Copyright (c) 2008-2011 Michal Wojciechowski (odyniec.net) * * Dual licensed under the MIT (MIT-LICENSE.txt) * and GPL (GPL-LICENSE.txt) licenses. @@ -13,86 +13,201 @@ (function($) { +/* + * Math functions will be used extensively, so it's convenient to make a few + * shortcuts + */ var abs = Math.abs, max = Math.max, min = Math.min, round = Math.round; +/** + * Create a new HTML div element + * + * @return A jQuery object representing the new element + */ function div() { return $('
'); } +/** + * imgAreaSelect initialization + * + * @param img + * A HTML image element to attach the plugin to + * @param options + * An options object + */ $.imgAreaSelect = function (img, options) { var - + /* jQuery object representing the image */ $img = $(img), + /* Has the image finished loading? */ imgLoaded, + /* Plugin elements */ + + /* Container box */ $box = div(), + /* Selection area */ $area = div(), + /* Border (four divs) */ $border = div().add(div()).add(div()).add(div()), + /* Outer area (four divs) */ $outer = div().add(div()).add(div()).add(div()), + /* Handles (empty by default, initialized in setOptions()) */ $handles = $([]), + /* + * Additional element to work around a cursor problem in Opera + * (explained later) + */ $areaOpera, + /* Image position (relative to viewport) */ left, top, - imgOfs, + /* Image offset (as returned by .offset()) */ + imgOfs = { left: 0, top: 0 }, + /* Image dimensions (as returned by .width() and .height()) */ imgWidth, imgHeight, + /* + * jQuery object representing the parent element that the plugin + * elements are appended to + */ $parent, - parOfs, + /* Parent element offset (as returned by .offset()) */ + parOfs = { left: 0, top: 0 }, + /* Base z-index for plugin elements */ zIndex = 0, + /* Plugin elements position */ position = 'absolute', + /* X/Y coordinates of the starting point for move/resize operations */ startX, startY, + /* Horizontal and vertical scaling factors */ scaleX, scaleY, - resizeMargin = 10, - + /* Current resize mode ("nw", "se", etc.) */ resize, + /* Selection area constraints */ + minWidth, minHeight, maxWidth, maxHeight, + + /* Aspect ratio to maintain (floating point number) */ aspectRatio, + /* Are the plugin elements currently displayed? */ shown, + /* Current selection (relative to parent element) */ x1, y1, x2, y2, + /* Current selection (relative to scaled image) */ selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 }, + /* Document element */ + docElem = document.documentElement, + + /* Various helper variables used throughout the code */ $p, d, i, o, w, h, adjusted; + /* + * Translate selection coordinates (relative to scaled image) to viewport + * coordinates (relative to parent element) + */ + + /** + * Translate selection X to viewport X + * + * @param x + * Selection X + * @return Viewport X + */ function viewX(x) { return x + imgOfs.left - parOfs.left; } + /** + * Translate selection Y to viewport Y + * + * @param y + * Selection Y + * @return Viewport Y + */ function viewY(y) { return y + imgOfs.top - parOfs.top; } + /* + * Translate viewport coordinates to selection coordinates + */ + + /** + * Translate viewport X to selection X + * + * @param x + * Viewport X + * @return Selection X + */ function selX(x) { return x - imgOfs.left + parOfs.left; } + /** + * Translate viewport Y to selection Y + * + * @param y + * Viewport Y + * @return Selection Y + */ function selY(y) { return y - imgOfs.top + parOfs.top; } + /* + * Translate event coordinates (relative to document) to viewport + * coordinates + */ + + /** + * Get event X and translate it to viewport X + * + * @param event + * The event object + * @return Viewport X + */ function evX(event) { return event.pageX - parOfs.left; } + /** + * Get event Y and translate it to viewport Y + * + * @param event + * The event object + * @return Viewport Y + */ function evY(event) { return event.pageY - parOfs.top; } + /** + * Get the current selection + * + * @param noScale + * If set to true, scaling is not applied to the + * returned selection + * @return Selection object + */ function getSelection(noScale) { var sx = noScale || scaleX, sy = noScale || scaleY; @@ -104,36 +219,79 @@ height: round(selection.y2 * sy) - round(selection.y1 * sy) }; } + /** + * Set the current selection + * + * @param x1 + * X coordinate of the upper left corner of the selection area + * @param y1 + * Y coordinate of the upper left corner of the selection area + * @param x2 + * X coordinate of the lower right corner of the selection area + * @param y2 + * Y coordinate of the lower right corner of the selection area + * @param noScale + * If set to true, scaling is not applied to the + * new selection + */ function setSelection(x1, y1, x2, y2, noScale) { var sx = noScale || scaleX, sy = noScale || scaleY; selection = { - x1: round(x1 / sx), - y1: round(y1 / sy), - x2: round(x2 / sx), - y2: round(y2 / sy) + x1: round(x1 / sx || 0), + y1: round(y1 / sy || 0), + x2: round(x2 / sx || 0), + y2: round(y2 / sy || 0) }; - selection.width = (x2 = viewX(selection.x2)) - (x1 = viewX(selection.x1)); - selection.height = (y2 = viewX(selection.y2)) - (y1 = viewX(selection.y1)); + selection.width = selection.x2 - selection.x1; + selection.height = selection.y2 - selection.y1; } + /** + * Recalculate image and parent offsets + */ function adjust() { + /* + * Do not adjust if image width is not a positive number. This might + * happen when imgAreaSelect is put on a parent element which is then + * hidden. + */ if (!$img.width()) return; + /* + * Get image offset. The .offset() method returns float values, so they + * need to be rounded. + */ imgOfs = { left: round($img.offset().left), top: round($img.offset().top) }; - imgWidth = $img.width(); - imgHeight = $img.height(); + /* Get image dimensions */ + imgWidth = $img.innerWidth(); + imgHeight = $img.innerHeight(); + + imgOfs.top += ($img.outerHeight() - imgHeight) >> 1; + imgOfs.left += ($img.outerWidth() - imgWidth) >> 1; - if ($().jquery == '1.3.2' && $.browser.safari && position == 'fixed') { - imgOfs.top += max(document.documentElement.scrollTop, $('body').scrollTop()); + /* Set minimum and maximum selection area dimensions */ + minWidth = round(options.minWidth / scaleX) || 0; + minHeight = round(options.minHeight / scaleY) || 0; + maxWidth = round(min(options.maxWidth / scaleX || 1<<24, imgWidth)); + maxHeight = round(min(options.maxHeight / scaleY || 1<<24, imgHeight)); - imgOfs.left += max(document.documentElement.scrollLeft, $('body').scrollLeft()); + /* + * Workaround for jQuery 1.3.2 incorrect offset calculation, originally + * observed in Safari 3. Firefox 2 is also affected. + */ + if ($().jquery == '1.3.2' && position == 'fixed' && + !docElem['getBoundingClientRect']) + { + imgOfs.top += max(document.body.scrollTop, docElem.scrollTop); + imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft); } - parOfs = $.inArray($parent.css('position'), ['absolute', 'relative']) + 1 ? + /* Determine parent element offset */ + parOfs = /absolute|relative/.test($parent.css('position')) ? { left: round($parent.offset().left) - $parent.scrollLeft(), top: round($parent.offset().top) - $parent.scrollTop() } : position == 'fixed' ? @@ -142,20 +300,45 @@ left = viewX(0); top = viewY(0); + + /* + * Check if selection area is within image boundaries, adjust if + * necessary + */ + if (selection.x2 > imgWidth || selection.y2 > imgHeight) + doResize(); } + /** + * Update plugin elements + * + * @param resetKeyPress + * If set to false, this instance's keypress + * event handler is not activated + */ function update(resetKeyPress) { + /* If plugin elements are hidden, do nothing */ if (!shown) return; + /* + * Set the position and size of the container box and the selection area + * inside it + */ $box.css({ left: viewX(selection.x1), top: viewY(selection.y1) }) .add($area).width(w = selection.width).height(h = selection.height); + /* + * Reset the position of selection area, borders, and handles (IE6/IE7 + * position them incorrectly if we don't do this) + */ $area.add($border).add($handles).css({ left: 0, top: 0 }); + /* Set border dimensions */ $border .width(max(w - $border.outerWidth() + $border.innerWidth(), 0)) .height(max(h - $border.outerHeight() + $border.innerHeight(), 0)); + /* Arrange the outer area elements */ $($outer[0]).css({ left: left, top: top, width: selection.x1, height: imgHeight }); $($outer[1]).css({ left: left + selection.x1, top: top, @@ -168,33 +351,62 @@ w -= $handles.outerWidth(); h -= $handles.outerHeight(); + /* Arrange handles */ switch ($handles.length) { case 8: - $($handles[4]).css({ left: w / 2 }); - $($handles[5]).css({ left: w, top: h / 2 }); - $($handles[6]).css({ left: w / 2, top: h }); - $($handles[7]).css({ top: h / 2 }); + $($handles[4]).css({ left: w >> 1 }); + $($handles[5]).css({ left: w, top: h >> 1 }); + $($handles[6]).css({ left: w >> 1, top: h }); + $($handles[7]).css({ top: h >> 1 }); case 4: $handles.slice(1,3).css({ left: w }); $handles.slice(2,4).css({ top: h }); } if (resetKeyPress !== false) { + /* + * Need to reset the document keypress event handler -- unbind the + * current handler + */ if ($.imgAreaSelect.keyPress != docKeyPress) $(document).unbind($.imgAreaSelect.keyPress, $.imgAreaSelect.onKeyPress); if (options.keys) + /* + * Set the document keypress event handler to this instance's + * docKeyPress() function + */ $(document)[$.imgAreaSelect.keyPress]( $.imgAreaSelect.onKeyPress = docKeyPress); } + /* + * Internet Explorer displays 1px-wide dashed borders incorrectly by + * filling the spaces between dashes with white. Toggling the margin + * property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still + * broken). This workaround is not perfect, as it requires setTimeout() + * and thus causes the border to flicker a bit, but I haven't found a + * better solution. + * + * Note: This only happens with CSS borders, set with the borderWidth, + * borderOpacity, borderColor1, and borderColor2 options (which are now + * deprecated). Borders created with GIF background images are fine. + */ if ($.browser.msie && $border.outerWidth() - $border.innerWidth() == 2) { $border.css('margin', 0); setTimeout(function () { $border.css('margin', 'auto'); }, 0); } } + /** + * Do the complete update sequence: recalculate offsets, update the + * elements, and set the correct values of x1, y1, x2, and y2. + * + * @param resetKeyPress + * If set to false, this instance's keypress + * event handler is not activated + */ function doUpdate(resetKeyPress) { adjust(); update(resetKeyPress); @@ -202,11 +414,24 @@ x2 = viewX(selection.x2); y2 = viewY(selection.y2); } + /** + * Hide or fade out an element (or multiple elements) + * + * @param $elem + * A jQuery object containing the element(s) to hide/fade out + * @param fn + * Callback function to be called when fadeOut() completes + */ function hide($elem, fn) { options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide(); - } + /** + * Selection area mousemove event handler + * + * @param event + * The event object + */ function areaMouseMove(event) { var x = selX(evX(event)) - selection.x1, y = selY(evY(event)) - selection.y1; @@ -218,16 +443,21 @@ $box.one('mouseout', function () { adjusted = false; }); } + /* Clear the resize mode */ resize = ''; if (options.resizable) { - if (y <= resizeMargin) + /* + * Check if the mouse pointer is over the resize margin area and set + * the resize mode accordingly + */ + if (y <= options.resizeMargin) resize = 'n'; - else if (y >= selection.height - resizeMargin) + else if (y >= selection.height - options.resizeMargin) resize = 's'; - if (x <= resizeMargin) + if (x <= options.resizeMargin) resize += 'w'; - else if (x >= selection.width - resizeMargin) + else if (x >= selection.width - options.resizeMargin) resize += 'e'; } @@ -237,24 +467,42 @@ $areaOpera.toggle(); } + /** + * Document mouseup event handler + * + * @param event + * The event object + */ function docMouseUp(event) { + /* Set back the default cursor */ $('body').css('cursor', ''); - + /* + * If autoHide is enabled, or if the selection has zero width/height, + * hide the selection and the outer area + */ if (options.autoHide || selection.width * selection.height == 0) hide($box.add($outer), function () { $(this).hide(); }); - options.onSelectEnd(img, getSelection()); - $(document).unbind('mousemove', selectingMouseMove); $box.mousemove(areaMouseMove); + + options.onSelectEnd(img, getSelection()); } + /** + * Selection area mousedown event handler + * + * @param event + * The event object + * @return false + */ function areaMouseDown(event) { if (event.which != 1) return false; adjust(); if (resize) { + /* Resize mode is in effect */ $('body').css('cursor', resize + '-resize'); x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']); @@ -284,59 +532,79 @@ return false; } - function aspectRatioXY() { - x2 = max(left, min(left + imgWidth, - x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))); - - y2 = round(max(top, min(top + imgHeight, - y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)))); - x2 = round(x2); - } - - function aspectRatioYX() { - y2 = max(top, min(top + imgHeight, - y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))); - x2 = round(max(left, min(left + imgWidth, - x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)))); - y2 = round(y2); + /** + * Adjust the x2/y2 coordinates to maintain aspect ratio (if defined) + * + * @param xFirst + * If set to true, calculate x2 first. Otherwise, + * calculate y2 first. + */ + function fixAspectRatio(xFirst) { + if (aspectRatio) + if (xFirst) { + x2 = max(left, min(left + imgWidth, + x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))); + y2 = round(max(top, min(top + imgHeight, + y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)))); + x2 = round(x2); + } + else { + y2 = max(top, min(top + imgHeight, + y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))); + x2 = round(max(left, min(left + imgWidth, + x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)))); + y2 = round(y2); + } } + /** + * Resize the selection area respecting the minimum/maximum dimensions and + * aspect ratio + */ function doResize() { - if (abs(x2 - x1) < options.minWidth) { - x2 = x1 - options.minWidth * (x2 < x1 || -1); + /* + * Make sure the top left corner of the selection area stays within + * image boundaries (it might not if the image source was dynamically + * changed). + */ + x1 = min(x1, left + imgWidth); + y1 = min(y1, top + imgHeight); + + if (abs(x2 - x1) < minWidth) { + /* Selection width is smaller than minWidth */ + x2 = x1 - minWidth * (x2 < x1 || -1); if (x2 < left) - x1 = left + options.minWidth; + x1 = left + minWidth; else if (x2 > left + imgWidth) - x1 = left + imgWidth - options.minWidth; + x1 = left + imgWidth - minWidth; } - if (abs(y2 - y1) < options.minHeight) { - y2 = y1 - options.minHeight * (y2 < y1 || -1); + if (abs(y2 - y1) < minHeight) { + /* Selection height is smaller than minHeight */ + y2 = y1 - minHeight * (y2 < y1 || -1); if (y2 < top) - y1 = top + options.minHeight; + y1 = top + minHeight; else if (y2 > top + imgHeight) - y1 = top + imgHeight - options.minHeight; + y1 = top + imgHeight - minHeight; } x2 = max(left, min(x2, left + imgWidth)); y2 = max(top, min(y2, top + imgHeight)); - if (aspectRatio) - if (abs(x2 - x1) / aspectRatio > abs(y2 - y1)) - aspectRatioYX(); - else - aspectRatioXY(); + fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio); - if (abs(x2 - x1) > options.maxWidth) { - x2 = x1 - options.maxWidth * (x2 < x1 || -1); - if (aspectRatio) aspectRatioYX(); + if (abs(x2 - x1) > maxWidth) { + /* Selection width is greater than maxWidth */ + x2 = x1 - maxWidth * (x2 < x1 || -1); + fixAspectRatio(); } - if (abs(y2 - y1) > options.maxHeight) { - y2 = y1 - options.maxHeight * (y2 < y1 || -1); - if (aspectRatio) aspectRatioXY(); + if (abs(y2 - y1) > maxHeight) { + /* Selection height is greater than maxHeight */ + y2 = y1 - maxHeight * (y2 < y1 || -1); + fixAspectRatio(true); } selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)), @@ -348,28 +616,49 @@ options.onSelectChange(img, getSelection()); } + /** + * Mousemove event handler triggered when the user is selecting an area + * + * @param event + * The event object + * @return false + */ function selectingMouseMove(event) { - x2 = resize == '' || /w|e/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2); - y2 = resize == '' || /n|s/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2); + x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2); + y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2); doResize(); return false; - } + /** + * Move the selection area + * + * @param newX1 + * New viewport X1 + * @param newY1 + * New viewport Y1 + */ function doMove(newX1, newY1) { x2 = (x1 = newX1) + selection.width; y2 = (y1 = newY1) + selection.height; - selection = $.extend(selection, { x1: selX(x1), y1: selY(y1), - x2: selX(x2), y2: selY(y2) }); + $.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2), + y2: selY(y2) }); update(); options.onSelectChange(img, getSelection()); } + /** + * Mousemove event handler triggered when the selection area is being moved + * + * @param event + * The event object + * @return false + */ function movingMouseMove(event) { x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width)); y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height)); @@ -377,21 +666,24 @@ doMove(x1, y1); event.preventDefault(); - return false; } + /** + * Start selection + */ function startSelection() { + $(document).unbind('mousemove', startSelection); adjust(); x2 = x1; y2 = y1; - doResize(); resize = ''; - if ($outer.is(':not(:visible)')) + if (!$outer.is(':visible')) + /* Show the plugin elements */ $box.add($outer).hide().fadeIn(options.fadeSpeed||0); shown = true; @@ -403,42 +695,65 @@ options.onSelectStart(img, getSelection()); } + /** + * Cancel selection + */ function cancelSelection() { - $(document).unbind('mousemove', startSelection); + $(document).unbind('mousemove', startSelection) + .unbind('mouseup', cancelSelection); hide($box.add($outer)); - selection = { x1: selX(x1), y1: selY(y1), x2: selX(x1), y2: selY(y1), - width: 0, height: 0 }; + setSelection(selX(x1), selY(y1), selX(x1), selY(y1)); - options.onSelectChange(img, getSelection()); - options.onSelectEnd(img, getSelection()); + /* If this is an API call, callback functions should not be triggered */ + if (!(this instanceof $.imgAreaSelect)) { + options.onSelectChange(img, getSelection()); + options.onSelectEnd(img, getSelection()); + } } + /** + * Image mousedown event handler + * + * @param event + * The event object + * @return false + */ function imgMouseDown(event) { + /* Ignore the event if animation is in progress */ if (event.which != 1 || $outer.is(':animated')) return false; adjust(); startX = x1 = evX(event); startY = y1 = evY(event); - $(document).one('mousemove', startSelection) - .one('mouseup', cancelSelection); + /* Selection will start when the mouse is moved */ + $(document).mousemove(startSelection).mouseup(cancelSelection); return false; } - function parentScroll() { + /** + * Window resize event handler + */ + function windowResize() { doUpdate(false); } + /** + * Image load event handler. This is the final part of the initialization + * process. + */ function imgLoad() { imgLoaded = true; + /* Set options */ setOptions(options = $.extend({ classPrefix: 'imgareaselect', movable: true, + parent: 'body', resizable: true, - parent: 'body', + resizeMargin: 10, onInit: function () {}, onSelectStart: function () {}, onSelectChange: function () {}, @@ -454,11 +769,23 @@ $box.add($outer).hide().fadeIn(options.fadeSpeed||0); } + /* + * Call the onInit callback. The setTimeout() call is used to ensure + * that the plugin has been fully initialized and the object instance is + * available (so that it can be obtained in the callback). + */ setTimeout(function () { options.onInit(img, getSelection()); }, 0); } + /** + * Document keypress event handler + * + * @param event + * The event object + * @return false + */ var docKeyPress = function(event) { - var k = options.keys, d, t, key = event.keyCode || event.which; + var k = options.keys, d, t, key = event.keyCode; d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt : !isNaN(k.ctrl) && event.ctrlKey ? k.ctrl : @@ -469,22 +796,28 @@ (k.ctrl == 'resize' && event.ctrlKey) || (k.alt == 'resize' && (event.altKey || event.originalEvent.altKey))) { + /* Resize selection */ + switch (key) { case 37: + /* Left */ d = -d; case 39: + /* Right */ t = max(x1, x2); x1 = min(x1, x2); x2 = max(t + d, x1); - if (aspectRatio) aspectRatioYX(); + fixAspectRatio(); break; case 38: + /* Up */ d = -d; case 40: + /* Down */ t = max(y1, y2); y1 = min(y1, y2); y2 = max(t + d, y1); - if (aspectRatio) aspectRatioXY(); + fixAspectRatio(true); break; default: return; @@ -493,20 +826,26 @@ doResize(); } else { + /* Move selection */ + x1 = min(x1, x2); y1 = min(y1, y2); switch (key) { case 37: + /* Left */ doMove(max(x1 - d, left), y1); break; case 38: + /* Up */ doMove(x1, max(y1 - d, top)); break; case 39: + /* Right */ doMove(x1 + min(d, imgWidth - selX(x2)), y1); break; case 40: + /* Down */ doMove(x1, y1 + min(d, imgHeight - selY(y2))); break; default: @@ -517,21 +856,38 @@ return false; }; + /** + * Apply style options to plugin element (or multiple elements) + * + * @param $elem + * A jQuery object representing the element(s) to style + * @param props + * An object that maps option names to corresponding CSS + * properties + */ function styleOptions($elem, props) { - for (option in props) + for (var option in props) if (options[option] !== undefined) $elem.css(props[option], options[option]); } + /** + * Set plugin options + * + * @param newOptions + * The new options object + */ function setOptions(newOptions) { if (newOptions.parent) ($parent = $(newOptions.parent)).append($box.add($outer)); - options = $.extend(options, newOptions); + /* Merge the new options with the existing ones */ + $.extend(options, newOptions); adjust(); if (newOptions.handles != null) { + /* Recreate selection area handles */ $handles.remove(); $handles = $([]); @@ -540,41 +896,60 @@ while (i--) $handles = $handles.add(div()); + /* Add a class to handles and set the CSS properties */ $handles.addClass(options.classPrefix + '-handle').css({ position: 'absolute', + /* + * The font-size property needs to be set to zero, otherwise + * Internet Explorer makes the handles too large + */ fontSize: 0, zIndex: zIndex + 1 || 1 }); - if (!parseInt($handles.css('width'))) + /* + * If handle width/height has not been set with CSS rules, set the + * default 5px + */ + if (!parseInt($handles.css('width')) >= 0) $handles.width(5).height(5); + /* + * If the borderWidth option is in use, add a solid border to + * handles + */ if (o = options.borderWidth) $handles.css({ borderWidth: o, borderStyle: 'solid' }); + /* Apply other style options */ styleOptions($handles, { borderColor1: 'border-color', borderColor2: 'background-color', borderOpacity: 'opacity' }); } + /* Calculate scale factors */ scaleX = options.imageWidth / imgWidth || 1; scaleY = options.imageHeight / imgHeight || 1; + /* Set selection */ if (newOptions.x1 != null) { setSelection(newOptions.x1, newOptions.y1, newOptions.x2, - newOptions.y2); + newOptions.y2); newOptions.show = !newOptions.hide; } if (newOptions.keys) + /* Enable keyboard support */ options.keys = $.extend({ shift: 1, ctrl: 'resize' }, newOptions.keys); + /* Add classes to plugin elements */ $outer.addClass(options.classPrefix + '-outer'); $area.addClass(options.classPrefix + '-selection'); for (i = 0; i++ < 4;) $($border[i-1]).addClass(options.classPrefix + '-border' + i); + /* Apply style options */ styleOptions($area, { selectionColor: 'background-color', selectionOpacity: 'opacity' }); styleOptions($border, { borderOpacity: 'opacity', @@ -586,12 +961,13 @@ if (o = options.borderColor2) $($border[1]).css({ borderStyle: 'dashed', borderColor: o }); - $box.append($area.add($border).add($handles).add($areaOpera)); + /* Append all the selection area elements to the container box */ + $box.append($area.add($border).add($areaOpera).add($handles)); if ($.browser.msie) { - if (o = $outer.css('filter').match(/opacity=([0-9]+)/)) + if (o = $outer.css('filter').match(/opacity=(\d+)/)) $outer.css('opacity', o[1]/100); - if (o = $border.css('filter').match(/opacity=([0-9]+)/)) + if (o = $border.css('filter').match(/opacity=(\d+)/)) $border.css('opacity', o[1]/100); } @@ -603,78 +979,205 @@ doUpdate(); } + /* Calculate the aspect ratio factor */ aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1]; + $img.add($outer).unbind('mousedown', imgMouseDown); + if (options.disable || options.enable === false) { + /* Disable the plugin */ $box.unbind('mousemove', areaMouseMove).unbind('mousedown', areaMouseDown); - $img.add($outer).unbind('mousedown', imgMouseDown); - $(window).unbind('resize', parentScroll); - $img.add($img.parents()).unbind('scroll', parentScroll); + $(window).unbind('resize', windowResize); } - else if (options.enable || options.disable === false) { - if (options.resizable || options.movable) - $box.mousemove(areaMouseMove).mousedown(areaMouseDown); + else { + if (options.enable || options.disable === false) { + /* Enable the plugin */ + if (options.resizable || options.movable) + $box.mousemove(areaMouseMove).mousedown(areaMouseDown); + + $(window).resize(windowResize); + } if (!options.persistent) $img.add($outer).mousedown(imgMouseDown); - $(window).resize(parentScroll); - $img.add($img.parents()).scroll(parentScroll); } options.enable = options.disable = undefined; } + /** + * Remove plugin completely + */ + this.remove = function () { + /* + * Call setOptions with { disable: true } to unbind the event handlers + */ + setOptions({ disable: true }); + $box.add($outer).remove(); + }; + + /* + * Public API + */ + + /** + * Get current options + * + * @return An object containing the set of options currently in use + */ this.getOptions = function () { return options; }; + /** + * Set plugin options + * + * @param newOptions + * The new options object + */ this.setOptions = setOptions; + /** + * Get the current selection + * + * @param noScale + * If set to true, scaling is not applied to the + * returned selection + * @return Selection object + */ this.getSelection = getSelection; + /** + * Set the current selection + * + * @param x1 + * X coordinate of the upper left corner of the selection area + * @param y1 + * Y coordinate of the upper left corner of the selection area + * @param x2 + * X coordinate of the lower right corner of the selection area + * @param y2 + * Y coordinate of the lower right corner of the selection area + * @param noScale + * If set to true, scaling is not applied to the + * new selection + */ this.setSelection = setSelection; + /** + * Cancel selection + */ + this.cancelSelection = cancelSelection; + + /** + * Update plugin elements + * + * @param resetKeyPress + * If set to false, this instance's keypress + * event handler is not activated + */ this.update = doUpdate; + /* + * Traverse the image's parent elements (up to ) and find the + * highest z-index + */ $p = $img; - while ($p.length && !$p.is('body')) { - if (!isNaN($p.css('z-index')) && $p.css('z-index') > zIndex) - zIndex = $p.css('z-index'); + while ($p.length) { + zIndex = max(zIndex, + !isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex); + /* Also check if any of the ancestor elements has fixed position */ if ($p.css('position') == 'fixed') position = 'fixed'; - $p = $p.parent(); + $p = $p.parent(':not(body)'); } - if (!isNaN(options.zIndex)) - zIndex = options.zIndex; + /* + * If z-index is given as an option, it overrides the one found by the + * above loop + */ + zIndex = options.zIndex || zIndex; if ($.browser.msie) $img.attr('unselectable', 'on'); + /* + * In MSIE and WebKit, we need to use the keydown event instead of keypress + */ $.imgAreaSelect.keyPress = $.browser.msie || $.browser.safari ? 'keydown' : 'keypress'; + /* + * There is a bug affecting the CSS cursor property in Opera (observed in + * versions up to 10.00) that prevents the cursor from being updated unless + * the mouse leaves and enters the element again. To trigger the mouseover + * event, we're adding an additional div to $box and we're going to toggle + * it when mouse moves inside the selection area. + */ if ($.browser.opera) $areaOpera = div().css({ width: '100%', height: '100%', position: 'absolute', zIndex: zIndex + 2 || 2 }); + /* + * We initially set visibility to "hidden" as a workaround for a weird + * behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally + * we would just set display to "none", but, for some reason, if we do so + * then Chrome refuses to later display the element with .show() or + * .fadeIn(). + */ $box.add($outer).css({ visibility: 'hidden', position: position, overflow: 'hidden', zIndex: zIndex || '0' }); $box.css({ zIndex: zIndex + 2 || 2 }); - $area.add($border).css({ position: 'absolute' }); + $area.add($border).css({ position: 'absolute', fontSize: 0 }); + /* + * If the image has been fully loaded, or if it is not really an image (eg. + * a div), call imgLoad() immediately; otherwise, bind it to be called once + * on image load event. + */ img.complete || img.readyState == 'complete' || !$img.is('img') ? imgLoad() : $img.one('load', imgLoad); + /* + * MSIE 9.0 doesn't always fire the image load event -- resetting the src + * attribute seems to trigger it. The check is for version 7 and above to + * accommodate for MSIE 9 running in compatibility mode. + */ + if (!imgLoaded && $.browser.msie && $.browser.version >= 7) + img.src = img.src; }; +/** + * Invoke imgAreaSelect on a jQuery object containing the image(s) + * + * @param options + * Options object + * @return The jQuery object or a reference to imgAreaSelect instance (if the + * instance option was specified) + */ $.fn.imgAreaSelect = function (options) { options = options || {}; this.each(function () { - if ($(this).data('imgAreaSelect')) - $(this).data('imgAreaSelect').setOptions(options); - else { + /* Is there already an imgAreaSelect instance bound to this element? */ + if ($(this).data('imgAreaSelect')) { + /* Yes there is -- is it supposed to be removed? */ + if (options.remove) { + /* Remove the plugin */ + $(this).data('imgAreaSelect').remove(); + $(this).removeData('imgAreaSelect'); + } + else + /* Reset options */ + $(this).data('imgAreaSelect').setOptions(options); + } + else if (!options.remove) { + /* No exising instance -- create a new one */ + + /* + * If neither the "enable" nor the "disable" option is present, add + * "enable" as the default + */ if (options.enable === undefined && options.disable === undefined) options.enable = true; @@ -683,6 +1186,10 @@ }); if (options.instance) + /* + * Return the imgAreaSelect instance bound to the first element in the + * set + */ return $(this).data('imgAreaSelect'); return this;