You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
626 lines
18 KiB
626 lines
18 KiB
define(["require", "dojo", "dijit", "dojox", "dojo/text!./resources/Lightbox.html", "dijit/Dialog", "dojox/fx/_base"], function(require, dojo, dijit, dojox, template){ |
|
|
|
dojo.experimental("dojox.image.Lightbox"); |
|
dojo.getObject("image", true, dojox); |
|
|
|
var Lightbox = dojo.declare("dojox.image.Lightbox", dijit._Widget, { |
|
// summary: |
|
// A dojo-based Lightbox implementation. |
|
// description: |
|
// An Elegant, keyboard accessible, markup and store capable Lightbox widget to show images |
|
// in a modal dialog-esque format. Can show individual images as Modal dialog, or can group |
|
// images with multiple entry points, all using a single "master" Dialog for visualization |
|
// |
|
// key controls: |
|
// |
|
// - ESC - close |
|
// - Down Arrow / Rt Arrow / N - Next Image |
|
// - Up Arrow / Lf Arrow / P - Previous Image |
|
// example: |
|
// | <a href="image1.jpg" dojoType="dojox.image.Lightbox">show lightbox</a> |
|
// example: |
|
// | <a href="image2.jpg" dojoType="dojox.image.Lightbox" group="one">show group lightbox</a> |
|
// | <a href="image3.jpg" dojoType="dojox.image.Lightbox" group="one">show group lightbox</a> |
|
// example: |
|
// | not implemented fully yet, though works with basic datastore access. need to manually call |
|
// | widget._attachedDialog.addImage(item,"fromStore") for each item in a store result set. |
|
// | <div dojoType="dojox.image.Lightbox" group="fromStore" store="storeName"></div> |
|
|
|
// group: String |
|
// Grouping images in a page with similar tags will provide a 'slideshow' like grouping of images |
|
group: "", |
|
|
|
// title: String |
|
// A string of text to be shown in the Lightbox beneath the image (empty if using a store) |
|
title: "", |
|
|
|
// href; String |
|
// Link to image to use for this Lightbox node (empty if using a store). |
|
href: "", |
|
|
|
// duration: Integer |
|
// Generic time in MS to adjust the feel of widget. could possibly add various |
|
// durations for the various actions (dialog fadein, sizeing, img fadein ...) |
|
duration: 500, |
|
|
|
// modal: Boolean |
|
// If true, this Dialog instance will be truly modal and prevent closing until |
|
// explicitly told to by calling hide() or clicking the (x) - Defaults to false |
|
// to preserve previous behaviors. (aka: enable click-to-close on the underlay) |
|
modal: false, |
|
|
|
// _allowPassthru: Boolean |
|
// Privately set this to disable/enable natural link of anchor tags |
|
_allowPassthru: false, |
|
|
|
// _attachedDialog: dojox.image._LightboxDialog |
|
// The pointer to the global lightbox dialog for this widget |
|
_attachedDialog: null, // try to share a single underlay per page? |
|
|
|
startup: function(){ |
|
this.inherited(arguments); |
|
// setup an attachment to the masterDialog (or create the masterDialog) |
|
var tmp = dijit.byId('dojoxLightboxDialog'); |
|
if(tmp){ |
|
this._attachedDialog = tmp; |
|
}else{ |
|
// this is the first instance to start, so we make the masterDialog |
|
this._attachedDialog = new dojox.image.LightboxDialog({ id: "dojoxLightboxDialog" }); |
|
this._attachedDialog.startup(); |
|
} |
|
if(!this.store){ |
|
// FIXME: full store support lacking, have to manually call this._attachedDialog.addImage(imgage,group) as it stands |
|
this._addSelf(); |
|
this.connect(this.domNode, "onclick", "_handleClick"); |
|
} |
|
|
|
}, |
|
|
|
_addSelf: function(){ |
|
// summary: |
|
// Add this instance to the master LightBoxDialog |
|
this._attachedDialog.addImage({ |
|
href: this.href, |
|
title: this.title |
|
}, this.group || null); |
|
}, |
|
|
|
_handleClick: function(/* Event */e){ |
|
// summary: |
|
// Handle the click on the link |
|
if(!this._allowPassthru){ e.preventDefault(); } |
|
else{ return; } |
|
this.show(); |
|
}, |
|
|
|
show: function(){ |
|
// summary: |
|
// Show the Lightbox with this instance as the starting point |
|
this._attachedDialog.show(this); |
|
}, |
|
|
|
hide: function(){ |
|
// summary: |
|
// Hide the Lightbox currently showing |
|
this._attachedDialog.hide(); |
|
}, |
|
|
|
// FIXME: switch to .attr, deprecate eventually. |
|
disable: function(){ |
|
// summary: |
|
// Disables event clobbering and dialog, and follows natural link |
|
this._allowPassthru = true; |
|
}, |
|
|
|
enable: function(){ |
|
// summary: |
|
// Enables the dialog (prevents default link) |
|
this._allowPassthru = false; |
|
}, |
|
|
|
onClick: function(){ |
|
// summary: |
|
// Stub fired when the image in the lightbox is clicked. |
|
}, |
|
|
|
destroy: function(){ |
|
this._attachedDialog.removeImage(this); |
|
this.inherited(arguments); |
|
} |
|
|
|
}); |
|
|
|
Lightbox.LightboxDialog = dojo.declare("dojox.image.LightboxDialog", |
|
dijit.Dialog, { |
|
// summary: |
|
// The "dialog" shared between any Lightbox instances on the page, publically available |
|
// for programmatic manipulation. |
|
// description: |
|
// A widget that intercepts anchor links (typically around images) |
|
// and displays a modal Dialog. this is the actual Dialog, which you can |
|
// create and populate manually, though should use simple Lightbox's |
|
// unless you need the direct access. |
|
// |
|
// There should only be one of these on a page, so all dojox.image.Lightbox's will us it |
|
// (the first instance of a Lightbox to be show()'n will create me If i do not exist) |
|
// example: |
|
// | // show a single image from a url |
|
// | var url = "http://dojotoolkit.org/logo.png"; |
|
// | var dialog = new dojox.image.LightboxDialog().startup(); |
|
// | dialog.show({ href: url, title:"My Remote Image"}); |
|
|
|
// title: String |
|
// The current title, read from object passed to show() |
|
title: "", |
|
|
|
// FIXME: implement titleTemplate |
|
|
|
// inGroup: Array |
|
// Array of objects. this is populated by from the JSON object _groups, and |
|
// should not be populate manually. it is a placeholder for the currently |
|
// showing group of images in this master dialog |
|
inGroup: null, |
|
|
|
// imgUrl: String |
|
// The src="" attribute of our imageNode (can be null at statup) |
|
imgUrl: dijit._Widget.prototype._blankGif, |
|
|
|
// errorMessage: String |
|
// The text to display when an unreachable image is linked |
|
errorMessage: "Image not found.", |
|
|
|
// adjust: Boolean |
|
// If true, ensure the image always stays within the viewport |
|
// more difficult than necessary to disable, but enabled by default |
|
// seems sane in most use cases. |
|
adjust: true, |
|
|
|
// modal: Boolean |
|
// If true, this Dialog instance will be truly modal and prevent closing until |
|
// explicitly told to by calling hide() or clicking the (x) - Defaults to false |
|
// to preserve previous behaviors. (aka: enable click-to-close on the underlay) |
|
modal: false, |
|
|
|
// imageClass: String |
|
// The classname to apply to the image node in the dialog (for extra styling) |
|
imageClass: "dojoxLightboxImage", |
|
|
|
/*===== |
|
// _groups: Object |
|
// an object of arrays, each array (of objects) being a unique 'group' |
|
_groups: { XnoGroupX: [] }, |
|
|
|
=====*/ |
|
|
|
// errorImg: Url |
|
// Path to the image used when a 404 is encountered |
|
errorImg: require.toUrl("./resources/images/warning.png"), |
|
|
|
templateString: template, |
|
|
|
constructor: function(args){ |
|
this._groups = this._groups || (args && args._groups) || { XnoGroupX:[] }; |
|
}, |
|
|
|
startup: function(){ |
|
// summary: |
|
// Add some extra event handlers, and startup our superclass. |
|
// returns: dijit._Widget |
|
// Perhaps the only `dijit._Widget` that returns itself to allow |
|
// 'chaining' or var referencing with .startup() |
|
|
|
this.inherited(arguments); |
|
|
|
this._animConnects = []; |
|
this.connect(this.nextButtonNode, "onclick", "_nextImage"); |
|
this.connect(this.prevButtonNode, "onclick", "_prevImage"); |
|
this.connect(this.closeButtonNode, "onclick", "hide"); |
|
this._makeAnims(); |
|
this._vp = dojo.window.getBox(); |
|
return this; |
|
}, |
|
|
|
show: function(/* Object */groupData){ |
|
// summary: |
|
// Show the Master Dialog. Starts the chain of events to show |
|
// an image in the dialog, including showing the dialog if it is |
|
// not already visible |
|
// groupData: Object |
|
// needs href and title attributes. the values for this image. |
|
|
|
var _t = this; // size |
|
this._lastGroup = groupData; |
|
|
|
// we only need to call dijit.Dialog.show() if we're not already open. |
|
if(!_t.open){ |
|
_t.inherited(arguments); |
|
_t._modalconnects.push( |
|
dojo.connect(dojo.global, "onscroll", this, "_position"), |
|
dojo.connect(dojo.global, "onresize", this, "_position"), |
|
dojo.connect(dojo.body(), "onkeypress", this, "_handleKey") |
|
); |
|
if(!groupData.modal){ |
|
_t._modalconnects.push( |
|
dojo.connect(dijit._underlay.domNode, "onclick", this, "onCancel") |
|
); |
|
} |
|
} |
|
|
|
if(this._wasStyled){ |
|
// ugly fix for IE being stupid. place the new image relative to the old |
|
// image to allow for overriden templates to adjust the location of the |
|
// titlebar. DOM will remain "unchanged" between views. |
|
var tmpImg = dojo.create("img", { |
|
className: _t.imageClass |
|
}, _t.imgNode, "after"); |
|
dojo.destroy(_t.imgNode); |
|
_t.imgNode = tmpImg; |
|
_t._makeAnims(); |
|
_t._wasStyled = false; |
|
} |
|
|
|
dojo.style(_t.imgNode,"opacity","0"); |
|
dojo.style(_t.titleNode,"opacity","0"); |
|
|
|
var src = groupData.href; |
|
|
|
if((groupData.group && groupData !== "XnoGroupX") || _t.inGroup){ |
|
if(!_t.inGroup){ |
|
_t.inGroup = _t._groups[(groupData.group)]; |
|
// determine where we were or are in the show |
|
dojo.forEach(_t.inGroup, function(g, i){ |
|
if(g.href == groupData.href){ |
|
_t._index = i; |
|
//return false; |
|
} |
|
//return true; |
|
}); |
|
} |
|
if(!_t._index){ |
|
_t._index = 0; |
|
var sr = _t.inGroup[_t._index]; |
|
src = (sr && sr.href) || _t.errorImg; |
|
} |
|
// FIXME: implement titleTemplate |
|
_t.groupCount.innerHTML = " (" + (_t._index + 1) + " of " + Math.max(1, _t.inGroup.length) + ")"; |
|
_t.prevButtonNode.style.visibility = "visible"; |
|
_t.nextButtonNode.style.visibility = "visible"; |
|
}else{ |
|
// single images don't have buttons, or counters: |
|
_t.groupCount.innerHTML = ""; |
|
_t.prevButtonNode.style.visibility = "hidden"; |
|
_t.nextButtonNode.style.visibility = "hidden"; |
|
} |
|
if(!groupData.leaveTitle){ |
|
_t.textNode.innerHTML = groupData.title; |
|
} |
|
_t._ready(src); |
|
}, |
|
|
|
_ready: function(src){ |
|
// summary: |
|
// A function to trigger all 'real' showing of some src |
|
|
|
var _t = this; |
|
|
|
// listen for 404's: |
|
_t._imgError = dojo.connect(_t.imgNode, "error", _t, function(){ |
|
dojo.disconnect(_t._imgError); |
|
// trigger the above onload with a new src: |
|
_t.imgNode.src = _t.errorImg; |
|
_t.textNode.innerHTML = _t.errorMessage; |
|
}); |
|
|
|
// connect to the onload of the image |
|
_t._imgConnect = dojo.connect(_t.imgNode, "load", _t, function(e){ |
|
_t.resizeTo({ |
|
w: _t.imgNode.width, |
|
h: _t.imgNode.height, |
|
duration:_t.duration |
|
}); |
|
// cleanup |
|
dojo.disconnect(_t._imgConnect); |
|
if(_t._imgError){ |
|
dojo.disconnect(_t._imgError); |
|
} |
|
}); |
|
|
|
_t.imgNode.src = src; |
|
}, |
|
|
|
_nextImage: function(){ |
|
// summary: |
|
// Load next image in group |
|
if(!this.inGroup){ return; } |
|
if(this._index + 1 < this.inGroup.length){ |
|
this._index++; |
|
}else{ |
|
this._index = 0; |
|
} |
|
this._loadImage(); |
|
}, |
|
|
|
_prevImage: function(){ |
|
// summary: |
|
// Load previous image in group |
|
if(this.inGroup){ |
|
if(this._index == 0){ |
|
this._index = this.inGroup.length - 1; |
|
}else{ |
|
this._index--; |
|
} |
|
this._loadImage(); |
|
} |
|
}, |
|
|
|
_loadImage: function(){ |
|
// summary: |
|
// Do the prep work before we can show another image |
|
this._loadingAnim.play(1); |
|
}, |
|
|
|
_prepNodes: function(){ |
|
// summary: |
|
// A localized hook to accompany _loadImage |
|
this._imageReady = false; |
|
if(this.inGroup && this.inGroup[this._index]){ |
|
this.show({ |
|
href: this.inGroup[this._index].href, |
|
title: this.inGroup[this._index].title |
|
}); |
|
}else{ |
|
this.show({ |
|
title: this.errorMessage, |
|
href: this.errorImg |
|
}); |
|
} |
|
|
|
}, |
|
|
|
_calcTitleSize: function(){ |
|
var sizes = dojo.map(dojo.query("> *", this.titleNode).position(), function(s){ return s.h; }); |
|
return { h: Math.max.apply(Math, sizes) }; |
|
}, |
|
|
|
resizeTo: function(/* Object */size, forceTitle){ |
|
// summary: |
|
// Resize our dialog container, and fire _showImage |
|
|
|
var adjustSize = dojo.boxModel == "border-box" ? |
|
dojo._getBorderExtents(this.domNode).w : 0, |
|
titleSize = forceTitle || this._calcTitleSize() |
|
; |
|
|
|
this._lastTitleSize = titleSize; |
|
|
|
if(this.adjust && |
|
(size.h + titleSize.h + adjustSize + 80 > this._vp.h || |
|
size.w + adjustSize + 60 > this._vp.w |
|
) |
|
){ |
|
this._lastSize = size; |
|
size = this._scaleToFit(size); |
|
} |
|
this._currentSize = size; |
|
|
|
var _sizeAnim = dojox.fx.sizeTo({ |
|
node: this.containerNode, |
|
duration: size.duration||this.duration, |
|
width: size.w + adjustSize, |
|
height: size.h + titleSize.h + adjustSize |
|
}); |
|
this.connect(_sizeAnim, "onEnd", "_showImage"); |
|
_sizeAnim.play(15); |
|
}, |
|
|
|
_scaleToFit: function(/* Object */size){ |
|
// summary: |
|
// resize an image to fit within the bounds of the viewport |
|
// size: Object |
|
// The 'size' object passed around for this image |
|
|
|
var ns = {}, // New size |
|
nvp = { |
|
w: this._vp.w - 80, |
|
h: this._vp.h - 60 - this._lastTitleSize.h |
|
}; // New viewport |
|
|
|
// Calculate aspect ratio |
|
var viewportAspect = nvp.w / nvp.h, |
|
imageAspect = size.w / size.h; |
|
|
|
// Calculate new image size |
|
if(imageAspect >= viewportAspect){ |
|
ns.h = nvp.w / imageAspect; |
|
ns.w = nvp.w; |
|
}else{ |
|
ns.w = imageAspect * nvp.h; |
|
ns.h = nvp.h; |
|
} |
|
|
|
// we actually have to style this image, it's too big |
|
this._wasStyled = true; |
|
this._setImageSize(ns); |
|
|
|
ns.duration = size.duration; |
|
return ns; // Object |
|
}, |
|
|
|
_setImageSize: function(size){ |
|
// summary: |
|
// Reset the image size to some actual size. |
|
var s = this.imgNode; |
|
s.height = size.h; |
|
s.width = size.w; |
|
}, |
|
|
|
// clobber inherited function, it is useless. |
|
_size: function(){}, |
|
|
|
_position: function(/* Event */e){ |
|
// summary: |
|
// we want to know the viewport size any time it changes |
|
this._vp = dojo.window.getBox(); |
|
this.inherited(arguments); |
|
|
|
// determine if we need to scale up or down, if at all. |
|
if(e && e.type == "resize"){ |
|
if(this._wasStyled){ |
|
this._setImageSize(this._lastSize); |
|
this.resizeTo(this._lastSize); |
|
}else{ |
|
if(this.imgNode.height + 80 > this._vp.h || this.imgNode.width + 60 > this._vp.h){ |
|
this.resizeTo({ |
|
w: this.imgNode.width, h: this.imgNode.height |
|
}); |
|
} |
|
} |
|
} |
|
}, |
|
|
|
_showImage: function(){ |
|
// summary: |
|
// Fade in the image, and fire showNav |
|
this._showImageAnim.play(1); |
|
}, |
|
|
|
_showNav: function(){ |
|
// summary: |
|
// Fade in the footer, and setup our connections. |
|
var titleSizeNow = dojo.marginBox(this.titleNode); |
|
if(titleSizeNow.h > this._lastTitleSize.h){ |
|
this.resizeTo(this._wasStyled ? this._lastSize : this._currentSize, titleSizeNow); |
|
}else{ |
|
this._showNavAnim.play(1); |
|
} |
|
}, |
|
|
|
hide: function(){ |
|
// summary: |
|
// Hide the Master Lightbox |
|
dojo.fadeOut({ |
|
node: this.titleNode, |
|
duration: 200, |
|
// #5112 - if you _don't_ change the .src, safari will |
|
// _never_ fire onload for this image |
|
onEnd: dojo.hitch(this, function(){ |
|
this.imgNode.src = this._blankGif; |
|
}) |
|
}).play(5); |
|
|
|
this.inherited(arguments); |
|
|
|
this.inGroup = null; |
|
this._index = null; |
|
}, |
|
|
|
addImage: function(child, group){ |
|
// summary: |
|
// Add an image to this Master Lightbox |
|
// child: Object |
|
// The image information to add. |
|
// |
|
// - href: String - link to image (required) |
|
// - title: String - title to display |
|
// group: String? |
|
// attach to group of similar tag or null for individual image instance |
|
|
|
var g = group; |
|
if(!child.href){ return; } |
|
if(g){ |
|
if(!this._groups[g]){ |
|
this._groups[g] = []; |
|
} |
|
this._groups[g].push(child); |
|
}else{ this._groups["XnoGroupX"].push(child); } |
|
}, |
|
|
|
removeImage: function(/* Widget */child){ |
|
// summary: |
|
// Remove an image instance from this LightboxDialog. |
|
// child: Object |
|
// A reference to the Lightbox child that was added (or an object literal) |
|
// only the .href member is compared for uniqueness. The object may contain |
|
// a .group member as well. |
|
|
|
var g = child.group || "XnoGroupX"; |
|
dojo.every(this._groups[g], function(item, i, ar){ |
|
if(item.href == child.href){ |
|
ar.splice(i, 1); |
|
return false; |
|
} |
|
return true; |
|
}); |
|
}, |
|
|
|
removeGroup: function(group){ |
|
// summary: |
|
// Remove all images in a passed group |
|
if(this._groups[group]){ this._groups[group] = []; } |
|
}, |
|
|
|
_handleKey: function(/* Event */e){ |
|
// summary: |
|
// Handle keyboard navigation internally |
|
if(!this.open){ return; } |
|
|
|
var dk = dojo.keys; |
|
switch(e.charOrCode){ |
|
|
|
case dk.ESCAPE: |
|
this.hide(); |
|
break; |
|
|
|
case dk.DOWN_ARROW: |
|
case dk.RIGHT_ARROW: |
|
case 78: // key "n" |
|
this._nextImage(); |
|
break; |
|
|
|
case dk.UP_ARROW: |
|
case dk.LEFT_ARROW: |
|
case 80: // key "p" |
|
this._prevImage(); |
|
break; |
|
} |
|
}, |
|
|
|
_makeAnims: function(){ |
|
// summary: |
|
// make and cleanup animation and animation connections |
|
|
|
dojo.forEach(this._animConnects, dojo.disconnect); |
|
this._animConnects = []; |
|
this._showImageAnim = dojo.fadeIn({ |
|
node: this.imgNode, |
|
duration: this.duration |
|
}); |
|
this._animConnects.push(dojo.connect(this._showImageAnim, "onEnd", this, "_showNav")); |
|
this._loadingAnim = dojo.fx.combine([ |
|
dojo.fadeOut({ node:this.imgNode, duration:175 }), |
|
dojo.fadeOut({ node:this.titleNode, duration:175 }) |
|
]); |
|
this._animConnects.push(dojo.connect(this._loadingAnim, "onEnd", this, "_prepNodes")); |
|
this._showNavAnim = dojo.fadeIn({ node: this.titleNode, duration:225 }); |
|
}, |
|
|
|
onClick: function(groupData){ |
|
// summary: |
|
// a stub function, called with the currently displayed image as the only argument |
|
}, |
|
|
|
_onImageClick: function(e){ |
|
if(e && e.target == this.imgNode){ |
|
this.onClick(this._lastGroup); |
|
// also fire the onclick for the Lightbox widget which triggered, if you |
|
// aren't working directly with the LBDialog |
|
if(this._lastGroup.declaredClass){ |
|
this._lastGroup.onClick(this._lastGroup); |
|
} |
|
} |
|
} |
|
}); |
|
|
|
return Lightbox; |
|
}); |
|
|
|
|