PageSourceSearch

https://jgarecruitmentinc.com/wp-content/plugins/photomosaic-for-wordpress/public/js/photomosaic.js?ver=2.15.8

js jgarecruitmentinc.com collected 2026-10-08 04:48:39 UTC 187,657 bytes, 4,882 lines download raw bytes

1(function (jQuery, window) {
2
3    function registerNamespace(ns, raw) {
4        var nsParts = ns.split('.');
5        var root = window;
6        for (var i = 0; i < nsParts.length; i++) {
7            if (typeof root[nsParts[i]] == 'undefined') {
8                root[nsParts[i]] = (raw) ? raw : {};
9            }
10            root = root[nsParts[i]];
11        }
12    }
13
14    // verbatim from jQuery Migrate (as of 10/13 never received an official release)
15    jQuery.sub = function() {
16        function jQuerySub( selector, context ) {
17            return new jQuerySub.fn.init( selector, context );
18        }
19        jQuery.extend( true, jQuerySub, this );
20        jQuerySub.superclass = this;
21        jQuerySub.fn = jQuerySub.prototype = this();
22        jQuerySub.fn.constructor = jQuerySub;
23        jQuerySub.sub = this.sub;
24        jQuerySub.fn.init = function init( selector, context ) {
25            var instance = jQuery.fn.init.call( this, selector, context, rootjQuerySub );
26            return instance instanceof jQuerySub ?
27                instance :
28                jQuerySub( instance );
29        };
30        jQuerySub.fn.init.prototype = jQuerySub.fn;
31        var rootjQuerySub = jQuerySub(document);
32        return jQuerySub;
33    };
34
35    var $sub = jQuery.sub();
36
37    registerNamespace('JQPM', $sub || {});
38    registerNamespace('PhotoMosaic');
39    registerNamespace('PhotoMosaic.$', $sub || {});
40    registerNamespace('PhotoMosaic.version', '2.15.8');
41    registerNamespace('PhotoMosaic.Utils');
42    registerNamespace('PhotoMosaic.Inputs');
43    registerNamespace('PhotoMosaic.Loader');
44    registerNamespace('PhotoMosaic.Layouts');
45    registerNamespace('PhotoMosaic.Plugins');
46    registerNamespace('PhotoMosaic.ErrorChecks');
47    registerNamespace('PhotoMosaic.Mosaics', []);
48    registerNamespace('PhotoMosaic.WP', {});
49    registerNamespace('PhotoMosaic.LightboxBridge', {});
50    registerNamespace('PhotoMosaic.each', function (callback) {
51        PhotoMosaic.$.each( PhotoMosaic.Mosaics, function (i, mosaic) {
52            // this = the raw target element
53            // arg[0] = the JQPM instance
54            // arg[1] = $( this )
55            // arg[2] = direct access to $(this).photoMosaic() w/o all the typing
56            // arg[3] = index of mosaic in list of mosaics
57            callback.apply(mosaic.el, [
58                PhotoMosaic.$,
59                mosaic.$el,
60                function (args) {
61                    mosaic.$el.photoMosaic.apply(mosaic.$el, [args]);
62                },
63                i
64            ]);
65        });
66    });
67    registerNamespace('PhotoMosaic.refreshPage', function () {
68        // calls instance.refresh() on existing mosaics
69        // inits any uninitialized mosaics
70        var $ = PhotoMosaic.$;
71        $.each(PhotoMosaic.WP, function (id, config) {
72            var $el = $('#' + config.target);
73            var instance = $el.data('photoMosaic')
74            var params = (instance) ? null : $.extend(true, {}, config.settings, {
75                    gallery : config.gallery
76                });
77
78            $el.photoMosaic( params );
79        });
80    });
81}(jQuery, window));
82/*
83    Modernizr 2.8.3 (Custom Build) | MIT & BSD
84    Build: http://modernizr.com/download/#-csstransforms-csstransitions-prefixed-testprop-testallprops-prefixes-domprefixes
85    // Extra > Add CSS Classes + prefix "PM_"
86*/
87(function(window){
88    window.PhotoMosaic.Plugins.Modernizr = function(a,b,c){function y(a){j.cssText=a}function z(a,b){return y(m.join(a+";")+(b||""))}function A(a,b){return typeof a===b}function B(a,b){return!!~(""+a).indexOf(b)}function C(a,b){for(var d in a){var e=a[d];if(!B(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function D(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:A(f,"function")?f.bind(d||b):f}return!1}function E(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+o.join(d+" ")+d).split(" ");return A(b,"string")||A(b,"undefined")?C(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),D(e,b,c))}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w={}.hasOwnProperty,x;!A(w,"undefined")&&!A(w.call,"undefined")?x=function(a,b){return w.call(a,b)}:x=function(a,b){return b in a&&A(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;
88var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.csstransforms=function(){return!!E("transform")},q.csstransitions=function(){return E("transition")};for(var F in q)x(q,F)&&(v=F.toLowerCase(),e[v]=q[F](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)x(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" PM_"+(b?"":"no-")+a),e[a]=b}return e},y(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return C([a])},e.testAllProps=E,e.prefixed=function(a,b,c){return b?E(a,b,c):E(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" PM_js PM_"+t.join(" PM_")+" ":""),e}(this,this.document);
89}(window));
90/*!
91 * imagesLoaded PACKAGED v3.1.4
92 * JavaScript is all like "You images are done yet or what?"
93 * MIT License
94 */
95
96
97/*!
98 * EventEmitter v4.2.6 - git.io/ee
99 * Oliver Caldwell
100 * MIT license
101 * @preserve
102 */
103(function () {
104    /**
105     * Class for managing events.
106     * Can be extended to provide event functionality in other classes.
107     *
108     * @class EventEmitter Manages event registering and emitting.
109     */
110    function EventEmitter() {}
111
112    // Shortcuts to improve speed and size
113    var proto = EventEmitter.prototype;
114    var exports = this;
115    var originalGlobalValue = exports.EventEmitter;
116
117    /**
118     * Finds the index of the listener for the event in it's storage array.
119     *
120     * @param {Function[]} listeners Array of listeners to search through.
121     * @param {Function} listener Method to look for.
122     * @return {Number} Index of the specified listener, -1 if not found
123     * @api private
124     */
125    function indexOfListener(listeners, listener) {
126        var i = listeners.length;
127        while (i--) {
128            if (listeners[i].listener === listener) {
129                return i;
130            }
131        }
132
133        return -1;
134    }
135
136    /**
137     * Alias a method while keeping the context correct, to allow for overwriting of target method.
138     *
139     * @param {String} name The name of the target method.
140     * @return {Function} The aliased method
141     * @api private
142     */
143    function alias(name) {
144        return function aliasClosure() {
145            return this[name].apply(this, arguments);
146        };
147    }
148
149    /**
150     * Returns the listener array for the specified event.
151     * Will initialise the event object and listener arrays if required.
152     * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object c
vendor: 16,384 bytes, lines 152-557
152ontaining bar and baz. But only if you have either defined them with defineEvent or added some listeners to them.
153     * Each property in the object response is an array of listener functions.
154     *
155     * @param {String|RegExp} evt Name of the event to return the listeners from.
156     * @return {Function[]|Object} All listener functions for the event.
157     */
158    proto.getListeners = function getListeners(evt) {
159        var events = this._getEvents();
160        var response;
161        var key;
162
163        // Return a concatenated array of all matching events if
164        // the selector is a regular expression.
165        if (typeof evt === 'object') {
166            response = {};
167            for (key in events) {
168                if (events.hasOwnProperty(key) && evt.test(key)) {
169                    response[key] = events[key];
170                }
171            }
172        }
173        else {
174            response = events[evt] || (events[evt] = []);
175        }
176
177        return response;
178    };
179
180    /**
181     * Takes a list of listener objects and flattens it into a list of listener functions.
182     *
183     * @param {Object[]} listeners Raw listener objects.
184     * @return {Function[]} Just the listener functions.
185     */
186    proto.flattenListeners = function flattenListeners(listeners) {
187        var flatListeners = [];
188        var i;
189
190        for (i = 0; i < listeners.length; i += 1) {
191            flatListeners.push(listeners[i].listener);
192        }
193
194        return flatListeners;
195    };
196
197    /**
198     * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful.
199     *
200     * @param {String|RegExp} evt Name of the event to return the listeners from.
201     * @return {Object} All listener functions for an event in an object.
202     */
203    proto.getListenersAsObject = function getListenersAsObject(evt) {
204        var listeners = this.getListeners(evt);
205        var response;
206
207        if (listeners instanceof Array) {
208            response = {};
209            response[evt] = listeners;
210        }
211
212        return response || listeners;
213    };
214
215    /**
216     * Adds a listener function to the specified event.
217     * The listener will not be added if it is a duplicate.
218     * If the listener returns true then it will be removed after it is called.
219     * If you pass a regular expression as the event name then the listener will be added to all events that match it.
220     *
221     * @param {String|RegExp} evt Name of the event to attach the listener to.
222     * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
223     * @return {Object} Current instance of EventEmitter for chaining.
224     */
225    proto.addListener = function addListener(evt, listener) {
226        var listeners = this.getListenersAsObject(evt);
227        var listenerIsWrapped = typeof listener === 'object';
228        var key;
229
230        for (key in listeners) {
231            if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) {
232                listeners[key].push(listenerIsWrapped ? listener : {
233                    listener: listener,
234                    once: false
235                });
236            }
237        }
238
239        return this;
240    };
241
242    /**
243     * Alias of addListener
244     */
245    proto.on = alias('addListener');
246
247    /**
248     * Semi-alias of addListener. It will add a listener that will be
249     * automatically removed after it's first execution.
250     *
251     * @param {String|RegExp} evt Name of the event to attach the listener to.
252     * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
253     * @return {Object} Current instance of EventEmitter for chaining.
254     */
255    proto.addOnceListener = function addOnceListener(evt, listener) {
256        return this.addListener(evt, {
257            listener: listener,
258            once: true
259        });
260    };
261
262    /**
263     * Alias of addOnceListener.
264     */
265    proto.once = alias('addOnceListener');
266
267    /**
268     * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad.
269     * You need to tell it what event names should be matched by a regex.
270     *
271     * @param {String} evt Name of the event to create.
272     * @return {Object} Current instance of EventEmitter for chaining.
273     */
274    proto.defineEvent = function defineEvent(evt) {
275        this.getListeners(evt);
276        return this;
277    };
278
279    /**
280     * Uses defineEvent to define multiple events.
281     *
282     * @param {String[]} evts An array of event names to define.
283     * @return {Object} Current instance of EventEmitter for chaining.
284     */
285    proto.defineEvents = function defineEvents(evts) {
286        for (var i = 0; i < evts.length; i += 1) {
287            this.defineEvent(evts[i]);
288        }
289        return this;
290    };
291
292    /**
293     * Removes a listener function from the specified event.
294     * When passed a regular expression as the event name, it will remove the listener from all events that match it.
295     *
296     * @param {String|RegExp} evt Name of the event to remove the listener from.
297     * @param {Function} listener Method to remove from the event.
298     * @return {Object} Current instance of EventEmitter for chaining.
299     */
300    proto.removeListener = function removeListener(evt, listener) {
301        var listeners = this.getListenersAsObject(evt);
302        var index;
303        var key;
304
305        for (key in listeners) {
306            if (listeners.hasOwnProperty(key)) {
307                index = indexOfListener(listeners[key], listener);
308
309                if (index !== -1) {
310                    listeners[key].splice(index, 1);
311                }
312            }
313        }
314
315        return this;
316    };
317
318    /**
319     * Alias of removeListener
320     */
321    proto.off = alias('removeListener');
322
323    /**
324     * Adds listeners in bulk using the manipulateListeners method.
325     * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added.
326     * You can also pass it a regular expression to add the array of listeners to all events that match it.
327     * Yeah, this function does quite a bit. That's probably a bad thing.
328     *
329     * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once.
330     * @param {Function[]} [listeners] An optional array of listener functions to add.
331     * @return {Object} Current instance of EventEmitter for chaining.
332     */
333    proto.addListeners = function addListeners(evt, listeners) {
334        // Pass through to manipulateListeners
335        return this.manipulateListeners(false, evt, listeners);
336    };
337
338    /**
339     * Removes listeners in bulk using the manipulateListeners method.
340     * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
341     * You can also pass it an event name and an array of listeners to be removed.
342     * You can also pass it a regular expression to remove the listeners from all events that match it.
343     *
344     * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once.
345     * @param {Function[]} [listeners] An optional array of listener functions to remove.
346     * @return {Object} Current instance of EventEmitter for chaining.
347     */
348    proto.removeListeners = function removeListeners(evt, listeners) {
349        // Pass through to manipulateListeners
350        return this.manipulateListeners(true, evt, listeners);
351    };
352
353    /**
354     * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level.
355     * The first argument will determine if the listeners are removed (true) or added (false).
356     * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays.
357     * You can also pass it an event name and an array of listeners to be added/removed.
358     * You can also pass it a regular expression to manipulate the listeners of all events that match it.
359     *
360     * @param {Boolean} remove True if you want to remove listeners, false if you want to add.
361     * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once.
362     * @param {Function[]} [listeners] An optional array of listener functions to add/remove.
363     * @return {Object} Current instance of EventEmitter for chaining.
364     */
365    proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) {
366        var i;
367        var value;
368        var single = remove ? this.removeListener : this.addListener;
369        var multiple = remove ? this.removeListeners : this.addListeners;
370
371        // If evt is an object then pass each of it's properties to this method
372        if (typeof evt === 'object' && !(evt instanceof RegExp)) {
373            for (i in evt) {
374                if (evt.hasOwnProperty(i) && (value = evt[i])) {
375                    // Pass the single listener straight through to the singular method
376                    if (typeof value === 'function') {
377                        single.call(this, i, value);
378                    }
379                    else {
380                        // Otherwise pass back to the multiple function
381                        multiple.call(this, i, value);
382                    }
383                }
384            }
385        }
386        else {
387            // So evt must be a string
388            // And listeners must be an array of listeners
389            // Loop over it and pass each one to the multiple method
390            i = listeners.length;
391            while (i--) {
392                single.call(this, evt, listeners[i]);
393            }
394        }
395
396        return this;
397    };
398
399    /**
400     * Removes all listeners from a specified event.
401     * If you do not specify an event then all listeners will be removed.
402     * That means every event will be emptied.
403     * You can also pass a regex to remove all events that match it.
404     *
405     * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed.
406     * @return {Object} Current instance of EventEmitter for chaining.
407     */
408    proto.removeEvent = function removeEvent(evt) {
409        var type = typeof evt;
410        var events = this._getEvents();
411        var key;
412
413        // Remove different things depending on the state of evt
414        if (type === 'string') {
415            // Remove all listeners for the specified event
416            delete events[evt];
417        }
418        else if (type === 'object') {
419            // Remove all events matching the regex.
420            for (key in events) {
421                if (events.hasOwnProperty(key) && evt.test(key)) {
422                    delete events[key];
423                }
424            }
425        }
426        else {
427            // Remove all listeners in all events
428            delete this._events;
429        }
430
431        return this;
432    };
433
434    /**
435     * Alias of removeEvent.
436     *
437     * Added to mirror the node API.
438     */
439    proto.removeAllListeners = alias('removeEvent');
440
441    /**
442     * Emits an event of your choice.
443     * When emitted, every listener attached to that event will be executed.
444     * If you pass the optional argument array then those arguments will be passed to every listener upon execution.
445     * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately.
446     * So they will not arrive within the array on the other side, they will be separate.
447     * You can also pass a regular expression to emit to all events that match it.
448     *
449     * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
450     * @param {Array} [args] Optional array of arguments to be passed to each listener.
451     * @return {Object} Current instance of EventEmitter for chaining.
452     */
453    proto.emitEvent = function emitEvent(evt, args) {
454        var listeners = this.getListenersAsObject(evt);
455        var listener;
456        var i;
457        var key;
458        var response;
459
460        for (key in listeners) {
461            if (listeners.hasOwnProperty(key)) {
462                i = listeners[key].length;
463
464                while (i--) {
465                    // If the listener returns true then it shall be removed from the event
466                    // The function is executed either with a basic call or an apply if there is an args array
467                    listener = listeners[key][i];
468
469                    if (listener.once === true) {
470                        this.removeListener(evt, listener.listener);
471                    }
472
473                    response = listener.listener.apply(this, args || []);
474
475                    if (response === this._getOnceReturnValue()) {
476                        this.removeListener(evt, listener.listener);
477                    }
478                }
479            }
480        }
481
482        return this;
483    };
484
485    /**
486     * Alias of emitEvent
487     */
488    proto.trigger = alias('emitEvent');
489
490    /**
491     * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on.
492     * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it.
493     *
494     * @param {String|RegExp} evt Name of the event to emit and execute listeners for.
495     * @param {...*} Optional additional arguments to be passed to each listener.
496     * @return {Object} Current instance of EventEmitter for chaining.
497     */
498    proto.emit = function emit(evt) {
499        var args = Array.prototype.slice.call(arguments, 1);
500        return this.emitEvent(evt, args);
501    };
502
503    /**
504     * Sets the current value to check against when executing listeners. If a
505     * listeners return value matches the one set here then it will be removed
506     * after execution. This value defaults to true.
507     *
508     * @param {*} value The new value to check for when executing listeners.
509     * @return {Object} Current instance of EventEmitter for chaining.
510     */
511    proto.setOnceReturnValue = function setOnceReturnValue(value) {
512        this._onceReturnValue = value;
513        return this;
514    };
515
516    /**
517     * Fetches the current value to check against when executing listeners. If
518     * the listeners return value matches this one then it should be removed
519     * automatically. It will return true by default.
520     *
521     * @return {*|Boolean} The current value to check for or the default, true.
522     * @api private
523     */
524    proto._getOnceReturnValue = function _getOnceReturnValue() {
525        if (this.hasOwnProperty('_onceReturnValue')) {
526            return this._onceReturnValue;
527        }
528        else {
529            return true;
530        }
531    };
532
533    /**
534     * Fetches the events object and creates one if required.
535     *
536     * @return {Object} The events storage object.
537     * @api private
538     */
539    proto._getEvents = function _getEvents() {
540        return this._events || (this._events = {});
541    };
542
543    /**
544     * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version.
545     *
546     * @return {Function} Non conflicting EventEmitter class.
547     */
548    EventEmitter.noConflict = function noConflict() {
549        exports.EventEmitter = originalGlobalValue;
550        return EventEmitter;
551    };
552
553    // Expose the class either via AMD, CommonJS or the global object
554    if (typeof define === 'function' && define.amd) {
555        define('eventEmitter/EventEmitter',[],function () {
556            return EventEmitter;
557       
557 });
558    }
559    else if (typeof module === 'object' && module.exports){
560        module.exports = EventEmitter;
561    }
562    else {
563        this.EventEmitter = EventEmitter;
564    }
565}.call(this));
566
567/*!
568 * eventie v1.0.4
569 * event binding helper
570 *   eventie.bind( elem, 'click', myFn )
571 *   eventie.unbind( elem, 'click', myFn )
572 */
573/*jshint browser: true, undef: true, unused: true */
574/*global define: false */
575(function( window ) {
576    var docElem = document.documentElement;
577    var bind = function() {};
578
579    function getIEEvent( obj ) {
580        var event = window.event;
581        // add event.target
582        event.target = event.target || event.srcElement || obj;
583        return event;
584    }
585
586    if ( docElem.addEventListener ) {
587        bind = function( obj, type, fn ) {
588            obj.addEventListener( type, fn, false );
589        };
590    } else if ( docElem.attachEvent ) {
591        bind = function( obj, type, fn ) {
592            obj[ type + fn ] = fn.handleEvent ?
593                function() {
594                    var event = getIEEvent( obj );
595                    fn.handleEvent.call( fn, event );
596                } :
597                function() {
598                    var event = getIEEvent( obj );
599                    fn.call( obj, event );
600                };
601            obj.attachEvent( "on" + type, obj[ type + fn ] );
602        };
603    }
604
605    var unbind = function() {};
606
607    if ( docElem.removeEventListener ) {
608        unbind = function( obj, type, fn ) {
609            obj.removeEventListener( type, fn, false );
610        };
611    } else if ( docElem.detachEvent ) {
612        unbind = function( obj, type, fn ) {
613            obj.detachEvent( "on" + type, obj[ type + fn ] );
614            try {
615                delete obj[ type + fn ];
616            } catch ( err ) {
617                // can't delete window object properties
618                obj[ type + fn ] = undefined;
619            }
620        };
621    }
622
623    var eventie = {
624        bind: bind,
625        unbind: unbind
626    };
627
628    // transport
629    if ( typeof define === 'function' && define.amd ) {
630        // AMD
631        define( 'eventie/eventie',eventie );
632    } else {
633        // browser global
634        window.eventie = eventie;
635    }
636})( this );
637
638/*!
639 * imagesLoaded v3.1.4
640 * JavaScript is all like "You images are done yet or what?"
641 * MIT License
642 */
643(function( window, factory ) { 
644    // universal module definition
645    /*global define: false, module: false, require: false */
646    if ( typeof define === 'function' && define.amd ) {
647        // AMD
648        define( [
649            'eventEmitter/EventEmitter',
650            'eventie/eventie'
651        ], function( EventEmitter, eventie ) {
652            return factory( window, EventEmitter, eventie );
653        });
654    } else if ( typeof exports === 'object' ) {
655        // CommonJS
656        module.exports = factory(
657            window,
658            require('eventEmitter'),
659            require('eventie')
660        );
661    } else {
662        // browser global
663        window.PhotoMosaic.Plugins.imagesLoaded = factory(
664            window,
665            window.EventEmitter,
666            window.eventie
667        );
668    }
669})( this,
670
671// --------------------------  factory -------------------------- //
672function factory( window, EventEmitter, eventie ) {
673    var $ = window.JQPM;
674    var console = window.console;
675    var hasConsole = typeof console !== 'undefined';
676
677    // -------------------------- helpers -------------------------- //
678    // extend objects
679    function extend( a, b ) {
680        for ( var prop in b ) {
681            a[ prop ] = b[ prop ];
682        }
683        return a;
684    }
685
686    var objToString = Object.prototype.toString;
687    function isArray( obj ) {
688        return objToString.call( obj ) === '[object Array]';
689    }
690
691    // turn element or nodeList into an array
692    function makeArray( obj ) {
693        var ary = [];
694        if ( isArray( obj ) ) {
695            // use object if already an array
696            ary = obj;
697        } else if ( typeof obj.length === 'number' ) {
698            // convert nodeList to array
699            for ( var i=0, len = obj.length; i < len; i++ ) {
700                ary.push( obj[i] );
701            }
702        } else {
703            // array of single index
704            ary.push( obj );
705        }
706        return ary;
707    }
708
709    // -------------------------- imagesLoaded -------------------------- //
710    /**
711     * @param {Array, Element, NodeList, String} elem
712     * @param {Object or Function} options - if function, use as callback
713     * @param {Function} onAlways - callback function
714     */
715    function ImagesLoaded( elem, options, onAlways ) {
716        // coerce ImagesLoaded() without new, to be new ImagesLoaded()
717        if ( !( this instanceof ImagesLoaded ) ) {
718            return new ImagesLoaded( elem, options );
719        }
720        // use elem as selector string
721        if ( typeof elem === 'string' ) {
722            elem = document.querySelectorAll( elem );
723        }
724
725        this.elements = makeArray( elem );
726        this.options = extend( {}, this.options );
727
728        if ( typeof options === 'function' ) {
729            onAlways = options;
730        } else {
731            extend( this.options, options );
732        }
733
734        if ( onAlways ) {
735            this.on( 'always', onAlways );
736        }
737
738        this.getImages();
739
740        if ( $ ) {
741            // add jQuery Deferred object
742            this.jqDeferred = new $.Deferred();
743        }
744
745        // HACK check async to allow time to bind listeners
746        var _this = this;
747        setTimeout( function() {
748            _this.check();
749        });
750    }
751
752    ImagesLoaded.prototype = new EventEmitter();
753
754    ImagesLoaded.prototype.options = {};
755
756    ImagesLoaded.prototype.getImages = function() {
757        this.images = [];
758
759        // filter & find items if we have an item selector
760        for ( var i=0, len = this.elements.length; i < len; i++ ) {
761            var elem = this.elements[i];
762            // filter siblings
763            if ( elem.nodeName === 'IMG' ) {
764                this.addImage( elem );
765            }
766            // find children
767            var childElems = elem.querySelectorAll('img');
768            // concat childElems to filterFound array
769            for ( var j=0, jLen = childElems.length; j < jLen; j++ ) {
770                var img = childElems[j];
771                this.addImage( img );
772            }
773        }
774    };
775
776    /**
777     * @param {Image} img
778     */
779    ImagesLoaded.prototype.addImage = function( img ) {
780        var loadingImage = new LoadingImage( img );
781        this.images.push( loadingImage );
782    };
783
784    ImagesLoaded.prototype.check = function() {
785        var _this = this;
786        var checkedCount = 0;
787        var length = this.images.length;
788        this.hasAnyBroken = false;
789        // complete if no images
790        if ( !length ) {
791            this.complete();
792            return;
793        }
794
795        function onConfirm( image, message ) {
796            if ( _this.options.debug && hasConsole ) {
797                console.log( 'confirm', image, message );
798            }
799
800            _this.progress( image );
801            checkedCount++;
802            if ( checkedCount === length ) {
803                _this.complete();
804            }
805            return true; // bind once
806        }
807
808        for ( var i=0; i < length; i++ ) {
809            var loadingImage = this.images[i];
810            loadingImage.on( 'confirm', onConfirm );
811            loadingImage.check();
812        }
813    };
814
815    ImagesLoaded.prototype.progress = function( image ) {
816        this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
817        // HACK - Chrome triggers event before object properties have changed. #83
818        var _this = this;
819        setTimeout( function() {
820            _this.emit( 'progress', _this, image );
821            if ( _this.jqDeferred && _this.jqDeferred.notify ) {
822                _this.jqDeferred.notify( _this, image );
823            }
824        });
825    };
826
827    ImagesLoaded.prototype.complete = function() {
828        var eventName = this.hasAnyBroken ? 'fail' : 'done';
829        this.isComplete = true;
830        var _this = this;
831        // HACK - another setTimeout so that confirm happens after progress
832        setTimeout( function() {
833            _this.emit( eventName, _this );
834            _this.emit( 'always', _this );
835            if ( _this.jqDeferred ) {
836                var jqMethod = _this.hasAnyBroken ? 'reject' : 'resolve';
837                _this.jqDeferred[ jqMethod ]( _this );
838            }
839        });
840    };
841
842    // -------------------------- jquery -------------------------- //
843    if ( $ ) {
844        $.fn.imagesLoaded = function( options, callback ) {
845            var instance = new ImagesLoaded( this, options, callback );
846            return instance.jqDeferred.promise( $(this) );
847        };
848    }
849
850    // --------------------------  -------------------------- //
851    function LoadingImage( img ) {
852        this.img = img;
853    }
854
855    LoadingImage.prototype = new EventEmitter();
856
857    LoadingImage.prototype.check = function() {
858        // first check cached any previous images that have same src
859        var resource = cache[ this.img.src ] || new Resource( this.img.src );
860        if ( resource.isConfirmed ) {
861            this.confirm( resource.isLoaded, 'cached was confirmed' );
862            return;
863        }
864
865        // If complete is true and browser supports natural sizes,
866        // try to check for image status manually.
867        if ( this.img.complete && this.img.naturalWidth !== undefined ) {
868            // report based on naturalWidth
869            this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
870            return;
871        }
872
873        // If none of the checks above matched, simulate loading on detached element.
874        var _this = this;
875        resource.on( 'confirm', function( resrc, message ) {
876            _this.confirm( resrc.isLoaded, message );
877            return true;
878        });
879
880        resource.check();
881    };
882
883    LoadingImage.prototype.confirm = function( isLoaded, message ) {
884        this.isLoaded = isLoaded;
885        this.emit( 'confirm', this, message );
886    };
887
888    // -------------------------- Resource -------------------------- //
889    // Resource checks each src, only once
890    // separate class from LoadingImage to prevent memory leaks. See #115
891
892    var cache = {};
893
894    function Resource( src ) {
895        this.src = src;
896        // add to cache
897        cache[ src ] = this;
898    }
899
900    Resource.prototype = new EventEmitter();
901
902    Resource.prototype.check = function() {
903        // only trigger checking once
904        if ( this.isChecked ) {
905            return;
906        }
907        // simulate loading on detached element
908        var proxyImage = new Image();
909        eventie.bind( proxyImage, 'load', this );
910        eventie.bind( proxyImage, 'error', this );
911        proxyImage.src = this.src;
912        // set flag
913        this.isChecked = true;
914    };
915
916    // ----- events ----- //
917
918    // trigger specified handler for event type
919    Resource.prototype.handleEvent = function( event ) {
920        var method = 'on' + event.type;
921        if ( this[ method ] ) {
922            this[ method ]( event );
923        }
924    };
925
926    Resource.prototype.onload = function( event ) {
927        this.confirm( true, 'onload' );
928        this.unbindProxyEvents( event );
929    };
930
931    Resource.prototype.onerror = function( event ) {
932        this.confirm( false, 'onerror' );
933        this.unbindProxyEvents( event );
934    };
935
936    // ----- confirm ----- //
937
938    Resource.prototype.confirm = function( isLoaded, message ) {
939        this.isConfirmed = true;
940        this.isLoaded = isLoaded;
vendor: 1,742 bytes, lines 941-991
941        this.emit( 'confirm', this, message );
942    };
943
944    Resource.prototype.unbindProxyEvents = function( event ) {
945        eventie.unbind( event.target, 'load', this );
946        eventie.unbind( event.target, 'error', this );
947    };
948
949    // -----  ----- //
950    return ImagesLoaded;
951});
952/*
953    Version: 2.0.5a
954    Modified by Mike Kafka (http://codecanyon.net/user/makfak) to serve my own purposes
955     - #28 - reference custom $ instance (root.jQuery >>> root.JQPM)
956     - #41,42 - custom event namespaces to prevent colisions with other waypoints instances
957*/
958// Generated by CoffeeScript 1.6.2
959/*!
960jQuery Waypoints - v2.0.5
961Copyright (c) 2011-2014 Caleb Troughton
962Licensed under the MIT license.
963https://github.com/imakewebthings/jquery-waypoints/blob/master/licenses.txt
964*/
965
966(function() {
967  var __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
968    __slice = [].slice;
969
970  (function(root, factory) {
971    if (typeof define === 'function' && define.amd) {
972      return define('waypoints', ['jquery'], function($) {
973        return factory($, root);
974      });
975    } else {
976      return factory(root.JQPM, root);
977    }
978  })(window, function($, window) {
979    var $w, Context, Waypoint, allWaypoints, contextCounter, contextKey, contexts, isTouch, jQMethods, methods, resizeEvent, scrollEvent, waypointCounter, waypointKey, wp, wps;
980
981    $w = $(window);
982    isTouch = __indexOf.call(window, 'ontouchstart') >= 0;
983    allWaypoints = {
984      horizontal: {},
985      vertical: {}
986    };
987    contextCounter = 1;
988    contexts = {};
989    contextKey = 'waypoints-context-id';
990    resizeEvent = 'resize.waypoints_pm';
991    scrollEvent = 'scroll.waypoints_pm
vendor: 15,618 bytes, lines 991-1473
991';
992    waypointCounter = 1;
993    waypointKey = 'waypoints-waypoint-ids';
994    wp = 'waypoint';
995    wps = 'waypoints';
996    Context = (function() {
997      function Context($element) {
998        var _this = this;
999
1000        this.$element = $element;
1001        this.element = $element[0];
1002        this.didResize = false;
1003        this.didScroll = false;
1004        this.id = 'context' + contextCounter++;
1005        this.oldScroll = {
1006          x: $element.scrollLeft(),
1007          y: $element.scrollTop()
1008        };
1009        this.waypoints = {
1010          horizontal: {},
1011          vertical: {}
1012        };
1013        this.element[contextKey] = this.id;
1014        contexts[this.id] = this;
1015        $element.bind(scrollEvent, function() {
1016          var scrollHandler;
1017
1018          if (!(_this.didScroll || isTouch)) {
1019            _this.didScroll = true;
1020            scrollHandler = function() {
1021              _this.doScroll();
1022              return _this.didScroll = false;
1023            };
1024            return window.setTimeout(scrollHandler, $[wps].settings.scrollThrottle);
1025          }
1026        });
1027        $element.bind(resizeEvent, function() {
1028          var resizeHandler;
1029
1030          if (!_this.didResize) {
1031            _this.didResize = true;
1032            resizeHandler = function() {
1033              $[wps]('refresh');
1034              return _this.didResize = false;
1035            };
1036            return window.setTimeout(resizeHandler, $[wps].settings.resizeThrottle);
1037          }
1038        });
1039      }
1040
1041      Context.prototype.doScroll = function() {
1042        var axes,
1043          _this = this;
1044
1045        axes = {
1046          horizontal: {
1047            newScroll: this.$element.scrollLeft(),
1048            oldScroll: this.oldScroll.x,
1049            forward: 'right',
1050            backward: 'left'
1051          },
1052          vertical: {
1053            newScroll: this.$element.scrollTop(),
1054            oldScroll: this.oldScroll.y,
1055            forward: 'down',
1056            backward: 'up'
1057          }
1058        };
1059        if (isTouch && (!axes.vertical.oldScroll || !axes.vertical.newScroll)) {
1060          $[wps]('refresh');
1061        }
1062        $.each(axes, function(aKey, axis) {
1063          var direction, isForward, triggered;
1064
1065          triggered = [];
1066          isForward = axis.newScroll > axis.oldScroll;
1067          direction = isForward ? axis.forward : axis.backward;
1068          $.each(_this.waypoints[aKey], function(wKey, waypoint) {
1069            var _ref, _ref1;
1070
1071            if ((axis.oldScroll < (_ref = waypoint.offset) && _ref <= axis.newScroll)) {
1072              return triggered.push(waypoint);
1073            } else if ((axis.newScroll < (_ref1 = waypoint.offset) && _ref1 <= axis.oldScroll)) {
1074              return triggered.push(waypoint);
1075            }
1076          });
1077          triggered.sort(function(a, b) {
1078            return a.offset - b.offset;
1079          });
1080          if (!isForward) {
1081            triggered.reverse();
1082          }
1083          return $.each(triggered, function(i, waypoint) {
1084            if (waypoint.options.continuous || i === triggered.length - 1) {
1085              return waypoint.trigger([direction]);
1086            }
1087          });
1088        });
1089        return this.oldScroll = {
1090          x: axes.horizontal.newScroll,
1091          y: axes.vertical.newScroll
1092        };
1093      };
1094
1095      Context.prototype.refresh = function() {
1096        var axes, cOffset, isWin,
1097          _this = this;
1098
1099        isWin = $.isWindow(this.element);
1100        cOffset = this.$element.offset();
1101        this.doScroll();
1102        axes = {
1103          horizontal: {
1104            contextOffset: isWin ? 0 : cOffset.left,
1105            contextScroll: isWin ? 0 : this.oldScroll.x,
1106            contextDimension: this.$element.width(),
1107            oldScroll: this.oldScroll.x,
1108            forward: 'right',
1109            backward: 'left',
1110            offsetProp: 'left'
1111          },
1112          vertical: {
1113            contextOffset: isWin ? 0 : cOffset.top,
1114            contextScroll: isWin ? 0 : this.oldScroll.y,
1115            contextDimension: isWin ? $[wps]('viewportHeight') : this.$element.height(),
1116            oldScroll: this.oldScroll.y,
1117            forward: 'down',
1118            backward: 'up',
1119            offsetProp: 'top'
1120          }
1121        };
1122        return $.each(axes, function(aKey, axis) {
1123          return $.each(_this.waypoints[aKey], function(i, waypoint) {
1124            var adjustment, elementOffset, oldOffset, _ref, _ref1;
1125
1126            adjustment = waypoint.options.offset;
1127            oldOffset = waypoint.offset;
1128            elementOffset = $.isWindow(waypoint.element) ? 0 : waypoint.$element.offset()[axis.offsetProp];
1129            if ($.isFunction(adjustment)) {
1130              adjustment = adjustment.apply(waypoint.element);
1131            } else if (typeof adjustment === 'string') {
1132              adjustment = parseFloat(adjustment);
1133              if (waypoint.options.offset.indexOf('%') > -1) {
1134                adjustment = Math.ceil(axis.contextDimension * adjustment / 100);
1135              }
1136            }
1137            waypoint.offset = elementOffset - axis.contextOffset + axis.contextScroll - adjustment;
1138            if ((waypoint.options.onlyOnScroll && (oldOffset != null)) || !waypoint.enabled) {
1139              return;
1140            }
1141            if (oldOffset !== null && (oldOffset < (_ref = axis.oldScroll) && _ref <= waypoint.offset)) {
1142              return waypoint.trigger([axis.backward]);
1143            } else if (oldOffset !== null && (oldOffset > (_ref1 = axis.oldScroll) && _ref1 >= waypoint.offset)) {
1144              return waypoint.trigger([axis.forward]);
1145            } else if (oldOffset === null && axis.oldScroll >= waypoint.offset) {
1146              return waypoint.trigger([axis.forward]);
1147            }
1148          });
1149        });
1150      };
1151
1152      Context.prototype.checkEmpty = function() {
1153        if ($.isEmptyObject(this.waypoints.horizontal) && $.isEmptyObject(this.waypoints.vertical)) {
1154          this.$element.unbind([resizeEvent, scrollEvent].join(' '));
1155          return delete contexts[this.id];
1156        }
1157      };
1158
1159      return Context;
1160
1161    })();
1162    Waypoint = (function() {
1163      function Waypoint($element, context, options) {
1164        var idList, _ref;
1165
1166        if (options.offset === 'bottom-in-view') {
1167          options.offset = function() {
1168            var contextHeight;
1169
1170            contextHeight = $[wps]('viewportHeight');
1171            if (!$.isWindow(context.element)) {
1172              contextHeight = context.$element.height();
1173            }
1174            return contextHeight - $(this).outerHeight();
1175          };
1176        }
1177        this.$element = $element;
1178        this.element = $element[0];
1179        this.axis = options.horizontal ? 'horizontal' : 'vertical';
1180        this.callback = options.handler;
1181        this.context = context;
1182        this.enabled = options.enabled;
1183        this.id = 'waypoints' + waypointCounter++;
1184        this.offset = null;
1185        this.options = options;
1186        context.waypoints[this.axis][this.id] = this;
1187        allWaypoints[this.axis][this.id] = this;
1188        idList = (_ref = this.element[waypointKey]) != null ? _ref : [];
1189        idList.push(this.id);
1190        this.element[waypointKey] = idList;
1191      }
1192
1193      Waypoint.prototype.trigger = function(args) {
1194        if (!this.enabled) {
1195          return;
1196        }
1197        if (this.callback != null) {
1198          this.callback.apply(this.element, args);
1199        }
1200        if (this.options.triggerOnce) {
1201          return this.destroy();
1202        }
1203      };
1204
1205      Waypoint.prototype.disable = function() {
1206        return this.enabled = false;
1207      };
1208
1209      Waypoint.prototype.enable = function() {
1210        this.context.refresh();
1211        return this.enabled = true;
1212      };
1213
1214      Waypoint.prototype.destroy = function() {
1215        delete allWaypoints[this.axis][this.id];
1216        delete this.context.waypoints[this.axis][this.id];
1217        return this.context.checkEmpty();
1218      };
1219
1220      Waypoint.getWaypointsByElement = function(element) {
1221        var all, ids;
1222
1223        ids = element[waypointKey];
1224        if (!ids) {
1225          return [];
1226        }
1227        all = $.extend({}, allWaypoints.horizontal, allWaypoints.vertical);
1228        return $.map(ids, function(id) {
1229          return all[id];
1230        });
1231      };
1232
1233      return Waypoint;
1234
1235    })();
1236    methods = {
1237      init: function(f, options) {
1238        var _ref;
1239
1240        options = $.extend({}, $.fn[wp].defaults, options);
1241        if ((_ref = options.handler) == null) {
1242          options.handler = f;
1243        }
1244        this.each(function() {
1245          var $this, context, contextElement, _ref1;
1246
1247          $this = $(this);
1248          contextElement = (_ref1 = options.context) != null ? _ref1 : $.fn[wp].defaults.context;
1249          if (!$.isWindow(contextElement)) {
1250            contextElement = $this.closest(contextElement);
1251          }
1252          contextElement = $(contextElement);
1253          context = contexts[contextElement[0][contextKey]];
1254          if (!context) {
1255            context = new Context(contextElement);
1256          }
1257          return new Waypoint($this, context, options);
1258        });
1259        $[wps]('refresh');
1260        return this;
1261      },
1262      disable: function() {
1263        return methods._invoke.call(this, 'disable');
1264      },
1265      enable: function() {
1266        return methods._invoke.call(this, 'enable');
1267      },
1268      destroy: function() {
1269        return methods._invoke.call(this, 'destroy');
1270      },
1271      prev: function(axis, selector) {
1272        return methods._traverse.call(this, axis, selector, function(stack, index, waypoints) {
1273          if (index > 0) {
1274            return stack.push(waypoints[index - 1]);
1275          }
1276        });
1277      },
1278      next: function(axis, selector) {
1279        return methods._traverse.call(this, axis, selector, function(stack, index, waypoints) {
1280          if (index < waypoints.length - 1) {
1281            return stack.push(waypoints[index + 1]);
1282          }
1283        });
1284      },
1285      _traverse: function(axis, selector, push) {
1286        var stack, waypoints;
1287
1288        if (axis == null) {
1289          axis = 'vertical';
1290        }
1291        if (selector == null) {
1292          selector = window;
1293        }
1294        waypoints = jQMethods.aggregate(selector);
1295        stack = [];
1296        this.each(function() {
1297          var index;
1298
1299          index = $.inArray(this, waypoints[axis]);
1300          return push(stack, index, waypoints[axis]);
1301        });
1302        return this.pushStack(stack);
1303      },
1304      _invoke: function(method) {
1305        this.each(function() {
1306          var waypoints;
1307
1308          waypoints = Waypoint.getWaypointsByElement(this);
1309          return $.each(waypoints, function(i, waypoint) {
1310            waypoint[method]();
1311            return true;
1312          });
1313        });
1314        return this;
1315      }
1316    };
1317    $.fn[wp] = function() {
1318      var args, method;
1319
1320      method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
1321      if (methods[method]) {
1322        return methods[method].apply(this, args);
1323      } else if ($.isFunction(method)) {
1324        return methods.init.apply(this, arguments);
1325      } else if ($.isPlainObject(method)) {
1326        return methods.init.apply(this, [null, method]);
1327      } else if (!method) {
1328        return $.error("jQuery Waypoints needs a callback function or handler option.");
1329      } else {
1330        return $.error("The " + method + " method does not exist in jQuery Waypoints.");
1331      }
1332    };
1333    $.fn[wp].defaults = {
1334      context: window,
1335      continuous: true,
1336      enabled: true,
1337      horizontal: false,
1338      offset: 0,
1339      triggerOnce: false
1340    };
1341    jQMethods = {
1342      refresh: function() {
1343        return $.each(contexts, function(i, context) {
1344          return context.refresh();
1345        });
1346      },
1347      viewportHeight: function() {
1348        var _ref;
1349
1350        return (_ref = window.innerHeight) != null ? _ref : $w.height();
1351      },
1352      aggregate: function(contextSelector) {
1353        var collection, waypoints, _ref;
1354
1355        collection = allWaypoints;
1356        if (contextSelector) {
1357          collection = (_ref = contexts[$(contextSelector)[0][contextKey]]) != null ? _ref.waypoints : void 0;
1358        }
1359        if (!collection) {
1360          return [];
1361        }
1362        waypoints = {
1363          horizontal: [],
1364          vertical: []
1365        };
1366        $.each(waypoints, function(axis, arr) {
1367          $.each(collection[axis], function(key, waypoint) {
1368            return arr.push(waypoint);
1369          });
1370          arr.sort(function(a, b) {
1371            return a.offset - b.offset;
1372          });
1373          waypoints[axis] = $.map(arr, function(waypoint) {
1374            return waypoint.element;
1375          });
1376          return waypoints[axis] = $.unique(waypoints[axis]);
1377        });
1378        return waypoints;
1379      },
1380      above: function(contextSelector) {
1381        if (contextSelector == null) {
1382          contextSelector = window;
1383        }
1384        return jQMethods._filter(contextSelector, 'vertical', function(context, waypoint) {
1385          return waypoint.offset <= context.oldScroll.y;
1386        });
1387      },
1388      below: function(contextSelector) {
1389        if (contextSelector == null) {
1390          contextSelector = window;
1391        }
1392        return jQMethods._filter(contextSelector, 'vertical', function(context, waypoint) {
1393          return waypoint.offset > context.oldScroll.y;
1394        });
1395      },
1396      left: function(contextSelector) {
1397        if (contextSelector == null) {
1398          contextSelector = window;
1399        }
1400        return jQMethods._filter(contextSelector, 'horizontal', function(context, waypoint) {
1401          return waypoint.offset <= context.oldScroll.x;
1402        });
1403      },
1404      right: function(contextSelector) {
1405        if (contextSelector == null) {
1406          contextSelector = window;
1407        }
1408        return jQMethods._filter(contextSelector, 'horizontal', function(context, waypoint) {
1409          return waypoint.offset > context.oldScroll.x;
1410        });
1411      },
1412      enable: function() {
1413        return jQMethods._invoke('enable');
1414      },
1415      disable: function() {
1416        return jQMethods._invoke('disable');
1417      },
1418      destroy: function() {
1419        return jQMethods._invoke('destroy');
1420      },
1421      extendFn: function(methodName, f) {
1422        return methods[methodName] = f;
1423      },
1424      _invoke: function(method) {
1425        var waypoints;
1426
1427        waypoints = $.extend({}, allWaypoints.vertical, allWaypoints.horizontal);
1428        return $.each(waypoints, function(key, waypoint) {
1429          waypoint[method]();
1430          return true;
1431        });
1432      },
1433      _filter: function(selector, axis, test) {
1434        var context, waypoints;
1435
1436        context = contexts[$(selector)[0][contextKey]];
1437        if (!context) {
1438          return [];
1439        }
1440        waypoints = [];
1441        $.each(context.waypoints[axis], function(i, waypoint) {
1442          if (test(context, waypoint)) {
1443            return waypoints.push(waypoint);
1444          }
1445        });
1446        waypoints.sort(function(a, b) {
1447          return a.offset - b.offset;
1448        });
1449        return $.map(waypoints, function(waypoint) {
1450          return waypoint.element;
1451        });
1452      }
1453    };
1454    $[wps] = function() {
1455      var args, method;
1456
1457      method = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
1458      if (jQMethods[method]) {
1459        return jQMethods[method].apply(null, args);
1460      } else {
1461        return jQMethods.aggregate.call(null, method);
1462      }
1463    };
1464    $[wps].settings = {
1465      resizeThrottle: 100,
1466      scrollThrottle: 30
1467    };
1468    return $w.on('load.waypoints', function() {
1469      return $[wps]('refresh');
1470    });
1471  });
1472
1473}).call(this);
1474/*
1475    Version: 3.1.6f
1476    Modified by Mike Kafka (http://codecanyon.net/user/makfak) to serve my own purposes
1477    # b
1478     - new jQuery namespace (JQPM)
1479     - _getFileType (#662) extended to autodetect image URLs (avoid the need for "iframe=true")
1480     - content type switch (#330) extended to support iframe URLs w/o "iframe=true"
1481    # c
1482     - normalized all jQuery references to '$'
1483     - self-invoke arguments ref the window and test for availability (window.JQPM || jQuery)
1484    # d
1485     - new namespace on events
1486    # e
1487     - changed viewport buffer 200 >>> 100 (~#600)
1488     - locked the overlay to the screen (CSS)
1489     - removed the .ppt bumper (CSS)
1490    #f
1491     - changed default 'horizontal_padding' value to match the default theme (what's with the check ~#164)
1492*/
1493/* ------------------------------------------------------------------------
1494    Class: prettyPhoto
1495    Use: Lightbox clone for jQuery
1496    Author: Stephane Caron (http://www.no-margin-for-errors.com)
1497    Version: 3.1.6
1498------------------------------------------------------------------------- */
1499(function($) {
1500    $.prettyPhoto = {version: '3.1.6'};
1501    
1502    $.fn.prettyPhoto = function(pp_settings) {
1503        pp_settings = $.extend({
1504            hook: 'rel', /* the attribute tag to use for prettyPhoto hooks. default: 'rel'. For HTML5, use "data-rel" or similar. */
1505            animation_speed: 'fast', /* fast/slow/normal */
1506            ajaxcallback: function() {},
1507            slideshow: 5000, /* false OR interval time in ms */
1508            autoplay_slideshow: false, /* true/false */
1509            opacity: 0.80, /* Value between 0 and 1 */
1510            show_title: true, /* true/false */
1511            allow_resize: true, /* Resize the photos bigger than viewport. true/false */
1512            allow_expand: true, /* Allow the user to expand a resized image. true/false */
1513            default_width: 500,
1514            default_height: 344,
1515            counter_separator_label: '/', /* The separator for the gallery counter 1 "of" 2 */
1516            theme: 'pp_default', /* light_rounded / dark_rounded / light_square / dark_square / facebook */
1517            horizontal_padding: 16, /* The padding on each side of the picture */
1518            hideflash: false, /* Hides all the flash object on a page, set to TRUE if flash appears over prettyPhoto */
1519            wmode: 'opaque', /* Set the flash wmode attribute */
1520            autoplay: true, /* Automatically start videos: True/False */
1521            modal: false, /* If set to true, only the close button will close the window */
1522            deeplinking: true, /* Allow prettyPhoto to update the url to enable deeplinking. */
1523            overlay_gallery: true, /* If set to true, a gallery will overlay the fullscreen image on mouse over */
1524            overlay_gallery_max: 30, /* Maximum number of pictures in the overlay gallery */
1525            keyboard_shortcuts: true, /* Set to false if you open forms inside prettyPhoto */
1526            changepicturecallback: function(){}, /* Called everytime an item is shown/changed */
1527            callback: function(){}, /* Called when prettyPhoto is closed */
1528            ie6_fallback: true,
1529            markup: '<div class="pp_pic_holder"> \
1530                        <div class="ppt">&nbsp;</div> \
1531                        <div class="pp_top"> \
1532                            <div class="pp_left"></div> \
1533                            <div class="pp_middle"></div> \
1534                            <div class="pp_right"></div> \
1535                        </div> \
1536                        <div class="pp_content_container"> \
1537                            <div class="pp_left"> \
1538                            <div class="pp_right"> \
1539                                <div class="pp_content"> \
1540                                    <div class="pp_loaderIcon"></div> \
1541                                    <div class="pp_fade"> \
1542                                        <a href="#" class="pp_expand" title="Expand the image">Expand</a> \
1543                                        <div class="pp_hoverContainer"> \
1544                                            <a class="pp_next" href="#">next</a> \
1545                                            <a class="pp_previous" href="#">previous</a> \
1546                                        </div> \
1547                                        <div id="pp_full_res"></div> \
1548                                        <div class="pp_details"> \
1549                                            <div class="pp_nav"> \
1550                                                <a href="#" class="pp_arrow_previous">Previous</a> \
1551                                                <p class="currentTextHolder">0/0</p> \
1552                                                <a href="#" class="pp_arrow_next">Next</a> \
1553                                            </div> \
1554                                            <p class="pp_description"></p> \
1555                                            <div class="pp_social">{pp_social}</div> \
1556                                            <a class="pp_close" href="#">Close</a> \
1557                                        </div> \
1558                                    </div> \
1559                                </div> \
1560                            </div> \
1561                            </div> \
1562                        </div> \
1563                        <div class="pp_bottom"> \
1564                            <div class="pp_left"></div> \
1565                            <div class="pp_middle"></div> \
1566                            <div class="pp_right"></div> \
1567                        </div> \
1568                    </div> \
1569                    <div class="pp_overlay"></div>',
1570            gallery_markup: '<div class="pp_gallery"> \
1571                                <a href="#" class="pp_arrow_previous">Previous</a> \
1572                                <div> \
1573                                    <ul> \
1574                                        {gallery} \
1575                                    </ul> \
1576                                </div> \
1577                                <a href="#" class="pp_arrow_next">Next</a> \
1578                            </div>',
1579            image_markup: '<img id="fullResImage" src="{path}" />',
1580            flash_markup: '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{width}" height="{height}"><param name="wmode" value="{wmode}" /><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="{path}" /><embed src="{path}" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="{width}" height="{height}" wmode="{wmode}"></embed></object>',
1581            quicktime_markup: '<object classid="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B" codebase="http://www.apple.com/qtactivex/qtplugin.cab" height="{height}" width="{width}"><param name="src" value="{path}"><param name="autoplay" value="{autoplay}"><param name="type" value="video/quicktime"><embed src="{path}" height="{height}" width="{width}" autoplay="{autoplay}" type="video/quicktime" pluginspage="http://www.apple.com/quicktime/download/"></embed></object>',
1582            iframe_markup: '<iframe src ="{path}" width="{width}" height="{height}" frameborder="no"></iframe>',
1583            inline_markup: '<div class="pp_inline">{content}</div>',
1584            custom_markup: '',
1585            social_tools: '<div class="twitter"><a href="http://twitter.com/share" class="twitter-share-button" data-count="none">Tweet</a><script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script></div><div class="facebook"><iframe src="//www.facebook.com/plugins/like.php?locale=en_US&href={location_href}&amp;layout=button_count&amp;show_faces=true&amp;width=500&amp;action=like&amp;font&amp;colorscheme=light&amp;height=23" scrolling="no" frameborder="0" style="border:none; overflow:hidden; width:500px; height:23px;" allowTransparency="true"></iframe></div>' /* html or false to disable */
1586        }, pp_settings);
1587        
1588        // Global variables accessible only by prettyPhoto
1589        var matchedObjects = this, percentBased = false, pp_dimensions, pp_open,
1590        
1591        // prettyPhoto container specific
1592        pp_contentHeight, pp_contentWidth, pp_containerHeight, pp_containerWidth,
1593        
1594        // Window size
1595        windowHeight = $(window).height(), windowWidth = $(window).width(),
1596
1597        // Global elements
1598        pp_slideshow;
1599        
1600        doresize = true, scroll_pos = _get_scroll();
1601    
1602        // Window/Keyboard events
1603        $(window).unbind('resize.pm-prettyphoto').bind('resize.pm-prettyphoto',function(){ _center_overlay(); _resize_overlay(); });
1604        
1605        if(pp_settings.keyboard_shortcuts) {
1606            $(document).unbind('keydown.pm-prettyphoto').bind('keydown.pm-prettyphoto',function(e){
1607                if(typeof $pp_pic_holder != 'undefined'){
1608                    if($pp_pic_holder.is(':visible')){
1609                        switch(e.keyCode){
1610                            case 37:
1611                                $.prettyPhoto.changePage('previous');
1612                                e.preventDefault();
1613                                break;
1614                            case 39:
1615                                $.prettyPhoto.changePage('next');
1616                                e.preventDefault();
1617                                break;
1618                            case 27:
1619                                if(!settings.modal)
1620                                $.prettyPhoto.close();
1621                                e.preventDefault();
1622                                break;
1623                        };
1624                        // return false;
1625                    };
1626                };
1627            });
1628        };
1629        
1630        /**
1631        * Initialize prettyPhoto.
1632        */
1633        $.prettyPhoto.initialize = function() {
1634            
1635            settings = pp_settings;
1636            
1637            if(settings.theme == 'pp_default') settings.horizontal_padding = 16;
1638            
1639            // Find out if the picture is part of a set
1640            theRel = $(this).attr(settings.hook);
1641            galleryRegExp = /\[(?:.*)\]/;
1642            isSet = (galleryRegExp.exec(theRel)) ? true : false;
1643            
1644            // Put the SRCs, TITLEs, ALTs into an array.
1645            pp_images = (isSet) ? $.map(matchedObjects, function(n, i){ if($(n).attr(settings.hook).indexOf(theRel) != -1) return $(n).attr('href'); }) : $.makeArray($(this).attr('href'));
1646            pp_titles = (isSet) ? $.map(matchedObjects, function(n, i){ if($(n).attr(settings.hook).indexOf(theRel) != -1) return ($(n).find('img').attr('alt')) ? $(n).find('img').attr('alt') : ""; }) : $.makeArray($(this).find('img').attr('alt'));
1647            pp_descriptions = (isSet) ? $.map(matchedObjects, function(n, i){ if($(n).attr(settings.hook).indexOf(theRel) != -1) return ($(n).attr('title')) ? $(n).attr('title') : ""; }) : $.makeArray($(this).attr('title'));
1648            
1649            if(pp_images.length > settings.overlay_gallery_max) settings.overlay_gallery = false;
1650            
1651            set_position = $.inArray($(this).attr('href'), pp_images); // Define where in the array the clicked item is positionned
1652            rel_index = (isSet) ? set_position : $("a["+settings.hook+"^='"+theRel+"']").index($(this));
1653            
1654            _build_overlay(this); // Build the overlay {this} being the caller
1655            
1656            if(settings.allow_resize)
1657                $(window).bind('scroll.pm-prettyphoto',function(){ _center_overlay(); });
1658            
1659            
1660            $.prettyPhoto.open();
1661            
1662            return false;
1663        }
1664
1665
1666        /**
1667        * Opens the prettyPhoto modal box.
1668        * @param image {String,Array} Full path to the image to be open, can also be an array containing full images paths.
1669        * @param title {String,Array} The title to be displayed with the picture, can also be an array containing all the titles.
1670        * @param description {String,Array} The description to be displayed with the picture, can also be an array containing all the descriptions.
1671        */
1672        $.prettyPhoto.open = function(event) {
1673            if(typeof settings == "undefined"){ // Means it's an API call, need to manually get the settings and set the variables
1674                settings = pp_settings;
1675                pp_images = $.makeArray(arguments[0]);
1676                pp_titles = (arguments[1]) ? $.makeArray(arguments[1]) : $.makeArray("");
1677                pp_descriptions = (arguments[2]) ? $.makeArray(arguments[2]) : $.makeArray("");
1678                isSet = (pp_images.length > 1) ? true : false;
1679                set_position = (arguments[3])? arguments[3]: 0;
1680                _build_overlay(event.target); // Build the overlay {this} being the caller
1681            }
1682            
1683            if(settings.hideflash) $('object,embed,iframe[src*=youtube],iframe[src*=vimeo]').css('visibility','hidden'); // Hide the flash
1684
1685            _checkPosition($(pp_images).size()); // Hide the next/previous links if on first or last images.
1686        
1687            $('.pp_loaderIcon').show();
1688        
1689            if(settings.deeplinking)
1690                setHashtag();
1691        
1692            // Rebuild Facebook Like Button with updated href
1693            if(settings.social_tools){
1694                facebook_like_link = settings.social_tools.replace('{location_href}', encodeURIComponent(location.href)); 
1695                $pp_pic_holder.find('.pp_social').html(facebook_like_link);
1696            }
1697            
1698            // Fade the content in
1699            if($ppt.is(':hidden')) $ppt.css('opacity',0).show();
1700            $pp_overlay.show().fadeTo(settings.animation_speed,settings.opacity);
1701
1702            // Display the current position
1703            $pp_pic_holder.find('.currentTextHolder').text((set_position+1) + settings.counter_separator_label + $(pp_images).size());
1704
1705            // Set the description
1706            if(typeof pp_descriptions[set_position] != 'undefined' && pp_descriptions[set_position] != ""){
1707                $pp_pic_holder.find('.pp_description').show().html(unescape(pp_descriptions[set_position]));
1708            }else{
1709                $pp_pic_holder.find('.pp_description').hide();
1710            }
1711            
1712            // Get the dimensions
1713            movie_width = ( parseFloat(getParam('width',pp_images[set_position])) ) ? getParam('width',pp_images[set_position]) : settings.default_width.toString();
1714            movie_height = ( parseFloat(getParam('height',pp_images[set_position])) ) ? getParam('height',pp_images[set_position]) : settings.default_height.toString();
1715            
1716            // If the size is % based, calculate according to window dimensions
1717            percentBased=false;
1718            if(movie_height.indexOf('%') != -1) { movie_height = parseFloat(($(window).height() * parseFloat(movie_height) / 100) - 150); percentBased = true; }
1719            if(movie_width.indexOf('%') != -1) { movie_width = parseFloat(($(window).width() * parseFloat(movie_width) / 100) - 150); percentBased = true; }
1720            
1721            // Fade the holder
1722            $pp_pic_holder.fadeIn(function(){
1723                // Set the title
1724                (settings.show_title && pp_titles[set_position] != "" && typeof pp_titles[set_position] != "undefined") ? $ppt.html(unescape(pp_titles[set_position])) : $ppt.html('&nbsp;');
1725                
1726                imgPreloader = "";
1727                skipInjection = false;
1728                
1729                // Inject the proper content
1730                switch(_getFileType(pp_images[set_position])){
1731                    case 'image':
1732                        imgPreloader = new Image();
1733
1734                        // Preload the neighbour images
1735                        nextImage = new Image();
1736                        if(isSet && set_position < $(pp_images).size() -1) nextImage.src = pp_images[set_position + 1];
1737                        prevImage = new Image();
1738                        if(isSet && pp_images[set_position - 1]) prevImage.src = pp_images[set_position - 1];
1739
1740                        $pp_pic_holder.find('#pp_full_res')[0].innerHTML = settings.image_markup.replace(/{path}/g,pp_images[set_position]);
1741
1742                        imgPreloader.onload = function(){
1743                            // Fit item to viewport
1744                            pp_dimensions = _fitToViewport(imgPreloader.width,imgPreloader.height);
1745
1746                            _showContent();
1747                        };
1748
1749                        imgPreloader.onerror = function(){
1750                            alert('Image cannot be loaded. Make sure the path is correct and image exist.');
1751                            $.prettyPhoto.close();
1752                        };
1753                    
1754                        imgPreloader.src = pp_images[set_position];
1755                    break;
1756                
1757                    case 'youtube':
1758                        pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
1759                        
1760                        // Regular youtube link
1761                        movie_id = getParam('v',pp_images[set_position]);
1762                        
1763                        // youtu.be link
1764                        if(movie_id == ""){
1765                            movie_id = pp_images[set_position].split('youtu.be/');
1766                            movie_id = movie_id[1];
1767                            if(movie_id.indexOf('?') > 0)
1768                                movie_id = movie_id.substr(0,movie_id.indexOf('?')); // Strip anything after the ?
1769
1770                            if(movie_id.indexOf('&') > 0)
1771                                movie_id = movie_id.substr(0,movie_id.indexOf('&')); // Strip anything after the &
1772                        }
1773
1774                        movie = 'http://www.youtube.com/embed/'+movie_id;
1775                        (getParam('rel',pp_images[set_position])) ? movie+="?rel="+getParam('rel',pp_images[set_position]) : movie+="?rel=1";
1776                            
1777                        if(settings.autoplay) movie += "&autoplay=1";
1778                    
1779                        toInject = settings.iframe_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,movie);
1780                    break;
1781                
1782                    case 'vimeo':
1783                        pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
1784                    
1785                        movie_id = pp_images[set_position];
1786                        var regExp = /http(s?):\/\/(www\.)?vimeo.com\/(\d+)/;
1787                        var match = movie_id.match(regExp);
1788                        
1789                        movie = 'http://player.vimeo.com/video/'+ match[3] +'?title=0&amp;byline=0&amp;portrait=0';
1790                        if(settings.autoplay) movie += "&autoplay=1;";
1791                
1792                        vimeo_width = pp_dimensions['width'] + '/embed/?moog_width='+ pp_dimensions['width'];
1793                
1794                        toInject = settings.iframe_markup.replace(/{width}/g,vimeo_width).replace(/{height}/g,pp_dimensions['height']).replace(/{path}/g,movie);
1795                    break;
1796                
1797                    case 'quicktime':
1798                        pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
1799                        pp_dimensions['height']+=15; pp_dimensions['contentHeight']+=15; pp_dimensions['containerHeight']+=15; // Add space for the control bar
1800                
1801                        toInject = settings.quicktime_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}
1801/g,pp_images[set_position]).replace(/{autoplay}/g,settings.autoplay);
1802                    break;
1803                
1804                    case 'flash':
1805                        pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
1806                    
1807                        flash_vars = pp_images[set_position];
1808                        flash_vars = flash_vars.substring(pp_images[set_position].indexOf('flashvars') + 10,pp_images[set_position].length);
1809
1810                        filename = pp_images[set_position];
1811                        filename = filename.substring(0,filename.indexOf('?'));
1812                    
1813                        toInject =  settings.flash_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{wmode}/g,settings.wmode).replace(/{path}/g,filename+'?'+flash_vars);
1814                    break;
1815                
1816                    case 'iframe':
1817                        pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
1818                
1819                        frame_url = pp_images[set_position];
1820
1821                        if ( frame_url.indexOf('?') !== 0 && frame_url.indexOf('iframe') !== -1) {
1822                            frame_url = frame_url.substr(0,frame_url.indexOf('iframe')-1);
1823                        }
1824
1825                        toInject = settings.iframe_markup.replace(/{width}/g,pp_dimensions['width']).replace(/{height}/g,pp_dimensions['height']).replace(/{path}/g,frame_url);
1826                    break;
1827                    
1828                    case 'ajax':
1829                        doresize = false; // Make sure the dimensions are not resized.
1830                        pp_dimensions = _fitToViewport(movie_width,movie_height);
1831                        doresize = true; // Reset the dimensions
1832                    
1833                        skipInjection = true;
1834                        $.get(pp_images[set_position],function(responseHTML){
1835                            toInject = settings.inline_markup.replace(/{content}/g,responseHTML);
1836                            $pp_pic_holder.find('#pp_full_res')[0].innerHTML = toInject;
1837                            _showContent();
1838                        });
1839                        
1840                    break;
1841                    
1842                    case 'custom':
1843                        pp_dimensions = _fitToViewport(movie_width,movie_height); // Fit item to viewport
1844                    
1845                        toInject = settings.custom_markup;
1846                    break;
1847                
1848                    case 'inline':
1849                        // to get the item height clone it, apply default width, wrap it in the prettyPhoto containers , then delete
1850                        myClone = $(pp_images[set_position]).clone().append('<br clear="all" />').css({'width':settings.default_width}).wrapInner('<div id="pp_full_res"><div class="pp_inline"></div></div>').appendTo($('body')).show();
1851                        doresize = false; // Make sure the dimensions are not resized.
1852                        pp_dimensions = _fitToViewport($(myClone).width(),$(myClone).height());
1853                        doresize = true; // Reset the dimensions
1854                        $(myClone).remove();
1855                        toInject = settings.inline_markup.replace(/{content}/g,$(pp_images[set_position]).html());
1856                    break;
1857                };
1858
1859                if(!imgPreloader && !skipInjection){
1860                    $pp_pic_holder.find('#pp_full_res')[0].innerHTML = toInject;
1861                
1862                    // Show content
1863                    _showContent();
1864                };
1865            });
1866
1867            return false;
1868        };
1869
1870    
1871        /**
1872        * Change page in the prettyPhoto modal box
1873        * @param direction {String} Direction of the paging, previous or next.
1874        */
1875        $.prettyPhoto.changePage = function(direction){
1876            currentGalleryPage = 0;
1877            
1878            if(direction == 'previous') {
1879                set_position--;
1880                if (set_position < 0) set_position = $(pp_images).size()-1;
1881            }else if(direction == 'next'){
1882                set_position++;
1883                if(set_position > $(pp_images).size()-1) set_position = 0;
1884            }else{
1885                set_position=direction;
1886            };
1887            
1888            rel_index = set_position;
1889
1890            if(!doresize) doresize = true; // Allow the resizing of the images
1891            if(settings.allow_expand) {
1892                $('.pp_contract').removeClass('pp_contract').addClass('pp_expand');
1893            }
1894
1895            _hideContent(function(){ $.prettyPhoto.open(); });
1896        };
1897
1898
1899        /**
1900        * Change gallery page in the prettyPhoto modal box
1901        * @param direction {String} Direction of the paging, previous or next.
1902        */
1903        $.prettyPhoto.changeGalleryPage = function(direction){
1904            if(direction=='next'){
1905                currentGalleryPage ++;
1906
1907                if(currentGalleryPage > totalPage) currentGalleryPage = 0;
1908            }else if(direction=='previous'){
1909                currentGalleryPage --;
1910
1911                if(currentGalleryPage < 0) currentGalleryPage = totalPage;
1912            }else{
1913                currentGalleryPage = direction;
1914            };
1915            
1916            slide_speed = (direction == 'next' || direction == 'previous') ? settings.animation_speed : 0;
1917
1918            slide_to = currentGalleryPage * (itemsPerPage * itemWidth);
1919
1920            $pp_gallery.find('ul').animate({left:-slide_to},slide_speed);
1921        };
1922
1923
1924        /**
1925        * Start the slideshow...
1926        */
1927        $.prettyPhoto.startSlideshow = function(){
1928            if(typeof pp_slideshow == 'undefined'){
1929                $pp_pic_holder.find('.pp_play').unbind('click').removeClass('pp_play').addClass('pp_pause').click(function(){
1930                    $.prettyPhoto.stopSlideshow();
1931                    return false;
1932                });
1933                pp_slideshow = setInterval($.prettyPhoto.startSlideshow,settings.slideshow);
1934            }else{
1935                $.prettyPhoto.changePage('next');   
1936            };
1937        }
1938
1939
1940        /**
1941        * Stop the slideshow...
1942        */
1943        $.prettyPhoto.stopSlideshow = function(){
1944            $pp_pic_holder.find('.pp_pause').unbind('click').removeClass('pp_pause').addClass('pp_play').click(function(){
1945                $.prettyPhoto.startSlideshow();
1946                return false;
1947            });
1948            clearInterval(pp_slideshow);
1949            pp_slideshow=undefined;
1950        }
1951
1952
1953        /**
1954        * Closes prettyPhoto.
1955        */
1956        $.prettyPhoto.close = function(){
1957            if($pp_overlay.is(":animated")) return;
1958            
1959            $.prettyPhoto.stopSlideshow();
1960            
1961            $pp_pic_holder.stop().find('object,embed').css('visibility','hidden');
1962            
1963            $('div.pp_pic_holder,div.ppt,.pp_fade').fadeOut(settings.animation_speed,function(){ $(this).remove(); });
1964            
1965            $pp_overlay.fadeOut(settings.animation_speed, function(){
1966                
1967                if(settings.hideflash) $('object,embed,iframe[src*=youtube],iframe[src*=vimeo]').css('visibility','visible'); // Show the flash
1968                
1969                $(this).remove(); // No more need for the prettyPhoto markup
1970                
1971                $(window).unbind('scroll.pm-prettyphoto');
1972                
1973                clearHashtag();
1974                
1975                settings.callback();
1976                
1977                doresize = true;
1978                
1979                pp_open = false;
1980                
1981                delete settings;
1982            });
1983        };
1984    
1985        /**
1986        * Set the proper sizes on the containers and animate the content in.
1987        */
1988        function _showContent(){
1989            $('.pp_loaderIcon').hide();
1990
1991            // Calculate the opened top position of the pic holder
1992            projectedTop = scroll_pos['scrollTop'] + ((windowHeight/2) - (pp_dimensions['containerHeight']/2));
1993            if(projectedTop < 0) projectedTop = 0;
1994
1995            $ppt.fadeTo(settings.animation_speed,1);
1996
1997            // Resize the content holder
1998            $pp_pic_holder.find('.pp_content')
1999                .animate({
2000                    height:pp_dimensions['contentHeight'],
2001                    width:pp_dimensions['contentWidth']
2002                },settings.animation_speed);
2003
2004            var halfWidth = (windowWidth/2) - (pp_dimensions['containerWidth']/2);
2005
2006            // Resize picture the holder
2007            $pp_pic_holder.animate({
2008                'top': projectedTop,
2009                'left': (halfWidth < 0) ? 0 : halfWidth,
2010                'width': pp_dimensions['containerWidth']
2011            },settings.animation_speed,function(){
2012                $pp_pic_holder.find('.pp_hoverContainer,#fullResImage').height(pp_dimensions['height']).width(pp_dimensions['width']);
2013
2014                $pp_pic_holder.find('.pp_fade').fadeIn(settings.animation_speed); // Fade the new content
2015
2016                // Show the nav
2017                if(isSet && _getFileType(pp_images[set_position])=="image") { $pp_pic_holder.find('.pp_hoverContainer').show(); }else{ $pp_pic_holder.find('.pp_hoverContainer').hide(); }
2018            
2019                if(settings.allow_expand) {
2020                    if(pp_dimensions['resized']){ // Fade the resizing link if the image is resized
2021                        $('a.pp_expand,a.pp_contract').show();
2022                    }else{
2023                        $('a.pp_expand').hide();
2024                    }
2025                }
2026                
2027                if(settings.autoplay_slideshow && !pp_slideshow && !pp_open) $.prettyPhoto.startSlideshow();
2028                
2029                settings.changepicturecallback(); // Callback!
2030                
2031                pp_open = true;
2032            });
2033            
2034            _insert_gallery();
2035            pp_settings.ajaxcallback();
2036        };
2037        
2038        /**
2039        * Hide the content...DUH!
2040        */
2041        function _hideContent(callback){
2042            // Fade out the current picture
2043            $pp_pic_holder.find('#pp_full_res object,#pp_full_res embed').css('visibility','hidden');
2044            $pp_pic_holder.find('.pp_fade').fadeOut(settings.animation_speed,function(){
2045                $('.pp_loaderIcon').show();
2046                
2047                callback();
2048            });
2049        };
2050    
2051        /**
2052        * Check the item position in the gallery array, hide or show the navigation links
2053        * @param setCount {integer} The total number of items in the set
2054        */
2055        function _checkPosition(setCount){
2056            (setCount > 1) ? $('.pp_nav').show() : $('.pp_nav').hide(); // Hide the bottom nav if it's not a set.
2057        };
2058    
2059        /**
2060        * Resize the item dimensions if it's bigger than the viewport
2061        * @param width {integer} Width of the item to be opened
2062        * @param height {integer} Height of the item to be opened
2063        * @return An array containin the "fitted" dimensions
2064        */
2065        function _fitToViewport(width,height){
2066            resized = false;
2067
2068            _getDimensions(width,height);
2069            
2070            // Define them in case there's no resize needed
2071            imageWidth = width, imageHeight = height;
2072
2073            if( ((pp_containerWidth > windowWidth) || (pp_containerHeight > windowHeight)) && doresize && settings.allow_resize && !percentBased) {
2074                resized = true, fitting = false;
2075            
2076                while (!fitting){
2077                    if((pp_containerWidth > windowWidth)){
2078                        imageWidth = (windowWidth - 100);
2079                        imageHeight = (height/width) * imageWidth;
2080                    }else if((pp_containerHeight > windowHeight)){
2081                        imageHeight = (windowHeight - 100);
2082                        imageWidth = (width/height) * imageHeight;
2083                    }else{
2084                        fitting = true;
2085                    };
2086
2087                    pp_containerHeight = imageHeight, pp_containerWidth = imageWidth;
2088                };
2089            
2090
2091                
2092                if((pp_containerWidth > windowWidth) || (pp_containerHeight > windowHeight)){
2093                    _fitToViewport(pp_containerWidth,pp_containerHeight)
2094                };
2095                
2096                _getDimensions(imageWidth,imageHeight);
2097            };
2098            
2099            return {
2100                width:Math.floor(imageWidth),
2101                height:Math.floor(imageHeight),
2102                containerHeight:Math.floor(pp_containerHeight),
2103                containerWidth:Math.floor(pp_containerWidth) + (settings.horizontal_padding * 2),
2104                contentHeight:Math.floor(pp_contentHeight),
2105                contentWidth:Math.floor(pp_contentWidth),
2106                resized:resized
2107            };
2108        };
2109        
2110        /**
2111        * Get the containers dimensions according to the item size
2112        * @param width {integer} Width of the item to be opened
2113        * @param height {integer} Height of the item to be opened
2114        */
2115        function _getDimensions(width,height){
2116            width = parseFloat(width);
2117            height = parseFloat(height);
2118
2119            // Get the details height, to do so, I need to clone it since it's invisible
2120            $pp_details = $pp_pic_holder.find('.pp_details');
2121            $pp_details.width(width);
2122            detailsHeight = parseFloat($pp_details.css('marginTop')) + parseFloat($pp_details.css('marginBottom'));
2123
2124            $pp_details = $pp_details.clone().addClass(settings.theme).width(width).appendTo($('body')).css({
2125                'position':'absolute',
2126                'top':-10000
2127            });
2128            detailsHeight += $pp_details.height();
2129            detailsHeight = (detailsHeight <= 34) ? 36 : detailsHeight; // Min-height for the details
2130            $pp_details.remove();
2131
2132            // Get the titles height, to do so, I need to clone it since it's invisible
2133            $pp_title = $pp_pic_holder.find('.ppt');
2134            $pp_title.width(width);
2135            titleHeight = parseFloat($pp_title.css('marginTop')) + parseFloat($pp_title.css('marginBottom'));
2136            $pp_title = $pp_title.clone().appendTo($('body')).css({
2137                'position':'absolute',
2138                'top':-10000
2139            });
2140            titleHeight += $pp_title.height();
2141            $pp_title.remove();
2142
2143            // Get the container size, to resize the holder to the right dimensions
2144            pp_contentHeight = height + detailsHeight;
2145            pp_contentWidth = width;
2146            pp_containerHeight = pp_contentHeight + titleHeight + $pp_pic_holder.find('.pp_top').height() + $pp_pic_holder.find('.pp_bottom').height();
2147            pp_containerWidth = width;
2148        }
2149    
2150        function _getFileType(itemSrc){
2151            var noQueryString = itemSrc.split("?")[0];
2152
2153            if (itemSrc.match(/youtube\.com\/watch/i) || itemSrc.match(/youtu\.be/i)) {
2154                return 'youtube';
2155            } else if (itemSrc.match(/vimeo\.com/i)) {
2156                return 'vimeo';
2157            } else if (itemSrc.match(/\b.mov\b/i)) {
2158                return 'quicktime';
2159            } else if (itemSrc.match(/\b.swf\b/i)) {
2160                return 'flash';
2161            } else if (itemSrc.match(/\bajax=true\b/i)) {
2162                return 'ajax';
2163            } else if (itemSrc.match(/\bcustom=true\b/i)) {
2164                return 'custom';
2165            } else if (itemSrc.substr(0,1) == '#') {
2166                return 'inline';
2167            } else if (
2168                itemSrc.match(/\biframe=true\b/i)
2169                || !noQueryString.match(/\.(gif|jpg|jpeg|tiff|png)$/i)
2170            ) {
2171                return 'iframe';
2172            } else {
2173                return 'image';
2174            };
2175        };
2176    
2177        function _center_overlay(){
2178            if(doresize && typeof $pp_pic_holder != 'undefined') {
2179                scroll_pos = _get_scroll();
2180                contentHeight = $pp_pic_holder.height(), contentwidth = $pp_pic_holder.width();
2181
2182                projectedTop = (windowHeight/2) + scroll_pos['scrollTop'] - (contentHeight/2);
2183                if(projectedTop < 0) projectedTop = 0;
2184
2185                if(contentHeight > windowHeight)
2186                    return;
2187
2188                $pp_pic_holder.css({
2189                    'top': projectedTop,
2190                    'left': (windowWidth/2) + scroll_pos['scrollLeft'] - (contentwidth/2)
2191                });
2192            };
2193        };
2194    
2195        function _get_scroll(){
2196            if (self.pageYOffset) {
2197                return {scrollTop:self.pageYOffset,scrollLeft:self.pageXOffset};
2198            } else if (document.documentElement && document.documentElement.scrollTop) { // Explorer 6 Strict
2199                return {scrollTop:document.documentElement.scrollTop,scrollLeft:document.documentElement.scrollLeft};
2200            } else if (document.body) {// all other Explorers
2201                return {scrollTop:document.body.scrollTop,scrollLeft:document.body.scrollLeft};
2202            };
2203        };
2204    
2205        function _resize_overlay() {
2206            windowHeight = $(window).height(), windowWidth = $(window).width();
2207            
2208            if(typeof $pp_overlay != "undefined") $pp_overlay.height($(document).height()).width(windowWidth);
2209        };
2210    
2211        function _insert_gallery(){
2212            if(isSet && settings.overlay_gallery && _getFileType(pp_images[set_position])=="image") {
2213                itemWidth = 52+5; // 52 beign the thumb width, 5 being the right margin.
2214                navWidth = (settings.theme == "facebook" || settings.theme == "pp_default") ? 50 : 30; // Define the arrow width depending on the theme
2215                
2216                itemsPerPage = Math.floor((pp_dimensions['containerWidth'] - 100 - navWidth) / itemWidth);
2217                itemsPerPage = (itemsPerPage < pp_images.length) ? itemsPerPage : pp_images.length;
2218                totalPage = Math.ceil(pp_images.length / itemsPerPage) - 1;
2219
2220                // Hide the nav in the case there's no need for links
2221                if(totalPage == 0){
2222                    navWidth = 0; // No nav means no width!
2223                    $pp_gallery.find('.pp_arrow_next,.pp_arrow_previous').hide();
2224                }else{
2225                    $pp_gallery.find('.pp_arrow_next,.pp_arrow_previous').show();
2226                };
2227
2228                galleryWidth = itemsPerPage * itemWidth;
2229                fullGalleryWidth = pp_images.length * itemWidth;
2230                
2231                // Set the proper width to the gallery items
2232                $pp_gallery
2233                    .css('margin-left',-((galleryWidth/2) + (navWidth/2)))
2234                    .find('div:first').width(galleryWidth+5)
2235                    .find('ul').width(fullGalleryWidth)
2236                    .find('li.selected').removeClass('selected');
2237                
2238                goToPage = (Math.floor(set_position/itemsPerPage) < totalPage) ? Math.floor(set_position/itemsPerPage) : totalPage;
2239
2240                $.prettyPhoto.changeGalleryPage(goToPage);
2241                
2242                $pp_gallery_li.filter(':eq('+set_position+')').addClass('selected');
2243            }else{
2244                $pp_pic_holder.find('.pp_content').unbind('mouseenter mouseleave');
2245                // $pp_gallery.hide();
2246            }
2247        }
2248    
2249        function _build_overlay(caller){
2250            // Inject Social Tool markup into General markup
2251            if(settings.social_tools)
2252                facebook_like_link = settings.social_tools.replace('{location_href}', encodeURIComponent(location.href)); 
2253
2254            settings.markup = settings.markup.replace('{pp_social}',''); 
2255            
2256            $('body').append(settings.markup); // Inject the markup
2257            
2258            $pp_pic_holder = $('.pp_pic_holder') , $ppt = $('.ppt'), $pp_overlay 
2258= $('div.pp_overlay'); // Set my global selectors
2259            
2260            // Inject the inline gallery!
2261            if(isSet && settings.overlay_gallery) {
2262                currentGalleryPage = 0;
2263                toInject = "";
2264                for (var i=0; i < pp_images.length; i++) {
2265                    if(!pp_images[i].match(/\b(jpg|jpeg|png|gif)\b/gi)){
2266                        classname = 'default';
2267                        img_src = '';
2268                    }else{
2269                        classname = '';
2270                        img_src = pp_images[i];
2271                    }
2272                    toInject += "<li class='"+classname+"'><a href='#'><img src='" + img_src + "' width='50' alt='' /></a></li>";
2273                };
2274                
2275                toInject = settings.gallery_markup.replace(/{gallery}/g,toInject);
2276                
2277                $pp_pic_holder.find('#pp_full_res').after(toInject);
2278                
2279                $pp_gallery = $('.pp_pic_holder .pp_gallery'), $pp_gallery_li = $pp_gallery.find('li'); // Set the gallery selectors
2280                
2281                $pp_gallery.find('.pp_arrow_next').click(function(){
2282                    $.prettyPhoto.changeGalleryPage('next');
2283                    $.prettyPhoto.stopSlideshow();
2284                    return false;
2285                });
2286                
2287                $pp_gallery.find('.pp_arrow_previous').click(function(){
2288                    $.prettyPhoto.changeGalleryPage('previous');
2289                    $.prettyPhoto.stopSlideshow();
2290                    return false;
2291                });
2292                
2293                $pp_pic_holder.find('.pp_content').hover(
2294                    function(){
2295                        $pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeIn();
2296                    },
2297                    function(){
2298                        $pp_pic_holder.find('.pp_gallery:not(.disabled)').fadeOut();
2299                    });
2300
2301                itemWidth = 52+5; // 52 beign the thumb width, 5 being the right margin.
2302                $pp_gallery_li.each(function(i){
2303                    $(this)
2304                        .find('a')
2305                        .click(function(){
2306                            $.prettyPhoto.changePage(i);
2307                            $.prettyPhoto.stopSlideshow();
2308                            return false;
2309                        });
2310                });
2311            };
2312            
2313            
2314            // Inject the play/pause if it's a slideshow
2315            if(settings.slideshow){
2316                $pp_pic_holder.find('.pp_nav').prepend('<a href="#" class="pp_play">Play</a>')
2317                $pp_pic_holder.find('.pp_nav .pp_play').click(function(){
2318                    $.prettyPhoto.startSlideshow();
2319                    return false;
2320                });
2321            }
2322            
2323            $pp_pic_holder.attr('class','pp_pic_holder ' + settings.theme); // Set the proper theme
2324            
2325            $pp_overlay
2326                .css({
2327                    'opacity':0,
2328                    'height':$(document).height(),
2329                    'width':$(window).width()
2330                    })
2331                .bind('click',function(){
2332                    if(!settings.modal) $.prettyPhoto.close();
2333                });
2334
2335            $('a.pp_close').bind('click',function(){ $.prettyPhoto.close(); return false; });
2336
2337
2338            if(settings.allow_expand) {
2339                $('a.pp_expand').bind('click',function(e){
2340                    // Expand the image
2341                    if($(this).hasClass('pp_expand')){
2342                        $(this).removeClass('pp_expand').addClass('pp_contract');
2343                        doresize = false;
2344                    }else{
2345                        $(this).removeClass('pp_contract').addClass('pp_expand');
2346                        doresize = true;
2347                    };
2348                
2349                    _hideContent(function(){ $.prettyPhoto.open(); });
2350            
2351                    return false;
2352                });
2353            }
2354        
2355            $pp_pic_holder.find('.pp_previous, .pp_nav .pp_arrow_previous').bind('click',function(){
2356                $.prettyPhoto.changePage('previous');
2357                $.prettyPhoto.stopSlideshow();
2358                return false;
2359            });
2360        
2361            $pp_pic_holder.find('.pp_next, .pp_nav .pp_arrow_next').bind('click',function(){
2362                $.prettyPhoto.changePage('next');
2363                $.prettyPhoto.stopSlideshow();
2364                return false;
2365            });
2366            
2367            _center_overlay(); // Center it
2368        };
2369
2370        if(!pp_alreadyInitialized && getHashtag()){
2371            pp_alreadyInitialized = true;
2372            
2373            // Grab the rel index to trigger the click on the correct element
2374            hashIndex = getHashtag();
2375            hashRel = hashIndex;
2376            hashIndex = hashIndex.substring(hashIndex.indexOf('/')+1,hashIndex.length-1);
2377            hashRel = hashRel.substring(0,hashRel.indexOf('/'));
2378
2379            // Little timeout to make sure all the prettyPhoto initialize scripts has been run.
2380            // Useful in the event the page contain several init scripts.
2381            setTimeout(function(){ $("a["+pp_settings.hook+"^='"+hashRel+"']:eq("+hashIndex+")").trigger('click'); },50);
2382        }
2383        
2384        return this.unbind('click.pm-prettyphoto').bind('click.pm-prettyphoto',$.prettyPhoto.initialize); // Return the jQuery object for chaining. The unbind method is used to avoid click conflict when the plugin is called more than once
2385    };
2386    
2387    function getHashtag(){
2388        var url = location.href;
2389        hashtag = (url.indexOf('#prettyPhoto') !== -1) ? decodeURI(url.substring(url.indexOf('#prettyPhoto')+1,url.length)) : false;
2390        if (hashtag) {
2391            hashtag = hashtag.replace(/<|>/g,'');
2392        }
2393        return hashtag;
2394    };
2395    
2396    function setHashtag(){
2397        if(typeof theRel == 'undefined') return; // theRel is set on normal calls, it's impossible to deeplink using the API
2398        location.hash = theRel + '/'+rel_index+'/';
2399    };
2400    
2401    function clearHashtag(){
2402        if ( location.href.indexOf('#prettyPhoto') !== -1 ) location.hash = "prettyPhoto";
2403    }
2404    
2405    function getParam(name,url){
2406      name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
2407      var regexS = "[\\?&]"+name+"=([^&#]*)";
2408      var regex = new RegExp( regexS );
2409      var results = regex.exec( url );
2410      return ( results == null ) ? "" : results[1];
2411    }
2412    
2413})(window.JQPM||jQuery);
2414
2415var pp_alreadyInitialized = false; // Used for the deep linking to make sure not to call the same function several times.
2416
2417PhotoMosaic.Utils = (function(){
2418    var S4 = function () {
2419        return ( ( (1 + Math.random()) * 0x10000 ) | 0 ).toString(16).substring(1);
2420    };
2421
2422    return {
2423        log : {
2424            info: function (msg) {
2425                this.prefix(msg);
2426            },
2427
2428            error: function (msg) {
2429                this.prefix("ERROR: " + msg);
2430            },
2431
2432            prefix: function (msg) {
2433                this.print("PhotoMosaic: " + msg);
2434            },
2435
2436            print: function (msg) {
2437                if (window.console !== undefined) {
2438                    window.console.log(msg);
2439                }
2440            }
2441        },
2442
2443        makeID : function (small, prefix) {
2444            prefix = (prefix) ? prefix + '_' : '';
2445            if (small) {
2446                return prefix + S4() + S4() + '' + S4() + S4();
2447            } else {
2448                return prefix + ( S4() + S4() + '-' + S4() + '-' + S4() + '-' + S4() + '-' + S4() + S4() + S4() );
2449            }
2450        },
2451
2452        deepSearch : function (obj, key, value) {
2453            // recursively traverses an nested arrays, and objects looking for a key/value pair
2454            var response = null;
2455            var i = 0;
2456            var prop;
2457
2458            if (obj instanceof Array) {
2459                for (i = 0; i < obj.length; i++) {
2460                    response = this.deepSearch(obj[i], key, value);
2461                    if (response) {
2462                        return response;
2463                    }
2464                }
2465            } else {
2466                if (obj.hasOwnProperty(key) && obj[key] == value) {
2467                    return obj
2468                } else {
2469                    for (prop in obj) {
2470                        if (obj.hasOwnProperty(prop)) {
2471                            if (obj[prop] instanceof Object || obj[prop] instanceof Array) {
2472                                response = this.deepSearch(obj[prop], key, value);
2473                                if (response) {
2474                                    return response;
2475                                }
2476                            }
2477                        }
2478                    }
2479                }
2480            }
2481
2482            return response;
2483        },
2484
2485        arrayToObj : function (array, key) {
2486            var response = {};
2487            for (var i = 0; i < array.length; i++) {
2488                response[ array[i][key] ] = array[i];
2489            };
2490            return response;
2491        },
2492
2493        pickImageSize : function (images, sizes, honor_device_pixel_ratio) {
2494            // currently only supported in PM4WP
2495            if (!sizes || !images[0].sizes) { return images; }
2496
2497            for (var i = 0; i < images.length; i++) {
2498                var image = images[i];
2499                var size = null;
2500                var scaled = {
2501                    width : 0,
2502                    height : 0
2503                };
2504                var pixel_ratio = (honor_device_pixel_ratio && window.devicePixelRatio) ? window.devicePixelRatio : 1;
2505
2506                for (var key in sizes) {
2507                    if (sizes.hasOwnProperty(key)) {
2508                        // are we dealing with a portrait or landscape image?
2509                        if (image.width.original >= image.height.original) {
2510                            scaled.width = sizes[key];
2511                            scaled.height = Math.floor((scaled.width * image.height.original) / image.width.original);
2512                        } else {
2513                            scaled.height = sizes[key];
2514                            scaled.width = Math.floor((scaled.height * image.width.original) / image.height.original);
2515                        }
2516
2517                        // compare the dims of the image to the space to which is has been scaled
2518                        // if either of the image's dims are less than the container's dims - we'd be scaling up
2519                        // scaling up is bad
2520                        // keep looping until we scale the image down
2521
2522                        if (scaled.width < (image.width.adjusted * pixel_ratio) ||
2523                            scaled.height < (image.height.adjusted * pixel_ratio)) {
2524                            continue;
2525                        } else {
2526                            size = key;
2527                            break;
2528                        }
2529                    }
2530                };
2531
2532                // if none of the known sizes are big enough, go with the biggest we've got
2533                if (!size) {
2534                    size = 'full';
2535                }
2536
2537                image.src = image.sizes[size];
2538            }
2539
2540            return images;
2541        },
2542
2543        // setImageConstraint : function (images, layout, sizing) {
2544        //     var prefix = "photomosaic-constrain-";
2545        //     var image = null;
2546        //     var container_is_larger = null;
2547        //     var sizing = sizing || 'cover';
2548
2549        //     function constrainWhichDimension (container_is_larger) {
2550        //         var dim = "width";
2551        //         if ((container_is_larger && (sizing === 'contain')) ||
2552        //             (!container_is_larger && (sizing === 'cover'))) {
2553        //             dim = "height";
2554        //         }
2555        //         return dim;
2556        //     }
2557
2558        //     for (var i = 0; i < images.length; i++) {
2559        //         image = images[i];
2560        //         container_is_larger = (image.width.container / image.height.container) > (image.width.original / image.height.original);
2561
2562        //         image.constraint = prefix + constrainWhichDimension(container_is_larger);
2563        //     }
2564
2565        //     images[i] = image;
2566
2567        //     return images;
2568        // },
2569
2570        decodeHTML : function (str) {
2571            var e = document.createElement('div');
2572            e.innerHTML = str;
2573            return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
2574        },
2575
2576        // taken from UnderscoreJS _.debounce()
2577        debounce : function(func, wait, immediate) {
2578            var timeout = null;
2579            var args = null;
2580            var context = null;
2581            var timestamp = null;
2582            var result = null;
2583
2584            var now = Date.now || function() { return new Date().getTime(); };
2585
2586            var later = function() {
2587                var last = now() - timestamp;
2588                if (last < wait) {
2589                    timeout = setTimeout(later, wait - last);
2590                } else {
2591                    timeout = null;
2592                    if (!immediate) {
2593                        result = func.apply(context, args);
2594                        context = args = null;
2595                    }
2596                }
2597            };
2598
2599            return function() {
2600                context = this;
2601                args = arguments;
2602                timestamp = now();
2603                var callNow = immediate && !timeout;
2604                if (!timeout) {
2605                    timeout = setTimeout(later, wait);
2606                }
2607                if (callNow) {
2608                    result = func.apply(context, args);
2609                    context = args = null;
2610                }
2611
2612                return result;
2613            };
2614        },
2615
2616        logGalleryData : function (gallery) {
2617            var output = [];
2618            for (var i = 0; i < gallery.length; i++) {
2619                output.push({
2620                    src : gallery[i].src,
2621                    thumb : gallery[i].thumb,
2622                    caption : gallery[i].caption,
2623                    width : gallery[i].width.original,
2624                    height : gallery[i].height.original
2625                });
2626            }
2627            PhotoMosaic.Utils.log.info("Generate Gallery Data...");
2628            PhotoMosaic.Utils.log.print( JSON.stringify(output) );
2629        }
2630    };
2631}(window.JQPM));
2632PhotoMosaic.ErrorChecks = (function($){
2633    return {
2634        initial: function (opts) {
2635            if (opts.input === 'xml') {
2636                if (typeof opts.gallery !== 'string' || opts.gallery === '') {
2637                    PhotoMosaic.Utils.log.error("No XML file path specified.");
2638                    return true;
2639                }
2640            }
2641
2642            if (opts.input === 'json') {
2643                if (typeof opts.gallery === 'string') {
2644                    if (opts.gallery === '') {
2645                        PhotoMosaic.Utils.log.error("No JSON object defined.");
2646                        return true;
2647                    }
2648
2649                    if (typeof window[opts.gallery] !== 'undefined') {
2650                        opts.gallery = window[opts.gallery];
2651                    } else {
2652                        PhotoMosaic.Utils.log.error("No JSON object found when referencing '" + opts.gallery + "'.");
2653                        PhotoMosaic.Utils.log.info("Make sure your variable is avaible to the global scope (window['" + opts.gallery + "']) or simply pass the object literal (gallery:" + opts.gallery + ") instead of a string (gallery:\"" + opts.gallery + "\").");
2654                        return true;
2655                    }
2656                }
2657
2658                if (opts.gallery.length === 0) {
2659                    PhotoMosaic.Utils.log.error("Specified gallery data is empty.");
2660                    return true;
2661                }
2662            }
2663
2664            if (opts.layout == 'columns' && opts.prevent_crop && opts.height !== 'auto') {
2665                PhotoMosaic.Utils.log.info("Height must be set to 'auto' to Prevent Cropping. The value for height (" + opts.height + ") is being ignored so as to prevent cropping.");
2666                opts.height = "auto";
2667            }
2668            return false;
2669        },
2670        nonModernBrowser: function () {
2671            // first 2 = can't handle React
2672            // last = can't position mosaic-items
2673            return (!Array.isArray || !Object.freeze || !PhotoMosaic.Plugins.Modernizr.csstransforms);
2674        },
2675// these got moved into layouts/columns.js
2676        imageDimensions: function (images) {
2677            var to_delete = [];
2678
2679            $.each(images, function (i) {
2680                if (isNaN(this.height.adjusted)) {
2681                    to_delete.push(i);
2682                }
2683            });
2684
2685            for (var i = to_delete.length - 1; i >= 0; i--) {
2686                PhotoMosaic.Utils.log.error("The following image failed to load and was skipped.\n" + images[to_delete[i]].src);
2687                var rest = images.slice( to_delete[i] + 1 );
2688                images.length = to_delete[i];
2689                images.push.apply(images, rest);
2690            }
2691
2692            return images;
2693        },
2694
2695        layout: function (json) {
2696            var i = 0;
2697            var j = 0;
2698
2699            for (i = 0; i < json.columns.length; i++) {
2700                for (j = 0; j < json.columns[i].images.length; j++) {
2701                    if (json.columns[i].images[j].height.constraint <= 0) {
2702                        PhotoMosaic.Utils.log.error("Your gallery's height is too small for your current settings and won't render correctly.");
2703                        return true;
2704                    }
2705                };
2706            };
2707
2708            return false;    
2709        }
2710    };
2711}(JQPM));
2712PhotoMosaic.Inputs = (function ($){
2713    return {
2714        json : function (gallery) {
2715            return gallery;
2716        },
2717        html : function ($node, opts) {
2718            var gallery = [];
2719            var $images = $node.find('img');
2720
2721            for (var i = 0; i < $images.length; i++) {
2722                var $image = $images.eq(i);
2723                var image = {
2724                    caption : $image.attr('title'),
2725                    alt : $image.attr('alt'),
2726                    width : parseInt( $image.attr('width') ),
2727                    height : parseInt( $image.attr('height') )
2728                };
2729
2730                if ($image.parent('a').length > 0 && opts.links) {
2731                    image.src = $image.attr('src');
2732                    image.url = $image.parent('a').attr('href');
2733                } else if ($image.parent('a').length > 0) {
2734                    image.src = $image.parent('a').attr('href');
2735                } else {
2736                    image.src = $image.attr('src');
2737                }
2738
2739                gallery.push(image);
2740            }
2741
2742            return gallery;
2743        },
2744        xml : function (gallery) {
2745            var response = [];
2746
2747            gallery.find('photo').each(function (i) {
2748                var image = {};
2749                var data = $(this);
2750
2751                image.caption = data.children('title').text();
2752                image.alt = data.children('alt').text();
2753                image.src = data.children('src').text();
2754                image.thumb = data.children('thumb').text();
2755                image.url = data.children('url').text();
2756                image.width = data.children('width').text();
2757                image.height = data.children('height').text();
2758
2759                response.push(image);
2760            });
2761
2762            return response;
2763        }
2764    };
2765
2766}(window.JQPM));
2767(function ($) {
2768    var self = null;
2769    var loading_class = "photomosaic-loading";
2770    var loaded_class = "photomosaic-loaded";
2771
2772    PhotoMosaic.Loader = function ($container, mosaic) {
2773        self = this;
2774
2775        this.obj = $container;
2776        this.images = $container.find('img');
2777        this.mosaic = mosaic;
2778        this.opts = mosaic.opts;
2779
2780        if (this.opts.lazyload === false) {
2781            this.skipLazyload();
2782        } else {
2783            this.trigger_point = $.waypoints('viewportHeight') + this.opts.lazyload;
2784            this.lazyload();
2785        }
2786
2787        // if you want a loading transition but the browser doesn't support it... fade (old IEs)
2788        if ( this.opts.loading_transition !== 'none' && !PhotoMosaic.Plugins.Modernizr.csstransitions ) {
2789            this.images.css('opacity','0');
2790        }
2791
2792        return this;
2793    };
2794
2795    PhotoMosaic.Loader.prototype = {
2796        lazyload : function () {
2797            this.images.parent().waypoint({
2798                triggerOnce : true,
2799                offset : this.trigger_point,
2800                handler : this.handler
2801            });
2802        },
2803
2804        skipLazyload : function () {
2805            this.images.parent().each(function () {
2806                self.handler.apply(this);
2807            });
2808        },
2809
2810        handler : function () {
2811            var $this = $(this);
2812            var $image = $this.find('img');
2813            var image_loaded = null;
2814
2815            $image.attr('src', $image.attr('data-src'));
2816
2817            image_loaded = new PhotoMosaic.Plugins.imagesLoaded($image.get(0));
2818            image_loaded.on('progress', self.progress);
2819            image_loaded.on('fail', self.fail);
2820            image_loaded.on('always', self.always);
2821        },
2822
2823        progress : function (instance, image) {
2824            // after each image has loaded
2825            setTimeout(function () {
2826                // if you don't want a loading transition OR it's handled by CSS
2827                if ( self.opts.loading_transition === 'none' || PhotoMosaic.Plugins.Modernizr.csstransitions ) {
2828                    var $image = $(image.img);
2829                    var $parent = $image.closest('.' + loading_class);
2830                    var toggleClasses = function () {
2831                        $parent.addClass(loaded_class);
2832                        $image.off(self.mosaic._transition_end_event_name);
2833                    };
2834
2835                    $image.on(
2836                        self.mosaic._transition_end_event_name,
2837                        toggleClasses
2838                    );
2839
2840                    $parent.removeClass(loading_class);
2841
2842                } else {
2843                    // you want a transition but the browser doesn't support CSS Transitions... fade (old IEs)
2844                    $(image.img).animate(
2845                        { 'opacity' : '1' },
2846                        self.opts.resize_transition_settings.duration * 1000,
2847                        function(){
2848                            $(this).parents('.' + loading_class).removeClass(loading_class);
2849                        }
2850                    );
2851                }
2852            }, 0);
2853        },
2854
2855        fail : function (instance) {
2856            // after all images have been loaded with at least one broken image
2857            setTimeout(function () {
2858                var id = '';
2859                var img = null;
2860                var i = 0;
2861                var j = 0;
2862                for (i = 0; i < instance.images.length; i++) {
2863                    if (!instance.images[i].isLoaded) {
2864                        $node = $(instance.images[i].img);
2865                        id = $node.attr('id');
2866                        img = PhotoMosaic.Utils.deepSearch(self.mosaic.images, 'id', id);
2867
2868                        for (j = 0; j < self.mosaic.images.length; j++) {
2869                            if (self.mosaic.images[j] === img) {
2870                                self.mosaic.images.splice(j,1);
2871                            }
2872                        };
2873
2874                        $node.parent().remove();
2875                    }
2876                };
2877
2878                self.mosaic.refresh();
2879            }, 0);
2880        },
2881
2882        always : function (instance) {
2883            // after all images have been either loaded or confirmed broken
2884            setTimeout(function () {
2885                var $mosaic = self.mosaic.obj.find('.photoMosaic')
2886                var $images = $mosaic.children('a, span'); 
2887                var $loading = $images.filter('.' + loading_class);
2888
2889                // transitionend fires for each proprty being transitioned, we only care about when the last one ends
2890                var toggleClasses = PhotoMosaic.Utils.debounce(function () {
2891                    $mosaic.removeClass(loading_class).addClass(loaded_class);
2892                    self.mosaic.obj.off(self.mosaic._transition_end_event_name);
2893                }, 1000);
2894
2895                if ($loading.length == 0) {
2896                    self.mosaic.obj.on(
2897                        self.mosaic._transition_end_event_name,
2898                        toggleClasses
2899                    );
2900                }
2901            }, 0);
2902        }
2903    };
2904}(window.JQPM));
2905(function ($) {
2906    PhotoMosaic.Layouts.Common = {
2907        getRelativeWidth : function (_options, opts, node) {
2908            // auto = 100%
2909            return this.getRelativeDimenion(_options, opts, node, 'width', node.width());
2910        },
2911
2912        getRelativeHeight : function (_options, opts, node) {
2913            // auto != 100%
2914            return this.getRelativeDimenion(_options, opts, node, 'height', false);
2915        },
2916
2917        getRelativeDimenion : function (_options, opts, node, dim, fallback) {
2918            if (opts[dim] && opts[dim] !== 'auto') {
2919                if (typeof(_options[dim]) == 'number') {
2920                    return opts[dim];
2921                } else if (_options[dim].indexOf('%') > -1) {
2922                    return Math.floor((node[dim]() * (parseInt(_options[dim], 10) / 100)));
2923                } else {
2924                    // I can't imaging what wouldn't be 'auto', a %, or an int
2925                    return fallback;
2926                }
2927            } else {
2928                return fallback;
2929            }
2930        },
2931
2932        makeColumnBuckets : function (opts) {
2933            var columns = [];
2934            var num_cols = 0;
2935            var max_width = 0;
2936            var num_images = 0
2937            var i = 0;
2938
2939            if (opts.columns && opts.columns !== 'auto') {
2940                num_images = opts.gallery.length;
2941                num_cols = (num_images < opts.columns) ? num_images : opts.columns;
2942            } else {
2943                max_width = opts.width;
2944                num_images = opts.gallery.length;
2945                mobile_cutoff = 425;
2946                num_cols = (max_width < mobile_cutoff) ? 1 : Math.floor(max_width / opts.min_column_width);
2947
2948                if (num_images < num_cols) {
2949                    num_cols = num_images;
2950                }
2951
2952                if ((opts.max_columns !== 'auto') && (num_cols > opts.max_columns)) {
2953                    num_cols = opts.max_columns;
2954                }
2955
2956                if ((opts.min_columns !== 'auto') && (num_cols < opts.min_columns)) {
2957                    num_cols = opts.min_columns;
2958                }
2959            }
2960
2961            for (i = 0; i < num_cols; i++) {
2962                columns.push( [] );
2963            };
2964
2965            return columns;
2966        },
2967
2968        getColumnWidth : function (columns, opts) {
2969            var num_cols = columns.length;
2970            var total_padding = opts.padding * (num_cols - 1); // we only pad between columns
2971            var usable_width = opts.width - total_padding;
2972            var col_width = Math.floor(usable_width / num_cols);
2973            return col_width;
2974        },
2975
2976        getColumnHeights : function (imagesById, columns, opts) {
2977            var column_heights = [];
2978            var column_height = 0;
2979            var image = null;
2980
2981            for (var i = 0; i < columns.length; i++) {
2982                column_height = 0;
2983
2984                for (var j = 0; j < columns[i].length; j++) {
2985                    image = imagesById[ columns[i][j] ];
2986                    column_height += image.height.container;
2987                }
2988
2989                column_height += (columns[i].length - 1) * opts.padding;
2990                column_heights.push( column_height );
2991            }
2992
2993            return column_heights;
2994        },
2995
2996        dealIntoColumns : function (images, columns, opts, isRefreshing) {
2997            switch ( opts.order ) {
2998                case 'random' :
2999                    columns = this.sortRandomly( images, columns, isRefreshing );
3000                    break;
3001
3002                case 'masonry' :
3003                    columns = this.sortIntoMasonry( images, columns );
3004                    break;
3005
3006                case 'columns' :
3007                    columns = this.sortIntoColumns( images, columns );
3008                    break;
3009
3010                case 'rows' :
3011                default :
3012                    columns = this.sortIntoRows( images, columns );
3013                    break;
3014            }
3015
3016            return columns;
3017        },
3018
3019        sortRandomly : function (images, columns, isRefreshing) {
3020            // randomize and sort into rows
3021            // don't re-randomize if we're refreshing
3022            if (!isRefreshing) {
3023                images = this.randomizeImages( images );
3024            }
3025
3026            columns = this.sortIntoMasonry( images, columns );
3027
3028            return columns;
3029        },
3030
3031        sortIntoMasonry : function (images, columns) {
3032            var num_columns = columns.length;
3033            var num_images = images.length;
3034            var column_heights = [];
3035            var which = 0;
3036            var i = 0;
3037
3038            for (i = 0; i < num_columns; i++) {
3039                column_heights[i] = 0;
3040            } 
3041
3042            // always place the next image into the shortest column
3043            for (i = 0; i < num_images; i++) {
3044                which = $.inArray(
3045                            Math.min.apply( Math, column_heights ),
3046                            column_heights
3047                        );
3048                columns[which].push( images[i].id );
3049                column_heights[which] = column_heights[which] + images[i].height.container;
3050            }
3051
3052            return columns;
3053        },
3054
3055        sortIntoRows : function (images, columns) {
3056            var num_columns = columns.length;
3057            var num_images = images.length;
3058            var which = 0;
3059            var i = 0;
3060
3061            for (i = 0; i < num_images; i++) {
3062                which = i % num_columns;
3063                columns[which].push(images[i].id);
3064            }
3065
3066            return columns;
3067        },
3068
3069        sortIntoColumns : function (images, columns) {
3070            // columns --> rows => columns
3071            var images = $.extend(true, [], images); // deep copy because we .shift()
3072            var num_images = images.length;
3073            var i = 0;
3074            var j = 0;
3075
3076            columns = this.sortIntoRows( images, columns );
3077
3078            // Sorting into rows tells us how many images are in each column.
3079            // The specific image in each slot is wrong but the slot-division is good.
3080            // So we replace the image in each slot with the next one from the ordered list.
3081            for (i = 0; i < columns.length; i++) {
3082                for (j = 0; j < columns[i].length; j++) {
3083                    columns[i][j] = images[0].id;
3084                    images.shift();
3085                }
3086            }
3087
3088            return columns;
3089        },
3090
3091        randomizeImages : function (images) {
3092            return images.sort(function (a, b) {
3093                return (0.5 - Math.random());
3094            });
3095        },
3096
3097        positionImagesInContainer : function (images, prevent_crop) {
3098            var image = null;
3099
3100            for (var i = 0; i < images.length; i++) {
3101                image = images[i];
3102
3103                image.adjustments = {};
3104
3105                if (!prevent_crop) {
3106                    // adjusted is still scaled to the column's width
3107                    if (image.height.adjusted > image.height.container) {
3108                        image.adjustments.top = Math.floor((image.height.adjusted - image.height.container) / 2);
3109                    } else {
3110                        image.width.adjusted = Math.floor((image.width.adjusted * image.height.container) / image.height.adjusted);
3111                        image.height.adjusted = image.height.container;
3112
3113                        image.adjustments.left = Math.floor((image.width.adjusted - image.width.container) / 2);
3114                    }
3115                }
3116
3117                image.adjustments = PhotoMosaic.Layouts.Common.normalizeAdjustments( image.adjustments );
3118
3119                images[i] = image;
3120            };
3121
3122            return images;
3123        },
3124
3125        positionImagesInMosaic : function (imagesById, columns, column_width, opts) {
3126            var col_height = 0;
3127            var image = null;
3128
3129            for (var i = 0; i < columns.length; i++) {
3130                col_height = 0;
3131
3132                for (var j = 0; j < columns[i].length; j++) {
3133                    image = imagesById[ columns[i][j] ];
3134
3135                    image.position = {
3136                        top : col_height,
3137                        left : (i * column_width) + (i * opts.padding)
3138                    };
3139
3140                    col_height = col_height + image.height.container + opts.padding;
3141                };
3142            };
3143        },
3144
3145        normalizeAdjustments : function (adjustments) {
3146            if (!adjustments.hasOwnProperty('top')) {
3147                adjustments.top = 0;
3148            }
3149
3150            if (!adjustments.hasOwnProperty('left')) {
3151                adjustments.left = 0;
3152            }
3153
3154            return adjustments;
3155        }
3156    };
3157}(window.JQPM));
3158(function ($) {
3159    'use strict';
3160
3161    PhotoMosaic.Layouts.columns = function (mosaic) {
3162        this.node = mosaic.obj;
3163        this.opts = mosaic.opts;
3164        this._options = mosaic._options;
3165        this._options.gallery = mosaic.opts.gallery.slice(); // we want to be able to refer to the original gallery order
3166        this.images = mosaic.opts.gallery;
3167        this.imagesById = PhotoMosaic.Utils.arrayToObj( this.images, 'id' );
3168        return this;
3169    };
3170
3171    PhotoMosaic.Layouts.columns.prototype = {
3172        getData : function (isRefreshing) {
3173            var images = this.images;
3174            var columns = null;
3175            var column_width = null;
3176            var mosaic_height = 0;
3177            var force_hidden = false;
3178
3179            this.opts.width = PhotoMosaic.Layouts.Common.getRelativeWidth( this._options, this.opts, this.node );
3180
3181            // lazyloading requires that there be nodes to attach to (so we need a width)
3182            // but we don't want to render the mosaic so we display:none it
3183            if ( this.errorChecks.width(this.opts.width) ) {
3184                this.opts.width = 300;
3185                force_hidden = true;
3186            }
3187
3188            // determine the number of columns
3189            this.columns = columns = PhotoMosaic.Layouts.Common.makeColumnBuckets( this.opts );
3190
3191            // determine the column width (all columns have the same width)
3192            this.column_width = column_width = PhotoMosaic.Layouts.Common.getColumnWidth( columns, this.opts );
3193
3194            // scale each image to the column width
3195            images = this.scaleToWidth( images, column_width );
3196
3197            // sort the images (based on opts.order) and assign them to columns
3198            columns = PhotoMosaic.Layouts.Common.dealIntoColumns( images, columns, this.opts, isRefreshing );
3199
3200            // determine the target height for the entire mosaic
3201            mosaic_height = this.getMosaicHeight( columns );
3202
3203            // adjust the images in each column to the new height (for a flat bottom edge)
3204            columns = this.adjustColumnsToHeight( columns, mosaic_height );
3205
3206            // do everything possible to make sure we show SOMETHING
3207            if ( this.errorChecks.aspectRatio(images) ) {
3208                columns = this.balanceColumnsToHeight( columns, mosaic_height );
3209            }
3210
3211            // bail if the user's settings creates a super-broken mosaic
3212            if ( this.errorChecks.height(images) ) {
3213                return false;
3214            }
3215
3216            // create crop position info
3217            images = PhotoMosaic.Layouts.Common.positionImagesInContainer( images, this.opts.prevent_crop );
3218
3219            // convert all this knowledge into position data
3220            // TODO : stop being a side-effect
3221            PhotoMosaic.Layouts.Common.positionImagesInMosaic( this.imagesById, columns, column_width, this.opts );
3222
3223            images = PhotoMosaic.Utils.pickImageSize( images, this.opts.sizes, this.opts.honor_device_pixel_ratio );
3224
3225            return {
3226                width : (column_width * columns.length) + (this.opts.padding * (columns.length - 1)),
3227                height : mosaic_height,
3228                images : images,
3229                force_hidden : force_hidden
3230            };
3231        },
3232
3233        scaleToWidth : function (images, width) {
3234            for (var i = 0; i < images.length; i++) {
3235                images[i].width.container = images[i].width.adjusted = width;
3236                images[i].height.container = images[i].height.adjusted = Math.floor((images[i].height.original * width) / images[i].width.original);
3237            }
3238
3239            return images;
3240        },
3241
3242        getMosaicHeight : function (columns) {
3243            var auto_height = PhotoMosaic.Layouts.Common.getRelativeHeight( this._options, this.opts, this.node );
3244
3245            if (auto_height) {
3246                return auto_height;
3247            }
3248
3249            var column_heights = PhotoMosaic.Layouts.Common.getColumnHeights( this.imagesById, columns, this.opts );
3250
3251            if (this.opts.prevent_crop) {
3252                return Math.max.apply( Math, column_heights );
3253            }
3254
3255            /*
3256                function median (values) {
3257                    var half = Math.floor( values.length / 2 );
3258                    values.sort( function (a,b) {
3259                        return a - b;
3260                    } );
3261                    if (values.length % 2) {
3262                        return values[half];
3263                    } else {
3264                        return Math.ceil( (values[half-1] + values[half]) / 2 );
3265                    }
3266                }
3267            */
3268
3269            function mean (values) {
3270                var sum = 0;
3271                var i = 0;
3272
3273                for (i = 0; i < values.length; i++) {
3274                    sum += values[i];
3275                };
3276
3277                return Math.ceil( sum / values.length );
3278            }
3279
3280            return mean( column_heights );
3281        },
3282
3283        adjustColumnsToHeight : function (columns, target_height) {
3284            if (this.opts.prevent_crop) {
3285                return columns;
3286            }
3287            for (var i = 0; i < columns.length; i++) {
3288                columns[i] = this.adjustColumnToHeight( columns[i], target_height );
3289            }
3290            return columns;
3291        },
3292
3293        adjustColumnToHeight: function (ids, target_height) {
3294            var column_height = 0;
3295            var i = 0;
3296
3297            // get the height of each column
3298            for (i = 0; i < ids.length; i++) {
3299                column_height += this.imagesById[ ids[i] ].height.container;
3300            }
3301
3302            column_height += (ids.length - 1) * this.opts.padding;
3303
3304            // how much do we need to grow or shrink the column
3305            var diff = target_height - column_height // (plus = grow, minus = shrink)
3306            var direction = (diff > 0) ? 'grow' : 'shrink';
3307            diff = Math.abs(diff);
3308
3309            // spread the diff between the image
3310            i = 0;
3311            while (diff > 0) {
3312                i = (i >= ids.length) ? 0 : i;
3313                if (direction === 'grow') {
3314                    this.imagesById[ ids[i] ].height.container++;
3315                } else {
3316                    this.imagesById[ ids[i] ].height.container--;
3317                }
3318                i++;
3319                diff--;
3320            }
3321
3322            return ids;
3323        },
3324
3325        balanceColumnsToHeight : function (columns, target_height) {
3326            if (this.opts.prevent_crop) {
3327                return columns;
3328            }
3329            for (var i = 0; i < columns.length; i++) {
3330                columns[i] = this.balanceColumnToHeight( columns[i], target_height );
3331            }
3332            return columns;
3333        },
3334
3335        balanceColumnToHeight : function (ids, target_height) {
3336            var column_height = target_height - ((ids.length - 1) * this.opts.padding);
3337            var divy = Math.floor(column_height / ids.length);
3338            var remainder = column_height % ids.length;
3339
3340            for (var i = 0; i < ids.length; i++) {
3341                this.imagesById[ ids[i] ].height.container = divy;
3342            }
3343
3344            for (var i = 0; i < remainder; i++) {
3345                this.imagesById[ ids[i] ].height.container++;
3346            }
3347
3348            return ids;
3349        },
3350
3351        refresh : function () {
3352            return this.getData( true );
3353        },
3354
3355        update : function (props) {
3356            this.opts = $.extend({}, this.opts, props);
3357
3358            // take care of any layout-specific change-logic
3359            if (props.hasOwnProperty('order')) {
3360                this.images = this._options.gallery.slice();
3361
3362                if (props.order == 'random') {
3363                    this.images = PhotoMosaic.Layouts.Common.randomizeImages( this.images );
3364                }
3365            }
3366
3367            if (props.hasOwnProperty('width')) {
3368                this._options.width = props.width;
3369
3370                if (props.width === 'auto' || props.width == 0) {
3371                    this.opts.width = this.node.width();
3372                }
3373            }
3374        },
3375
3376        errorChecks : {
3377            aspectRatio: function (images) {
3378                for (var i = 0; i < images.length; i++) {
3379                    if (images[i].height.container <= 10) { // 10 is pretty arbitrary
3380                        PhotoMosaic.Utils.log.error("Your gallery's he
3380ight doesn't allow your images to proportioned based on their aspect ratios.");
3381                        return true;
3382                    }
3383                }
3384                return false;
3385            },
3386            height: function (images) {
3387                for (var i = 0; i < images.length; i++) {
3388                    if (images[i].height.container <= 0) {
3389                        PhotoMosaic.Utils.log.error("Your gallery has been hidden because its height is too small for your current settings and won't render correctly.");
3390                        return true;
3391                    }
3392                }
3393                return false;
3394            },
3395            width: function (width) {
3396                if (width == 0) {
3397                    PhotoMosaic.Utils.log.error("Your gallery's target container doesn't have a width (width = 0). Make sure it isn't hidden.");
3398                    return true;
3399                }
3400                return false;
3401            }
3402        }
3403    };
3404}(window.JQPM));
3405(function ($) {
3406    PhotoMosaic.Layouts.rows = function (mosaic) {
3407        this.node = mosaic.obj;
3408        this.opts = mosaic.opts;
3409        this._options = mosaic._options;
3410        this._options.gallery = mosaic.opts.gallery.slice(); // we want to be able to refer to the original gallery order
3411        this.images = mosaic.opts.gallery;
3412        this.imagesById = PhotoMosaic.Utils.arrayToObj( this.images, 'id' );
3413        this.ordered_images = $.map( this.images, function (item, i) {
3414            return item.id;
3415        } );
3416        return this;
3417    };
3418    PhotoMosaic.Layouts.rows.prototype = {
3419        getData : function (isRefreshing) {
3420            var images = this.images;
3421            var rows = null;
3422
3423            this.opts.width = PhotoMosaic.Layouts.Common.getRelativeWidth( this._options, this.opts, this.node );
3424            this.opts.height = PhotoMosaic.Layouts.Common.getRelativeDimenion( this._options, this.opts, this.node, 'height', this.node.height());
3425
3426            // look for conflicting settings
3427            this.opts = this.errorChecks.initial( this._options, this.opts );
3428
3429            // scale images to the tallest height
3430            images = this.scaleImagesToHeight( images, this.opts.max_row_height );
3431
3432            // group into rows assuming auto-rows
3433            rows = this.groupIntoRows( images, this.opts.width );
3434
3435            // adjust the groups to get the desired number of rows
3436            if (
3437                (this.opts.rows === 'auto' || this.opts.rows === 0)
3438                && (this._options.height !== 'auto' && this._options.height !== 0)
3439            ) {
3440                rows = this.adjustNumberOfRowsByHeight( images, rows, this.opts.height );
3441            } else {
3442                rows = this.adjustNumberOfRowsByRows( images, rows );
3443            }
3444
3445            // scale the rows to the correct width
3446            rows = this.adjustRowsToWidth( rows, this.opts.width );
3447
3448            if (!this.opts.prevent_crop) {
3449                rows = this.plumbRows( rows, this.opts.width );
3450            }
3451
3452            if (this.opts.allow_orphans && this.hasOrphans(rows)) {
3453                // the orphan row tends to be much taller than the others
3454                rows = this.normalizeOrphanRowHeight( rows, this.opts.width );
3455            }
3456
3457            if (this._options.height !== 'auto' && this._options.height !== 0) {
3458                rows = this.setMosaicHeight( images, rows, this.opts.height );
3459            }
3460
3461            // create crop position info
3462            images = PhotoMosaic.Layouts.Common.positionImagesInContainer( images, this.opts.prevent_crop );
3463
3464            // convert all this knowledge into position data
3465            this.positionImagesInMosaic( rows, this.opts.padding );
3466
3467            images = PhotoMosaic.Utils.pickImageSize( images, this.opts.sizes, this.opts.honor_device_pixel_ratio );
3468
3469            return {
3470                width : this.opts.width,
3471                height : this.getMosaicHeight(rows),
3472                images : images
3473            };
3474        },
3475
3476        groupIntoRows : function (images, container_width) {
3477            var rows = [];
3478            var row = [];
3479            var row_width = 0;
3480
3481            for (var i = 0; i < images.length; i++) {
3482                if (row_width > container_width) {
3483                    rows.push( row );
3484                    row = [ images[i] ];
3485                    row_width = images[i].width.container;
3486                } else {
3487                    row.push( images[i] );
3488                    row_width = row_width + images[i].width.container;
3489                }
3490            };
3491
3492            rows.push( row );
3493
3494            return rows;
3495        },
3496
3497        scaleImagesToHeight : function (images, height) {
3498            for (var i = 0; i < images.length; i++) {
3499                images[i] = this.scaleImageToHeight( images[i], height );
3500            };
3501
3502            return images;
3503        },
3504
3505        scaleImageToHeight : function (image, height) {
3506            image.height.container = image.height.adjusted = height;
3507            image.width.container = image.width.adjusted = Math.floor((image.width.original * height) / image.height.original);
3508
3509            return image;
3510        },
3511
3512        adjustNumberOfRowsByRows : function (images, rows, force) {
3513            var desired = null;
3514
3515            if (force) {
3516                desired = force;
3517            } else if (
3518                (this.opts.rows === 'auto' || this.opts.rows === 0)
3519                && !this.opts.allow_orphans
3520                && this.hasOrphans
3521            ) {
3522                desired = rows.length - 1;
3523            } else if (this.opts.rows !== 'auto' && this.opts.rows !== 0) {
3524                desired = this.opts.rows;
3525            } else {
3526                // we have what we want
3527                return rows;
3528            }
3529
3530            // binary search
3531            var lower = (rows.length < desired) ? this.opts.max_row_height : 0;
3532            var upper = (rows.length > desired) ? this.opts.max_row_height : 1000;
3533            var middle = 0;
3534            var i = 0;
3535
3536            while((upper - lower) > 1) {
3537                middle = Math.floor((upper - lower) / 2) + lower;
3538
3539                images = this.scaleImagesToHeight( images, middle );
3540                rows = this.groupIntoRows( images, this.opts.width );
3541
3542                if (rows.length > desired) {
3543                    upper = middle;
3544                } else {
3545                    lower = middle;
3546                }
3547            }
3548
3549            if (rows.length > desired) {
3550                images = this.scaleImagesToHeight( images, lower );
3551                rows = this.groupIntoRows( images, this.opts.width );
3552            }
3553
3554            return rows;
3555        },
3556
3557        adjustNumberOfRowsByHeight : function (images, rows, desired) {
3558            // binary search
3559            var current = this.getMosaicHeight(rows);
3560            var lower = (current < desired) ? this.opts.max_row_height : 0;
3561            var upper = (current > desired) ? this.opts.max_row_height : 1000;
3562            var middle = 0;
3563            var i = 0;
3564
3565            while((upper - lower) > 1) {
3566                middle = Math.floor((upper - lower) / 2) + lower;
3567
3568                images = this.scaleImagesToHeight( images, middle );
3569                rows = this.groupIntoRows( images, this.opts.width );
3570
3571                current = this.getMosaicHeight(rows);
3572
3573                if (current > desired) {
3574                    upper = middle;
3575                } else {
3576                    lower = middle;
3577                }
3578            }
3579
3580            if (current > desired) {
3581                images = this.scaleImagesToHeight( images, upper );
3582                rows = this.groupIntoRows( images, this.opts.width );
3583            }
3584
3585            return rows;
3586        },
3587
3588        adjustRowsToWidth : function (rows, container_width) {
3589            for (var i = 0; i < rows.length; i++) {
3590                rows[i] = this.adjustRowToWidth( rows[i], container_width );
3591            }
3592
3593            return rows;
3594        },
3595
3596        adjustRowToWidth : function (row, container_width) {
3597            var row_width = this.getRowWidth(row);
3598            var row_height = row[0].height.container;
3599            var i = 0;
3600
3601            if (row_width > container_width) {
3602                row = this.adjustRowToHeight( row, (row_height - 1) );
3603                return this.adjustRowToWidth( row, container_width );
3604            } else {
3605                if (!this.opts.prevent_crop) {
3606                    return this.adjustRowToHeight( row, (row_height + 1) );
3607                }
3608            }
3609
3610            return row;
3611        },
3612
3613        adjustRowToHeight : function (row, height) {
3614            for (var i = 0; i < row.length; i++) {
3615                row[i] = this.scaleImageToHeight( row[i], height );
3616            }
3617
3618            return row;
3619        },
3620
3621        plumbRows : function (rows, container_width) {
3622            for (var i = 0; i < rows.length; i++) {
3623                rows[i] = this.plumbRow( rows[i], container_width );
3624            }
3625            return rows;
3626        },
3627
3628        plumbRow : function (row, container_width) {
3629            var row_width = this.getRowWidth(row);
3630            var diff = row_width - container_width;
3631            var i = 0;
3632
3633            if (diff > 0) {
3634                // trim
3635                while(diff > 0) {
3636                    i = (i >= row.length) ? 0 : i;
3637                    row[i].width.container = row[i].width.container - 1;
3638                    i++;
3639                    diff--;
3640                }
3641            } else {
3642                if (Math.abs(diff) < (container_width / 10)) {
3643                    // scale up
3644                    while(diff < 0) {
3645                        i = (i >= row.length) ? 0 : i;
3646                        row[i].width.container = row[i].width.container + 1;
3647                        i++;
3648                        diff++;
3649                    }
3650                }
3651            }
3652
3653            return row;
3654        },
3655
3656        normalizeOrphanRowHeight : function (rows, container_width) {
3657            var last_row = rows[rows.length - 1];
3658            var row_width = this.getRowWidth(last_row);
3659            var average_height = 0;
3660
3661            if (row_width >= container_width) {
3662                return rows;
3663            } else {
3664                average_height = this.getAverageRowHeight(rows);
3665                last_row = this.adjustRowToHeight(last_row, average_height);
3666            }
3667
3668            rows[rows.length - 1] = last_row;
3669            return rows
3670        },
3671
3672        hasOrphans : function (rows) {
3673            var row_width = this.getRowWidth( rows[rows.length - 1] );
3674            return (row_width < this.opts.width);
3675        },
3676
3677        getRowWidth : function (row) {
3678            var row_width = 0;
3679
3680            for (var i = 0; i < row.length; i++) {
3681                row_width += row[i].width.container;
3682            }
3683
3684            row_width += (row.length - 1) * this.opts.padding;
3685
3686            return row_width;
3687        },
3688
3689        getAverageRowHeight : function (rows) {
3690            var total_height = 0;
3691
3692            for (var i = 0; i < rows.length; i++) {
3693                total_height += rows[i][0].height.container;
3694            }
3695
3696            return Math.floor(total_height / rows.length);
3697        },
3698
3699        setMosaicHeight : function (images, rows, desired) {
3700            var current = this.getMosaicHeight(rows);
3701            var diff = current - desired;
3702            var row_heights = [];
3703            var padding = this.opts.padding * (rows.length - 1);
3704
3705            // get each row's height as a percentage of the container_height
3706            for (var i = 0; i < rows.length; i++) {
3707                row_heights.push(
3708                    Math.round(
3709                        (((desired - padding) * rows[i][0].height.container) / (current - padding))
3710                    )
3711                );
3712            }
3713
3714            for (var i = 0; i < rows.length; i++) {
3715                for (var j = 0; j < rows[i].length; j++) {
3716                    rows[i][j].height.container = row_heights[i];
3717                }
3718            }
3719
3720            return rows;
3721        },
3722
3723        getMosaicHeight : function (rows) {
3724            var total_height = 0;
3725
3726            for (var i = 0; i < rows.length; i++) {
3727                total_height += rows[i][0].height.container;
3728            }
3729
3730            return Math.ceil(total_height + ((rows.length - 1) * this.opts.padding));
3731        },
3732
3733        positionImagesInMosaic : function (rows, padding) {
3734            var from_left = 0;
3735            var from_top = 0;
3736            var image = null;
3737
3738            for (var i = 0; i < rows.length; i++) {
3739                from_left = 0;
3740
3741                for (var j = 0; j < rows[i].length; j++) {
3742                    image = rows[i][j];
3743
3744                    image.position = {
3745                        top : from_top,
3746                        left : from_left
3747                    };
3748
3749                    from_left = from_left + image.width.container + padding;
3750                };
3751
3752                from_top = from_top + rows[i][0].height.container + padding;
3753            };
3754        },
3755
3756        refresh : function () {
3757            return this.getData( true );
3758        },
3759
3760        update : function (props) {
3761            this.opts = $.extend({}, this.opts, props);
3762
3763            // take care of any layout-specific change-logic
3764            if (props.hasOwnProperty('order')) {
3765                this.images = this._options.gallery.slice();
3766
3767                if (props.order == 'random') {
3768                    this.images = PhotoMosaic.Layouts.Common.randomizeImages( this.images );
3769                }
3770            }
3771
3772            if (props.hasOwnProperty('width')) {
3773                this._options.width = props.width;
3774
3775                if (props.width === 'auto' || props.width == 0) {
3776                    this.opts.width = this.node.width();
3777                }
3778            }
3779        },
3780
3781        errorChecks : {
3782            initial : function (_options, opts) {
3783                if (this.heightRowMismatch(_options, opts)) {
3784                    PhotoMosaic.Utils.log.info("Rows must be set to 'auto' to set a fixed Height");
3785                    opts.rows = 'auto';
3786                }
3787                if (this.heightOrphanMismatch(_options, opts)) {
3788                    PhotoMosaic.Utils.log.info("Allow Orphans must be 'true' to set a fixed Height");
3789                    opts.allow_orphans = true;
3790                }
3791                if (this.heightCropMismatch(_options, opts)) {
3792                    PhotoMosaic.Utils.log.info("Prevent Cropping must be 'false' to set a fixed Height");
3793                    opts.prevent_crop = false;
3794                }
3795                return opts;
3796            },
3797            heightRowMismatch : function (_options, opts) {
3798                return (
3799                    (_options.height !== 'auto' && _options.height !== 0)
3800                    && (opts.rows !== 'auto' && opts.rows !== 0)
3801                );
3802            },
3803            heightOrphanMismatch : function (_options, opts) {
3804                return (
3805                    (_options.height !== 'auto' && _options.height !== 0)
3806                    && !opts.allow_orphans
3807                );
3808            },
3809            heightCropMismatch : function (_options, opts) {
3810                return (
3811                    (_options.height !== 'auto' && _options.height !== 0)
3812                    && opts.prevent_crop
3813                );
3814            },
3815        }
3816    };
3817}(window.JQPM));
3818(function ($) {
3819    PhotoMosaic.Layouts.grid = function (mosaic) {
3820        this.node = mosaic.obj;
3821        this.opts = mosaic.opts;
3822        this._options = mosaic._options;
3823        this._options.gallery = mosaic.opts.gallery.slice(); // we want to be able to refer to the original gallery order
3824        this.images = mosaic.opts.gallery;
3825        this.imagesById = PhotoMosaic.Utils.arrayToObj( this.images, 'id' );
3826        return this;
3827    };
3828
3829    PhotoMosaic.Layouts.grid.prototype = {
3830        getData : function (isRefreshing) {
3831            var images = this.images;
3832            var columns = null;
3833            var column_width = null;
3834            var mosaic_height = 0;
3835
3836            this.opts.width = PhotoMosaic.Layouts.Common.getRelativeWidth( this._options, this.opts, this.node );
3837
3838            // look for conflicting settings
3839            this.opts = this.errorChecks.initial( this.opts );
3840
3841            // determine the number of columns
3842            this.columns = columns = PhotoMosaic.Layouts.Common.makeColumnBuckets( this.opts );
3843
3844            // determine the column width (all columns have the same width)
3845            this.column_width = column_width = PhotoMosaic.Layouts.Common.getColumnWidth( columns, this.opts );
3846
3847            // set image container
3848            images = this.setContainers(images, column_width);
3849
3850            // scale the image to the container (based on shape)
3851            images = this.scaleToContainer(images);
3852
3853            // sort the images (based on opts.order) and assign them to columns
3854            columns = PhotoMosaic.Layouts.Common.dealIntoColumns( images, columns, this.opts, isRefreshing );
3855
3856            // adjust for height
3857            columns = this.scaleColumnsToHeight(columns, this.opts.height);
3858
3859            // determine the target height for the entire mosaic
3860            mosaic_height = this.getMosaicHeight( this.imagesById, columns, this.opts );
3861
3862            // align
3863            images = this.alignInContainer( images );
3864
3865            // convert all this knowledge into position data
3866            // TODO : stop being a side-effect
3867            PhotoMosaic.Layouts.Common.positionImagesInMosaic( this.imagesById, columns, column_width, this.opts );
3868
3869            images = PhotoMosaic.Utils.pickImageSize( images, this.opts.sizes, this.opts.honor_device_pixel_ratio );
3870
3871            return {
3872                width : (column_width * columns.length) + (this.opts.padding * (columns.length - 1)),
3873                height : mosaic_height,
3874                images : images
3875            };
3876        },
3877
3878        setContainers : function (images, width) {
3879            var image = null;
3880            var aspect_ratio = null;
3881
3882            for (var i = 0; i < images.length; i++) {
3883                image = images[i];
3884                aspect_ratio = this.getAspectRatio( this.opts.shape, image );
3885
3886                image.width.container = width;
3887                image.height.container = Math.floor( (aspect_ratio[1] * width) / aspect_ratio[0] );
3888
3889                images[i] = image;
3890            }
3891
3892            return images;
3893        },
3894
3895        scaleToContainer : function (images) {
3896            var image = null;
3897            var scaled_height = null;
3898
3899            for (var i = 0; i < images.length; i++) {
3900                image = (typeof images[i] == 'string') ? this.imagesById[ images[i] ] : images[i];
3901                scaled_height = Math.floor( (image.height.original * image.width.container) / image.width.original );
3902
3903                if ( this.shouldScaleWidth( image ) ) {
3904                    image.width.adjusted = image.width.container;
3905                    image.height.adjusted = Math.floor( (image.height.original * image.width.adjusted) / image.width.original );
3906                } else {
3907                    image.height.adjusted = image.height.container;
3908                    image.width.adjusted = Math.floor( (image.width.original * image.height.adjusted) / image.height.original );
3909                }
3910            }
3911
3912            return images;
3913        },
3914
3915        scaleColumnsToHeight : function (columns, height) {
3916            if (height == 'auto' || height == 0) {
3917                return columns;
3918            } else {
3919                if ( (typeof(height) == 'string') && (height.indexOf('%') > -1) ) {
3920                    height = (this.node.height() * (parseInt(height, 10) / 100));
3921                }
3922
3923                for (var i = 0; i < columns.length; i++) {
3924                    columns[i] = this.scaleColumnToHeight(columns[i], height);
3925                }
3926
3927                return columns;
3928            }
3929        },
3930
3931        scaleColumnToHeight : function (ids, target_height) {
3932            var column_height = 0;
3933            var i = 0;
3934
3935            // get the height of each column
3936            for (i = 0; i < ids.length; i++) {
3937                column_height += this.imagesById[ ids[i] ].height.container;
3938            }
3939
3940            column_height += (ids.length - 1) * this.opts.padding;
3941
3942            // how much do we need to grow or shrink the column
3943            var diff = target_height - column_height // (plus = grow, minus = shrink)
3944            var direction = (diff > 0) ? 'grow' : 'shrink';
3945            diff = Math.abs(diff);
3946
3947            // spread the diff between the image
3948            i = 0;
3949            while (diff > 0) {
3950                i = (i >= ids.length) ? 0 : i;
3951                if (direction === 'grow') {
3952                    this.imagesById[ ids[i] ].height.container++;
3953                } else {
3954                    this.imagesById[ ids[i] ].height.container--;
3955                }
3956                i++;
3957                diff--;
3958            }
3959
3960            ids = this.scaleToContainer(ids);
3961
3962            return ids;
3963        },
3964
3965        getMosaicHeight : function (imagesById, columns, opts) {
3966            var column_heights = PhotoMosaic.Layouts.Common.getColumnHeights( imagesById, columns, opts );
3967            return Math.max.apply( Math, column_heights );
3968        },
3969
3970        alignInContainer : function (images) {
3971            var image = null;
3972
3973            for (var i = 0; i < images.length; i++) {
3974                image = images[i];
3975                image.adjustments = {};
3976
3977                if ( this.shouldScaleWidth( image ) ) {
3978                    image.adjustments.top = Math.floor( (image.height.adjusted - image.height.container) / 2 );
3979                } else {
3980                    image.adjustments.left = Math.floor( (image.width.adjusted - image.width.container) / 2 );
3981                }
3982
3983                if (this.opts.sizing == 'contain') {
3984                    switch (this.opts.align) {
3985                        case 'top':
3986                            image.adjustments.top = 0;
3987                            break;
3988
3989                        case 'middle':
3990                            image.adjustments.top = Math.floor( (image.height.container - image.height.adjusted) / 2 ) * -1;
3991                            break;
3992
3993                        case 'bottom':
3994                            image.adjustments.top = (image.height.container - image.height.adjusted) * -1;
3995                            break;
3996                    }
3997                }
3998
3999                image.adjustments = PhotoMosaic.Layouts.Common.normalizeAdjustments( image.adjustments );
4000
4001                images[i] = image;
4002            };
4003
4004            return images;
4005        },
4006
4007        getAspectRatio : function (shape, image) {
4008            var aspect_ratio = null;
4009
4010            if ( shape == 'natural' || this.errorChecks.shape(shape) ) {
4011                aspect_ratio = [1,1];
4012            } else {
4013                aspect_ratio = shape.split(':');
4014            }
4015
4016            return aspect_ratio;
4017        },
4018
4019        shouldScaleWidth : function (image) {
4020            // cover = shortest image side == longest container side
4021            // contain = longest image side == shortest container side
4022            var scaled_height = Math.floor( (image.height.original * image.width.container) / image.width.original );
4023            return (
4024                ((this.opts.sizing == 'cover') && (scaled_height > image.height.container)) ||
4025                ((this.opts.sizing == 'contain') && (scaled_height < image.height.container))
4026            );
4027        },
4028
4029        refresh : function () {
4030            return this.getData( true );
4031        },
4032
4033        update : function (props) {
4034            this.opts = $.extend({}, this.opts, props);
4035
4036            // take care of any layout-specific change-logic
4037            if (props.hasOwnProperty('order')) {
4038                this.images = this._options.gallery.slice();
4039
4040                if (props.order == 'random') {
4041                    this.images = PhotoMosaic.Layouts.Common.randomizeImages( this.images );
4042                }
4043            }
4044
4045            if (props.hasOwnProperty('width')) {
4046                this._options.width = props.width;
4047
4048                if (props.width === 'auto' || props.width == 0) {
4049                    this.opts.width = this.node.width();
4050                }
4051            }
4052        },
4053
4054        errorChecks : {
4055            initial : function (opts) {
4056                if (this.coverCropMismatch(opts)) {
4057                    PhotoMosaic.Utils.log.error("Setting 'Sizing' to 'Cover' requires that images be cropped. Setting 'Prevent Cropping' to 'false'.");
4058                    opts.prevent_crop = false;
4059                }
4060                if (this.heightShapeMismatch(opts)) {
4061                    PhotoMosaic.Utils.log.info("A fixed 'Height' requires changing the grid item's 'Shape'. The 'Shape' setting ("+ opts.shape +") will not be honored.");
4062                    opts.allow_orphans = true;
4063                }
4064                return opts;
4065            },
4066            coverCropMismatch : function (opts) {
4067                return (opts.sizing == 'cover' && opts.prevent_crop);
4068            },
4069            heightShapeMismatch : function (opts) {
4070                return (opts.height !== 'auto' && opts.height !== 0);
4071            },
4072            shape : function (shape) {
4073                if ( (shape != 'natural') && (shape.indexOf(':') <= 0) ) {
4074                    PhotoMosaic.Utils.log.error("'Shape' must be 'natural' or an aspect ratio in the form 'x:y'. ")
4075                    return true;
4076                }
4077                return false;
4078            }
4079        }
4080    };
4081}(window.JQPM));
4082(function ($) {
4083    'use strict';
4084
4085    function prefixId (id) {
4086        return 'photoMosaic_' + id;
4087    }
4088
4089    function vendorPrefix(str) {
4090        var prop = PhotoMosaic.Plugins.Modernizr.prefixed(str);
4091        return prop.replace(/([A-Z])/g, function(str,m1){
4092                return '-' + m1.toLowerCase();
4093            }).replace(/^ms-/,'-ms-');
4094    }
4095
4096	PhotoMosaic.Layouts.React = {
4097        //mosaic : React.createClass({
4098		mosaic : createReactClass({
4099            componentDidMount : function () {
4100                $(ReactDOM.findDOMNode(this)).addClass('photomosaic-loading');
4101            },
4102            render : function () {
4103                var id = prefixId(this.props.id);
4104                var class_name = 'photoMosaic ' + this.props.class_name;
4105                var style = {
4106                    width : this.props.width,
4107                    height : this.props.height
4108                };
4109                var images = this.props.images.map(function (image) {
4110                    return (
4111                        React.createElement(PhotoMosaic.Layouts.React.item, image)
4112                    );
4113                });
4114
4115                if (this.props.center) {
4116                    style['marginRight'] = 'auto';
4117                    style['marginLeft'] = 'auto';
4118                }
4119
4120                if (this.props.force_hidden) {
4121                    style['display'] = 'none';
4122                }
4123
4124                return (
4125                    React.createElement(
4126                        'div',
4127                        {
4128                            id : id,
4129                            className : class_name,
4130                            style : style,
4131                            children : images
4132                        }
4133                    )
4134                );
4135            }
4136        }),
4137
4138        item : createReactClass({
4139            componentDidMount : function () {
4140                $(ReactDOM.findDOMNode(this)).addClass('photomosaic-loading');
4141            },
4142            render : function () {
4143                var data = this.props;
4144                var node_type = (data.link) ? 'a' : 'span';
4145                var params = {
4146                    className : 'photomosaic-item',
4147                    key : data.id,
4148                    style : {
4149                        width : data.width.container,
4150                        height : data.height.container
4151                    },
4152                    children : [
4153                        React.createElement(
4154                            PhotoMosaic.Layouts.React.spinner,
4155                            $.extend({}, data, {key : data.id + '_spinner'})
4156                        ),
4157                        React.createElement(
4158                            PhotoMosaic.Layouts.React.animation_wrapper,
4159                            data
4160                        )
4161                    ]
4162                };
4163
4164                params.style[vendorPrefix('transform')] = 'translate(' + data.position.left + 'px, ' + data.position.top + 'px)';
4165
4166                if (data.link) {
4167                    if (data.external) { params.target = '_blank'; }
4168                    if (data.modal) { params.rel = data.modal; }
4169                    if (data.caption) { params.title = data.caption; }
4170                    params.href = data.path;
4171                }
4172
4173                return (
4174                    React.createElement(node_type, params)
4175                );
4176            }
4177        }),
4178
4179        animation_wrapper : createReactClass({
4180            render : function () {
4181                var data = this.props;
4182                var params = {
4183                    className : 'photomosaic-animation-wrap',
4184                    children : [
4185                        React.createElement(PhotoMosaic.Layouts.React.image, data)
4186                    ]
4187                };
4188
4189                return (
4190                    React.createElement('div', params)
4191                );
4192            }
4193        }),
4194
4195        image : createReactClass({
4196            componentDidUpdate : function (prev_props, prev_state) {
4197                var $image = $(ReactDOM.findDOMNode(this));
4198                var next_src = $image.attr('data-src');
4199
4200                if (prev_props.src && (prev_props.src != next_src)) {
4201                    $image.attr('src', next_src);
4202                }
4203            },
4204            render : function () {
4205                var data = this.props;
4206                var style = {
4207                    width : data.width.adjusted,
4208                    height : data.height.adjusted
4209                };
4210
4211                style[vendorPrefix('transform')] = 'translate(' + (data.adjustments.left * -1) + 'px, ' + (data.adjustments.top * -1) + 'px)';
4212
4213                return (
4214                    React.createElement(
4215                        'IMG', // uppercase because of a MooTools v1.4.5 conflict
4216                        {
4217                            id : data.id,
4218                            'data-src' : data.src,
4219                            title : data.caption,
4220                            alt : data.alt,
4221                            style : style
4222                        }
4223                    )
4224                );
4225            }
4226        }),
4227
4228        spinner : createReactClass({
4229            render : function () {
4230                var data = this.props;
4231                var style = {
4232                    width : data.width.adjusted,
4233                    height : data.height.adjusted,
4234                };
4235
4236                style[vendorPrefix('transform')] = 'translate(' + (data.adjustments.left * -1) + 'px, ' + (data.adjustments.top * -1) + 'px)';
4237
4238                return (
4239                    React.createElement(
4240                        'div',
4241                        {
4242                            className : 'photomosaic-spinner-wrap',
4243                            children : [
4244                                React.createElement(
4245                                    'div',
4246                                    {
4247                                        className : 'photomosaic-spinner',
4248                                        style : style
4249                                    }
4250                                )
4251                            ]
4252                        }
4253                    )
4254                );
4255            }
4256        }),
4257
4258        loading : createReactClass({
4259            render : function () {
4260                return (
4261                    React.createElement(
4262                        'div',
4263                        {
4264                            id : prefixId(this.props.id),
4265                            className : 'photoMosaic',
4266                            children : React.createElement(
4267                                'div',
4268                                {
4269                                    className : 'photoMosaicLoading',
4270                                    children : 'loading gallery...'
4271                                }
4272                            )
4273                        }
4274                    )
4275                );
4276            }
4277        })
4278    };
4279
4280}(window.JQPM));
4281(function ($, window) {
4282    'use strict';
4283
4284    var pluginName = 'photoMosaic';
4285    var self;
4286
4287    var photoMosaic = function (el, options, i) {
4288        self = this;
4289
4290        this.$ = $;
4291        this.el = el;
4292        this.obj = $(el);
4293        this._options = options;
4294
4295        this._id = options.id || PhotoMosaic.Utils.makeID(true);
4296
4297        this._transition_end_event_name = (function () {
4298            var event_names = {
4299                'WebkitTransition': 'webkitTransitionEnd',
4300                'MozTransition': 'transitionend',
4301                'OTransition': 'oTransitionEnd',
4302                'msTransition': 'MSTransitionEnd',
4303                'transition': 'transitionend'
4304            };
4305            return event_names[ PhotoMosaic.Plugins.Modernizr.prefixed( 'transition' ) ];
4306        })();
4307
4308        // for onReady Callback / modal_ready_callback
4309        $.data(this.el, pluginName, this);
4310
4311        // for debugging
4312        window.PhotoMosaic.Mosaics.push({
4313            'el' : this.el,
4314            '$el' : this.obj,
4315            'opts' : this._options,
4316            'instance' : this
4317        });
4318
4319        this.init();
4320    };
4321
4322    photoMosaic.prototype = {
4323        _defaults: {
4324            input : 'json', // json, html, xml
4325            gallery : 'PMalbum', // json object, xml file path
4326            padding : 2,
4327            width : 'auto', // auto (str) or (int)
4328            height : 'auto', // auto (str) or (int)
4329            links : true,
4330            external_links: false,
4331            center : true,
4332            prevent_crop : false,
4333            show_loading : false,
4334            loading_transition : 'fade', // none, fade, scale-up|down, slide-top|right|bottom|left, custom
4335            resize_transition : true,
4336            resize_transition_settings : {
4337                time: 0,
4338                duration: 0.3,
4339                effect: 'easeOut'
4340            },
4341            modal_name : null,
4342            modal_group : true,
4343            modal_hash : null,
4344            modal_ready_callback : null,
4345            lazyload : 0, // int || false
4346            lightbox_rendition : 'full',
4347            honor_device_pixel_ratio : true,
4348
4349            layout : 'columns', // rows, columns, grid
4350
4351            // columns opts
4352            columns : 'auto', // auto (str) or (int)
4353            order : 'rows', // rows, columns, masonry, random
4354            min_column_width : 300,
4355
4356            // rows opts
4357            rows : 'auto', // auto (str) or (int)
4358            allow_orphans : false,
4359            max_row_height : 325,
4360
4361            // grid opts
4362            shape : '16:9', // aspect-ratio (16:9)
4363            sizing : 'contain', // cover, contain
4364            align : 'middle', // top, middle, bottom
4365            orphans : 'left', // left, center, right
4366
4367            log_gallery_data : false
4368            // random : false (deprecated: v2.2)
4369            // force_order : false (deprecated: v2.2)
4370            // auto_columns : false (deprecated: v2.2)
4371            // responsive_transition --> resize_transition (deprecated: v2.8)
4372            // responsive_transition_settings --> resize_transition_settings (deprecated: v2.8)
4373        },
4374
4375        init: function () {
4376            var self = this;
4377
4378            this.opts = $.extend({}, this._defaults, this._options);
4379            this.opts = this.adjustDeprecatedOptions(this.opts);
4380            this._options = $.extend(true, {}, this.opts); // jQuery deep copy
4381
4382            this.preload = 'PM_preloadify' + this._id;
4383
4384            // Error Checks
4385            if ( PhotoMosaic.ErrorChecks.initial(this.opts) ) {
4386                return;
4387            }
4388
4389            if ( PhotoMosaic.ErrorChecks.nonModernBrowser() ) {
4390                var fallback_node = this.el.childNodes[0];
4391                var fallback = fallback_node ? fallback_node.textContent : null;
4392
4393                if ( fallback ) {
4394                    this.obj.html( fallback );
4395
4396                    setTimeout(function(){
4397                        self.modalCallback( self.obj.find('.gallery') );
4398                    }, 0);
4399                } else {
4400                    setTimeout(function(){
4401                        self.modalCallback( self.obj );
4402                    }, 0);
4403                }
4404
4405                return;
4406            }
4407
4408            $.when(
4409                this.getGalleryData()
4410            ).then(function (data) {
4411                self.opts.gallery = self.setImageIDs(data);
4412                self.configure();
4413            });
4414        },
4415
4416        configure: function () {
4417            var self = this;
4418
4419            if (this.opts.show_loading) {
4420                this.react = ReactDOM.render(
4421                    React.createElement(
4422                        PhotoMosaic.Layouts.React.loading,
4423                        { id : this._id }
4424                    ),
4425                    this.obj.get(0)
4426                );
4427            }
4428
4429            // if all items have defined w/h we don't need to
4430            // wait for them to load to do the mosaic math
4431            if (this.hasDims()) {
4432                this.opts.gallery = this.prepData(this.opts.gallery);
4433                this.render();
4434            } else {
4435                $.when(this.preloadify()).then(
4436                    $.proxy(this.preloadDone, this), // all loaded successfully
4437                    $.proxy(this.preloadDone, this), // not all loaded successfully
4438                    $.proxy(this.preloadProgress, this) // an item has finished, successful or not
4439                );
4440            }
4441        },
4442
4443        render: function () {
4444            var self = this;
4445            var layout_data = null;
4446            var view_model = null;
4447            var mosaic_data = {
4448                key : this._id,
4449                id : this._id,
4450                class_name : this.makeSpecialClasses(),
4451                center : this.opts.center
4452            };
4453
4454            // bail if we don't have any images (e.g. they all failed to load)
4455            if ( PhotoMosaic.ErrorChecks.initial(this.opts) ) { return; }
4456
4457            this.layout = new PhotoMosaic.Layouts[ this.opts.layout ]( this );
4458            layout_data = this.layout.getData( false );
4459
4460            view_model = $.extend({}, mosaic_data, layout_data);
4461
4462            if (layout_data == false) {
4463                this.react = null;
4464
4465                // update the mosaic on window.resize
4466                $(window)
4467                    .unbind('resize.photoMosaic' + self._id)
4468                    .bind('resize.photoMosaic' + self._id, function () {
4469                        self.refresh();
4470                    });
4471                return;
4472            }
4473
4474            this.react = ReactDOM.render(
4475                React.createElement(
4476                    PhotoMosaic.Layouts.React.mosaic,
4477                    view_model
4478                ), // the component to render
4479                this.obj.get(0), // the dom node container
4480                function () {  // the callback
4481                    // triggers lazyloading / imagesLoaded
4482                    self.loader = new PhotoMosaic.Loader(self.obj, self);
4483
4484                    // update the mosaic on window.resize
4485                    $(window)
4486                        .unbind('resize.photoMosaic' + self._id)
4487                        .bind('resize.photoMosaic' + self._id, function () {
4488                            self.refresh();
4489                        });
4490
4491                    // run the user's modal_ready_callback
4492                    self.modalCallback();
4493                }
4494            );
4495
4496            // logging
4497            if (this.opts.log_gallery_data) {
4498                PhotoMosaic.Utils.logGalleryData(this.opts.gallery);
4499            }
4500        },
4501
4502        preloadify: function () {
4503            var $images = $('<div>').attr({
4504                    'id': this.preload,
4505                    'class' : 'PM_preloadify'
4506                });
4507            var self = this;
4508
4509            $.each(this.opts.gallery, function (i) {
4510                var image_url = (this.thumb && this.thumb !== '') ? this.thumb : this.src;
4511                var $item = $('<img>')
4512                                .attr({ src : image_url })
4513                                .addClass(this.id);
4514
4515                $images.append($item);
4516            });
4517
4518            $('body').append($images);
4519
4520            return $images.imagesLoaded();
4521        },
4522
4523        preloadDone : function () {
4524            this.opts.gallery = this.prepData(this.opts.gallery, true);
4525            this.render();
4526        },
4527
4528        preloadProgress : function (instance, image) {
4529            var img = null;
4530            if (!image.isLoaded) {
4531                id = image.img.className;
4532
4533                for (i = 0; i < this.opts.gallery.length; i++) {
4534                    if (this.opts.gallery[i].id == id) {
4535                        this.opts.gallery.splice(i,1);
4536                    }
4537                }
4538
4539                PhotoMosaic.Utils.log.error("The following image failed to load and was skipped.\n" + image.img.src);
4540            }
4541        },
4542
4543        prepData: function (gallery, isPreload) {
4544            var self = this;
4545            var $preload = (isPreload) ? $('#' + this.preload) : null;
4546            var $img = null;
4547            var image = null;
4548            var image_url = '';
4549            var mem = {};
4550            var images = [];
4551
4552            $.each(gallery, function (i) {
4553                image = $.extend(true, {}, this); // jQuery deep copy
4554
4555                if (isPreload) {
4556                    $img = $img = $preload.find('.' + this.id);
4557                    mem = {
4558                        w : $img.width(),
4559                        h : $img.height()
4560                    };
4561                } else {
4562                    mem = {
4563                        w : parseInt(this.width),
4564                        h : parseInt(this.height)
4565                    };
4566                }
4567
4568                image.width = {
4569                    original: mem.w
4570                };
4571                image.height = {
4572                    original: mem.h
4573                };
4574
4575                image_url = (image.thumb && image.thumb !== '') ? image.thumb : image.src;
4576
4577                // image sizes
4578                image.full = image.src;
4579                image.src = image_url;
4580                image.padding = self.opts.padding;
4581
4582                image.caption = PhotoMosaic.Utils.decodeHTML( image.caption );
4583
4584                image = self.prepDataModal( image );
4585
4586                image = self.prepDataLinks( image );
4587
4588                images.push(image);
4589            });
4590
4591            return images;
4592        },
4593
4594        prepDataModal: function (image) {
4595            var modal_text = '';
4596
4597            if (this.opts.modal_name) {
4598                if (this.opts.modal_group) {
4599                    if (this.opts.modal_hash) {
4600                        modal_text = this.opts.modal_name + '[' + this.opts.modal_hash + ']';
4601                    } else {
4602                        modal_text = this.opts.modal_name + '[' + this._id + ']';
4603                    }
4604                } else {
4605                    modal_text = this.opts.modal_name;
4606                }
4607                image.modal = modal_text;
4608            }
4609
4610            return image;
4611        },
4612
4613        prepDataLinks: function (image) {
4614            if (this.opts.links && image.url) {
4615                image.link = true;
4616                image.path = image.url;
4617                image.external = this.opts.external_links;
4618                // delete image.modal;
4619            } else if (this.opts.links) {
4620                image.link = true;
4621                image.external = this.opts.external_links;
4622
4623                if (image.sizes && image.sizes.hasOwnProperty(this.opts.lightbox_rendition)) {
4624                    image.path = image.sizes[this.opts.lightbox_rendition];
4625                } else {
4626                    image.path = image.full;
4627                }
4628            } else {
4629                image.link = false;
4630            }
4631
4632            return image;
4633        },
4634
4635        getGalleryData: function () {
4636            var self = this;
4637
4638            // construct the gallery
4639            if (this.opts.input === 'json') {
4640                return PhotoMosaic.Inputs.json(this.opts.gallery);
4641
4642            } else if (this.opts.input === 'html') {
4643                return PhotoMosaic.Inputs.html(this.obj, this.opts);
4644
4645            } else if (this.opts.input === 'xml' ) {
4646                return $.when(
4647                        $.get(this.opts.gallery)
4648                    ).then(
4649                        // success
4650                        function (data) {
4651                            var gallery;
4652                            if ($(data).find('photos').length > 0) {
4653                                return PhotoMosaic.Inputs.xml( $(data).find('photos') );
4654                            } else {
4655                                PhotoMosaic.Utils.log.error("The XML doesn't contain any <photo> nodes.");
4656                                return;
4657                            }
4658                        },
4659                        // fail
4660                        function () {
4661                            PhotoMosaic.Utils.log.error("The XML either couldn't be found or was malformed.");
4662                            return;
4663                        }
4664                    );
4665            }
4666        },
4667
4668        setImageIDs: function (gallery) {
4669            for (var i = 0; i < gallery.length; i++) {
4670                gallery[i].key = gallery[i].id = PhotoMosaic.Utils.makeID(false, 'pm');
4671            };
4672            return gallery;
4673        },
4674
4675        hasDims: function () {
4676            var some = false; // set to true if any dims are found
4677            var all = true; // set to false if any dims aren't found
4678
4679            if (this.hasSpecifiedDims !== undefined) {
4680                return this.hasSpecifiedDims;
4681            }
4682
4683            for (var i = 0; i < this.opts.gallery.length; i++) {
4684                // are w/h properties present?
4685                if (this.opts.gallery[i].hasOwnProperty('width') && this.opts.gallery[i].hasOwnProperty('height')) {
4686                    // is there valid data?
4687                    // in some cases WP reports 0 for both the height and width
4688                    if (
4689                        isNaN(parseInt(this.opts.gallery[i].width)) ||
4690                        isNaN(parseInt(this.opts.gallery[i].height)) ||
4691                        this.opts.gallery[i].width == 0 ||
4692                        this.opts.gallery[i].height == 0
4693                    ) {
4694                        all = false;
4695                    } else {
4696                        some = true;
4697                    }
4698                } else {
4699                    all = false;
4700                }
4701            };
4702
4703            if (some && !all) {
4704                PhotoMosaic.Utils.log.error("Width / Height data not present for all images.");
4705            }
4706
4707            this.hasSpecifiedDims = all;
4708
4709            return this.hasSpecifiedDims;
4710        },
4711
4712        getLoadingTransition: function () {
4713            var transition = 'none';
4714
4715            if (PhotoMosaic.Plugins.Modernizr.csstransitions && PhotoMosaic.Plugins.Modernizr.csstransforms && this.opts.loading_transition !== false) {
4716                transition = this.opts.loading_transition
4717            }
4718            return 'loading-transition-' + transition;
4719        },
4720
4721        getResizeTransition : function () {
4722            return (this.opts.resize_transition) ? '' : 'resize-transition-none';
4723        },
4724
4725        getLayoutClass: function () {
4726            return 'layout-' + this.opts.layout + ((this.opts.layout == 'grid') ? '-' + this.opts.sizing : '');
4727        },
4728
4729        getUniqueClass: function () {
4730            return (this.opts.modal_hash) ? 'photomosaic-' + this.opts.modal_hash : '';
4731        },
4732
4733        getAvia: function () {
4734            // Kriesi's Avia framework overwrites the 'left' prop on my links
4735            // 'noLightbox' prevents that shit from happening
4736            return (typeof window.avia_framework_globals !== 'undefined') ? 'noLightbox' : '';
4737        },
4738
4739        makeSpecialClasses: function () {
4740            var classes = [
4741                this.getUniqueClass(),
4742                this.getLoadingTransition(),
4743                this.getResizeTransition(),
4744                this.getLayoutClass(),
4745                this.getAvia()
4746            ];
4747            return classes.join(' ');
4748        },
4749
4750        refresh: function () {
4751            var self = this;
4752            var mosaic_data = {
4753                key : this._id,
4754                id : this._id,
4755                class_name : this.makeSpecialClasses(),
4756                center : this.opts.center
4757            };
4758
4759            var layout_data = this.layout.getData( true );
4760
4761            var view_model = $.extend({}, mosaic_data, layout_data);
4762
4763            // transitionend fires for each proprty being transitioned, we only care about when the last one ends
4764            var checkLazyload = PhotoMosaic.Utils.debounce(function () {
4765                $.waypoints('refresh');
4766            }, 300);
4767
4768            if (layout_data == false) {
4769                this.react = null;
4770                return;
4771            }
4772
4773            this.react = ReactDOM.render(
4774                React.createElement(
4775                    PhotoMosaic.Layouts.React.mosaic,
4776                    view_model
4777                ),
4778                this.obj.get(0),
4779                function () {
4780                    // if applicable, wait until after the CSS transitions fire to trigger a lazyloading check
4781                    if (PhotoMosaic.Plugins.Modernizr.csstransitions && self.opts.lazyload !== false) {
4782                        self.obj.on(
4783                            self._transition_end_event_name,
4784                            checkLazyload
4785                        );
4786                    }
4787                }
4788            );
4789        },
4790
4791        update: function (props) {
4792            if ('object' != typeof(props)) {
4793                PhotoMosaic.Utils.log.error("The 'update' method accepts an object of parameters to be updated.");
4794                return false;
4795            }
4796
4797            // make sure numbers are numbers
4798            var exclusions = ['width', 'height', 'shape', 'modal_hash'];
4799            for (var k in props) {
4800                if (($.inArray(k, exclusions) == -1) && !isNaN(parseFloat(props[k]))) {
4801                    props[k] = parseFloat(props[k]);
4802                }
4803            };
4804
4805            this.opts = $.extend({}, this.opts, props);
4806
4807            var self = this;
4808            var gallery = [];
4809            $.each(this.opts.gallery, function (idx, image) {
4810                gallery.push( self.prepDataModal(image) );
4811            });
4812
4813            this.opts.gallery = gallery;
4814
4815            this.layout = new PhotoMosaic.Layouts[ this.opts.layout ]( this );
4816
4817            this.refresh();
4818        },
4819
4820        modalCallback: function ($node) {
4821            var $ = $ || this.$;
4822            var $node = $node || this.obj.children().eq(0);
4823            var $items = $node.children();
4824            var lightbox_callback = PhotoMosaic.WP[ this.opts.id ].lightbox_callback;
4825
4826            if ( $.isFunction(this.opts.modal_ready_callback) ) {
4827                this.opts.modal_ready_callback.apply(this, [$, $node, $items]);
4828            }
4829
4830            if ( lightbox_callback && $.isFunction( lightbox_callback ) ) {
4831                lightbox_callback.apply(this, [$, $node, $items]);
4832            }
4833        },
4834
4835        adjustDeprecatedOptions: function (opts) {
4836            // random : true | false
4837            if (opts.random) {
4838                opts.order = 'random';
4839            }
4840
4841            // force_order : true | false
4842            if (opts.force_order) {
4843                opts.order = 'columns';
4844            }
4845
4846            // responsive_transition renamed to resize_transition
4847            if (opts.hasOwnProperty('responsive_transition')) {
4848                opts.resize_transition = opts.responsive_transition;
4849            }
4850            if (opts.hasOwnProperty('responsive_transition_settings')) {
4851                opts.resize_transition_settings = opts.responsive_transition_settings;
4852            }
4853
4854            return opts;
4855        },
4856
4857        _name : pluginName,
4858
4859        version : PhotoMosaic.version
4860    };
4861
4862
4863    $.fn[pluginName] = function (options) {
4864        return this.each(function () {
4865            var instance = $.data(this, pluginName);
4866
4867            if ( instance && options ) {
4868                instance.update(options);
4869            } else if ( instance ) {
4870                instance.refresh();
4871            } else {
4872                options = options || {};
4873                instance = new photoMosaic(this, options);
4874            }
4875        });
4876    };
4877
4878    $(document).on('ready', function () {
4879        PhotoMosaic.refreshPage();
4880    });
4881
4882}(window.JQPM, window));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.