/*! Jcrop.js v2.0.4 - build: 20151117 * @copyright 2008-2015 Tapmodo Interactive LLC * @license Free software under MIT License * @website http://jcrop.org/ **/ (function($){ 'use strict'; // Jcrop constructor var Jcrop = function(element,opt){ var _ua = navigator.userAgent.toLowerCase(); this.opt = $.extend({},Jcrop.defaults,opt || {}); this.container = $(element); this.opt.is_msie = /msie/.test(_ua); this.opt.is_ie_lt9 = /msie [1-8]\./.test(_ua); this.container.addClass(this.opt.css_container); this.ui = {}; this.state = null; this.ui.multi = []; this.ui.selection = null; this.filter = {}; this.init(); this.setOptions(opt); this.applySizeConstraints(); this.container.trigger('cropinit',this); // IE<9 doesn't work if mouse events are attached to window if (this.opt.is_ie_lt9) this.opt.dragEventTarget = document.body; }; // Jcrop static functions $.extend(Jcrop,{ component: { }, filter: { }, stage: { }, registerComponent: function(name,component){ Jcrop.component[name] = component; }, registerFilter: function(name,filter){ Jcrop.filter[name] = filter; }, registerStageType: function(name,stage){ Jcrop.stage[name] = stage; }, // attach: function(element,opt){{{ attach: function(element,opt){ var obj = new $.Jcrop(element,opt); return obj; }, // }}} // imgCopy: function(imgel){{{ imgCopy: function(imgel){ var img = new Image; img.src = imgel.src; return img; }, // }}} // imageClone: function(imgel){{{ imageClone: function(imgel){ return $.Jcrop.supportsCanvas? Jcrop.canvasClone(imgel): Jcrop.imgCopy(imgel); }, // }}} // canvasClone: function(imgel){{{ canvasClone: function(imgel){ var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'); $(canvas).width(imgel.width).height(imgel.height), canvas.width = imgel.naturalWidth; canvas.height = imgel.naturalHeight; ctx.drawImage(imgel,0,0,imgel.naturalWidth,imgel.naturalHeight); return canvas; }, // }}} // propagate: function(plist,config,obj){{{ propagate: function(plist,config,obj){ for(var i=0,l=plist.length;i<l;i++) if (config.hasOwnProperty(plist[i])) obj[plist[i]] = config[plist[i]]; }, // }}} // getLargestBox: function(ratio,w,h){{{ getLargestBox: function(ratio,w,h){ if ((w/h) > ratio) return [ h * ratio, h ]; else return [ w, w / ratio ]; }, // }}} // stageConstructor: function(el,options,callback){{{ stageConstructor: function(el,options,callback){ // Get a priority-ordered list of available stages var stages = []; $.each(Jcrop.stage,function(i,e){ stages.push(e); }); stages.sort(function(a,b){ return a.priority - b.priority; }); // Find the first one that supports this element for(var i=0,l=stages.length;i<l;i++){ if (stages[i].isSupported(el,options)){ stages[i].create(el,options,function(obj,opt){ if (typeof callback == 'function') callback(obj,opt); }); break; } } }, // }}} // supportsColorFade: function(){{{ supportsColorFade: function(){ return $.fx.step.hasOwnProperty('backgroundColor'); }, // }}} // wrapFromXywh: function(xywh){{{ wrapFromXywh: function(xywh){ var b = { x: xywh[0], y: xywh[1], w: xywh[2], h: xywh[3] }; b.x2 = b.x + b.w; b.y2 = b.y + b.h; return b; } // }}} }); var AbstractStage = function(){ }; $.extend(AbstractStage,{ isSupported: function(el,o){ // @todo: should actually check if it's an HTML element return true; }, // A higher priority means less desirable // AbstractStage is the last one we want to use priority: 100, create: function(el,options,callback){ var obj = new AbstractStage; obj.element = el; callback.call(this,obj,options); }, prototype: { attach: function(core){ this.init(core); core.ui.stage = this; }, triggerEvent: function(ev){ $(this.element).trigger(ev); return this; }, getElement: function(){ return this.element; } } }); Jcrop.registerStageType('Block',AbstractStage); var ImageStage = function(){ }; ImageStage.prototype = new AbstractStage(); $.extend(ImageStage,{ isSupported: function(el,o){ if (el.tagName == 'IMG') return true; }, priority: 90, create: function(el,options,callback){ $.Jcrop.component.ImageLoader.attach(el,function(w,h){ var obj = new ImageStage; obj.element = $(el).wrap('<div />').parent(); obj.element.width(w).height(h); obj.imgsrc = el; if (typeof callback == 'function') callback.call(this,obj,options); }); } }); Jcrop.registerStageType('Image',ImageStage); var CanvasStage = function(){ this.angle = 0; this.scale = 1; this.scaleMin = 0.2; this.scaleMax = 1.25; this.offset = [0,0]; }; CanvasStage.prototype = new ImageStage(); $.extend(CanvasStage,{ isSupported: function(el,o){ if ($.Jcrop.supportsCanvas && (el.tagName == 'IMG')) return true; }, priority: 60, create: function(el,options,callback){ var $el = $(el); var opt = $.extend({},options); $.Jcrop.component.ImageLoader.attach(el,function(w,h){ var obj = new CanvasStage; $el.hide(); obj.createCanvas(el,w,h); $el.before(obj.element); obj.imgsrc = el; opt.imgsrc = el; if (typeof callback == 'function'){ callback(obj,opt); obj.redraw(); } }); } }); $.extend(CanvasStage.prototype,{ init: function(core){ this.core = core; }, // setOffset: function(x,y) {{{ setOffset: function(x,y) { this.offset = [x,y]; return this; }, // }}} // setAngle: function(v) {{{ setAngle: function(v) { this.angle = v; return this; }, // }}} // setScale: function(v) {{{ setScale: function(v) { this.scale = this.boundScale(v); return this; }, // }}} boundScale: function(v){ if (v<this.scaleMin) v = this.scaleMin; else if (v>this.scaleMax) v = this.scaleMax; return v; }, createCanvas: function(img,w,h){ this.width = w; this.height = h; this.canvas = document.createElement('canvas'); this.canvas.width = w; this.canvas.height = h; this.$canvas = $(this.canvas).width('100%').height('100%'); this.context = this.canvas.getContext('2d'); this.fillstyle = "rgb(0,0,0)"; this.element = this.$canvas.wrap('<div />').parent().width(w).height(h); }, triggerEvent: function(ev){ this.$canvas.trigger(ev); return this; }, // clear: function() {{{ clear: function() { this.context.fillStyle = this.fillstyle; this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); return this; }, // }}} // redraw: function() {{{ redraw: function() { // Save the current context this.context.save(); this.clear(); // Translate to the center point of our image this.context.translate(parseInt(this.width * 0.5), parseInt(this.height * 0.5)); // Perform the rotation and scaling this.context.translate(this.offset[0]/this.core.opt.xscale,this.offset[1]/this.core.opt.yscale); this.context.rotate(this.angle * (Math.PI/180)); this.context.scale(this.scale,this.scale); // Translate back to the top left of our image this.context.translate(-parseInt(this.width * 0.5), -parseInt(this.height * 0.5)); // Finally we draw the image this.context.drawImage(this.imgsrc,0,0,this.width,this.height); // And restore the updated context this.context.restore(); this.$canvas.trigger('cropredraw'); return this; }, // }}} // setFillStyle: function(v) {{{ setFillStyle: function(v) { this.fillstyle = v; return this; } // }}} }); Jcrop.registerStageType('Canvas',CanvasStage); /** * BackoffFilter * move out-of-bounds selection into allowed position at same size */ var BackoffFilter = function(){ this.minw = 40; this.minh = 40; this.maxw = 0; this.maxh = 0; this.core = null; }; $.extend(BackoffFilter.prototype,{ tag: 'backoff', priority: 22, filter: function(b){ var r = this.bound; if (b.x < r.minx) { b.x = r.minx; b.x2 = b.w + b.x; } if (b.y < r.miny) { b.y = r.miny; b.y2 = b.h + b.y; } if (b.x2 > r.maxx) { b.x2 = r.maxx; b.x = b.x2 - b.w; } if (b.y2 > r.maxy) { b.y2 = r.maxy; b.y = b.y2 - b.h; } return b; }, refresh: function(sel){ this.elw = sel.core.container.width(); this.elh = sel.core.container.height(); this.bound = { minx: 0 + sel.edge.w, miny: 0 + sel.edge.n, maxx: this.elw + sel.edge.e, maxy: this.elh + sel.edge.s }; } }); Jcrop.registerFilter('backoff',BackoffFilter); /** * ConstrainFilter * a filter to constrain crop selection to bounding element */ var ConstrainFilter = function(){ this.core = null; }; $.extend(ConstrainFilter.prototype,{ tag: 'constrain', priority: 5, filter: function(b,ord){ if (ord == 'move') { if (b.x < this.minx) { b.x = this.minx; b.x2 = b.w + b.x; } if (b.y < this.miny) { b.y = this.miny; b.y2 = b.h + b.y; } if (b.x2 > this.maxx) { b.x2 = this.maxx; b.x = b.x2 - b.w; } if (b.y2 > this.maxy) { b.y2 = this.maxy; b.y = b.y2 - b.h; } } else { if (b.x < this.minx) { b.x = this.minx; } if (b.y < this.miny) { b.y = this.miny; } if (b.x2 > this.maxx) { b.x2 = this.maxx; } if (b.y2 > this.maxy) { b.y2 = this.maxy; } } b.w = b.x2 - b.x; b.h = b.y2 - b.y; return b; }, refresh: function(sel){ this.elw = sel.core.container.width(); this.elh = sel.core.container.height(); this.minx = 0 + sel.edge.w; this.miny = 0 + sel.edge.n; this.maxx = this.elw + sel.edge.e; this.maxy = this.elh + sel.edge.s; } }); Jcrop.registerFilter('constrain',ConstrainFilter); /** * ExtentFilter * a filter to implement minimum or maximum size */ var ExtentFilter = function(){ this.core = null; }; $.extend(ExtentFilter.prototype,{ tag: 'extent', priority: 12, offsetFromCorner: function(corner,box,b){ var w = box[0], h = box[1]; switch(corner){ case 'bl': return [ b.x2 - w, b.y, w, h ]; case 'tl': return [ b.x2 - w , b.y2 - h, w, h ]; case 'br': return [ b.x, b.y, w, h ]; case 'tr': return [ b.x, b.y2 - h, w, h ]; } }, getQuadrant: function(s){ var relx = s.opposite[0]-s.offsetx var rely = s.opposite[1]-s.offsety; if ((relx < 0) && (rely < 0)) return 'br'; else if ((relx >= 0) && (rely >= 0)) return 'tl'; else if ((relx < 0) && (rely >= 0)) return 'tr'; return 'bl'; }, filter: function(b,ord,sel){ if (ord == 'move') return b; var w = b.w, h = b.h, st = sel.state, r = this.limits; var quad = st? this.getQuadrant(st): 'br'; if (r.minw && (w < r.minw)) w = r.minw; if (r.minh && (h < r.minh)) h = r.minh; if (r.maxw && (w > r.maxw)) w = r.maxw; if (r.maxh && (h > r.maxh)) h = r.maxh; if ((w == b.w) && (h == b.h)) return b; return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,[w,h],b)); }, refresh: function(sel){ this.elw = sel.core.container.width(); this.elh = sel.core.container.height(); this.limits = { minw: sel.minSize[0], minh: sel.minSize[1], maxw: sel.maxSize[0], maxh: sel.maxSize[1] }; } }); Jcrop.registerFilter('extent',ExtentFilter); /** * GridFilter * a rudimentary grid effect */ var GridFilter = function(){ this.stepx = 1; this.stepy = 1; this.core = null; }; $.extend(GridFilter.prototype,{ tag: 'grid', priority: 19, filter: function(b){ var n = { x: Math.round(b.x / this.stepx) * this.stepx, y: Math.round(b.y / this.stepy) * this.stepy, x2: Math.round(b.x2 / this.stepx) * this.stepx, y2: Math.round(b.y2 / this.stepy) * this.stepy }; n.w = n.x2 - n.x; n.h = n.y2 - n.y; return n; } }); Jcrop.registerFilter('grid',GridFilter); /** * RatioFilter * implements aspectRatio locking */ var RatioFilter = function(){ this.ratio = 0; this.core = null; }; $.extend(RatioFilter.prototype,{ tag: 'ratio', priority: 15, offsetFromCorner: function(corner,box,b){ var w = box[0], h = box[1]; switch(corner){ case 'bl': return [ b.x2 - w, b.y, w, h ]; case 'tl': return [ b.x2 - w , b.y2 - h, w, h ]; case 'br': return [ b.x, b.y, w, h ]; case 'tr': return [ b.x, b.y2 - h, w, h ]; } }, getBoundRatio: function(b,quad){ var box = Jcrop.getLargestBox(this.ratio,b.w,b.h); return Jcrop.wrapFromXywh(this.offsetFromCorner(quad,box,b)); }, getQuadrant: function(s){ var relx = s.opposite[0]-s.offsetx var rely = s.opposite[1]-s.offsety; if ((relx < 0) && (rely < 0)) return 'br'; else if ((relx >= 0) && (rely >= 0)) return 'tl'; else if ((relx < 0) && (rely >= 0)) return 'tr'; return 'bl'; }, filter: function(b,ord,sel){ if (!this.ratio) return b; var rt = b.w / b.h; var st = sel.state; var quad = st? this.getQuadrant(st): 'br'; ord = ord || 'se'; if (ord == 'move') return b; switch(ord) { case 'n': b.x2 = this.elw; b.w = b.x2 - b.x; quad = 'tr'; break; case 's': b.x2 = this.elw; b.w = b.x2 - b.x; quad = 'br'; break; case 'e': b.y2 = this.elh; b.h = b.y2 - b.y; quad = 'br'; break; case 'w': b.y2 = this.elh; b.h = b.y2 - b.y; quad = 'bl'; break; } return this.getBoundRatio(b,quad); }, refresh: function(sel){ this.ratio = sel.aspectRatio; this.elw = sel.core.container.width(); this.elh = sel.core.container.height(); } }); Jcrop.registerFilter('ratio',RatioFilter); /** * RoundFilter * rounds coordinate values to integers */ var RoundFilter = function(){ this.core = null; }; $.extend(RoundFilter.prototype,{ tag: 'round', priority: 90, filter: function(b){ var n = { x: Math.round(b.x), y: Math.round(b.y), x2: Math.round(b.x2), y2: Math.round(b.y2) }; n.w = n.x2 - n.x; n.h = n.y2 - n.y; return n; } }); Jcrop.registerFilter('round',RoundFilter); /** * ShadeFilter * A filter that implements div-based shading on any element * * The shading you see is actually four semi-opaque divs * positioned inside the container, around the selection */ var ShadeFilter = function(opacity,color){ this.color = color || 'black'; this.opacity = opacity || 0.5; this.core = null; this.shades = {}; }; $.extend(ShadeFilter.prototype,{ tag: 'shader', fade: true, fadeEasing: 'swing', fadeSpeed: 320, priority: 95, init: function(){ var t = this; if (!t.attached) { t.visible = false; t.container = $('<div />').addClass(t.core.opt.css_shades) .prependTo(this.core.container).hide(); t.elh = this.core.container.height(); t.elw = this.core.container.width(); t.shades = { top: t.createShade(), right: t.createShade(), left: t.createShade(), bottom: t.createShade() }; t.attached = true; } }, destroy: function(){ this.container.remove(); }, setColor: function(color,instant){ var t = this; if (color == t.color) return t; this.color = color; var colorfade = Jcrop.supportsColorFade(); $.each(t.shades,function(u,i){ if (!t.fade || instant || !colorfade) i.css('backgroundColor',color); else i.animate({backgroundColor:color},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing}); }); return t; }, setOpacity: function(opacity,instant){ var t = this; if (opacity == t.opacity) return t; t.opacity = opacity; $.each(t.shades,function(u,i){ if (!t.fade || instant) i.css({opacity:opacity}); else i.animate({opacity:opacity},{queue:false,duration:t.fadeSpeed,easing:t.fadeEasing}); }); return t; }, createShade: function(){ return $('<div />').css({ position: 'absolute', backgroundColor: this.color, opacity: this.opacity }).appendTo(this.container); }, refresh: function(sel){ var m = this.core, s = this.shades; this.setColor(sel.bgColor?sel.bgColor:this.core.opt.bgColor); this.setOpacity(sel.bgOpacity?sel.bgOpacity:this.core.opt.bgOpacity); this.elh = m.container.height(); this.elw = m.container.width(); s.right.css('height',this.elh+'px'); s.left.css('height',this.elh+'px'); }, filter: function(b,ord,sel){ if (!sel.active) return b; var t = this, s = t.shades; s.top.css({ left: Math.round(b.x)+'px', width: Math.round(b.w)+'px', height: Math.round(b.y)+'px' }); s.bottom.css({ top: Math.round(b.y2)+'px', left: Math.round(b.x)+'px', width: Math.round(b.w)+'px', height: (t.elh-Math.round(b.y2))+'px' }); s.right.css({ left: Math.round(b.x2)+'px', width: (t.elw-Math.round(b.x2))+'px' }); s.left.css({ width: Math.round(b.x)+'px' }); if (!t.visible) { t.container.show(); t.visible = true; } return b; } }); Jcrop.registerFilter('shader',ShadeFilter); /** * CanvasAnimator * manages smooth cropping animation * * This object is called internally to manage animation. * An in-memory div is animated and a progress callback * is used to update the selection coordinates of the * visible selection in realtime. */ var CanvasAnimator = function(stage){ this.stage = stage; this.core = stage.core; this.cloneStagePosition(); }; CanvasAnimator.prototype = { cloneStagePosition: function(){ var s = this.stage; this.angle = s.angle; this.scale = s.scale; this.offset = s.offset; }, getElement: function(){ var s = this.stage; return $('<div />') .css({ position: 'absolute', top: s.offset[0]+'px', left: s.offset[1]+'px', width: s.angle+'px', height: s.scale+'px' }); }, animate: function(cb){ var t = this; this.scale = this.stage.boundScale(this.scale); t.stage.triggerEvent('croprotstart'); t.getElement().animate({ top: t.offset[0]+'px', left: t.offset[1]+'px', width: t.angle+'px', height: t.scale+'px' },{ easing: t.core.opt.animEasing, duration: t.core.opt.animDuration, complete: function(){ t.stage.triggerEvent('croprotend'); (typeof cb == 'function') && cb.call(this); }, progress: function(anim){ var props = {}, i, tw = anim.tweens; for(i=0;i<tw.length;i++){ props[tw[i].prop] = tw[i].now; } t.stage.setAngle(props.width) .setScale(props.height) .setOffset(props.top,props.left) .redraw(); } }); } }; Jcrop.stage.Canvas.prototype.getAnimator = function(){ return new CanvasAnimator(this); }; Jcrop.registerComponent('CanvasAnimator',CanvasAnimator); /** * CropAnimator * manages smooth cropping animation * * This object is called internally to manage animation. * An in-memory div is animated and a progress callback * is used to update the selection coordinates of the * visible selection in realtime. */ // var CropAnimator = function(selection){{{ var CropAnimator = function(selection){ this.selection = selection; this.core = selection.core; }; // }}} CropAnimator.prototype = { getElement: function(){ var b = this.selection.get(); return $('<div />') .css({ position: 'absolute', top: b.y+'px', left: b.x+'px', width: b.w+'px', height: b.h+'px' }); }, animate: function(x,y,w,h,cb){ var t = this; t.selection.allowResize(false); t.getElement().animate({ top: y+'px', left: x+'px', width: w+'px', height: h+'px' },{ easing: t.core.opt.animEasing, duration: t.core.opt.animDuration, complete: function(){ t.selection.allowResize(true); cb && cb.call(this); }, progress: function(anim){ var props = {}, i, tw = anim.tweens; for(i=0;i<tw.length;i++){ props[tw[i].prop] = tw[i].now; } var b = { x: parseInt(props.left), y: parseInt(props.top), w: parseInt(props.width), h: parseInt(props.height) }; b.x2 = b.x + b.w; b.y2 = b.y + b.h; t.selection.updateRaw(b,'se'); } }); } }; Jcrop.registerComponent('Animator',CropAnimator); /** * DragState * an object that handles dragging events * * This object is used by the built-in selection object to * track a dragging operation on a selection */ // var DragState = function(e,selection,ord){{{ var DragState = function(e,selection,ord){ var t = this; t.x = e.pageX; t.y = e.pageY; t.selection = selection; t.eventTarget = selection.core.opt.dragEventTarget; t.orig = selection.get(); selection.callFilterFunction('refresh'); var p = selection.core.container.position(); t.elx = p.left; t.ely = p.top; t.offsetx = 0; t.offsety = 0; t.ord = ord; t.opposite = t.getOppositeCornerOffset(); t.initEvents(e); }; // }}} DragState.prototype = { // getOppositeCornerOffset: function(){{{ // Calculate relative offset of locked corner getOppositeCornerOffset: function(){ var o = this.orig; var relx = this.x - this.elx - o.x; var rely = this.y - this.ely - o.y; switch(this.ord){ case 'nw': case 'w': return [ o.w - relx, o.h - rely ]; return [ o.x + o.w, o.y + o.h ]; case 'sw': return [ o.w - relx, -rely ]; return [ o.x + o.w, o.y ]; case 'se': case 's': case 'e': return [ -relx, -rely ]; return [ o.x, o.y ]; case 'ne': case 'n': return [ -relx, o.h - rely ]; return [ o.w, o.y + o.h ]; } return [ null, null ]; }, // }}} // initEvents: function(e){{{ initEvents: function(e){ $(this.eventTarget) .on('mousemove.jcrop',this.createDragHandler()) .on('mouseup.jcrop',this.createStopHandler()); }, // }}} // dragEvent: function(e){{{ dragEvent: function(e){ this.offsetx = e.pageX - this.x; this.offsety = e.pageY - this.y; this.selection.updateRaw(this.getBox(),this.ord); }, // }}} // endDragEvent: function(e){{{ endDragEvent: function(e){ var sel = this.selection; sel.core.container.removeClass('jcrop-dragging'); sel.element.trigger('cropend',[sel,sel.core.unscale(sel.get())]); sel.focus(); }, // }}} // createStopHandler: function(){{{ createStopHandler: function(){ var t = this; return function(e){ $(t.eventTarget).off('.jcrop'); t.endDragEvent(e); return false; }; }, // }}} // createDragHandler: function(){{{ createDragHandler: function(){ var t = this; return function(e){ t.dragEvent(e); return false; }; }, // }}} //update: function(x,y){{{ update: function(x,y){ var t = this; t.offsetx = x - t.x; t.offsety = y - t.y; }, //}}} //resultWrap: function(d){{{ resultWrap: function(d){ var b = { x: Math.min(d[0],d[2]), y: Math.min(d[1],d[3]), x2: Math.max(d[0],d[2]), y2: Math.max(d[1],d[3]) }; b.w = b.x2 - b.x; b.h = b.y2 - b.y; return b; }, //}}} //getBox: function(){{{ getBox: function(){ var t = this; var o = t.orig; var _c = { x2: o.x + o.w, y2: o.y + o.h }; switch(t.ord){ case 'n': return t.resultWrap([ o.x, t.offsety + o.y, _c.x2, _c.y2 ]); case 's': return t.resultWrap([ o.x, o.y, _c.x2, t.offsety + _c.y2 ]); case 'e': return t.resultWrap([ o.x, o.y, t.offsetx + _c.x2, _c.y2 ]); case 'w': return t.resultWrap([ o.x + t.offsetx, o.y, _c.x2, _c.y2 ]); case 'sw': return t.resultWrap([ t.offsetx + o.x, o.y, _c.x2, t.offsety + _c.y2 ]); case 'se': return t.resultWrap([ o.x, o.y, t.offsetx + _c.x2, t.offsety + _c.y2 ]); case 'ne': return t.resultWrap([ o.x, t.offsety + o.y, t.offsetx + _c.x2, _c.y2 ]); case 'nw': return t.resultWrap([ t.offsetx + o.x, t.offsety + o.y, _c.x2, _c.y2 ]); case 'move': _c.nx = o.x + t.offsetx; _c.ny = o.y + t.offsety; return t.resultWrap([ _c.nx, _c.ny, _c.nx + o.w, _c.ny + o.h ]); } } //}}} }; Jcrop.registerComponent('DragState',DragState); /** * EventManager * provides internal event support */ var EventManager = function(core){ this.core = core; }; EventManager.prototype = { on: function(n,cb){ $(this).on(n,cb); }, off: function(n){ $(this).off(n); }, trigger: function(n){ $(this).trigger(n); } }; Jcrop.registerComponent('EventManager',EventManager); /** * Image Loader * Reliably pre-loads images */ // var ImageLoader = function(src,element,cb){{{ var ImageLoader = function(src,element,cb){ this.src = src; if (!element) element = new Image; this.element = element; this.callback = cb; this.load(); }; // }}} $.extend(ImageLoader,{ // attach: function(el,cb){{{ attach: function(el,cb){ return new ImageLoader(el.src,el,cb); }, // }}} // prototype: {{{ prototype: { getDimensions: function(){ var el = this.element; if (el.naturalWidth) return [ el.naturalWidth, el. naturalHeight ]; if (el.width) return [ el.width, el.height ]; return null; }, fireCallback: function(){ this.element.onload = null; if (typeof this.callback == 'function') this.callback.apply(this,this.getDimensions()); }, isLoaded: function(){ return this.element.complete; }, load: function(){ var t = this; var el = t.element; el.src = t.src; if (t.isLoaded()) t.fireCallback(); else t.element.onload = function(e){ t.fireCallback(); }; } } // }}} }); Jcrop.registerComponent('ImageLoader',ImageLoader); /** * JcropTouch * Detects and enables mobile touch support */ // var JcropTouch = function(core){{{ var JcropTouch = function(core){ this.core = core; this.init(); }; // }}} $.extend(JcropTouch,{ // support: function(){{{ support: function(){ if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) return true; }, // }}} prototype: { // init: function(){{{ init: function(){ var t = this, p = $.Jcrop.component.DragState.prototype; // A bit of an ugly hack to make sure we modify prototype // only once, store a key on the prototype if (!p.touch) { t.initEvents(); t.shimDragState(); t.shimStageDrag(); p.touch = true; } }, // }}} // shimDragState: function(){{{ shimDragState: function(){ var t = this; $.Jcrop.component.DragState.prototype.initEvents = function(e){ // Attach subsequent drag event handlers based on initial // event type - avoids collecting "pseudo-mouse" events // generated by some mobile browsers in some circumstances if (e.type.substr(0,5) == 'touch') { $(this.eventTarget) .on('touchmove.jcrop.jcrop-touch',t.dragWrap(this.createDragHandler())) .on('touchend.jcrop.jcrop-touch',this.createStopHandler()); } // For other events, use the mouse handlers that // the default DragState.initEvents() method sets... else { $(this.eventTarget) .on('mousemove.jcrop',this.createDragHandler()) .on('mouseup.jcrop',this.createStopHandler()); } }; }, // }}} // shimStageDrag: function(){{{ shimStageDrag: function(){ this.core.container .addClass('jcrop-touch') .on('touchstart.jcrop.jcrop-stage',this.dragWrap(this.core.ui.manager.startDragHandler())); }, // }}} // dragWrap: function(cb){{{ dragWrap: function(cb){ return function(e){ e.preventDefault(); e.stopPropagation(); if (e.type.substr(0,5) == 'touch') { e.pageX = e.originalEvent.changedTouches[0].pageX; e.pageY = e.originalEvent.changedTouches[0].pageY; return cb(e); } return false; }; }, // }}} // initEvents: function(){{{ initEvents: function(){ var t = this, c = t.core; c.container.on( 'touchstart.jcrop.jcrop-touch', '.'+c.opt.css_drag, t.dragWrap(c.startDrag()) ); } // }}} } }); Jcrop.registerComponent('Touch',JcropTouch); /** * KeyWatcher * provides keyboard support */ // var KeyWatcher = function(core){{{ var KeyWatcher = function(core){ this.core = core; this.init(); }; // }}} $.extend(KeyWatcher,{ // defaults: {{{ defaults: { eventName: 'keydown.jcrop', passthru: [ 9 ], debug: false }, // }}} prototype: { // init: function(){{{ init: function(){ $.extend(this,KeyWatcher.defaults); this.enable(); }, // }}} // disable: function(){{{ disable: function(){ this.core.container.off(this.eventName); }, // }}} // enable: function(){{{ enable: function(){ var t = this, m = t.core; m.container.on(t.eventName,function(e){ var nudge = e.shiftKey? 16: 2; if ($.inArray(e.keyCode,t.passthru) >= 0) return true; switch(e.keyCode){ case 37: m.nudge(-nudge,0); break; case 38: m.nudge(0,-nudge); break; case 39: m.nudge(nudge,0); break; case 40: m.nudge(0,nudge); break; case 46: case 8: m.requestDelete(); return false; break; default: if (t.debug) console.log('keycode: ' + e.keyCode); break; } if (!e.metaKey && !e.ctrlKey) e.preventDefault(); }); } // }}} } }); Jcrop.registerComponent('Keyboard',KeyWatcher); /** * Selection * Built-in selection object */ var Selection = function(){}; $.extend(Selection,{ // defaults: {{{ defaults: { minSize: [ 8, 8 ], maxSize: [ 0, 0 ], aspectRatio: 0, edge: { n: 0, s: 0, e: 0, w: 0 }, bgColor: null, bgOpacity: null, last: null, state: null, active: true, linked: true, canDelete: true, canDrag: true, canResize: true, canSelect: true }, // }}} prototype: { // init: function(core){{{ init: function(core){ this.core = core; this.startup(); this.linked = this.core.opt.linked; this.attach(); this.setOptions(this.core.opt); core.container.trigger('cropcreate',[this]); }, // }}} // attach: function(){{{ attach: function(){ // For extending init() sequence }, // }}} // startup: function(){{{ startup: function(){ var t = this, o = t.core.opt; $.extend(t,Selection.defaults); t.filter = t.core.getDefaultFilters(); t.element = $('<div />').addClass(o.css_selection).data({ selection: t }); t.frame = $('<button />').addClass(o.css_button).data('ord','move').attr('type','button'); t.element.append(t.frame).appendTo(t.core.container); // IE background/draggable hack if (t.core.opt.is_msie) t.frame.css({ opacity: 0, backgroundColor: 'white' }); t.insertElements(); // Bind focus and blur events for this selection t.frame.on('focus.jcrop',function(e){ t.core.setSelection(t); t.element.trigger('cropfocus',t); t.element.addClass('jcrop-focus'); }).on('blur.jcrop',function(e){ t.element.removeClass('jcrop-focus'); t.element.trigger('cropblur',t); }); }, // }}} // propagate: [{{{ propagate: [ 'canDelete', 'canDrag', 'canResize', 'canSelect', 'minSize', 'maxSize', 'aspectRatio', 'edge' ], // }}} // setOptions: function(opt){{{ setOptions: function(opt){ Jcrop.propagate(this.propagate,opt,this); this.refresh(); return this; }, // }}} // refresh: function(){{{ refresh: function(){ this.allowResize(); this.allowDrag(); this.allowSelect(); this.callFilterFunction('refresh'); this.updateRaw(this.get(),'se'); }, // }}} // callFilterFunction: function(f,args){{{ callFilterFunction: function(f,args){ for(var i=0;i<this.filter.length;i++) if (this.filter[i][f]) this.filter[i][f](this); return this; }, // }}} //addFilter: function(filter){{{ addFilter: function(filter){ filter.core = this.core; if (!this.hasFilter(filter)) { this.filter.push(filter); this.sortFilters(); if (filter.init) filter.init(); this.refresh(); } }, //}}} // hasFilter: function(filter){{{ hasFilter: function(filter){ var i, f = this.filter, n = []; for(i=0;i<f.length;i++) if (f[i] === filter) return true; }, // }}} // sortFilters: function(){{{ sortFilters: function(){ this.filter.sort( function(x,y){ return x.priority - y.priority; } ); }, // }}} //clearFilters: function(){{{ clearFilters: function(){ var i, f = this.filter; for(var i=0;i<f.length;i++) if (f[i].destroy) f[i].destroy(); this.filter = []; }, //}}} // removeFiltersByTag: function(tag){{{ removeFilter: function(tag){ var i, f = this.filter, n = []; for(var i=0;i<f.length;i++) if ((f[i].tag && (f[i].tag == tag)) || (tag === f[i])){ if (f[i].destroy) f[i].destroy(); } else n.push(f[i]); this.filter = n; }, // }}} // runFilters: function(b,ord){{{ runFilters: function(b,ord){ for(var i=0;i<this.filter.length;i++) b = this.filter[i].filter(b,ord,this); return b; }, // }}} //endDrag: function(){{{ endDrag: function(){ if (this.state) { $(document.body).off('.jcrop'); this.focus(); this.state = null; } }, //}}} // startDrag: function(e,ord){{{ startDrag: function(e,ord){ var t = this; var m = t.core; ord = ord || $(e.target).data('ord'); this.focus(); if ((ord == 'move') && t.element.hasClass(t.core.opt.css_nodrag)) return false; this.state = new Jcrop.component.DragState(e,this,ord); return false; }, // }}} // allowSelect: function(v){{{ allowSelect: function(v){ if (v === undefined) v = this.canSelect; if (v && this.canSelect) this.frame.attr('disabled',false); else this.frame.attr('disabled','disabled'); return this; }, // }}} // allowDrag: function(v){{{ allowDrag: function(v){ var t = this, o = t.core.opt; if (v == undefined) v = t.canDrag; if (v && t.canDrag) t.element.removeClass(o.css_nodrag); else t.element.addClass(o.css_nodrag); return this; }, // }}} // allowResize: function(v){{{ allowResize: function(v){ var t = this, o = t.core.opt; if (v == undefined) v = t.canResize; if (v && t.canResize) t.element.removeClass(o.css_noresize); else t.element.addClass(o.css_noresize); return this; }, // }}} // remove: function(){{{ remove: function(){ this.element.trigger('cropremove',this); this.element.remove(); }, // }}} // toBack: function(){{{ toBack: function(){ this.active = false; this.element.removeClass('jcrop-current jcrop-focus'); }, // }}} // toFront: function(){{{ toFront: function(){ this.active = true; this.element.addClass('jcrop-current'); this.callFilterFunction('refresh'); this.refresh(); }, // }}} // redraw: function(b){{{ redraw: function(b){ this.moveTo(b.x,b.y); this.resize(b.w,b.h); this.last = b; return this; }, // }}} // update: function(b,ord){{{ update: function(b,ord){ return this.updateRaw(this.core.scale(b),ord); }, // }}} // update: function(b,ord){{{ updateRaw: function(b,ord){ b = this.runFilters(b,ord); this.redraw(b); this.element.trigger('cropmove',[this,this.core.unscale(b)]); return this; }, // }}} // animateTo: function(box,cb){{{ animateTo: function(box,cb){ var ca = new Jcrop.component.Animator(this), b = this.core.scale(Jcrop.wrapFromXywh(box)); ca.animate(b.x,b.y,b.w,b.h,cb); }, // }}} // center: function(instant){{{ center: function(instant){ var b = this.get(), m = this.core; var elw = m.container.width(), elh = m.container.height(); var box = [ (elw-b.w)/2, (elh-b.h)/2, b.w, b.h ]; return this[instant?'setSelect':'animateTo'](box); }, // }}} //createElement: function(type,ord){{{ createElement: function(type,ord){ return $('<div />').addClass(type+' ord-'+ord).data('ord',ord); }, //}}} //moveTo: function(x,y){{{ moveTo: function(x,y){ this.element.css({top: y+'px', left: x+'px'}); }, //}}} // blur: function(){{{ blur: function(){ this.element.blur(); return this; }, // }}} // focus: function(){{{ focus: function(){ this.core.setSelection(this); this.frame.focus(); return this; }, // }}} //resize: function(w,h){{{ resize: function(w,h){ this.element.css({width: w+'px', height: h+'px'}); }, //}}} //get: function(){{{ get: function(){ var b = this.element, o = b.position(), w = b.width(), h = b.height(), rv = { x: o.left, y: o.top }; rv.x2 = rv.x + w; rv.y2 = rv.y + h; rv.w = w; rv.h = h; return rv; }, //}}} //insertElements: function(){{{ insertElements: function(){ var t = this, i, m = t.core, fr = t.element, o = t.core.opt, b = o.borders, h = o.handles, d = o.dragbars; for(i=0; i<d.length; i++) fr.append(t.createElement(o.css_dragbars,d[i])); for(i=0; i<h.length; i++) fr.append(t.createElement(o.css_handles,h[i])); for(i=0; i<b.length; i++) fr.append(t.createElement(o.css_borders,b[i])); } //}}} } }); Jcrop.registerComponent('Selection',Selection); /** * StageDrag * Facilitates dragging */ // var StageDrag = function(manager,opt){{{ var StageDrag = function(manager,opt){ $.extend(this,StageDrag.defaults,opt || {}); this.manager = manager; this.core = manager.core; }; // }}} // StageDrag.defaults = {{{ StageDrag.defaults = { offset: [ -8, -8 ], active: true, minsize: [ 20, 20 ] }; // }}} $.extend(StageDrag.prototype,{ // start: function(e){{{ start: function(e){ var c = this.core; // Do nothing if allowSelect is off if (!c.opt.allowSelect) return; // Also do nothing if we can't draw any more selections if (c.opt.multi && c.opt.multiMax && (c.ui.multi.length >= c.opt.multiMax)) return false; // calculate a few variables for this drag operation var o = $(e.currentTarget).offset(); var origx = e.pageX - o.left + this.offset[0]; var origy = e.pageY - o.top + this.offset[1]; var m = c.ui.multi; // Determine newly dragged crop behavior if multi disabled if (!c.opt.multi) { // For multiCleaanup true, remove all existing selections if (c.opt.multiCleanup){ for(var i=0;i<m.length;i++) m[i].remove(); c.ui.multi = []; } // If not, only remove the currently active selection else { c.removeSelection(c.ui.selection); } } c.container.addClass('jcrop-dragging'); // Create the new selection var sel = c.newSelection() // and position it .updateRaw(Jcrop.wrapFromXywh([origx,origy,1,1])); sel.element.trigger('cropstart',[sel,this.core.unscale(sel.get())]); return sel.startDrag(e,'se'); }, // }}} // end: function(x,y){{{ end: function(x,y){ this.drag(x,y); var b = this.sel.get(); this.core.container.removeClass('jcrop-dragging'); if ((b.w < this.minsize[0]) || (b.h < this.minsize[1])) this.core.requestDelete(); else this.sel.focus(); } // }}} }); Jcrop.registerComponent('StageDrag',StageDrag); /** * StageManager * Provides basic stage-specific functionality */ // var StageManager = function(core){{{ var StageManager = function(core){ this.core = core; this.ui = core.ui; this.init(); }; // }}} $.extend(StageManager.prototype,{ // init: function(){{{ init: function(){ this.setupEvents(); this.dragger = new StageDrag(this); }, // }}} // tellConfigUpdate: function(options){{{ tellConfigUpdate: function(options){ for(var i=0,m=this.ui.multi,l=m.length;i<l;i++) if (m[i].setOptions && (m[i].linked || (this.core.opt.linkCurrent && m[i] == this.ui.selection))) m[i].setOptions(options); }, // }}} // startDragHandler: function(){{{ startDragHandler: function(){ var t = this; return function(e){ if (!e.button || t.core.opt.is_ie_lt9) return t.dragger.start(e); }; }, // }}} // removeEvents: function(){{{ removeEvents: function(){ this.core.event.off('.jcrop-stage'); this.core.container.off('.jcrop-stage'); }, // }}} // shimLegacyHandlers: function(options){{{ // This method uses the legacyHandlers configuration object to // gracefully wrap old-style Jcrop events with new ones shimLegacyHandlers: function(options){ var _x = {}, core = this.core, tmp; $.each(core.opt.legacyHandlers,function(k,i){ if (k in options) { tmp = options[k]; core.container.off('.jcrop-'+k) .on(i+'.jcrop.jcrop-'+k,function(e,s,c){ tmp.call(core,c); }); delete options[k]; } }); }, // }}} // setupEvents: function(){{{ setupEvents: function(){ var t = this, c = t.core; c.event.on('configupdate.jcrop-stage',function(e){ t.shimLegacyHandlers(c.opt); t.tellConfigUpdate(c.opt) c.container.trigger('cropconfig',[c,c.opt]); }); this.core.container .on('mousedown.jcrop.jcrop-stage',this.startDragHandler()); } // }}} }); Jcrop.registerComponent('StageManager',StageManager); var Thumbnailer = function(){ }; $.extend(Thumbnailer,{ defaults: { // Set to a specific Selection object // If this value is set, the preview will only track that Selection selection: null, fading: true, fadeDelay: 1000, fadeDuration: 1000, autoHide: false, width: 80, height: 80, _hiding: null }, prototype: { recopyCanvas: function(){ var s = this.core.ui.stage, cxt = s.context; this.context.putImageData(cxt.getImageData(0,0,s.canvas.width,s.canvas.height),0,0); }, init: function(core,options){ var t = this; this.core = core; $.extend(this,Thumbnailer.defaults,options); t.initEvents(); t.refresh(); t.insertElements(); if (t.selection) { t.renderSelection(t.selection); t.selectionTarget = t.selection.element[0]; } else if (t.core.ui.selection) { t.renderSelection(t.core.ui.selection); } if (t.core.ui.stage.canvas) { t.context = t.preview[0].getContext('2d'); t.core.container.on('cropredraw',function(e){ t.recopyCanvas(); t.refresh(); }); } }, updateImage: function(imgel){ this.preview.remove(); this.preview = $($.Jcrop.imageClone(imgel)); this.element.append(this.preview); this.refresh(); return this; }, insertElements: function(){ this.preview = $($.Jcrop.imageClone(this.core.ui.stage.imgsrc)); this.element = $('<div />').addClass('jcrop-thumb') .width(this.width).height(this.height) .append(this.preview) .appendTo(this.core.container); }, resize: function(w,h){ this.width = w; this.height = h; this.element.width(w).height(h); this.renderCoords(this.last); }, refresh: function(){ this.cw = (this.core.opt.xscale * this.core.container.width()); this.ch = (this.core.opt.yscale * this.core.container.height()); if (this.last) { this.renderCoords(this.last); } }, renderCoords: function(c){ var rx = this.width / c.w; var ry = this.height / c.h; this.preview.css({ width: Math.round(rx * this.cw) + 'px', height: Math.round(ry * this.ch) + 'px', marginLeft: '-' + Math.round(rx * c.x) + 'px', marginTop: '-' + Math.round(ry * c.y) + 'px' }); this.last = c; return this; }, renderSelection: function(s){ return this.renderCoords(s.core.unscale(s.get())); }, selectionStart: function(s){ this.renderSelection(s); }, show: function(){ if (this._hiding) clearTimeout(this._hiding); if (!this.fading) this.element.stop().css({ opacity: 1 }); else this.element.stop().animate({ opacity: 1 },{ duration: 80, queue: false }); }, hide: function(){ var t = this; if (!t.fading) t.element.hide(); else t._hiding = setTimeout(function(){ t._hiding = null; t.element.stop().animate({ opacity: 0 },{ duration: t.fadeDuration, queue: false }); },t.fadeDelay); }, initEvents: function(){ var t = this; t.core.container.on('croprotstart croprotend cropimage cropstart cropmove cropend',function(e,s,c){ if (t.selectionTarget && (t.selectionTarget !== e.target)) return false; switch(e.type){ case 'cropimage': t.updateImage(c); break; case 'cropstart': t.selectionStart(s); case 'croprotstart': t.show(); break; case 'cropend': t.renderCoords(c); case 'croprotend': if (t.autoHide) t.hide(); break; case 'cropmove': t.renderCoords(c); break; } }); } } }); Jcrop.registerComponent('Thumbnailer',Thumbnailer); /** * DialDrag component * This is a little hacky, it was adapted from some previous/old code * Plan to update this API in the future */ var DialDrag = function() { }; DialDrag.prototype = { init: function(core,actuator,callback){ var that = this; if (!actuator) actuator = core.container; this.$btn = $(actuator); this.$targ = $(actuator); this.core = core; this.$btn .addClass('dialdrag') .on('mousedown.dialdrag',this.mousedown()) .data('dialdrag',this); if (!$.isFunction(callback)) callback = function(){ }; this.callback = callback; this.ondone = callback; }, remove: function(){ this.$btn .removeClass('dialdrag') .off('.dialdrag') .data('dialdrag',null); return this; }, setTarget: function(obj){ this.$targ = $(obj); return this; }, getOffset: function(){ var targ = this.$targ, pos = targ.offset(); return [ pos.left + (targ.width()/2), pos.top + (targ.height()/2) ]; }, relMouse: function(e){ var x = e.pageX - this.offset[0], y = e.pageY - this.offset[1], ang = Math.atan2(y,x) * (180 / Math.PI), vec = Math.sqrt(Math.pow(x,2)+Math.pow(y,2)); return [ x, y, ang, vec ]; }, mousedown: function(){ var that = this; function mouseUp(e){ $(window).off('.dialdrag'); that.ondone.call(that,that.relMouse(e)); that.core.container.trigger('croprotend'); } function mouseMove(e){ that.callback.call(that,that.relMouse(e)); } return function(e) { that.offset = that.getOffset(); var rel = that.relMouse(e); that.angleOffset = -that.core.ui.stage.angle+rel[2]; that.distOffset = rel[3]; that.dragOffset = [rel[0],rel[1]]; that.core.container.trigger('croprotstart'); $(window) .on('mousemove.dialdrag',mouseMove) .on('mouseup.dialdrag',mouseUp); that.callback.call(that,that.relMouse(e)); return false; }; } }; Jcrop.registerComponent('DialDrag',DialDrag); ///////////////////////////////// // DEFAULT SETTINGS Jcrop.defaults = { // Selection Behavior edge: { n: 0, s: 0, e: 0, w: 0 }, setSelect: null, linked: true, linkCurrent: true, canDelete: true, canSelect: true, canDrag: true, canResize: true, // Component constructors eventManagerComponent: Jcrop.component.EventManager, keyboardComponent: Jcrop.component.Keyboard, dragstateComponent: Jcrop.component.DragState, stagemanagerComponent: Jcrop.component.StageManager, animatorComponent: Jcrop.component.Animator, selectionComponent: Jcrop.component.Selection, // This is a function that is called, which returns a stage object stageConstructor: Jcrop.stageConstructor, // Stage Behavior allowSelect: true, multi: false, multiMax: false, multiCleanup: true, animation: true, animEasing: 'swing', animDuration: 400, fading: true, fadeDuration: 300, fadeEasing: 'swing', bgColor: 'black', bgOpacity: .5, // Startup options applyFilters: [ 'constrain', 'extent', 'backoff', 'ratio', 'shader', 'round' ], borders: [ 'e', 'w', 's', 'n' ], handles: [ 'n', 's', 'e', 'w', 'sw', 'ne', 'nw', 'se' ], dragbars: [ 'n', 'e', 'w', 's' ], dragEventTarget: window, xscale: 1, yscale: 1, boxWidth: null, boxHeight: null, // CSS Classes // @todo: These need to be moved to top-level object keys // for better customization. Currently if you try to extend one // via an options object to Jcrop, it will wipe out all // the others you don't specify. Be careful for now! css_nodrag: 'jcrop-nodrag', css_drag: 'jcrop-drag', css_container: 'jcrop-active', css_shades: 'jcrop-shades', css_selection: 'jcrop-selection', css_borders: 'jcrop-border', css_handles: 'jcrop-handle jcrop-drag', css_button: 'jcrop-box jcrop-drag', css_noresize: 'jcrop-noresize', css_dragbars: 'jcrop-dragbar jcrop-drag', legacyHandlers: { onChange: 'cropmove', onSelect: 'cropend' } }; // Jcrop API methods $.extend(Jcrop.prototype,{ //init: function(){{{ init: function(){ this.event = new this.opt.eventManagerComponent(this); this.ui.keyboard = new this.opt.keyboardComponent(this); this.ui.manager = new this.opt.stagemanagerComponent(this); this.applyFilters(); if ($.Jcrop.supportsTouch) new $.Jcrop.component.Touch(this); this.initEvents(); }, //}}} // applySizeConstraints: function(){{{ applySizeConstraints: function(){ var o = this.opt, img = this.opt.imgsrc; if (img){ var iw = img.naturalWidth || img.width, ih = img.naturalHeight || img.height, bw = o.boxWidth || iw, bh = o.boxHeight || ih; if (img && ((iw > bw) || (ih > bh))){ var bx = Jcrop.getLargestBox(iw/ih,bw,bh); $(img).width(bx[0]).height(bx[1]); this.resizeContainer(bx[0],bx[1]); this.opt.xscale = iw / bx[0]; this.opt.yscale = ih / bx[1]; } } if (this.opt.trueSize){ var dw = this.opt.trueSize[0]; var dh = this.opt.trueSize[1]; var cs = this.getContainerSize(); this.opt.xscale = dw / cs[0]; this.opt.yscale = dh / cs[1]; } }, // }}} initComponent: function(name){ if (Jcrop.component[name]) { var args = Array.prototype.slice.call(arguments); var obj = new Jcrop.component[name]; args.shift(); args.unshift(this); obj.init.apply(obj,args); return obj; } }, // setOptions: function(opt){{{ setOptions: function(opt,proptype){ if (!$.isPlainObject(opt)) opt = {}; $.extend(this.opt,opt); // Handle a setSelect value if (this.opt.setSelect) { // If there is no current selection // passing setSelect will create one if (!this.ui.multi.length) this.newSelection(); // Use these values to update the current selection this.setSelect(this.opt.setSelect); // Set to null so it doesn't get called again this.opt.setSelect = null; } this.event.trigger('configupdate'); return this; }, // }}} //destroy: function(){{{ destroy: function(){ if (this.opt.imgsrc) { this.container.before(this.opt.imgsrc); this.container.remove(); $(this.opt.imgsrc).removeData('Jcrop').show(); } else { // @todo: more elegant destroy() process for non-image containers this.container.remove(); } }, // }}} // applyFilters: function(){{{ applyFilters: function(){ var obj; for(var i=0,f=this.opt.applyFilters,l=f.length; i<l; i++){ if ($.Jcrop.filter[f[i]]) obj = new $.Jcrop.filter[f[i]]; obj.core = this; if (obj.init) obj.init(); this.filter[f[i]] = obj; } }, // }}} // getDefaultFilters: function(){{{ getDefaultFilters: function(){ var rv = []; for(var i=0,f=this.opt.applyFilters,l=f.length; i<l; i++) if(this.filter.hasOwnProperty(f[i])) rv.push(this.filter[f[i]]); rv.sort(function(x,y){ return x.priority - y.priority; }); return rv; }, // }}} // setSelection: function(sel){{{ setSelection: function(sel){ var m = this.ui.multi; var n = []; for(var i=0;i<m.length;i++) { if (m[i] !== sel) n.push(m[i]); m[i].toBack(); } n.unshift(sel); this.ui.multi = n; this.ui.selection = sel; sel.toFront(); return sel; }, // }}} // getSelection: function(raw){{{ getSelection: function(raw){ var b = this.ui.selection.get(); return b; }, // }}} // newSelection: function(){{{ newSelection: function(sel){ if (!sel) sel = new this.opt.selectionComponent(); sel.init(this); this.setSelection(sel); return sel; }, // }}} // hasSelection: function(sel){{{ hasSelection: function(sel){ for(var i=0;i<this.ui.multi;i++) if (sel === this.ui.multi[i]) return true; }, // }}} // removeSelection: function(sel){{{ removeSelection: function(sel){ var i, n = [], m = this.ui.multi; for(var i=0;i<m.length;i++){ if (sel !== m[i]) n.push(m[i]); else m[i].remove(); } return this.ui.multi = n; }, // }}} //addFilter: function(filter){{{ addFilter: function(filter){ for(var i=0,m=this.ui.multi,l=m.length; i<l; i++) m[i].addFilter(filter); return this; }, //}}} // removeFiltersByTag: function(tag){{{ removeFilter: function(filter){ for(var i=0,m=this.ui.multi,l=m.length; i<l; i++) m[i].removeFilter(filter); return this; }, // }}} // blur: function(){{{ blur: function(){ this.ui.selection.blur(); return this; }, // }}} // focus: function(){{{ focus: function(){ this.ui.selection.focus(); return this; }, // }}} //initEvents: function(){{{ initEvents: function(){ var t = this; t.container.on('selectstart',function(e){ return false; }) .on('mousedown','.'+t.opt.css_drag,t.startDrag()); }, //}}} // maxSelect: function(){{{ maxSelect: function(){ this.setSelect([0,0,this.elw,this.elh]); }, // }}} // nudge: function(x,y){{{ nudge: function(x,y){ var s = this.ui.selection, b = s.get(); b.x += x; b.x2 += x; b.y += y; b.y2 += y; if (b.x < 0) { b.x2 = b.w; b.x = 0; } else if (b.x2 > this.elw) { b.x2 = this.elw; b.x = b.x2 - b.w; } if (b.y < 0) { b.y2 = b.h; b.y = 0; } else if (b.y2 > this.elh) { b.y2 = this.elh; b.y = b.y2 - b.h; } s.element.trigger('cropstart',[s,this.unscale(b)]); s.updateRaw(b,'move'); s.element.trigger('cropend',[s,this.unscale(b)]); }, // }}} // refresh: function(){{{ refresh: function(){ for(var i=0,s=this.ui.multi,l=s.length;i<l;i++) s[i].refresh(); }, // }}} // blurAll: function(){{{ blurAll: function(){ var m = this.ui.multi; for(var i=0;i<m.length;i++) { if (m[i] !== sel) n.push(m[i]); m[i].toBack(); } }, // }}} // scale: function(b){{{ scale: function(b){ var xs = this.opt.xscale, ys = this.opt.yscale; return { x: b.x / xs, y: b.y / ys, x2: b.x2 / xs, y2: b.y2 / ys, w: b.w / xs, h: b.h / ys }; }, // }}} // unscale: function(b){{{ unscale: function(b){ var xs = this.opt.xscale, ys = this.opt.yscale; return { x: b.x * xs, y: b.y * ys, x2: b.x2 * xs, y2: b.y2 * ys, w: b.w * xs, h: b.h * ys }; }, // }}} // requestDelete: function(){{{ requestDelete: function(){ if ((this.ui.multi.length > 1) && (this.ui.selection.canDelete)) return this.deleteSelection(); }, // }}} // deleteSelection: function(){{{ deleteSelection: function(){ if (this.ui.selection) { this.removeSelection(this.ui.selection); if (this.ui.multi.length) this.ui.multi[0].focus(); this.ui.selection.refresh(); } }, // }}} // animateTo: function(box){{{ animateTo: function(box){ if (this.ui.selection) this.ui.selection.animateTo(box); return this; }, // }}} // setselect: function(box){{{ setSelect: function(box){ if (this.ui.selection) this.ui.selection.update(Jcrop.wrapFromXywh(box)); return this; }, // }}} //startDrag: function(){{{ startDrag: function(){ var t = this; return function(e){ var $targ = $(e.target); var selection = $targ.closest('.'+t.opt.css_selection).data('selection'); var ord = $targ.data('ord'); t.container.trigger('cropstart',[selection,t.unscale(selection.get())]); selection.startDrag(e,ord); return false; }; }, //}}} // getContainerSize: function(){{{ getContainerSize: function(){ return [ this.container.width(), this.container.height() ]; }, // }}} // resizeContainer: function(w,h){{{ resizeContainer: function(w,h){ this.container.width(w).height(h); this.refresh(); }, // }}} // setImage: function(src,cb){{{ setImage: function(src,cb){ var t = this, targ = t.opt.imgsrc; if (!targ) return false; new $.Jcrop.component.ImageLoader(src,null,function(w,h){ t.resizeContainer(w,h); targ.src = src; $(targ).width(w).height(h); t.applySizeConstraints(); t.refresh(); t.container.trigger('cropimage',[t,targ]); if (typeof cb == 'function') cb.call(t,w,h); }); }, // }}} // update: function(b){{{ update: function(b){ if (this.ui.selection) this.ui.selection.update(b); } // }}} }); // Jcrop jQuery plugin function $.fn.Jcrop = function(options,callback){ options = options || {}; var first = this.eq(0).data('Jcrop'); var args = Array.prototype.slice.call(arguments); // Return API if requested if (options == 'api') { return first; } // Allow calling API methods (with arguments) else if (first && (typeof options == 'string')) { // Call method if it exists if (first[options]) { args.shift(); first[options].apply(first,args); return first; } // Unknown input/method does not exist return false; } // Otherwise, loop over selected elements this.each(function(){ var t = this, $t = $(this); var exists = $t.data('Jcrop'); var obj; // If Jcrop already exists on this element only setOptions() if (exists) exists.setOptions(options); else { if (!options.stageConstructor) options.stageConstructor = $.Jcrop.stageConstructor; options.stageConstructor(this,options,function(stage,options){ var selection = options.setSelect; if (selection) delete(options.setSelect); var obj = $.Jcrop.attach(stage.element,options); if (typeof stage.attach == 'function') stage.attach(obj); $t.data('Jcrop',obj); if (selection) { obj.newSelection(); obj.setSelect(selection); } if (typeof callback == 'function') callback.call(obj); }); } return this; }); }; /* Modernizr 2.7.1 (Custom Build) | MIT & BSD * Build: http://modernizr.com/download/#-csstransforms-canvas-canvastext-draganddrop-inlinesvg-svg-svgclippaths-touch-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-url_data_uri */ ; var Modernizr = (function( window, document, undefined ) { var version = '2.7.1', Modernizr = {}, docElement = document.documentElement, mod = 'modernizr', modElem = document.createElement(mod), mStyle = modElem.style, inputElem , toString = {}.toString, prefixes = ' -webkit- -moz- -o- -ms- '.split(' '), omPrefixes = 'Webkit Moz O ms', cssomPrefixes = omPrefixes.split(' '), domPrefixes = omPrefixes.toLowerCase().split(' '), ns = {'svg': 'http://www.w3.org/2000/svg'}, tests = {}, inputs = {}, attrs = {}, classes = [], slice = classes.slice, featureName, injectElementWithStyles = function( rule, callback, nodes, testnames ) { var style, ret, node, docOverflow, div = document.createElement('div'), body = document.body, fakeBody = body || document.createElement('body'); if ( parseInt(nodes, 10) ) { while ( nodes-- ) { node = document.createElement('div'); node.id = testnames ? testnames[nodes] : mod + (nodes + 1); div.appendChild(node); } } style = ['­','<style id="s', mod, '">', rule, '</style>'].join(''); div.id = mod; (body ? div : fakeBody).innerHTML += style; fakeBody.appendChild(div); if ( !body ) { fakeBody.style.background = ''; fakeBody.style.overflow = 'hidden'; docOverflow = docElement.style.overflow; docElement.style.overflow = 'hidden'; docElement.appendChild(fakeBody); } ret = callback(div, rule); if ( !body ) { fakeBody.parentNode.removeChild(fakeBody); docElement.style.overflow = docOverflow; } else { div.parentNode.removeChild(div); } return !!ret; }, isEventSupported = (function() { var TAGNAMES = { 'select': 'input', 'change': 'input', 'submit': 'form', 'reset': 'form', 'error': 'img', 'load': 'img', 'abort': 'img' }; function isEventSupported( eventName, element ) { element = element || document.createElement(TAGNAMES[eventName] || 'div'); eventName = 'on' + eventName; var isSupported = eventName in element; if ( !isSupported ) { if ( !element.setAttribute ) { element = document.createElement('div'); } if ( element.setAttribute && element.removeAttribute ) { element.setAttribute(eventName, ''); isSupported = is(element[eventName], 'function'); if ( !is(element[eventName], 'undefined') ) { element[eventName] = undefined; } element.removeAttribute(eventName); } } element = null; return isSupported; } return isEventSupported; })(), _hasOwnProperty = ({}).hasOwnProperty, hasOwnProp; if ( !is(_hasOwnProperty, 'undefined') && !is(_hasOwnProperty.call, 'undefined') ) { hasOwnProp = function (object, property) { return _hasOwnProperty.call(object, property); }; } else { hasOwnProp = function (object, property) { return ((property in object) && is(object.constructor.prototype[property], 'undefined')); }; } if (!Function.prototype.bind) { Function.prototype.bind = function bind(that) { var target = this; if (typeof target != "function") { throw new TypeError(); } var args = slice.call(arguments, 1), bound = function () { if (this instanceof bound) { var F = function(){}; F.prototype = target.prototype; var self = new F(); var result = target.apply( self, args.concat(slice.call(arguments)) ); if (Object(result) === result) { return result; } return self; } else { return target.apply( that, args.concat(slice.call(arguments)) ); } }; return bound; }; } function setCss( str ) { mStyle.cssText = str; } function setCssAll( str1, str2 ) { return setCss(prefixes.join(str1 + ';') + ( str2 || '' )); } function is( obj, type ) { return typeof obj === type; } function contains( str, substr ) { return !!~('' + str).indexOf(substr); } function testProps( props, prefixed ) { for ( var i in props ) { var prop = props[i]; if ( !contains(prop, "-") && mStyle[prop] !== undefined ) { return prefixed == 'pfx' ? prop : true; } } return false; } function testDOMProps( props, obj, elem ) { for ( var i in props ) { var item = obj[props[i]]; if ( item !== undefined) { if (elem === false) return props[i]; if (is(item, 'function')){ return item.bind(elem || obj); } return item; } } return false; } function testPropsAll( prop, prefixed, elem ) { var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1), props = (prop + ' ' + cssomPrefixes.join(ucProp + ' ') + ucProp).split(' '); if(is(prefixed, "string") || is(prefixed, "undefined")) { return testProps(props, prefixed); } else { props = (prop + ' ' + (domPrefixes).join(ucProp + ' ') + ucProp).split(' '); return testDOMProps(props, prefixed, elem); } } tests['canvas'] = function() { var elem = document.createElement('canvas'); return !!(elem.getContext && elem.getContext('2d')); }; tests['canvastext'] = function() { return !!(Modernizr['canvas'] && is(document.createElement('canvas').getContext('2d').fillText, 'function')); }; tests['touch'] = function() { var bool; if(('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) { bool = true; } else { injectElementWithStyles(['@media (',prefixes.join('touch-enabled),('),mod,')','{#modernizr{top:9px;position:absolute}}'].join(''), function( node ) { bool = node.offsetTop === 9; }); } return bool; }; tests['draganddrop'] = function() { var div = document.createElement('div'); return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div); }; tests['csstransforms'] = function() { return !!testPropsAll('transform'); }; tests['svg'] = function() { return !!document.createElementNS && !!document.createElementNS(ns.svg, 'svg').createSVGRect; }; tests['inlinesvg'] = function() { var div = document.createElement('div'); div.innerHTML = '<svg/>'; return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; }; tests['svgclippaths'] = function() { return !!document.createElementNS && /SVGClipPath/.test(toString.call(document.createElementNS(ns.svg, 'clipPath'))); }; for ( var feature in tests ) { if ( hasOwnProp(tests, feature) ) { featureName = feature.toLowerCase(); Modernizr[featureName] = tests[feature](); classes.push((Modernizr[featureName] ? '' : 'no-') + featureName); } } Modernizr.addTest = function ( feature, test ) { if ( typeof feature == 'object' ) { for ( var key in feature ) { if ( hasOwnProp( feature, key ) ) { Modernizr.addTest( key, feature[ key ] ); } } } else { feature = feature.toLowerCase(); if ( Modernizr[feature] !== undefined ) { return Modernizr; } test = typeof test == 'function' ? test() : test; if (typeof enableClasses !== "undefined" && enableClasses) { docElement.className += ' ' + (test ? '' : 'no-') + feature; } Modernizr[feature] = test; } return Modernizr; }; setCss(''); modElem = inputElem = null; Modernizr._version = version; Modernizr._prefixes = prefixes; Modernizr._domPrefixes = domPrefixes; Modernizr._cssomPrefixes = cssomPrefixes; Modernizr.hasEvent = isEventSupported; Modernizr.testProp = function(prop){ return testProps([prop]); }; Modernizr.testAllProps = testPropsAll; Modernizr.testStyles = injectElementWithStyles; return Modernizr; })(window, window.document); // data uri test. // https://github.com/Modernizr/Modernizr/issues/14 // This test is asynchronous. Watch out. // in IE7 in HTTPS this can cause a Mixed Content security popup. // github.com/Modernizr/Modernizr/issues/362 // To avoid that you can create a new iframe and inject this.. perhaps.. (function(){ var datauri = new Image(); datauri.onerror = function() { Modernizr.addTest('datauri', function () { return false; }); }; datauri.onload = function() { Modernizr.addTest('datauri', function () { return (datauri.width == 1 && datauri.height == 1); }); }; datauri.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw=="; })(); ; // Attach to jQuery object $.Jcrop = Jcrop; $.Jcrop.supportsCanvas = Modernizr.canvas; $.Jcrop.supportsCanvasText = Modernizr.canvastext; $.Jcrop.supportsDragAndDrop = Modernizr.draganddrop; $.Jcrop.supportsDataURI = Modernizr.datauri; $.Jcrop.supportsSVG = Modernizr.svg; $.Jcrop.supportsInlineSVG = Modernizr.inlinesvg; $.Jcrop.supportsSVGClipPaths = Modernizr.svgclippaths; $.Jcrop.supportsCSSTransforms = Modernizr.csstransforms; $.Jcrop.supportsTouch = Modernizr.touch; })(jQuery);