/**
* Gallerify.js | v0.3.0
* jQuery plugin for making thumbnail galleries with lightbox viewers from unhelpful markup
* @author Calvin Juárez
*/
!function ($) {
/**
* Gallerify.js | v0.3.0
* jQuery plugin for making thumbnail galleries with lightbox viewers from unhelpful markup
* @author Calvin Juárez
*/
!function ($) {
/*! GALLERIFY CLASS DEFINITION */
/*! ============================= */
var Gallerify = function (_element, _options) {
var element = _element
, options = $.extend({}, $.fn.gallerify.defaults) // a clone of the gallerify defaults
check for id and make a new one, if necessary
if (!$(element).attr('id')) {
$(element).attr('id', generateID(1))
function generateID(dex) {
keep checking for elements w/ our id until we have a unique id, then give this element that id
return !$('#gallerify-' + dex)[0] ? 'gallerify-' + dex : generateID(dex + 1)
}
}
clean up the options so only relevant ones are passed to .init()
for(var prop in _options) // only properties declared in `options` that exist in `defaults`
if (options[prop] !== undefined) options[prop] = _options[prop] // `!== undefined` means falsey values still go through
if options.format
is invalid, we don't rebuild and we disable pep, which depends on the rebuild atm
if ($.inArray(options.format,['ul','ol']) <= -1) // `$.inArray` makes this much more maintainable than doing "options.format !== 'this' && …"
options.pep = options.format = false
and initiate
this.init('gallerify', element, options)
}
Gallerify.prototype = {
constructor: Gallerify
, init: function (type, element, options) {
set the properties
this.type = type
this.options = options
this.$element = $(element)
this.id = this.$element.attr('id')
get the items
this.items = (function() {
var items = []
build the items and put them into items
, p
by p
(see what I did there?)
this.$element.find('p').each(function (i,p) {
create a new item object and push it if one of $(p)
's children is an <img>
if ($(p).children().is('img'))
items.push({
caption: []
, feature: {
src: $(p).find('img').attr('src')
, title: $(p).find('img').attr('title')
, alt: $(p).find('img').attr('alt')
}
})
otherwise, push the p
's HTML into the caption[]
of the latest item object
else items[items.length - 1].caption.push($(p).html())
})
return items // an array of item objects
}.call(this)) // this function's `this` our object (`this`)
set up dev mode if it's called for
if (this.options.dev) addLogs(this)
add 'gallerify' class to $element
this.$element.addClass('gallerify')
add the lightbox elements if lightbox isn't disabled and the overlay doesn't exist already
if (this.options.lightbox && !$('#gallerify-overlay')[0]) {
if (this.items.length > 1)
$(document.body).append(
'\n <div id="gallerify-overlay" class="gallerify-overlay">'
+ '\n <div class="gallerify-container" class="gallerify-container">'
+ '\n <div id="gallerify-wrapper" class="gallerify-wrapper">'
+ '\n <a href="#" id="gallerify-dismiss" class="gallerify-dismiss">×</a>'
+ '\n <a href="#" id="gallerify-back" class="gallerify-back"><i>‹</i></a>'
+ '\n <a href="#" id="gallerify-next" class="gallerify-next"><i>›</i></a>'
+ '\n <div class="gallerify-feature"></div>'
+ '\n <div class="gallerify-caption"></div>'
+ '\n </div>\n </div>\n </div>\n')
else
$(document.body).append(
'\n <div id="gallerify-overlay" class="gallerify-overlay">'
+ '\n <div class="gallerify-container" class="gallerify-container">'
+ '\n <div id="gallerify-wrapper" class="gallerify-wrapper">'
+ '\n <a href="#" id="gallerify-dismiss" class="gallerify-dismiss">×</a>'
+ '\n <div class="gallerify-feature"></div>'
+ '\n <div class="gallerify-caption"></div>'
+ '\n </div>\n </div>\n </div>\n')
}
build the new markup (according to options.format
) if there's stuff to build
if (this.items.length > 0) {
var theGallery = this
if options.format
is set to false
, we'll just add anchors so we can still trigger the lightbox
if (!this.options.format) {
this.$element.find('img').each(function (i,img) {
$(img)
.removeAttr('height width')
.wrap('<a href="#" class="' + theGallery.options.itemClasses + '" data-item="' + i + '">')
})
}
otherwise, we update the markup a little more proactively
else {
var $inner = $('<' + this.options.format + ' id="' + this.id + '-inner" class="gallerify-inner">')
add inner element
this.$element.wrapInner($inner)
reset $inner
variable to refer to the actual inner element, rather than a "blueprint"
$inner = this.$element.find('.gallerify-inner')
add thumbs controls for pep
this.options.pep && this.$element
.prepend('<a href="#" id="' + this.id + '-thumbs-right" class="gallerify-thumbs-right"><i>›</i></a>')
.prepend('<a href="#" id="' + this.id + '-thumbs-left" class="gallerify-thumbs-left"><i>‹</i></a>')
itemize the features
$inner.children('p').has('img').each(function (i,p) {
$(p).wrap('<li id="' + theGallery.id + '-item-' + i + '" class="gallerify-item" />')
.wrap('<a href="#" data-item="' + i + '" />')
remove the <p>
element, 'cause it's no longer necessary
.find('img').unwrap()
})
collapse whitespace (we'll add it back later, if it's requested, but it's about to get all wonky)
$inner.html($inner.html().replace(/\n[\t]*[\n\t]/g, ''))
remove the captions to hide them from the thumbnails bit
.children('p').remove()
add white space if it's not nixed (options.indent
)
if (this.options.indent) {
get current tab level and set the indent according to options.indent
var currentTabs = this.$element.parent().html().substr(0, this.$element.parent().html().indexOf('<')).replace(/\n/g,'')
, t = (this.options.indent === 'spaces') ? ' ' : '\t'
add whitespace for $element
this.$element.append('\n' + currentTabs)
add whitespace for the child elements and comments
.contents().filter(function () { return this.nodeType === Node.ELEMENT_NODE || this.nodeType === Node.COMMENT_NODE })
.each(function () {
addWhiteSpace($(this), currentTabs + t)
declared here, 'cause Firefox complained when it was out a scope level
function addWhiteSpace($element, tabs) {
fix whitespace for the passed element
$element.before('\n' + tabs).not('p,a').append('\n' + tabs)
fix whitespace for the child element and comment nodes
.contents().filter(function () { return this.nodeType === Node.ELEMENT_NODE || this.nodeType === Node.COMMENT_NODE })
.each(function () { addWhiteSpace($(this), tabs + t) })
}
})
}
add pep http://pep.briangonzalez.org if there are at least two items
(this.items.length > 1) && addPep(this)
/* //! #enhancement
add pep http://pep.briangonzalez.org if it's kosh' and if there are at least two items
if (this.options.pep && this.items.length > 1) addPep(this)
else addSimpleThumb(this)
*/
add listeners
this.options.lightbox && addListeners(this)
trigger 'built' here, when everything's built
this.$element.trigger('built.gallerify') // a hook event
trigger 'loaded' once all our item images are loaded (gives us a nice hook event for when we're good to go)
var imagesLoaded = 0
this.$element.find('a[data-item] img').on('load.gallerify', function () {
imagesLoaded++
if (imagesLoaded >= theGallery.items.length)
theGallery.$element.trigger('loaded.gallerify') // a hook event
})
}
}
addListeners(theGallery)
function addListeners(theGallery) {
return (function () {
add lightbox trigger on a[data-item]
s
this.$element
.on('click.gallerify.item touchend.gallerify.item MSPointerUp.gallerify.item', 'a[data-item]', function (e) {
e.preventDefault()
theGallery.$element.trigger('show-item.gallerify', [$(this).data('item')])
})
.on('show-item.gallerify', function (e, itemNº) {
note the new item and show it
$('#gallerify-wrapper').data('item', itemNº)
theGallery.show(itemNº)
})
add an esc key listener when the lightbox is up
.on({
'lightbox-show.gallerify': function (e) {
add esc key listener when the lightbox happens
$(document.body).on('keypress.esc.gallerify.dismiss', function(e) {
if (e.keyCode == 27) { e.preventDefault(); theGallery.dismiss() }
})
}
, 'lightbox-dismiss.gallerify': function (e) {
remove esc key listener when the lightbox is dismissed
$(document.body).off('keypress.esc.gallerify.dismiss')
}
})
add dismiss listener for the lightbox
$('#gallerify-overlay, #gallerify-dismiss')
.on('click.gallerify.dismiss touchend.gallerify.item MSPointerUp.gallerify.item', function(e) {
e.preventDefault()
theGallery.dismiss()
})
stop propagation at the wrapper, so clicking the image doesn't dismiss the lightbox
$('#gallerify-wrapper').on('click.gallerify touchend.gallerify MSPointerUp.gallerify', function (e) { e.stopPropagation() })
add back and next listeners, if those elements exist
if (!!$('#gallerify-back')[0]) {
$('#gallerify-back')
.on('click.gallerify.navigate.back touchend.gallerify.navigate.back MSPointerUp.gallerify.navigate.back', function (e) {
e.preventDefault()
theGallery.back()
})
$('#gallerify-next')
.on('click.gallerify.navigate.next touchend.gallerify.navigate.next MSPointerUp.gallerify.navigate.next', function (e) {
e.preventDefault()
theGallery.next()
})
}
}.call(theGallery))
}
addPep(theGallery)
function addPep(theGallery) {
return (function () {
var $element = this.$element
, $inner = this.$element.find('.gallerify-inner')
note the current left position
$inner
.data('margin', $inner.position().left)
and initiate pep
.pep({
axis: 'x'
, constrainTo: parent
, useCSSTranslation: false
, hardwareAccelerate: true
, multiplier: 5
, velocityMultiplier: 0.2
, start: function (e, pep) {
remove the item trigger listener so we won't get the lightbox when we didn't want it
$element.off('show-item.gallerify')
}
, stop: function (e, pep) {
e.preventDefault()
outOfBounds(pep)
updateThumbColtrols(pep)
}
, rest: function (e, pep) {
e.preventDefault()
outOfBounds(pep)
updateThumbColtrols(pep)
add the item trigger listener back
$element.off('show-item.gallerify') // this prevents duplication of the listener
$element.on('show-item.gallerify', function (e, itemNº) {
note the new item and show it
$('#gallerify-wrapper').data('item', itemNº)
theGallery.show(itemNº)
})
}
})
$element
add listener for both thumb controls
.on('click.gallerify.thumb-nav touchend.gallerify.thumb-nav MSPointerUp.gallerify.thumb-nav',
'#' + this.id + '-thumbs-right, #' + this.id + '-thumbs-left', function (e) {
e.preventDefault()
var pep = $inner.data('plugin_pep')
wake pep up, if it's not awake yet
if (pep.options.hardwareAccelerate && !pep.hardwareAccelerated) {
pep.hardwareAccelerate();
pep.hardwareAccelerated = true;
$inner.css(pep.getCSSEaseHash());
}
wait a while, then call the rest function (just in case)
pep.restTimeout = setTimeout(function () {
pep.options.rest.call(pep, e, pep)
}, pep.options.cssEaseDuration + 100) // the wait's just a little longer in case things are gummed up somehow
})
add listener for the left thumb control
.on('click.gallerify.thumb-nav.left touchend.gallerify.thumb-nav.left MSPointerUp.gallerify.thumb-nav.left',
'#' + this.id + '-thumbs-left', function (e) {
var pep = $inner.data('plugin_pep')
, currentLeft = $inner.position().left
, shift = Math.round($inner.innerWidth() / theGallery.items.length) > 100
? Math.round($inner.innerWidth() / theGallery.items.length) // roughly the average item width
: 125 // or else fixed at 125, a nice round number
if we're not at the left-most item
if (currentLeft < 0) {
and going and item-width to the left won't put us out of bounds, slide the view an item-width leftward
if (currentLeft + shift < 0)
pep.moveTo(currentLeft + shift, 0, true)
otherwise just slide to 0,0
else
pep.moveTo(0, 0, true)
}
})
add listener for the right thumb control
.on('click.gallerify.thumb-nav.right touchend.gallerify.thumb-nav.right MSPointerUp.gallerify.thumb-nav.right',
'#' + this.id + '-thumbs-right', function (e) {
var pep = $inner.data('plugin_pep')
, currentLeft = $inner.position().left
, shift = Math.round($inner.innerWidth() / theGallery.items.length) > 100
? Math.round($inner.innerWidth() / theGallery.items.length) // roughly the average item width
: 125 // or else fixed at 125, a nice round number
if we're not at the right-most item
if (-currentLeft < $inner.outerWidth() - $inner.parent().innerWidth()) {
and going and item-width to the right won't put us out of bounds, slide the view an item-width rightward
if ((-currentLeft + shift) < $inner.outerWidth() - $inner.parent().innerWidth())
pep.moveTo(currentLeft - shift, 0, true)
otherwise just slide to as far right as the last item needs
else
pep.moveTo(-($inner.innerWidth() - $inner.parent().innerWidth() + 1), 0, true)
}
})
maybe fix crazy image bug
.one('click.gallerify.thumb-nav touchend.gallerify.thumb-nav MSPointerUp.gallerify.thumb-nav',
'#' + this.id + '-thumbs-right, #' + this.id + '-thumbs-left', function(e) {
trigger redraw or something to fix crazy images issue
$element.gallerify('dismiss')
})
once all the item images are loaded, …
.on('loaded.gallerify', function (e) {
var totalWidth = 0
, pep = $inner.data('plugin_pep')
get total width
$inner.find('.gallerify-item').each(function (i,item) { totalWidth += $(item).outerWidth() })
$inner.css({
height: $inner.find('a[data-item]').outerHeight()
, width: totalWidth
})
$element.css({
height: $inner.children().first().outerHeight()
, width: ($inner.outerWidth() > $element.parent().innerWidth())
? '100%'
: $inner.outerWidth()
, maxWidth: $inner.outerWidth()
})
.addClass('pep') // this adds a few more styles and provides a way for more custom styling when pep is used
if ($inner.outerWidth() <= $element.innerWidth())
$('#' + theGallery.id + '-thumbs-left, #' + theGallery.id + '-thumbs-right').hide()
else
$('#' + theGallery.id + '-thumbs-left, #' + theGallery.id + '-thumbs-right').show()
outOfBounds($inner.data('plugin_pep'))
updateThumbColtrols($inner.data('plugin_pep'))
})
also do some of that "on loaded" stuff when the window's resized
$(window).resize(function() {
$element
's styles (the inner element won't change size, so no need for that here) $element.css({
width: ($inner.outerWidth() > $element.parent().innerWidth()) ? '100%' : $inner.outerWidth()
, maxWidth: $inner.outerWidth()
})
if ($inner.outerWidth() <= $element.innerWidth())
$('#' + theGallery.id + '-thumbs-left, #' + theGallery.id + '-thumbs-right').hide()
else
$('#' + theGallery.id + '-thumbs-left, #' + theGallery.id + '-thumbs-right').show()
outOfBounds($inner.data('plugin_pep'))
updateThumbColtrols($inner.data('plugin_pep'))
})
remove all of our mouseup
handlers on iOS, so we don't register two events
if ((/iphone|ipad/gi).test(navigator.appVersion)) $element.off('mouseup.gallerify')
function outOfBounds(pep) {
if (-pep.$el.position().left > (pep.$el.innerWidth() - pep.$el.parent().innerWidth()))
pep.moveTo(-(pep.$el.outerWidth() - pep.$el.parent().innerWidth()),0,true)
if (pep.$el.position().left > pep.$el.data('margin'))
pep.moveTo(0,0,true)
}
hide the appropriate control if we're at full-left or full-right
function updateThumbColtrols(pep) {
if (-pep.$el.position().left <= 0)
$('#' + theGallery.id + '-thumbs-left').hide()
else
$('#' + theGallery.id + '-thumbs-left').show()
if (-pep.$el.position().left >= pep.$el.outerWidth() - pep.$el.parent().innerWidth())
$('#' + theGallery.id + '-thumbs-right').hide()
else
$('#' + theGallery.id + '-thumbs-right').show()
}
}.call(theGallery))
}
addSimpleThumb(theGallery) (not yet implemented)
function addSimpleThumb(theGallery) {
return (function () {
var $inner = this.$element.find('.gallerify-inner')
/*! IMPORTANT: add fallback for not pep */
return false
}.call(theGallery))
}
addLogs(theGallery)
function addLogs(theGallery) {
var hooks = [
'built' // elements should be in place (like 'ready')
, 'loaded' // images should be loaded (like 'load')
, 'lightbox-show'
, 'lightbox-dismiss'
, 'lightbox-dismissed'
, 'show'
, 'shown'
, 'navigate'
]
console.log('Gallerify Custom Events:\n=========================\n', ' ' + hooks.join('\n '))
theGallery.$element
.on(hooks.join('.gallerify ') + '.gallerify', function (e,a,b) {
if (b !== undefined) console.log([e.type, e.namespace, a, b, e])
else if (a !== undefined) console.log([e.type, e.namespace, a, e])
else console.log([e.type, e.namespace, e])
})
}
click(namespace) (not implemented; unnecessary)
function click(namespace) {
var namespace = namespace || ''
, events = (/iphone|ipad/gi).test(navigator.appVersion)
? ['touchend','MSPointerUp']
: ['mouseup','touchend','MSPointerUp']
return events.join(namespace + ' ') + namespace
}
}
, next: function () {
get the new item number
var itemNº = this.options.loop
? ($('#gallerify-wrapper').data('item') + 1) % this.items.length
: $('#gallerify-wrapper').data('item') + 1
default to 0 if nothing's showing
if (!this.$element.data('gallerifyOverlayShowing')) itemNº = 0
go to that item, if it exists
if (this.items[itemNº]) {
this.$element.trigger('navigate.gallerify', [itemNº, 'next']) // a hook event
this.show(itemNº)
}
return this.$element // for jQuery chaining
}
, back: function () {
get the new item number
var itemNº = this.options.loop
? (($('#gallerify-wrapper').data('item') - 1 + this.items.length) % this.items.length)
: $('#gallerify-wrapper').data('item') - 1
default to 0 if nothing's showing
if (!this.$element.data('gallerifyOverlayShowing')) itemNº = 0
go to that item, if it exists
if (this.items[itemNº]) {
this.$element.trigger('navigate.gallerify', [itemNº, 'back']) // a hook event
this.show(itemNº)
}
return this.$element // for jQuery chaining
}
, show: function (itemNº) {
validate
!function (m) {
if (itemNº === undefined)
throw new SyntaxError(m + ' needs the number of the item it\'s meant to show.' + '\n')
if (isNaN(parseInt(itemNº)))
throw new SyntaxError(m + ' needs a numeric (or `parseInt()`-able) argument. It was "' + itemNº + '".')
if (!this.items[itemNº])
throw new RangeError(m + ' just tried to show items[' + itemNº + '], but that item doesn\'t seem to exist.')
}.call(this, 'Gallerify\'s .show() method')
var $element = this.$element
, loop = this.options.loop
, last = this.items.length - 1
, item = this.items[parseInt(itemNº)]
, $feature = $('#gallerify-wrapper').find('.gallerify-feature')
, $caption = $('#gallerify-wrapper').find('.gallerify-caption')
show the overlay if it's not showing already
if (!$element.data('gallerifyOverlayShowing')) {
$element.trigger('lightbox-show.gallerify') // a hook event
lock the body and show the overlay
$('#gallerify-overlay').delay(500).fadeIn(function (e) {
set overlay showing flag to true
$element.data('gallerifyOverlayShowing', true)
.trigger('lightbox-shown.gallerify') // a hook event
})
$(document.body).css({overflow: 'hidden'})
}
fade out the item wrapper, then proceed
$('#gallerify-wrapper').fadeOut(function () {
hide/show the appropriate controls at the appropriate times
if (!loop) {
if (itemNº === 0) $('#gallerify-back').hide()
if (itemNº === 1) $('#gallerify-back').show()
if (itemNº === last) $('#gallerify-next').hide()
if (itemNº === last - 1) $('#gallerify-next').show()
}
$element.trigger('show.gallerify', itemNº) // a hook event
update markup
$feature.html('<img src="' + item.feature.src + '">')
$caption.html('')
for (var i = 0; i < item.caption.length; i++)
$('<p>').append(item.caption[i]).appendTo($caption)
note the current item and fade in the item wrapper again
$('#gallerify-wrapper').data('item', itemNº)
.fadeIn(function () { $element.trigger('shown.gallerify', itemNº) }) // a hook event
})
return this.$element // for jQuery chaining
}
, dismiss: function () {
var $element = this.$element
$element.trigger('lightbox-dismiss.gallerify') // a hook event
$(document.body).css('overflow', 'auto').off('keypress.gallerify.dismiss.esc') // allow body scrolling and remove esc key listener
$('#gallerify-overlay').fadeOut(function (e) { $element.trigger('lightbox-dismissed.gallerify') }) // a hook event
return this.$element.data('gallerifyOverlayShowing', false)
}
/* //! #enhancement
add .add()
method that adds an item to items
and appends it to the $element
*/
}
/* GALLERIFY PLUGIN DEFINITION */
/* ============================== */
$.fn.gallerify = function (arg, methodArg) {
return this.each(function () {
var data = $(this).data('gallerify')
, options = (typeof arg == 'object') && arg
if (!data) $(this).data('gallerify', (data = new Gallerify(this, options)))
if (typeof arg == 'string')
if (methodArg) { data[arg].call(data,methodArg) }
else data[arg]()
})
}
$.fn.gallerify.Constructor = Gallerify
define default options
$.fn.gallerify.defaults = {
format: 'ul' // || 'ol' || false (meaning don't rebuild)
, itemClasses: '' // any valid CSS class name/names, space separated
, indent: true // || 'spaces' || false
, pep: true // || false
, hideCaptions: true // || false
, lightbox: true // || false
, loop: true // || false
, dev: false // || true
}
/* GALLERIFY DATA-API */
/* ===================== */
$(function () {
$('[data-provide="gallery"]').not('[data-auto-init="false"]').each(function (i,gallery) {
$(gallery).gallerify($(gallery).data())
})
})
}(window.jQuery);